Upgrade to ExtJS 3.2.0 - Released 03/30/2010
[extjs.git] / src / util / History.js
1 /*!
2  * Ext JS Library 3.2.0
3  * Copyright(c) 2006-2010 Ext JS, Inc.
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 /**
8  * @class Ext.History
9  * @extends Ext.util.Observable
10  * History management component that allows you to register arbitrary tokens that signify application
11  * history state on navigation actions.  You can then handle the history {@link #change} event in order
12  * to reset your application UI to the appropriate state when the user navigates forward or backward through
13  * the browser history stack.
14  * @singleton
15  */
16 Ext.History = (function () {
17     var iframe, hiddenField;
18     var ready = false;
19     var currentToken;
20
21     function getHash() {
22         var href = top.location.href, i = href.indexOf("#");
23         return i >= 0 ? href.substr(i + 1) : null;
24     }
25
26     function doSave() {
27         hiddenField.value = currentToken;
28     }
29
30     function handleStateChange(token) {
31         currentToken = token;
32         Ext.History.fireEvent('change', token);
33     }
34
35     function updateIFrame (token) {
36         var html = ['<html><body><div id="state">',Ext.util.Format.htmlEncode(token),'</div></body></html>'].join('');
37         try {
38             var doc = iframe.contentWindow.document;
39             doc.open();
40             doc.write(html);
41             doc.close();
42             return true;
43         } catch (e) {
44             return false;
45         }
46     }
47
48     function checkIFrame() {
49         if (!iframe.contentWindow || !iframe.contentWindow.document) {
50             setTimeout(checkIFrame, 10);
51             return;
52         }
53
54         var doc = iframe.contentWindow.document;
55         var elem = doc.getElementById("state");
56         var token = elem ? elem.innerText : null;
57
58         var hash = getHash();
59
60         setInterval(function () {
61
62             doc = iframe.contentWindow.document;
63             elem = doc.getElementById("state");
64
65             var newtoken = elem ? elem.innerText : null;
66
67             var newHash = getHash();
68
69             if (newtoken !== token) {
70                 token = newtoken;
71                 handleStateChange(token);
72                 top.location.hash = token;
73                 hash = token;
74                 doSave();
75             } else if (newHash !== hash) {
76                 hash = newHash;
77                 updateIFrame(newHash);
78             }
79
80         }, 50);
81
82         ready = true;
83
84         Ext.History.fireEvent('ready', Ext.History);
85     }
86
87     function startUp() {
88         currentToken = hiddenField.value ? hiddenField.value : getHash();
89
90         if (Ext.isIE) {
91             checkIFrame();
92         } else {
93             var hash = getHash();
94             setInterval(function () {
95                 var newHash = getHash();
96                 if (newHash !== hash) {
97                     hash = newHash;
98                     handleStateChange(hash);
99                     doSave();
100                 }
101             }, 50);
102             ready = true;
103             Ext.History.fireEvent('ready', Ext.History);
104         }
105     }
106
107     return {
108         /**
109          * The id of the hidden field required for storing the current history token.
110          * @type String
111          * @property
112          */
113         fieldId: 'x-history-field',
114         /**
115          * The id of the iframe required by IE to manage the history stack.
116          * @type String
117          * @property
118          */
119         iframeId: 'x-history-frame',
120
121         events:{},
122
123         /**
124          * Initialize the global History instance.
125          * @param {Boolean} onReady (optional) A callback function that will be called once the history
126          * component is fully initialized.
127          * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to the browser window.
128          */
129         init: function (onReady, scope) {
130             if(ready) {
131                 Ext.callback(onReady, scope, [this]);
132                 return;
133             }
134             if(!Ext.isReady){
135                 Ext.onReady(function(){
136                     Ext.History.init(onReady, scope);
137                 });
138                 return;
139             }
140             hiddenField = Ext.getDom(Ext.History.fieldId);
141             if (Ext.isIE) {
142                 iframe = Ext.getDom(Ext.History.iframeId);
143             }
144             this.addEvents(
145                 /**
146                  * @event ready
147                  * Fires when the Ext.History singleton has been initialized and is ready for use.
148                  * @param {Ext.History} The Ext.History singleton.
149                  */
150                 'ready',
151                 /**
152                  * @event change
153                  * Fires when navigation back or forwards within the local page's history occurs.
154                  * @param {String} token An identifier associated with the page state at that point in its history.
155                  */
156                 'change'
157             );
158             if(onReady){
159                 this.on('ready', onReady, scope, {single:true});
160             }
161             startUp();
162         },
163
164         /**
165          * Add a new token to the history stack. This can be any arbitrary value, although it would
166          * commonly be the concatenation of a component id and another id marking the specifc history
167          * state of that component.  Example usage:
168          * <pre><code>
169 // Handle tab changes on a TabPanel
170 tabPanel.on('tabchange', function(tabPanel, tab){
171     Ext.History.add(tabPanel.id + ':' + tab.id);
172 });
173 </code></pre>
174          * @param {String} token The value that defines a particular application-specific history state
175          * @param {Boolean} preventDuplicates When true, if the passed token matches the current token
176          * it will not save a new history step. Set to false if the same state can be saved more than once
177          * at the same history stack location (defaults to true).
178          */
179         add: function (token, preventDup) {
180             if(preventDup !== false){
181                 if(this.getToken() == token){
182                     return true;
183                 }
184             }
185             if (Ext.isIE) {
186                 return updateIFrame(token);
187             } else {
188                 top.location.hash = token;
189                 return true;
190             }
191         },
192
193         /**
194          * Programmatically steps back one step in browser history (equivalent to the user pressing the Back button).
195          */
196         back: function(){
197             history.go(-1);
198         },
199
200         /**
201          * Programmatically steps forward one step in browser history (equivalent to the user pressing the Forward button).
202          */
203         forward: function(){
204             history.go(1);
205         },
206
207         /**
208          * Retrieves the currently-active history token.
209          * @return {String} The token
210          */
211         getToken: function() {
212             return ready ? currentToken : getHash();
213         }
214     };
215 })();
216 Ext.apply(Ext.History, new Ext.util.Observable());