Upgrade to ExtJS 4.0.0 - Released 04/26/2011
[extjs.git] / src / state / Stateful.js
1 /**
2  * @class Ext.state.Stateful
3  * A mixin for being able to save the state of an object to an underlying 
4  * {@link Ext.state.Provider}.
5  */
6 Ext.define('Ext.state.Stateful', {
7     
8     /* Begin Definitions */
9    
10    mixins: {
11         observable: 'Ext.util.Observable'
12     },
13     
14     requires: ['Ext.state.Manager'],
15     
16     /* End Definitions */
17     
18     /**
19      * @cfg {Boolean} stateful
20      * <p>A flag which causes the object to attempt to restore the state of
21      * internal properties from a saved state on startup. The object must have
22      * a <code>{@link #stateId}</code> for state to be managed. 
23      * Auto-generated ids are not guaranteed to be stable across page loads and 
24      * cannot be relied upon to save and restore the same state for a object.<p>
25      * <p>For state saving to work, the state manager's provider must have been
26      * set to an implementation of {@link Ext.state.Provider} which overrides the
27      * {@link Ext.state.Provider#set set} and {@link Ext.state.Provider#get get}
28      * methods to save and recall name/value pairs. A built-in implementation,
29      * {@link Ext.state.CookieProvider} is available.</p>
30      * <p>To set the state provider for the current page:</p>
31      * <pre><code>
32 Ext.state.Manager.setProvider(new Ext.state.CookieProvider({
33     expires: new Date(new Date().getTime()+(1000*60*60*24*7)), //7 days from now
34 }));
35      * </code></pre>
36      * <p>A stateful object attempts to save state when one of the events
37      * listed in the <code>{@link #stateEvents}</code> configuration fires.</p>
38      * <p>To save state, a stateful object first serializes its state by
39      * calling <b><code>{@link #getState}</code></b>. By default, this function does
40      * nothing. The developer must provide an implementation which returns an
41      * object hash which represents the restorable state of the object.</p>
42      * <p>The value yielded by getState is passed to {@link Ext.state.Manager#set}
43      * which uses the configured {@link Ext.state.Provider} to save the object
44      * keyed by the <code>{@link stateId}</code></p>.
45      * <p>During construction, a stateful object attempts to <i>restore</i>
46      * its state by calling {@link Ext.state.Manager#get} passing the
47      * <code>{@link #stateId}</code></p>
48      * <p>The resulting object is passed to <b><code>{@link #applyState}</code></b>.
49      * The default implementation of <code>{@link #applyState}</code> simply copies
50      * properties into the object, but a developer may override this to support
51      * more behaviour.</p>
52      * <p>You can perform extra processing on state save and restore by attaching
53      * handlers to the {@link #beforestaterestore}, {@link #staterestore},
54      * {@link #beforestatesave} and {@link #statesave} events.</p>
55      */
56     stateful: true,
57     
58     /**
59      * @cfg {String} stateId
60      * The unique id for this object to use for state management purposes.
61      * <p>See {@link #stateful} for an explanation of saving and restoring state.</p>
62      */
63     
64     /**
65      * @cfg {Array} stateEvents
66      * <p>An array of events that, when fired, should trigger this object to
67      * save its state (defaults to none). <code>stateEvents</code> may be any type
68      * of event supported by this object, including browser or custom events
69      * (e.g., <tt>['click', 'customerchange']</tt>).</p>
70      * <p>See <code>{@link #stateful}</code> for an explanation of saving and
71      * restoring object state.</p>
72      */
73     
74     /**
75      * @cfg {Number} saveBuffer A buffer to be applied if many state events are fired within
76      * a short period. Defaults to 100.
77      */
78     saveDelay: 100,
79     
80     autoGenIdRe: /^((\w+-)|(ext-comp-))\d{4,}$/i,
81     
82     constructor: function(config) {
83         var me = this;
84         
85         config = config || {};
86         if (Ext.isDefined(config.stateful)) {
87             me.stateful = config.stateful;
88         }
89         if (Ext.isDefined(config.saveDelay)) {
90             me.saveDelay = config.saveDelay;
91         }
92         me.stateId = config.stateId;
93         
94         if (!me.stateEvents) {
95             me.stateEvents = [];
96         }
97         if (config.stateEvents) {
98             me.stateEvents.concat(config.stateEvents);
99         }
100         this.addEvents(
101             /**
102              * @event beforestaterestore
103              * Fires before the state of the object is restored. Return false from an event handler to stop the restore.
104              * @param {Ext.state.Stateful} this
105              * @param {Object} state The hash of state values returned from the StateProvider. If this
106              * event is not vetoed, then the state object is passed to <b><tt>applyState</tt></b>. By default,
107              * that simply copies property values into this object. The method maybe overriden to
108              * provide custom state restoration.
109              */
110             'beforestaterestore',
111             
112             /**
113              * @event staterestore
114              * Fires after the state of the object is restored.
115              * @param {Ext.state.Stateful} this
116              * @param {Object} state The hash of state values returned from the StateProvider. This is passed
117              * to <b><tt>applyState</tt></b>. By default, that simply copies property values into this
118              * object. The method maybe overriden to provide custom state restoration.
119              */
120             'staterestore',
121             
122             /**
123              * @event beforestatesave
124              * Fires before the state of the object is saved to the configured state provider. Return false to stop the save.
125              * @param {Ext.state.Stateful} this
126              * @param {Object} state The hash of state values. This is determined by calling
127              * <b><tt>getState()</tt></b> on the object. This method must be provided by the
128              * developer to return whetever representation of state is required, by default, Ext.state.Stateful
129              * has a null implementation.
130              */
131             'beforestatesave',
132             
133             /**
134              * @event statesave
135              * Fires after the state of the object is saved to the configured state provider.
136              * @param {Ext.state.Stateful} this
137              * @param {Object} state The hash of state values. This is determined by calling
138              * <b><tt>getState()</tt></b> on the object. This method must be provided by the
139              * developer to return whetever representation of state is required, by default, Ext.state.Stateful
140              * has a null implementation.
141              */
142             'statesave'
143         );
144         me.mixins.observable.constructor.call(me);
145         if (me.stateful !== false) {
146             me.initStateEvents();
147             me.initState();
148         }
149     },
150     
151     /**
152      * Initializes any state events for this object.
153      * @private
154      */
155     initStateEvents: function() {
156         this.addStateEvents(this.stateEvents);
157     },
158     
159     /**
160      * Add events that will trigger the state to be saved.
161      * @param {String/Array} events The event name or an array of event names.
162      */
163     addStateEvents: function(events){
164         if (!Ext.isArray(events)) {
165             events = [events];
166         }
167         
168         var me = this,
169             i = 0,
170             len = events.length;
171             
172         for (; i < len; ++i) {
173             me.on(events[i], me.onStateChange, me);
174         }
175     },
176     
177     /**
178      * This method is called when any of the {@link #stateEvents} are fired.
179      * @private
180      */
181     onStateChange: function(){
182         var me = this,
183             delay = me.saveDelay;
184         
185         if (delay > 0) {
186             if (!me.stateTask) {
187                 me.stateTask = Ext.create('Ext.util.DelayedTask', me.saveState, me);
188             }
189             me.stateTask.delay(me.saveDelay);
190         } else {
191             me.saveState();
192         }
193     },
194     
195     /**
196      * Saves the state of the object to the persistence store.
197      * @private
198      */
199     saveState: function() {
200         var me = this,
201             id,
202             state;
203         
204         if (me.stateful !== false) {
205             id = me.getStateId();
206             if (id) {
207                 state = me.getState();
208                 if (me.fireEvent('beforestatesave', me, state) !== false) {
209                     Ext.state.Manager.set(id, state);
210                     me.fireEvent('statesave', me, state);
211                 }
212             }
213         }
214     },
215     
216     /**
217      * Gets the current state of the object. By default this function returns null,
218      * it should be overridden in subclasses to implement methods for getting the state.
219      * @return {Object} The current state
220      */
221     getState: function(){
222         return null;    
223     },
224     
225     /**
226      * Applies the state to the object. This should be overridden in subclasses to do
227      * more complex state operations. By default it applies the state properties onto
228      * the current object.
229      * @param {Object} state The state
230      */
231     applyState: function(state) {
232         if (state) {
233             Ext.apply(this, state);
234         }
235     },
236     
237     /**
238      * Gets the state id for this object.
239      * @return {String} The state id, null if not found.
240      */
241     getStateId: function() {
242         var me = this,
243             id = me.stateId;
244         
245         if (!id) {
246             id = me.autoGenIdRe.test(String(me.id)) ? null : me.id;
247         }
248         return id;
249     },
250     
251     /**
252      * Initializes the state of the object upon construction.
253      * @private
254      */
255     initState: function(){
256         var me = this,
257             id = me.getStateId(),
258             state;
259             
260         if (me.stateful !== false) {
261             if (id) {
262                 state = Ext.state.Manager.get(id);
263                 if (state) {
264                     state = Ext.apply({}, state);
265                     if (me.fireEvent('beforestaterestore', me, state) !== false) {
266                         me.applyState(state);
267                         me.fireEvent('staterestore', me, state);
268                     }
269                 }
270             }
271         }
272     },
273     
274     /**
275      * Destroys this stateful object.
276      */
277     destroy: function(){
278         var task = this.stateTask;
279         if (task) {
280             task.cancel();
281         }
282         this.clearListeners();
283         
284     }
285     
286 });