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