See live demo at http://jsfiddle.net/donabrams/txpdk/
SHORT DESCR:
This is an html embeddable widget that changes state depending on actions done
to a widget.
LONG DESCRIPTION:
At the api level, there are stores, states, and the widget:
States:
- Determine what the next state will be asynchronously (synchronous wrapper
provided).
- Can be loaded. By default, this will go through a function stack (either
strings assumed to be this-local function name or actually functions) and
wrap them so they are called asynchronously in turn.
Store:
- Can be used to asynchronously get/put data.
Widget can:
- Locally get/save data synchronously (stored in the local instance)
- Use a store to asynchronously get/put data.
- Do actions, which transition a state or not. By default, while the action
is being processed, no further actions can be processed. Using the
queueAfterIfChangingState flag can allow actions to be chained but does
not stop the current state from loading.
- Save the current state.
At the implementation level, I have many conventions, all of which can be
overridden:
States
- Default function stack is: delayActionSupport, init, loadTemplate,
decorateTemplate, onTemplateLoad. By default init, decorateTemplate and
onTemplateLoad are blank wrappers around the synchronous _init,
_decorateTemplate and _onTemplateLoad functions respectively (if defined).
- loadTemplate uses the data stored on the widget to decorate a jquery
template wrapper ($.udel.template) stored in the widget keyed by
this.templateName asynchronously. It also respects the flag
this.keepPreviousTemplate which by default is false.
- decorateTemplate decorates all nodes with the class stateWidgetAction to
bind to click to doAction (node.attr('action'). This is useful for buttons
since the widget is usually inaccessible from the view.
- getNextState returns a state provided by the widget keyed by the
stateName (keyed by the action in this.action). The reason for this double
keying is twofold: limiting what states can be transitioned to and allowing
the state selection to be programmatically determined (synchronous only)
- Use delayAction rather than use a setTimeout if you want to delay an action.
This also can be used for autosaves and such. If the action is changed before
the delay, the delayedAction will not execute (providing the next state uses
delayedActionSupport).
Store:
- preformatter and postformatter used to massage data if provided
- Choice of ajax method (POST by default)
Widget:
- Intializes stores, templates, and states found in the options and sets them
on this.
- For stores and templates, assumes if only a string is provided it is a url.
- For states, assumes if only a string is provided, it is the templateName
(and additional assumes it is a final state with no transitions).
- Has storeBaseUrl and templateBaseUrl options (http urls ignore these).
- Binds saveState to document.onUnload.
TODO:
- Add an event in addition to the this.changingState flag.
Techniques/:
- If you want to add to initial data from input fields wrapped by a
stateWidget during init, there is a useful js at
https://github.com/donabrams/jQuery-Form---Object-Transforms.
Include that script and call $(widget).fieldsToObj() in init and you can do
whatever you want with the resulting map.
- State objects are reused. Do not use 'this' to store mutable data in a
state. If you need to keep state between states, use widget.[get/save]Data()
- If you want to change the state during a state load, be sure to use the
queueAfterIfChangingState flag!
LICENSE:
Although this project's source is open (since it is javascript after all), all
of its content is copyright of University of Delaware and we have no policy on
what license we can release things under without a lot of calling around. If
you really want a license to use this, I'll try and help, but it will likely be
low priority-- so good luck. I'd personally recommend extracting the principles
from this code and doing something similar.
Donald Abrams
[email protected]
Programmer/Analyst
University of Delaware