| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // Licensed to the Apache Software Foundation (ASF) under one or more |
| // contributor license agreements. See the NOTICE file distributed with |
| // this work for additional information regarding copyright ownership. |
| // The ASF licenses this file to You under the Apache License, Version 2.0 |
| // (the "License"); you may not use this file except in compliance with |
| // the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| package mx.states |
| { |
| |
| import mx.core.FlexVersion; |
| import mx.core.IDeferredInstance; |
| import mx.core.UIComponent; |
| import mx.core.mx_internal; |
| import mx.utils.ObjectUtil; |
| |
| use namespace mx_internal; |
| |
| /** |
| * The SetProperty class specifies a property value that is in effect only |
| * during the parent view state. |
| * You use this class in the <code>overrides</code> property of the State class. |
| * |
| * @mxml |
| * |
| * <p>The <code><mx:SetProperty></code> tag |
| * has the following attributes:</p> |
| * |
| * <pre> |
| * <mx:SetProperty |
| * <b>Properties</b> |
| * name="null" |
| * target="null" |
| * value="undefined" |
| * /> |
| * </pre> |
| * |
| * @see mx.states.State |
| * @see mx.states.SetEventHandler |
| * @see mx.states.SetStyle |
| * @see mx.effects.SetPropertyAction |
| * |
| * @includeExample examples/StatesExample.mxml |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public class SetProperty extends OverrideBase |
| { |
| include "../core/Version.as"; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Class constants |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * This is a table of pseudonyms. |
| * Whenever the property being overridden is found in this table, |
| * the pseudonym is saved/restored instead. |
| */ |
| private static const PSEUDONYMS:Object = |
| { |
| width: "explicitWidth", |
| height: "explicitHeight", |
| currentState: "currentStateDeferred" |
| }; |
| |
| /** |
| * @private |
| * This is a table of related properties. |
| * Whenever the property being overridden is found in this table, |
| * the related property is also saved and restored. |
| */ |
| private static const RELATED_PROPERTIES:Object = |
| { |
| explicitWidth: [ "percentWidth" ], |
| explicitHeight: [ "percentHeight" ], |
| percentWidth: [ "explicitWidth" ], |
| percentHeight: [ "explicitHeight" ] |
| }; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Constructor |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Constructor. |
| * |
| * @param target The object whose property is being set. |
| * By default, Flex uses the immediate parent of the State object. |
| * |
| * @param name The property to set. |
| * |
| * @param value The value of the property in the view state. |
| * |
| * @param valueFactory An optional write-only property from which to obtain |
| * a shared value. This is primarily used when this override's value is |
| * shared by multiple states or state groups. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function SetProperty( |
| target:Object = null, |
| name:String = null, |
| value:* = undefined, |
| valueFactory:IDeferredInstance = null) |
| { |
| super(); |
| |
| this.target = target; |
| this.name = name; |
| this.value = value; |
| this.valueFactory = valueFactory; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Variables |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Storage for the old property value. |
| */ |
| private var oldValue:Object; |
| |
| /** |
| * @private |
| * Storage for the old related property values, if used. |
| */ |
| private var oldRelatedValues:Array; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Properties |
| // |
| //-------------------------------------------------------------------------- |
| |
| //---------------------------------- |
| // name |
| //---------------------------------- |
| |
| [Inspectable(category="General")] |
| |
| /** |
| * The name of the property to change. |
| * You must set this property, either in |
| * the SetProperty constructor or by setting |
| * the property value directly. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public var name:String; |
| |
| //---------------------------------- |
| // target |
| //---------------------------------- |
| |
| [Inspectable(category="General")] |
| |
| /** |
| * The object containing the property to be changed. |
| * If the property value is <code>null</code>, Flex uses the |
| * immediate parent of the State object. |
| * |
| * @default null |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public var target:Object; |
| |
| /** |
| * The cached target for which we applied our override. |
| * We keep track of the applied target while applied since |
| * our target may be swapped out in the owning document and |
| * we want to make sure we roll back the correct (original) |
| * element. |
| * |
| * @private |
| */ |
| private var appliedTarget:Object; |
| |
| //---------------------------------- |
| // value |
| //---------------------------------- |
| |
| [Inspectable(category="General")] |
| |
| /** |
| * @private |
| * Storage for the value property. |
| */ |
| public var _value:*; |
| |
| /** |
| * The new value for the property. |
| * |
| * @default undefined |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function get value():* |
| { |
| return _value; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set value(val:*):void |
| { |
| _value = val; |
| |
| // Reapply if necessary. |
| if (applied) |
| { |
| apply(parentContext); |
| } |
| } |
| |
| //---------------------------------- |
| // valueFactory |
| //---------------------------------- |
| |
| /** |
| * An optional write-only property from which to obtain a shared value. This |
| * is primarily used when this override's value is shared by multiple states |
| * or state groups. |
| * |
| * @default undefined |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 4 |
| */ |
| public function set valueFactory(factory:IDeferredInstance):void |
| { |
| // We instantiate immediately in order to retain the instantiation |
| // behavior of a typical (unshared) value. We may later enhance to |
| // allow for deferred instantiation. |
| if (factory) |
| value = factory.getInstance(); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Methods: IOverride |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Utility function to return the pseudonym of the property |
| * name if it exists on the object |
| */ |
| private function getPseudonym(obj:*, name:String):String |
| { |
| var propName:String; |
| if (FlexVersion.compatibilityVersion < FlexVersion.VERSION_4_0) |
| return (PSEUDONYMS[name] in obj) ? |
| PSEUDONYMS[name] : |
| name; |
| propName = PSEUDONYMS[name]; |
| if (!(propName in obj)) |
| { |
| if (ObjectUtil.isDynamicObject(obj)) |
| { |
| // If this is a dynamic object we fall back immediately |
| // to our original property name. |
| propName = name; |
| } |
| else |
| { |
| // 'in' does not work for mx_internal properties |
| // like currentStateDeferred |
| try |
| { |
| // Check if we can access the property; if it doesn't |
| // exist, it'll throw a ReferenceError |
| var tmp:* = obj[propName]; |
| } |
| catch (e:ReferenceError) |
| { |
| propName = name; |
| } |
| } |
| } |
| return propName; |
| } |
| |
| /** |
| * @inheritDoc |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| override public function apply(parent:UIComponent):void |
| { |
| parentContext = parent; |
| var obj:* = getOverrideContext(target, parent); |
| if (obj != null) |
| { |
| appliedTarget = obj; |
| var propName:String = PSEUDONYMS[name] ? getPseudonym(obj, name) : name; |
| |
| var relatedProps:Array = RELATED_PROPERTIES[propName] ? |
| RELATED_PROPERTIES[propName] : |
| null; |
| |
| var newValue:* = value; |
| |
| // Remember the original value so it can be restored later |
| // after we are asked to remove our override (and only if we |
| // aren't being asked to re-apply a value). |
| if (!applied) |
| { |
| oldValue = obj[propName]; |
| } |
| |
| if (relatedProps) |
| { |
| oldRelatedValues = []; |
| |
| for (var i:int = 0; i < relatedProps.length; i++) |
| oldRelatedValues[i] = obj[relatedProps[i]]; |
| } |
| |
| // Special case for width and height. If they are percentage values, |
| // set the percentWidth/percentHeight instead. |
| if (name == "width" || name == "height") |
| { |
| if (newValue is String && newValue.indexOf("%") >= 0) |
| { |
| propName = name == "width" ? "percentWidth" : "percentHeight"; |
| newValue = newValue.slice(0, newValue.indexOf("%")); |
| } |
| else |
| { |
| // Need to set width/height instead of explicitWidth/explicitHeight |
| // otherwise width/height are out of sync until the target is validated. |
| propName = name; |
| } |
| } |
| |
| // Set new value |
| setPropertyValue(obj, propName, newValue, oldValue); |
| |
| // Disable bindings for the base property if appropriate. If the binding |
| // fires while our override is applied, the correct value will automatically |
| // be applied when the binding is later enabled. |
| enableBindings(obj, parent, propName, false); |
| } |
| else if (!applied) |
| { |
| // Our target context is unavailable so we attempt to register |
| // a listener on our parent document to detect when/if it becomes |
| // valid. |
| addContextListener(target); |
| } |
| |
| // Save state in case our value or target is changed while applied. This |
| // can occur when our value property is databound or when a target is |
| // deferred instantiated. |
| applied = true; |
| } |
| |
| /** |
| * @inheritDoc |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| override public function remove(parent:UIComponent):void |
| { |
| var obj:* = getOverrideContext(appliedTarget, parent); |
| if (obj != null && appliedTarget) |
| { |
| var propName:String = PSEUDONYMS[name] ? getPseudonym(obj, name) : name; |
| |
| var relatedProps:Array = RELATED_PROPERTIES[propName] ? |
| RELATED_PROPERTIES[propName] : |
| null; |
| |
| // Special case for width and height. Restore the "width" and |
| // "height" properties instead of explicitWidth/explicitHeight |
| // so they can be kept in sync. |
| if ((name == "width" || name == "height") && !isNaN(Number(oldValue))) |
| { |
| propName = name; |
| } |
| |
| // Restore the old value |
| setPropertyValue(obj, propName, oldValue, oldValue); |
| |
| // Re-enable bindings for the base property if appropriate. If the binding |
| // fired while our override was applied, the current value will automatically |
| // be applied once enabled. |
| enableBindings(obj, parent, propName); |
| |
| // Restore related value, if needed |
| if (relatedProps) |
| { |
| for (var i:int = 0; i < relatedProps.length; i++) |
| { |
| setPropertyValue(obj, relatedProps[i], |
| oldRelatedValues[i], oldRelatedValues[i]); |
| } |
| } |
| } |
| else |
| { |
| // It seems our override is no longer active, but we were never |
| // able to successfully apply ourselves, so remove our context |
| // listener if applicable. |
| removeContextListener(); |
| } |
| |
| // Clear our flags and override context. |
| applied = false; |
| parentContext = null; |
| appliedTarget = null; |
| } |
| |
| /** |
| * @private |
| * Sets the property to a value, coercing if necessary. |
| */ |
| private function setPropertyValue(obj:Object, name:String, value:*, |
| valueForType:Object):void |
| { |
| // special-case undefined and null: we don't want to cast it |
| // to some special type and lose that information |
| if (value === undefined || value === null) |
| obj[name] = value; |
| else if (valueForType is Number) |
| obj[name] = Number(value); |
| else if (valueForType is Boolean) |
| obj[name] = toBoolean(value); |
| else |
| obj[name] = value; |
| } |
| |
| /** |
| * @private |
| * Converts a value to a Boolean true/false. |
| */ |
| private function toBoolean(value:Object):Boolean |
| { |
| if (value is String) |
| return value.toLowerCase() == "true"; |
| |
| return value != false; |
| } |
| } |
| |
| } |