jquery.layout.state.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. /**
  2. * @preserve jquery.layout.state 1.0
  3. * $Date: 2011-07-16 08:00:00 (Sat, 16 July 2011) $
  4. *
  5. * Copyright (c) 2010
  6. * Kevin Dalman (http://allpro.net)
  7. *
  8. * Dual licensed under the GPL (http://www.gnu.org/licenses/gpl.html)
  9. * and MIT (http://www.opensource.org/licenses/mit-license.php) licenses.
  10. *
  11. * @dependancies: UI Layout 1.3.0.rc30.1 or higher
  12. * @dependancies: $.ui.cookie (above)
  13. *
  14. * @support: http://groups.google.com/group/jquery-ui-layout
  15. */
  16. /*
  17. * State-management options stored in options.stateManagement, which includes a .cookie hash
  18. * Default options saves ALL KEYS for ALL PANES, ie: pane.size, pane.isClosed, pane.isHidden
  19. *
  20. * // STATE/COOKIE OPTIONS
  21. * @example $(el).layout({
  22. stateManagement: {
  23. enabled: true
  24. , stateKeys: "east.size,west.size,east.isClosed,west.isClosed"
  25. , cookie: { name: "appLayout", path: "/" }
  26. }
  27. })
  28. * @example $(el).layout({ stateManagement__enabled: true }) // enable auto-state-management using cookies
  29. * @example $(el).layout({ stateManagement__cookie: { name: "appLayout", path: "/" } })
  30. * @example $(el).layout({ stateManagement__cookie__name: "appLayout", stateManagement__cookie__path: "/" })
  31. *
  32. * // STATE/COOKIE METHODS
  33. * @example myLayout.saveCookie( "west.isClosed,north.size,south.isHidden", {expires: 7} );
  34. * @example myLayout.loadCookie();
  35. * @example myLayout.deleteCookie();
  36. * @example var JSON = myLayout.readState(); // CURRENT Layout State
  37. * @example var JSON = myLayout.readCookie(); // SAVED Layout State (from cookie)
  38. * @example var JSON = myLayout.state.stateData; // LAST LOADED Layout State (cookie saved in layout.state hash)
  39. *
  40. * CUSTOM STATE-MANAGEMENT (eg, saved in a database)
  41. * @example var JSON = myLayout.readState( "west.isClosed,north.size,south.isHidden" );
  42. * @example myLayout.loadState( JSON );
  43. */
  44. // NOTE: For best readability, view with a fixed-width font and tabs equal to 4-chars
  45. ;(function ($) {
  46. if (!$.layout) return;
  47. /*
  48. * GENERIC COOKIE/DATA MANAGEMENT
  49. *
  50. * A $.cookie OR $.ui.cookie namespace *should be* standard.
  51. * Until this is standard, I'll create my own $.ui.cookie methods!
  52. */
  53. $.ui.cookie = {
  54. // TODO: is the cookieEnabled property fully cross-browser???
  55. acceptsCookies: !!navigator.cookieEnabled
  56. , read: function (name) {
  57. var
  58. c = document.cookie
  59. , cs = c ? c.split(';') : []
  60. , pair // loop var
  61. ;
  62. for (var i=0, n=cs.length; i < n; i++) {
  63. pair = $.trim(cs[i]).split('='); // name=value pair
  64. if (pair[0] == name) // found the layout cookie
  65. return decodeURIComponent(pair[1]);
  66. }
  67. return "";
  68. }
  69. , write: function (name, val, cookieOpts) {
  70. var
  71. params = ''
  72. , date = ''
  73. , clear = false
  74. , o = cookieOpts || {}
  75. ;
  76. if (!o.expires) ; // skip
  77. else if (o.expires.toUTCString)
  78. date = o.expires;
  79. else if (typeof o.expires == 'number') {
  80. date = new Date();
  81. if (o.expires > 0)
  82. date.setDate(date.getDate() + o.expires);
  83. else {
  84. date.setYear(1970);
  85. clear = true;
  86. }
  87. }
  88. if (date) params += ';expires='+ date.toUTCString();
  89. if (o.path) params += ';path='+ o.path;
  90. if (o.domain) params += ';domain='+ o.domain;
  91. if (o.secure) params += ';secure';
  92. document.cookie = name +'='+ (clear ? "" : encodeURIComponent( val )) + params; // write or clear cookie
  93. }
  94. , clear: function (name) {
  95. $.ui.cookie.write(name, '', {expires: -1});
  96. }
  97. };
  98. // tell Layout that the state plugin is available
  99. $.layout.plugins.stateManagement = true;
  100. // Add State-Management options to layout.defaults
  101. $.layout.defaults.stateManagement = {
  102. enabled: false // true = enable state-management, even if not using cookies
  103. , autoSave: true // Save a state-cookie when page exits?
  104. , autoLoad: true // Load the state-cookie when Layout inits?
  105. // List state-data to save - must be pane-specific
  106. , stateKeys: "north.size,south.size,east.size,west.size,"+
  107. "north.isClosed,south.isClosed,east.isClosed,west.isClosed,"+
  108. "north.isHidden,south.isHidden,east.isHidden,west.isHidden"
  109. , cookie: {
  110. name: "" // If not specified, will use Layout.name, else just "Layout"
  111. , domain: "" // blank = current domain
  112. , path: "" // blank = current page, '/' = entire website
  113. , expires: "" // 'days' to keep cookie - leave blank for 'session cookie'
  114. , secure: false
  115. }
  116. };
  117. // Set stateManagement as a layout-option, NOT a pane-option
  118. $.layout.optionsMap.layout.push("stateManagement");
  119. /*
  120. * State Managment methods
  121. */
  122. $.layout.state = {
  123. // set data used by multiple methods below
  124. config: {
  125. allPanes: "north,south,west,east,center"
  126. }
  127. /**
  128. * Get the current layout state and save it to a cookie
  129. *
  130. * myLayout.saveCookie( keys, cookieOpts )
  131. *
  132. * @param {Object} inst
  133. * @param {(string|Array)=} keys
  134. * @param {Object=} opts
  135. */
  136. , saveCookie: function (inst, keys, cookieOpts) {
  137. var o = inst.options
  138. , oS = o.stateManagement
  139. , oC = $.extend( {}, oS.cookie, cookieOpts || {} )
  140. , data = inst.state.stateData = inst.readState( keys || oS.stateKeys ) // read current panes-state
  141. ;
  142. $.ui.cookie.write( oC.name || o.name || "Layout", $.layout.state.encodeJSON(data), oC );
  143. return $.extend( {}, data ); // return COPY of state.stateData data
  144. }
  145. /**
  146. * Remove the state cookie
  147. *
  148. * @param {Object} inst
  149. */
  150. , deleteCookie: function (inst) {
  151. var o = inst.options;
  152. $.ui.cookie.clear( o.stateManagement.cookie.name || o.name || "Layout" );
  153. }
  154. /**
  155. * Read & return data from the cookie - as JSON
  156. *
  157. * @param {Object} inst
  158. */
  159. , readCookie: function (inst) {
  160. var o = inst.options;
  161. var c = $.ui.cookie.read( o.stateManagement.cookie.name || o.name || "Layout" );
  162. // convert cookie string back to a hash and return it
  163. return c ? $.layout.state.decodeJSON(c) : {};
  164. }
  165. /**
  166. * Get data from the cookie and USE IT to loadState
  167. *
  168. * @param {Object} inst
  169. */
  170. , loadCookie: function (inst) {
  171. var c = $.layout.state.readCookie(inst); // READ the cookie
  172. if (c && !$.isEmptyObject( c )) {
  173. inst.state.stateData = $.extend({}, c); // SET state.stateData
  174. inst.loadState(c); // LOAD the retrieved state
  175. }
  176. return c;
  177. }
  178. /**
  179. * Update layout options from the cookie, if one exists
  180. *
  181. * @param {Object} inst
  182. * @param {Object=} stateData
  183. * @param {boolean=} animate
  184. */
  185. , loadState: function (inst, stateData, animate) {
  186. stateData = $.layout.transformData( stateData ); // panes = default subkey
  187. $.extend( true, inst.options, stateData ); // update layout options
  188. // if layout has already been initialized, then UPDATE layout state
  189. if (inst.state.initialized) {
  190. var pane, o, s, h, c
  191. , noAnimate = (animate===false);
  192. $.each($.layout.state.config.allPanes.split(","), function (idx, pane) {
  193. o = stateData[ pane ];
  194. if (typeof o != 'object') return; // no key, continue
  195. s = o.size;
  196. c = o.initClosed;
  197. h = o.initHidden;
  198. if (s > 0 || s=="auto") inst.sizePane(pane, s, false, null, noAnimate); // will animate resize if option enabled
  199. if (h === true) inst.hide(pane, a);
  200. else if (c === false) inst.open (pane, false, noAnimate);
  201. else if (c === true) inst.close(pane, false, noAnimate);
  202. else if (h === false) inst.show (pane, false, noAnimate);
  203. });
  204. }
  205. }
  206. /**
  207. * Get the *current layout state* and return it as a hash
  208. *
  209. * @param {Object=} inst
  210. * @param {(string|Array)=} keys
  211. */
  212. , readState: function (inst, keys) {
  213. var
  214. data = {}
  215. , alt = { isClosed: 'initClosed', isHidden: 'initHidden' }
  216. , state = inst.state
  217. , pair, pane, key, val
  218. ;
  219. if (!keys) keys = inst.options.stateManagement.stateKeys; // if called by user
  220. if ($.isArray(keys)) keys = keys.join(",");
  221. // convert keys to an array and change delimiters from '__' to '.'
  222. keys = keys.replace(/__/g, ".").split(',');
  223. // loop keys and create a data hash
  224. for (var i=0, n=keys.length; i < n; i++) {
  225. pair = keys[i].split(".");
  226. pane = pair[0];
  227. key = pair[1];
  228. if ($.layout.state.config.allPanes.indexOf(pane) < 0) continue; // bad pane!
  229. val = state[ pane ][ key ];
  230. if (val == undefined) continue;
  231. if (key=="isClosed" && state[pane]["isSliding"])
  232. val = true; // if sliding, then *really* isClosed
  233. ( data[pane] || (data[pane]={}) )[ alt[key] ? alt[key] : key ] = val;
  234. }
  235. return data;
  236. }
  237. /**
  238. * Stringify a JSON hash so can save in a cookie or db-field
  239. */
  240. , encodeJSON: function (JSON) {
  241. var native = window.JSON || {};
  242. return (native.stringify || stringify)(JSON);
  243. function stringify (h) {
  244. var D=[], i=0, k, v, t; // k = key, v = value
  245. for (k in h) {
  246. v = h[k];
  247. t = typeof v;
  248. if (t == 'string') // STRING - add quotes
  249. v = '"'+ v +'"';
  250. else if (t == 'object') // SUB-KEY - recurse into it
  251. v = parse(v);
  252. D[i++] = '"'+ k +'":'+ v;
  253. }
  254. return '{'+ D.join(',') +'}';
  255. };
  256. }
  257. /**
  258. * Convert stringified JSON back to a hash object
  259. * @see $.parseJSON(), adding in jQuery 1.4.1
  260. */
  261. , decodeJSON: function (str) {
  262. try { return $.parseJSON ? $.parseJSON(str) : window["eval"]("("+ str +")") || {}; }
  263. catch (e) { return {}; }
  264. }
  265. , _create: function (inst) {
  266. // ADD State-Management plugin methods to inst
  267. $.extend( inst, {
  268. // readCookie - update options from cookie - returns hash of cookie data
  269. readCookie: function () { return $.layout.state.readCookie(inst); }
  270. // deleteCookie
  271. , deleteCookie: function () { $.layout.state.deleteCookie(inst); }
  272. // saveCookie - optionally pass keys-list and cookie-options (hash)
  273. , saveCookie: function (keys, cookieOpts) { return $.layout.state.saveCookie(inst, keys, cookieOpts); }
  274. // loadCookie - readCookie and use to loadState() - returns hash of cookie data
  275. , loadCookie: function () { return $.layout.state.loadCookie(inst); }
  276. // loadState - pass a hash of state to use to update options
  277. , loadState: function (stateData, animate) { $.layout.state.loadState(inst, stateData, animate); }
  278. // readState - returns hash of current layout-state
  279. , readState: function (keys) { return $.layout.state.readState(inst, keys); }
  280. // add JSON utility methods too...
  281. , encodeJSON: $.layout.state.encodeJSON
  282. , decodeJSON: $.layout.state.decodeJSON
  283. });
  284. // init state.stateData key, even if plugin is initially disabled
  285. inst.state.stateData = {};
  286. // read and load cookie-data per options
  287. var oS = inst.options.stateManagement;
  288. if (oS.enabled) {
  289. if (oS.autoLoad) // update the options from the cookie
  290. inst.loadCookie();
  291. else // don't modify options - just store cookie data in state.stateData
  292. inst.state.stateData = inst.readCookie();
  293. }
  294. }
  295. , _unload: function (inst) {
  296. var oS = inst.options.stateManagement;
  297. if (oS.enabled) {
  298. if (oS.autoSave) // save a state-cookie automatically
  299. inst.saveCookie();
  300. else // don't save a cookie, but do store state-data in state.stateData key
  301. inst.state.stateData = inst.readState();
  302. }
  303. }
  304. };
  305. // add state initialization method to Layout's onCreate array of functions
  306. $.layout.onCreate.push( $.layout.state._create );
  307. $.layout.onUnload.push( $.layout.state._unload );
  308. })( jQuery );