Problem Description
The value of a property is potentially different in a given state defined by the MXML document, depending on how the property is specified.
The rules of specifying properties in MXML today are:
- if the value is left unspecified, it defaults to whatever the object initializes it to. It can be changed at runtime via actionscript.
- if the value is specified with base value (with an attribute unscoped to any state, i.e., label="foo") is it initialized to that value. It can be changed at runtime via actionscript.
- if the value is specified with a state scope, it will be set to that value whenever the document enters that state. It can be changed at runtime via actionscript, but whenever the document enters that state, it will be reset to that value. When the document leaves that state, it will be reset to the value it was just before entering that state.
What this means is that the following three buttons:
<Group>
<states>
<State name="stateA" />
<State name="stateB" />
</states>
<Button id="b1" label.stateA="foo" />
<Button id="b1" label="" label.stateA="foo" />
<Button id="b1" label.stateB="" label.stateA="foo" />
</Group>
will potentially behave differently when the document enters stateB.
They will only behave differently if the value of label is modified at runtime via actionscript. For example, given the following script:
b1.label="bar";
b1.currentState = 'stateA';
b1.currentState = 'stateB';
the first and second button will show 'bar' as the label. The third button will show the empty string.
Now arguably, this behavior is a good thing. Flex and MXML are specifically focused on letting developers and designers marry the static nature of declarative, toolable UI with the dynamic nature of programmatic UI.
It becomes a problem, however, when it's the user changing values dynamically at runtime, and not the developer. For example, the following scenario has come up repeatedly when customers use tools to create MXML user interfaces:
<Group>
<states>
<State name="search" />
<State name="checkout" />
</states>
<ToggleButton label="Search" click='currentState="search"' selected.search="true" />
<ToggleButton label="Checkout" click='currentState="checkout"' selected.checkout="true" />
</Group>
In the example, the user wants the first button to switch to the search state and show itself as selected, and the second button to switch to checkout and show itself as selected.
But here's what happens: When the user clicks on the checkout button, it switches into the checkout state. In doing so, it first captures the current value of the checkout button (which is true, because you just clicked on it), then applys selected.checkout.
Now the user clicks on the search button. The document switches out of the checkout state, by reapplying the value it captured before entering it (selected=true). it then enters the search state. Since the checkout button has no defined value for the search state it leaves the current value alone, which is to leave it true. Which, looking at the document, is clearly not what the user intended.
Now if the checkout button was written as so:
<ToggleButton label="Checkout" click='currentState="checkout"'
selected='false' selected.checkout="true" />
The behavior would have remained the same. That's potentially surprising by looking at the markup, as the developer seems to be implying they want the default value in unspecified states to be selected=false.
Written like this:
<ToggleButton label="Checkout" click='currentState="checkout"'
selected.search='false' selected.checkout="true" />
It would behave as intended, resetting selected to false when entering the search state. Note that this is less maintianable, as the developer needs to explicitly specify a value for each state in the document, adding new values as the set of states grows.
So the problem is: what should the relationship between actionscript applied values and state applied values be, given the various ways a developer can specify a value in markup?
Decision
After discussion with the tooling teams, we are going with option 0: change nothing in the SDK. The tools will add support for the specific nav button scenario by explicitly writing out alternate values in the other states.
Decision Criteria
- The solution must be able to maintian backwards compatible beahvior in MXML 2006 documents, although it's OK to have different behavior in 2009 documents.
- THe solution should respect that mxml documents are dynamic living documents at runtime whose state is modified by actionscript. CHanges made in actionscript to properties unspecified in the document should persist.
- The solution should be easy to understand
- THe solution will ideally require the least amount of deviation from the current definition of states.
Proposed Solution(s):
Option 0: Change nothing
No matter what default behavior we choose for actionscript interacting with states, there will be valid use cases where some other behavior is expected, and our current proposal is as good as any. Instead, the tool should recognize when users are expecting states to behavior differently, and explicitly specify the values expected in all document states. Specifically, when setting the selected value on a toggle button in a particular state, the tool would write out selected=false for all other states.
pro: requires no change to the SDK. Keeps MXML consistent with previous behavior.
con: doesn't scale well in the tool.
Option 1: avoid the problem
When looking at markup, the behavior is relatively easy to understand. THe problem really only arises in tools, where the user can't see the difference between default values, base values not scoped to any state, and stateful values. Rather than making changes to states and MXML, instead we can identify the places that users are running into conflicts between actionscript and states and adjust the framework to eliminate them. Specifically,
- introduce a new flag on [ToggleButton] called autoToggle. When true, clicking the button will toggle its status. When false, it will only dispatch a click event.
- set the default autoToggle on [ToggleButton] to false. CHeckbox and [RadioButton] default it to true.
There are a few variations on this idea that are also being considered:
- push the selected flag and states on [ToggleButton] up to Button. Leave the autoToggle behavior on [ToggleButton]
- change the name of [ButtonBarButton] to [NavButton], promote it as a first class component for use in tooling and MXML, and remove the autoToggle behavior from it
pro: simple and straightforward, doesn't require changes to states, mxml, or tooling.
con: doesn't necessarily extend to all use cases - others might not be as easily avoidable. Arguable whether the default on [ToggleButton] should be false.
Option 2: auto-apply base values to all unspecified states
While we still want actionscript and states to interact as they did before, it doesn't make sense that:
<ToggleButton selected='false' selected.checkout='true' />
<ToggleButton selected.search='false' selected.checkout='true' />
have different behavior. So we should change that:
- If a property value is only specified as a base (unqualified) value in MXML, it behaves as before.
- if a property value is specified as a state qualified value, any base (unqualified) values specified in MXML for the property are expanded to be applied in all unspecified states.
- if a property value is specified as a state qualified value, and no base value is specified, the behavior remains the same as before.
- tools that want to explicitly define values in all states need only specify a base value for all states
Pro: still backwards compatible with 2006. scales with more states added to the document. Makes MXML feel more consistent
Con: eliminates the ability to specify an initial value an a stateful value, but have actionscript assigned values persist. Now there is a difference between using the default value of selected and explicitly setting the value to its default. selected='false' behaves differently depending on whether there is a stateful scoped value defined as well. requires extra data to be stored at runtime, and extra code run on state change to assign base values.
Option 3: auto-apply default whenever one can be determined
Today, any state that doesn't have an explicit value defined defaults to whatever value is set in actionscript. That makes the behavior of a state-set value unpredictable...sometimes it shows what's in the document, and sometimes it doesn't. We should move to a more consistent model - specifically, whenever you set a state specific value, you always get a state specific value in every state. If one isn't specified in the document, we determine the default value at compile time, and explicitly assign that in every state.
- if a property value is specified for a particular state, mxmlc will generate state values for all states in the document
- if a base unqualified value is specified, it will be used for all unspecified states
- if no base value is specified, mxmlc will look for 'default' metadata on the property definition specifying what the default value is. That value will be used for unspecified values
- if no default metadata is specified, mxmlc will throw an error.
pro: behavior of stateful properties is consistent regardless of how they are specified. tools don't need to generate any extra mxml.
con: eliminates the ability of states to interact with dynamic values. still no consistency between stateless and stateful properties in mxml. REquires decorating all the framework with default metadata. Problems arise when values are objects, or when defaults change in subclasses. errors when values are left unspecified and no metadata can be found will be annoying. More memory used to store values, and moore wok done on state changes.
Option 4: auto-determine initial value at runtime
This option is the same as option 3, except that if no value can be determined at compile time, it is captured once during initialization at runtime and used in all states that are unspecified.
- if a property value is specified for a particular state, mxmlc will generate state values for all states in the document
- if a base unqualified value is specified, it will be used for all unspecified states
- if no base value is specified, mxmlc will look for 'default' metadata on the property definition specifying what the default value is. That value will be used for unspecified values
- if no default metadata is specified, capture the value of the property at creation complete and use it in all states with no specified value.
pro: same as above, but no errors for unspecified values.
con: same as above, plus poorly defined concept of what the 'default' at runtime means unpredictable behavior.
Option 5: change states to never roll back values.
In this option, we fundamentally change the meaning of states. Rather than thinking of a state as a complete description of the document (absent actionscript changes) in a particular state, they simply become a set of values applied to the document when you go into the state. WHen you leave the state, the values are not unapplied...they are either overridden by other states, or simply left as is.
- change overrides to not capture or unapply values when leaving a state.
pro: simple, easy to understand. allows us to eliminate rollback code, reducing runtime memory and code overhead.
con: makes it difficult for tools or users to predict what a component will look like in a paritcular state. TOols will compensate by always writing out values for all properties in a state, erasing the performance benefits of the change, and making MXML more verbose.
Recommended Solution:
Our current reccomended solution is either option 1 or 2. 1 is preferrable, but needs more thought.
Notes:
| You must be logged in to comment. |
|
We should do #1. Leave states as they are.