//////////////////////////////////////////////////////////////////////////////// // // 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 overrides property of the State class. * * @mxml * *

The <mx:SetProperty> tag * has the following attributes:

* *
 *  <mx:SetProperty
 *   Properties
 *   name="null"
 *   target="null"
 *   value="undefined"
 *  />
 *  
* * @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 null, 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; } } }