1 /*
  2  * Timemap.js Copyright 2010 Nick Rabinowitz.
  3  * Licensed under the MIT License (see LICENSE.txt)
  4  */
  5 
  6 /**
  7  * @fileOverview
  8  * Functions in this file are used to set the timemap state programmatically,
  9  * either in a script or from the url hash.
 10  *
 11  * @requires param.js
 12  *
 13  * @author Nick Rabinowitz (www.nickrabinowitz.com)
 14  */
 15  
 16 // save a few bytes
 17 (function() {
 18 
 19 /*----------------------------------------------------------------------------
 20  * State namespace, with setters, serializers, and url functions
 21  *---------------------------------------------------------------------------*/
 22 
 23 var paramNS = TimeMap.params,
 24 
 25     /**
 26      * @name TimeMap.state
 27      * @namespace Namespace for static state functions used to 
 28      * set the timemap state programmatically, either in a script or 
 29      * from the url hash.
 30      * @see <a href="../../examples/state.html#zoom=8¢er=44.04811573082351,13.29345703125&date=1500-01-21T12:17:37Z&selected=0">State Example</a>
 31      */
 32     stateNS = TimeMap.state = {
 33     
 34     /**
 35      * Get the state parameters from the URL, returning as a config object
 36      * 
 37      * @return {Object}             Object with state config settings
 38      */
 39     fromUrl: function() {
 40         var pairs = location.hash.substring(1).split('&'),
 41             params = stateNS.params,
 42             state = {}, x, pair, key;
 43         for (x=0; x < pairs.length; x++) {
 44             if (pairs[x] != "") {
 45                 pair = pairs[x].split('=');
 46                 key = pair[0];
 47                 if (key && key in params) {
 48                     state[key] = params[key].fromString(decodeURI(pair[1]));
 49                 }
 50             }
 51         }
 52         return state;
 53     },
 54     
 55     /**
 56      * Make a parameter string from a state object
 57      *
 58      * @param {Object} state        Object with state config settings
 59      * @return {String}             Parameter string in URL param format
 60      */
 61     toParamString: function(state) {
 62         var params = stateNS.params, 
 63             paramArray = [], 
 64             key;
 65         // go through each key in state
 66         for (key in state) {
 67             if (state.hasOwnProperty(key)) {
 68                 if (key in params) {
 69                     paramArray.push(key + "=" + encodeURI(params[key].toString(state[key])));
 70                 }
 71             }
 72         }
 73         return paramArray.join("&");
 74     },
 75     
 76     /**
 77      * Make a full URL from a state object
 78      *
 79      * @param {Object} state        Object with state config settings
 80      * @return {String}             Full URL with parameters
 81      */
 82     toUrl: function(state) {
 83         var paramString = stateNS.toParamString(state),
 84             url = location.href.split("#")[0];
 85         return url + "#" + paramString;
 86     },
 87     
 88     /**
 89      * Set state settings on a config object for TimeMap.init()
 90      * @see TimeMap.init
 91      *
 92      * @param {Object} config       Config object for TimeMap.init(), modified in place
 93      * @param {Object} state        Object with state config settings
 94      */
 95     setConfig: function(config, state) {
 96         var params = stateNS.params,
 97             key;
 98         for (key in state) {
 99             if (state.hasOwnProperty(key)) {
100                 if (key in params) {
101                     params[key].setConfig(config, state[key]);
102                 }
103             }
104         }
105     },
106     
107     /**
108      * Set state settings on a config object for TimeMap.init() using
109      * parameters in the URL. Note that as of Timemap.js v.1.6, this
110      * will run automatically if state functions are present.
111      * @see TimeMap.init
112      * @example
113  // set up the config object
114  var config = {
115     // various settings, as usual for TimeMap.init()
116  };
117  
118  // get state settings from the URL, e.g.:
119  // http://www.example.com/mytimemap.html#zoom=4&selected=1
120  TimeMap.state.setConfigFromUrl(config);
121  
122  // initialize TimeMap object
123  var tm = TimeMap.init(config);
124      *
125      * @param {Object} config       Config object for TimeMap.init()
126      */
127     setConfigFromUrl: function(config) {
128         stateNS.setConfig(config, stateNS.fromUrl());
129     }
130 
131 };
132 
133 /*----------------------------------------------------------------------------
134  * TimeMap object methods
135  *---------------------------------------------------------------------------*/
136 
137 /**
138  * Set the timemap state with a set of configuration options.
139  *
140  * @param {Object} state    Object with state config settings
141  */
142 TimeMap.prototype.setState = function(state) {
143     var params = stateNS.params,
144         key;
145     // go through each key in state
146     for (key in state) {
147         if (state.hasOwnProperty(key)) {
148             if (key in params) {
149                 // run setter function with config value
150                 params[key].set(this, state[key]);
151             }
152         }
153     }
154 };
155 
156 /**
157  * Get a configuration object of state variables
158  *
159  * @return {Object}     Object with state config settings
160  */
161 TimeMap.prototype.getState = function() {
162     var state = {},
163         params = stateNS.params,
164         key;
165     // run through params, adding values to state
166     for (key in params) {
167         if (params.hasOwnProperty(key)) {
168             // get state value
169             state[key] = params[key].get(this);
170         }
171     }
172     return state;
173 };
174 
175 /**
176  * Initialize state tracking based on URL. 
177  * Note: continuous tracking will only work
178  * on browsers that support the "onhashchange" event.
179  */
180 TimeMap.prototype.initState = function() {   
181     var tm = this;
182     tm.setStateFromUrl();
183     window.onhashchange = function() {
184         tm.setStateFromUrl();
185     };
186 };
187 
188 /**
189  * Set the timemap state with parameters in the URL
190  */
191 TimeMap.prototype.setStateFromUrl = function() {
192     this.setState(stateNS.fromUrl());
193 };
194 
195 /**
196  * Get current state parameters serialized as a hash string
197  *
198  * @return {String}     State parameters serialized as a hash string
199  */
200 TimeMap.prototype.getStateParamString = function() {
201     return stateNS.toParamString(this.getState());
202 };
203 
204 /**
205  * Get URL with current state parameters in hash
206  *
207  * @return {String}     URL with state parameters
208  */
209 TimeMap.prototype.getStateUrl = function() {
210     return stateNS.toUrl(this.getState());
211 };
212 
213 
214 /*----------------------------------------------------------------------------
215  * State parameters
216  *---------------------------------------------------------------------------*/
217 
218 /**
219  * @namespace
220  * Namespace for state parameters, each with a set of functions to set and serialize values.
221  * Add your own Param objects to this namespace to get and set additional state variables.
222  */
223 TimeMap.state.params = {
224         
225         /**
226          * Map zoom level
227          * @type TimeMap.params.Param
228          */
229         zoom: new paramNS.OptionParam("mapZoom", {
230             get: function(tm) {
231                 return tm.map.getZoom();
232             },
233             set: function(tm, value) {
234                 tm.map.setZoom(value);
235             },
236             fromStr: function(s) {
237                 return parseInt(s);
238             }
239         }),
240         
241         /**
242          * Map center
243          * @type TimeMap.params.Param
244          */
245         center: new paramNS.OptionParam("mapCenter", {
246             get: function(tm) {
247                 return tm.map.getCenter();
248             },
249             set: function(tm, value) {
250                 tm.map.setCenter(value);
251             },
252             fromStr: function(s) {
253                 var params = s.split(",");
254                 if (params.length < 2) {
255                     // give up
256                     return null;
257                 }
258                 return new GLatLng(
259                     parseFloat(params[0]),
260                     parseFloat(params[1])
261                 );
262             },
263             toStr: function(value) {
264                 return value.lat() + "," + value.lng();
265             }
266         }),
267         
268         /**
269          * Timeline center date
270          * @type TimeMap.params.Param
271          */
272         date: new paramNS.Param("scrollTo", {
273             get: function(tm) {
274                 return tm.timeline.getBand(0).getCenterVisibleDate();
275             },
276             set: function(tm, value) {
277                 tm.scrollToDate(value);
278             },
279             fromStr: function(s) {
280                 return TimeMapDataset.hybridParser(s);
281             },
282             toStr: function(value) {
283                 return TimeMap.util.formatDate(value);
284             }
285         }),
286         
287         /**
288          * Index of selected/open item, if any
289          * @type TimeMap.params.Param
290          */
291         selected: new paramNS.Param("selected", {
292             get: function(tm) {
293                 var items = tm.getItems(),
294                     i = items.length-1;
295                 while (i >= 0 && i--) {
296                     if (items[i].selected) break;
297                 }
298                 return i;
299             },
300             set: function(tm, value) {
301                 if (value >= 0) {
302                     var item = tm.getItems()[value];
303                     if (item) {
304                         item.openInfoWindow();
305                     }
306                 }
307             },
308             fromStr: function(s) {
309                 return parseInt(s);
310             }
311         })
312 };
313 
314 })();
315