We had some frustrations all the times, on animating a window correctly.
Recently I try to dedicate on solving the puzzle.
But at first I need to apologize for my poor knowledge and thought about css transitions using class names.
In the past I usually thought it was stupid to use class list to control the style.
I thought it was ambiguous and strange if we ran into a situation that there're more than one classes of same type put on one element. And an element full of different class names for different purposes may conflict. Recently I see Effeckt and know this is the trend: a simple class name stands for a kind of animation.
We know, that, in certain moment, the app window is always in a specific state A, not B nor C. So what's the problem? It's how could we switch the state correctly.
So far it doesn't take times for us to figure out that a window should have 4 basic transition state: closed, opening, opened, closing.
A basic transition life cycle of a window could be:
Now we have 4 states, the next is what's the trigger to states switching?
In finite state machine, what triggers state change is named for 'event'.
We know that at least we have 2 events: 'open' and 'close'.
'open' would trigger closed switches to opening
'close' would trigger opened switches to closing
If we sends 'open' event into the state machine continously,
the second event would be ignored.
(Exactly it depends on what policy we choose. For example, we could devide 'opening' state into 'opening-part-1' and 'opening-part-2' states. And implement the async state change. But if we need s two-state opening, it sounds like a CSS design problem to me. Let's discuss this later if necessary.)
In order to gurantee the transition does really end and thus being independent from the 'animationend' event(The event here is HTML DOM Event), we need to add a timer between 'opening' and 'opened' state. Also between 'closing' and 'closed' state.
Let's call the new event 'timeout', the timing we set the timer is right after the transitioning from 'closed' to 'opening', and from 'opened' to 'closing' successfully occurs.
For some use case we may want to cancel the transition. The 'cancel' event is only valid in 'opening' and 'closing' state.
So far, the state machine for transition we have:
My anwser is put every transition relevant functions/attributes in another mixin object, which would be mixed into appWindow.prototype:
The state machine's usage and notes
A single app window instance would send 'open' and 'close' event due to user action:
Note, app window doesn't need to know current state of the state machine.
The state machine itself is the one who creates/removes the timer which triggers timeout event. I am also thinking about moving this out of the state machine and do this in another mixin, only have the callback functions provided by the state machine. But I am not sure.
The state machine has some callback for others(other state machine!) listed below:
Enter a state successfully.
Leave a state successfully.
When an event triggers state switch successfully.
In all these callback we would get the previous state and current state, and the event who triggers them.
The CSS class for real UI closing animation is added in _enterClosing and removed in _enterClosed. Or else we could do that in _leaveOpened and _leaveClosing. I have no strong opinion here.
Maybe we could define the level of the callback into three here, according to the callback order.
The CSS class for real UI opening animation is added in enterOpening and removed in enterOpened.
We could also move out (4) and (5) to another mixin to purify the state machine.
Finally, back to the problems addressed in Tim's article:
Do we need intermediate state?
I don't think so, at least for now. If we really need to goto next state when we successfully from state A to state B, we could call _processEvent again in the inner callback of the state machine. This doesn't violate the policy that only state machine ifself could decide its next state.
If the intermediate state needs to acquire other type of state -- just fetch the current state of the other state machine. Or, if we need, register an one-time callback if the current state doesn't meet our requirement.
How about state conflict between two apps?
That won't happen if we deal with the state changes correctly and independently in each app's scope. I hope so.