;(function ($) {
// jquery.layout.js MUST load first
if (!$.layout) return;
// NOTE: For best readability, view with a fixed-width font and tabs equal to 4-chars
/**
* @preserve jquery.layout.state 1.0
* $Date: 2011-07-16 08:00:00 (Sat, 16 July 2011) $
*
* Copyright (c) 2010
* 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.
*
* @dependancies: UI Layout 1.3.0.rc30.1 or higher
* @dependancies: $.ui.cookie (above)
*
* @support: http://groups.google.com/group/jquery-ui-layout
*/
/*
* 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 );
*/
/**
* 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
*/
$.ui.cookie = {
// TODO: is the cookieEnabled property fully cross-browser???
acceptsCookies: !!navigator.cookieEnabled
, read: function (name) {
var
c = document.cookie
, cs = c ? c.split(';') : []
, pair // loop var
;
for (var i=0, n=cs.length; i < n; i++) {
pair = $.trim(cs[i]).split('='); // name=value pair
if (pair[0] == name) // found the layout cookie
return decodeURIComponent(pair[1]);
}
return "";
}
, write: function (name, val, cookieOpts) {
var
params = ''
, date = ''
, clear = false
, o = cookieOpts || {}
;
if (!o.expires) {} // skip
else if (o.expires.toUTCString)
date = o.expires;
else if (typeof o.expires == 'number') {
date = new Date();
if (o.expires > 0)
date.setDate(date.getDate() + o.expires);
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});
}
};
// Cookie methods in Layout are part of State Managment
// 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?
// 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");
/*
* State Managment methods
*/
$.layout.state = {
// set data used by multiple methods below
config: {
allPanes: "north,south,west,east,center"
}
/**
* Get the current layout state and save it to a cookie
*
* myLayout.saveCookie( keys, cookieOpts )
*
* @param {Object} inst
* @param {(string|Array)=} keys
* @param {Object=} opts
*/
, saveCookie: function (inst, keys, cookieOpts) {
var o = inst.options
, oS = o.stateManagement
, oC = $.extend( {}, oS.cookie, cookieOpts || {} )
, data = inst.state.stateData = inst.readState( keys || oS.stateKeys ) // read current panes-state
;
$.ui.cookie.write( oC.name || o.name || "Layout", $.layout.state.encodeJSON(data), oC );
return $.extend( {}, 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) {
inst.state.stateData = $.extend({}, 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, stateData, animate) {
stateData = $.layout.transformData( stateData ); // panes = default subkey
$.extend( true, inst.options, stateData ); // update layout options
// if layout has already been initialized, then UPDATE layout state
if (inst.state.initialized) {
var pane, o, s, h, c
, noAnimate = (animate===false);
$.each($.layout.state.config.allPanes.split(","), function (idx, pane) {
o = stateData[ pane ];
if (typeof o != 'object') return; // no key, continue
s = o.size;
c = o.initClosed;
h = o.initHidden;
if (s > 0 || s=="auto") inst.sizePane(pane, s, false, null, noAnimate); // will animate resize if option enabled
if (h === true) inst.hide(pane, a);
else if (c === false) inst.open (pane, false, noAnimate);
else if (c === true) inst.close(pane, false, noAnimate);
else if (h === false) inst.show (pane, false, noAnimate);
});
}
}
/**
* Get the *current layout state* and return it as a hash
*
* @param {Object=} inst
* @param {(string|Array)=} keys
*/
, readState: function (inst, keys) {
var
data = {}
, alt = { isClosed: 'initClosed', isHidden: 'initHidden' }
, state = inst.state
, pair, pane, key, val
;
if (!keys) keys = inst.options.stateManagement.stateKeys; // if called by user
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 ($.layout.state.config.allPanes.indexOf(pane) < 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;
}
return data;
}
/**
* Stringify a JSON hash so can save in a cookie or db-field
*/
, encodeJSON: function (JSON) {
return parse(JSON);
function parse (h) {
var D=[], i=0, k, v, t; // k = key, v = value
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++] = '"'+ k +'":'+ v;
}
return '{'+ D.join(',') +'}';
};
}
/**
* 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) {
// ADD State-Management plugin methods to inst
$.extend( inst, {
// readCookie - update options from cookie - returns hash of cookie data
readCookie: function () { return $.layout.state.readCookie(inst); }
// deleteCookie
, deleteCookie: function () { $.layout.state.deleteCookie(inst); }
// saveCookie - optionally pass keys-list and cookie-options (hash)
, saveCookie: function (keys, cookieOpts) { return $.layout.state.saveCookie(inst, keys, cookieOpts); }
// loadCookie - readCookie and use to loadState() - returns hash of cookie data
, loadCookie: function () { return $.layout.state.loadCookie(inst); }
// loadState - pass a hash of state to use to update options
, loadState: function (stateData, animate) { $.layout.state.loadState(inst, stateData, animate); }
// readState - returns hash of current layout-state
, readState: function (keys) { return $.layout.state.readState(inst, keys); }
// add JSON utility methods too...
, encodeJSON: $.layout.state.encodeJSON
, decodeJSON: $.layout.state.decodeJSON
});
// init state.stateData key, even if plugin is initially disabled
inst.state.stateData = {};
// read and load cookie-data per options
var oS = inst.options.stateManagement;
if (oS.enabled) {
if (oS.autoLoad) // update the options from the cookie
inst.loadCookie();
else // don't modify options - just store cookie data in state.stateData
inst.state.stateData = inst.readCookie();
}
}
, _unload: function (inst) {
var oS = inst.options.stateManagement;
if (oS.enabled) {
if (oS.autoSave) // save a state-cookie automatically
inst.saveCookie();
else // don't save a cookie, but do store state-data in state.stateData key
inst.state.stateData = inst.readState();
}
}
};
// add state initialization method to Layout's onCreate array of functions
$.layout.onCreate.push( $.layout.state._create );
$.layout.onUnload.push( $.layout.state._unload );
/**
* @preserve jquery.layout.buttons 1.0
* $Date: 2011-07-16 08:00:00 (Sat, 16 July 2011) $
*
* Copyright (c) 2010
* 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.
*
* @dependancies: UI Layout 1.3.0.rc30.1 or higher
*
* @support: http://groups.google.com/group/jquery-ui-layout
*
* Docs: [ to come ]
* Tips: [ to come ]
*/
// tell Layout that the state plugin is available
$.layout.plugins.buttons = true;
// Add buttons options to layout.defaults
$.layout.defaults.autoBindCustomButtons = false;
// Specify autoBindCustomButtons as a layout-option, NOT a pane-option
$.layout.optionsMap.layout.push("autoBindCustomButtons");
/*
* Button methods
*/
$.layout.buttons = {
// set data used by multiple methods below
config: {
borderPanes: "north,south,west,east"
}
/**
* Searches for .ui-layout-button-xxx elements and auto-binds them as layout-buttons
*
* @see _create()
*/
, init: function (inst) {
var pre = "ui-layout-button-"
, layout = inst.options.name || ""
, name;
$.each("toggle,open,close,pin,toggle-slide,open-slide".split(","), function (i, action) {
$.each($.layout.buttons.config.borderPanes.split(","), function (ii, pane) {
$("."+pre+action+"-"+pane).each(function(){
// if button was previously 'bound', data.layoutName was set, but is blank if layout has no 'name'
name = $(this).data("layoutName") || $(this).attr("layoutName");
if (name == undefined || name === layout)
inst.bindButton(this, action, pane);
});
});
});
}
/**
* Helper function to validate params received by addButton utilities
*
* Two classes are added to the element, based on the buttonClass...
* The type of button is appended to create the 2nd className:
* - ui-layout-button-pin
* - ui-layout-pane-button-toggle
* - ui-layout-pane-button-open
* - ui-layout-pane-button-close
*
* @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button"
* @param {string} pane Name of the pane the button is for: 'north', 'south', etc.
* @return {Array.<Object>} If both params valid, the element matching 'selector' in a jQuery wrapper - otherwise returns null
*/
, get: function (inst, selector, pane, action) {
var $E = $(selector)
, o = inst.options
, err = o.showErrorMessages
, lang= $.layout.language
;
if (!$E.length) { // element not found
if (err) alert(lang.errButton + lang.selector +": "+ selector);
}
else if ($.layout.buttons.config.borderPanes.indexOf(pane) === -1) { // invalid 'pane' sepecified
if (err) alert(lang.errButton + lang.pane +": "+ pane);
$E = $(""); // NO BUTTON
}
else { // VALID
var btn = o[pane].buttonClass +"-"+ action;
$E .addClass( btn +" "+ btn +"-"+ pane )
.data("layoutName", o.name); // add layout identifier - even if blank!
}
return $E;
}
/**
* NEW syntax for binding layout-buttons - will eventually replace addToggle, addOpen, etc.
*
* @param {(string|!Object)} sel jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button"
* @param {string} action
* @param {string} pane
*/
, bind: function (inst, sel, action, pane) {
var _ = $.layout.buttons;
switch (action.toLowerCase()) {
case "toggle": _.addToggle (inst, sel, pane); break;
case "open": _.addOpen (inst, sel, pane); break;
case "close": _.addClose (inst, sel, pane); break;
case "pin": _.addPin (inst, sel, pane); break;
case "toggle-slide": _.addToggle (inst, sel, pane, true); break;
case "open-slide": _.addOpen (inst, sel, pane, true); break;
}
return inst;
}
/**
* Add a custom Toggler button for a pane
*
* @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button"
* @param {string} pane Name of the pane the button is for: 'north', 'south', etc.
* @param {boolean=} slide true = slide-open, false = pin-open
*/
, addToggle: function (inst, selector, pane, slide) {
$.layout.buttons.get(inst, selector, pane, "toggle")
.click(function(evt){
inst.toggle(pane, !!slide);
evt.stopPropagation();
});
return inst;
}
/**
* Add a custom Open button for a pane
*
* @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button"
* @param {string} pane Name of the pane the button is for: 'north', 'south', etc.
* @param {boolean=} slide true = slide-open, false = pin-open
*/
, addOpen: function (inst, selector, pane, slide) {
$.layout.buttons.get(inst, selector, pane, "open")
.attr("title", $.layout.language.Open)
.click(function (evt) {
inst.open(pane, !!slide);
evt.stopPropagation();
});
return inst;
}
/**
* Add a custom Close button for a pane
*
* @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button"
* @param {string} pane Name of the pane the button is for: 'north', 'south', etc.
*/
, addClose: function (inst, selector, pane) {
$.layout.buttons.get(inst, selector, pane, "close")
.attr("title", $.layout.language.Close)
.click(function (evt) {
inst.close(pane);
evt.stopPropagation();
});
return inst;
}
/**
* Add a custom Pin button for a pane
*
* Four classes are added to the element, based on the paneClass for the associated pane...
* Assuming the default paneClass and the pin is 'up', these classes are added for a west-pane pin:
* - ui-layout-pane-pin
* - ui-layout-pane-west-pin
* - ui-layout-pane-pin-up
* - ui-layout-pane-west-pin-up
*
* @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button"
* @param {string} pane Name of the pane the pin is for: 'north', 'south', etc.
*/
, addPin: function (inst, selector, pane) {
var $E = $.layout.buttons.get(inst, selector, pane, "pin");
if ($E.length) {
var s = inst.state[pane];
$E.click(function (evt) {
$.layout.buttons.setPinState(inst, $(this), pane, (s.isSliding || s.isClosed));
if (s.isSliding || s.isClosed) inst.open( pane ); // change from sliding to open
else inst.close( pane ); // slide-closed
evt.stopPropagation();
});
// add up/down pin attributes and classes
$.layout.buttons.setPinState(inst, $E, pane, (!s.isClosed && !s.isSliding));
// add this pin to the pane data so we can 'sync it' automatically
// PANE.pins key is an array so we can store multiple pins for each pane
s.pins.push( selector ); // just save the selector string
}
return inst;
}
/**
* Change the class of the pin button to make it look 'up' or 'down'
*
* @see addPin(), syncPins()
* @param {Array.<Object>} $Pin The pin-span element in a jQuery wrapper
* @param {string} pane These are the params returned to callbacks by layout()
* @param {boolean} doPin true = set the pin 'down', false = set it 'up'
*/
, setPinState: function (inst, $Pin, pane, doPin) {
var updown = $Pin.attr("pin");
if (updown && doPin === (updown=="down")) return; // already in correct state
var
pin = inst.options[pane].buttonClass +"-pin"
, side = pin +"-"+ pane
, UP = pin +"-up "+ side +"-up"
, DN = pin +"-down "+side +"-down"
, lang = $.layout.language
;
$Pin
.attr("pin", doPin ? "down" : "up") // logic
.attr("title", doPin ? lang.Unpin : lang.Pin)
.removeClass( doPin ? UP : DN )
.addClass( doPin ? DN : UP )
;
}
/**
* INTERNAL function to sync 'pin buttons' when pane is opened or closed
* Unpinned means the pane is 'sliding' - ie, over-top of the adjacent panes
*
* @see open(), close()
* @param {string} pane These are the params returned to callbacks by layout()
* @param {boolean} doPin True means set the pin 'down', False means 'up'
*/
, syncPinBtns: function (inst, pane, doPin) {
// REAL METHOD IS _INSIDE_ LAYOUT - THIS IS HERE JUST FOR REFERENCE
$.each(state[pane].pins, function (i, selector) {
$.layout.buttons.setPinState(inst, $(selector), pane, doPin);
});
}
, _load: function (inst) {
// ADD Button methods to Layout Instance
$.extend( inst, {
bindButton: function (selector, action, pane) { return $.layout.buttons.bind(inst, selector, action, pane); }
// DEPRECATED METHODS...
, addToggleBtn: function (selector, pane, slide) { return $.layout.buttons.addToggle(inst, selector, pane, slide); }
, addOpenBtn: function (selector, pane, slide) { return $.layout.buttons.addOpen(inst, selector, pane, slide); }
, addCloseBtn: function (selector, pane) { return $.layout.buttons.addClose(inst, selector, pane); }
, addPinBtn: function (selector, pane) { return $.layout.buttons.addPin(inst, selector, pane); }
});
// init state array to hold pin-buttons
for (var i=0; i<4; i++) {
var pane = $.layout.buttons.config.borderPanes[i];
inst.state[pane].pins = [];
}
// auto-init buttons onLoad if option is enabled
if ( inst.options.autoBindCustomButtons )
$.layout.buttons.init(inst);
}
, _unload: function (inst) {
// TODO: unbind all buttons???
}
};
// add initialization method to Layout's onLoad array of functions
$.layout.onLoad.push( $.layout.buttons._load );
//$.layout.onUnload.push( $.layout.buttons._unload );
})( jQuery );