474 lines
15 KiB
JavaScript
Executable File
474 lines
15 KiB
JavaScript
Executable File
/**
|
|
* jquery.layout.state 1.2
|
|
* $Date: 2014-08-30 08:00:00 (Sat, 30 Aug 2014) $
|
|
*
|
|
* Copyright (c) 2014
|
|
* Kevin Dalman (http://allpro.net)
|
|
*
|
|
* Dual licensed under the GPL (http://www.gnu.org/licenses/gpl.html)
|
|
* and MIT (http://www.opensource.org/licenses/mit-license.php) licenses.
|
|
*
|
|
* @requires: UI Layout 1.4.0 or higher
|
|
* @requires: $.ui.cookie (above)
|
|
*
|
|
* @see: http://groups.google.com/group/jquery-ui-layout
|
|
*/
|
|
|
|
// NOTE: For best readability, view with a fixed-width font and tabs equal to 4-chars
|
|
;(function ($) {
|
|
|
|
if (!$.layout) return;
|
|
|
|
|
|
/**
|
|
* UI COOKIE UTILITY
|
|
*
|
|
* A $.cookie OR $.ui.cookie namespace *should be standard*, but until then...
|
|
* This creates $.ui.cookie so Layout does not need the cookie.jquery.js plugin
|
|
* NOTE: This utility is REQUIRED by the layout.state plugin
|
|
*
|
|
* Cookie methods in Layout are created as part of State Management
|
|
*/
|
|
if (!$.ui) $.ui = {};
|
|
$.ui.cookie = {
|
|
|
|
// cookieEnabled is not in DOM specs, but DOES works in all browsers,including IE6
|
|
acceptsCookies: !!navigator.cookieEnabled
|
|
|
|
, read: function (name) {
|
|
var
|
|
c = document.cookie
|
|
, cs = c ? c.split(';') : []
|
|
, pair, data, i
|
|
;
|
|
for (i=0; pair=cs[i]; i++) {
|
|
data = $.trim(pair).split('='); // name=value => [ name, value ]
|
|
if (data[0] == name) // found the layout cookie
|
|
return decodeURIComponent(data[1]);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
, write: function (name, val, cookieOpts) {
|
|
var params = ""
|
|
, date = ""
|
|
, clear = false
|
|
, o = cookieOpts || {}
|
|
, x = o.expires || null
|
|
, t = $.type(x)
|
|
;
|
|
if (t === "date")
|
|
date = x;
|
|
else if (t === "string" && x > 0) {
|
|
x = parseInt(x,10);
|
|
t = "number";
|
|
}
|
|
if (t === "number") {
|
|
date = new Date();
|
|
if (x > 0)
|
|
date.setDate(date.getDate() + x);
|
|
else {
|
|
date.setFullYear(1970);
|
|
clear = true;
|
|
}
|
|
}
|
|
if (date) params += ";expires="+ date.toUTCString();
|
|
if (o.path) params += ";path="+ o.path;
|
|
if (o.domain) params += ";domain="+ o.domain;
|
|
if (o.secure) params += ";secure";
|
|
document.cookie = name +"="+ (clear ? "" : encodeURIComponent( val )) + params; // write or clear cookie
|
|
}
|
|
|
|
, clear: function (name) {
|
|
$.ui.cookie.write(name, "", {expires: -1});
|
|
}
|
|
|
|
};
|
|
// if cookie.jquery.js is not loaded, create an alias to replicate it
|
|
// this may be useful to other plugins or code dependent on that plugin
|
|
if (!$.cookie) $.cookie = function (k, v, o) {
|
|
var C = $.ui.cookie;
|
|
if (v === null)
|
|
C.clear(k);
|
|
else if (v === undefined)
|
|
return C.read(k);
|
|
else
|
|
C.write(k, v, o);
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
* State-management options stored in options.stateManagement, which includes a .cookie hash
|
|
* Default options saves ALL KEYS for ALL PANES, ie: pane.size, pane.isClosed, pane.isHidden
|
|
*
|
|
* // STATE/COOKIE OPTIONS
|
|
* @example $(el).layout({
|
|
stateManagement: {
|
|
enabled: true
|
|
, stateKeys: "east.size,west.size,east.isClosed,west.isClosed"
|
|
, cookie: { name: "appLayout", path: "/" }
|
|
}
|
|
})
|
|
* @example $(el).layout({ stateManagement__enabled: true }) // enable auto-state-management using cookies
|
|
* @example $(el).layout({ stateManagement__cookie: { name: "appLayout", path: "/" } })
|
|
* @example $(el).layout({ stateManagement__cookie__name: "appLayout", stateManagement__cookie__path: "/" })
|
|
*
|
|
* // STATE/COOKIE METHODS
|
|
* @example myLayout.saveCookie( "west.isClosed,north.size,south.isHidden", {expires: 7} );
|
|
* @example myLayout.loadCookie();
|
|
* @example myLayout.deleteCookie();
|
|
* @example var JSON = myLayout.readState(); // CURRENT Layout State
|
|
* @example var JSON = myLayout.readCookie(); // SAVED Layout State (from cookie)
|
|
* @example var JSON = myLayout.state.stateData; // LAST LOADED Layout State (cookie saved in layout.state hash)
|
|
*
|
|
* CUSTOM STATE-MANAGEMENT (eg, saved in a database)
|
|
* @example var JSON = myLayout.readState( "west.isClosed,north.size,south.isHidden" );
|
|
* @example myLayout.loadState( JSON );
|
|
*/
|
|
|
|
// tell Layout that the state plugin is available
|
|
$.layout.plugins.stateManagement = true;
|
|
|
|
// Add State-Management options to layout.defaults
|
|
$.layout.defaults.stateManagement = {
|
|
enabled: false // true = enable state-management, even if not using cookies
|
|
, autoSave: true // Save a state-cookie when page exits?
|
|
, autoLoad: true // Load the state-cookie when Layout inits?
|
|
, animateLoad: true // animate panes when loading state into an active layout
|
|
, includeChildren: true // recurse into child layouts to include their state as well
|
|
// List state-data to save - must be pane-specific
|
|
, stateKeys: "north.size,south.size,east.size,west.size,"+
|
|
"north.isClosed,south.isClosed,east.isClosed,west.isClosed,"+
|
|
"north.isHidden,south.isHidden,east.isHidden,west.isHidden"
|
|
, cookie: {
|
|
name: "" // If not specified, will use Layout.name, else just "Layout"
|
|
, domain: "" // blank = current domain
|
|
, path: "" // blank = current page, "/" = entire website
|
|
, expires: "" // 'days' to keep cookie - leave blank for 'session cookie'
|
|
, secure: false
|
|
}
|
|
};
|
|
|
|
// Set stateManagement as a 'layout-option', NOT a 'pane-option'
|
|
$.layout.optionsMap.layout.push("stateManagement");
|
|
// Update config so layout does not move options into the pane-default branch (panes)
|
|
$.layout.config.optionRootKeys.push("stateManagement");
|
|
|
|
/*
|
|
* State Management methods
|
|
*/
|
|
$.layout.state = {
|
|
|
|
/**
|
|
* Get the current layout state and save it to a cookie
|
|
*
|
|
* myLayout.saveCookie( keys, cookieOpts )
|
|
*
|
|
* @param {Object} inst
|
|
* @param {(string|Array)=} keys
|
|
* @param {Object=} cookieOpts
|
|
*/
|
|
saveCookie: function (inst, keys, cookieOpts) {
|
|
var o = inst.options
|
|
, sm = o.stateManagement
|
|
, oC = $.extend(true, {}, sm.cookie, cookieOpts || null)
|
|
, data = inst.state.stateData = inst.readState( keys || sm.stateKeys ) // read current panes-state
|
|
;
|
|
$.ui.cookie.write( oC.name || o.name || "Layout", $.layout.state.encodeJSON(data), oC );
|
|
return $.extend(true, {}, data); // return COPY of state.stateData data
|
|
}
|
|
|
|
/**
|
|
* Remove the state cookie
|
|
*
|
|
* @param {Object} inst
|
|
*/
|
|
, deleteCookie: function (inst) {
|
|
var o = inst.options;
|
|
$.ui.cookie.clear( o.stateManagement.cookie.name || o.name || "Layout" );
|
|
}
|
|
|
|
/**
|
|
* Read & return data from the cookie - as JSON
|
|
*
|
|
* @param {Object} inst
|
|
*/
|
|
, readCookie: function (inst) {
|
|
var o = inst.options;
|
|
var c = $.ui.cookie.read( o.stateManagement.cookie.name || o.name || "Layout" );
|
|
// convert cookie string back to a hash and return it
|
|
return c ? $.layout.state.decodeJSON(c) : {};
|
|
}
|
|
|
|
/**
|
|
* Get data from the cookie and USE IT to loadState
|
|
*
|
|
* @param {Object} inst
|
|
*/
|
|
, loadCookie: function (inst) {
|
|
var c = $.layout.state.readCookie(inst); // READ the cookie
|
|
if (c && !$.isEmptyObject( c )) {
|
|
inst.state.stateData = $.extend(true, {}, c); // SET state.stateData
|
|
inst.loadState(c); // LOAD the retrieved state
|
|
}
|
|
return c;
|
|
}
|
|
|
|
/**
|
|
* Update layout options from the cookie, if one exists
|
|
*
|
|
* @param {Object} inst
|
|
* @param {Object=} stateData
|
|
* @param {boolean=} animate
|
|
*/
|
|
, loadState: function (inst, data, opts) {
|
|
if (!$.isPlainObject( data ) || $.isEmptyObject( data )) return;
|
|
|
|
// normalize data & cache in the state object
|
|
data = inst.state.stateData = $.layout.transformData( data ); // panes = default subkey
|
|
|
|
// add missing/default state-restore options
|
|
var smo = inst.options.stateManagement;
|
|
opts = $.extend({
|
|
animateLoad: false //smo.animateLoad
|
|
, includeChildren: smo.includeChildren
|
|
}, opts );
|
|
|
|
if (!inst.state.initialized) {
|
|
/*
|
|
* layout NOT initialized, so just update its options
|
|
*/
|
|
// MUST remove pane.children keys before applying to options
|
|
// use a copy so we don't remove keys from original data
|
|
var o = $.extend(true, {}, data);
|
|
//delete o.center; // center has no state-data - only children
|
|
$.each($.layout.config.allPanes, function (idx, pane) {
|
|
if (o[pane]) delete o[pane].children;
|
|
});
|
|
// update CURRENT layout-options with saved state data
|
|
$.extend(true, inst.options, o);
|
|
}
|
|
else {
|
|
/*
|
|
* layout already initialized, so modify layout's configuration
|
|
*/
|
|
var noAnimate = !opts.animateLoad
|
|
, o, c, h, state, open
|
|
;
|
|
$.each($.layout.config.borderPanes, function (idx, pane) {
|
|
o = data[ pane ];
|
|
if (!$.isPlainObject( o )) return; // no key, skip pane
|
|
|
|
s = o.size;
|
|
c = o.initClosed;
|
|
h = o.initHidden;
|
|
ar = o.autoResize
|
|
state = inst.state[pane];
|
|
open = state.isVisible;
|
|
|
|
// reset autoResize
|
|
if (ar)
|
|
state.autoResize = ar;
|
|
// resize BEFORE opening
|
|
if (!open)
|
|
inst._sizePane(pane, s, false, false, false); // false=skipCallback/noAnimation/forceResize
|
|
// open/close as necessary - DO NOT CHANGE THIS ORDER!
|
|
if (h === true) inst.hide(pane, noAnimate);
|
|
else if (c === true) inst.close(pane, false, noAnimate);
|
|
else if (c === false) inst.open (pane, false, noAnimate);
|
|
else if (h === false) inst.show (pane, false, noAnimate);
|
|
// resize AFTER any other actions
|
|
if (open)
|
|
inst._sizePane(pane, s, false, false, noAnimate); // animate resize if option passed
|
|
});
|
|
|
|
/*
|
|
* RECURSE INTO CHILD-LAYOUTS
|
|
*/
|
|
if (opts.includeChildren) {
|
|
var paneStateChildren, childState;
|
|
$.each(inst.children, function (pane, paneChildren) {
|
|
paneStateChildren = data[pane] ? data[pane].children : 0;
|
|
if (paneStateChildren && paneChildren) {
|
|
$.each(paneChildren, function (stateKey, child) {
|
|
childState = paneStateChildren[stateKey];
|
|
if (child && childState)
|
|
child.loadState( childState );
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the *current layout state* and return it as a hash
|
|
*
|
|
* @param {Object=} inst // Layout instance to get state for
|
|
* @param {object=} [opts] // State-Managements override options
|
|
*/
|
|
, readState: function (inst, opts) {
|
|
// backward compatility
|
|
if ($.type(opts) === 'string') opts = { keys: opts };
|
|
if (!opts) opts = {};
|
|
var sm = inst.options.stateManagement
|
|
, ic = opts.includeChildren
|
|
, recurse = ic !== undefined ? ic : sm.includeChildren
|
|
, keys = opts.stateKeys || sm.stateKeys
|
|
, alt = { isClosed: 'initClosed', isHidden: 'initHidden' }
|
|
, state = inst.state
|
|
, panes = $.layout.config.allPanes
|
|
, data = {}
|
|
, pair, pane, key, val
|
|
, ps, pC, child, array, count, branch
|
|
;
|
|
if ($.isArray(keys)) keys = keys.join(",");
|
|
// convert keys to an array and change delimiters from '__' to '.'
|
|
keys = keys.replace(/__/g, ".").split(',');
|
|
// loop keys and create a data hash
|
|
for (var i=0, n=keys.length; i < n; i++) {
|
|
pair = keys[i].split(".");
|
|
pane = pair[0];
|
|
key = pair[1];
|
|
if ($.inArray(pane, panes) < 0) continue; // bad pane!
|
|
val = state[ pane ][ key ];
|
|
if (val == undefined) continue;
|
|
if (key=="isClosed" && state[pane]["isSliding"])
|
|
val = true; // if sliding, then *really* isClosed
|
|
( data[pane] || (data[pane]={}) )[ alt[key] ? alt[key] : key ] = val;
|
|
}
|
|
|
|
// recurse into the child-layouts for each pane
|
|
if (recurse) {
|
|
$.each(panes, function (idx, pane) {
|
|
pC = inst.children[pane];
|
|
ps = state.stateData[pane];
|
|
if ($.isPlainObject( pC ) && !$.isEmptyObject( pC )) {
|
|
// ensure a key exists for this 'pane', eg: branch = data.center
|
|
branch = data[pane] || (data[pane] = {});
|
|
if (!branch.children) branch.children = {};
|
|
$.each( pC, function (key, child) {
|
|
// ONLY read state from an initialize layout
|
|
if ( child.state.initialized )
|
|
branch.children[ key ] = $.layout.state.readState( child );
|
|
// if we have PREVIOUS (onLoad) state for this child-layout, KEEP IT!
|
|
else if ( ps && ps.children && ps.children[ key ] ) {
|
|
branch.children[ key ] = $.extend(true, {}, ps.children[ key ] );
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Stringify a JSON hash so can save in a cookie or db-field
|
|
*/
|
|
, encodeJSON: function (json) {
|
|
var local = window.JSON || {};
|
|
return (local.stringify || stringify)(json);
|
|
|
|
function stringify (h) {
|
|
var D=[], i=0, k, v, t // k = key, v = value
|
|
, a = $.isArray(h)
|
|
;
|
|
for (k in h) {
|
|
v = h[k];
|
|
t = typeof v;
|
|
if (t == 'string') // STRING - add quotes
|
|
v = '"'+ v +'"';
|
|
else if (t == 'object') // SUB-KEY - recurse into it
|
|
v = parse(v);
|
|
D[i++] = (!a ? '"'+ k +'":' : '') + v;
|
|
}
|
|
return (a ? '[' : '{') + D.join(',') + (a ? ']' : '}');
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Convert stringified JSON back to a hash object
|
|
* @see $.parseJSON(), adding in jQuery 1.4.1
|
|
*/
|
|
, decodeJSON: function (str) {
|
|
try { return $.parseJSON ? $.parseJSON(str) : window["eval"]("("+ str +")") || {}; }
|
|
catch (e) { return {}; }
|
|
}
|
|
|
|
|
|
, _create: function (inst) {
|
|
var s = $.layout.state
|
|
, o = inst.options
|
|
, sm = o.stateManagement
|
|
;
|
|
// ADD State-Management plugin methods to inst
|
|
$.extend( inst, {
|
|
// readCookie - update options from cookie - returns hash of cookie data
|
|
readCookie: function () { return s.readCookie(inst); }
|
|
// deleteCookie
|
|
, deleteCookie: function () { s.deleteCookie(inst); }
|
|
// saveCookie - optionally pass keys-list and cookie-options (hash)
|
|
, saveCookie: function (keys, cookieOpts) { return s.saveCookie(inst, keys, cookieOpts); }
|
|
// loadCookie - readCookie and use to loadState() - returns hash of cookie data
|
|
, loadCookie: function () { return s.loadCookie(inst); }
|
|
// loadState - pass a hash of state to use to update options
|
|
, loadState: function (stateData, opts) { s.loadState(inst, stateData, opts); }
|
|
// readState - returns hash of current layout-state
|
|
, readState: function (keys) { return s.readState(inst, keys); }
|
|
// add JSON utility methods too...
|
|
, encodeJSON: s.encodeJSON
|
|
, decodeJSON: s.decodeJSON
|
|
});
|
|
|
|
// init state.stateData key, even if plugin is initially disabled
|
|
inst.state.stateData = {};
|
|
|
|
// autoLoad MUST BE one of: data-array, data-hash, callback-function, or TRUE
|
|
if ( !sm.autoLoad ) return;
|
|
|
|
// When state-data exists in the autoLoad key USE IT,
|
|
// even if stateManagement.enabled == false
|
|
if ($.isPlainObject( sm.autoLoad )) {
|
|
if (!$.isEmptyObject( sm.autoLoad )) {
|
|
inst.loadState( sm.autoLoad );
|
|
}
|
|
}
|
|
else if ( sm.enabled ) {
|
|
// update the options from cookie or callback
|
|
// if options is a function, call it to get stateData
|
|
if ($.isFunction( sm.autoLoad )) {
|
|
var d = {};
|
|
try {
|
|
d = sm.autoLoad( inst, inst.state, inst.options, inst.options.name || '' ); // try to get data from fn
|
|
} catch (e) {}
|
|
if (d && $.isPlainObject( d ) && !$.isEmptyObject( d ))
|
|
inst.loadState(d);
|
|
}
|
|
else // any other truthy value will trigger loadCookie
|
|
inst.loadCookie();
|
|
}
|
|
}
|
|
|
|
, _unload: function (inst) {
|
|
var sm = inst.options.stateManagement;
|
|
if (sm.enabled && sm.autoSave) {
|
|
// if options is a function, call it to save the stateData
|
|
if ($.isFunction( sm.autoSave )) {
|
|
try {
|
|
sm.autoSave( inst, inst.state, inst.options, inst.options.name || '' ); // try to get data from fn
|
|
} catch (e) {}
|
|
}
|
|
else // any truthy value will trigger saveCookie
|
|
inst.saveCookie();
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
// add state initialization method to Layout's onCreate array of functions
|
|
$.layout.onCreate.push( $.layout.state._create );
|
|
$.layout.onUnload.push( $.layout.state._unload );
|
|
|
|
})( jQuery ); |