mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-30 06:55:22 -05:00
9174 lines
291 KiB
JavaScript
9174 lines
291 KiB
JavaScript
/*! jQuery UI - v1.10.3 - 2013-10-24
|
|
* http://jqueryui.com
|
|
* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.draggable.js, jquery.ui.droppable.js, jquery.ui.resizable.js
|
|
* Copyright 2013 jQuery Foundation and other contributors; Licensed MIT */
|
|
|
|
(function( $, undefined ) {
|
|
|
|
var uuid = 0,
|
|
runiqueId = /^ui-id-\d+$/;
|
|
|
|
// $.ui might exist from components with no dependencies, e.g., $.ui.position
|
|
$.ui = $.ui || {};
|
|
|
|
$.extend( $.ui, {
|
|
version: "1.10.3",
|
|
|
|
keyCode: {
|
|
BACKSPACE: 8,
|
|
COMMA: 188,
|
|
DELETE: 46,
|
|
DOWN: 40,
|
|
END: 35,
|
|
ENTER: 13,
|
|
ESCAPE: 27,
|
|
HOME: 36,
|
|
LEFT: 37,
|
|
NUMPAD_ADD: 107,
|
|
NUMPAD_DECIMAL: 110,
|
|
NUMPAD_DIVIDE: 111,
|
|
NUMPAD_ENTER: 108,
|
|
NUMPAD_MULTIPLY: 106,
|
|
NUMPAD_SUBTRACT: 109,
|
|
PAGE_DOWN: 34,
|
|
PAGE_UP: 33,
|
|
PERIOD: 190,
|
|
RIGHT: 39,
|
|
SPACE: 32,
|
|
TAB: 9,
|
|
UP: 38
|
|
}
|
|
});
|
|
|
|
// plugins
|
|
$.fn.extend({
|
|
focus: (function( orig ) {
|
|
return function( delay, fn ) {
|
|
return typeof delay === "number" ?
|
|
this.each(function() {
|
|
var elem = this;
|
|
setTimeout(function() {
|
|
$( elem ).focus();
|
|
if ( fn ) {
|
|
fn.call( elem );
|
|
}
|
|
}, delay );
|
|
}) :
|
|
orig.apply( this, arguments );
|
|
};
|
|
})( $.fn.focus ),
|
|
|
|
scrollParent: function() {
|
|
var scrollParent;
|
|
if (($.ui.ie && (/(static|relative)/).test(this.css("position"))) || (/absolute/).test(this.css("position"))) {
|
|
scrollParent = this.parents().filter(function() {
|
|
return (/(relative|absolute|fixed)/).test($.css(this,"position")) && (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x"));
|
|
}).eq(0);
|
|
} else {
|
|
scrollParent = this.parents().filter(function() {
|
|
return (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x"));
|
|
}).eq(0);
|
|
}
|
|
|
|
return (/fixed/).test(this.css("position")) || !scrollParent.length ? $(document) : scrollParent;
|
|
},
|
|
|
|
zIndex: function( zIndex ) {
|
|
if ( zIndex !== undefined ) {
|
|
return this.css( "zIndex", zIndex );
|
|
}
|
|
|
|
if ( this.length ) {
|
|
var elem = $( this[ 0 ] ), position, value;
|
|
while ( elem.length && elem[ 0 ] !== document ) {
|
|
// Ignore z-index if position is set to a value where z-index is ignored by the browser
|
|
// This makes behavior of this function consistent across browsers
|
|
// WebKit always returns auto if the element is positioned
|
|
position = elem.css( "position" );
|
|
if ( position === "absolute" || position === "relative" || position === "fixed" ) {
|
|
// IE returns 0 when zIndex is not specified
|
|
// other browsers return a string
|
|
// we ignore the case of nested elements with an explicit value of 0
|
|
// <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
|
|
value = parseInt( elem.css( "zIndex" ), 10 );
|
|
if ( !isNaN( value ) && value !== 0 ) {
|
|
return value;
|
|
}
|
|
}
|
|
elem = elem.parent();
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
},
|
|
|
|
uniqueId: function() {
|
|
return this.each(function() {
|
|
if ( !this.id ) {
|
|
this.id = "ui-id-" + (++uuid);
|
|
}
|
|
});
|
|
},
|
|
|
|
removeUniqueId: function() {
|
|
return this.each(function() {
|
|
if ( runiqueId.test( this.id ) ) {
|
|
$( this ).removeAttr( "id" );
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// selectors
|
|
function focusable( element, isTabIndexNotNaN ) {
|
|
var map, mapName, img,
|
|
nodeName = element.nodeName.toLowerCase();
|
|
if ( "area" === nodeName ) {
|
|
map = element.parentNode;
|
|
mapName = map.name;
|
|
if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) {
|
|
return false;
|
|
}
|
|
img = $( "img[usemap=#" + mapName + "]" )[0];
|
|
return !!img && visible( img );
|
|
}
|
|
return ( /input|select|textarea|button|object/.test( nodeName ) ?
|
|
!element.disabled :
|
|
"a" === nodeName ?
|
|
element.href || isTabIndexNotNaN :
|
|
isTabIndexNotNaN) &&
|
|
// the element and all of its ancestors must be visible
|
|
visible( element );
|
|
}
|
|
|
|
function visible( element ) {
|
|
return $.expr.filters.visible( element ) &&
|
|
!$( element ).parents().addBack().filter(function() {
|
|
return $.css( this, "visibility" ) === "hidden";
|
|
}).length;
|
|
}
|
|
|
|
$.extend( $.expr[ ":" ], {
|
|
data: $.expr.createPseudo ?
|
|
$.expr.createPseudo(function( dataName ) {
|
|
return function( elem ) {
|
|
return !!$.data( elem, dataName );
|
|
};
|
|
}) :
|
|
// support: jQuery <1.8
|
|
function( elem, i, match ) {
|
|
return !!$.data( elem, match[ 3 ] );
|
|
},
|
|
|
|
focusable: function( element ) {
|
|
return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) );
|
|
},
|
|
|
|
tabbable: function( element ) {
|
|
var tabIndex = $.attr( element, "tabindex" ),
|
|
isTabIndexNaN = isNaN( tabIndex );
|
|
return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN );
|
|
}
|
|
});
|
|
|
|
// support: jQuery <1.8
|
|
if ( !$( "<a>" ).outerWidth( 1 ).jquery ) {
|
|
$.each( [ "Width", "Height" ], function( i, name ) {
|
|
var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ],
|
|
type = name.toLowerCase(),
|
|
orig = {
|
|
innerWidth: $.fn.innerWidth,
|
|
innerHeight: $.fn.innerHeight,
|
|
outerWidth: $.fn.outerWidth,
|
|
outerHeight: $.fn.outerHeight
|
|
};
|
|
|
|
function reduce( elem, size, border, margin ) {
|
|
$.each( side, function() {
|
|
size -= parseFloat( $.css( elem, "padding" + this ) ) || 0;
|
|
if ( border ) {
|
|
size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0;
|
|
}
|
|
if ( margin ) {
|
|
size -= parseFloat( $.css( elem, "margin" + this ) ) || 0;
|
|
}
|
|
});
|
|
return size;
|
|
}
|
|
|
|
$.fn[ "inner" + name ] = function( size ) {
|
|
if ( size === undefined ) {
|
|
return orig[ "inner" + name ].call( this );
|
|
}
|
|
|
|
return this.each(function() {
|
|
$( this ).css( type, reduce( this, size ) + "px" );
|
|
});
|
|
};
|
|
|
|
$.fn[ "outer" + name] = function( size, margin ) {
|
|
if ( typeof size !== "number" ) {
|
|
return orig[ "outer" + name ].call( this, size );
|
|
}
|
|
|
|
return this.each(function() {
|
|
$( this).css( type, reduce( this, size, true, margin ) + "px" );
|
|
});
|
|
};
|
|
});
|
|
}
|
|
|
|
// support: jQuery <1.8
|
|
if ( !$.fn.addBack ) {
|
|
$.fn.addBack = function( selector ) {
|
|
return this.add( selector == null ?
|
|
this.prevObject : this.prevObject.filter( selector )
|
|
);
|
|
};
|
|
}
|
|
|
|
// support: jQuery 1.6.1, 1.6.2 (http://bugs.jquery.com/ticket/9413)
|
|
if ( $( "<a>" ).data( "a-b", "a" ).removeData( "a-b" ).data( "a-b" ) ) {
|
|
$.fn.removeData = (function( removeData ) {
|
|
return function( key ) {
|
|
if ( arguments.length ) {
|
|
return removeData.call( this, $.camelCase( key ) );
|
|
} else {
|
|
return removeData.call( this );
|
|
}
|
|
};
|
|
})( $.fn.removeData );
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// deprecated
|
|
$.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() );
|
|
|
|
$.support.selectstart = "onselectstart" in document.createElement( "div" );
|
|
$.fn.extend({
|
|
disableSelection: function() {
|
|
return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) +
|
|
".ui-disableSelection", function( event ) {
|
|
event.preventDefault();
|
|
});
|
|
},
|
|
|
|
enableSelection: function() {
|
|
return this.unbind( ".ui-disableSelection" );
|
|
}
|
|
});
|
|
|
|
$.extend( $.ui, {
|
|
// $.ui.plugin is deprecated. Use $.widget() extensions instead.
|
|
plugin: {
|
|
add: function( module, option, set ) {
|
|
var i,
|
|
proto = $.ui[ module ].prototype;
|
|
for ( i in set ) {
|
|
proto.plugins[ i ] = proto.plugins[ i ] || [];
|
|
proto.plugins[ i ].push( [ option, set[ i ] ] );
|
|
}
|
|
},
|
|
call: function( instance, name, args ) {
|
|
var i,
|
|
set = instance.plugins[ name ];
|
|
if ( !set || !instance.element[ 0 ].parentNode || instance.element[ 0 ].parentNode.nodeType === 11 ) {
|
|
return;
|
|
}
|
|
|
|
for ( i = 0; i < set.length; i++ ) {
|
|
if ( instance.options[ set[ i ][ 0 ] ] ) {
|
|
set[ i ][ 1 ].apply( instance.element, args );
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
// only used by resizable
|
|
hasScroll: function( el, a ) {
|
|
|
|
//If overflow is hidden, the element might have extra content, but the user wants to hide it
|
|
if ( $( el ).css( "overflow" ) === "hidden") {
|
|
return false;
|
|
}
|
|
|
|
var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop",
|
|
has = false;
|
|
|
|
if ( el[ scroll ] > 0 ) {
|
|
return true;
|
|
}
|
|
|
|
// TODO: determine which cases actually cause this to happen
|
|
// if the element doesn't have the scroll set, see if it's possible to
|
|
// set the scroll
|
|
el[ scroll ] = 1;
|
|
has = ( el[ scroll ] > 0 );
|
|
el[ scroll ] = 0;
|
|
return has;
|
|
}
|
|
});
|
|
|
|
})( jQuery );
|
|
(function( $, undefined ) {
|
|
|
|
var uuid = 0,
|
|
slice = Array.prototype.slice,
|
|
_cleanData = $.cleanData;
|
|
$.cleanData = function( elems ) {
|
|
for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
|
|
try {
|
|
$( elem ).triggerHandler( "remove" );
|
|
// http://bugs.jquery.com/ticket/8235
|
|
} catch( e ) {}
|
|
}
|
|
_cleanData( elems );
|
|
};
|
|
|
|
$.widget = function( name, base, prototype ) {
|
|
var fullName, existingConstructor, constructor, basePrototype,
|
|
// proxiedPrototype allows the provided prototype to remain unmodified
|
|
// so that it can be used as a mixin for multiple widgets (#8876)
|
|
proxiedPrototype = {},
|
|
namespace = name.split( "." )[ 0 ];
|
|
|
|
name = name.split( "." )[ 1 ];
|
|
fullName = namespace + "-" + name;
|
|
|
|
if ( !prototype ) {
|
|
prototype = base;
|
|
base = $.Widget;
|
|
}
|
|
|
|
// create selector for plugin
|
|
$.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
|
|
return !!$.data( elem, fullName );
|
|
};
|
|
|
|
$[ namespace ] = $[ namespace ] || {};
|
|
existingConstructor = $[ namespace ][ name ];
|
|
constructor = $[ namespace ][ name ] = function( options, element ) {
|
|
// allow instantiation without "new" keyword
|
|
if ( !this._createWidget ) {
|
|
return new constructor( options, element );
|
|
}
|
|
|
|
// allow instantiation without initializing for simple inheritance
|
|
// must use "new" keyword (the code above always passes args)
|
|
if ( arguments.length ) {
|
|
this._createWidget( options, element );
|
|
}
|
|
};
|
|
// extend with the existing constructor to carry over any static properties
|
|
$.extend( constructor, existingConstructor, {
|
|
version: prototype.version,
|
|
// copy the object used to create the prototype in case we need to
|
|
// redefine the widget later
|
|
_proto: $.extend( {}, prototype ),
|
|
// track widgets that inherit from this widget in case this widget is
|
|
// redefined after a widget inherits from it
|
|
_childConstructors: []
|
|
});
|
|
|
|
basePrototype = new base();
|
|
// we need to make the options hash a property directly on the new instance
|
|
// otherwise we'll modify the options hash on the prototype that we're
|
|
// inheriting from
|
|
basePrototype.options = $.widget.extend( {}, basePrototype.options );
|
|
$.each( prototype, function( prop, value ) {
|
|
if ( !$.isFunction( value ) ) {
|
|
proxiedPrototype[ prop ] = value;
|
|
return;
|
|
}
|
|
proxiedPrototype[ prop ] = (function() {
|
|
var _super = function() {
|
|
return base.prototype[ prop ].apply( this, arguments );
|
|
},
|
|
_superApply = function( args ) {
|
|
return base.prototype[ prop ].apply( this, args );
|
|
};
|
|
return function() {
|
|
var __super = this._super,
|
|
__superApply = this._superApply,
|
|
returnValue;
|
|
|
|
this._super = _super;
|
|
this._superApply = _superApply;
|
|
|
|
returnValue = value.apply( this, arguments );
|
|
|
|
this._super = __super;
|
|
this._superApply = __superApply;
|
|
|
|
return returnValue;
|
|
};
|
|
})();
|
|
});
|
|
constructor.prototype = $.widget.extend( basePrototype, {
|
|
// TODO: remove support for widgetEventPrefix
|
|
// always use the name + a colon as the prefix, e.g., draggable:start
|
|
// don't prefix for widgets that aren't DOM-based
|
|
widgetEventPrefix: existingConstructor ? basePrototype.widgetEventPrefix : name
|
|
}, proxiedPrototype, {
|
|
constructor: constructor,
|
|
namespace: namespace,
|
|
widgetName: name,
|
|
widgetFullName: fullName
|
|
});
|
|
|
|
// If this widget is being redefined then we need to find all widgets that
|
|
// are inheriting from it and redefine all of them so that they inherit from
|
|
// the new version of this widget. We're essentially trying to replace one
|
|
// level in the prototype chain.
|
|
if ( existingConstructor ) {
|
|
$.each( existingConstructor._childConstructors, function( i, child ) {
|
|
var childPrototype = child.prototype;
|
|
|
|
// redefine the child widget using the same prototype that was
|
|
// originally used, but inherit from the new version of the base
|
|
$.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
|
|
});
|
|
// remove the list of existing child constructors from the old constructor
|
|
// so the old child constructors can be garbage collected
|
|
delete existingConstructor._childConstructors;
|
|
} else {
|
|
base._childConstructors.push( constructor );
|
|
}
|
|
|
|
$.widget.bridge( name, constructor );
|
|
};
|
|
|
|
$.widget.extend = function( target ) {
|
|
var input = slice.call( arguments, 1 ),
|
|
inputIndex = 0,
|
|
inputLength = input.length,
|
|
key,
|
|
value;
|
|
for ( ; inputIndex < inputLength; inputIndex++ ) {
|
|
for ( key in input[ inputIndex ] ) {
|
|
value = input[ inputIndex ][ key ];
|
|
if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
|
|
// Clone objects
|
|
if ( $.isPlainObject( value ) ) {
|
|
target[ key ] = $.isPlainObject( target[ key ] ) ?
|
|
$.widget.extend( {}, target[ key ], value ) :
|
|
// Don't extend strings, arrays, etc. with objects
|
|
$.widget.extend( {}, value );
|
|
// Copy everything else by reference
|
|
} else {
|
|
target[ key ] = value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return target;
|
|
};
|
|
|
|
$.widget.bridge = function( name, object ) {
|
|
var fullName = object.prototype.widgetFullName || name;
|
|
$.fn[ name ] = function( options ) {
|
|
var isMethodCall = typeof options === "string",
|
|
args = slice.call( arguments, 1 ),
|
|
returnValue = this;
|
|
|
|
// allow multiple hashes to be passed on init
|
|
options = !isMethodCall && args.length ?
|
|
$.widget.extend.apply( null, [ options ].concat(args) ) :
|
|
options;
|
|
|
|
if ( isMethodCall ) {
|
|
this.each(function() {
|
|
var methodValue,
|
|
instance = $.data( this, fullName );
|
|
if ( !instance ) {
|
|
return $.error( "cannot call methods on " + name + " prior to initialization; " +
|
|
"attempted to call method '" + options + "'" );
|
|
}
|
|
if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
|
|
return $.error( "no such method '" + options + "' for " + name + " widget instance" );
|
|
}
|
|
methodValue = instance[ options ].apply( instance, args );
|
|
if ( methodValue !== instance && methodValue !== undefined ) {
|
|
returnValue = methodValue && methodValue.jquery ?
|
|
returnValue.pushStack( methodValue.get() ) :
|
|
methodValue;
|
|
return false;
|
|
}
|
|
});
|
|
} else {
|
|
this.each(function() {
|
|
var instance = $.data( this, fullName );
|
|
if ( instance ) {
|
|
instance.option( options || {} )._init();
|
|
} else {
|
|
$.data( this, fullName, new object( options, this ) );
|
|
}
|
|
});
|
|
}
|
|
|
|
return returnValue;
|
|
};
|
|
};
|
|
|
|
$.Widget = function( /* options, element */ ) {};
|
|
$.Widget._childConstructors = [];
|
|
|
|
$.Widget.prototype = {
|
|
widgetName: "widget",
|
|
widgetEventPrefix: "",
|
|
defaultElement: "<div>",
|
|
options: {
|
|
disabled: false,
|
|
|
|
// callbacks
|
|
create: null
|
|
},
|
|
_createWidget: function( options, element ) {
|
|
element = $( element || this.defaultElement || this )[ 0 ];
|
|
this.element = $( element );
|
|
this.uuid = uuid++;
|
|
this.eventNamespace = "." + this.widgetName + this.uuid;
|
|
this.options = $.widget.extend( {},
|
|
this.options,
|
|
this._getCreateOptions(),
|
|
options );
|
|
|
|
this.bindings = $();
|
|
this.hoverable = $();
|
|
this.focusable = $();
|
|
|
|
if ( element !== this ) {
|
|
$.data( element, this.widgetFullName, this );
|
|
this._on( true, this.element, {
|
|
remove: function( event ) {
|
|
if ( event.target === element ) {
|
|
this.destroy();
|
|
}
|
|
}
|
|
});
|
|
this.document = $( element.style ?
|
|
// element within the document
|
|
element.ownerDocument :
|
|
// element is window or document
|
|
element.document || element );
|
|
this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
|
|
}
|
|
|
|
this._create();
|
|
this._trigger( "create", null, this._getCreateEventData() );
|
|
this._init();
|
|
},
|
|
_getCreateOptions: $.noop,
|
|
_getCreateEventData: $.noop,
|
|
_create: $.noop,
|
|
_init: $.noop,
|
|
|
|
destroy: function() {
|
|
this._destroy();
|
|
// we can probably remove the unbind calls in 2.0
|
|
// all event bindings should go through this._on()
|
|
this.element
|
|
.unbind( this.eventNamespace )
|
|
// 1.9 BC for #7810
|
|
// TODO remove dual storage
|
|
.removeData( this.widgetName )
|
|
.removeData( this.widgetFullName )
|
|
// support: jquery <1.6.3
|
|
// http://bugs.jquery.com/ticket/9413
|
|
.removeData( $.camelCase( this.widgetFullName ) );
|
|
this.widget()
|
|
.unbind( this.eventNamespace )
|
|
.removeAttr( "aria-disabled" )
|
|
.removeClass(
|
|
this.widgetFullName + "-disabled " +
|
|
"ui-state-disabled" );
|
|
|
|
// clean up events and states
|
|
this.bindings.unbind( this.eventNamespace );
|
|
this.hoverable.removeClass( "ui-state-hover" );
|
|
this.focusable.removeClass( "ui-state-focus" );
|
|
},
|
|
_destroy: $.noop,
|
|
|
|
widget: function() {
|
|
return this.element;
|
|
},
|
|
|
|
option: function( key, value ) {
|
|
var options = key,
|
|
parts,
|
|
curOption,
|
|
i;
|
|
|
|
if ( arguments.length === 0 ) {
|
|
// don't return a reference to the internal hash
|
|
return $.widget.extend( {}, this.options );
|
|
}
|
|
|
|
if ( typeof key === "string" ) {
|
|
// handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
|
|
options = {};
|
|
parts = key.split( "." );
|
|
key = parts.shift();
|
|
if ( parts.length ) {
|
|
curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
|
|
for ( i = 0; i < parts.length - 1; i++ ) {
|
|
curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
|
|
curOption = curOption[ parts[ i ] ];
|
|
}
|
|
key = parts.pop();
|
|
if ( value === undefined ) {
|
|
return curOption[ key ] === undefined ? null : curOption[ key ];
|
|
}
|
|
curOption[ key ] = value;
|
|
} else {
|
|
if ( value === undefined ) {
|
|
return this.options[ key ] === undefined ? null : this.options[ key ];
|
|
}
|
|
options[ key ] = value;
|
|
}
|
|
}
|
|
|
|
this._setOptions( options );
|
|
|
|
return this;
|
|
},
|
|
_setOptions: function( options ) {
|
|
var key;
|
|
|
|
for ( key in options ) {
|
|
this._setOption( key, options[ key ] );
|
|
}
|
|
|
|
return this;
|
|
},
|
|
_setOption: function( key, value ) {
|
|
this.options[ key ] = value;
|
|
|
|
if ( key === "disabled" ) {
|
|
this.widget()
|
|
.toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value )
|
|
.attr( "aria-disabled", value );
|
|
this.hoverable.removeClass( "ui-state-hover" );
|
|
this.focusable.removeClass( "ui-state-focus" );
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
enable: function() {
|
|
return this._setOption( "disabled", false );
|
|
},
|
|
disable: function() {
|
|
return this._setOption( "disabled", true );
|
|
},
|
|
|
|
_on: function( suppressDisabledCheck, element, handlers ) {
|
|
var delegateElement,
|
|
instance = this;
|
|
|
|
// no suppressDisabledCheck flag, shuffle arguments
|
|
if ( typeof suppressDisabledCheck !== "boolean" ) {
|
|
handlers = element;
|
|
element = suppressDisabledCheck;
|
|
suppressDisabledCheck = false;
|
|
}
|
|
|
|
// no element argument, shuffle and use this.element
|
|
if ( !handlers ) {
|
|
handlers = element;
|
|
element = this.element;
|
|
delegateElement = this.widget();
|
|
} else {
|
|
// accept selectors, DOM elements
|
|
element = delegateElement = $( element );
|
|
this.bindings = this.bindings.add( element );
|
|
}
|
|
|
|
$.each( handlers, function( event, handler ) {
|
|
function handlerProxy() {
|
|
// allow widgets to customize the disabled handling
|
|
// - disabled as an array instead of boolean
|
|
// - disabled class as method for disabling individual parts
|
|
if ( !suppressDisabledCheck &&
|
|
( instance.options.disabled === true ||
|
|
$( this ).hasClass( "ui-state-disabled" ) ) ) {
|
|
return;
|
|
}
|
|
return ( typeof handler === "string" ? instance[ handler ] : handler )
|
|
.apply( instance, arguments );
|
|
}
|
|
|
|
// copy the guid so direct unbinding works
|
|
if ( typeof handler !== "string" ) {
|
|
handlerProxy.guid = handler.guid =
|
|
handler.guid || handlerProxy.guid || $.guid++;
|
|
}
|
|
|
|
var match = event.match( /^(\w+)\s*(.*)$/ ),
|
|
eventName = match[1] + instance.eventNamespace,
|
|
selector = match[2];
|
|
if ( selector ) {
|
|
delegateElement.delegate( selector, eventName, handlerProxy );
|
|
} else {
|
|
element.bind( eventName, handlerProxy );
|
|
}
|
|
});
|
|
},
|
|
|
|
_off: function( element, eventName ) {
|
|
eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace;
|
|
element.unbind( eventName ).undelegate( eventName );
|
|
},
|
|
|
|
_delay: function( handler, delay ) {
|
|
function handlerProxy() {
|
|
return ( typeof handler === "string" ? instance[ handler ] : handler )
|
|
.apply( instance, arguments );
|
|
}
|
|
var instance = this;
|
|
return setTimeout( handlerProxy, delay || 0 );
|
|
},
|
|
|
|
_hoverable: function( element ) {
|
|
this.hoverable = this.hoverable.add( element );
|
|
this._on( element, {
|
|
mouseenter: function( event ) {
|
|
$( event.currentTarget ).addClass( "ui-state-hover" );
|
|
},
|
|
mouseleave: function( event ) {
|
|
$( event.currentTarget ).removeClass( "ui-state-hover" );
|
|
}
|
|
});
|
|
},
|
|
|
|
_focusable: function( element ) {
|
|
this.focusable = this.focusable.add( element );
|
|
this._on( element, {
|
|
focusin: function( event ) {
|
|
$( event.currentTarget ).addClass( "ui-state-focus" );
|
|
},
|
|
focusout: function( event ) {
|
|
$( event.currentTarget ).removeClass( "ui-state-focus" );
|
|
}
|
|
});
|
|
},
|
|
|
|
_trigger: function( type, event, data ) {
|
|
var prop, orig,
|
|
callback = this.options[ type ];
|
|
|
|
data = data || {};
|
|
event = $.Event( event );
|
|
event.type = ( type === this.widgetEventPrefix ?
|
|
type :
|
|
this.widgetEventPrefix + type ).toLowerCase();
|
|
// the original event may come from any element
|
|
// so we need to reset the target on the new event
|
|
event.target = this.element[ 0 ];
|
|
|
|
// copy original event properties over to the new event
|
|
orig = event.originalEvent;
|
|
if ( orig ) {
|
|
for ( prop in orig ) {
|
|
if ( !( prop in event ) ) {
|
|
event[ prop ] = orig[ prop ];
|
|
}
|
|
}
|
|
}
|
|
|
|
this.element.trigger( event, data );
|
|
return !( $.isFunction( callback ) &&
|
|
callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
|
|
event.isDefaultPrevented() );
|
|
}
|
|
};
|
|
|
|
$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
|
|
$.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
|
|
if ( typeof options === "string" ) {
|
|
options = { effect: options };
|
|
}
|
|
var hasOptions,
|
|
effectName = !options ?
|
|
method :
|
|
options === true || typeof options === "number" ?
|
|
defaultEffect :
|
|
options.effect || defaultEffect;
|
|
options = options || {};
|
|
if ( typeof options === "number" ) {
|
|
options = { duration: options };
|
|
}
|
|
hasOptions = !$.isEmptyObject( options );
|
|
options.complete = callback;
|
|
if ( options.delay ) {
|
|
element.delay( options.delay );
|
|
}
|
|
if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
|
|
element[ method ]( options );
|
|
} else if ( effectName !== method && element[ effectName ] ) {
|
|
element[ effectName ]( options.duration, options.easing, callback );
|
|
} else {
|
|
element.queue(function( next ) {
|
|
$( this )[ method ]();
|
|
if ( callback ) {
|
|
callback.call( element[ 0 ] );
|
|
}
|
|
next();
|
|
});
|
|
}
|
|
};
|
|
});
|
|
|
|
})( jQuery );
|
|
(function( $, undefined ) {
|
|
|
|
var mouseHandled = false;
|
|
$( document ).mouseup( function() {
|
|
mouseHandled = false;
|
|
});
|
|
|
|
$.widget("ui.mouse", {
|
|
version: "1.10.3",
|
|
options: {
|
|
cancel: "input,textarea,button,select,option",
|
|
distance: 1,
|
|
delay: 0
|
|
},
|
|
_mouseInit: function() {
|
|
var that = this;
|
|
|
|
this.element
|
|
.bind("mousedown."+this.widgetName, function(event) {
|
|
return that._mouseDown(event);
|
|
})
|
|
.bind("click."+this.widgetName, function(event) {
|
|
if (true === $.data(event.target, that.widgetName + ".preventClickEvent")) {
|
|
$.removeData(event.target, that.widgetName + ".preventClickEvent");
|
|
event.stopImmediatePropagation();
|
|
return false;
|
|
}
|
|
});
|
|
|
|
this.started = false;
|
|
},
|
|
|
|
// TODO: make sure destroying one instance of mouse doesn't mess with
|
|
// other instances of mouse
|
|
_mouseDestroy: function() {
|
|
this.element.unbind("."+this.widgetName);
|
|
if ( this._mouseMoveDelegate ) {
|
|
$(document)
|
|
.unbind("mousemove."+this.widgetName, this._mouseMoveDelegate)
|
|
.unbind("mouseup."+this.widgetName, this._mouseUpDelegate);
|
|
}
|
|
},
|
|
|
|
_mouseDown: function(event) {
|
|
// don't let more than one widget handle mouseStart
|
|
if( mouseHandled ) { return; }
|
|
|
|
// we may have missed mouseup (out of window)
|
|
(this._mouseStarted && this._mouseUp(event));
|
|
|
|
this._mouseDownEvent = event;
|
|
|
|
var that = this,
|
|
btnIsLeft = (event.which === 1),
|
|
// event.target.nodeName works around a bug in IE 8 with
|
|
// disabled inputs (#7620)
|
|
elIsCancel = (typeof this.options.cancel === "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false);
|
|
if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) {
|
|
return true;
|
|
}
|
|
|
|
this.mouseDelayMet = !this.options.delay;
|
|
if (!this.mouseDelayMet) {
|
|
this._mouseDelayTimer = setTimeout(function() {
|
|
that.mouseDelayMet = true;
|
|
}, this.options.delay);
|
|
}
|
|
|
|
if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
|
|
this._mouseStarted = (this._mouseStart(event) !== false);
|
|
if (!this._mouseStarted) {
|
|
event.preventDefault();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Click event may never have fired (Gecko & Opera)
|
|
if (true === $.data(event.target, this.widgetName + ".preventClickEvent")) {
|
|
$.removeData(event.target, this.widgetName + ".preventClickEvent");
|
|
}
|
|
|
|
// these delegates are required to keep context
|
|
this._mouseMoveDelegate = function(event) {
|
|
return that._mouseMove(event);
|
|
};
|
|
this._mouseUpDelegate = function(event) {
|
|
return that._mouseUp(event);
|
|
};
|
|
$(document)
|
|
.bind("mousemove."+this.widgetName, this._mouseMoveDelegate)
|
|
.bind("mouseup."+this.widgetName, this._mouseUpDelegate);
|
|
|
|
event.preventDefault();
|
|
|
|
mouseHandled = true;
|
|
return true;
|
|
},
|
|
|
|
_mouseMove: function(event) {
|
|
// IE mouseup check - mouseup happened when mouse was out of window
|
|
if ($.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && !event.button) {
|
|
return this._mouseUp(event);
|
|
}
|
|
|
|
if (this._mouseStarted) {
|
|
this._mouseDrag(event);
|
|
return event.preventDefault();
|
|
}
|
|
|
|
if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
|
|
this._mouseStarted =
|
|
(this._mouseStart(this._mouseDownEvent, event) !== false);
|
|
(this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event));
|
|
}
|
|
|
|
return !this._mouseStarted;
|
|
},
|
|
|
|
_mouseUp: function(event) {
|
|
$(document)
|
|
.unbind("mousemove."+this.widgetName, this._mouseMoveDelegate)
|
|
.unbind("mouseup."+this.widgetName, this._mouseUpDelegate);
|
|
|
|
if (this._mouseStarted) {
|
|
this._mouseStarted = false;
|
|
|
|
if (event.target === this._mouseDownEvent.target) {
|
|
$.data(event.target, this.widgetName + ".preventClickEvent", true);
|
|
}
|
|
|
|
this._mouseStop(event);
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
_mouseDistanceMet: function(event) {
|
|
return (Math.max(
|
|
Math.abs(this._mouseDownEvent.pageX - event.pageX),
|
|
Math.abs(this._mouseDownEvent.pageY - event.pageY)
|
|
) >= this.options.distance
|
|
);
|
|
},
|
|
|
|
_mouseDelayMet: function(/* event */) {
|
|
return this.mouseDelayMet;
|
|
},
|
|
|
|
// These are placeholder methods, to be overriden by extending plugin
|
|
_mouseStart: function(/* event */) {},
|
|
_mouseDrag: function(/* event */) {},
|
|
_mouseStop: function(/* event */) {},
|
|
_mouseCapture: function(/* event */) { return true; }
|
|
});
|
|
|
|
})(jQuery);
|
|
(function( $, undefined ) {
|
|
|
|
$.widget("ui.draggable", $.ui.mouse, {
|
|
version: "1.10.3",
|
|
widgetEventPrefix: "drag",
|
|
options: {
|
|
addClasses: true,
|
|
appendTo: "parent",
|
|
axis: false,
|
|
connectToSortable: false,
|
|
containment: false,
|
|
cursor: "auto",
|
|
cursorAt: false,
|
|
grid: false,
|
|
handle: false,
|
|
helper: "original",
|
|
iframeFix: false,
|
|
opacity: false,
|
|
refreshPositions: false,
|
|
revert: false,
|
|
revertDuration: 500,
|
|
scope: "default",
|
|
scroll: true,
|
|
scrollSensitivity: 20,
|
|
scrollSpeed: 20,
|
|
snap: false,
|
|
snapMode: "both",
|
|
snapTolerance: 20,
|
|
stack: false,
|
|
zIndex: false,
|
|
|
|
// callbacks
|
|
drag: null,
|
|
start: null,
|
|
stop: null
|
|
},
|
|
_create: function() {
|
|
|
|
if (this.options.helper === "original" && !(/^(?:r|a|f)/).test(this.element.css("position"))) {
|
|
this.element[0].style.position = "relative";
|
|
}
|
|
if (this.options.addClasses){
|
|
this.element.addClass("ui-draggable");
|
|
}
|
|
if (this.options.disabled){
|
|
this.element.addClass("ui-draggable-disabled");
|
|
}
|
|
|
|
this._mouseInit();
|
|
|
|
},
|
|
|
|
_destroy: function() {
|
|
this.element.removeClass( "ui-draggable ui-draggable-dragging ui-draggable-disabled" );
|
|
this._mouseDestroy();
|
|
},
|
|
|
|
_mouseCapture: function(event) {
|
|
|
|
var o = this.options;
|
|
|
|
// among others, prevent a drag on a resizable-handle
|
|
if (this.helper || o.disabled || $(event.target).closest(".ui-resizable-handle").length > 0) {
|
|
return false;
|
|
}
|
|
|
|
//Quit if we're not on a valid handle
|
|
this.handle = this._getHandle(event);
|
|
if (!this.handle) {
|
|
return false;
|
|
}
|
|
|
|
$(o.iframeFix === true ? "iframe" : o.iframeFix).each(function() {
|
|
$("<div class='ui-draggable-iframeFix' style='background: #fff;'></div>")
|
|
.css({
|
|
width: this.offsetWidth+"px", height: this.offsetHeight+"px",
|
|
position: "absolute", opacity: "0.001", zIndex: 1000
|
|
})
|
|
.css($(this).offset())
|
|
.appendTo("body");
|
|
});
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
_mouseStart: function(event) {
|
|
|
|
var o = this.options;
|
|
|
|
//Create and append the visible helper
|
|
this.helper = this._createHelper(event);
|
|
|
|
this.helper.addClass("ui-draggable-dragging");
|
|
|
|
//Cache the helper size
|
|
this._cacheHelperProportions();
|
|
|
|
//If ddmanager is used for droppables, set the global draggable
|
|
if($.ui.ddmanager) {
|
|
$.ui.ddmanager.current = this;
|
|
}
|
|
|
|
/*
|
|
* - Position generation -
|
|
* This block generates everything position related - it's the core of draggables.
|
|
*/
|
|
|
|
//Cache the margins of the original element
|
|
this._cacheMargins();
|
|
|
|
//Store the helper's css position
|
|
this.cssPosition = this.helper.css( "position" );
|
|
this.scrollParent = this.helper.scrollParent();
|
|
this.offsetParent = this.helper.offsetParent();
|
|
this.offsetParentCssPosition = this.offsetParent.css( "position" );
|
|
|
|
//The element's absolute position on the page minus margins
|
|
this.offset = this.positionAbs = this.element.offset();
|
|
this.offset = {
|
|
top: this.offset.top - this.margins.top,
|
|
left: this.offset.left - this.margins.left
|
|
};
|
|
|
|
//Reset scroll cache
|
|
this.offset.scroll = false;
|
|
|
|
$.extend(this.offset, {
|
|
click: { //Where the click happened, relative to the element
|
|
left: event.pageX - this.offset.left,
|
|
top: event.pageY - this.offset.top
|
|
},
|
|
parent: this._getParentOffset(),
|
|
relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
|
|
});
|
|
|
|
//Generate the original position
|
|
this.originalPosition = this.position = this._generatePosition(event);
|
|
this.originalPageX = event.pageX;
|
|
this.originalPageY = event.pageY;
|
|
|
|
//Adjust the mouse offset relative to the helper if "cursorAt" is supplied
|
|
(o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
|
|
|
|
//Set a containment if given in the options
|
|
this._setContainment();
|
|
|
|
//Trigger event + callbacks
|
|
if(this._trigger("start", event) === false) {
|
|
this._clear();
|
|
return false;
|
|
}
|
|
|
|
//Recache the helper size
|
|
this._cacheHelperProportions();
|
|
|
|
//Prepare the droppable offsets
|
|
if ($.ui.ddmanager && !o.dropBehaviour) {
|
|
$.ui.ddmanager.prepareOffsets(this, event);
|
|
}
|
|
|
|
|
|
this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position
|
|
|
|
//If the ddmanager is used for droppables, inform the manager that dragging has started (see #5003)
|
|
if ( $.ui.ddmanager ) {
|
|
$.ui.ddmanager.dragStart(this, event);
|
|
}
|
|
|
|
return true;
|
|
},
|
|
|
|
_mouseDrag: function(event, noPropagation) {
|
|
// reset any necessary cached properties (see #5009)
|
|
if ( this.offsetParentCssPosition === "fixed" ) {
|
|
this.offset.parent = this._getParentOffset();
|
|
}
|
|
|
|
//Compute the helpers position
|
|
this.position = this._generatePosition(event);
|
|
this.positionAbs = this._convertPositionTo("absolute");
|
|
|
|
//Call plugins and callbacks and use the resulting position if something is returned
|
|
if (!noPropagation) {
|
|
var ui = this._uiHash();
|
|
if(this._trigger("drag", event, ui) === false) {
|
|
this._mouseUp({});
|
|
return false;
|
|
}
|
|
this.position = ui.position;
|
|
}
|
|
|
|
if(!this.options.axis || this.options.axis !== "y") {
|
|
this.helper[0].style.left = this.position.left+"px";
|
|
}
|
|
if(!this.options.axis || this.options.axis !== "x") {
|
|
this.helper[0].style.top = this.position.top+"px";
|
|
}
|
|
if($.ui.ddmanager) {
|
|
$.ui.ddmanager.drag(this, event);
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
_mouseStop: function(event) {
|
|
|
|
//If we are using droppables, inform the manager about the drop
|
|
var that = this,
|
|
dropped = false;
|
|
if ($.ui.ddmanager && !this.options.dropBehaviour) {
|
|
dropped = $.ui.ddmanager.drop(this, event);
|
|
}
|
|
|
|
//if a drop comes from outside (a sortable)
|
|
if(this.dropped) {
|
|
dropped = this.dropped;
|
|
this.dropped = false;
|
|
}
|
|
|
|
//if the original element is no longer in the DOM don't bother to continue (see #8269)
|
|
if ( this.options.helper === "original" && !$.contains( this.element[ 0 ].ownerDocument, this.element[ 0 ] ) ) {
|
|
return false;
|
|
}
|
|
|
|
if((this.options.revert === "invalid" && !dropped) || (this.options.revert === "valid" && dropped) || this.options.revert === true || ($.isFunction(this.options.revert) && this.options.revert.call(this.element, dropped))) {
|
|
$(this.helper).animate(this.originalPosition, parseInt(this.options.revertDuration, 10), function() {
|
|
if(that._trigger("stop", event) !== false) {
|
|
that._clear();
|
|
}
|
|
});
|
|
} else {
|
|
if(this._trigger("stop", event) !== false) {
|
|
this._clear();
|
|
}
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
_mouseUp: function(event) {
|
|
//Remove frame helpers
|
|
$("div.ui-draggable-iframeFix").each(function() {
|
|
this.parentNode.removeChild(this);
|
|
});
|
|
|
|
//If the ddmanager is used for droppables, inform the manager that dragging has stopped (see #5003)
|
|
if( $.ui.ddmanager ) {
|
|
$.ui.ddmanager.dragStop(this, event);
|
|
}
|
|
|
|
return $.ui.mouse.prototype._mouseUp.call(this, event);
|
|
},
|
|
|
|
cancel: function() {
|
|
|
|
if(this.helper.is(".ui-draggable-dragging")) {
|
|
this._mouseUp({});
|
|
} else {
|
|
this._clear();
|
|
}
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
_getHandle: function(event) {
|
|
return this.options.handle ?
|
|
!!$( event.target ).closest( this.element.find( this.options.handle ) ).length :
|
|
true;
|
|
},
|
|
|
|
_createHelper: function(event) {
|
|
|
|
var o = this.options,
|
|
helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event])) : (o.helper === "clone" ? this.element.clone().removeAttr("id") : this.element);
|
|
|
|
if(!helper.parents("body").length) {
|
|
helper.appendTo((o.appendTo === "parent" ? this.element[0].parentNode : o.appendTo));
|
|
}
|
|
|
|
if(helper[0] !== this.element[0] && !(/(fixed|absolute)/).test(helper.css("position"))) {
|
|
helper.css("position", "absolute");
|
|
}
|
|
|
|
return helper;
|
|
|
|
},
|
|
|
|
_adjustOffsetFromHelper: function(obj) {
|
|
if (typeof obj === "string") {
|
|
obj = obj.split(" ");
|
|
}
|
|
if ($.isArray(obj)) {
|
|
obj = {left: +obj[0], top: +obj[1] || 0};
|
|
}
|
|
if ("left" in obj) {
|
|
this.offset.click.left = obj.left + this.margins.left;
|
|
}
|
|
if ("right" in obj) {
|
|
this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
|
|
}
|
|
if ("top" in obj) {
|
|
this.offset.click.top = obj.top + this.margins.top;
|
|
}
|
|
if ("bottom" in obj) {
|
|
this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
|
|
}
|
|
},
|
|
|
|
_getParentOffset: function() {
|
|
|
|
//Get the offsetParent and cache its position
|
|
var po = this.offsetParent.offset();
|
|
|
|
// This is a special case where we need to modify a offset calculated on start, since the following happened:
|
|
// 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
|
|
// 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
|
|
// the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
|
|
if(this.cssPosition === "absolute" && this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) {
|
|
po.left += this.scrollParent.scrollLeft();
|
|
po.top += this.scrollParent.scrollTop();
|
|
}
|
|
|
|
//This needs to be actually done for all browsers, since pageX/pageY includes this information
|
|
//Ugly IE fix
|
|
if((this.offsetParent[0] === document.body) ||
|
|
(this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() === "html" && $.ui.ie)) {
|
|
po = { top: 0, left: 0 };
|
|
}
|
|
|
|
return {
|
|
top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
|
|
left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
|
|
};
|
|
|
|
},
|
|
|
|
_getRelativeOffset: function() {
|
|
|
|
if(this.cssPosition === "relative") {
|
|
var p = this.element.position();
|
|
return {
|
|
top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
|
|
left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
|
|
};
|
|
} else {
|
|
return { top: 0, left: 0 };
|
|
}
|
|
|
|
},
|
|
|
|
_cacheMargins: function() {
|
|
this.margins = {
|
|
left: (parseInt(this.element.css("marginLeft"),10) || 0),
|
|
top: (parseInt(this.element.css("marginTop"),10) || 0),
|
|
right: (parseInt(this.element.css("marginRight"),10) || 0),
|
|
bottom: (parseInt(this.element.css("marginBottom"),10) || 0)
|
|
};
|
|
},
|
|
|
|
_cacheHelperProportions: function() {
|
|
this.helperProportions = {
|
|
width: this.helper.outerWidth(),
|
|
height: this.helper.outerHeight()
|
|
};
|
|
},
|
|
|
|
_setContainment: function() {
|
|
|
|
var over, c, ce,
|
|
o = this.options;
|
|
|
|
if ( !o.containment ) {
|
|
this.containment = null;
|
|
return;
|
|
}
|
|
|
|
if ( o.containment === "window" ) {
|
|
this.containment = [
|
|
$( window ).scrollLeft() - this.offset.relative.left - this.offset.parent.left,
|
|
$( window ).scrollTop() - this.offset.relative.top - this.offset.parent.top,
|
|
$( window ).scrollLeft() + $( window ).width() - this.helperProportions.width - this.margins.left,
|
|
$( window ).scrollTop() + ( $( window ).height() || document.body.parentNode.scrollHeight ) - this.helperProportions.height - this.margins.top
|
|
];
|
|
return;
|
|
}
|
|
|
|
if ( o.containment === "document") {
|
|
this.containment = [
|
|
0,
|
|
0,
|
|
$( document ).width() - this.helperProportions.width - this.margins.left,
|
|
( $( document ).height() || document.body.parentNode.scrollHeight ) - this.helperProportions.height - this.margins.top
|
|
];
|
|
return;
|
|
}
|
|
|
|
if ( o.containment.constructor === Array ) {
|
|
this.containment = o.containment;
|
|
return;
|
|
}
|
|
|
|
if ( o.containment === "parent" ) {
|
|
o.containment = this.helper[ 0 ].parentNode;
|
|
}
|
|
|
|
c = $( o.containment );
|
|
ce = c[ 0 ];
|
|
|
|
if( !ce ) {
|
|
return;
|
|
}
|
|
|
|
over = c.css( "overflow" ) !== "hidden";
|
|
|
|
this.containment = [
|
|
( parseInt( c.css( "borderLeftWidth" ), 10 ) || 0 ) + ( parseInt( c.css( "paddingLeft" ), 10 ) || 0 ),
|
|
( parseInt( c.css( "borderTopWidth" ), 10 ) || 0 ) + ( parseInt( c.css( "paddingTop" ), 10 ) || 0 ) ,
|
|
( over ? Math.max( ce.scrollWidth, ce.offsetWidth ) : ce.offsetWidth ) - ( parseInt( c.css( "borderRightWidth" ), 10 ) || 0 ) - ( parseInt( c.css( "paddingRight" ), 10 ) || 0 ) - this.helperProportions.width - this.margins.left - this.margins.right,
|
|
( over ? Math.max( ce.scrollHeight, ce.offsetHeight ) : ce.offsetHeight ) - ( parseInt( c.css( "borderBottomWidth" ), 10 ) || 0 ) - ( parseInt( c.css( "paddingBottom" ), 10 ) || 0 ) - this.helperProportions.height - this.margins.top - this.margins.bottom
|
|
];
|
|
this.relative_container = c;
|
|
},
|
|
|
|
_convertPositionTo: function(d, pos) {
|
|
|
|
if(!pos) {
|
|
pos = this.position;
|
|
}
|
|
|
|
var mod = d === "absolute" ? 1 : -1,
|
|
scroll = this.cssPosition === "absolute" && !( this.scrollParent[ 0 ] !== document && $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ? this.offsetParent : this.scrollParent;
|
|
|
|
//Cache the scroll
|
|
if (!this.offset.scroll) {
|
|
this.offset.scroll = {top : scroll.scrollTop(), left : scroll.scrollLeft()};
|
|
}
|
|
|
|
return {
|
|
top: (
|
|
pos.top + // The absolute mouse position
|
|
this.offset.relative.top * mod + // Only for relative positioned nodes: Relative offset from element to offset parent
|
|
this.offset.parent.top * mod - // The offsetParent's offset without borders (offset + border)
|
|
( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : this.offset.scroll.top ) * mod )
|
|
),
|
|
left: (
|
|
pos.left + // The absolute mouse position
|
|
this.offset.relative.left * mod + // Only for relative positioned nodes: Relative offset from element to offset parent
|
|
this.offset.parent.left * mod - // The offsetParent's offset without borders (offset + border)
|
|
( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : this.offset.scroll.left ) * mod )
|
|
)
|
|
};
|
|
|
|
},
|
|
|
|
_generatePosition: function(event) {
|
|
|
|
var containment, co, top, left,
|
|
o = this.options,
|
|
scroll = this.cssPosition === "absolute" && !( this.scrollParent[ 0 ] !== document && $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ? this.offsetParent : this.scrollParent,
|
|
pageX = event.pageX,
|
|
pageY = event.pageY;
|
|
|
|
//Cache the scroll
|
|
if (!this.offset.scroll) {
|
|
this.offset.scroll = {top : scroll.scrollTop(), left : scroll.scrollLeft()};
|
|
}
|
|
|
|
/*
|
|
* - Position constraining -
|
|
* Constrain the position to a mix of grid, containment.
|
|
*/
|
|
|
|
// If we are not dragging yet, we won't check for options
|
|
if ( this.originalPosition ) {
|
|
if ( this.containment ) {
|
|
if ( this.relative_container ){
|
|
co = this.relative_container.offset();
|
|
containment = [
|
|
this.containment[ 0 ] + co.left,
|
|
this.containment[ 1 ] + co.top,
|
|
this.containment[ 2 ] + co.left,
|
|
this.containment[ 3 ] + co.top
|
|
];
|
|
}
|
|
else {
|
|
containment = this.containment;
|
|
}
|
|
|
|
if(event.pageX - this.offset.click.left < containment[0]) {
|
|
pageX = containment[0] + this.offset.click.left;
|
|
}
|
|
if(event.pageY - this.offset.click.top < containment[1]) {
|
|
pageY = containment[1] + this.offset.click.top;
|
|
}
|
|
if(event.pageX - this.offset.click.left > containment[2]) {
|
|
pageX = containment[2] + this.offset.click.left;
|
|
}
|
|
if(event.pageY - this.offset.click.top > containment[3]) {
|
|
pageY = containment[3] + this.offset.click.top;
|
|
}
|
|
}
|
|
|
|
if(o.grid) {
|
|
//Check for grid elements set to 0 to prevent divide by 0 error causing invalid argument errors in IE (see ticket #6950)
|
|
top = o.grid[1] ? this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1] : this.originalPageY;
|
|
pageY = containment ? ((top - this.offset.click.top >= containment[1] || top - this.offset.click.top > containment[3]) ? top : ((top - this.offset.click.top >= containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;
|
|
|
|
left = o.grid[0] ? this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0] : this.originalPageX;
|
|
pageX = containment ? ((left - this.offset.click.left >= containment[0] || left - this.offset.click.left > containment[2]) ? left : ((left - this.offset.click.left >= containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
|
|
}
|
|
|
|
}
|
|
|
|
return {
|
|
top: (
|
|
pageY - // The absolute mouse position
|
|
this.offset.click.top - // Click offset (relative to the element)
|
|
this.offset.relative.top - // Only for relative positioned nodes: Relative offset from element to offset parent
|
|
this.offset.parent.top + // The offsetParent's offset without borders (offset + border)
|
|
( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : this.offset.scroll.top )
|
|
),
|
|
left: (
|
|
pageX - // The absolute mouse position
|
|
this.offset.click.left - // Click offset (relative to the element)
|
|
this.offset.relative.left - // Only for relative positioned nodes: Relative offset from element to offset parent
|
|
this.offset.parent.left + // The offsetParent's offset without borders (offset + border)
|
|
( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : this.offset.scroll.left )
|
|
)
|
|
};
|
|
|
|
},
|
|
|
|
_clear: function() {
|
|
this.helper.removeClass("ui-draggable-dragging");
|
|
if(this.helper[0] !== this.element[0] && !this.cancelHelperRemoval) {
|
|
this.helper.remove();
|
|
}
|
|
this.helper = null;
|
|
this.cancelHelperRemoval = false;
|
|
},
|
|
|
|
// From now on bulk stuff - mainly helpers
|
|
|
|
_trigger: function(type, event, ui) {
|
|
ui = ui || this._uiHash();
|
|
$.ui.plugin.call(this, type, [event, ui]);
|
|
//The absolute position has to be recalculated after plugins
|
|
if(type === "drag") {
|
|
this.positionAbs = this._convertPositionTo("absolute");
|
|
}
|
|
return $.Widget.prototype._trigger.call(this, type, event, ui);
|
|
},
|
|
|
|
plugins: {},
|
|
|
|
_uiHash: function() {
|
|
return {
|
|
helper: this.helper,
|
|
position: this.position,
|
|
originalPosition: this.originalPosition,
|
|
offset: this.positionAbs
|
|
};
|
|
}
|
|
|
|
});
|
|
|
|
$.ui.plugin.add("draggable", "connectToSortable", {
|
|
start: function(event, ui) {
|
|
|
|
var inst = $(this).data("ui-draggable"), o = inst.options,
|
|
uiSortable = $.extend({}, ui, { item: inst.element });
|
|
inst.sortables = [];
|
|
$(o.connectToSortable).each(function() {
|
|
var sortable = $.data(this, "ui-sortable");
|
|
if (sortable && !sortable.options.disabled) {
|
|
inst.sortables.push({
|
|
instance: sortable,
|
|
shouldRevert: sortable.options.revert
|
|
});
|
|
sortable.refreshPositions(); // Call the sortable's refreshPositions at drag start to refresh the containerCache since the sortable container cache is used in drag and needs to be up to date (this will ensure it's initialised as well as being kept in step with any changes that might have happened on the page).
|
|
sortable._trigger("activate", event, uiSortable);
|
|
}
|
|
});
|
|
|
|
},
|
|
stop: function(event, ui) {
|
|
|
|
//If we are still over the sortable, we fake the stop event of the sortable, but also remove helper
|
|
var inst = $(this).data("ui-draggable"),
|
|
uiSortable = $.extend({}, ui, { item: inst.element });
|
|
|
|
$.each(inst.sortables, function() {
|
|
if(this.instance.isOver) {
|
|
|
|
this.instance.isOver = 0;
|
|
|
|
inst.cancelHelperRemoval = true; //Don't remove the helper in the draggable instance
|
|
this.instance.cancelHelperRemoval = false; //Remove it in the sortable instance (so sortable plugins like revert still work)
|
|
|
|
//The sortable revert is supported, and we have to set a temporary dropped variable on the draggable to support revert: "valid/invalid"
|
|
if(this.shouldRevert) {
|
|
this.instance.options.revert = this.shouldRevert;
|
|
}
|
|
|
|
//Trigger the stop of the sortable
|
|
this.instance._mouseStop(event);
|
|
|
|
this.instance.options.helper = this.instance.options._helper;
|
|
|
|
//If the helper has been the original item, restore properties in the sortable
|
|
if(inst.options.helper === "original") {
|
|
this.instance.currentItem.css({ top: "auto", left: "auto" });
|
|
}
|
|
|
|
} else {
|
|
this.instance.cancelHelperRemoval = false; //Remove the helper in the sortable instance
|
|
this.instance._trigger("deactivate", event, uiSortable);
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
drag: function(event, ui) {
|
|
|
|
var inst = $(this).data("ui-draggable"), that = this;
|
|
|
|
$.each(inst.sortables, function() {
|
|
|
|
var innermostIntersecting = false,
|
|
thisSortable = this;
|
|
|
|
//Copy over some variables to allow calling the sortable's native _intersectsWith
|
|
this.instance.positionAbs = inst.positionAbs;
|
|
this.instance.helperProportions = inst.helperProportions;
|
|
this.instance.offset.click = inst.offset.click;
|
|
|
|
if(this.instance._intersectsWith(this.instance.containerCache)) {
|
|
innermostIntersecting = true;
|
|
$.each(inst.sortables, function () {
|
|
this.instance.positionAbs = inst.positionAbs;
|
|
this.instance.helperProportions = inst.helperProportions;
|
|
this.instance.offset.click = inst.offset.click;
|
|
if (this !== thisSortable &&
|
|
this.instance._intersectsWith(this.instance.containerCache) &&
|
|
$.contains(thisSortable.instance.element[0], this.instance.element[0])
|
|
) {
|
|
innermostIntersecting = false;
|
|
}
|
|
return innermostIntersecting;
|
|
});
|
|
}
|
|
|
|
|
|
if(innermostIntersecting) {
|
|
//If it intersects, we use a little isOver variable and set it once, so our move-in stuff gets fired only once
|
|
if(!this.instance.isOver) {
|
|
|
|
this.instance.isOver = 1;
|
|
//Now we fake the start of dragging for the sortable instance,
|
|
//by cloning the list group item, appending it to the sortable and using it as inst.currentItem
|
|
//We can then fire the start event of the sortable with our passed browser event, and our own helper (so it doesn't create a new one)
|
|
this.instance.currentItem = $(that).clone().removeAttr("id").appendTo(this.instance.element).data("ui-sortable-item", true);
|
|
this.instance.options._helper = this.instance.options.helper; //Store helper option to later restore it
|
|
this.instance.options.helper = function() { return ui.helper[0]; };
|
|
|
|
event.target = this.instance.currentItem[0];
|
|
this.instance._mouseCapture(event, true);
|
|
this.instance._mouseStart(event, true, true);
|
|
|
|
//Because the browser event is way off the new appended portlet, we modify a couple of variables to reflect the changes
|
|
this.instance.offset.click.top = inst.offset.click.top;
|
|
this.instance.offset.click.left = inst.offset.click.left;
|
|
this.instance.offset.parent.left -= inst.offset.parent.left - this.instance.offset.parent.left;
|
|
this.instance.offset.parent.top -= inst.offset.parent.top - this.instance.offset.parent.top;
|
|
|
|
inst._trigger("toSortable", event);
|
|
inst.dropped = this.instance.element; //draggable revert needs that
|
|
//hack so receive/update callbacks work (mostly)
|
|
inst.currentItem = inst.element;
|
|
this.instance.fromOutside = inst;
|
|
|
|
}
|
|
|
|
//Provided we did all the previous steps, we can fire the drag event of the sortable on every draggable drag, when it intersects with the sortable
|
|
if(this.instance.currentItem) {
|
|
this.instance._mouseDrag(event);
|
|
}
|
|
|
|
} else {
|
|
|
|
//If it doesn't intersect with the sortable, and it intersected before,
|
|
//we fake the drag stop of the sortable, but make sure it doesn't remove the helper by using cancelHelperRemoval
|
|
if(this.instance.isOver) {
|
|
|
|
this.instance.isOver = 0;
|
|
this.instance.cancelHelperRemoval = true;
|
|
|
|
//Prevent reverting on this forced stop
|
|
this.instance.options.revert = false;
|
|
|
|
// The out event needs to be triggered independently
|
|
this.instance._trigger("out", event, this.instance._uiHash(this.instance));
|
|
|
|
this.instance._mouseStop(event, true);
|
|
this.instance.options.helper = this.instance.options._helper;
|
|
|
|
//Now we remove our currentItem, the list group clone again, and the placeholder, and animate the helper back to it's original size
|
|
this.instance.currentItem.remove();
|
|
if(this.instance.placeholder) {
|
|
this.instance.placeholder.remove();
|
|
}
|
|
|
|
inst._trigger("fromSortable", event);
|
|
inst.dropped = false; //draggable revert needs that
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
});
|
|
|
|
$.ui.plugin.add("draggable", "cursor", {
|
|
start: function() {
|
|
var t = $("body"), o = $(this).data("ui-draggable").options;
|
|
if (t.css("cursor")) {
|
|
o._cursor = t.css("cursor");
|
|
}
|
|
t.css("cursor", o.cursor);
|
|
},
|
|
stop: function() {
|
|
var o = $(this).data("ui-draggable").options;
|
|
if (o._cursor) {
|
|
$("body").css("cursor", o._cursor);
|
|
}
|
|
}
|
|
});
|
|
|
|
$.ui.plugin.add("draggable", "opacity", {
|
|
start: function(event, ui) {
|
|
var t = $(ui.helper), o = $(this).data("ui-draggable").options;
|
|
if(t.css("opacity")) {
|
|
o._opacity = t.css("opacity");
|
|
}
|
|
t.css("opacity", o.opacity);
|
|
},
|
|
stop: function(event, ui) {
|
|
var o = $(this).data("ui-draggable").options;
|
|
if(o._opacity) {
|
|
$(ui.helper).css("opacity", o._opacity);
|
|
}
|
|
}
|
|
});
|
|
|
|
$.ui.plugin.add("draggable", "scroll", {
|
|
start: function() {
|
|
var i = $(this).data("ui-draggable");
|
|
if(i.scrollParent[0] !== document && i.scrollParent[0].tagName !== "HTML") {
|
|
i.overflowOffset = i.scrollParent.offset();
|
|
}
|
|
},
|
|
drag: function( event ) {
|
|
|
|
var i = $(this).data("ui-draggable"), o = i.options, scrolled = false;
|
|
|
|
if(i.scrollParent[0] !== document && i.scrollParent[0].tagName !== "HTML") {
|
|
|
|
if(!o.axis || o.axis !== "x") {
|
|
if((i.overflowOffset.top + i.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) {
|
|
i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop + o.scrollSpeed;
|
|
} else if(event.pageY - i.overflowOffset.top < o.scrollSensitivity) {
|
|
i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop - o.scrollSpeed;
|
|
}
|
|
}
|
|
|
|
if(!o.axis || o.axis !== "y") {
|
|
if((i.overflowOffset.left + i.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) {
|
|
i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft + o.scrollSpeed;
|
|
} else if(event.pageX - i.overflowOffset.left < o.scrollSensitivity) {
|
|
i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft - o.scrollSpeed;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
if(!o.axis || o.axis !== "x") {
|
|
if(event.pageY - $(document).scrollTop() < o.scrollSensitivity) {
|
|
scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
|
|
} else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) {
|
|
scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
|
|
}
|
|
}
|
|
|
|
if(!o.axis || o.axis !== "y") {
|
|
if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity) {
|
|
scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
|
|
} else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) {
|
|
scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) {
|
|
$.ui.ddmanager.prepareOffsets(i, event);
|
|
}
|
|
|
|
}
|
|
});
|
|
|
|
$.ui.plugin.add("draggable", "snap", {
|
|
start: function() {
|
|
|
|
var i = $(this).data("ui-draggable"),
|
|
o = i.options;
|
|
|
|
i.snapElements = [];
|
|
|
|
$(o.snap.constructor !== String ? ( o.snap.items || ":data(ui-draggable)" ) : o.snap).each(function() {
|
|
var $t = $(this),
|
|
$o = $t.offset();
|
|
if(this !== i.element[0]) {
|
|
i.snapElements.push({
|
|
item: this,
|
|
width: $t.outerWidth(), height: $t.outerHeight(),
|
|
top: $o.top, left: $o.left
|
|
});
|
|
}
|
|
});
|
|
|
|
},
|
|
drag: function(event, ui) {
|
|
|
|
var ts, bs, ls, rs, l, r, t, b, i, first,
|
|
inst = $(this).data("ui-draggable"),
|
|
o = inst.options,
|
|
d = o.snapTolerance,
|
|
x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width,
|
|
y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height;
|
|
|
|
for (i = inst.snapElements.length - 1; i >= 0; i--){
|
|
|
|
l = inst.snapElements[i].left;
|
|
r = l + inst.snapElements[i].width;
|
|
t = inst.snapElements[i].top;
|
|
b = t + inst.snapElements[i].height;
|
|
|
|
if ( x2 < l - d || x1 > r + d || y2 < t - d || y1 > b + d || !$.contains( inst.snapElements[ i ].item.ownerDocument, inst.snapElements[ i ].item ) ) {
|
|
if(inst.snapElements[i].snapping) {
|
|
(inst.options.snap.release && inst.options.snap.release.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
|
|
}
|
|
inst.snapElements[i].snapping = false;
|
|
continue;
|
|
}
|
|
|
|
if(o.snapMode !== "inner") {
|
|
ts = Math.abs(t - y2) <= d;
|
|
bs = Math.abs(b - y1) <= d;
|
|
ls = Math.abs(l - x2) <= d;
|
|
rs = Math.abs(r - x1) <= d;
|
|
if(ts) {
|
|
ui.position.top = inst._convertPositionTo("relative", { top: t - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
|
|
}
|
|
if(bs) {
|
|
ui.position.top = inst._convertPositionTo("relative", { top: b, left: 0 }).top - inst.margins.top;
|
|
}
|
|
if(ls) {
|
|
ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l - inst.helperProportions.width }).left - inst.margins.left;
|
|
}
|
|
if(rs) {
|
|
ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r }).left - inst.margins.left;
|
|
}
|
|
}
|
|
|
|
first = (ts || bs || ls || rs);
|
|
|
|
if(o.snapMode !== "outer") {
|
|
ts = Math.abs(t - y1) <= d;
|
|
bs = Math.abs(b - y2) <= d;
|
|
ls = Math.abs(l - x1) <= d;
|
|
rs = Math.abs(r - x2) <= d;
|
|
if(ts) {
|
|
ui.position.top = inst._convertPositionTo("relative", { top: t, left: 0 }).top - inst.margins.top;
|
|
}
|
|
if(bs) {
|
|
ui.position.top = inst._convertPositionTo("relative", { top: b - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
|
|
}
|
|
if(ls) {
|
|
ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l }).left - inst.margins.left;
|
|
}
|
|
if(rs) {
|
|
ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r - inst.helperProportions.width }).left - inst.margins.left;
|
|
}
|
|
}
|
|
|
|
if(!inst.snapElements[i].snapping && (ts || bs || ls || rs || first)) {
|
|
(inst.options.snap.snap && inst.options.snap.snap.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
|
|
}
|
|
inst.snapElements[i].snapping = (ts || bs || ls || rs || first);
|
|
|
|
}
|
|
|
|
}
|
|
});
|
|
|
|
$.ui.plugin.add("draggable", "stack", {
|
|
start: function() {
|
|
var min,
|
|
o = this.data("ui-draggable").options,
|
|
group = $.makeArray($(o.stack)).sort(function(a,b) {
|
|
return (parseInt($(a).css("zIndex"),10) || 0) - (parseInt($(b).css("zIndex"),10) || 0);
|
|
});
|
|
|
|
if (!group.length) { return; }
|
|
|
|
min = parseInt($(group[0]).css("zIndex"), 10) || 0;
|
|
$(group).each(function(i) {
|
|
$(this).css("zIndex", min + i);
|
|
});
|
|
this.css("zIndex", (min + group.length));
|
|
}
|
|
});
|
|
|
|
$.ui.plugin.add("draggable", "zIndex", {
|
|
start: function(event, ui) {
|
|
var t = $(ui.helper), o = $(this).data("ui-draggable").options;
|
|
if(t.css("zIndex")) {
|
|
o._zIndex = t.css("zIndex");
|
|
}
|
|
t.css("zIndex", o.zIndex);
|
|
},
|
|
stop: function(event, ui) {
|
|
var o = $(this).data("ui-draggable").options;
|
|
if(o._zIndex) {
|
|
$(ui.helper).css("zIndex", o._zIndex);
|
|
}
|
|
}
|
|
});
|
|
|
|
})(jQuery);
|
|
(function( $, undefined ) {
|
|
|
|
function isOverAxis( x, reference, size ) {
|
|
return ( x > reference ) && ( x < ( reference + size ) );
|
|
}
|
|
|
|
$.widget("ui.droppable", {
|
|
version: "1.10.3",
|
|
widgetEventPrefix: "drop",
|
|
options: {
|
|
accept: "*",
|
|
activeClass: false,
|
|
addClasses: true,
|
|
greedy: false,
|
|
hoverClass: false,
|
|
scope: "default",
|
|
tolerance: "intersect",
|
|
|
|
// callbacks
|
|
activate: null,
|
|
deactivate: null,
|
|
drop: null,
|
|
out: null,
|
|
over: null
|
|
},
|
|
_create: function() {
|
|
|
|
var o = this.options,
|
|
accept = o.accept;
|
|
|
|
this.isover = false;
|
|
this.isout = true;
|
|
|
|
this.accept = $.isFunction(accept) ? accept : function(d) {
|
|
return d.is(accept);
|
|
};
|
|
|
|
//Store the droppable's proportions
|
|
this.proportions = { width: this.element[0].offsetWidth, height: this.element[0].offsetHeight };
|
|
|
|
// Add the reference and positions to the manager
|
|
$.ui.ddmanager.droppables[o.scope] = $.ui.ddmanager.droppables[o.scope] || [];
|
|
$.ui.ddmanager.droppables[o.scope].push(this);
|
|
|
|
(o.addClasses && this.element.addClass("ui-droppable"));
|
|
|
|
},
|
|
|
|
_destroy: function() {
|
|
var i = 0,
|
|
drop = $.ui.ddmanager.droppables[this.options.scope];
|
|
|
|
for ( ; i < drop.length; i++ ) {
|
|
if ( drop[i] === this ) {
|
|
drop.splice(i, 1);
|
|
}
|
|
}
|
|
|
|
this.element.removeClass("ui-droppable ui-droppable-disabled");
|
|
},
|
|
|
|
_setOption: function(key, value) {
|
|
|
|
if(key === "accept") {
|
|
this.accept = $.isFunction(value) ? value : function(d) {
|
|
return d.is(value);
|
|
};
|
|
}
|
|
$.Widget.prototype._setOption.apply(this, arguments);
|
|
},
|
|
|
|
_activate: function(event) {
|
|
var draggable = $.ui.ddmanager.current;
|
|
if(this.options.activeClass) {
|
|
this.element.addClass(this.options.activeClass);
|
|
}
|
|
if(draggable){
|
|
this._trigger("activate", event, this.ui(draggable));
|
|
}
|
|
},
|
|
|
|
_deactivate: function(event) {
|
|
var draggable = $.ui.ddmanager.current;
|
|
if(this.options.activeClass) {
|
|
this.element.removeClass(this.options.activeClass);
|
|
}
|
|
if(draggable){
|
|
this._trigger("deactivate", event, this.ui(draggable));
|
|
}
|
|
},
|
|
|
|
_over: function(event) {
|
|
|
|
var draggable = $.ui.ddmanager.current;
|
|
|
|
// Bail if draggable and droppable are same element
|
|
if (!draggable || (draggable.currentItem || draggable.element)[0] === this.element[0]) {
|
|
return;
|
|
}
|
|
|
|
if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
|
|
if(this.options.hoverClass) {
|
|
this.element.addClass(this.options.hoverClass);
|
|
}
|
|
this._trigger("over", event, this.ui(draggable));
|
|
}
|
|
|
|
},
|
|
|
|
_out: function(event) {
|
|
|
|
var draggable = $.ui.ddmanager.current;
|
|
|
|
// Bail if draggable and droppable are same element
|
|
if (!draggable || (draggable.currentItem || draggable.element)[0] === this.element[0]) {
|
|
return;
|
|
}
|
|
|
|
if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
|
|
if(this.options.hoverClass) {
|
|
this.element.removeClass(this.options.hoverClass);
|
|
}
|
|
this._trigger("out", event, this.ui(draggable));
|
|
}
|
|
|
|
},
|
|
|
|
_drop: function(event,custom) {
|
|
|
|
var draggable = custom || $.ui.ddmanager.current,
|
|
childrenIntersection = false;
|
|
|
|
// Bail if draggable and droppable are same element
|
|
if (!draggable || (draggable.currentItem || draggable.element)[0] === this.element[0]) {
|
|
return false;
|
|
}
|
|
|
|
this.element.find(":data(ui-droppable)").not(".ui-draggable-dragging").each(function() {
|
|
var inst = $.data(this, "ui-droppable");
|
|
if(
|
|
inst.options.greedy &&
|
|
!inst.options.disabled &&
|
|
inst.options.scope === draggable.options.scope &&
|
|
inst.accept.call(inst.element[0], (draggable.currentItem || draggable.element)) &&
|
|
$.ui.intersect(draggable, $.extend(inst, { offset: inst.element.offset() }), inst.options.tolerance)
|
|
) { childrenIntersection = true; return false; }
|
|
});
|
|
if(childrenIntersection) {
|
|
return false;
|
|
}
|
|
|
|
if(this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
|
|
if(this.options.activeClass) {
|
|
this.element.removeClass(this.options.activeClass);
|
|
}
|
|
if(this.options.hoverClass) {
|
|
this.element.removeClass(this.options.hoverClass);
|
|
}
|
|
this._trigger("drop", event, this.ui(draggable));
|
|
return this.element;
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
ui: function(c) {
|
|
return {
|
|
draggable: (c.currentItem || c.element),
|
|
helper: c.helper,
|
|
position: c.position,
|
|
offset: c.positionAbs
|
|
};
|
|
}
|
|
|
|
});
|
|
|
|
$.ui.intersect = function(draggable, droppable, toleranceMode) {
|
|
|
|
if (!droppable.offset) {
|
|
return false;
|
|
}
|
|
|
|
var draggableLeft, draggableTop,
|
|
x1 = (draggable.positionAbs || draggable.position.absolute).left, x2 = x1 + draggable.helperProportions.width,
|
|
y1 = (draggable.positionAbs || draggable.position.absolute).top, y2 = y1 + draggable.helperProportions.height,
|
|
l = droppable.offset.left, r = l + droppable.proportions.width,
|
|
t = droppable.offset.top, b = t + droppable.proportions.height;
|
|
|
|
switch (toleranceMode) {
|
|
case "fit":
|
|
return (l <= x1 && x2 <= r && t <= y1 && y2 <= b);
|
|
case "intersect":
|
|
return (l < x1 + (draggable.helperProportions.width / 2) && // Right Half
|
|
x2 - (draggable.helperProportions.width / 2) < r && // Left Half
|
|
t < y1 + (draggable.helperProportions.height / 2) && // Bottom Half
|
|
y2 - (draggable.helperProportions.height / 2) < b ); // Top Half
|
|
case "pointer":
|
|
draggableLeft = ((draggable.positionAbs || draggable.position.absolute).left + (draggable.clickOffset || draggable.offset.click).left);
|
|
draggableTop = ((draggable.positionAbs || draggable.position.absolute).top + (draggable.clickOffset || draggable.offset.click).top);
|
|
return isOverAxis( draggableTop, t, droppable.proportions.height ) && isOverAxis( draggableLeft, l, droppable.proportions.width );
|
|
case "touch":
|
|
return (
|
|
(y1 >= t && y1 <= b) || // Top edge touching
|
|
(y2 >= t && y2 <= b) || // Bottom edge touching
|
|
(y1 < t && y2 > b) // Surrounded vertically
|
|
) && (
|
|
(x1 >= l && x1 <= r) || // Left edge touching
|
|
(x2 >= l && x2 <= r) || // Right edge touching
|
|
(x1 < l && x2 > r) // Surrounded horizontally
|
|
);
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
};
|
|
|
|
/*
|
|
This manager tracks offsets of draggables and droppables
|
|
*/
|
|
$.ui.ddmanager = {
|
|
current: null,
|
|
droppables: { "default": [] },
|
|
prepareOffsets: function(t, event) {
|
|
|
|
var i, j,
|
|
m = $.ui.ddmanager.droppables[t.options.scope] || [],
|
|
type = event ? event.type : null, // workaround for #2317
|
|
list = (t.currentItem || t.element).find(":data(ui-droppable)").addBack();
|
|
|
|
droppablesLoop: for (i = 0; i < m.length; i++) {
|
|
|
|
//No disabled and non-accepted
|
|
if(m[i].options.disabled || (t && !m[i].accept.call(m[i].element[0],(t.currentItem || t.element)))) {
|
|
continue;
|
|
}
|
|
|
|
// Filter out elements in the current dragged item
|
|
for (j=0; j < list.length; j++) {
|
|
if(list[j] === m[i].element[0]) {
|
|
m[i].proportions.height = 0;
|
|
continue droppablesLoop;
|
|
}
|
|
}
|
|
|
|
m[i].visible = m[i].element.css("display") !== "none";
|
|
if(!m[i].visible) {
|
|
continue;
|
|
}
|
|
|
|
//Activate the droppable if used directly from draggables
|
|
if(type === "mousedown") {
|
|
m[i]._activate.call(m[i], event);
|
|
}
|
|
|
|
m[i].offset = m[i].element.offset();
|
|
m[i].proportions = { width: m[i].element[0].offsetWidth, height: m[i].element[0].offsetHeight };
|
|
|
|
}
|
|
|
|
},
|
|
drop: function(draggable, event) {
|
|
|
|
var dropped = false;
|
|
// Create a copy of the droppables in case the list changes during the drop (#9116)
|
|
$.each(($.ui.ddmanager.droppables[draggable.options.scope] || []).slice(), function() {
|
|
|
|
if(!this.options) {
|
|
return;
|
|
}
|
|
if (!this.options.disabled && this.visible && $.ui.intersect(draggable, this, this.options.tolerance)) {
|
|
dropped = this._drop.call(this, event) || dropped;
|
|
}
|
|
|
|
if (!this.options.disabled && this.visible && this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
|
|
this.isout = true;
|
|
this.isover = false;
|
|
this._deactivate.call(this, event);
|
|
}
|
|
|
|
});
|
|
return dropped;
|
|
|
|
},
|
|
dragStart: function( draggable, event ) {
|
|
//Listen for scrolling so that if the dragging causes scrolling the position of the droppables can be recalculated (see #5003)
|
|
draggable.element.parentsUntil( "body" ).bind( "scroll.droppable", function() {
|
|
if( !draggable.options.refreshPositions ) {
|
|
$.ui.ddmanager.prepareOffsets( draggable, event );
|
|
}
|
|
});
|
|
},
|
|
drag: function(draggable, event) {
|
|
|
|
//If you have a highly dynamic page, you might try this option. It renders positions every time you move the mouse.
|
|
if(draggable.options.refreshPositions) {
|
|
$.ui.ddmanager.prepareOffsets(draggable, event);
|
|
}
|
|
|
|
//Run through all droppables and check their positions based on specific tolerance options
|
|
$.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() {
|
|
|
|
if(this.options.disabled || this.greedyChild || !this.visible) {
|
|
return;
|
|
}
|
|
|
|
var parentInstance, scope, parent,
|
|
intersects = $.ui.intersect(draggable, this, this.options.tolerance),
|
|
c = !intersects && this.isover ? "isout" : (intersects && !this.isover ? "isover" : null);
|
|
if(!c) {
|
|
return;
|
|
}
|
|
|
|
if (this.options.greedy) {
|
|
// find droppable parents with same scope
|
|
scope = this.options.scope;
|
|
parent = this.element.parents(":data(ui-droppable)").filter(function () {
|
|
return $.data(this, "ui-droppable").options.scope === scope;
|
|
});
|
|
|
|
if (parent.length) {
|
|
parentInstance = $.data(parent[0], "ui-droppable");
|
|
parentInstance.greedyChild = (c === "isover");
|
|
}
|
|
}
|
|
|
|
// we just moved into a greedy child
|
|
if (parentInstance && c === "isover") {
|
|
parentInstance.isover = false;
|
|
parentInstance.isout = true;
|
|
parentInstance._out.call(parentInstance, event);
|
|
}
|
|
|
|
this[c] = true;
|
|
this[c === "isout" ? "isover" : "isout"] = false;
|
|
this[c === "isover" ? "_over" : "_out"].call(this, event);
|
|
|
|
// we just moved out of a greedy child
|
|
if (parentInstance && c === "isout") {
|
|
parentInstance.isout = false;
|
|
parentInstance.isover = true;
|
|
parentInstance._over.call(parentInstance, event);
|
|
}
|
|
});
|
|
|
|
},
|
|
dragStop: function( draggable, event ) {
|
|
draggable.element.parentsUntil( "body" ).unbind( "scroll.droppable" );
|
|
//Call prepareOffsets one final time since IE does not fire return scroll events when overflow was caused by drag (see #5003)
|
|
if( !draggable.options.refreshPositions ) {
|
|
$.ui.ddmanager.prepareOffsets( draggable, event );
|
|
}
|
|
}
|
|
};
|
|
|
|
})(jQuery);
|
|
(function( $, undefined ) {
|
|
|
|
function num(v) {
|
|
return parseInt(v, 10) || 0;
|
|
}
|
|
|
|
function isNumber(value) {
|
|
return !isNaN(parseInt(value, 10));
|
|
}
|
|
|
|
$.widget("ui.resizable", $.ui.mouse, {
|
|
version: "1.10.3",
|
|
widgetEventPrefix: "resize",
|
|
options: {
|
|
alsoResize: false,
|
|
animate: false,
|
|
animateDuration: "slow",
|
|
animateEasing: "swing",
|
|
aspectRatio: false,
|
|
autoHide: false,
|
|
containment: false,
|
|
ghost: false,
|
|
grid: false,
|
|
handles: "e,s,se",
|
|
helper: false,
|
|
maxHeight: null,
|
|
maxWidth: null,
|
|
minHeight: 10,
|
|
minWidth: 10,
|
|
// See #7960
|
|
zIndex: 90,
|
|
|
|
// callbacks
|
|
resize: null,
|
|
start: null,
|
|
stop: null
|
|
},
|
|
_create: function() {
|
|
|
|
var n, i, handle, axis, hname,
|
|
that = this,
|
|
o = this.options;
|
|
this.element.addClass("ui-resizable");
|
|
|
|
$.extend(this, {
|
|
_aspectRatio: !!(o.aspectRatio),
|
|
aspectRatio: o.aspectRatio,
|
|
originalElement: this.element,
|
|
_proportionallyResizeElements: [],
|
|
_helper: o.helper || o.ghost || o.animate ? o.helper || "ui-resizable-helper" : null
|
|
});
|
|
|
|
//Wrap the element if it cannot hold child nodes
|
|
if(this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)) {
|
|
|
|
//Create a wrapper element and set the wrapper to the new current internal element
|
|
this.element.wrap(
|
|
$("<div class='ui-wrapper' style='overflow: hidden;'></div>").css({
|
|
position: this.element.css("position"),
|
|
width: this.element.outerWidth(),
|
|
height: this.element.outerHeight(),
|
|
top: this.element.css("top"),
|
|
left: this.element.css("left")
|
|
})
|
|
);
|
|
|
|
//Overwrite the original this.element
|
|
this.element = this.element.parent().data(
|
|
"ui-resizable", this.element.data("ui-resizable")
|
|
);
|
|
|
|
this.elementIsWrapper = true;
|
|
|
|
//Move margins to the wrapper
|
|
this.element.css({ marginLeft: this.originalElement.css("marginLeft"), marginTop: this.originalElement.css("marginTop"), marginRight: this.originalElement.css("marginRight"), marginBottom: this.originalElement.css("marginBottom") });
|
|
this.originalElement.css({ marginLeft: 0, marginTop: 0, marginRight: 0, marginBottom: 0});
|
|
|
|
//Prevent Safari textarea resize
|
|
this.originalResizeStyle = this.originalElement.css("resize");
|
|
this.originalElement.css("resize", "none");
|
|
|
|
//Push the actual element to our proportionallyResize internal array
|
|
this._proportionallyResizeElements.push(this.originalElement.css({ position: "static", zoom: 1, display: "block" }));
|
|
|
|
// avoid IE jump (hard set the margin)
|
|
this.originalElement.css({ margin: this.originalElement.css("margin") });
|
|
|
|
// fix handlers offset
|
|
this._proportionallyResize();
|
|
|
|
}
|
|
|
|
this.handles = o.handles || (!$(".ui-resizable-handle", this.element).length ? "e,s,se" : { n: ".ui-resizable-n", e: ".ui-resizable-e", s: ".ui-resizable-s", w: ".ui-resizable-w", se: ".ui-resizable-se", sw: ".ui-resizable-sw", ne: ".ui-resizable-ne", nw: ".ui-resizable-nw" });
|
|
if(this.handles.constructor === String) {
|
|
|
|
if ( this.handles === "all") {
|
|
this.handles = "n,e,s,w,se,sw,ne,nw";
|
|
}
|
|
|
|
n = this.handles.split(",");
|
|
this.handles = {};
|
|
|
|
for(i = 0; i < n.length; i++) {
|
|
|
|
handle = $.trim(n[i]);
|
|
hname = "ui-resizable-"+handle;
|
|
axis = $("<div class='ui-resizable-handle " + hname + "'></div>");
|
|
|
|
// Apply zIndex to all handles - see #7960
|
|
axis.css({ zIndex: o.zIndex });
|
|
|
|
//TODO : What's going on here?
|
|
if ("se" === handle) {
|
|
axis.addClass("ui-icon ui-icon-gripsmall-diagonal-se");
|
|
}
|
|
|
|
//Insert into internal handles object and append to element
|
|
this.handles[handle] = ".ui-resizable-"+handle;
|
|
this.element.append(axis);
|
|
}
|
|
|
|
}
|
|
|
|
this._renderAxis = function(target) {
|
|
|
|
var i, axis, padPos, padWrapper;
|
|
|
|
target = target || this.element;
|
|
|
|
for(i in this.handles) {
|
|
|
|
if(this.handles[i].constructor === String) {
|
|
this.handles[i] = $(this.handles[i], this.element).show();
|
|
}
|
|
|
|
//Apply pad to wrapper element, needed to fix axis position (textarea, inputs, scrolls)
|
|
if (this.elementIsWrapper && this.originalElement[0].nodeName.match(/textarea|input|select|button/i)) {
|
|
|
|
axis = $(this.handles[i], this.element);
|
|
|
|
//Checking the correct pad and border
|
|
padWrapper = /sw|ne|nw|se|n|s/.test(i) ? axis.outerHeight() : axis.outerWidth();
|
|
|
|
//The padding type i have to apply...
|
|
padPos = [ "padding",
|
|
/ne|nw|n/.test(i) ? "Top" :
|
|
/se|sw|s/.test(i) ? "Bottom" :
|
|
/^e$/.test(i) ? "Right" : "Left" ].join("");
|
|
|
|
target.css(padPos, padWrapper);
|
|
|
|
this._proportionallyResize();
|
|
|
|
}
|
|
|
|
//TODO: What's that good for? There's not anything to be executed left
|
|
if(!$(this.handles[i]).length) {
|
|
continue;
|
|
}
|
|
}
|
|
};
|
|
|
|
//TODO: make renderAxis a prototype function
|
|
this._renderAxis(this.element);
|
|
|
|
this._handles = $(".ui-resizable-handle", this.element)
|
|
.disableSelection();
|
|
|
|
//Matching axis name
|
|
this._handles.mouseover(function() {
|
|
if (!that.resizing) {
|
|
if (this.className) {
|
|
axis = this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);
|
|
}
|
|
//Axis, default = se
|
|
that.axis = axis && axis[1] ? axis[1] : "se";
|
|
}
|
|
});
|
|
|
|
//If we want to auto hide the elements
|
|
if (o.autoHide) {
|
|
this._handles.hide();
|
|
$(this.element)
|
|
.addClass("ui-resizable-autohide")
|
|
.mouseenter(function() {
|
|
if (o.disabled) {
|
|
return;
|
|
}
|
|
$(this).removeClass("ui-resizable-autohide");
|
|
that._handles.show();
|
|
})
|
|
.mouseleave(function(){
|
|
if (o.disabled) {
|
|
return;
|
|
}
|
|
if (!that.resizing) {
|
|
$(this).addClass("ui-resizable-autohide");
|
|
that._handles.hide();
|
|
}
|
|
});
|
|
}
|
|
|
|
//Initialize the mouse interaction
|
|
this._mouseInit();
|
|
|
|
},
|
|
|
|
_destroy: function() {
|
|
|
|
this._mouseDestroy();
|
|
|
|
var wrapper,
|
|
_destroy = function(exp) {
|
|
$(exp).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing")
|
|
.removeData("resizable").removeData("ui-resizable").unbind(".resizable").find(".ui-resizable-handle").remove();
|
|
};
|
|
|
|
//TODO: Unwrap at same DOM position
|
|
if (this.elementIsWrapper) {
|
|
_destroy(this.element);
|
|
wrapper = this.element;
|
|
this.originalElement.css({
|
|
position: wrapper.css("position"),
|
|
width: wrapper.outerWidth(),
|
|
height: wrapper.outerHeight(),
|
|
top: wrapper.css("top"),
|
|
left: wrapper.css("left")
|
|
}).insertAfter( wrapper );
|
|
wrapper.remove();
|
|
}
|
|
|
|
this.originalElement.css("resize", this.originalResizeStyle);
|
|
_destroy(this.originalElement);
|
|
|
|
return this;
|
|
},
|
|
|
|
_mouseCapture: function(event) {
|
|
var i, handle,
|
|
capture = false;
|
|
|
|
for (i in this.handles) {
|
|
handle = $(this.handles[i])[0];
|
|
if (handle === event.target || $.contains(handle, event.target)) {
|
|
capture = true;
|
|
}
|
|
}
|
|
|
|
return !this.options.disabled && capture;
|
|
},
|
|
|
|
_mouseStart: function(event) {
|
|
|
|
var curleft, curtop, cursor,
|
|
o = this.options,
|
|
iniPos = this.element.position(),
|
|
el = this.element;
|
|
|
|
this.resizing = true;
|
|
|
|
// bugfix for http://dev.jquery.com/ticket/1749
|
|
if ( (/absolute/).test( el.css("position") ) ) {
|
|
el.css({ position: "absolute", top: el.css("top"), left: el.css("left") });
|
|
} else if (el.is(".ui-draggable")) {
|
|
el.css({ position: "absolute", top: iniPos.top, left: iniPos.left });
|
|
}
|
|
|
|
this._renderProxy();
|
|
|
|
curleft = num(this.helper.css("left"));
|
|
curtop = num(this.helper.css("top"));
|
|
|
|
if (o.containment) {
|
|
curleft += $(o.containment).scrollLeft() || 0;
|
|
curtop += $(o.containment).scrollTop() || 0;
|
|
}
|
|
|
|
//Store needed variables
|
|
this.offset = this.helper.offset();
|
|
this.position = { left: curleft, top: curtop };
|
|
this.size = this._helper ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() };
|
|
this.originalSize = this._helper ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() };
|
|
this.originalPosition = { left: curleft, top: curtop };
|
|
this.sizeDiff = { width: el.outerWidth() - el.width(), height: el.outerHeight() - el.height() };
|
|
this.originalMousePosition = { left: event.pageX, top: event.pageY };
|
|
|
|
//Aspect Ratio
|
|
this.aspectRatio = (typeof o.aspectRatio === "number") ? o.aspectRatio : ((this.originalSize.width / this.originalSize.height) || 1);
|
|
|
|
cursor = $(".ui-resizable-" + this.axis).css("cursor");
|
|
$("body").css("cursor", cursor === "auto" ? this.axis + "-resize" : cursor);
|
|
|
|
el.addClass("ui-resizable-resizing");
|
|
this._propagate("start", event);
|
|
return true;
|
|
},
|
|
|
|
_mouseDrag: function(event) {
|
|
|
|
//Increase performance, avoid regex
|
|
var data,
|
|
el = this.helper, props = {},
|
|
smp = this.originalMousePosition,
|
|
a = this.axis,
|
|
prevTop = this.position.top,
|
|
prevLeft = this.position.left,
|
|
prevWidth = this.size.width,
|
|
prevHeight = this.size.height,
|
|
dx = (event.pageX-smp.left)||0,
|
|
dy = (event.pageY-smp.top)||0,
|
|
trigger = this._change[a];
|
|
|
|
if (!trigger) {
|
|
return false;
|
|
}
|
|
|
|
// Calculate the attrs that will be change
|
|
data = trigger.apply(this, [event, dx, dy]);
|
|
|
|
// Put this in the mouseDrag handler since the user can start pressing shift while resizing
|
|
this._updateVirtualBoundaries(event.shiftKey);
|
|
if (this._aspectRatio || event.shiftKey) {
|
|
data = this._updateRatio(data, event);
|
|
}
|
|
|
|
data = this._respectSize(data, event);
|
|
|
|
this._updateCache(data);
|
|
|
|
// plugins callbacks need to be called first
|
|
this._propagate("resize", event);
|
|
|
|
if (this.position.top !== prevTop) {
|
|
props.top = this.position.top + "px";
|
|
}
|
|
if (this.position.left !== prevLeft) {
|
|
props.left = this.position.left + "px";
|
|
}
|
|
if (this.size.width !== prevWidth) {
|
|
props.width = this.size.width + "px";
|
|
}
|
|
if (this.size.height !== prevHeight) {
|
|
props.height = this.size.height + "px";
|
|
}
|
|
el.css(props);
|
|
|
|
if (!this._helper && this._proportionallyResizeElements.length) {
|
|
this._proportionallyResize();
|
|
}
|
|
|
|
// Call the user callback if the element was resized
|
|
if ( ! $.isEmptyObject(props) ) {
|
|
this._trigger("resize", event, this.ui());
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
_mouseStop: function(event) {
|
|
|
|
this.resizing = false;
|
|
var pr, ista, soffseth, soffsetw, s, left, top,
|
|
o = this.options, that = this;
|
|
|
|
if(this._helper) {
|
|
|
|
pr = this._proportionallyResizeElements;
|
|
ista = pr.length && (/textarea/i).test(pr[0].nodeName);
|
|
soffseth = ista && $.ui.hasScroll(pr[0], "left") /* TODO - jump height */ ? 0 : that.sizeDiff.height;
|
|
soffsetw = ista ? 0 : that.sizeDiff.width;
|
|
|
|
s = { width: (that.helper.width() - soffsetw), height: (that.helper.height() - soffseth) };
|
|
left = (parseInt(that.element.css("left"), 10) + (that.position.left - that.originalPosition.left)) || null;
|
|
top = (parseInt(that.element.css("top"), 10) + (that.position.top - that.originalPosition.top)) || null;
|
|
|
|
if (!o.animate) {
|
|
this.element.css($.extend(s, { top: top, left: left }));
|
|
}
|
|
|
|
that.helper.height(that.size.height);
|
|
that.helper.width(that.size.width);
|
|
|
|
if (this._helper && !o.animate) {
|
|
this._proportionallyResize();
|
|
}
|
|
}
|
|
|
|
$("body").css("cursor", "auto");
|
|
|
|
this.element.removeClass("ui-resizable-resizing");
|
|
|
|
this._propagate("stop", event);
|
|
|
|
if (this._helper) {
|
|
this.helper.remove();
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
_updateVirtualBoundaries: function(forceAspectRatio) {
|
|
var pMinWidth, pMaxWidth, pMinHeight, pMaxHeight, b,
|
|
o = this.options;
|
|
|
|
b = {
|
|
minWidth: isNumber(o.minWidth) ? o.minWidth : 0,
|
|
maxWidth: isNumber(o.maxWidth) ? o.maxWidth : Infinity,
|
|
minHeight: isNumber(o.minHeight) ? o.minHeight : 0,
|
|
maxHeight: isNumber(o.maxHeight) ? o.maxHeight : Infinity
|
|
};
|
|
|
|
if(this._aspectRatio || forceAspectRatio) {
|
|
// We want to create an enclosing box whose aspect ration is the requested one
|
|
// First, compute the "projected" size for each dimension based on the aspect ratio and other dimension
|
|
pMinWidth = b.minHeight * this.aspectRatio;
|
|
pMinHeight = b.minWidth / this.aspectRatio;
|
|
pMaxWidth = b.maxHeight * this.aspectRatio;
|
|
pMaxHeight = b.maxWidth / this.aspectRatio;
|
|
|
|
if(pMinWidth > b.minWidth) {
|
|
b.minWidth = pMinWidth;
|
|
}
|
|
if(pMinHeight > b.minHeight) {
|
|
b.minHeight = pMinHeight;
|
|
}
|
|
if(pMaxWidth < b.maxWidth) {
|
|
b.maxWidth = pMaxWidth;
|
|
}
|
|
if(pMaxHeight < b.maxHeight) {
|
|
b.maxHeight = pMaxHeight;
|
|
}
|
|
}
|
|
this._vBoundaries = b;
|
|
},
|
|
|
|
_updateCache: function(data) {
|
|
this.offset = this.helper.offset();
|
|
if (isNumber(data.left)) {
|
|
this.position.left = data.left;
|
|
}
|
|
if (isNumber(data.top)) {
|
|
this.position.top = data.top;
|
|
}
|
|
if (isNumber(data.height)) {
|
|
this.size.height = data.height;
|
|
}
|
|
if (isNumber(data.width)) {
|
|
this.size.width = data.width;
|
|
}
|
|
},
|
|
|
|
_updateRatio: function( data ) {
|
|
|
|
var cpos = this.position,
|
|
csize = this.size,
|
|
a = this.axis;
|
|
|
|
if (isNumber(data.height)) {
|
|
data.width = (data.height * this.aspectRatio);
|
|
} else if (isNumber(data.width)) {
|
|
data.height = (data.width / this.aspectRatio);
|
|
}
|
|
|
|
if (a === "sw") {
|
|
data.left = cpos.left + (csize.width - data.width);
|
|
data.top = null;
|
|
}
|
|
if (a === "nw") {
|
|
data.top = cpos.top + (csize.height - data.height);
|
|
data.left = cpos.left + (csize.width - data.width);
|
|
}
|
|
|
|
return data;
|
|
},
|
|
|
|
_respectSize: function( data ) {
|
|
|
|
var o = this._vBoundaries,
|
|
a = this.axis,
|
|
ismaxw = isNumber(data.width) && o.maxWidth && (o.maxWidth < data.width), ismaxh = isNumber(data.height) && o.maxHeight && (o.maxHeight < data.height),
|
|
isminw = isNumber(data.width) && o.minWidth && (o.minWidth > data.width), isminh = isNumber(data.height) && o.minHeight && (o.minHeight > data.height),
|
|
dw = this.originalPosition.left + this.originalSize.width,
|
|
dh = this.position.top + this.size.height,
|
|
cw = /sw|nw|w/.test(a), ch = /nw|ne|n/.test(a);
|
|
if (isminw) {
|
|
data.width = o.minWidth;
|
|
}
|
|
if (isminh) {
|
|
data.height = o.minHeight;
|
|
}
|
|
if (ismaxw) {
|
|
data.width = o.maxWidth;
|
|
}
|
|
if (ismaxh) {
|
|
data.height = o.maxHeight;
|
|
}
|
|
|
|
if (isminw && cw) {
|
|
data.left = dw - o.minWidth;
|
|
}
|
|
if (ismaxw && cw) {
|
|
data.left = dw - o.maxWidth;
|
|
}
|
|
if (isminh && ch) {
|
|
data.top = dh - o.minHeight;
|
|
}
|
|
if (ismaxh && ch) {
|
|
data.top = dh - o.maxHeight;
|
|
}
|
|
|
|
// fixing jump error on top/left - bug #2330
|
|
if (!data.width && !data.height && !data.left && data.top) {
|
|
data.top = null;
|
|
} else if (!data.width && !data.height && !data.top && data.left) {
|
|
data.left = null;
|
|
}
|
|
|
|
return data;
|
|
},
|
|
|
|
_proportionallyResize: function() {
|
|
|
|
if (!this._proportionallyResizeElements.length) {
|
|
return;
|
|
}
|
|
|
|
var i, j, borders, paddings, prel,
|
|
element = this.helper || this.element;
|
|
|
|
for ( i=0; i < this._proportionallyResizeElements.length; i++) {
|
|
|
|
prel = this._proportionallyResizeElements[i];
|
|
|
|
if (!this.borderDif) {
|
|
this.borderDif = [];
|
|
borders = [prel.css("borderTopWidth"), prel.css("borderRightWidth"), prel.css("borderBottomWidth"), prel.css("borderLeftWidth")];
|
|
paddings = [prel.css("paddingTop"), prel.css("paddingRight"), prel.css("paddingBottom"), prel.css("paddingLeft")];
|
|
|
|
for ( j = 0; j < borders.length; j++ ) {
|
|
this.borderDif[ j ] = ( parseInt( borders[ j ], 10 ) || 0 ) + ( parseInt( paddings[ j ], 10 ) || 0 );
|
|
}
|
|
}
|
|
|
|
prel.css({
|
|
height: (element.height() - this.borderDif[0] - this.borderDif[2]) || 0,
|
|
width: (element.width() - this.borderDif[1] - this.borderDif[3]) || 0
|
|
});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
_renderProxy: function() {
|
|
|
|
var el = this.element, o = this.options;
|
|
this.elementOffset = el.offset();
|
|
|
|
if(this._helper) {
|
|
|
|
this.helper = this.helper || $("<div style='overflow:hidden;'></div>");
|
|
|
|
this.helper.addClass(this._helper).css({
|
|
width: this.element.outerWidth() - 1,
|
|
height: this.element.outerHeight() - 1,
|
|
position: "absolute",
|
|
left: this.elementOffset.left +"px",
|
|
top: this.elementOffset.top +"px",
|
|
zIndex: ++o.zIndex //TODO: Don't modify option
|
|
});
|
|
|
|
this.helper
|
|
.appendTo("body")
|
|
.disableSelection();
|
|
|
|
} else {
|
|
this.helper = this.element;
|
|
}
|
|
|
|
},
|
|
|
|
_change: {
|
|
e: function(event, dx) {
|
|
return { width: this.originalSize.width + dx };
|
|
},
|
|
w: function(event, dx) {
|
|
var cs = this.originalSize, sp = this.originalPosition;
|
|
return { left: sp.left + dx, width: cs.width - dx };
|
|
},
|
|
n: function(event, dx, dy) {
|
|
var cs = this.originalSize, sp = this.originalPosition;
|
|
return { top: sp.top + dy, height: cs.height - dy };
|
|
},
|
|
s: function(event, dx, dy) {
|
|
return { height: this.originalSize.height + dy };
|
|
},
|
|
se: function(event, dx, dy) {
|
|
return $.extend(this._change.s.apply(this, arguments), this._change.e.apply(this, [event, dx, dy]));
|
|
},
|
|
sw: function(event, dx, dy) {
|
|
return $.extend(this._change.s.apply(this, arguments), this._change.w.apply(this, [event, dx, dy]));
|
|
},
|
|
ne: function(event, dx, dy) {
|
|
return $.extend(this._change.n.apply(this, arguments), this._change.e.apply(this, [event, dx, dy]));
|
|
},
|
|
nw: function(event, dx, dy) {
|
|
return $.extend(this._change.n.apply(this, arguments), this._change.w.apply(this, [event, dx, dy]));
|
|
}
|
|
},
|
|
|
|
_propagate: function(n, event) {
|
|
$.ui.plugin.call(this, n, [event, this.ui()]);
|
|
(n !== "resize" && this._trigger(n, event, this.ui()));
|
|
},
|
|
|
|
plugins: {},
|
|
|
|
ui: function() {
|
|
return {
|
|
originalElement: this.originalElement,
|
|
element: this.element,
|
|
helper: this.helper,
|
|
position: this.position,
|
|
size: this.size,
|
|
originalSize: this.originalSize,
|
|
originalPosition: this.originalPosition
|
|
};
|
|
}
|
|
|
|
});
|
|
|
|
/*
|
|
* Resizable Extensions
|
|
*/
|
|
|
|
$.ui.plugin.add("resizable", "animate", {
|
|
|
|
stop: function( event ) {
|
|
var that = $(this).data("ui-resizable"),
|
|
o = that.options,
|
|
pr = that._proportionallyResizeElements,
|
|
ista = pr.length && (/textarea/i).test(pr[0].nodeName),
|
|
soffseth = ista && $.ui.hasScroll(pr[0], "left") /* TODO - jump height */ ? 0 : that.sizeDiff.height,
|
|
soffsetw = ista ? 0 : that.sizeDiff.width,
|
|
style = { width: (that.size.width - soffsetw), height: (that.size.height - soffseth) },
|
|
left = (parseInt(that.element.css("left"), 10) + (that.position.left - that.originalPosition.left)) || null,
|
|
top = (parseInt(that.element.css("top"), 10) + (that.position.top - that.originalPosition.top)) || null;
|
|
|
|
that.element.animate(
|
|
$.extend(style, top && left ? { top: top, left: left } : {}), {
|
|
duration: o.animateDuration,
|
|
easing: o.animateEasing,
|
|
step: function() {
|
|
|
|
var data = {
|
|
width: parseInt(that.element.css("width"), 10),
|
|
height: parseInt(that.element.css("height"), 10),
|
|
top: parseInt(that.element.css("top"), 10),
|
|
left: parseInt(that.element.css("left"), 10)
|
|
};
|
|
|
|
if (pr && pr.length) {
|
|
$(pr[0]).css({ width: data.width, height: data.height });
|
|
}
|
|
|
|
// propagating resize, and updating values for each animation step
|
|
that._updateCache(data);
|
|
that._propagate("resize", event);
|
|
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
});
|
|
|
|
$.ui.plugin.add("resizable", "containment", {
|
|
|
|
start: function() {
|
|
var element, p, co, ch, cw, width, height,
|
|
that = $(this).data("ui-resizable"),
|
|
o = that.options,
|
|
el = that.element,
|
|
oc = o.containment,
|
|
ce = (oc instanceof $) ? oc.get(0) : (/parent/.test(oc)) ? el.parent().get(0) : oc;
|
|
|
|
if (!ce) {
|
|
return;
|
|
}
|
|
|
|
that.containerElement = $(ce);
|
|
|
|
if (/document/.test(oc) || oc === document) {
|
|
that.containerOffset = { left: 0, top: 0 };
|
|
that.containerPosition = { left: 0, top: 0 };
|
|
|
|
that.parentData = {
|
|
element: $(document), left: 0, top: 0,
|
|
width: $(document).width(), height: $(document).height() || document.body.parentNode.scrollHeight
|
|
};
|
|
}
|
|
|
|
// i'm a node, so compute top, left, right, bottom
|
|
else {
|
|
element = $(ce);
|
|
p = [];
|
|
$([ "Top", "Right", "Left", "Bottom" ]).each(function(i, name) { p[i] = num(element.css("padding" + name)); });
|
|
|
|
that.containerOffset = element.offset();
|
|
that.containerPosition = element.position();
|
|
that.containerSize = { height: (element.innerHeight() - p[3]), width: (element.innerWidth() - p[1]) };
|
|
|
|
co = that.containerOffset;
|
|
ch = that.containerSize.height;
|
|
cw = that.containerSize.width;
|
|
width = ($.ui.hasScroll(ce, "left") ? ce.scrollWidth : cw );
|
|
height = ($.ui.hasScroll(ce) ? ce.scrollHeight : ch);
|
|
|
|
that.parentData = {
|
|
element: ce, left: co.left, top: co.top, width: width, height: height
|
|
};
|
|
}
|
|
},
|
|
|
|
resize: function( event ) {
|
|
var woset, hoset, isParent, isOffsetRelative,
|
|
that = $(this).data("ui-resizable"),
|
|
o = that.options,
|
|
co = that.containerOffset, cp = that.position,
|
|
pRatio = that._aspectRatio || event.shiftKey,
|
|
cop = { top:0, left:0 }, ce = that.containerElement;
|
|
|
|
if (ce[0] !== document && (/static/).test(ce.css("position"))) {
|
|
cop = co;
|
|
}
|
|
|
|
if (cp.left < (that._helper ? co.left : 0)) {
|
|
that.size.width = that.size.width + (that._helper ? (that.position.left - co.left) : (that.position.left - cop.left));
|
|
if (pRatio) {
|
|
that.size.height = that.size.width / that.aspectRatio;
|
|
}
|
|
that.position.left = o.helper ? co.left : 0;
|
|
}
|
|
|
|
if (cp.top < (that._helper ? co.top : 0)) {
|
|
that.size.height = that.size.height + (that._helper ? (that.position.top - co.top) : that.position.top);
|
|
if (pRatio) {
|
|
that.size.width = that.size.height * that.aspectRatio;
|
|
}
|
|
that.position.top = that._helper ? co.top : 0;
|
|
}
|
|
|
|
that.offset.left = that.parentData.left+that.position.left;
|
|
that.offset.top = that.parentData.top+that.position.top;
|
|
|
|
woset = Math.abs( (that._helper ? that.offset.left - cop.left : (that.offset.left - cop.left)) + that.sizeDiff.width );
|
|
hoset = Math.abs( (that._helper ? that.offset.top - cop.top : (that.offset.top - co.top)) + that.sizeDiff.height );
|
|
|
|
isParent = that.containerElement.get(0) === that.element.parent().get(0);
|
|
isOffsetRelative = /relative|absolute/.test(that.containerElement.css("position"));
|
|
|
|
if(isParent && isOffsetRelative) {
|
|
woset -= that.parentData.left;
|
|
}
|
|
|
|
if (woset + that.size.width >= that.parentData.width) {
|
|
that.size.width = that.parentData.width - woset;
|
|
if (pRatio) {
|
|
that.size.height = that.size.width / that.aspectRatio;
|
|
}
|
|
}
|
|
|
|
if (hoset + that.size.height >= that.parentData.height) {
|
|
that.size.height = that.parentData.height - hoset;
|
|
if (pRatio) {
|
|
that.size.width = that.size.height * that.aspectRatio;
|
|
}
|
|
}
|
|
},
|
|
|
|
stop: function(){
|
|
var that = $(this).data("ui-resizable"),
|
|
o = that.options,
|
|
co = that.containerOffset,
|
|
cop = that.containerPosition,
|
|
ce = that.containerElement,
|
|
helper = $(that.helper),
|
|
ho = helper.offset(),
|
|
w = helper.outerWidth() - that.sizeDiff.width,
|
|
h = helper.outerHeight() - that.sizeDiff.height;
|
|
|
|
if (that._helper && !o.animate && (/relative/).test(ce.css("position"))) {
|
|
$(this).css({ left: ho.left - cop.left - co.left, width: w, height: h });
|
|
}
|
|
|
|
if (that._helper && !o.animate && (/static/).test(ce.css("position"))) {
|
|
$(this).css({ left: ho.left - cop.left - co.left, width: w, height: h });
|
|
}
|
|
|
|
}
|
|
});
|
|
|
|
$.ui.plugin.add("resizable", "alsoResize", {
|
|
|
|
start: function () {
|
|
var that = $(this).data("ui-resizable"),
|
|
o = that.options,
|
|
_store = function (exp) {
|
|
$(exp).each(function() {
|
|
var el = $(this);
|
|
el.data("ui-resizable-alsoresize", {
|
|
width: parseInt(el.width(), 10), height: parseInt(el.height(), 10),
|
|
left: parseInt(el.css("left"), 10), top: parseInt(el.css("top"), 10)
|
|
});
|
|
});
|
|
};
|
|
|
|
if (typeof(o.alsoResize) === "object" && !o.alsoResize.parentNode) {
|
|
if (o.alsoResize.length) { o.alsoResize = o.alsoResize[0]; _store(o.alsoResize); }
|
|
else { $.each(o.alsoResize, function (exp) { _store(exp); }); }
|
|
}else{
|
|
_store(o.alsoResize);
|
|
}
|
|
},
|
|
|
|
resize: function (event, ui) {
|
|
var that = $(this).data("ui-resizable"),
|
|
o = that.options,
|
|
os = that.originalSize,
|
|
op = that.originalPosition,
|
|
delta = {
|
|
height: (that.size.height - os.height) || 0, width: (that.size.width - os.width) || 0,
|
|
top: (that.position.top - op.top) || 0, left: (that.position.left - op.left) || 0
|
|
},
|
|
|
|
_alsoResize = function (exp, c) {
|
|
$(exp).each(function() {
|
|
var el = $(this), start = $(this).data("ui-resizable-alsoresize"), style = {},
|
|
css = c && c.length ? c : el.parents(ui.originalElement[0]).length ? ["width", "height"] : ["width", "height", "top", "left"];
|
|
|
|
$.each(css, function (i, prop) {
|
|
var sum = (start[prop]||0) + (delta[prop]||0);
|
|
if (sum && sum >= 0) {
|
|
style[prop] = sum || null;
|
|
}
|
|
});
|
|
|
|
el.css(style);
|
|
});
|
|
};
|
|
|
|
if (typeof(o.alsoResize) === "object" && !o.alsoResize.nodeType) {
|
|
$.each(o.alsoResize, function (exp, c) { _alsoResize(exp, c); });
|
|
}else{
|
|
_alsoResize(o.alsoResize);
|
|
}
|
|
},
|
|
|
|
stop: function () {
|
|
$(this).removeData("resizable-alsoresize");
|
|
}
|
|
});
|
|
|
|
$.ui.plugin.add("resizable", "ghost", {
|
|
|
|
start: function() {
|
|
|
|
var that = $(this).data("ui-resizable"), o = that.options, cs = that.size;
|
|
|
|
that.ghost = that.originalElement.clone();
|
|
that.ghost
|
|
.css({ opacity: 0.25, display: "block", position: "relative", height: cs.height, width: cs.width, margin: 0, left: 0, top: 0 })
|
|
.addClass("ui-resizable-ghost")
|
|
.addClass(typeof o.ghost === "string" ? o.ghost : "");
|
|
|
|
that.ghost.appendTo(that.helper);
|
|
|
|
},
|
|
|
|
resize: function(){
|
|
var that = $(this).data("ui-resizable");
|
|
if (that.ghost) {
|
|
that.ghost.css({ position: "relative", height: that.size.height, width: that.size.width });
|
|
}
|
|
},
|
|
|
|
stop: function() {
|
|
var that = $(this).data("ui-resizable");
|
|
if (that.ghost && that.helper) {
|
|
that.helper.get(0).removeChild(that.ghost.get(0));
|
|
}
|
|
}
|
|
|
|
});
|
|
|
|
$.ui.plugin.add("resizable", "grid", {
|
|
|
|
resize: function() {
|
|
var that = $(this).data("ui-resizable"),
|
|
o = that.options,
|
|
cs = that.size,
|
|
os = that.originalSize,
|
|
op = that.originalPosition,
|
|
a = that.axis,
|
|
grid = typeof o.grid === "number" ? [o.grid, o.grid] : o.grid,
|
|
gridX = (grid[0]||1),
|
|
gridY = (grid[1]||1),
|
|
ox = Math.round((cs.width - os.width) / gridX) * gridX,
|
|
oy = Math.round((cs.height - os.height) / gridY) * gridY,
|
|
newWidth = os.width + ox,
|
|
newHeight = os.height + oy,
|
|
isMaxWidth = o.maxWidth && (o.maxWidth < newWidth),
|
|
isMaxHeight = o.maxHeight && (o.maxHeight < newHeight),
|
|
isMinWidth = o.minWidth && (o.minWidth > newWidth),
|
|
isMinHeight = o.minHeight && (o.minHeight > newHeight);
|
|
|
|
o.grid = grid;
|
|
|
|
if (isMinWidth) {
|
|
newWidth = newWidth + gridX;
|
|
}
|
|
if (isMinHeight) {
|
|
newHeight = newHeight + gridY;
|
|
}
|
|
if (isMaxWidth) {
|
|
newWidth = newWidth - gridX;
|
|
}
|
|
if (isMaxHeight) {
|
|
newHeight = newHeight - gridY;
|
|
}
|
|
|
|
if (/^(se|s|e)$/.test(a)) {
|
|
that.size.width = newWidth;
|
|
that.size.height = newHeight;
|
|
} else if (/^(ne)$/.test(a)) {
|
|
that.size.width = newWidth;
|
|
that.size.height = newHeight;
|
|
that.position.top = op.top - oy;
|
|
} else if (/^(sw)$/.test(a)) {
|
|
that.size.width = newWidth;
|
|
that.size.height = newHeight;
|
|
that.position.left = op.left - ox;
|
|
} else {
|
|
that.size.width = newWidth;
|
|
that.size.height = newHeight;
|
|
that.position.top = op.top - oy;
|
|
that.position.left = op.left - ox;
|
|
}
|
|
}
|
|
|
|
});
|
|
|
|
})(jQuery);
|
|
|
|
/**
|
|
* @preserve
|
|
* jquery.layout 1.3.0 - Release Candidate 30.79
|
|
* $Date: 2013-01-12 08:00:00 (Sat, 12 Jan 2013) $
|
|
* $Rev: 303007 $
|
|
*
|
|
* Copyright (c) 2013 Kevin Dalman (http://allpro.net)
|
|
* Based on work by Fabrizio Balliano (http://www.fabrizioballiano.net)
|
|
*
|
|
* Dual licensed under the GPL (http://www.gnu.org/licenses/gpl.html)
|
|
* and MIT (http://www.opensource.org/licenses/mit-license.php) licenses.
|
|
*
|
|
* SEE: http://layout.jquery-dev.net/LICENSE.txt
|
|
*
|
|
* Changelog: http://layout.jquery-dev.net/changelog.cfm#1.3.0.rc30.79
|
|
*
|
|
* Docs: http://layout.jquery-dev.net/documentation.html
|
|
* Tips: http://layout.jquery-dev.net/tips.html
|
|
* Help: http://groups.google.com/group/jquery-ui-layout
|
|
*/
|
|
|
|
/* JavaDoc Info: http://code.google.com/closure/compiler/docs/js-for-compiler.html
|
|
* {!Object} non-nullable type (never NULL)
|
|
* {?string} nullable type (sometimes NULL) - default for {Object}
|
|
* {number=} optional parameter
|
|
* {*} ALL types
|
|
*/
|
|
/* TODO for jQ 2.0
|
|
* change .andSelf() to .addBack()
|
|
* $.fn.disableSelection won't work
|
|
*/
|
|
|
|
// NOTE: For best readability, view with a fixed-width font and tabs equal to 4-chars
|
|
|
|
;(function ($) {
|
|
|
|
// alias Math methods - used a lot!
|
|
var min = Math.min
|
|
, max = Math.max
|
|
, round = Math.floor
|
|
|
|
, isStr = function (v) { return $.type(v) === "string"; }
|
|
|
|
/**
|
|
* @param {!Object} Instance
|
|
* @param {Array.<string>} a_fn
|
|
*/
|
|
, runPluginCallbacks = function (Instance, a_fn) {
|
|
if ($.isArray(a_fn))
|
|
for (var i=0, c=a_fn.length; i<c; i++) {
|
|
var fn = a_fn[i];
|
|
try {
|
|
if (isStr(fn)) // 'name' of a function
|
|
fn = eval(fn);
|
|
if ($.isFunction(fn))
|
|
g(fn)( Instance );
|
|
} catch (ex) {}
|
|
}
|
|
function g (f) { return f; }; // compiler hack
|
|
}
|
|
;
|
|
|
|
/*
|
|
* GENERIC $.layout METHODS - used by all layouts
|
|
*/
|
|
$.layout = {
|
|
|
|
version: "1.3.rc30.79"
|
|
, revision: 0.033007 // 1.3.0 final = 1.0300 - major(n+).minor(nn)+patch(nn+)
|
|
|
|
// $.layout.browser REPLACES $.browser
|
|
, browser: {} // set below
|
|
|
|
// *PREDEFINED* EFFECTS & DEFAULTS
|
|
// MUST list effect here - OR MUST set an fxSettings option (can be an empty hash: {})
|
|
, effects: {
|
|
|
|
// Pane Open/Close Animations
|
|
slide: {
|
|
all: { duration: "fast" } // eg: duration: 1000, easing: "easeOutBounce"
|
|
, north: { direction: "up" }
|
|
, south: { direction: "down" }
|
|
, east: { direction: "right"}
|
|
, west: { direction: "left" }
|
|
}
|
|
, drop: {
|
|
all: { duration: "slow" }
|
|
, north: { direction: "up" }
|
|
, south: { direction: "down" }
|
|
, east: { direction: "right"}
|
|
, west: { direction: "left" }
|
|
}
|
|
, scale: {
|
|
all: { duration: "fast" }
|
|
}
|
|
// these are not recommended, but can be used
|
|
, blind: {}
|
|
, clip: {}
|
|
, explode: {}
|
|
, fade: {}
|
|
, fold: {}
|
|
, puff: {}
|
|
|
|
// Pane Resize Animations
|
|
, size: {
|
|
all: { easing: "swing" }
|
|
}
|
|
}
|
|
|
|
// INTERNAL CONFIG DATA - DO NOT CHANGE THIS!
|
|
, config: {
|
|
optionRootKeys: "effects,panes,north,south,west,east,center".split(",")
|
|
, allPanes: "north,south,west,east,center".split(",")
|
|
, borderPanes: "north,south,west,east".split(",")
|
|
, oppositeEdge: {
|
|
north: "south"
|
|
, south: "north"
|
|
, east: "west"
|
|
, west: "east"
|
|
}
|
|
// offscreen data
|
|
, offscreenCSS: { left: "-99999px", right: "auto" } // used by hide/close if useOffscreenClose=true
|
|
, offscreenReset: "offscreenReset" // key used for data
|
|
// CSS used in multiple places
|
|
, hidden: { visibility: "hidden" }
|
|
, visible: { visibility: "visible" }
|
|
// layout element settings
|
|
, resizers: {
|
|
cssReq: {
|
|
position: "absolute"
|
|
, padding: 0
|
|
, margin: 0
|
|
, fontSize: "1px"
|
|
, textAlign: "left" // to counter-act "center" alignment!
|
|
, overflow: "hidden" // prevent toggler-button from overflowing
|
|
// SEE $.layout.defaults.zIndexes.resizer_normal
|
|
}
|
|
, cssDemo: { // DEMO CSS - applied if: options.PANE.applyDemoStyles=true
|
|
background: "#DDD"
|
|
, border: "none"
|
|
}
|
|
}
|
|
, togglers: {
|
|
cssReq: {
|
|
position: "absolute"
|
|
, display: "block"
|
|
, padding: 0
|
|
, margin: 0
|
|
, overflow: "hidden"
|
|
, textAlign: "center"
|
|
, fontSize: "1px"
|
|
, cursor: "pointer"
|
|
, zIndex: 1
|
|
}
|
|
, cssDemo: { // DEMO CSS - applied if: options.PANE.applyDemoStyles=true
|
|
background: "#AAA"
|
|
}
|
|
}
|
|
, content: {
|
|
cssReq: {
|
|
position: "relative" /* contain floated or positioned elements */
|
|
}
|
|
, cssDemo: { // DEMO CSS - applied if: options.PANE.applyDemoStyles=true
|
|
overflow: "auto"
|
|
, padding: "10px"
|
|
}
|
|
, cssDemoPane: { // DEMO CSS - REMOVE scrolling from 'pane' when it has a content-div
|
|
overflow: "hidden"
|
|
, padding: 0
|
|
}
|
|
}
|
|
, panes: { // defaults for ALL panes - overridden by 'per-pane settings' below
|
|
cssReq: {
|
|
position: "absolute"
|
|
, margin: 0
|
|
// $.layout.defaults.zIndexes.pane_normal
|
|
}
|
|
, cssDemo: { // DEMO CSS - applied if: options.PANE.applyDemoStyles=true
|
|
padding: "10px"
|
|
, background: "#FFF"
|
|
, border: "1px solid #BBB"
|
|
, overflow: "auto"
|
|
}
|
|
}
|
|
, north: {
|
|
side: "top"
|
|
, sizeType: "Height"
|
|
, dir: "horz"
|
|
, cssReq: {
|
|
top: 0
|
|
, bottom: "auto"
|
|
, left: 0
|
|
, right: 0
|
|
, width: "auto"
|
|
// height: DYNAMIC
|
|
}
|
|
}
|
|
, south: {
|
|
side: "bottom"
|
|
, sizeType: "Height"
|
|
, dir: "horz"
|
|
, cssReq: {
|
|
top: "auto"
|
|
, bottom: 0
|
|
, left: 0
|
|
, right: 0
|
|
, width: "auto"
|
|
// height: DYNAMIC
|
|
}
|
|
}
|
|
, east: {
|
|
side: "right"
|
|
, sizeType: "Width"
|
|
, dir: "vert"
|
|
, cssReq: {
|
|
left: "auto"
|
|
, right: 0
|
|
, top: "auto" // DYNAMIC
|
|
, bottom: "auto" // DYNAMIC
|
|
, height: "auto"
|
|
// width: DYNAMIC
|
|
}
|
|
}
|
|
, west: {
|
|
side: "left"
|
|
, sizeType: "Width"
|
|
, dir: "vert"
|
|
, cssReq: {
|
|
left: 0
|
|
, right: "auto"
|
|
, top: "auto" // DYNAMIC
|
|
, bottom: "auto" // DYNAMIC
|
|
, height: "auto"
|
|
// width: DYNAMIC
|
|
}
|
|
}
|
|
, center: {
|
|
dir: "center"
|
|
, cssReq: {
|
|
left: "auto" // DYNAMIC
|
|
, right: "auto" // DYNAMIC
|
|
, top: "auto" // DYNAMIC
|
|
, bottom: "auto" // DYNAMIC
|
|
, height: "auto"
|
|
, width: "auto"
|
|
}
|
|
}
|
|
}
|
|
|
|
// CALLBACK FUNCTION NAMESPACE - used to store reusable callback functions
|
|
, callbacks: {}
|
|
|
|
, getParentPaneElem: function (el) {
|
|
// must pass either a container or pane element
|
|
var $el = $(el)
|
|
, layout = $el.data("layout") || $el.data("parentLayout");
|
|
if (layout) {
|
|
var $cont = layout.container;
|
|
// see if this container is directly-nested inside an outer-pane
|
|
if ($cont.data("layoutPane")) return $cont;
|
|
var $pane = $cont.closest("."+ $.layout.defaults.panes.paneClass);
|
|
// if a pane was found, return it
|
|
if ($pane.data("layoutPane")) return $pane;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
, getParentPaneInstance: function (el) {
|
|
// must pass either a container or pane element
|
|
var $pane = $.layout.getParentPaneElem(el);
|
|
return $pane ? $pane.data("layoutPane") : null;
|
|
}
|
|
|
|
, getParentLayoutInstance: function (el) {
|
|
// must pass either a container or pane element
|
|
var $pane = $.layout.getParentPaneElem(el);
|
|
return $pane ? $pane.data("parentLayout") : null;
|
|
}
|
|
|
|
, getEventObject: function (evt) {
|
|
return typeof evt === "object" && evt.stopPropagation ? evt : null;
|
|
}
|
|
, parsePaneName: function (evt_or_pane) {
|
|
var evt = $.layout.getEventObject( evt_or_pane )
|
|
, pane = evt_or_pane;
|
|
if (evt) {
|
|
// ALWAYS stop propagation of events triggered in Layout!
|
|
evt.stopPropagation();
|
|
pane = $(this).data("layoutEdge");
|
|
}
|
|
if (pane && !/^(west|east|north|south|center)$/.test(pane)) {
|
|
$.layout.msg('LAYOUT ERROR - Invalid pane-name: "'+ pane +'"');
|
|
pane = "error";
|
|
}
|
|
return pane;
|
|
}
|
|
|
|
|
|
// LAYOUT-PLUGIN REGISTRATION
|
|
// more plugins can added beyond this default list
|
|
, plugins: {
|
|
draggable: !!$.fn.draggable // resizing
|
|
, effects: {
|
|
core: !!$.effects // animimations (specific effects tested by initOptions)
|
|
, slide: $.effects && ($.effects.slide || ($.effects.effect && $.effects.effect.slide)) // default effect
|
|
}
|
|
}
|
|
|
|
// arrays of plugin or other methods to be triggered for events in *each layout* - will be passed 'Instance'
|
|
, onCreate: [] // runs when layout is just starting to be created - right after options are set
|
|
, onLoad: [] // runs after layout container and global events init, but before initPanes is called
|
|
, onReady: [] // runs after initialization *completes* - ie, after initPanes completes successfully
|
|
, onDestroy: [] // runs after layout is destroyed
|
|
, onUnload: [] // runs after layout is destroyed OR when page unloads
|
|
, afterOpen: [] // runs after setAsOpen() completes
|
|
, afterClose: [] // runs after setAsClosed() completes
|
|
|
|
/*
|
|
* GENERIC UTILITY METHODS
|
|
*/
|
|
|
|
// calculate and return the scrollbar width, as an integer
|
|
, scrollbarWidth: function () { return window.scrollbarWidth || $.layout.getScrollbarSize('width'); }
|
|
, scrollbarHeight: function () { return window.scrollbarHeight || $.layout.getScrollbarSize('height'); }
|
|
, getScrollbarSize: function (dim) {
|
|
var $c = $('<div style="position: absolute; top: -10000px; left: -10000px; width: 100px; height: 100px; overflow: scroll;"></div>').appendTo("body");
|
|
var d = { width: $c.css("width") - $c[0].clientWidth, height: $c.height() - $c[0].clientHeight };
|
|
$c.remove();
|
|
window.scrollbarWidth = d.width;
|
|
window.scrollbarHeight = d.height;
|
|
return dim.match(/^(width|height)$/) ? d[dim] : d;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns hash container 'display' and 'visibility'
|
|
*
|
|
* @see $.swap() - swaps CSS, runs callback, resets CSS
|
|
* @param {!Object} $E jQuery element
|
|
* @param {boolean=} [force=false] Run even if display != none
|
|
* @return {!Object} Returns current style props, if applicable
|
|
*/
|
|
, showInvisibly: function ($E, force) {
|
|
if ($E && $E.length && (force || $E.css("display") === "none")) { // only if not *already hidden*
|
|
var s = $E[0].style
|
|
// save ONLY the 'style' props because that is what we must restore
|
|
, CSS = { display: s.display || '', visibility: s.visibility || '' };
|
|
// show element 'invisibly' so can be measured
|
|
$E.css({ display: "block", visibility: "hidden" });
|
|
return CSS;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
/**
|
|
* Returns data for setting size of an element (container or a pane).
|
|
*
|
|
* @see _create(), onWindowResize() for container, plus others for pane
|
|
* @return JSON Returns a hash of all dimensions: top, bottom, left, right, outerWidth, innerHeight, etc
|
|
*/
|
|
, getElementDimensions: function ($E, inset) {
|
|
var
|
|
// dimensions hash - start with current data IF passed
|
|
d = { css: {}, inset: {} }
|
|
, x = d.css // CSS hash
|
|
, i = { bottom: 0 } // TEMP insets (bottom = complier hack)
|
|
, N = $.layout.cssNum
|
|
, off = $E.offset()
|
|
, b, p, ei // TEMP border, padding
|
|
;
|
|
d.offsetLeft = off.left;
|
|
d.offsetTop = off.top;
|
|
|
|
if (!inset) inset = {}; // simplify logic below
|
|
|
|
$.each("Left,Right,Top,Bottom".split(","), function (idx, e) { // e = edge
|
|
b = x["border" + e] = $.layout.borderWidth($E, e);
|
|
p = x["padding"+ e] = $.layout.cssNum($E, "padding"+e);
|
|
ei = e.toLowerCase();
|
|
d.inset[ei] = inset[ei] >= 0 ? inset[ei] : p; // any missing insetX value = paddingX
|
|
i[ei] = d.inset[ei] + b; // total offset of content from outer side
|
|
});
|
|
|
|
x.width = $E.width();
|
|
x.height = $E.height();
|
|
x.top = N($E,"top",true);
|
|
x.bottom = N($E,"bottom",true);
|
|
x.left = N($E,"left",true);
|
|
x.right = N($E,"right",true);
|
|
|
|
d.outerWidth = $E.outerWidth();
|
|
d.outerHeight = $E.outerHeight();
|
|
// calc the TRUE inner-dimensions, even in quirks-mode!
|
|
d.innerWidth = max(0, d.outerWidth - i.left - i.right);
|
|
d.innerHeight = max(0, d.outerHeight - i.top - i.bottom);
|
|
// layoutWidth/Height is used in calcs for manual resizing
|
|
// layoutW/H only differs from innerW/H when in quirks-mode - then is like outerW/H
|
|
d.layoutWidth = $E.innerWidth();
|
|
d.layoutHeight = $E.innerHeight();
|
|
|
|
//if ($E.prop('tagName') === 'BODY') { debugData( d, $E.prop('tagName') ); } // DEBUG
|
|
|
|
//d.visible = $E.is(":visible");// && x.width > 0 && x.height > 0;
|
|
|
|
return d;
|
|
}
|
|
|
|
, getElementStyles: function ($E, list) {
|
|
var
|
|
CSS = {}
|
|
, style = $E[0].style
|
|
, props = list.split(",")
|
|
, sides = "Top,Bottom,Left,Right".split(",")
|
|
, attrs = "Color,Style,Width".split(",")
|
|
, p, s, a, i, j, k
|
|
;
|
|
for (i=0; i < props.length; i++) {
|
|
p = props[i];
|
|
if (p.match(/(border|padding|margin)$/))
|
|
for (j=0; j < 4; j++) {
|
|
s = sides[j];
|
|
if (p === "border")
|
|
for (k=0; k < 3; k++) {
|
|
a = attrs[k];
|
|
CSS[p+s+a] = style[p+s+a];
|
|
}
|
|
else
|
|
CSS[p+s] = style[p+s];
|
|
}
|
|
else
|
|
CSS[p] = style[p];
|
|
};
|
|
return CSS
|
|
}
|
|
|
|
/**
|
|
* Return the innerWidth for the current browser/doctype
|
|
*
|
|
* @see initPanes(), sizeMidPanes(), initHandles(), sizeHandles()
|
|
* @param {Array.<Object>} $E Must pass a jQuery object - first element is processed
|
|
* @param {number=} outerWidth (optional) Can pass a width, allowing calculations BEFORE element is resized
|
|
* @return {number} Returns the innerWidth of the elem by subtracting padding and borders
|
|
*/
|
|
, cssWidth: function ($E, outerWidth) {
|
|
// a 'calculated' outerHeight can be passed so borders and/or padding are removed if needed
|
|
if (outerWidth <= 0) return 0;
|
|
|
|
var bs = !$.layout.browser.boxModel ? "border-box" : $.support.boxSizing ? $E.css("boxSizing") : "content-box"
|
|
, b = $.layout.borderWidth
|
|
, n = $.layout.cssNum
|
|
, W = outerWidth
|
|
;
|
|
// strip border and/or padding from outerWidth to get CSS Width
|
|
if (bs !== "border-box")
|
|
W -= (b($E, "Left") + b($E, "Right"));
|
|
if (bs === "content-box")
|
|
W -= (n($E, "paddingLeft") + n($E, "paddingRight"));
|
|
return max(0,W);
|
|
}
|
|
|
|
/**
|
|
* Return the innerHeight for the current browser/doctype
|
|
*
|
|
* @see initPanes(), sizeMidPanes(), initHandles(), sizeHandles()
|
|
* @param {Array.<Object>} $E Must pass a jQuery object - first element is processed
|
|
* @param {number=} outerHeight (optional) Can pass a width, allowing calculations BEFORE element is resized
|
|
* @return {number} Returns the innerHeight of the elem by subtracting padding and borders
|
|
*/
|
|
, cssHeight: function ($E, outerHeight) {
|
|
// a 'calculated' outerHeight can be passed so borders and/or padding are removed if needed
|
|
if (outerHeight <= 0) return 0;
|
|
|
|
var bs = !$.layout.browser.boxModel ? "border-box" : $.support.boxSizing ? $E.css("boxSizing") : "content-box"
|
|
, b = $.layout.borderWidth
|
|
, n = $.layout.cssNum
|
|
, H = outerHeight
|
|
;
|
|
// strip border and/or padding from outerHeight to get CSS Height
|
|
if (bs !== "border-box")
|
|
H -= (b($E, "Top") + b($E, "Bottom"));
|
|
if (bs === "content-box")
|
|
H -= (n($E, "paddingTop") + n($E, "paddingBottom"));
|
|
return max(0,H);
|
|
}
|
|
|
|
/**
|
|
* Returns the 'current CSS numeric value' for a CSS property - 0 if property does not exist
|
|
*
|
|
* @see Called by many methods
|
|
* @param {Array.<Object>} $E Must pass a jQuery object - first element is processed
|
|
* @param {string} prop The name of the CSS property, eg: top, width, etc.
|
|
* @param {boolean=} [allowAuto=false] true = return 'auto' if that is value; false = return 0
|
|
* @return {(string|number)} Usually used to get an integer value for position (top, left) or size (height, width)
|
|
*/
|
|
, cssNum: function ($E, prop, allowAuto) {
|
|
if (!$E.jquery) $E = $($E);
|
|
var CSS = $.layout.showInvisibly($E)
|
|
, p = $.css($E[0], prop, true)
|
|
, v = allowAuto && p=="auto" ? p : Math.round(parseFloat(p) || 0);
|
|
$E.css( CSS ); // RESET
|
|
return v;
|
|
}
|
|
|
|
, borderWidth: function (el, side) {
|
|
if (el.jquery) el = el[0];
|
|
var b = "border"+ side.substr(0,1).toUpperCase() + side.substr(1); // left => Left
|
|
return $.css(el, b+"Style", true) === "none" ? 0 : Math.round(parseFloat($.css(el, b+"Width", true)) || 0);
|
|
}
|
|
|
|
/**
|
|
* Mouse-tracking utility - FUTURE REFERENCE
|
|
*
|
|
* init: if (!window.mouse) {
|
|
* window.mouse = { x: 0, y: 0 };
|
|
* $(document).mousemove( $.layout.trackMouse );
|
|
* }
|
|
*
|
|
* @param {Object} evt
|
|
*
|
|
, trackMouse: function (evt) {
|
|
window.mouse = { x: evt.clientX, y: evt.clientY };
|
|
}
|
|
*/
|
|
|
|
/**
|
|
* SUBROUTINE for preventPrematureSlideClose option
|
|
*
|
|
* @param {Object} evt
|
|
* @param {Object=} el
|
|
*/
|
|
, isMouseOverElem: function (evt, el) {
|
|
var
|
|
$E = $(el || this)
|
|
, d = $E.offset()
|
|
, T = d.top
|
|
, L = d.left
|
|
, R = L + $E.outerWidth()
|
|
, B = T + $E.outerHeight()
|
|
, x = evt.pageX // evt.clientX ?
|
|
, y = evt.pageY // evt.clientY ?
|
|
;
|
|
// if X & Y are < 0, probably means is over an open SELECT
|
|
return ($.layout.browser.msie && x < 0 && y < 0) || ((x >= L && x <= R) && (y >= T && y <= B));
|
|
}
|
|
|
|
/**
|
|
* Message/Logging Utility
|
|
*
|
|
* @example $.layout.msg("My message"); // log text
|
|
* @example $.layout.msg("My message", true); // alert text
|
|
* @example $.layout.msg({ foo: "bar" }, "Title"); // log hash-data, with custom title
|
|
* @example $.layout.msg({ foo: "bar" }, true, "Title", { sort: false }); -OR-
|
|
* @example $.layout.msg({ foo: "bar" }, "Title", { sort: false, display: true }); // alert hash-data
|
|
*
|
|
* @param {(Object|string)} info String message OR Hash/Array
|
|
* @param {(Boolean|string|Object)=} [popup=false] True means alert-box - can be skipped
|
|
* @param {(Object|string)=} [debugTitle=""] Title for Hash data - can be skipped
|
|
* @param {Object=} [debugOpts] Extra options for debug output
|
|
*/
|
|
, msg: function (info, popup, debugTitle, debugOpts) {
|
|
if ($.isPlainObject(info) && window.debugData) {
|
|
if (typeof popup === "string") {
|
|
debugOpts = debugTitle;
|
|
debugTitle = popup;
|
|
}
|
|
else if (typeof debugTitle === "object") {
|
|
debugOpts = debugTitle;
|
|
debugTitle = null;
|
|
}
|
|
var t = debugTitle || "log( <object> )"
|
|
, o = $.extend({ sort: false, returnHTML: false, display: false }, debugOpts);
|
|
if (popup === true || o.display)
|
|
debugData( info, t, o );
|
|
else if (window.console)
|
|
console.log(debugData( info, t, o ));
|
|
}
|
|
else if (popup)
|
|
alert(info);
|
|
else if (window.console)
|
|
console.log(info);
|
|
else {
|
|
var id = "#layoutLogger"
|
|
, $l = $(id);
|
|
if (!$l.length)
|
|
$l = createLog();
|
|
$l.children("ul").append('<li style="padding: 4px 10px; margin: 0; border-top: 1px solid #CCC;">'+ info.replace(/\</g,"<").replace(/\>/g,">") +'</li>');
|
|
}
|
|
|
|
function createLog () {
|
|
var pos = $.support.fixedPosition ? 'fixed' : 'absolute'
|
|
, $e = $('<div id="layoutLogger" style="position: '+ pos +'; top: 5px; z-index: 999999; max-width: 25%; overflow: hidden; border: 1px solid #000; border-radius: 5px; background: #FBFBFB; box-shadow: 0 2px 10px rgba(0,0,0,0.3);">'
|
|
+ '<div style="font-size: 13px; font-weight: bold; padding: 5px 10px; background: #F6F6F6; border-radius: 5px 5px 0 0; cursor: move;">'
|
|
+ '<span style="float: right; padding-left: 7px; cursor: pointer;" title="Remove Console" onclick="$(this).closest(\'#layoutLogger\').remove()">X</span>Layout console.log</div>'
|
|
+ '<ul style="font-size: 13px; font-weight: none; list-style: none; margin: 0; padding: 0 0 2px;"></ul>'
|
|
+ '</div>'
|
|
).appendTo("body");
|
|
$e.css('left', $(window).width() - $e.outerWidth() - 5)
|
|
if ($.ui.draggable) $e.draggable({ handle: ':first-child' });
|
|
return $e;
|
|
};
|
|
}
|
|
|
|
};
|
|
|
|
|
|
/*
|
|
* $.layout.browser REPLACES removed $.browser, with extra data
|
|
* Parsing code here adapted from jQuery 1.8 $.browse
|
|
*/
|
|
var u = navigator.userAgent.toLowerCase()
|
|
, m = /(chrome)[ \/]([\w.]+)/.exec( u )
|
|
|| /(webkit)[ \/]([\w.]+)/.exec( u )
|
|
|| /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( u )
|
|
|| /(msie) ([\w.]+)/.exec( u )
|
|
|| u.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( u )
|
|
|| []
|
|
, b = m[1] || ""
|
|
, v = m[2] || 0
|
|
, ie = b === "msie"
|
|
;
|
|
$.layout.browser = {
|
|
version: v
|
|
, safari: b === "webkit" // webkit (NOT chrome) = safari
|
|
, webkit: b === "chrome" // chrome = webkit
|
|
, msie: ie
|
|
, isIE6: ie && v == 6
|
|
// ONLY IE reverts to old box-model - update for older jQ onReady
|
|
, boxModel: !ie || $.support.boxModel !== false
|
|
};
|
|
if (b) $.layout.browser[b] = true; // set CURRENT browser
|
|
/* OLD versions of jQuery only set $.support.boxModel after page is loaded
|
|
* so if this is IE, use support.boxModel to test for quirks-mode (ONLY IE changes boxModel) */
|
|
if (ie) $(function(){ $.layout.browser.boxModel = $.support.boxModel; });
|
|
|
|
|
|
// DEFAULT OPTIONS
|
|
$.layout.defaults = {
|
|
/*
|
|
* LAYOUT & LAYOUT-CONTAINER OPTIONS
|
|
* - none of these options are applicable to individual panes
|
|
*/
|
|
name: "" // Not required, but useful for buttons and used for the state-cookie
|
|
, containerClass: "ui-layout-container" // layout-container element
|
|
, inset: null // custom container-inset values (override padding)
|
|
, scrollToBookmarkOnLoad: true // after creating a layout, scroll to bookmark in URL (.../page.htm#myBookmark)
|
|
, resizeWithWindow: true // bind thisLayout.resizeAll() to the window.resize event
|
|
, resizeWithWindowDelay: 200 // delay calling resizeAll because makes window resizing very jerky
|
|
, resizeWithWindowMaxDelay: 0 // 0 = none - force resize every XX ms while window is being resized
|
|
, maskPanesEarly: false // true = create pane-masks on resizer.mouseDown instead of waiting for resizer.dragstart
|
|
, onresizeall_start: null // CALLBACK when resizeAll() STARTS - NOT pane-specific
|
|
, onresizeall_end: null // CALLBACK when resizeAll() ENDS - NOT pane-specific
|
|
, onload_start: null // CALLBACK when Layout inits - after options initialized, but before elements
|
|
, onload_end: null // CALLBACK when Layout inits - after EVERYTHING has been initialized
|
|
, onunload_start: null // CALLBACK when Layout is destroyed OR onWindowUnload
|
|
, onunload_end: null // CALLBACK when Layout is destroyed OR onWindowUnload
|
|
, initPanes: true // false = DO NOT initialize the panes onLoad - will init later
|
|
, showErrorMessages: true // enables fatal error messages to warn developers of common errors
|
|
, showDebugMessages: false // display console-and-alert debug msgs - IF this Layout version _has_ debugging code!
|
|
// Changing this zIndex value will cause other zIndex values to automatically change
|
|
, zIndex: null // the PANE zIndex - resizers and masks will be +1
|
|
// DO NOT CHANGE the zIndex values below unless you clearly understand their relationships
|
|
, zIndexes: { // set _default_ z-index values here...
|
|
pane_normal: 0 // normal z-index for panes
|
|
, content_mask: 1 // applied to overlays used to mask content INSIDE panes during resizing
|
|
, resizer_normal: 2 // normal z-index for resizer-bars
|
|
, pane_sliding: 100 // applied to *BOTH* the pane and its resizer when a pane is 'slid open'
|
|
, pane_animate: 1000 // applied to the pane when being animated - not applied to the resizer
|
|
, resizer_drag: 10000 // applied to the CLONED resizer-bar when being 'dragged'
|
|
}
|
|
, errors: {
|
|
pane: "pane" // description of "layout pane element" - used only in error messages
|
|
, selector: "selector" // description of "jQuery-selector" - used only in error messages
|
|
, addButtonError: "Error Adding Button\nInvalid "
|
|
, containerMissing: "UI Layout Initialization Error\nThe specified layout-container does not exist."
|
|
, centerPaneMissing: "UI Layout Initialization Error\nThe center-pane element does not exist.\nThe center-pane is a required element."
|
|
, noContainerHeight: "UI Layout Initialization Warning\nThe layout-container \"CONTAINER\" has no height.\nTherefore the layout is 0-height and hence 'invisible'!"
|
|
, callbackError: "UI Layout Callback Error\nThe EVENT callback is not a valid function."
|
|
}
|
|
/*
|
|
* PANE DEFAULT SETTINGS
|
|
* - settings under the 'panes' key become the default settings for *all panes*
|
|
* - ALL pane-options can also be set specifically for each panes, which will override these 'default values'
|
|
*/
|
|
, panes: { // default options for 'all panes' - will be overridden by 'per-pane settings'
|
|
applyDemoStyles: false // NOTE: renamed from applyDefaultStyles for clarity
|
|
, closable: true // pane can open & close
|
|
, resizable: true // when open, pane can be resized
|
|
, slidable: true // when closed, pane can 'slide open' over other panes - closes on mouse-out
|
|
, initClosed: false // true = init pane as 'closed'
|
|
, initHidden: false // true = init pane as 'hidden' - no resizer-bar/spacing
|
|
// SELECTORS
|
|
//, paneSelector: "" // MUST be pane-specific - jQuery selector for pane
|
|
, contentSelector: ".ui-layout-content" // INNER div/element to auto-size so only it scrolls, not the entire pane!
|
|
, contentIgnoreSelector: ".ui-layout-ignore" // element(s) to 'ignore' when measuring 'content'
|
|
, findNestedContent: false // true = $P.find(contentSelector), false = $P.children(contentSelector)
|
|
// GENERIC ROOT-CLASSES - for auto-generated classNames
|
|
, paneClass: "ui-layout-pane" // Layout Pane
|
|
, resizerClass: "ui-layout-resizer" // Resizer Bar
|
|
, togglerClass: "ui-layout-toggler" // Toggler Button
|
|
, buttonClass: "ui-layout-button" // CUSTOM Buttons - eg: '[ui-layout-button]-toggle/-open/-close/-pin'
|
|
// ELEMENT SIZE & SPACING
|
|
//, size: 100 // MUST be pane-specific -initial size of pane
|
|
, minSize: 0 // when manually resizing a pane
|
|
, maxSize: 0 // ditto, 0 = no limit
|
|
, spacing_open: 6 // space between pane and adjacent panes - when pane is 'open'
|
|
, spacing_closed: 6 // ditto - when pane is 'closed'
|
|
, togglerLength_open: 50 // Length = WIDTH of toggler button on north/south sides - HEIGHT on east/west sides
|
|
, togglerLength_closed: 50 // 100% OR -1 means 'full height/width of resizer bar' - 0 means 'hidden'
|
|
, togglerAlign_open: "center" // top/left, bottom/right, center, OR...
|
|
, togglerAlign_closed: "center" // 1 => nn = offset from top/left, -1 => -nn == offset from bottom/right
|
|
, togglerContent_open: "" // text or HTML to put INSIDE the toggler
|
|
, togglerContent_closed: "" // ditto
|
|
// RESIZING OPTIONS
|
|
, resizerDblClickToggle: true //
|
|
, autoResize: true // IF size is 'auto' or a percentage, then recalc 'pixel size' whenever the layout resizes
|
|
, autoReopen: true // IF a pane was auto-closed due to noRoom, reopen it when there is room? False = leave it closed
|
|
, resizerDragOpacity: 1 // option for ui.draggable
|
|
//, resizerCursor: "" // MUST be pane-specific - cursor when over resizer-bar
|
|
, maskContents: false // true = add DIV-mask over-or-inside this pane so can 'drag' over IFRAMES
|
|
, maskObjects: false // true = add IFRAME-mask over-or-inside this pane to cover objects/applets - content-mask will overlay this mask
|
|
, maskZindex: null // will override zIndexes.content_mask if specified - not applicable to iframe-panes
|
|
, resizingGrid: false // grid size that the resizers will snap-to during resizing, eg: [20,20]
|
|
, livePaneResizing: false // true = LIVE Resizing as resizer is dragged
|
|
, liveContentResizing: false // true = re-measure header/footer heights as resizer is dragged
|
|
, liveResizingTolerance: 1 // how many px change before pane resizes, to control performance
|
|
// SLIDING OPTIONS
|
|
, sliderCursor: "pointer" // cursor when resizer-bar will trigger 'sliding'
|
|
, slideTrigger_open: "click" // click, dblclick, mouseenter
|
|
, slideTrigger_close: "mouseleave"// click, mouseleave
|
|
, slideDelay_open: 300 // applies only for mouseenter event - 0 = instant open
|
|
, slideDelay_close: 300 // applies only for mouseleave event (300ms is the minimum!)
|
|
, hideTogglerOnSlide: false // when pane is slid-open, should the toggler show?
|
|
, preventQuickSlideClose: $.layout.browser.webkit // Chrome triggers slideClosed as it is opening
|
|
, preventPrematureSlideClose: false // handle incorrect mouseleave trigger, like when over a SELECT-list in IE
|
|
// PANE-SPECIFIC TIPS & MESSAGES
|
|
, tips: {
|
|
Open: "Open" // eg: "Open Pane"
|
|
, Close: "Close"
|
|
, Resize: "Resize"
|
|
, Slide: "Slide Open"
|
|
, Pin: "Pin"
|
|
, Unpin: "Un-Pin"
|
|
, noRoomToOpen: "Not enough room to show this panel." // alert if user tries to open a pane that cannot
|
|
, minSizeWarning: "Panel has reached its minimum size" // displays in browser statusbar
|
|
, maxSizeWarning: "Panel has reached its maximum size" // ditto
|
|
}
|
|
// HOT-KEYS & MISC
|
|
, showOverflowOnHover: false // will bind allowOverflow() utility to pane.onMouseOver
|
|
, enableCursorHotkey: true // enabled 'cursor' hotkeys
|
|
//, customHotkey: "" // MUST be pane-specific - EITHER a charCode OR a character
|
|
, customHotkeyModifier: "SHIFT" // either 'SHIFT', 'CTRL' or 'CTRL+SHIFT' - NOT 'ALT'
|
|
// PANE ANIMATION
|
|
// NOTE: fxSss_open, fxSss_close & fxSss_size options (eg: fxName_open) are auto-generated if not passed
|
|
, fxName: "slide" // ('none' or blank), slide, drop, scale -- only relevant to 'open' & 'close', NOT 'size'
|
|
, fxSpeed: null // slow, normal, fast, 200, nnn - if passed, will OVERRIDE fxSettings.duration
|
|
, fxSettings: {} // can be passed, eg: { easing: "easeOutBounce", duration: 1500 }
|
|
, fxOpacityFix: true // tries to fix opacity in IE to restore anti-aliasing after animation
|
|
, animatePaneSizing: false // true = animate resizing after dragging resizer-bar OR sizePane() is called
|
|
/* NOTE: Action-specific FX options are auto-generated from the options above if not specifically set:
|
|
fxName_open: "slide" // 'Open' pane animation
|
|
fnName_close: "slide" // 'Close' pane animation
|
|
fxName_size: "slide" // 'Size' pane animation - when animatePaneSizing = true
|
|
fxSpeed_open: null
|
|
fxSpeed_close: null
|
|
fxSpeed_size: null
|
|
fxSettings_open: {}
|
|
fxSettings_close: {}
|
|
fxSettings_size: {}
|
|
*/
|
|
// CHILD/NESTED LAYOUTS
|
|
, children: null // Layout-options for nested/child layout - even {} is valid as options
|
|
, containerSelector: '' // if child is NOT 'directly nested', a selector to find it/them (can have more than one child layout!)
|
|
, initChildren: true // true = child layout will be created as soon as _this_ layout completes initialization
|
|
, destroyChildren: true // true = destroy child-layout if this pane is destroyed
|
|
, resizeChildren: true // true = trigger child-layout.resizeAll() when this pane is resized
|
|
// EVENT TRIGGERING
|
|
, triggerEventsOnLoad: false // true = trigger onopen OR onclose callbacks when layout initializes
|
|
, triggerEventsDuringLiveResize: true // true = trigger onresize callback REPEATEDLY if livePaneResizing==true
|
|
// PANE CALLBACKS
|
|
, onshow_start: null // CALLBACK when pane STARTS to Show - BEFORE onopen/onhide_start
|
|
, onshow_end: null // CALLBACK when pane ENDS being Shown - AFTER onopen/onhide_end
|
|
, onhide_start: null // CALLBACK when pane STARTS to Close - BEFORE onclose_start
|
|
, onhide_end: null // CALLBACK when pane ENDS being Closed - AFTER onclose_end
|
|
, onopen_start: null // CALLBACK when pane STARTS to Open
|
|
, onopen_end: null // CALLBACK when pane ENDS being Opened
|
|
, onclose_start: null // CALLBACK when pane STARTS to Close
|
|
, onclose_end: null // CALLBACK when pane ENDS being Closed
|
|
, onresize_start: null // CALLBACK when pane STARTS being Resized ***FOR ANY REASON***
|
|
, onresize_end: null // CALLBACK when pane ENDS being Resized ***FOR ANY REASON***
|
|
, onsizecontent_start: null // CALLBACK when sizing of content-element STARTS
|
|
, onsizecontent_end: null // CALLBACK when sizing of content-element ENDS
|
|
, onswap_start: null // CALLBACK when pane STARTS to Swap
|
|
, onswap_end: null // CALLBACK when pane ENDS being Swapped
|
|
, ondrag_start: null // CALLBACK when pane STARTS being ***MANUALLY*** Resized
|
|
, ondrag_end: null // CALLBACK when pane ENDS being ***MANUALLY*** Resized
|
|
}
|
|
/*
|
|
* PANE-SPECIFIC SETTINGS
|
|
* - options listed below MUST be specified per-pane - they CANNOT be set under 'panes'
|
|
* - all options under the 'panes' key can also be set specifically for any pane
|
|
* - most options under the 'panes' key apply only to 'border-panes' - NOT the the center-pane
|
|
*/
|
|
, north: {
|
|
paneSelector: ".ui-layout-north"
|
|
, size: "auto" // eg: "auto", "30%", .30, 200
|
|
, resizerCursor: "n-resize" // custom = url(myCursor.cur)
|
|
, customHotkey: "" // EITHER a charCode (43) OR a character ("o")
|
|
}
|
|
, south: {
|
|
paneSelector: ".ui-layout-south"
|
|
, size: "auto"
|
|
, resizerCursor: "s-resize"
|
|
, customHotkey: ""
|
|
}
|
|
, east: {
|
|
paneSelector: ".ui-layout-east"
|
|
, size: 200
|
|
, resizerCursor: "e-resize"
|
|
, customHotkey: ""
|
|
}
|
|
, west: {
|
|
paneSelector: ".ui-layout-west"
|
|
, size: 200
|
|
, resizerCursor: "w-resize"
|
|
, customHotkey: ""
|
|
}
|
|
, center: {
|
|
paneSelector: ".ui-layout-center"
|
|
, minWidth: 0
|
|
, minHeight: 0
|
|
}
|
|
};
|
|
|
|
$.layout.optionsMap = {
|
|
// layout/global options - NOT pane-options
|
|
layout: ("name,instanceKey,stateManagement,effects,inset,zIndexes,errors,"
|
|
+ "zIndex,scrollToBookmarkOnLoad,showErrorMessages,maskPanesEarly,"
|
|
+ "outset,resizeWithWindow,resizeWithWindowDelay,resizeWithWindowMaxDelay,"
|
|
+ "onresizeall,onresizeall_start,onresizeall_end,onload,onload_start,onload_end,onunload,onunload_start,onunload_end").split(",")
|
|
// borderPanes: [ ALL options that are NOT specified as 'layout' ]
|
|
// default.panes options that apply to the center-pane (most options apply _only_ to border-panes)
|
|
, center: ("paneClass,contentSelector,contentIgnoreSelector,findNestedContent,applyDemoStyles,triggerEventsOnLoad,"
|
|
+ "showOverflowOnHover,maskContents,maskObjects,liveContentResizing,"
|
|
+ "containerSelector,children,initChildren,resizeChildren,destroyChildren,"
|
|
+ "onresize,onresize_start,onresize_end,onsizecontent,onsizecontent_start,onsizecontent_end").split(",")
|
|
// options that MUST be specifically set 'per-pane' - CANNOT set in the panes (defaults) key
|
|
, noDefault: ("paneSelector,resizerCursor,customHotkey").split(",")
|
|
};
|
|
|
|
/**
|
|
* Processes options passed in converts flat-format data into subkey (JSON) format
|
|
* In flat-format, subkeys are _currently_ separated with 2 underscores, like north__optName
|
|
* Plugins may also call this method so they can transform their own data
|
|
*
|
|
* @param {!Object} hash Data/options passed by user - may be a single level or nested levels
|
|
* @param {boolean=} [addKeys=false] Should the primary layout.options keys be added if they do not exist?
|
|
* @return {Object} Returns hash of minWidth & minHeight
|
|
*/
|
|
$.layout.transformData = function (hash, addKeys) {
|
|
var json = addKeys ? { panes: {}, center: {} } : {} // init return object
|
|
, branch, optKey, keys, key, val, i, c;
|
|
|
|
if (typeof hash !== "object") return json; // no options passed
|
|
|
|
// convert all 'flat-keys' to 'sub-key' format
|
|
for (optKey in hash) {
|
|
branch = json;
|
|
val = hash[ optKey ];
|
|
keys = optKey.split("__"); // eg: west__size or north__fxSettings__duration
|
|
c = keys.length - 1;
|
|
// convert underscore-delimited to subkeys
|
|
for (i=0; i <= c; i++) {
|
|
key = keys[i];
|
|
if (i === c) { // last key = value
|
|
if ($.isPlainObject( val ))
|
|
branch[key] = $.layout.transformData( val ); // RECURSE
|
|
else
|
|
branch[key] = val;
|
|
}
|
|
else {
|
|
if (!branch[key])
|
|
branch[key] = {}; // create the subkey
|
|
// recurse to sub-key for next loop - if not done
|
|
branch = branch[key];
|
|
}
|
|
}
|
|
}
|
|
return json;
|
|
};
|
|
|
|
// INTERNAL CONFIG DATA - DO NOT CHANGE THIS!
|
|
$.layout.backwardCompatibility = {
|
|
// data used by renameOldOptions()
|
|
map: {
|
|
// OLD Option Name: NEW Option Name
|
|
applyDefaultStyles: "applyDemoStyles"
|
|
// CHILD/NESTED LAYOUTS
|
|
, childOptions: "children"
|
|
, initChildLayout: "initChildren"
|
|
, destroyChildLayout: "destroyChildren"
|
|
, resizeChildLayout: "resizeChildren"
|
|
, resizeNestedLayout: "resizeChildren"
|
|
// MISC Options
|
|
, resizeWhileDragging: "livePaneResizing"
|
|
, resizeContentWhileDragging: "liveContentResizing"
|
|
, triggerEventsWhileDragging: "triggerEventsDuringLiveResize"
|
|
, maskIframesOnResize: "maskContents"
|
|
// STATE MANAGEMENT
|
|
, useStateCookie: "stateManagement.enabled"
|
|
, "cookie.autoLoad": "stateManagement.autoLoad"
|
|
, "cookie.autoSave": "stateManagement.autoSave"
|
|
, "cookie.keys": "stateManagement.stateKeys"
|
|
, "cookie.name": "stateManagement.cookie.name"
|
|
, "cookie.domain": "stateManagement.cookie.domain"
|
|
, "cookie.path": "stateManagement.cookie.path"
|
|
, "cookie.expires": "stateManagement.cookie.expires"
|
|
, "cookie.secure": "stateManagement.cookie.secure"
|
|
// OLD Language options
|
|
, noRoomToOpenTip: "tips.noRoomToOpen"
|
|
, togglerTip_open: "tips.Close" // open = Close
|
|
, togglerTip_closed: "tips.Open" // closed = Open
|
|
, resizerTip: "tips.Resize"
|
|
, sliderTip: "tips.Slide"
|
|
}
|
|
|
|
/**
|
|
* @param {Object} opts
|
|
*/
|
|
, renameOptions: function (opts) {
|
|
var map = $.layout.backwardCompatibility.map
|
|
, oldData, newData, value
|
|
;
|
|
for (var itemPath in map) {
|
|
oldData = getBranch( itemPath );
|
|
value = oldData.branch[ oldData.key ];
|
|
if (value !== undefined) {
|
|
newData = getBranch( map[itemPath], true );
|
|
newData.branch[ newData.key ] = value;
|
|
delete oldData.branch[ oldData.key ];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {string} path
|
|
* @param {boolean=} [create=false] Create path if does not exist
|
|
*/
|
|
function getBranch (path, create) {
|
|
var a = path.split(".") // split keys into array
|
|
, c = a.length - 1
|
|
, D = { branch: opts, key: a[c] } // init branch at top & set key (last item)
|
|
, i = 0, k, undef;
|
|
for (; i<c; i++) { // skip the last key (data)
|
|
k = a[i];
|
|
if (D.branch[ k ] == undefined) { // child-key does not exist
|
|
if (create) {
|
|
D.branch = D.branch[ k ] = {}; // create child-branch
|
|
}
|
|
else // can't go any farther
|
|
D.branch = {}; // branch is undefined
|
|
}
|
|
else
|
|
D.branch = D.branch[ k ]; // get child-branch
|
|
}
|
|
return D;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param {Object} opts
|
|
*/
|
|
, renameAllOptions: function (opts) {
|
|
var ren = $.layout.backwardCompatibility.renameOptions;
|
|
// rename root (layout) options
|
|
ren( opts );
|
|
// rename 'defaults' to 'panes'
|
|
if (opts.defaults) {
|
|
if (typeof opts.panes !== "object")
|
|
opts.panes = {};
|
|
$.extend(true, opts.panes, opts.defaults);
|
|
delete opts.defaults;
|
|
}
|
|
// rename options in the the options.panes key
|
|
if (opts.panes) ren( opts.panes );
|
|
// rename options inside *each pane key*, eg: options.west
|
|
$.each($.layout.config.allPanes, function (i, pane) {
|
|
if (opts[pane]) ren( opts[pane] );
|
|
});
|
|
return opts;
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
/* ============================================================
|
|
* BEGIN WIDGET: $( selector ).layout( {options} );
|
|
* ============================================================
|
|
*/
|
|
$.fn.layout = function (opts) {
|
|
var
|
|
|
|
// local aliases to global data
|
|
browser = $.layout.browser
|
|
, _c = $.layout.config
|
|
|
|
// local aliases to utlity methods
|
|
, cssW = $.layout.cssWidth
|
|
, cssH = $.layout.cssHeight
|
|
, elDims = $.layout.getElementDimensions
|
|
, styles = $.layout.getElementStyles
|
|
, evtObj = $.layout.getEventObject
|
|
, evtPane = $.layout.parsePaneName
|
|
|
|
/**
|
|
* options - populated by initOptions()
|
|
*/
|
|
, options = $.extend(true, {}, $.layout.defaults)
|
|
, effects = options.effects = $.extend(true, {}, $.layout.effects)
|
|
|
|
/**
|
|
* layout-state object
|
|
*/
|
|
, state = {
|
|
// generate unique ID to use for event.namespace so can unbind only events added by 'this layout'
|
|
id: "layout"+ $.now() // code uses alias: sID
|
|
, initialized: false
|
|
, paneResizing: false
|
|
, panesSliding: {}
|
|
, container: { // list all keys referenced in code to avoid compiler error msgs
|
|
innerWidth: 0
|
|
, innerHeight: 0
|
|
, outerWidth: 0
|
|
, outerHeight: 0
|
|
, layoutWidth: 0
|
|
, layoutHeight: 0
|
|
}
|
|
, north: { childIdx: 0 }
|
|
, south: { childIdx: 0 }
|
|
, east: { childIdx: 0 }
|
|
, west: { childIdx: 0 }
|
|
, center: { childIdx: 0 }
|
|
}
|
|
|
|
/**
|
|
* parent/child-layout pointers
|
|
*/
|
|
//, hasParentLayout = false - exists ONLY inside Instance so can be set externally
|
|
, children = {
|
|
north: null
|
|
, south: null
|
|
, east: null
|
|
, west: null
|
|
, center: null
|
|
}
|
|
|
|
/*
|
|
* ###########################
|
|
* INTERNAL HELPER FUNCTIONS
|
|
* ###########################
|
|
*/
|
|
|
|
/**
|
|
* Manages all internal timers
|
|
*/
|
|
, timer = {
|
|
data: {}
|
|
, set: function (s, fn, ms) { timer.clear(s); timer.data[s] = setTimeout(fn, ms); }
|
|
, clear: function (s) { var t=timer.data; if (t[s]) {clearTimeout(t[s]); delete t[s];} }
|
|
}
|
|
|
|
/**
|
|
* Alert or console.log a message - IF option is enabled.
|
|
*
|
|
* @param {(string|!Object)} msg Message (or debug-data) to display
|
|
* @param {boolean=} [popup=false] True by default, means 'alert', false means use console.log
|
|
* @param {boolean=} [debug=false] True means is a widget debugging message
|
|
*/
|
|
, _log = function (msg, popup, debug) {
|
|
var o = options;
|
|
if ((o.showErrorMessages && !debug) || (debug && o.showDebugMessages))
|
|
$.layout.msg( o.name +' / '+ msg, (popup !== false) );
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Executes a Callback function after a trigger event, like resize, open or close
|
|
*
|
|
* @param {string} evtName Name of the layout callback, eg "onresize_start"
|
|
* @param {(string|boolean)=} [pane=""] This is passed only so we can pass the 'pane object' to the callback
|
|
* @param {(string|boolean)=} [skipBoundEvents=false] True = do not run events bound to the elements - only the callbacks set in options
|
|
*/
|
|
, _runCallbacks = function (evtName, pane, skipBoundEvents) {
|
|
var hasPane = pane && isStr(pane)
|
|
, s = hasPane ? state[pane] : state
|
|
, o = hasPane ? options[pane] : options
|
|
, lName = options.name
|
|
// names like onopen and onopen_end separate are interchangeable in options...
|
|
, lng = evtName + (evtName.match(/_/) ? "" : "_end")
|
|
, shrt = lng.match(/_end$/) ? lng.substr(0, lng.length - 4) : ""
|
|
, fn = o[lng] || o[shrt]
|
|
, retVal = "NC" // NC = No Callback
|
|
, args = []
|
|
, $P
|
|
;
|
|
if ( !hasPane && $.type(pane) === 'boolean' ) {
|
|
skipBoundEvents = pane; // allow pane param to be skipped for Layout callback
|
|
pane = "";
|
|
}
|
|
|
|
// first trigger the callback set in the options
|
|
if (fn) {
|
|
try {
|
|
// convert function name (string) to function object
|
|
if (isStr( fn )) {
|
|
if (fn.match(/,/)) {
|
|
// function name cannot contain a comma,
|
|
// so must be a function name AND a parameter to pass
|
|
args = fn.split(",")
|
|
, fn = eval(args[0]);
|
|
}
|
|
else // just the name of an external function?
|
|
fn = eval(fn);
|
|
}
|
|
// execute the callback, if exists
|
|
if ($.isFunction( fn )) {
|
|
if (args.length)
|
|
retVal = g(fn)(args[1]); // pass the argument parsed from 'list'
|
|
else if ( hasPane )
|
|
// pass data: pane-name, pane-element, pane-state, pane-options, and layout-name
|
|
retVal = g(fn)( pane, $Ps[pane], s, o, lName );
|
|
else // must be a layout/container callback - pass suitable info
|
|
retVal = g(fn)( Instance, s, o, lName );
|
|
}
|
|
}
|
|
catch (ex) {
|
|
_log( options.errors.callbackError.replace(/EVENT/, $.trim((pane || "") +" "+ lng)), false );
|
|
if ($.type(ex) === 'string' && string.length)
|
|
_log('Exception: '+ ex, false );
|
|
}
|
|
}
|
|
|
|
// trigger additional events bound directly to the pane
|
|
if (!skipBoundEvents && retVal !== false) {
|
|
if ( hasPane ) { // PANE events can be bound to each pane-elements
|
|
$P = $Ps[pane];
|
|
o = options[pane];
|
|
s = state[pane];
|
|
$P.triggerHandler('layoutpane'+ lng, [ pane, $P, s, o, lName ]);
|
|
if (shrt)
|
|
$P.triggerHandler('layoutpane'+ shrt, [ pane, $P, s, o, lName ]);
|
|
}
|
|
else { // LAYOUT events can be bound to the container-element
|
|
$N.triggerHandler('layout'+ lng, [ Instance, s, o, lName ]);
|
|
if (shrt)
|
|
$N.triggerHandler('layout'+ shrt, [ Instance, s, o, lName ]);
|
|
}
|
|
}
|
|
|
|
// ALWAYS resizeChildren after an onresize_end event - even during initialization
|
|
// IGNORE onsizecontent_end event because causes child-layouts to resize TWICE
|
|
if (hasPane && evtName === "onresize_end") // BAD: || evtName === "onsizecontent_end"
|
|
resizeChildren(pane+"", true); // compiler hack -force string
|
|
|
|
return retVal;
|
|
|
|
function g (f) { return f; }; // compiler hack
|
|
}
|
|
|
|
|
|
/**
|
|
* cure iframe display issues in IE & other browsers
|
|
*/
|
|
, _fixIframe = function (pane) {
|
|
if (browser.mozilla) return; // skip FireFox - it auto-refreshes iframes onShow
|
|
var $P = $Ps[pane];
|
|
// if the 'pane' is an iframe, do it
|
|
if (state[pane].tagName === "IFRAME")
|
|
$P.css(_c.hidden).css(_c.visible);
|
|
else // ditto for any iframes INSIDE the pane
|
|
$P.find('IFRAME').css(_c.hidden).css(_c.visible);
|
|
}
|
|
|
|
/**
|
|
* @param {string} pane Can accept ONLY a 'pane' (east, west, etc)
|
|
* @param {number=} outerSize (optional) Can pass a width, allowing calculations BEFORE element is resized
|
|
* @return {number} Returns the innerHeight/Width of el by subtracting padding and borders
|
|
*/
|
|
, cssSize = function (pane, outerSize) {
|
|
var fn = _c[pane].dir=="horz" ? cssH : cssW;
|
|
return fn($Ps[pane], outerSize);
|
|
}
|
|
|
|
/**
|
|
* @param {string} pane Can accept ONLY a 'pane' (east, west, etc)
|
|
* @return {Object} Returns hash of minWidth & minHeight
|
|
*/
|
|
, cssMinDims = function (pane) {
|
|
// minWidth/Height means CSS width/height = 1px
|
|
var $P = $Ps[pane]
|
|
, dir = _c[pane].dir
|
|
, d = {
|
|
minWidth: 1001 - cssW($P, 1000)
|
|
, minHeight: 1001 - cssH($P, 1000)
|
|
}
|
|
;
|
|
if (dir === "horz") d.minSize = d.minHeight;
|
|
if (dir === "vert") d.minSize = d.minWidth;
|
|
return d;
|
|
}
|
|
|
|
// TODO: see if these methods can be made more useful...
|
|
// TODO: *maybe* return cssW/H from these so caller can use this info
|
|
|
|
/**
|
|
* @param {(string|!Object)} el
|
|
* @param {number=} outerWidth
|
|
* @param {boolean=} [autoHide=false]
|
|
*/
|
|
, setOuterWidth = function (el, outerWidth, autoHide) {
|
|
var $E = el, w;
|
|
if (isStr(el)) $E = $Ps[el]; // west
|
|
else if (!el.jquery) $E = $(el);
|
|
w = cssW($E, outerWidth);
|
|
$E.css({ width: w });
|
|
if (w > 0) {
|
|
if (autoHide && $E.data('autoHidden') && $E.innerHeight() > 0) {
|
|
$E.show().data('autoHidden', false);
|
|
if (!browser.mozilla) // FireFox refreshes iframes - IE does not
|
|
// make hidden, then visible to 'refresh' display after animation
|
|
$E.css(_c.hidden).css(_c.visible);
|
|
}
|
|
}
|
|
else if (autoHide && !$E.data('autoHidden'))
|
|
$E.hide().data('autoHidden', true);
|
|
}
|
|
|
|
/**
|
|
* @param {(string|!Object)} el
|
|
* @param {number=} outerHeight
|
|
* @param {boolean=} [autoHide=false]
|
|
*/
|
|
, setOuterHeight = function (el, outerHeight, autoHide) {
|
|
var $E = el, h;
|
|
if (isStr(el)) $E = $Ps[el]; // west
|
|
else if (!el.jquery) $E = $(el);
|
|
h = cssH($E, outerHeight);
|
|
$E.css({ height: h, visibility: "visible" }); // may have been 'hidden' by sizeContent
|
|
if (h > 0 && $E.innerWidth() > 0) {
|
|
if (autoHide && $E.data('autoHidden')) {
|
|
$E.show().data('autoHidden', false);
|
|
if (!browser.mozilla) // FireFox refreshes iframes - IE does not
|
|
$E.css(_c.hidden).css(_c.visible);
|
|
}
|
|
}
|
|
else if (autoHide && !$E.data('autoHidden'))
|
|
$E.hide().data('autoHidden', true);
|
|
}
|
|
|
|
|
|
/**
|
|
* Converts any 'size' params to a pixel/integer size, if not already
|
|
* If 'auto' or a decimal/percentage is passed as 'size', a pixel-size is calculated
|
|
*
|
|
/**
|
|
* @param {string} pane
|
|
* @param {(string|number)=} size
|
|
* @param {string=} [dir]
|
|
* @return {number}
|
|
*/
|
|
, _parseSize = function (pane, size, dir) {
|
|
if (!dir) dir = _c[pane].dir;
|
|
|
|
if (isStr(size) && size.match(/%/))
|
|
size = (size === '100%') ? -1 : parseInt(size, 10) / 100; // convert % to decimal
|
|
|
|
if (size === 0)
|
|
return 0;
|
|
else if (size >= 1)
|
|
return parseInt(size, 10);
|
|
|
|
var o = options, avail = 0;
|
|
if (dir=="horz") // north or south or center.minHeight
|
|
avail = sC.innerHeight - ($Ps.north ? o.north.spacing_open : 0) - ($Ps.south ? o.south.spacing_open : 0);
|
|
else if (dir=="vert") // east or west or center.minWidth
|
|
avail = sC.innerWidth - ($Ps.west ? o.west.spacing_open : 0) - ($Ps.east ? o.east.spacing_open : 0);
|
|
|
|
if (size === -1) // -1 == 100%
|
|
return avail;
|
|
else if (size > 0) // percentage, eg: .25
|
|
return round(avail * size);
|
|
else if (pane=="center")
|
|
return 0;
|
|
else { // size < 0 || size=='auto' || size==Missing || size==Invalid
|
|
// auto-size the pane
|
|
var dim = (dir === "horz" ? "height" : "width")
|
|
, $P = $Ps[pane]
|
|
, $C = dim === 'height' ? $Cs[pane] : false
|
|
, vis = $.layout.showInvisibly($P) // show pane invisibly if hidden
|
|
, szP = $P.css(dim) // SAVE current pane size
|
|
, szC = $C ? $C.css(dim) : 0 // SAVE current content size
|
|
;
|
|
$P.css(dim, "auto");
|
|
if ($C) $C.css(dim, "auto");
|
|
size = (dim === "height") ? $P.outerHeight() : $P.outerWidth(); // MEASURE
|
|
$P.css(dim, szP).css(vis); // RESET size & visibility
|
|
if ($C) $C.css(dim, szC);
|
|
return size;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculates current 'size' (outer-width or outer-height) of a border-pane - optionally with 'pane-spacing' added
|
|
*
|
|
* @param {(string|!Object)} pane
|
|
* @param {boolean=} [inclSpace=false]
|
|
* @return {number} Returns EITHER Width for east/west panes OR Height for north/south panes
|
|
*/
|
|
, getPaneSize = function (pane, inclSpace) {
|
|
var
|
|
$P = $Ps[pane]
|
|
, o = options[pane]
|
|
, s = state[pane]
|
|
, oSp = (inclSpace ? o.spacing_open : 0)
|
|
, cSp = (inclSpace ? o.spacing_closed : 0)
|
|
;
|
|
if (!$P || s.isHidden)
|
|
return 0;
|
|
else if (s.isClosed || (s.isSliding && inclSpace))
|
|
return cSp;
|
|
else if (_c[pane].dir === "horz")
|
|
return $P.outerHeight() + oSp;
|
|
else // dir === "vert"
|
|
return $P.outerWidth() + oSp;
|
|
}
|
|
|
|
/**
|
|
* Calculate min/max pane dimensions and limits for resizing
|
|
*
|
|
* @param {string} pane
|
|
* @param {boolean=} [slide=false]
|
|
*/
|
|
, setSizeLimits = function (pane, slide) {
|
|
if (!isInitialized()) return;
|
|
var
|
|
o = options[pane]
|
|
, s = state[pane]
|
|
, c = _c[pane]
|
|
, dir = c.dir
|
|
, type = c.sizeType.toLowerCase()
|
|
, isSliding = (slide != undefined ? slide : s.isSliding) // only open() passes 'slide' param
|
|
, $P = $Ps[pane]
|
|
, paneSpacing = o.spacing_open
|
|
// measure the pane on the *opposite side* from this pane
|
|
, altPane = _c.oppositeEdge[pane]
|
|
, altS = state[altPane]
|
|
, $altP = $Ps[altPane]
|
|
, altPaneSize = (!$altP || altS.isVisible===false || altS.isSliding ? 0 : (dir=="horz" ? $altP.outerHeight() : $altP.outerWidth()))
|
|
, altPaneSpacing = ((!$altP || altS.isHidden ? 0 : options[altPane][ altS.isClosed !== false ? "spacing_closed" : "spacing_open" ]) || 0)
|
|
// limitSize prevents this pane from 'overlapping' opposite pane
|
|
, containerSize = (dir=="horz" ? sC.innerHeight : sC.innerWidth)
|
|
, minCenterDims = cssMinDims("center")
|
|
, minCenterSize = dir=="horz" ? max(options.center.minHeight, minCenterDims.minHeight) : max(options.center.minWidth, minCenterDims.minWidth)
|
|
// if pane is 'sliding', then ignore center and alt-pane sizes - because 'overlays' them
|
|
, limitSize = (containerSize - paneSpacing - (isSliding ? 0 : (_parseSize("center", minCenterSize, dir) + altPaneSize + altPaneSpacing)))
|
|
, minSize = s.minSize = max( _parseSize(pane, o.minSize), cssMinDims(pane).minSize )
|
|
, maxSize = s.maxSize = min( (o.maxSize ? _parseSize(pane, o.maxSize) : 100000), limitSize )
|
|
, r = s.resizerPosition = {} // used to set resizing limits
|
|
, top = sC.inset.top
|
|
, left = sC.inset.left
|
|
, W = sC.innerWidth
|
|
, H = sC.innerHeight
|
|
, rW = o.spacing_open // subtract resizer-width to get top/left position for south/east
|
|
;
|
|
switch (pane) {
|
|
case "north": r.min = top + minSize;
|
|
r.max = top + maxSize;
|
|
break;
|
|
case "west": r.min = left + minSize;
|
|
r.max = left + maxSize;
|
|
break;
|
|
case "south": r.min = top + H - maxSize - rW;
|
|
r.max = top + H - minSize - rW;
|
|
break;
|
|
case "east": r.min = left + W - maxSize - rW;
|
|
r.max = left + W - minSize - rW;
|
|
break;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Returns data for setting the size/position of center pane. Also used to set Height for east/west panes
|
|
*
|
|
* @return JSON Returns a hash of all dimensions: top, bottom, left, right, (outer) width and (outer) height
|
|
*/
|
|
, calcNewCenterPaneDims = function () {
|
|
var d = {
|
|
top: getPaneSize("north", true) // true = include 'spacing' value for pane
|
|
, bottom: getPaneSize("south", true)
|
|
, left: getPaneSize("west", true)
|
|
, right: getPaneSize("east", true)
|
|
, width: 0
|
|
, height: 0
|
|
};
|
|
|
|
// NOTE: sC = state.container
|
|
// calc center-pane outer dimensions
|
|
d.width = sC.innerWidth - d.left - d.right; // outerWidth
|
|
d.height = sC.innerHeight - d.bottom - d.top; // outerHeight
|
|
// add the 'container border/padding' to get final positions relative to the container
|
|
d.top += sC.inset.top;
|
|
d.bottom += sC.inset.bottom;
|
|
d.left += sC.inset.left;
|
|
d.right += sC.inset.right;
|
|
|
|
return d;
|
|
}
|
|
|
|
|
|
/**
|
|
* @param {!Object} el
|
|
* @param {boolean=} [allStates=false]
|
|
*/
|
|
, getHoverClasses = function (el, allStates) {
|
|
var
|
|
$El = $(el)
|
|
, type = $El.data("layoutRole")
|
|
, pane = $El.data("layoutEdge")
|
|
, o = options[pane]
|
|
, root = o[type +"Class"]
|
|
, _pane = "-"+ pane // eg: "-west"
|
|
, _open = "-open"
|
|
, _closed = "-closed"
|
|
, _slide = "-sliding"
|
|
, _hover = "-hover " // NOTE the trailing space
|
|
, _state = $El.hasClass(root+_closed) ? _closed : _open
|
|
, _alt = _state === _closed ? _open : _closed
|
|
, classes = (root+_hover) + (root+_pane+_hover) + (root+_state+_hover) + (root+_pane+_state+_hover)
|
|
;
|
|
if (allStates) // when 'removing' classes, also remove alternate-state classes
|
|
classes += (root+_alt+_hover) + (root+_pane+_alt+_hover);
|
|
|
|
if (type=="resizer" && $El.hasClass(root+_slide))
|
|
classes += (root+_slide+_hover) + (root+_pane+_slide+_hover);
|
|
|
|
return $.trim(classes);
|
|
}
|
|
, addHover = function (evt, el) {
|
|
var $E = $(el || this);
|
|
if (evt && $E.data("layoutRole") === "toggler")
|
|
evt.stopPropagation(); // prevent triggering 'slide' on Resizer-bar
|
|
$E.addClass( getHoverClasses($E) );
|
|
}
|
|
, removeHover = function (evt, el) {
|
|
var $E = $(el || this);
|
|
$E.removeClass( getHoverClasses($E, true) );
|
|
}
|
|
|
|
, onResizerEnter = function (evt) { // ALSO called by toggler.mouseenter
|
|
var pane = $(this).data("layoutEdge")
|
|
, s = state[pane]
|
|
;
|
|
// ignore closed-panes and mouse moving back & forth over resizer!
|
|
// also ignore if ANY pane is currently resizing
|
|
if ( s.isClosed || s.isResizing || state.paneResizing ) return;
|
|
|
|
if ($.fn.disableSelection)
|
|
$("body").disableSelection();
|
|
if (options.maskPanesEarly)
|
|
showMasks( pane, { resizing: true });
|
|
}
|
|
, onResizerLeave = function (evt, el) {
|
|
var e = el || this // el is only passed when called by the timer
|
|
, pane = $(e).data("layoutEdge")
|
|
, name = pane +"ResizerLeave"
|
|
;
|
|
timer.clear(pane+"_openSlider"); // cancel slideOpen timer, if set
|
|
timer.clear(name); // cancel enableSelection timer - may re/set below
|
|
// this method calls itself on a timer because it needs to allow
|
|
// enough time for dragging to kick-in and set the isResizing flag
|
|
// dragging has a 100ms delay set, so this delay must be >100
|
|
if (!el) // 1st call - mouseleave event
|
|
timer.set(name, function(){ onResizerLeave(evt, e); }, 200);
|
|
// if user is resizing, then dragStop will enableSelection(), so can skip it here
|
|
else if ( !state.paneResizing ) { // 2nd call - by timer
|
|
if ($.fn.enableSelection)
|
|
$("body").enableSelection();
|
|
if (options.maskPanesEarly)
|
|
hideMasks();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* ###########################
|
|
* INITIALIZATION METHODS
|
|
* ###########################
|
|
*/
|
|
|
|
/**
|
|
* Initialize the layout - called automatically whenever an instance of layout is created
|
|
*
|
|
* @see none - triggered onInit
|
|
* @return mixed true = fully initialized | false = panes not initialized (yet) | 'cancel' = abort
|
|
*/
|
|
, _create = function () {
|
|
// initialize config/options
|
|
initOptions();
|
|
var o = options
|
|
, s = state;
|
|
|
|
// TEMP state so isInitialized returns true during init process
|
|
s.creatingLayout = true;
|
|
|
|
// init plugins for this layout, if there are any (eg: stateManagement)
|
|
runPluginCallbacks( Instance, $.layout.onCreate );
|
|
|
|
// options & state have been initialized, so now run beforeLoad callback
|
|
// onload will CANCEL layout creation if it returns false
|
|
if (false === _runCallbacks("onload_start"))
|
|
return 'cancel';
|
|
|
|
// initialize the container element
|
|
_initContainer();
|
|
|
|
// bind hotkey function - keyDown - if required
|
|
initHotkeys();
|
|
|
|
// bind window.onunload
|
|
$(window).bind("unload."+ sID, unload);
|
|
|
|
// init plugins for this layout, if there are any (eg: customButtons)
|
|
runPluginCallbacks( Instance, $.layout.onLoad );
|
|
|
|
// if layout elements are hidden, then layout WILL NOT complete initialization!
|
|
// initLayoutElements will set initialized=true and run the onload callback IF successful
|
|
if (o.initPanes) _initLayoutElements();
|
|
|
|
delete s.creatingLayout;
|
|
|
|
return state.initialized;
|
|
}
|
|
|
|
/**
|
|
* Initialize the layout IF not already
|
|
*
|
|
* @see All methods in Instance run this test
|
|
* @return boolean true = layoutElements have been initialized | false = panes are not initialized (yet)
|
|
*/
|
|
, isInitialized = function () {
|
|
if (state.initialized || state.creatingLayout) return true; // already initialized
|
|
else return _initLayoutElements(); // try to init panes NOW
|
|
}
|
|
|
|
/**
|
|
* Initialize the layout - called automatically whenever an instance of layout is created
|
|
*
|
|
* @see _create() & isInitialized
|
|
* @param {boolean=} [retry=false] // indicates this is a 2nd try
|
|
* @return An object pointer to the instance created
|
|
*/
|
|
, _initLayoutElements = function (retry) {
|
|
// initialize config/options
|
|
var o = options;
|
|
// CANNOT init panes inside a hidden container!
|
|
if (!$N.is(":visible")) {
|
|
// handle Chrome bug where popup window 'has no height'
|
|
// if layout is BODY element, try again in 50ms
|
|
// SEE: http://layout.jquery-dev.net/samples/test_popup_window.html
|
|
if ( !retry && browser.webkit && $N[0].tagName === "BODY" )
|
|
setTimeout(function(){ _initLayoutElements(true); }, 50);
|
|
return false;
|
|
}
|
|
|
|
// a center pane is required, so make sure it exists
|
|
if (!getPane("center").length) {
|
|
return _log( o.errors.centerPaneMissing );
|
|
}
|
|
|
|
// TEMP state so isInitialized returns true during init process
|
|
state.creatingLayout = true;
|
|
|
|
// update Container dims
|
|
$.extend(sC, elDims( $N, o.inset )); // passing inset means DO NOT include insetX values
|
|
|
|
// initialize all layout elements
|
|
initPanes(); // size & position panes - calls initHandles() - which calls initResizable()
|
|
|
|
if (o.scrollToBookmarkOnLoad) {
|
|
var l = self.location;
|
|
if (l.hash) l.replace( l.hash ); // scrollTo Bookmark
|
|
}
|
|
|
|
// check to see if this layout 'nested' inside a pane
|
|
if (Instance.hasParentLayout)
|
|
o.resizeWithWindow = false;
|
|
// bind resizeAll() for 'this layout instance' to window.resize event
|
|
else if (o.resizeWithWindow)
|
|
$(window).bind("resize."+ sID, windowResize);
|
|
|
|
delete state.creatingLayout;
|
|
state.initialized = true;
|
|
|
|
// init plugins for this layout, if there are any
|
|
runPluginCallbacks( Instance, $.layout.onReady );
|
|
|
|
// now run the onload callback, if exists
|
|
_runCallbacks("onload_end");
|
|
|
|
return true; // elements initialized successfully
|
|
}
|
|
|
|
/**
|
|
* Initialize nested layouts for a specific pane - can optionally pass layout-options
|
|
*
|
|
* @param {(string|Object)} evt_or_pane The pane being opened, ie: north, south, east, or west
|
|
* @param {Object=} [opts] Layout-options - if passed, will OVERRRIDE options[pane].children
|
|
* @return An object pointer to the layout instance created - or null
|
|
*/
|
|
, createChildren = function (evt_or_pane, opts) {
|
|
var pane = evtPane.call(this, evt_or_pane)
|
|
, $P = $Ps[pane]
|
|
;
|
|
if (!$P) return;
|
|
var $C = $Cs[pane]
|
|
, s = state[pane]
|
|
, o = options[pane]
|
|
, sm = options.stateManagement || {}
|
|
, cos = opts ? (o.children = opts) : o.children
|
|
;
|
|
if ( $.isPlainObject( cos ) )
|
|
cos = [ cos ]; // convert a hash to a 1-elem array
|
|
else if (!cos || !$.isArray( cos ))
|
|
return;
|
|
|
|
$.each( cos, function (idx, co) {
|
|
if ( !$.isPlainObject( co ) ) return;
|
|
|
|
// determine which element is supposed to be the 'child container'
|
|
// if pane has a 'containerSelector' OR a 'content-div', use those instead of the pane
|
|
var $containers = co.containerSelector ? $P.find( co.containerSelector ) : ($C || $P);
|
|
|
|
$containers.each(function(){
|
|
var $cont = $(this)
|
|
, child = $cont.data("layout") // see if a child-layout ALREADY exists on this element
|
|
;
|
|
// if no layout exists, but children are set, try to create the layout now
|
|
if (!child) {
|
|
// TODO: see about moving this to the stateManagement plugin, as a method
|
|
// set a unique child-instance key for this layout, if not already set
|
|
setInstanceKey({ container: $cont, options: co }, s );
|
|
// If THIS layout has a hash in stateManagement.autoLoad,
|
|
// then see if it also contains state-data for this child-layout
|
|
// If so, copy the stateData to child.options.stateManagement.autoLoad
|
|
if ( sm.includeChildren && state.stateData[pane] ) {
|
|
// THIS layout's state was cached when its state was loaded
|
|
var paneChildren = state.stateData[pane].children || {}
|
|
, childState = paneChildren[ co.instanceKey ]
|
|
, co_sm = co.stateManagement || (co.stateManagement = { autoLoad: true })
|
|
;
|
|
// COPY the stateData into the autoLoad key
|
|
if ( co_sm.autoLoad === true && childState ) {
|
|
co_sm.autoSave = false; // disable autoSave because saving handled by parent-layout
|
|
co_sm.includeChildren = true; // cascade option - FOR NOW
|
|
co_sm.autoLoad = $.extend(true, {}, childState); // COPY the state-hash
|
|
}
|
|
}
|
|
|
|
// create the layout
|
|
child = $cont.layout( co );
|
|
|
|
// if successful, update data
|
|
if (child) {
|
|
// add the child and update all layout-pointers
|
|
// MAY have already been done by child-layout calling parent.refreshChildren()
|
|
refreshChildren( pane, child );
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
, setInstanceKey = function (child, parentPaneState) {
|
|
// create a named key for use in state and instance branches
|
|
var $c = child.container
|
|
, o = child.options
|
|
, sm = o.stateManagement
|
|
, key = o.instanceKey || $c.data("layoutInstanceKey")
|
|
;
|
|
if (!key) key = (sm && sm.cookie ? sm.cookie.name : '') || o.name; // look for a name/key
|
|
if (!key) key = "layout"+ (++parentPaneState.childIdx); // if no name/key found, generate one
|
|
else key = key.replace(/[^\w-]/gi, '_').replace(/_{2,}/g, '_'); // ensure is valid as a hash key
|
|
o.instanceKey = key;
|
|
$c.data("layoutInstanceKey", key); // useful if layout is destroyed and then recreated
|
|
return key;
|
|
}
|
|
|
|
/**
|
|
* @param {string} pane The pane being opened, ie: north, south, east, or west
|
|
* @param {Object=} newChild New child-layout Instance to add to this pane
|
|
*/
|
|
, refreshChildren = function (pane, newChild) {
|
|
var $P = $Ps[pane]
|
|
, pC = children[pane]
|
|
, s = state[pane]
|
|
, o
|
|
;
|
|
// check for destroy()ed layouts and update the child pointers & arrays
|
|
if ($.isPlainObject( pC )) {
|
|
$.each( pC, function (key, child) {
|
|
if (child.destroyed) delete pC[key]
|
|
});
|
|
// if no more children, remove the children hash
|
|
if ($.isEmptyObject( pC ))
|
|
pC = children[pane] = null; // clear children hash
|
|
}
|
|
|
|
// see if there is a directly-nested layout inside this pane
|
|
// if there is, then there can be only ONE child-layout, so check that...
|
|
if (!newChild && !pC) {
|
|
newChild = $P.data("layout");
|
|
}
|
|
|
|
// if a newChild instance was passed, add it to children[pane]
|
|
if (newChild) {
|
|
// update child.state
|
|
newChild.hasParentLayout = true; // set parent-flag in child
|
|
// instanceKey is a key-name used in both state and children
|
|
o = newChild.options;
|
|
// set a unique child-instance key for this layout, if not already set
|
|
setInstanceKey( newChild, s );
|
|
// add pointer to pane.children hash
|
|
if (!pC) pC = children[pane] = {}; // create an empty children hash
|
|
pC[ o.instanceKey ] = newChild.container.data("layout"); // add childLayout instance
|
|
}
|
|
|
|
// ALWAYS refresh the pane.children alias, even if null
|
|
Instance[pane].children = children[pane];
|
|
|
|
// if newChild was NOT passed - see if there is a child layout NOW
|
|
if (!newChild) {
|
|
createChildren(pane); // MAY create a child and re-call this method
|
|
}
|
|
}
|
|
|
|
, windowResize = function () {
|
|
var o = options
|
|
, delay = Number(o.resizeWithWindowDelay);
|
|
if (delay < 10) delay = 100; // MUST have a delay!
|
|
// resizing uses a delay-loop because the resize event fires repeatly - except in FF, but delay anyway
|
|
timer.clear("winResize"); // if already running
|
|
timer.set("winResize", function(){
|
|
timer.clear("winResize");
|
|
timer.clear("winResizeRepeater");
|
|
var dims = elDims( $N, o.inset );
|
|
// only trigger resizeAll() if container has changed size
|
|
if (dims.innerWidth !== sC.innerWidth || dims.innerHeight !== sC.innerHeight)
|
|
resizeAll();
|
|
}, delay);
|
|
// ALSO set fixed-delay timer, if not already running
|
|
if (!timer.data["winResizeRepeater"]) setWindowResizeRepeater();
|
|
}
|
|
|
|
, setWindowResizeRepeater = function () {
|
|
var delay = Number(options.resizeWithWindowMaxDelay);
|
|
if (delay > 0)
|
|
timer.set("winResizeRepeater", function(){ setWindowResizeRepeater(); resizeAll(); }, delay);
|
|
}
|
|
|
|
, unload = function () {
|
|
var o = options;
|
|
|
|
_runCallbacks("onunload_start");
|
|
|
|
// trigger plugin callabacks for this layout (eg: stateManagement)
|
|
runPluginCallbacks( Instance, $.layout.onUnload );
|
|
|
|
_runCallbacks("onunload_end");
|
|
}
|
|
|
|
/**
|
|
* Validate and initialize container CSS and events
|
|
*
|
|
* @see _create()
|
|
*/
|
|
, _initContainer = function () {
|
|
var
|
|
N = $N[0]
|
|
, $H = $("html")
|
|
, tag = sC.tagName = N.tagName
|
|
, id = sC.id = N.id
|
|
, cls = sC.className = N.className
|
|
, o = options
|
|
, name = o.name
|
|
, props = "position,margin,padding,border"
|
|
, css = "layoutCSS"
|
|
, CSS = {}
|
|
, hid = "hidden" // used A LOT!
|
|
// see if this container is a 'pane' inside an outer-layout
|
|
, parent = $N.data("parentLayout") // parent-layout Instance
|
|
, pane = $N.data("layoutEdge") // pane-name in parent-layout
|
|
, isChild = parent && pane
|
|
, num = $.layout.cssNum
|
|
, $parent, n
|
|
;
|
|
// sC = state.container
|
|
sC.selector = $N.selector.split(".slice")[0];
|
|
sC.ref = (o.name ? o.name +' layout / ' : '') + tag + (id ? "#"+id : cls ? '.['+cls+']' : ''); // used in messages
|
|
sC.isBody = (tag === "BODY");
|
|
|
|
// try to find a parent-layout
|
|
if (!isChild && !sC.isBody) {
|
|
$parent = $N.closest("."+ $.layout.defaults.panes.paneClass);
|
|
parent = $parent.data("parentLayout");
|
|
pane = $parent.data("layoutEdge");
|
|
isChild = parent && pane;
|
|
}
|
|
|
|
$N .data({
|
|
layout: Instance
|
|
, layoutContainer: sID // FLAG to indicate this is a layout-container - contains unique internal ID
|
|
})
|
|
.addClass(o.containerClass)
|
|
;
|
|
var layoutMethods = {
|
|
destroy: ''
|
|
, initPanes: ''
|
|
, resizeAll: 'resizeAll'
|
|
, resize: 'resizeAll'
|
|
};
|
|
// loop hash and bind all methods - include layoutID namespacing
|
|
for (name in layoutMethods) {
|
|
$N.bind("layout"+ name.toLowerCase() +"."+ sID, Instance[ layoutMethods[name] || name ]);
|
|
}
|
|
|
|
// if this container is another layout's 'pane', then set child/parent pointers
|
|
if (isChild) {
|
|
// update parent flag
|
|
Instance.hasParentLayout = true;
|
|
// set pointers to THIS child-layout (Instance) in parent-layout
|
|
parent.refreshChildren( pane, Instance );
|
|
}
|
|
|
|
// SAVE original container CSS for use in destroy()
|
|
if (!$N.data(css)) {
|
|
// handle props like overflow different for BODY & HTML - has 'system default' values
|
|
if (sC.isBody) {
|
|
// SAVE <BODY> CSS
|
|
$N.data(css, $.extend( styles($N, props), {
|
|
height: $N.css("height")
|
|
, overflow: $N.css("overflow")
|
|
, overflowX: $N.css("overflowX")
|
|
, overflowY: $N.css("overflowY")
|
|
}));
|
|
// ALSO SAVE <HTML> CSS
|
|
$H.data(css, $.extend( styles($H, 'padding'), {
|
|
height: "auto" // FF would return a fixed px-size!
|
|
, overflow: $H.css("overflow")
|
|
, overflowX: $H.css("overflowX")
|
|
, overflowY: $H.css("overflowY")
|
|
}));
|
|
}
|
|
else // handle props normally for non-body elements
|
|
$N.data(css, styles($N, props+",top,bottom,left,right,width,height,overflow,overflowX,overflowY") );
|
|
}
|
|
|
|
try {
|
|
// common container CSS
|
|
CSS = {
|
|
overflow: hid
|
|
, overflowX: hid
|
|
, overflowY: hid
|
|
};
|
|
$N.css( CSS );
|
|
|
|
if (o.inset && !$.isPlainObject(o.inset)) {
|
|
// can specify a single number for equal outset all-around
|
|
n = parseInt(o.inset, 10) || 0
|
|
o.inset = {
|
|
top: n
|
|
, bottom: n
|
|
, left: n
|
|
, right: n
|
|
};
|
|
}
|
|
|
|
// format html & body if this is a full page layout
|
|
if (sC.isBody) {
|
|
// if HTML has padding, use this as an outer-spacing around BODY
|
|
if (!o.outset) {
|
|
// use padding from parent-elem (HTML) as outset
|
|
o.outset = {
|
|
top: num($H, "paddingTop")
|
|
, bottom: num($H, "paddingBottom")
|
|
, left: num($H, "paddingLeft")
|
|
, right: num($H, "paddingRight")
|
|
};
|
|
}
|
|
else if (!$.isPlainObject(o.outset)) {
|
|
// can specify a single number for equal outset all-around
|
|
n = parseInt(o.outset, 10) || 0
|
|
o.outset = {
|
|
top: n
|
|
, bottom: n
|
|
, left: n
|
|
, right: n
|
|
};
|
|
}
|
|
// HTML
|
|
$H.css( CSS ).css({
|
|
height: "100%"
|
|
, border: "none" // no border or padding allowed when using height = 100%
|
|
, padding: 0 // ditto
|
|
, margin: 0
|
|
});
|
|
// BODY
|
|
if (browser.isIE6) {
|
|
// IE6 CANNOT use the trick of setting absolute positioning on all 4 sides - must have 'height'
|
|
$N.css({
|
|
width: "100%"
|
|
, height: "100%"
|
|
, border: "none" // no border or padding allowed when using height = 100%
|
|
, padding: 0 // ditto
|
|
, margin: 0
|
|
, position: "relative"
|
|
});
|
|
// convert body padding to an inset option - the border cannot be measured in IE6!
|
|
if (!o.inset) o.inset = elDims( $N ).inset;
|
|
}
|
|
else { // use absolute positioning for BODY to allow borders & padding without overflow
|
|
$N.css({
|
|
width: "auto"
|
|
, height: "auto"
|
|
, margin: 0
|
|
, position: "absolute" // allows for border and padding on BODY
|
|
});
|
|
// apply edge-positioning created above
|
|
$N.css( o.outset );
|
|
}
|
|
// set current layout-container dimensions
|
|
$.extend(sC, elDims( $N, o.inset )); // passing inset means DO NOT include insetX values
|
|
}
|
|
else {
|
|
// container MUST have 'position'
|
|
var p = $N.css("position");
|
|
if (!p || !p.match(/(fixed|absolute|relative)/))
|
|
$N.css("position","relative");
|
|
|
|
// set current layout-container dimensions
|
|
if ( $N.is(":visible") ) {
|
|
$.extend(sC, elDims( $N, o.inset )); // passing inset means DO NOT change insetX (padding) values
|
|
if (sC.innerHeight < 1) // container has no 'height' - warn developer
|
|
_log( o.errors.noContainerHeight.replace(/CONTAINER/, sC.ref) );
|
|
}
|
|
}
|
|
|
|
// if container has min-width/height, then enable scrollbar(s)
|
|
if ( num($N, "minWidth") ) $N.parent().css("overflowX","auto");
|
|
if ( num($N, "minHeight") ) $N.parent().css("overflowY","auto");
|
|
|
|
} catch (ex) {}
|
|
}
|
|
|
|
/**
|
|
* Bind layout hotkeys - if options enabled
|
|
*
|
|
* @see _create() and addPane()
|
|
* @param {string=} [panes=""] The edge(s) to process
|
|
*/
|
|
, initHotkeys = function (panes) {
|
|
panes = panes ? panes.split(",") : _c.borderPanes;
|
|
// bind keyDown to capture hotkeys, if option enabled for ANY pane
|
|
$.each(panes, function (i, pane) {
|
|
var o = options[pane];
|
|
if (o.enableCursorHotkey || o.customHotkey) {
|
|
$(document).bind("keydown."+ sID, keyDown); // only need to bind this ONCE
|
|
return false; // BREAK - binding was done
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Build final OPTIONS data
|
|
*
|
|
* @see _create()
|
|
*/
|
|
, initOptions = function () {
|
|
var data, d, pane, key, val, i, c, o;
|
|
|
|
// reprocess user's layout-options to have correct options sub-key structure
|
|
opts = $.layout.transformData( opts, true ); // panes = default subkey
|
|
|
|
// auto-rename old options for backward compatibility
|
|
opts = $.layout.backwardCompatibility.renameAllOptions( opts );
|
|
|
|
// if user-options has 'panes' key (pane-defaults), clean it...
|
|
if (!$.isEmptyObject(opts.panes)) {
|
|
// REMOVE any pane-defaults that MUST be set per-pane
|
|
data = $.layout.optionsMap.noDefault;
|
|
for (i=0, c=data.length; i<c; i++) {
|
|
key = data[i];
|
|
delete opts.panes[key]; // OK if does not exist
|
|
}
|
|
// REMOVE any layout-options specified under opts.panes
|
|
data = $.layout.optionsMap.layout;
|
|
for (i=0, c=data.length; i<c; i++) {
|
|
key = data[i];
|
|
delete opts.panes[key]; // OK if does not exist
|
|
}
|
|
}
|
|
|
|
// MOVE any NON-layout-options from opts-root to opts.panes
|
|
data = $.layout.optionsMap.layout;
|
|
var rootKeys = $.layout.config.optionRootKeys;
|
|
for (key in opts) {
|
|
val = opts[key];
|
|
if ($.inArray(key, rootKeys) < 0 && $.inArray(key, data) < 0) {
|
|
if (!opts.panes[key])
|
|
opts.panes[key] = $.isPlainObject(val) ? $.extend(true, {}, val) : val;
|
|
delete opts[key]
|
|
}
|
|
}
|
|
|
|
// START by updating ALL options from opts
|
|
$.extend(true, options, opts);
|
|
|
|
// CREATE final options (and config) for EACH pane
|
|
$.each(_c.allPanes, function (i, pane) {
|
|
|
|
// apply 'pane-defaults' to CONFIG.[PANE]
|
|
_c[pane] = $.extend(true, {}, _c.panes, _c[pane]);
|
|
|
|
d = options.panes;
|
|
o = options[pane];
|
|
|
|
// center-pane uses SOME keys in defaults.panes branch
|
|
if (pane === 'center') {
|
|
// ONLY copy keys from opts.panes listed in: $.layout.optionsMap.center
|
|
data = $.layout.optionsMap.center; // list of 'center-pane keys'
|
|
for (i=0, c=data.length; i<c; i++) { // loop the list...
|
|
key = data[i];
|
|
// only need to use pane-default if pane-specific value not set
|
|
if (!opts.center[key] && (opts.panes[key] || !o[key]))
|
|
o[key] = d[key]; // pane-default
|
|
}
|
|
}
|
|
else {
|
|
// border-panes use ALL keys in defaults.panes branch
|
|
o = options[pane] = $.extend(true, {}, d, o); // re-apply pane-specific opts AFTER pane-defaults
|
|
createFxOptions( pane );
|
|
// ensure all border-pane-specific base-classes exist
|
|
if (!o.resizerClass) o.resizerClass = "ui-layout-resizer";
|
|
if (!o.togglerClass) o.togglerClass = "ui-layout-toggler";
|
|
}
|
|
// ensure we have base pane-class (ALL panes)
|
|
if (!o.paneClass) o.paneClass = "ui-layout-pane";
|
|
});
|
|
|
|
// update options.zIndexes if a zIndex-option specified
|
|
var zo = opts.zIndex
|
|
, z = options.zIndexes;
|
|
if (zo > 0) {
|
|
z.pane_normal = zo;
|
|
z.content_mask = max(zo+1, z.content_mask); // MIN = +1
|
|
z.resizer_normal = max(zo+2, z.resizer_normal); // MIN = +2
|
|
}
|
|
|
|
// DELETE 'panes' key now that we are done - values were copied to EACH pane
|
|
delete options.panes;
|
|
|
|
|
|
function createFxOptions ( pane ) {
|
|
var o = options[pane]
|
|
, d = options.panes;
|
|
// ensure fxSettings key to avoid errors
|
|
if (!o.fxSettings) o.fxSettings = {};
|
|
if (!d.fxSettings) d.fxSettings = {};
|
|
|
|
$.each(["_open","_close","_size"], function (i,n) {
|
|
var
|
|
sName = "fxName"+ n
|
|
, sSpeed = "fxSpeed"+ n
|
|
, sSettings = "fxSettings"+ n
|
|
// recalculate fxName according to specificity rules
|
|
, fxName = o[sName] =
|
|
o[sName] // options.west.fxName_open
|
|
|| d[sName] // options.panes.fxName_open
|
|
|| o.fxName // options.west.fxName
|
|
|| d.fxName // options.panes.fxName
|
|
|| "none" // MEANS $.layout.defaults.panes.fxName == "" || false || null || 0
|
|
, fxExists = $.effects && ($.effects[fxName] || ($.effects.effect && $.effects.effect[fxName]))
|
|
;
|
|
// validate fxName to ensure is valid effect - MUST have effect-config data in options.effects
|
|
if (fxName === "none" || !options.effects[fxName] || !fxExists)
|
|
fxName = o[sName] = "none"; // effect not loaded OR unrecognized fxName
|
|
|
|
// set vars for effects subkeys to simplify logic
|
|
var fx = options.effects[fxName] || {} // effects.slide
|
|
, fx_all = fx.all || null // effects.slide.all
|
|
, fx_pane = fx[pane] || null // effects.slide.west
|
|
;
|
|
// create fxSpeed[_open|_close|_size]
|
|
o[sSpeed] =
|
|
o[sSpeed] // options.west.fxSpeed_open
|
|
|| d[sSpeed] // options.west.fxSpeed_open
|
|
|| o.fxSpeed // options.west.fxSpeed
|
|
|| d.fxSpeed // options.panes.fxSpeed
|
|
|| null // DEFAULT - let fxSetting.duration control speed
|
|
;
|
|
// create fxSettings[_open|_close|_size]
|
|
o[sSettings] = $.extend(
|
|
true
|
|
, {}
|
|
, fx_all // effects.slide.all
|
|
, fx_pane // effects.slide.west
|
|
, d.fxSettings // options.panes.fxSettings
|
|
, o.fxSettings // options.west.fxSettings
|
|
, d[sSettings] // options.panes.fxSettings_open
|
|
, o[sSettings] // options.west.fxSettings_open
|
|
);
|
|
});
|
|
|
|
// DONE creating action-specific-settings for this pane,
|
|
// so DELETE generic options - are no longer meaningful
|
|
delete o.fxName;
|
|
delete o.fxSpeed;
|
|
delete o.fxSettings;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize module objects, styling, size and position for all panes
|
|
*
|
|
* @see _initElements()
|
|
* @param {string} pane The pane to process
|
|
*/
|
|
, getPane = function (pane) {
|
|
var sel = options[pane].paneSelector
|
|
if (sel.substr(0,1)==="#") // ID selector
|
|
// NOTE: elements selected 'by ID' DO NOT have to be 'children'
|
|
return $N.find(sel).eq(0);
|
|
else { // class or other selector
|
|
var $P = $N.children(sel).eq(0);
|
|
// look for the pane nested inside a 'form' element
|
|
return $P.length ? $P : $N.children("form:first").children(sel).eq(0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {Object=} evt
|
|
*/
|
|
, initPanes = function (evt) {
|
|
// stopPropagation if called by trigger("layoutinitpanes") - use evtPane utility
|
|
evtPane(evt);
|
|
|
|
// NOTE: do north & south FIRST so we can measure their height - do center LAST
|
|
$.each(_c.allPanes, function (idx, pane) {
|
|
addPane( pane, true );
|
|
});
|
|
|
|
// init the pane-handles NOW in case we have to hide or close the pane below
|
|
initHandles();
|
|
|
|
// now that all panes have been initialized and initially-sized,
|
|
// make sure there is really enough space available for each pane
|
|
$.each(_c.borderPanes, function (i, pane) {
|
|
if ($Ps[pane] && state[pane].isVisible) { // pane is OPEN
|
|
setSizeLimits(pane);
|
|
makePaneFit(pane); // pane may be Closed, Hidden or Resized by makePaneFit()
|
|
}
|
|
});
|
|
// size center-pane AGAIN in case we 'closed' a border-pane in loop above
|
|
sizeMidPanes("center");
|
|
|
|
// Chrome/Webkit sometimes fires callbacks BEFORE it completes resizing!
|
|
// Before RC30.3, there was a 10ms delay here, but that caused layout
|
|
// to load asynchrously, which is BAD, so try skipping delay for now
|
|
|
|
// process pane contents and callbacks, and init/resize child-layout if exists
|
|
$.each(_c.allPanes, function (idx, pane) {
|
|
afterInitPane(pane);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Add a pane to the layout - subroutine of initPanes()
|
|
*
|
|
* @see initPanes()
|
|
* @param {string} pane The pane to process
|
|
* @param {boolean=} [force=false] Size content after init
|
|
*/
|
|
, addPane = function (pane, force) {
|
|
if (!force && !isInitialized()) return;
|
|
var
|
|
o = options[pane]
|
|
, s = state[pane]
|
|
, c = _c[pane]
|
|
, dir = c.dir
|
|
, fx = s.fx
|
|
, spacing = o.spacing_open || 0
|
|
, isCenter = (pane === "center")
|
|
, CSS = {}
|
|
, $P = $Ps[pane]
|
|
, size, minSize, maxSize, child
|
|
;
|
|
// if pane-pointer already exists, remove the old one first
|
|
if ($P)
|
|
removePane( pane, false, true, false );
|
|
else
|
|
$Cs[pane] = false; // init
|
|
|
|
$P = $Ps[pane] = getPane(pane);
|
|
if (!$P.length) {
|
|
$Ps[pane] = false; // logic
|
|
return;
|
|
}
|
|
|
|
// SAVE original Pane CSS
|
|
if (!$P.data("layoutCSS")) {
|
|
var props = "position,top,left,bottom,right,width,height,overflow,zIndex,display,backgroundColor,padding,margin,border";
|
|
$P.data("layoutCSS", styles($P, props));
|
|
}
|
|
|
|
// create alias for pane data in Instance - initHandles will add more
|
|
Instance[pane] = {
|
|
name: pane
|
|
, pane: $Ps[pane]
|
|
, content: $Cs[pane]
|
|
, options: options[pane]
|
|
, state: state[pane]
|
|
, children: children[pane]
|
|
};
|
|
|
|
// add classes, attributes & events
|
|
$P .data({
|
|
parentLayout: Instance // pointer to Layout Instance
|
|
, layoutPane: Instance[pane] // NEW pointer to pane-alias-object
|
|
, layoutEdge: pane
|
|
, layoutRole: "pane"
|
|
})
|
|
.css(c.cssReq).css("zIndex", options.zIndexes.pane_normal)
|
|
.css(o.applyDemoStyles ? c.cssDemo : {}) // demo styles
|
|
.addClass( o.paneClass +" "+ o.paneClass+"-"+pane ) // default = "ui-layout-pane ui-layout-pane-west" - may be a dupe of 'paneSelector'
|
|
.bind("mouseenter."+ sID, addHover )
|
|
.bind("mouseleave."+ sID, removeHover )
|
|
;
|
|
var paneMethods = {
|
|
hide: ''
|
|
, show: ''
|
|
, toggle: ''
|
|
, close: ''
|
|
, open: ''
|
|
, slideOpen: ''
|
|
, slideClose: ''
|
|
, slideToggle: ''
|
|
, size: 'sizePane'
|
|
, sizePane: 'sizePane'
|
|
, sizeContent: ''
|
|
, sizeHandles: ''
|
|
, enableClosable: ''
|
|
, disableClosable: ''
|
|
, enableSlideable: ''
|
|
, disableSlideable: ''
|
|
, enableResizable: ''
|
|
, disableResizable: ''
|
|
, swapPanes: 'swapPanes'
|
|
, swap: 'swapPanes'
|
|
, move: 'swapPanes'
|
|
, removePane: 'removePane'
|
|
, remove: 'removePane'
|
|
, createChildren: ''
|
|
, resizeChildren: ''
|
|
, resizeAll: 'resizeAll'
|
|
, resizeLayout: 'resizeAll'
|
|
}
|
|
, name;
|
|
// loop hash and bind all methods - include layoutID namespacing
|
|
for (name in paneMethods) {
|
|
$P.bind("layoutpane"+ name.toLowerCase() +"."+ sID, Instance[ paneMethods[name] || name ]);
|
|
}
|
|
|
|
// see if this pane has a 'scrolling-content element'
|
|
initContent(pane, false); // false = do NOT sizeContent() - called later
|
|
|
|
if (!isCenter) {
|
|
// call _parseSize AFTER applying pane classes & styles - but before making visible (if hidden)
|
|
// if o.size is auto or not valid, then MEASURE the pane and use that as its 'size'
|
|
size = s.size = _parseSize(pane, o.size);
|
|
minSize = _parseSize(pane,o.minSize) || 1;
|
|
maxSize = _parseSize(pane,o.maxSize) || 100000;
|
|
if (size > 0) size = max(min(size, maxSize), minSize);
|
|
s.autoResize = o.autoResize; // used with percentage sizes
|
|
|
|
// state for border-panes
|
|
s.isClosed = false; // true = pane is closed
|
|
s.isSliding = false; // true = pane is currently open by 'sliding' over adjacent panes
|
|
s.isResizing= false; // true = pane is in process of being resized
|
|
s.isHidden = false; // true = pane is hidden - no spacing, resizer or toggler is visible!
|
|
|
|
// array for 'pin buttons' whose classNames are auto-updated on pane-open/-close
|
|
if (!s.pins) s.pins = [];
|
|
}
|
|
// states common to ALL panes
|
|
s.tagName = $P[0].tagName;
|
|
s.edge = pane; // useful if pane is (or about to be) 'swapped' - easy find out where it is (or is going)
|
|
s.noRoom = false; // true = pane 'automatically' hidden due to insufficient room - will unhide automatically
|
|
s.isVisible = true; // false = pane is invisible - closed OR hidden - simplify logic
|
|
|
|
// init pane positioning
|
|
setPanePosition( pane );
|
|
|
|
// if pane is not visible,
|
|
if (dir === "horz") // north or south pane
|
|
CSS.height = cssH($P, size);
|
|
else if (dir === "vert") // east or west pane
|
|
CSS.width = cssW($P, size);
|
|
//else if (isCenter) {}
|
|
|
|
$P.css(CSS); // apply size -- top, bottom & height will be set by sizeMidPanes
|
|
if (dir != "horz") sizeMidPanes(pane, true); // true = skipCallback
|
|
|
|
// if manually adding a pane AFTER layout initialization, then...
|
|
if (state.initialized) {
|
|
initHandles( pane );
|
|
initHotkeys( pane );
|
|
}
|
|
|
|
// close or hide the pane if specified in settings
|
|
if (o.initClosed && o.closable && !o.initHidden)
|
|
close(pane, true, true); // true, true = force, noAnimation
|
|
else if (o.initHidden || o.initClosed)
|
|
hide(pane); // will be completely invisible - no resizer or spacing
|
|
else if (!s.noRoom)
|
|
// make the pane visible - in case was initially hidden
|
|
$P.css("display","block");
|
|
// ELSE setAsOpen() - called later by initHandles()
|
|
|
|
// RESET visibility now - pane will appear IF display:block
|
|
$P.css("visibility","visible");
|
|
|
|
// check option for auto-handling of pop-ups & drop-downs
|
|
if (o.showOverflowOnHover)
|
|
$P.hover( allowOverflow, resetOverflow );
|
|
|
|
// if manually adding a pane AFTER layout initialization, then...
|
|
if (state.initialized) {
|
|
afterInitPane( pane );
|
|
}
|
|
}
|
|
|
|
, afterInitPane = function (pane) {
|
|
var $P = $Ps[pane]
|
|
, s = state[pane]
|
|
, o = options[pane]
|
|
;
|
|
if (!$P) return;
|
|
|
|
// see if there is a directly-nested layout inside this pane
|
|
if ($P.data("layout"))
|
|
refreshChildren( pane, $P.data("layout") );
|
|
|
|
// process pane contents and callbacks, and init/resize child-layout if exists
|
|
if (s.isVisible) { // pane is OPEN
|
|
if (state.initialized) // this pane was added AFTER layout was created
|
|
resizeAll(); // will also sizeContent
|
|
else
|
|
sizeContent(pane);
|
|
|
|
if (o.triggerEventsOnLoad)
|
|
_runCallbacks("onresize_end", pane);
|
|
else // automatic if onresize called, otherwise call it specifically
|
|
// resize child - IF inner-layout already exists (created before this layout)
|
|
resizeChildren(pane, true); // a previously existing childLayout
|
|
}
|
|
|
|
// init childLayouts - even if pane is not visible
|
|
if (o.initChildren && o.children)
|
|
createChildren(pane);
|
|
}
|
|
|
|
/**
|
|
* @param {string=} panes The pane(s) to process
|
|
*/
|
|
, setPanePosition = function (panes) {
|
|
panes = panes ? panes.split(",") : _c.borderPanes;
|
|
|
|
// create toggler DIVs for each pane, and set object pointers for them, eg: $R.north = north toggler DIV
|
|
$.each(panes, function (i, pane) {
|
|
var $P = $Ps[pane]
|
|
, $R = $Rs[pane]
|
|
, o = options[pane]
|
|
, s = state[pane]
|
|
, side = _c[pane].side
|
|
, CSS = {}
|
|
;
|
|
if (!$P) return; // pane does not exist - skip
|
|
|
|
// set css-position to account for container borders & padding
|
|
switch (pane) {
|
|
case "north": CSS.top = sC.inset.top;
|
|
CSS.left = sC.inset.left;
|
|
CSS.right = sC.inset.right;
|
|
break;
|
|
case "south": CSS.bottom = sC.inset.bottom;
|
|
CSS.left = sC.inset.left;
|
|
CSS.right = sC.inset.right;
|
|
break;
|
|
case "west": CSS.left = sC.inset.left; // top, bottom & height set by sizeMidPanes()
|
|
break;
|
|
case "east": CSS.right = sC.inset.right; // ditto
|
|
break;
|
|
case "center": // top, left, width & height set by sizeMidPanes()
|
|
}
|
|
// apply position
|
|
$P.css(CSS);
|
|
|
|
// update resizer position
|
|
if ($R && s.isClosed)
|
|
$R.css(side, sC.inset[side]);
|
|
else if ($R && !s.isHidden)
|
|
$R.css(side, sC.inset[side] + getPaneSize(pane));
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Initialize module objects, styling, size and position for all resize bars and toggler buttons
|
|
*
|
|
* @see _create()
|
|
* @param {string=} [panes=""] The edge(s) to process
|
|
*/
|
|
, initHandles = function (panes) {
|
|
panes = panes ? panes.split(",") : _c.borderPanes;
|
|
|
|
// create toggler DIVs for each pane, and set object pointers for them, eg: $R.north = north toggler DIV
|
|
$.each(panes, function (i, pane) {
|
|
var $P = $Ps[pane];
|
|
$Rs[pane] = false; // INIT
|
|
$Ts[pane] = false;
|
|
if (!$P) return; // pane does not exist - skip
|
|
|
|
var o = options[pane]
|
|
, s = state[pane]
|
|
, c = _c[pane]
|
|
, paneId = o.paneSelector.substr(0,1) === "#" ? o.paneSelector.substr(1) : ""
|
|
, rClass = o.resizerClass
|
|
, tClass = o.togglerClass
|
|
, spacing = (s.isVisible ? o.spacing_open : o.spacing_closed)
|
|
, _pane = "-"+ pane // used for classNames
|
|
, _state = (s.isVisible ? "-open" : "-closed") // used for classNames
|
|
, I = Instance[pane]
|
|
// INIT RESIZER BAR
|
|
, $R = I.resizer = $Rs[pane] = $("<div></div>")
|
|
// INIT TOGGLER BUTTON
|
|
, $T = I.toggler = (o.closable ? $Ts[pane] = $("<div></div>") : false)
|
|
;
|
|
|
|
//if (s.isVisible && o.resizable) ... handled by initResizable
|
|
if (!s.isVisible && o.slidable)
|
|
$R.attr("title", o.tips.Slide).css("cursor", o.sliderCursor);
|
|
|
|
$R // if paneSelector is an ID, then create a matching ID for the resizer, eg: "#paneLeft" => "paneLeft-resizer"
|
|
.attr("id", paneId ? paneId +"-resizer" : "" )
|
|
.data({
|
|
parentLayout: Instance
|
|
, layoutPane: Instance[pane] // NEW pointer to pane-alias-object
|
|
, layoutEdge: pane
|
|
, layoutRole: "resizer"
|
|
})
|
|
.css(_c.resizers.cssReq).css("zIndex", options.zIndexes.resizer_normal)
|
|
.css(o.applyDemoStyles ? _c.resizers.cssDemo : {}) // add demo styles
|
|
.addClass(rClass +" "+ rClass+_pane)
|
|
.hover(addHover, removeHover) // ALWAYS add hover-classes, even if resizing is not enabled - handle with CSS instead
|
|
.hover(onResizerEnter, onResizerLeave) // ALWAYS NEED resizer.mouseleave to balance toggler.mouseenter
|
|
.appendTo($N) // append DIV to container
|
|
;
|
|
if (o.resizerDblClickToggle)
|
|
$R.bind("dblclick."+ sID, toggle );
|
|
|
|
if ($T) {
|
|
$T // if paneSelector is an ID, then create a matching ID for the resizer, eg: "#paneLeft" => "#paneLeft-toggler"
|
|
.attr("id", paneId ? paneId +"-toggler" : "" )
|
|
.data({
|
|
parentLayout: Instance
|
|
, layoutPane: Instance[pane] // NEW pointer to pane-alias-object
|
|
, layoutEdge: pane
|
|
, layoutRole: "toggler"
|
|
})
|
|
.css(_c.togglers.cssReq) // add base/required styles
|
|
.css(o.applyDemoStyles ? _c.togglers.cssDemo : {}) // add demo styles
|
|
.addClass(tClass +" "+ tClass+_pane)
|
|
.hover(addHover, removeHover) // ALWAYS add hover-classes, even if toggling is not enabled - handle with CSS instead
|
|
.bind("mouseenter", onResizerEnter) // NEED toggler.mouseenter because mouseenter MAY NOT fire on resizer
|
|
.appendTo($R) // append SPAN to resizer DIV
|
|
;
|
|
// ADD INNER-SPANS TO TOGGLER
|
|
if (o.togglerContent_open) // ui-layout-open
|
|
$("<span>"+ o.togglerContent_open +"</span>")
|
|
.data({
|
|
layoutEdge: pane
|
|
, layoutRole: "togglerContent"
|
|
})
|
|
.data("layoutRole", "togglerContent")
|
|
.data("layoutEdge", pane)
|
|
.addClass("content content-open")
|
|
.css("display","none")
|
|
.appendTo( $T )
|
|
//.hover( addHover, removeHover ) // use ui-layout-toggler-west-hover .content-open instead!
|
|
;
|
|
if (o.togglerContent_closed) // ui-layout-closed
|
|
$("<span>"+ o.togglerContent_closed +"</span>")
|
|
.data({
|
|
layoutEdge: pane
|
|
, layoutRole: "togglerContent"
|
|
})
|
|
.addClass("content content-closed")
|
|
.css("display","none")
|
|
.appendTo( $T )
|
|
//.hover( addHover, removeHover ) // use ui-layout-toggler-west-hover .content-closed instead!
|
|
;
|
|
// ADD TOGGLER.click/.hover
|
|
enableClosable(pane);
|
|
}
|
|
|
|
// add Draggable events
|
|
initResizable(pane);
|
|
|
|
// ADD CLASSNAMES & SLIDE-BINDINGS - eg: class="resizer resizer-west resizer-open"
|
|
if (s.isVisible)
|
|
setAsOpen(pane); // onOpen will be called, but NOT onResize
|
|
else {
|
|
setAsClosed(pane); // onClose will be called
|
|
bindStartSlidingEvents(pane, true); // will enable events IF option is set
|
|
}
|
|
|
|
});
|
|
|
|
// SET ALL HANDLE DIMENSIONS
|
|
sizeHandles();
|
|
}
|
|
|
|
|
|
/**
|
|
* Initialize scrolling ui-layout-content div - if exists
|
|
*
|
|
* @see initPane() - or externally after an Ajax injection
|
|
* @param {string} pane The pane to process
|
|
* @param {boolean=} [resize=true] Size content after init
|
|
*/
|
|
, initContent = function (pane, resize) {
|
|
if (!isInitialized()) return;
|
|
var
|
|
o = options[pane]
|
|
, sel = o.contentSelector
|
|
, I = Instance[pane]
|
|
, $P = $Ps[pane]
|
|
, $C
|
|
;
|
|
if (sel) $C = I.content = $Cs[pane] = (o.findNestedContent)
|
|
? $P.find(sel).eq(0) // match 1-element only
|
|
: $P.children(sel).eq(0)
|
|
;
|
|
if ($C && $C.length) {
|
|
$C.data("layoutRole", "content");
|
|
// SAVE original Content CSS
|
|
if (!$C.data("layoutCSS"))
|
|
$C.data("layoutCSS", styles($C, "height"));
|
|
$C.css( _c.content.cssReq );
|
|
if (o.applyDemoStyles) {
|
|
$C.css( _c.content.cssDemo ); // add padding & overflow: auto to content-div
|
|
$P.css( _c.content.cssDemoPane ); // REMOVE padding/scrolling from pane
|
|
}
|
|
// ensure no vertical scrollbar on pane - will mess up measurements
|
|
if ($P.css("overflowX").match(/(scroll|auto)/)) {
|
|
$P.css("overflow", "hidden");
|
|
}
|
|
state[pane].content = {}; // init content state
|
|
if (resize !== false) sizeContent(pane);
|
|
// sizeContent() is called AFTER init of all elements
|
|
}
|
|
else
|
|
I.content = $Cs[pane] = false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Add resize-bars to all panes that specify it in options
|
|
* -dependancy: $.fn.resizable - will skip if not found
|
|
*
|
|
* @see _create()
|
|
* @param {string=} [panes=""] The edge(s) to process
|
|
*/
|
|
, initResizable = function (panes) {
|
|
var draggingAvailable = $.layout.plugins.draggable
|
|
, side // set in start()
|
|
;
|
|
panes = panes ? panes.split(",") : _c.borderPanes;
|
|
|
|
$.each(panes, function (idx, pane) {
|
|
var o = options[pane];
|
|
if (!draggingAvailable || !$Ps[pane] || !o.resizable) {
|
|
o.resizable = false;
|
|
return true; // skip to next
|
|
}
|
|
|
|
var s = state[pane]
|
|
, z = options.zIndexes
|
|
, c = _c[pane]
|
|
, side = c.dir=="horz" ? "top" : "left"
|
|
, $P = $Ps[pane]
|
|
, $R = $Rs[pane]
|
|
, base = o.resizerClass
|
|
, lastPos = 0 // used when live-resizing
|
|
, r, live // set in start because may change
|
|
// 'drag' classes are applied to the ORIGINAL resizer-bar while dragging is in process
|
|
, resizerClass = base+"-drag" // resizer-drag
|
|
, resizerPaneClass = base+"-"+pane+"-drag" // resizer-north-drag
|
|
// 'helper' class is applied to the CLONED resizer-bar while it is being dragged
|
|
, helperClass = base+"-dragging" // resizer-dragging
|
|
, helperPaneClass = base+"-"+pane+"-dragging" // resizer-north-dragging
|
|
, helperLimitClass = base+"-dragging-limit" // resizer-drag
|
|
, helperPaneLimitClass = base+"-"+pane+"-dragging-limit" // resizer-north-drag
|
|
, helperClassesSet = false // logic var
|
|
;
|
|
|
|
if (!s.isClosed)
|
|
$R.attr("title", o.tips.Resize)
|
|
.css("cursor", o.resizerCursor); // n-resize, s-resize, etc
|
|
|
|
$R.draggable({
|
|
containment: $N[0] // limit resizing to layout container
|
|
, axis: (c.dir=="horz" ? "y" : "x") // limit resizing to horz or vert axis
|
|
, delay: 0
|
|
, distance: 1
|
|
, grid: o.resizingGrid
|
|
// basic format for helper - style it using class: .ui-draggable-dragging
|
|
, helper: "clone"
|
|
, opacity: o.resizerDragOpacity
|
|
, addClasses: false // avoid ui-state-disabled class when disabled
|
|
//, iframeFix: o.draggableIframeFix // TODO: consider using when bug is fixed
|
|
, zIndex: z.resizer_drag
|
|
|
|
, start: function (e, ui) {
|
|
// REFRESH options & state pointers in case we used swapPanes
|
|
o = options[pane];
|
|
s = state[pane];
|
|
// re-read options
|
|
live = o.livePaneResizing;
|
|
|
|
// ondrag_start callback - will CANCEL hide if returns false
|
|
// TODO: dragging CANNOT be cancelled like this, so see if there is a way?
|
|
if (false === _runCallbacks("ondrag_start", pane)) return false;
|
|
|
|
s.isResizing = true; // prevent pane from closing while resizing
|
|
state.paneResizing = pane; // easy to see if ANY pane is resizing
|
|
timer.clear(pane+"_closeSlider"); // just in case already triggered
|
|
|
|
// SET RESIZER LIMITS - used in drag()
|
|
setSizeLimits(pane); // update pane/resizer state
|
|
r = s.resizerPosition;
|
|
lastPos = ui.position[ side ]
|
|
|
|
$R.addClass( resizerClass +" "+ resizerPaneClass ); // add drag classes
|
|
helperClassesSet = false; // reset logic var - see drag()
|
|
|
|
// DISABLE TEXT SELECTION (probably already done by resizer.mouseOver)
|
|
$('body').disableSelection();
|
|
|
|
// MASK PANES CONTAINING IFRAMES, APPLETS OR OTHER TROUBLESOME ELEMENTS
|
|
showMasks( pane, { resizing: true });
|
|
}
|
|
|
|
, drag: function (e, ui) {
|
|
if (!helperClassesSet) { // can only add classes after clone has been added to the DOM
|
|
//$(".ui-draggable-dragging")
|
|
ui.helper
|
|
.addClass( helperClass +" "+ helperPaneClass ) // add helper classes
|
|
.css({ right: "auto", bottom: "auto" }) // fix dir="rtl" issue
|
|
.children().css("visibility","hidden") // hide toggler inside dragged resizer-bar
|
|
;
|
|
helperClassesSet = true;
|
|
// draggable bug!? RE-SET zIndex to prevent E/W resize-bar showing through N/S pane!
|
|
if (s.isSliding) $Ps[pane].css("zIndex", z.pane_sliding);
|
|
}
|
|
// CONTAIN RESIZER-BAR TO RESIZING LIMITS
|
|
var limit = 0;
|
|
if (ui.position[side] < r.min) {
|
|
ui.position[side] = r.min;
|
|
limit = -1;
|
|
}
|
|
else if (ui.position[side] > r.max) {
|
|
ui.position[side] = r.max;
|
|
limit = 1;
|
|
}
|
|
// ADD/REMOVE dragging-limit CLASS
|
|
if (limit) {
|
|
ui.helper.addClass( helperLimitClass +" "+ helperPaneLimitClass ); // at dragging-limit
|
|
window.defaultStatus = (limit>0 && pane.match(/(north|west)/)) || (limit<0 && pane.match(/(south|east)/)) ? o.tips.maxSizeWarning : o.tips.minSizeWarning;
|
|
}
|
|
else {
|
|
ui.helper.removeClass( helperLimitClass +" "+ helperPaneLimitClass ); // not at dragging-limit
|
|
window.defaultStatus = "";
|
|
}
|
|
// DYNAMICALLY RESIZE PANES IF OPTION ENABLED
|
|
// won't trigger unless resizer has actually moved!
|
|
if (live && Math.abs(ui.position[side] - lastPos) >= o.liveResizingTolerance) {
|
|
lastPos = ui.position[side];
|
|
resizePanes(e, ui, pane)
|
|
}
|
|
}
|
|
|
|
, stop: function (e, ui) {
|
|
$('body').enableSelection(); // RE-ENABLE TEXT SELECTION
|
|
window.defaultStatus = ""; // clear 'resizing limit' message from statusbar
|
|
$R.removeClass( resizerClass +" "+ resizerPaneClass ); // remove drag classes from Resizer
|
|
s.isResizing = false;
|
|
state.paneResizing = false; // easy to see if ANY pane is resizing
|
|
resizePanes(e, ui, pane, true); // true = resizingDone
|
|
}
|
|
|
|
});
|
|
});
|
|
|
|
/**
|
|
* resizePanes
|
|
*
|
|
* Sub-routine called from stop() - and drag() if livePaneResizing
|
|
*
|
|
* @param {!Object} evt
|
|
* @param {!Object} ui
|
|
* @param {string} pane
|
|
* @param {boolean=} [resizingDone=false]
|
|
*/
|
|
var resizePanes = function (evt, ui, pane, resizingDone) {
|
|
var dragPos = ui.position
|
|
, c = _c[pane]
|
|
, o = options[pane]
|
|
, s = state[pane]
|
|
, resizerPos
|
|
;
|
|
switch (pane) {
|
|
case "north": resizerPos = dragPos.top; break;
|
|
case "west": resizerPos = dragPos.left; break;
|
|
case "south": resizerPos = sC.layoutHeight - dragPos.top - o.spacing_open; break;
|
|
case "east": resizerPos = sC.layoutWidth - dragPos.left - o.spacing_open; break;
|
|
};
|
|
// remove container margin from resizer position to get the pane size
|
|
var newSize = resizerPos - sC.inset[c.side];
|
|
|
|
// Disable OR Resize Mask(s) created in drag.start
|
|
if (!resizingDone) {
|
|
// ensure we meet liveResizingTolerance criteria
|
|
if (Math.abs(newSize - s.size) < o.liveResizingTolerance)
|
|
return; // SKIP resize this time
|
|
// resize the pane
|
|
manualSizePane(pane, newSize, false, true); // true = noAnimation
|
|
sizeMasks(); // resize all visible masks
|
|
}
|
|
else { // resizingDone
|
|
// ondrag_end callback
|
|
if (false !== _runCallbacks("ondrag_end", pane))
|
|
manualSizePane(pane, newSize, false, true); // true = noAnimation
|
|
hideMasks(true); // true = force hiding all masks even if one is 'sliding'
|
|
if (s.isSliding) // RE-SHOW 'object-masks' so objects won't show through sliding pane
|
|
showMasks( pane, { resizing: true });
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* sizeMask
|
|
*
|
|
* Needed to overlay a DIV over an IFRAME-pane because mask CANNOT be *inside* the pane
|
|
* Called when mask created, and during livePaneResizing
|
|
*/
|
|
, sizeMask = function () {
|
|
var $M = $(this)
|
|
, pane = $M.data("layoutMask") // eg: "west"
|
|
, s = state[pane]
|
|
;
|
|
// only masks over an IFRAME-pane need manual resizing
|
|
if (s.tagName == "IFRAME" && s.isVisible) // no need to mask closed/hidden panes
|
|
$M.css({
|
|
top: s.offsetTop
|
|
, left: s.offsetLeft
|
|
, width: s.outerWidth
|
|
, height: s.outerHeight
|
|
});
|
|
/* ALT Method...
|
|
var $P = $Ps[pane];
|
|
$M.css( $P.position() ).css({ width: $P[0].offsetWidth, height: $P[0].offsetHeight });
|
|
*/
|
|
}
|
|
, sizeMasks = function () {
|
|
$Ms.each( sizeMask ); // resize all 'visible' masks
|
|
}
|
|
|
|
/**
|
|
* @param {string} pane The pane being resized, animated or isSliding
|
|
* @param {Object=} [args] (optional) Options: which masks to apply, and to which panes
|
|
*/
|
|
, showMasks = function (pane, args) {
|
|
var c = _c[pane]
|
|
, panes = ["center"]
|
|
, z = options.zIndexes
|
|
, a = $.extend({
|
|
objectsOnly: false
|
|
, animation: false
|
|
, resizing: true
|
|
, sliding: state[pane].isSliding
|
|
}, args )
|
|
, o, s
|
|
;
|
|
if (a.resizing)
|
|
panes.push( pane );
|
|
if (a.sliding)
|
|
panes.push( _c.oppositeEdge[pane] ); // ADD the oppositeEdge-pane
|
|
|
|
if (c.dir === "horz") {
|
|
panes.push("west");
|
|
panes.push("east");
|
|
}
|
|
|
|
$.each(panes, function(i,p){
|
|
s = state[p];
|
|
o = options[p];
|
|
if (s.isVisible && ( o.maskObjects || (!a.objectsOnly && o.maskContents) )) {
|
|
getMasks(p).each(function(){
|
|
sizeMask.call(this);
|
|
this.style.zIndex = s.isSliding ? z.pane_sliding+1 : z.pane_normal+1
|
|
this.style.display = "block";
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {boolean=} force Hide masks even if a pane is sliding
|
|
*/
|
|
, hideMasks = function (force) {
|
|
// ensure no pane is resizing - could be a timing issue
|
|
if (force || !state.paneResizing) {
|
|
$Ms.hide(); // hide ALL masks
|
|
}
|
|
// if ANY pane is sliding, then DO NOT remove masks from panes with maskObjects enabled
|
|
else if (!force && !$.isEmptyObject( state.panesSliding )) {
|
|
var i = $Ms.length - 1
|
|
, p, $M;
|
|
for (; i >= 0; i--) {
|
|
$M = $Ms.eq(i);
|
|
p = $M.data("layoutMask");
|
|
if (!options[p].maskObjects) {
|
|
$M.hide();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {string} pane
|
|
*/
|
|
, getMasks = function (pane) {
|
|
var $Masks = $([])
|
|
, $M, i = 0, c = $Ms.length
|
|
;
|
|
for (; i<c; i++) {
|
|
$M = $Ms.eq(i);
|
|
if ($M.data("layoutMask") === pane)
|
|
$Masks = $Masks.add( $M );
|
|
}
|
|
if ($Masks.length)
|
|
return $Masks;
|
|
else
|
|
return createMasks(pane);
|
|
}
|
|
|
|
/**
|
|
* createMasks
|
|
*
|
|
* Generates both DIV (ALWAYS used) and IFRAME (optional) elements as masks
|
|
* An IFRAME mask is created *under* the DIV when maskObjects=true, because a DIV cannot mask an applet
|
|
*
|
|
* @param {string} pane
|
|
*/
|
|
, createMasks = function (pane) {
|
|
var
|
|
$P = $Ps[pane]
|
|
, s = state[pane]
|
|
, o = options[pane]
|
|
, z = options.zIndexes
|
|
//, objMask = o.maskObjects && s.tagName != "IFRAME" // check for option
|
|
, $Masks = $([])
|
|
, isIframe, el, $M, css, i
|
|
;
|
|
if (!o.maskContents && !o.maskObjects) return $Masks;
|
|
// if o.maskObjects=true, then loop TWICE to create BOTH kinds of mask, else only create a DIV
|
|
for (i=0; i < (o.maskObjects ? 2 : 1); i++) {
|
|
isIframe = o.maskObjects && i==0;
|
|
el = document.createElement( isIframe ? "iframe" : "div" );
|
|
$M = $(el).data("layoutMask", pane); // add data to relate mask to pane
|
|
el.className = "ui-layout-mask ui-layout-mask-"+ pane; // for user styling
|
|
css = el.style;
|
|
// styles common to both DIVs and IFRAMES
|
|
css.display = "block";
|
|
css.position = "absolute";
|
|
css.background = "#FFF";
|
|
if (isIframe) { // IFRAME-only props
|
|
el.frameborder = 0;
|
|
el.src = "about:blank";
|
|
//el.allowTransparency = true; - for IE, but breaks masking ability!
|
|
css.opacity = 0;
|
|
css.filter = "Alpha(Opacity='0')";
|
|
css.border = 0;
|
|
}
|
|
// if pane is an IFRAME, then must mask the pane itself
|
|
if (s.tagName == "IFRAME") {
|
|
// NOTE sizing done by a subroutine so can be called during live-resizing
|
|
css.zIndex = z.pane_normal+1; // 1-higher than pane
|
|
$N.append( el ); // append to LAYOUT CONTAINER
|
|
}
|
|
// otherwise put masks *inside the pane* to mask its contents
|
|
else {
|
|
$M.addClass("ui-layout-mask-inside-pane");
|
|
css.zIndex = o.maskZindex || z.content_mask; // usually 1, but customizable
|
|
css.top = 0;
|
|
css.left = 0;
|
|
css.width = "100%";
|
|
css.height = "100%";
|
|
$P.append( el ); // append INSIDE pane element
|
|
}
|
|
// add to return object
|
|
$Masks = $Masks.add( el );
|
|
// add Mask to cached array so can be resized & reused
|
|
$Ms = $Ms.add( el );
|
|
}
|
|
return $Masks;
|
|
}
|
|
|
|
|
|
/**
|
|
* Destroy this layout and reset all elements
|
|
*
|
|
* @param {boolean=} [destroyChildren=false] Destory Child-Layouts first?
|
|
*/
|
|
, destroy = function (evt_or_destroyChildren, destroyChildren) {
|
|
// UNBIND layout events and remove global object
|
|
$(window).unbind("."+ sID); // resize & unload
|
|
$(document).unbind("."+ sID); // keyDown (hotkeys)
|
|
|
|
if (typeof evt_or_destroyChildren === "object")
|
|
// stopPropagation if called by trigger("layoutdestroy") - use evtPane utility
|
|
evtPane(evt_or_destroyChildren);
|
|
else // no event, so transfer 1st param to destroyChildren param
|
|
destroyChildren = evt_or_destroyChildren;
|
|
|
|
// need to look for parent layout BEFORE we remove the container data, else skips a level
|
|
//var parentPane = Instance.hasParentLayout ? $.layout.getParentPaneInstance( $N ) : null;
|
|
|
|
// reset layout-container
|
|
$N .clearQueue()
|
|
.removeData("layout")
|
|
.removeData("layoutContainer")
|
|
.removeClass(options.containerClass)
|
|
.unbind("."+ sID) // remove ALL Layout events
|
|
;
|
|
|
|
// remove all mask elements that have been created
|
|
$Ms.remove();
|
|
|
|
// loop all panes to remove layout classes, attributes and bindings
|
|
$.each(_c.allPanes, function (i, pane) {
|
|
removePane( pane, false, true, destroyChildren ); // true = skipResize
|
|
});
|
|
|
|
// do NOT reset container CSS if is a 'pane' (or 'content') in an outer-layout - ie, THIS layout is 'nested'
|
|
var css = "layoutCSS";
|
|
if ($N.data(css) && !$N.data("layoutRole")) // RESET CSS
|
|
$N.css( $N.data(css) ).removeData(css);
|
|
|
|
// for full-page layouts, also reset the <HTML> CSS
|
|
if (sC.tagName === "BODY" && ($N = $("html")).data(css)) // RESET <HTML> CSS
|
|
$N.css( $N.data(css) ).removeData(css);
|
|
|
|
// trigger plugins for this layout, if there are any
|
|
runPluginCallbacks( Instance, $.layout.onDestroy );
|
|
|
|
// trigger state-management and onunload callback
|
|
unload();
|
|
|
|
// clear the Instance of everything except for container & options (so could recreate)
|
|
// RE-CREATE: myLayout = myLayout.container.layout( myLayout.options );
|
|
for (var n in Instance)
|
|
if (!n.match(/^(container|options)$/)) delete Instance[ n ];
|
|
// add a 'destroyed' flag to make it easy to check
|
|
Instance.destroyed = true;
|
|
|
|
// if this is a child layout, CLEAR the child-pointer in the parent
|
|
/* for now the pointer REMAINS, but with only container, options and destroyed keys
|
|
if (parentPane) {
|
|
var layout = parentPane.pane.data("parentLayout")
|
|
, key = layout.options.instanceKey || 'error';
|
|
// THIS SYNTAX MAY BE WRONG!
|
|
parentPane.children[key] = layout.children[ parentPane.name ].children[key] = null;
|
|
}
|
|
*/
|
|
|
|
return Instance; // for coding convenience
|
|
}
|
|
|
|
/**
|
|
* Remove a pane from the layout - subroutine of destroy()
|
|
*
|
|
* @see destroy()
|
|
* @param {(string|Object)} evt_or_pane The pane to process
|
|
* @param {boolean=} [remove=false] Remove the DOM element?
|
|
* @param {boolean=} [skipResize=false] Skip calling resizeAll()?
|
|
* @param {boolean=} [destroyChild=true] Destroy Child-layouts? If not passed, obeys options setting
|
|
*/
|
|
, removePane = function (evt_or_pane, remove, skipResize, destroyChild) {
|
|
if (!isInitialized()) return;
|
|
var pane = evtPane.call(this, evt_or_pane)
|
|
, $P = $Ps[pane]
|
|
, $C = $Cs[pane]
|
|
, $R = $Rs[pane]
|
|
, $T = $Ts[pane]
|
|
;
|
|
// NOTE: elements can still exist even after remove()
|
|
// so check for missing data(), which is cleared by removed()
|
|
if ($P && $.isEmptyObject( $P.data() )) $P = false;
|
|
if ($C && $.isEmptyObject( $C.data() )) $C = false;
|
|
if ($R && $.isEmptyObject( $R.data() )) $R = false;
|
|
if ($T && $.isEmptyObject( $T.data() )) $T = false;
|
|
|
|
if ($P) $P.stop(true, true);
|
|
|
|
var o = options[pane]
|
|
, s = state[pane]
|
|
, d = "layout"
|
|
, css = "layoutCSS"
|
|
, pC = children[pane]
|
|
, hasChildren = $.isPlainObject( pC ) && !$.isEmptyObject( pC )
|
|
, destroy = destroyChild !== undefined ? destroyChild : o.destroyChildren
|
|
;
|
|
// FIRST destroy the child-layout(s)
|
|
if (hasChildren && destroy) {
|
|
$.each( pC, function (key, child) {
|
|
if (!child.destroyed)
|
|
child.destroy(true);// tell child-layout to destroy ALL its child-layouts too
|
|
if (child.destroyed) // destroy was successful
|
|
delete pC[key];
|
|
});
|
|
// if no more children, remove the children hash
|
|
if ($.isEmptyObject( pC )) {
|
|
pC = children[pane] = null; // clear children hash
|
|
hasChildren = false;
|
|
}
|
|
}
|
|
|
|
// Note: can't 'remove' a pane element with non-destroyed children
|
|
if ($P && remove && !hasChildren)
|
|
$P.remove(); // remove the pane-element and everything inside it
|
|
else if ($P && $P[0]) {
|
|
// create list of ALL pane-classes that need to be removed
|
|
var root = o.paneClass // default="ui-layout-pane"
|
|
, pRoot = root +"-"+ pane // eg: "ui-layout-pane-west"
|
|
, _open = "-open"
|
|
, _sliding= "-sliding"
|
|
, _closed = "-closed"
|
|
, classes = [ root, root+_open, root+_closed, root+_sliding, // generic classes
|
|
pRoot, pRoot+_open, pRoot+_closed, pRoot+_sliding ] // pane-specific classes
|
|
;
|
|
$.merge(classes, getHoverClasses($P, true)); // ADD hover-classes
|
|
// remove all Layout classes from pane-element
|
|
$P .removeClass( classes.join(" ") ) // remove ALL pane-classes
|
|
.removeData("parentLayout")
|
|
.removeData("layoutPane")
|
|
.removeData("layoutRole")
|
|
.removeData("layoutEdge")
|
|
.removeData("autoHidden") // in case set
|
|
.unbind("."+ sID) // remove ALL Layout events
|
|
// TODO: remove these extra unbind commands when jQuery is fixed
|
|
//.unbind("mouseenter"+ sID)
|
|
//.unbind("mouseleave"+ sID)
|
|
;
|
|
// do NOT reset CSS if this pane/content is STILL the container of a nested layout!
|
|
// the nested layout will reset its 'container' CSS when/if it is destroyed
|
|
if (hasChildren && $C) {
|
|
// a content-div may not have a specific width, so give it one to contain the Layout
|
|
$C.width( $C.width() );
|
|
$.each( pC, function (key, child) {
|
|
child.resizeAll(); // resize the Layout
|
|
});
|
|
}
|
|
else if ($C)
|
|
$C.css( $C.data(css) ).removeData(css).removeData("layoutRole");
|
|
// remove pane AFTER content in case there was a nested layout
|
|
if (!$P.data(d))
|
|
$P.css( $P.data(css) ).removeData(css);
|
|
}
|
|
|
|
// REMOVE pane resizer and toggler elements
|
|
if ($T) $T.remove();
|
|
if ($R) $R.remove();
|
|
|
|
// CLEAR all pointers and state data
|
|
Instance[pane] = $Ps[pane] = $Cs[pane] = $Rs[pane] = $Ts[pane] = false;
|
|
s = { removed: true };
|
|
|
|
if (!skipResize)
|
|
resizeAll();
|
|
}
|
|
|
|
|
|
/*
|
|
* ###########################
|
|
* ACTION METHODS
|
|
* ###########################
|
|
*/
|
|
|
|
/**
|
|
* @param {string} pane
|
|
*/
|
|
, _hidePane = function (pane) {
|
|
var $P = $Ps[pane]
|
|
, o = options[pane]
|
|
, s = $P[0].style
|
|
;
|
|
if (o.useOffscreenClose) {
|
|
if (!$P.data(_c.offscreenReset))
|
|
$P.data(_c.offscreenReset, { left: s.left, right: s.right });
|
|
$P.css( _c.offscreenCSS );
|
|
}
|
|
else
|
|
$P.hide().removeData(_c.offscreenReset);
|
|
}
|
|
|
|
/**
|
|
* @param {string} pane
|
|
*/
|
|
, _showPane = function (pane) {
|
|
var $P = $Ps[pane]
|
|
, o = options[pane]
|
|
, off = _c.offscreenCSS
|
|
, old = $P.data(_c.offscreenReset)
|
|
, s = $P[0].style
|
|
;
|
|
$P .show() // ALWAYS show, just in case
|
|
.removeData(_c.offscreenReset);
|
|
if (o.useOffscreenClose && old) {
|
|
if (s.left == off.left)
|
|
s.left = old.left;
|
|
if (s.right == off.right)
|
|
s.right = old.right;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Completely 'hides' a pane, including its spacing - as if it does not exist
|
|
* The pane is not actually 'removed' from the source, so can use 'show' to un-hide it
|
|
*
|
|
* @param {(string|Object)} evt_or_pane The pane being hidden, ie: north, south, east, or west
|
|
* @param {boolean=} [noAnimation=false]
|
|
*/
|
|
, hide = function (evt_or_pane, noAnimation) {
|
|
if (!isInitialized()) return;
|
|
var pane = evtPane.call(this, evt_or_pane)
|
|
, o = options[pane]
|
|
, s = state[pane]
|
|
, $P = $Ps[pane]
|
|
, $R = $Rs[pane]
|
|
;
|
|
if (!$P || s.isHidden) return; // pane does not exist OR is already hidden
|
|
|
|
// onhide_start callback - will CANCEL hide if returns false
|
|
if (state.initialized && false === _runCallbacks("onhide_start", pane)) return;
|
|
|
|
s.isSliding = false; // just in case
|
|
delete state.panesSliding[pane];
|
|
|
|
// now hide the elements
|
|
if ($R) $R.hide(); // hide resizer-bar
|
|
if (!state.initialized || s.isClosed) {
|
|
s.isClosed = true; // to trigger open-animation on show()
|
|
s.isHidden = true;
|
|
s.isVisible = false;
|
|
if (!state.initialized)
|
|
_hidePane(pane); // no animation when loading page
|
|
sizeMidPanes(_c[pane].dir === "horz" ? "" : "center");
|
|
if (state.initialized || o.triggerEventsOnLoad)
|
|
_runCallbacks("onhide_end", pane);
|
|
}
|
|
else {
|
|
s.isHiding = true; // used by onclose
|
|
close(pane, false, noAnimation); // adjust all panes to fit
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Show a hidden pane - show as 'closed' by default unless openPane = true
|
|
*
|
|
* @param {(string|Object)} evt_or_pane The pane being opened, ie: north, south, east, or west
|
|
* @param {boolean=} [openPane=false]
|
|
* @param {boolean=} [noAnimation=false]
|
|
* @param {boolean=} [noAlert=false]
|
|
*/
|
|
, show = function (evt_or_pane, openPane, noAnimation, noAlert) {
|
|
if (!isInitialized()) return;
|
|
var pane = evtPane.call(this, evt_or_pane)
|
|
, o = options[pane]
|
|
, s = state[pane]
|
|
, $P = $Ps[pane]
|
|
, $R = $Rs[pane]
|
|
;
|
|
if (!$P || !s.isHidden) return; // pane does not exist OR is not hidden
|
|
|
|
// onshow_start callback - will CANCEL show if returns false
|
|
if (false === _runCallbacks("onshow_start", pane)) return;
|
|
|
|
s.isShowing = true; // used by onopen/onclose
|
|
//s.isHidden = false; - will be set by open/close - if not cancelled
|
|
s.isSliding = false; // just in case
|
|
delete state.panesSliding[pane];
|
|
|
|
// now show the elements
|
|
//if ($R) $R.show(); - will be shown by open/close
|
|
if (openPane === false)
|
|
close(pane, true); // true = force
|
|
else
|
|
open(pane, false, noAnimation, noAlert); // adjust all panes to fit
|
|
}
|
|
|
|
|
|
/**
|
|
* Toggles a pane open/closed by calling either open or close
|
|
*
|
|
* @param {(string|Object)} evt_or_pane The pane being toggled, ie: north, south, east, or west
|
|
* @param {boolean=} [slide=false]
|
|
*/
|
|
, toggle = function (evt_or_pane, slide) {
|
|
if (!isInitialized()) return;
|
|
var evt = evtObj(evt_or_pane)
|
|
, pane = evtPane.call(this, evt_or_pane)
|
|
, s = state[pane]
|
|
;
|
|
if (evt) // called from to $R.dblclick OR triggerPaneEvent
|
|
evt.stopImmediatePropagation();
|
|
if (s.isHidden)
|
|
show(pane); // will call 'open' after unhiding it
|
|
else if (s.isClosed)
|
|
open(pane, !!slide);
|
|
else
|
|
close(pane);
|
|
}
|
|
|
|
|
|
/**
|
|
* Utility method used during init or other auto-processes
|
|
*
|
|
* @param {string} pane The pane being closed
|
|
* @param {boolean=} [setHandles=false]
|
|
*/
|
|
, _closePane = function (pane, setHandles) {
|
|
var
|
|
$P = $Ps[pane]
|
|
, s = state[pane]
|
|
;
|
|
_hidePane(pane);
|
|
s.isClosed = true;
|
|
s.isVisible = false;
|
|
if (setHandles) setAsClosed(pane);
|
|
}
|
|
|
|
/**
|
|
* Close the specified pane (animation optional), and resize all other panes as needed
|
|
*
|
|
* @param {(string|Object)} evt_or_pane The pane being closed, ie: north, south, east, or west
|
|
* @param {boolean=} [force=false]
|
|
* @param {boolean=} [noAnimation=false]
|
|
* @param {boolean=} [skipCallback=false]
|
|
*/
|
|
, close = function (evt_or_pane, force, noAnimation, skipCallback) {
|
|
var pane = evtPane.call(this, evt_or_pane);
|
|
// if pane has been initialized, but NOT the complete layout, close pane instantly
|
|
if (!state.initialized && $Ps[pane]) {
|
|
_closePane(pane, true); // INIT pane as closed
|
|
return;
|
|
}
|
|
if (!isInitialized()) return;
|
|
|
|
var
|
|
$P = $Ps[pane]
|
|
, $R = $Rs[pane]
|
|
, $T = $Ts[pane]
|
|
, o = options[pane]
|
|
, s = state[pane]
|
|
, c = _c[pane]
|
|
, doFX, isShowing, isHiding, wasSliding;
|
|
|
|
// QUEUE in case another action/animation is in progress
|
|
$N.queue(function( queueNext ){
|
|
|
|
if ( !$P
|
|
|| (!o.closable && !s.isShowing && !s.isHiding) // invalid request // (!o.resizable && !o.closable) ???
|
|
|| (!force && s.isClosed && !s.isShowing) // already closed
|
|
) return queueNext();
|
|
|
|
// onclose_start callback - will CANCEL hide if returns false
|
|
// SKIP if just 'showing' a hidden pane as 'closed'
|
|
var abort = !s.isShowing && false === _runCallbacks("onclose_start", pane);
|
|
|
|
// transfer logic vars to temp vars
|
|
isShowing = s.isShowing;
|
|
isHiding = s.isHiding;
|
|
wasSliding = s.isSliding;
|
|
// now clear the logic vars (REQUIRED before aborting)
|
|
delete s.isShowing;
|
|
delete s.isHiding;
|
|
|
|
if (abort) return queueNext();
|
|
|
|
doFX = !noAnimation && !s.isClosed && (o.fxName_close != "none");
|
|
s.isMoving = true;
|
|
s.isClosed = true;
|
|
s.isVisible = false;
|
|
// update isHidden BEFORE sizing panes
|
|
if (isHiding) s.isHidden = true;
|
|
else if (isShowing) s.isHidden = false;
|
|
|
|
if (s.isSliding) // pane is being closed, so UNBIND trigger events
|
|
bindStopSlidingEvents(pane, false); // will set isSliding=false
|
|
else // resize panes adjacent to this one
|
|
sizeMidPanes(_c[pane].dir === "horz" ? "" : "center", false); // false = NOT skipCallback
|
|
|
|
// if this pane has a resizer bar, move it NOW - before animation
|
|
setAsClosed(pane);
|
|
|
|
// CLOSE THE PANE
|
|
if (doFX) { // animate the close
|
|
lockPaneForFX(pane, true); // need to set left/top so animation will work
|
|
$P.hide( o.fxName_close, o.fxSettings_close, o.fxSpeed_close, function () {
|
|
lockPaneForFX(pane, false); // undo
|
|
if (s.isClosed) close_2();
|
|
queueNext();
|
|
});
|
|
}
|
|
else { // hide the pane without animation
|
|
_hidePane(pane);
|
|
close_2();
|
|
queueNext();
|
|
};
|
|
});
|
|
|
|
// SUBROUTINE
|
|
function close_2 () {
|
|
s.isMoving = false;
|
|
bindStartSlidingEvents(pane, true); // will enable if o.slidable = true
|
|
|
|
// if opposite-pane was autoClosed, see if it can be autoOpened now
|
|
var altPane = _c.oppositeEdge[pane];
|
|
if (state[ altPane ].noRoom) {
|
|
setSizeLimits( altPane );
|
|
makePaneFit( altPane );
|
|
}
|
|
|
|
if (!skipCallback && (state.initialized || o.triggerEventsOnLoad)) {
|
|
// onclose callback - UNLESS just 'showing' a hidden pane as 'closed'
|
|
if (!isShowing) _runCallbacks("onclose_end", pane);
|
|
// onhide OR onshow callback
|
|
if (isShowing) _runCallbacks("onshow_end", pane);
|
|
if (isHiding) _runCallbacks("onhide_end", pane);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {string} pane The pane just closed, ie: north, south, east, or west
|
|
*/
|
|
, setAsClosed = function (pane) {
|
|
if (!$Rs[pane]) return; // handles not initialized yet!
|
|
var
|
|
$P = $Ps[pane]
|
|
, $R = $Rs[pane]
|
|
, $T = $Ts[pane]
|
|
, o = options[pane]
|
|
, s = state[pane]
|
|
, side = _c[pane].side
|
|
, rClass = o.resizerClass
|
|
, tClass = o.togglerClass
|
|
, _pane = "-"+ pane // used for classNames
|
|
, _open = "-open"
|
|
, _sliding= "-sliding"
|
|
, _closed = "-closed"
|
|
;
|
|
$R
|
|
.css(side, sC.inset[side]) // move the resizer
|
|
.removeClass( rClass+_open +" "+ rClass+_pane+_open )
|
|
.removeClass( rClass+_sliding +" "+ rClass+_pane+_sliding )
|
|
.addClass( rClass+_closed +" "+ rClass+_pane+_closed )
|
|
;
|
|
// DISABLE 'resizing' when closed - do this BEFORE bindStartSlidingEvents?
|
|
if (o.resizable && $.layout.plugins.draggable)
|
|
$R
|
|
.draggable("disable")
|
|
.removeClass("ui-state-disabled") // do NOT apply disabled styling - not suitable here
|
|
.css("cursor", "default")
|
|
.attr("title","")
|
|
;
|
|
|
|
// if pane has a toggler button, adjust that too
|
|
if ($T) {
|
|
$T
|
|
.removeClass( tClass+_open +" "+ tClass+_pane+_open )
|
|
.addClass( tClass+_closed +" "+ tClass+_pane+_closed )
|
|
.attr("title", o.tips.Open) // may be blank
|
|
;
|
|
// toggler-content - if exists
|
|
$T.children(".content-open").hide();
|
|
$T.children(".content-closed").css("display","block");
|
|
}
|
|
|
|
// sync any 'pin buttons'
|
|
syncPinBtns(pane, false);
|
|
|
|
if (state.initialized) {
|
|
// resize 'length' and position togglers for adjacent panes
|
|
sizeHandles();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Open the specified pane (animation optional), and resize all other panes as needed
|
|
*
|
|
* @param {(string|Object)} evt_or_pane The pane being opened, ie: north, south, east, or west
|
|
* @param {boolean=} [slide=false]
|
|
* @param {boolean=} [noAnimation=false]
|
|
* @param {boolean=} [noAlert=false]
|
|
*/
|
|
, open = function (evt_or_pane, slide, noAnimation, noAlert) {
|
|
if (!isInitialized()) return;
|
|
var pane = evtPane.call(this, evt_or_pane)
|
|
, $P = $Ps[pane]
|
|
, $R = $Rs[pane]
|
|
, $T = $Ts[pane]
|
|
, o = options[pane]
|
|
, s = state[pane]
|
|
, c = _c[pane]
|
|
, doFX, isShowing
|
|
;
|
|
// QUEUE in case another action/animation is in progress
|
|
$N.queue(function( queueNext ){
|
|
|
|
if ( !$P
|
|
|| (!o.resizable && !o.closable && !s.isShowing) // invalid request
|
|
|| (s.isVisible && !s.isSliding) // already open
|
|
) return queueNext();
|
|
|
|
// pane can ALSO be unhidden by just calling show(), so handle this scenario
|
|
if (s.isHidden && !s.isShowing) {
|
|
queueNext(); // call before show() because it needs the queue free
|
|
show(pane, true);
|
|
return;
|
|
}
|
|
|
|
if (s.autoResize && s.size != o.size) // resize pane to original size set in options
|
|
sizePane(pane, o.size, true, true, true); // true=skipCallback/noAnimation/forceResize
|
|
else
|
|
// make sure there is enough space available to open the pane
|
|
setSizeLimits(pane, slide);
|
|
|
|
// onopen_start callback - will CANCEL open if returns false
|
|
var cbReturn = _runCallbacks("onopen_start", pane);
|
|
|
|
if (cbReturn === "abort")
|
|
return queueNext();
|
|
|
|
// update pane-state again in case options were changed in onopen_start
|
|
if (cbReturn !== "NC") // NC = "No Callback"
|
|
setSizeLimits(pane, slide);
|
|
|
|
if (s.minSize > s.maxSize) { // INSUFFICIENT ROOM FOR PANE TO OPEN!
|
|
syncPinBtns(pane, false); // make sure pin-buttons are reset
|
|
if (!noAlert && o.tips.noRoomToOpen)
|
|
alert(o.tips.noRoomToOpen);
|
|
return queueNext(); // ABORT
|
|
}
|
|
|
|
if (slide) // START Sliding - will set isSliding=true
|
|
bindStopSlidingEvents(pane, true); // BIND trigger events to close sliding-pane
|
|
else if (s.isSliding) // PIN PANE (stop sliding) - open pane 'normally' instead
|
|
bindStopSlidingEvents(pane, false); // UNBIND trigger events - will set isSliding=false
|
|
else if (o.slidable)
|
|
bindStartSlidingEvents(pane, false); // UNBIND trigger events
|
|
|
|
s.noRoom = false; // will be reset by makePaneFit if 'noRoom'
|
|
makePaneFit(pane);
|
|
|
|
// transfer logic var to temp var
|
|
isShowing = s.isShowing;
|
|
// now clear the logic var
|
|
delete s.isShowing;
|
|
|
|
doFX = !noAnimation && s.isClosed && (o.fxName_open != "none");
|
|
s.isMoving = true;
|
|
s.isVisible = true;
|
|
s.isClosed = false;
|
|
// update isHidden BEFORE sizing panes - WHY??? Old?
|
|
if (isShowing) s.isHidden = false;
|
|
|
|
if (doFX) { // ANIMATE
|
|
// mask adjacent panes with objects
|
|
lockPaneForFX(pane, true); // need to set left/top so animation will work
|
|
$P.show( o.fxName_open, o.fxSettings_open, o.fxSpeed_open, function() {
|
|
lockPaneForFX(pane, false); // undo
|
|
if (s.isVisible) open_2(); // continue
|
|
queueNext();
|
|
});
|
|
}
|
|
else { // no animation
|
|
_showPane(pane);// just show pane and...
|
|
open_2(); // continue
|
|
queueNext();
|
|
};
|
|
});
|
|
|
|
// SUBROUTINE
|
|
function open_2 () {
|
|
s.isMoving = false;
|
|
|
|
// cure iframe display issues
|
|
_fixIframe(pane);
|
|
|
|
// NOTE: if isSliding, then other panes are NOT 'resized'
|
|
if (!s.isSliding) { // resize all panes adjacent to this one
|
|
sizeMidPanes(_c[pane].dir=="vert" ? "center" : "", false); // false = NOT skipCallback
|
|
}
|
|
|
|
// set classes, position handles and execute callbacks...
|
|
setAsOpen(pane);
|
|
};
|
|
|
|
}
|
|
|
|
/**
|
|
* @param {string} pane The pane just opened, ie: north, south, east, or west
|
|
* @param {boolean=} [skipCallback=false]
|
|
*/
|
|
, setAsOpen = function (pane, skipCallback) {
|
|
var
|
|
$P = $Ps[pane]
|
|
, $R = $Rs[pane]
|
|
, $T = $Ts[pane]
|
|
, o = options[pane]
|
|
, s = state[pane]
|
|
, side = _c[pane].side
|
|
, rClass = o.resizerClass
|
|
, tClass = o.togglerClass
|
|
, _pane = "-"+ pane // used for classNames
|
|
, _open = "-open"
|
|
, _closed = "-closed"
|
|
, _sliding= "-sliding"
|
|
;
|
|
$R
|
|
.css(side, sC.inset[side] + getPaneSize(pane)) // move the resizer
|
|
.removeClass( rClass+_closed +" "+ rClass+_pane+_closed )
|
|
.addClass( rClass+_open +" "+ rClass+_pane+_open )
|
|
;
|
|
if (s.isSliding)
|
|
$R.addClass( rClass+_sliding +" "+ rClass+_pane+_sliding )
|
|
else // in case 'was sliding'
|
|
$R.removeClass( rClass+_sliding +" "+ rClass+_pane+_sliding )
|
|
|
|
removeHover( 0, $R ); // remove hover classes
|
|
if (o.resizable && $.layout.plugins.draggable)
|
|
$R .draggable("enable")
|
|
.css("cursor", o.resizerCursor)
|
|
.attr("title", o.tips.Resize);
|
|
else if (!s.isSliding)
|
|
$R.css("cursor", "default"); // n-resize, s-resize, etc
|
|
|
|
// if pane also has a toggler button, adjust that too
|
|
if ($T) {
|
|
$T .removeClass( tClass+_closed +" "+ tClass+_pane+_closed )
|
|
.addClass( tClass+_open +" "+ tClass+_pane+_open )
|
|
.attr("title", o.tips.Close); // may be blank
|
|
removeHover( 0, $T ); // remove hover classes
|
|
// toggler-content - if exists
|
|
$T.children(".content-closed").hide();
|
|
$T.children(".content-open").css("display","block");
|
|
}
|
|
|
|
// sync any 'pin buttons'
|
|
syncPinBtns(pane, !s.isSliding);
|
|
|
|
// update pane-state dimensions - BEFORE resizing content
|
|
$.extend(s, elDims($P));
|
|
|
|
if (state.initialized) {
|
|
// resize resizer & toggler sizes for all panes
|
|
sizeHandles();
|
|
// resize content every time pane opens - to be sure
|
|
sizeContent(pane, true); // true = remeasure headers/footers, even if 'pane.isMoving'
|
|
}
|
|
|
|
if (!skipCallback && (state.initialized || o.triggerEventsOnLoad) && $P.is(":visible")) {
|
|
// onopen callback
|
|
_runCallbacks("onopen_end", pane);
|
|
// onshow callback - TODO: should this be here?
|
|
if (s.isShowing) _runCallbacks("onshow_end", pane);
|
|
|
|
// ALSO call onresize because layout-size *may* have changed while pane was closed
|
|
if (state.initialized)
|
|
_runCallbacks("onresize_end", pane);
|
|
}
|
|
|
|
// TODO: Somehow sizePane("north") is being called after this point???
|
|
}
|
|
|
|
|
|
/**
|
|
* slideOpen / slideClose / slideToggle
|
|
*
|
|
* Pass-though methods for sliding
|
|
*/
|
|
, slideOpen = function (evt_or_pane) {
|
|
if (!isInitialized()) return;
|
|
var evt = evtObj(evt_or_pane)
|
|
, pane = evtPane.call(this, evt_or_pane)
|
|
, s = state[pane]
|
|
, delay = options[pane].slideDelay_open
|
|
;
|
|
// prevent event from triggering on NEW resizer binding created below
|
|
if (evt) evt.stopImmediatePropagation();
|
|
|
|
if (s.isClosed && evt && evt.type === "mouseenter" && delay > 0)
|
|
// trigger = mouseenter - use a delay
|
|
timer.set(pane+"_openSlider", open_NOW, delay);
|
|
else
|
|
open_NOW(); // will unbind events if is already open
|
|
|
|
/**
|
|
* SUBROUTINE for timed open
|
|
*/
|
|
function open_NOW () {
|
|
if (!s.isClosed) // skip if no longer closed!
|
|
bindStopSlidingEvents(pane, true); // BIND trigger events to close sliding-pane
|
|
else if (!s.isMoving)
|
|
open(pane, true); // true = slide - open() will handle binding
|
|
};
|
|
}
|
|
|
|
, slideClose = function (evt_or_pane) {
|
|
if (!isInitialized()) return;
|
|
var evt = evtObj(evt_or_pane)
|
|
, pane = evtPane.call(this, evt_or_pane)
|
|
, o = options[pane]
|
|
, s = state[pane]
|
|
, delay = s.isMoving ? 1000 : 300 // MINIMUM delay - option may override
|
|
;
|
|
if (s.isClosed || s.isResizing)
|
|
return; // skip if already closed OR in process of resizing
|
|
else if (o.slideTrigger_close === "click")
|
|
close_NOW(); // close immediately onClick
|
|
else if (o.preventQuickSlideClose && s.isMoving)
|
|
return; // handle Chrome quick-close on slide-open
|
|
else if (o.preventPrematureSlideClose && evt && $.layout.isMouseOverElem(evt, $Ps[pane]))
|
|
return; // handle incorrect mouseleave trigger, like when over a SELECT-list in IE
|
|
else if (evt) // trigger = mouseleave - use a delay
|
|
// 1 sec delay if 'opening', else .3 sec
|
|
timer.set(pane+"_closeSlider", close_NOW, max(o.slideDelay_close, delay));
|
|
else // called programically
|
|
close_NOW();
|
|
|
|
/**
|
|
* SUBROUTINE for timed close
|
|
*/
|
|
function close_NOW () {
|
|
if (s.isClosed) // skip 'close' if already closed!
|
|
bindStopSlidingEvents(pane, false); // UNBIND trigger events - TODO: is this needed here?
|
|
else if (!s.isMoving)
|
|
close(pane); // close will handle unbinding
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param {(string|Object)} evt_or_pane The pane being opened, ie: north, south, east, or west
|
|
*/
|
|
, slideToggle = function (evt_or_pane) {
|
|
var pane = evtPane.call(this, evt_or_pane);
|
|
toggle(pane, true);
|
|
}
|
|
|
|
|
|
/**
|
|
* Must set left/top on East/South panes so animation will work properly
|
|
*
|
|
* @param {string} pane The pane to lock, 'east' or 'south' - any other is ignored!
|
|
* @param {boolean} doLock true = set left/top, false = remove
|
|
*/
|
|
, lockPaneForFX = function (pane, doLock) {
|
|
var $P = $Ps[pane]
|
|
, s = state[pane]
|
|
, o = options[pane]
|
|
, z = options.zIndexes
|
|
;
|
|
if (doLock) {
|
|
showMasks( pane, { animation: true, objectsOnly: true });
|
|
$P.css({ zIndex: z.pane_animate }); // overlay all elements during animation
|
|
if (pane=="south")
|
|
$P.css({ top: sC.inset.top + sC.innerHeight - $P.outerHeight() });
|
|
else if (pane=="east")
|
|
$P.css({ left: sC.inset.left + sC.innerWidth - $P.outerWidth() });
|
|
}
|
|
else { // animation DONE - RESET CSS
|
|
hideMasks();
|
|
$P.css({ zIndex: (s.isSliding ? z.pane_sliding : z.pane_normal) });
|
|
if (pane=="south")
|
|
$P.css({ top: "auto" });
|
|
// if pane is positioned 'off-screen', then DO NOT screw with it!
|
|
else if (pane=="east" && !$P.css("left").match(/\-99999/))
|
|
$P.css({ left: "auto" });
|
|
// fix anti-aliasing in IE - only needed for animations that change opacity
|
|
if (browser.msie && o.fxOpacityFix && o.fxName_open != "slide" && $P.css("filter") && $P.css("opacity") == 1)
|
|
$P[0].style.removeAttribute('filter');
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Toggle sliding functionality of a specific pane on/off by adding removing 'slide open' trigger
|
|
*
|
|
* @see open(), close()
|
|
* @param {string} pane The pane to enable/disable, 'north', 'south', etc.
|
|
* @param {boolean} enable Enable or Disable sliding?
|
|
*/
|
|
, bindStartSlidingEvents = function (pane, enable) {
|
|
var o = options[pane]
|
|
, $P = $Ps[pane]
|
|
, $R = $Rs[pane]
|
|
, evtName = o.slideTrigger_open.toLowerCase()
|
|
;
|
|
if (!$R || (enable && !o.slidable)) return;
|
|
|
|
// make sure we have a valid event
|
|
if (evtName.match(/mouseover/))
|
|
evtName = o.slideTrigger_open = "mouseenter";
|
|
else if (!evtName.match(/(click|dblclick|mouseenter)/))
|
|
evtName = o.slideTrigger_open = "click";
|
|
|
|
// must remove double-click-toggle when using dblclick-slide
|
|
if (o.resizerDblClickToggle && evtName.match(/click/)) {
|
|
$R[enable ? "unbind" : "bind"]('dblclick.'+ sID, toggle)
|
|
}
|
|
|
|
$R
|
|
// add or remove event
|
|
[enable ? "bind" : "unbind"](evtName +'.'+ sID, slideOpen)
|
|
// set the appropriate cursor & title/tip
|
|
.css("cursor", enable ? o.sliderCursor : "default")
|
|
.attr("title", enable ? o.tips.Slide : "")
|
|
;
|
|
}
|
|
|
|
/**
|
|
* Add or remove 'mouseleave' events to 'slide close' when pane is 'sliding' open or closed
|
|
* Also increases zIndex when pane is sliding open
|
|
* See bindStartSlidingEvents for code to control 'slide open'
|
|
*
|
|
* @see slideOpen(), slideClose()
|
|
* @param {string} pane The pane to process, 'north', 'south', etc.
|
|
* @param {boolean} enable Enable or Disable events?
|
|
*/
|
|
, bindStopSlidingEvents = function (pane, enable) {
|
|
var o = options[pane]
|
|
, s = state[pane]
|
|
, c = _c[pane]
|
|
, z = options.zIndexes
|
|
, evtName = o.slideTrigger_close.toLowerCase()
|
|
, action = (enable ? "bind" : "unbind")
|
|
, $P = $Ps[pane]
|
|
, $R = $Rs[pane]
|
|
;
|
|
timer.clear(pane+"_closeSlider"); // just in case
|
|
|
|
if (enable) {
|
|
s.isSliding = true;
|
|
state.panesSliding[pane] = true;
|
|
// remove 'slideOpen' event from resizer
|
|
// ALSO will raise the zIndex of the pane & resizer
|
|
bindStartSlidingEvents(pane, false);
|
|
}
|
|
else {
|
|
s.isSliding = false;
|
|
delete state.panesSliding[pane];
|
|
}
|
|
|
|
// RE/SET zIndex - increases when pane is sliding-open, resets to normal when not
|
|
$P.css("zIndex", enable ? z.pane_sliding : z.pane_normal);
|
|
$R.css("zIndex", enable ? z.pane_sliding+2 : z.resizer_normal); // NOTE: mask = pane_sliding+1
|
|
|
|
// make sure we have a valid event
|
|
if (!evtName.match(/(click|mouseleave)/))
|
|
evtName = o.slideTrigger_close = "mouseleave"; // also catches 'mouseout'
|
|
|
|
// add/remove slide triggers
|
|
$R[action](evtName, slideClose); // base event on resize
|
|
// need extra events for mouseleave
|
|
if (evtName === "mouseleave") {
|
|
// also close on pane.mouseleave
|
|
$P[action]("mouseleave."+ sID, slideClose);
|
|
// cancel timer when mouse moves between 'pane' and 'resizer'
|
|
$R[action]("mouseenter."+ sID, cancelMouseOut);
|
|
$P[action]("mouseenter."+ sID, cancelMouseOut);
|
|
}
|
|
|
|
if (!enable)
|
|
timer.clear(pane+"_closeSlider");
|
|
else if (evtName === "click" && !o.resizable) {
|
|
// IF pane is not resizable (which already has a cursor and tip)
|
|
// then set the a cursor & title/tip on resizer when sliding
|
|
$R.css("cursor", enable ? o.sliderCursor : "default");
|
|
$R.attr("title", enable ? o.tips.Close : ""); // use Toggler-tip, eg: "Close Pane"
|
|
}
|
|
|
|
// SUBROUTINE for mouseleave timer clearing
|
|
function cancelMouseOut (evt) {
|
|
timer.clear(pane+"_closeSlider");
|
|
evt.stopPropagation();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Hides/closes a pane if there is insufficient room - reverses this when there is room again
|
|
* MUST have already called setSizeLimits() before calling this method
|
|
*
|
|
* @param {string} pane The pane being resized
|
|
* @param {boolean=} [isOpening=false] Called from onOpen?
|
|
* @param {boolean=} [skipCallback=false] Should the onresize callback be run?
|
|
* @param {boolean=} [force=false]
|
|
*/
|
|
, makePaneFit = function (pane, isOpening, skipCallback, force) {
|
|
var o = options[pane]
|
|
, s = state[pane]
|
|
, c = _c[pane]
|
|
, $P = $Ps[pane]
|
|
, $R = $Rs[pane]
|
|
, isSidePane = c.dir==="vert"
|
|
, hasRoom = false
|
|
;
|
|
// special handling for center & east/west panes
|
|
if (pane === "center" || (isSidePane && s.noVerticalRoom)) {
|
|
// see if there is enough room to display the pane
|
|
// ERROR: hasRoom = s.minHeight <= s.maxHeight && (isSidePane || s.minWidth <= s.maxWidth);
|
|
hasRoom = (s.maxHeight >= 0);
|
|
if (hasRoom && s.noRoom) { // previously hidden due to noRoom, so show now
|
|
_showPane(pane);
|
|
if ($R) $R.show();
|
|
s.isVisible = true;
|
|
s.noRoom = false;
|
|
if (isSidePane) s.noVerticalRoom = false;
|
|
_fixIframe(pane);
|
|
}
|
|
else if (!hasRoom && !s.noRoom) { // not currently hidden, so hide now
|
|
_hidePane(pane);
|
|
if ($R) $R.hide();
|
|
s.isVisible = false;
|
|
s.noRoom = true;
|
|
}
|
|
}
|
|
|
|
// see if there is enough room to fit the border-pane
|
|
if (pane === "center") {
|
|
// ignore center in this block
|
|
}
|
|
else if (s.minSize <= s.maxSize) { // pane CAN fit
|
|
hasRoom = true;
|
|
if (s.size > s.maxSize) // pane is too big - shrink it
|
|
sizePane(pane, s.maxSize, skipCallback, true, force); // true = noAnimation
|
|
else if (s.size < s.minSize) // pane is too small - enlarge it
|
|
sizePane(pane, s.minSize, skipCallback, true, force); // true = noAnimation
|
|
// need s.isVisible because new pseudoClose method keeps pane visible, but off-screen
|
|
else if ($R && s.isVisible && $P.is(":visible")) {
|
|
// make sure resizer-bar is positioned correctly
|
|
// handles situation where nested layout was 'hidden' when initialized
|
|
var pos = s.size + sC.inset[c.side];
|
|
if ($.layout.cssNum( $R, c.side ) != pos) $R.css( c.side, pos );
|
|
}
|
|
|
|
// if was previously hidden due to noRoom, then RESET because NOW there is room
|
|
if (s.noRoom) {
|
|
// s.noRoom state will be set by open or show
|
|
if (s.wasOpen && o.closable) {
|
|
if (o.autoReopen)
|
|
open(pane, false, true, true); // true = noAnimation, true = noAlert
|
|
else // leave the pane closed, so just update state
|
|
s.noRoom = false;
|
|
}
|
|
else
|
|
show(pane, s.wasOpen, true, true); // true = noAnimation, true = noAlert
|
|
}
|
|
}
|
|
else { // !hasRoom - pane CANNOT fit
|
|
if (!s.noRoom) { // pane not set as noRoom yet, so hide or close it now...
|
|
s.noRoom = true; // update state
|
|
s.wasOpen = !s.isClosed && !s.isSliding;
|
|
if (s.isClosed){} // SKIP
|
|
else if (o.closable) // 'close' if possible
|
|
close(pane, true, true); // true = force, true = noAnimation
|
|
else // 'hide' pane if cannot just be closed
|
|
hide(pane, true); // true = noAnimation
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* manualSizePane is an exposed flow-through method allowing extra code when pane is 'manually resized'
|
|
*
|
|
* @param {(string|Object)} evt_or_pane The pane being resized
|
|
* @param {number} size The *desired* new size for this pane - will be validated
|
|
* @param {boolean=} [skipCallback=false] Should the onresize callback be run?
|
|
* @param {boolean=} [noAnimation=false]
|
|
* @param {boolean=} [force=false] Force resizing even if does not seem necessary
|
|
*/
|
|
, manualSizePane = function (evt_or_pane, size, skipCallback, noAnimation, force) {
|
|
if (!isInitialized()) return;
|
|
var pane = evtPane.call(this, evt_or_pane)
|
|
, o = options[pane]
|
|
, s = state[pane]
|
|
// if resizing callbacks have been delayed and resizing is now DONE, force resizing to complete...
|
|
, forceResize = force || (o.livePaneResizing && !s.isResizing)
|
|
;
|
|
// ANY call to manualSizePane disables autoResize - ie, percentage sizing
|
|
s.autoResize = false;
|
|
// flow-through...
|
|
sizePane(pane, size, skipCallback, noAnimation, forceResize); // will animate resize if option enabled
|
|
}
|
|
|
|
/**
|
|
* sizePane is called only by internal methods whenever a pane needs to be resized
|
|
*
|
|
* @param {(string|Object)} evt_or_pane The pane being resized
|
|
* @param {number} size The *desired* new size for this pane - will be validated
|
|
* @param {boolean=} [skipCallback=false] Should the onresize callback be run?
|
|
* @param {boolean=} [noAnimation=false]
|
|
* @param {boolean=} [force=false] Force resizing even if does not seem necessary
|
|
*/
|
|
, sizePane = function (evt_or_pane, size, skipCallback, noAnimation, force) {
|
|
if (!isInitialized()) return;
|
|
var pane = evtPane.call(this, evt_or_pane) // probably NEVER called from event?
|
|
, o = options[pane]
|
|
, s = state[pane]
|
|
, $P = $Ps[pane]
|
|
, $R = $Rs[pane]
|
|
, side = _c[pane].side
|
|
, dimName = _c[pane].sizeType.toLowerCase()
|
|
, skipResizeWhileDragging = s.isResizing && !o.triggerEventsDuringLiveResize
|
|
, doFX = noAnimation !== true && o.animatePaneSizing
|
|
, oldSize, newSize
|
|
;
|
|
// QUEUE in case another action/animation is in progress
|
|
$N.queue(function( queueNext ){
|
|
// calculate 'current' min/max sizes
|
|
setSizeLimits(pane); // update pane-state
|
|
oldSize = s.size;
|
|
size = _parseSize(pane, size); // handle percentages & auto
|
|
size = max(size, _parseSize(pane, o.minSize));
|
|
size = min(size, s.maxSize);
|
|
if (size < s.minSize) { // not enough room for pane!
|
|
queueNext(); // call before makePaneFit() because it needs the queue free
|
|
makePaneFit(pane, false, skipCallback); // will hide or close pane
|
|
return;
|
|
}
|
|
|
|
// IF newSize is same as oldSize, then nothing to do - abort
|
|
if (!force && size === oldSize)
|
|
return queueNext();
|
|
|
|
s.newSize = size;
|
|
|
|
// onresize_start callback CANNOT cancel resizing because this would break the layout!
|
|
if (!skipCallback && state.initialized && s.isVisible)
|
|
_runCallbacks("onresize_start", pane);
|
|
|
|
// resize the pane, and make sure its visible
|
|
newSize = cssSize(pane, size);
|
|
|
|
if (doFX && $P.is(":visible")) { // ANIMATE
|
|
var fx = $.layout.effects.size[pane] || $.layout.effects.size.all
|
|
, easing = o.fxSettings_size.easing || fx.easing
|
|
, z = options.zIndexes
|
|
, props = {};
|
|
props[ dimName ] = newSize +'px';
|
|
s.isMoving = true;
|
|
// overlay all elements during animation
|
|
$P.css({ zIndex: z.pane_animate })
|
|
.show().animate( props, o.fxSpeed_size, easing, function(){
|
|
// reset zIndex after animation
|
|
$P.css({ zIndex: (s.isSliding ? z.pane_sliding : z.pane_normal) });
|
|
s.isMoving = false;
|
|
delete s.newSize;
|
|
sizePane_2(); // continue
|
|
queueNext();
|
|
});
|
|
}
|
|
else { // no animation
|
|
$P.css( dimName, newSize ); // resize pane
|
|
delete s.newSize;
|
|
// if pane is visible, then
|
|
if ($P.is(":visible"))
|
|
sizePane_2(); // continue
|
|
else {
|
|
// pane is NOT VISIBLE, so just update state data...
|
|
// when pane is *next opened*, it will have the new size
|
|
s.size = size; // update state.size
|
|
$.extend(s, elDims($P)); // update state dimensions
|
|
}
|
|
queueNext();
|
|
};
|
|
|
|
});
|
|
|
|
// SUBROUTINE
|
|
function sizePane_2 () {
|
|
/* Panes are sometimes not sized precisely in some browsers!?
|
|
* This code will resize the pane up to 3 times to nudge the pane to the correct size
|
|
*/
|
|
var actual = dimName==='width' ? $P.outerWidth() : $P.outerHeight()
|
|
, tries = [{
|
|
pane: pane
|
|
, count: 1
|
|
, target: size
|
|
, actual: actual
|
|
, correct: (size === actual)
|
|
, attempt: size
|
|
, cssSize: newSize
|
|
}]
|
|
, lastTry = tries[0]
|
|
, thisTry = {}
|
|
, msg = 'Inaccurate size after resizing the '+ pane +'-pane.'
|
|
;
|
|
while ( !lastTry.correct ) {
|
|
thisTry = { pane: pane, count: lastTry.count+1, target: size };
|
|
|
|
if (lastTry.actual > size)
|
|
thisTry.attempt = max(0, lastTry.attempt - (lastTry.actual - size));
|
|
else // lastTry.actual < size
|
|
thisTry.attempt = max(0, lastTry.attempt + (size - lastTry.actual));
|
|
|
|
thisTry.cssSize = cssSize(pane, thisTry.attempt);
|
|
$P.css( dimName, thisTry.cssSize );
|
|
|
|
thisTry.actual = dimName=='width' ? $P.outerWidth() : $P.outerHeight();
|
|
thisTry.correct = (size === thisTry.actual);
|
|
|
|
// log attempts and alert the user of this *non-fatal error* (if showDebugMessages)
|
|
if ( tries.length === 1) {
|
|
_log(msg, false, true);
|
|
_log(lastTry, false, true);
|
|
}
|
|
_log(thisTry, false, true);
|
|
// after 4 tries, is as close as its gonna get!
|
|
if (tries.length > 3) break;
|
|
|
|
tries.push( thisTry );
|
|
lastTry = tries[ tries.length - 1 ];
|
|
}
|
|
// END TESTING CODE
|
|
|
|
// update pane-state dimensions
|
|
s.size = size;
|
|
$.extend(s, elDims($P));
|
|
|
|
if (s.isVisible && $P.is(":visible")) {
|
|
// reposition the resizer-bar
|
|
if ($R) $R.css( side, size + sC.inset[side] );
|
|
// resize the content-div
|
|
sizeContent(pane);
|
|
}
|
|
|
|
if (!skipCallback && !skipResizeWhileDragging && state.initialized && s.isVisible)
|
|
_runCallbacks("onresize_end", pane);
|
|
|
|
// resize all the adjacent panes, and adjust their toggler buttons
|
|
// when skipCallback passed, it means the controlling method will handle 'other panes'
|
|
if (!skipCallback) {
|
|
// also no callback if live-resize is in progress and NOT triggerEventsDuringLiveResize
|
|
if (!s.isSliding) sizeMidPanes(_c[pane].dir=="horz" ? "" : "center", skipResizeWhileDragging, force);
|
|
sizeHandles();
|
|
}
|
|
|
|
// if opposite-pane was autoClosed, see if it can be autoOpened now
|
|
var altPane = _c.oppositeEdge[pane];
|
|
if (size < oldSize && state[ altPane ].noRoom) {
|
|
setSizeLimits( altPane );
|
|
makePaneFit( altPane, false, skipCallback );
|
|
}
|
|
|
|
// DEBUG - ALERT user/developer so they know there was a sizing problem
|
|
if (tries.length > 1)
|
|
_log(msg +'\nSee the Error Console for details.', true, true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @see initPanes(), sizePane(), resizeAll(), open(), close(), hide()
|
|
* @param {(Array.<string>|string)} panes The pane(s) being resized, comma-delmited string
|
|
* @param {boolean=} [skipCallback=false] Should the onresize callback be run?
|
|
* @param {boolean=} [force=false]
|
|
*/
|
|
, sizeMidPanes = function (panes, skipCallback, force) {
|
|
panes = (panes ? panes : "east,west,center").split(",");
|
|
|
|
$.each(panes, function (i, pane) {
|
|
if (!$Ps[pane]) return; // NO PANE - skip
|
|
var
|
|
o = options[pane]
|
|
, s = state[pane]
|
|
, $P = $Ps[pane]
|
|
, $R = $Rs[pane]
|
|
, isCenter= (pane=="center")
|
|
, hasRoom = true
|
|
, CSS = {}
|
|
// if pane is not visible, show it invisibly NOW rather than for *each call* in this script
|
|
, visCSS = $.layout.showInvisibly($P)
|
|
|
|
, newCenter = calcNewCenterPaneDims()
|
|
;
|
|
|
|
// update pane-state dimensions
|
|
$.extend(s, elDims($P));
|
|
|
|
if (pane === "center") {
|
|
if (!force && s.isVisible && newCenter.width === s.outerWidth && newCenter.height === s.outerHeight) {
|
|
$P.css(visCSS);
|
|
return true; // SKIP - pane already the correct size
|
|
}
|
|
// set state for makePaneFit() logic
|
|
$.extend(s, cssMinDims(pane), {
|
|
maxWidth: newCenter.width
|
|
, maxHeight: newCenter.height
|
|
});
|
|
CSS = newCenter;
|
|
s.newWidth = CSS.width;
|
|
s.newHeight = CSS.height;
|
|
// convert OUTER width/height to CSS width/height
|
|
CSS.width = cssW($P, CSS.width);
|
|
// NEW - allow pane to extend 'below' visible area rather than hide it
|
|
CSS.height = cssH($P, CSS.height);
|
|
hasRoom = CSS.width >= 0 && CSS.height >= 0; // height >= 0 = ALWAYS TRUE NOW
|
|
|
|
// during layout init, try to shrink east/west panes to make room for center
|
|
if (!state.initialized && o.minWidth > newCenter.width) {
|
|
var
|
|
reqPx = o.minWidth - s.outerWidth
|
|
, minE = options.east.minSize || 0
|
|
, minW = options.west.minSize || 0
|
|
, sizeE = state.east.size
|
|
, sizeW = state.west.size
|
|
, newE = sizeE
|
|
, newW = sizeW
|
|
;
|
|
if (reqPx > 0 && state.east.isVisible && sizeE > minE) {
|
|
newE = max( sizeE-minE, sizeE-reqPx );
|
|
reqPx -= sizeE-newE;
|
|
}
|
|
if (reqPx > 0 && state.west.isVisible && sizeW > minW) {
|
|
newW = max( sizeW-minW, sizeW-reqPx );
|
|
reqPx -= sizeW-newW;
|
|
}
|
|
// IF we found enough extra space, then resize the border panes as calculated
|
|
if (reqPx === 0) {
|
|
if (sizeE && sizeE != minE)
|
|
sizePane('east', newE, true, true, force); // true = skipCallback/noAnimation - initPanes will handle when done
|
|
if (sizeW && sizeW != minW)
|
|
sizePane('west', newW, true, true, force); // true = skipCallback/noAnimation
|
|
// now start over!
|
|
sizeMidPanes('center', skipCallback, force);
|
|
$P.css(visCSS);
|
|
return; // abort this loop
|
|
}
|
|
}
|
|
}
|
|
else { // for east and west, set only the height, which is same as center height
|
|
// set state.min/maxWidth/Height for makePaneFit() logic
|
|
if (s.isVisible && !s.noVerticalRoom)
|
|
$.extend(s, elDims($P), cssMinDims(pane))
|
|
if (!force && !s.noVerticalRoom && newCenter.height === s.outerHeight) {
|
|
$P.css(visCSS);
|
|
return true; // SKIP - pane already the correct size
|
|
}
|
|
// east/west have same top, bottom & height as center
|
|
CSS.top = newCenter.top;
|
|
CSS.bottom = newCenter.bottom;
|
|
s.newSize = newCenter.height
|
|
// NEW - allow pane to extend 'below' visible area rather than hide it
|
|
CSS.height = cssH($P, newCenter.height);
|
|
s.maxHeight = CSS.height;
|
|
hasRoom = (s.maxHeight >= 0); // ALWAYS TRUE NOW
|
|
if (!hasRoom) s.noVerticalRoom = true; // makePaneFit() logic
|
|
}
|
|
|
|
if (hasRoom) {
|
|
// resizeAll passes skipCallback because it triggers callbacks after ALL panes are resized
|
|
if (!skipCallback && state.initialized)
|
|
_runCallbacks("onresize_start", pane);
|
|
|
|
$P.css(CSS); // apply the CSS to pane
|
|
if (pane !== "center")
|
|
sizeHandles(pane); // also update resizer length
|
|
if (s.noRoom && !s.isClosed && !s.isHidden)
|
|
makePaneFit(pane); // will re-open/show auto-closed/hidden pane
|
|
if (s.isVisible) {
|
|
$.extend(s, elDims($P)); // update pane dimensions
|
|
if (state.initialized) sizeContent(pane); // also resize the contents, if exists
|
|
}
|
|
}
|
|
else if (!s.noRoom && s.isVisible) // no room for pane
|
|
makePaneFit(pane); // will hide or close pane
|
|
|
|
// reset visibility, if necessary
|
|
$P.css(visCSS);
|
|
|
|
delete s.newSize;
|
|
delete s.newWidth;
|
|
delete s.newHeight;
|
|
|
|
if (!s.isVisible)
|
|
return true; // DONE - next pane
|
|
|
|
/*
|
|
* Extra CSS for IE6 or IE7 in Quirks-mode - add 'width' to NORTH/SOUTH panes
|
|
* Normally these panes have only 'left' & 'right' positions so pane auto-sizes
|
|
* ALSO required when pane is an IFRAME because will NOT default to 'full width'
|
|
* TODO: Can I use width:100% for a north/south iframe?
|
|
* TODO: Sounds like a job for $P.outerWidth( sC.innerWidth ) SETTER METHOD
|
|
*/
|
|
if (pane === "center") { // finished processing midPanes
|
|
var fix = browser.isIE6 || !browser.boxModel;
|
|
if ($Ps.north && (fix || state.north.tagName=="IFRAME"))
|
|
$Ps.north.css("width", cssW($Ps.north, sC.innerWidth));
|
|
if ($Ps.south && (fix || state.south.tagName=="IFRAME"))
|
|
$Ps.south.css("width", cssW($Ps.south, sC.innerWidth));
|
|
}
|
|
|
|
// resizeAll passes skipCallback because it triggers callbacks after ALL panes are resized
|
|
if (!skipCallback && state.initialized)
|
|
_runCallbacks("onresize_end", pane);
|
|
});
|
|
}
|
|
|
|
|
|
/**
|
|
* @see window.onresize(), callbacks or custom code
|
|
* @param {(Object|boolean)=} evt_or_refresh If 'true', then also reset pane-positioning
|
|
*/
|
|
, resizeAll = function (evt_or_refresh) {
|
|
var oldW = sC.innerWidth
|
|
, oldH = sC.innerHeight
|
|
;
|
|
// stopPropagation if called by trigger("layoutdestroy") - use evtPane utility
|
|
evtPane(evt_or_refresh);
|
|
|
|
// cannot size layout when 'container' is hidden or collapsed
|
|
if (!$N.is(":visible")) return;
|
|
|
|
if (!state.initialized) {
|
|
_initLayoutElements();
|
|
return; // no need to resize since we just initialized!
|
|
}
|
|
|
|
if (evt_or_refresh === true && $.isPlainObject(options.outset)) {
|
|
// update container CSS in case outset option has changed
|
|
$N.css( options.outset );
|
|
}
|
|
// UPDATE container dimensions
|
|
$.extend(sC, elDims( $N, options.inset ));
|
|
if (!sC.outerHeight) return;
|
|
|
|
// if 'true' passed, refresh pane & handle positioning too
|
|
if (evt_or_refresh === true) {
|
|
setPanePosition();
|
|
}
|
|
|
|
// onresizeall_start will CANCEL resizing if returns false
|
|
// state.container has already been set, so user can access this info for calcuations
|
|
if (false === _runCallbacks("onresizeall_start")) return false;
|
|
|
|
var // see if container is now 'smaller' than before
|
|
shrunkH = (sC.innerHeight < oldH)
|
|
, shrunkW = (sC.innerWidth < oldW)
|
|
, $P, o, s
|
|
;
|
|
// NOTE special order for sizing: S-N-E-W
|
|
$.each(["south","north","east","west"], function (i, pane) {
|
|
if (!$Ps[pane]) return; // no pane - SKIP
|
|
o = options[pane];
|
|
s = state[pane];
|
|
if (s.autoResize && s.size != o.size) // resize pane to original size set in options
|
|
sizePane(pane, o.size, true, true, true); // true=skipCallback/noAnimation/forceResize
|
|
else {
|
|
setSizeLimits(pane);
|
|
makePaneFit(pane, false, true, true); // true=skipCallback/forceResize
|
|
}
|
|
});
|
|
|
|
sizeMidPanes("", true, true); // true=skipCallback/forceResize
|
|
sizeHandles(); // reposition the toggler elements
|
|
|
|
// trigger all individual pane callbacks AFTER layout has finished resizing
|
|
$.each(_c.allPanes, function (i, pane) {
|
|
$P = $Ps[pane];
|
|
if (!$P) return; // SKIP
|
|
if (state[pane].isVisible) // undefined for non-existent panes
|
|
_runCallbacks("onresize_end", pane); // callback - if exists
|
|
});
|
|
|
|
_runCallbacks("onresizeall_end");
|
|
//_triggerLayoutEvent(pane, 'resizeall');
|
|
}
|
|
|
|
/**
|
|
* Whenever a pane resizes or opens that has a nested layout, trigger resizeAll
|
|
*
|
|
* @param {(string|Object)} evt_or_pane The pane just resized or opened
|
|
*/
|
|
, resizeChildren = function (evt_or_pane, skipRefresh) {
|
|
var pane = evtPane.call(this, evt_or_pane);
|
|
|
|
if (!options[pane].resizeChildren) return;
|
|
|
|
// ensure the pane-children are up-to-date
|
|
if (!skipRefresh) refreshChildren( pane );
|
|
var pC = children[pane];
|
|
if ($.isPlainObject( pC )) {
|
|
// resize one or more children
|
|
$.each( pC, function (key, child) {
|
|
if (!child.destroyed) child.resizeAll();
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* IF pane has a content-div, then resize all elements inside pane to fit pane-height
|
|
*
|
|
* @param {(string|Object)} evt_or_panes The pane(s) being resized
|
|
* @param {boolean=} [remeasure=false] Should the content (header/footer) be remeasured?
|
|
*/
|
|
, sizeContent = function (evt_or_panes, remeasure) {
|
|
if (!isInitialized()) return;
|
|
|
|
var panes = evtPane.call(this, evt_or_panes);
|
|
panes = panes ? panes.split(",") : _c.allPanes;
|
|
|
|
$.each(panes, function (idx, pane) {
|
|
var
|
|
$P = $Ps[pane]
|
|
, $C = $Cs[pane]
|
|
, o = options[pane]
|
|
, s = state[pane]
|
|
, m = s.content // m = measurements
|
|
;
|
|
if (!$P || !$C || !$P.is(":visible")) return true; // NOT VISIBLE - skip
|
|
|
|
// if content-element was REMOVED, update OR remove the pointer
|
|
if (!$C.length) {
|
|
initContent(pane, false); // false = do NOT sizeContent() - already there!
|
|
if (!$C) return; // no replacement element found - pointer have been removed
|
|
}
|
|
|
|
// onsizecontent_start will CANCEL resizing if returns false
|
|
if (false === _runCallbacks("onsizecontent_start", pane)) return;
|
|
|
|
// skip re-measuring offsets if live-resizing
|
|
if ((!s.isMoving && !s.isResizing) || o.liveContentResizing || remeasure || m.top == undefined) {
|
|
_measure();
|
|
// if any footers are below pane-bottom, they may not measure correctly,
|
|
// so allow pane overflow and re-measure
|
|
if (m.hiddenFooters > 0 && $P.css("overflow") === "hidden") {
|
|
$P.css("overflow", "visible");
|
|
_measure(); // remeasure while overflowing
|
|
$P.css("overflow", "hidden");
|
|
}
|
|
}
|
|
// NOTE: spaceAbove/Below *includes* the pane paddingTop/Bottom, but not pane.borders
|
|
var newH = s.innerHeight - (m.spaceAbove - s.css.paddingTop) - (m.spaceBelow - s.css.paddingBottom);
|
|
|
|
if (!$C.is(":visible") || m.height != newH) {
|
|
// size the Content element to fit new pane-size - will autoHide if not enough room
|
|
setOuterHeight($C, newH, true); // true=autoHide
|
|
m.height = newH; // save new height
|
|
};
|
|
|
|
if (state.initialized)
|
|
_runCallbacks("onsizecontent_end", pane);
|
|
|
|
function _below ($E) {
|
|
return max(s.css.paddingBottom, (parseInt($E.css("marginBottom"), 10) || 0));
|
|
};
|
|
|
|
function _measure () {
|
|
var
|
|
ignore = options[pane].contentIgnoreSelector
|
|
, $Fs = $C.nextAll().not(".ui-layout-mask").not(ignore || ":lt(0)") // not :lt(0) = ALL
|
|
, $Fs_vis = $Fs.filter(':visible')
|
|
, $F = $Fs_vis.filter(':last')
|
|
;
|
|
m = {
|
|
top: $C[0].offsetTop
|
|
, height: $C.outerHeight()
|
|
, numFooters: $Fs.length
|
|
, hiddenFooters: $Fs.length - $Fs_vis.length
|
|
, spaceBelow: 0 // correct if no content footer ($E)
|
|
}
|
|
m.spaceAbove = m.top; // just for state - not used in calc
|
|
m.bottom = m.top + m.height;
|
|
if ($F.length)
|
|
//spaceBelow = (LastFooter.top + LastFooter.height) [footerBottom] - Content.bottom + max(LastFooter.marginBottom, pane.paddingBotom)
|
|
m.spaceBelow = ($F[0].offsetTop + $F.outerHeight()) - m.bottom + _below($F);
|
|
else // no footer - check marginBottom on Content element itself
|
|
m.spaceBelow = _below($C);
|
|
};
|
|
});
|
|
}
|
|
|
|
|
|
/**
|
|
* Called every time a pane is opened, closed, or resized to slide the togglers to 'center' and adjust their length if necessary
|
|
*
|
|
* @see initHandles(), open(), close(), resizeAll()
|
|
* @param {(string|Object)=} evt_or_panes The pane(s) being resized
|
|
*/
|
|
, sizeHandles = function (evt_or_panes) {
|
|
var panes = evtPane.call(this, evt_or_panes)
|
|
panes = panes ? panes.split(",") : _c.borderPanes;
|
|
|
|
$.each(panes, function (i, pane) {
|
|
var
|
|
o = options[pane]
|
|
, s = state[pane]
|
|
, $P = $Ps[pane]
|
|
, $R = $Rs[pane]
|
|
, $T = $Ts[pane]
|
|
, $TC
|
|
;
|
|
if (!$P || !$R) return;
|
|
|
|
var
|
|
dir = _c[pane].dir
|
|
, _state = (s.isClosed ? "_closed" : "_open")
|
|
, spacing = o["spacing"+ _state]
|
|
, togAlign = o["togglerAlign"+ _state]
|
|
, togLen = o["togglerLength"+ _state]
|
|
, paneLen
|
|
, left
|
|
, offset
|
|
, CSS = {}
|
|
;
|
|
|
|
if (spacing === 0) {
|
|
$R.hide();
|
|
return;
|
|
}
|
|
else if (!s.noRoom && !s.isHidden) // skip if resizer was hidden for any reason
|
|
$R.show(); // in case was previously hidden
|
|
|
|
// Resizer Bar is ALWAYS same width/height of pane it is attached to
|
|
if (dir === "horz") { // north/south
|
|
//paneLen = $P.outerWidth(); // s.outerWidth ||
|
|
paneLen = sC.innerWidth; // handle offscreen-panes
|
|
s.resizerLength = paneLen;
|
|
left = $.layout.cssNum($P, "left")
|
|
$R.css({
|
|
width: cssW($R, paneLen) // account for borders & padding
|
|
, height: cssH($R, spacing) // ditto
|
|
, left: left > -9999 ? left : sC.inset.left // handle offscreen-panes
|
|
});
|
|
}
|
|
else { // east/west
|
|
paneLen = $P.outerHeight(); // s.outerHeight ||
|
|
s.resizerLength = paneLen;
|
|
$R.css({
|
|
height: cssH($R, paneLen) // account for borders & padding
|
|
, width: cssW($R, spacing) // ditto
|
|
, top: sC.inset.top + getPaneSize("north", true) // TODO: what if no North pane?
|
|
//, top: $.layout.cssNum($Ps["center"], "top")
|
|
});
|
|
}
|
|
|
|
// remove hover classes
|
|
removeHover( o, $R );
|
|
|
|
if ($T) {
|
|
if (togLen === 0 || (s.isSliding && o.hideTogglerOnSlide)) {
|
|
$T.hide(); // always HIDE the toggler when 'sliding'
|
|
return;
|
|
}
|
|
else
|
|
$T.show(); // in case was previously hidden
|
|
|
|
if (!(togLen > 0) || togLen === "100%" || togLen > paneLen) {
|
|
togLen = paneLen;
|
|
offset = 0;
|
|
}
|
|
else { // calculate 'offset' based on options.PANE.togglerAlign_open/closed
|
|
if (isStr(togAlign)) {
|
|
switch (togAlign) {
|
|
case "top":
|
|
case "left": offset = 0;
|
|
break;
|
|
case "bottom":
|
|
case "right": offset = paneLen - togLen;
|
|
break;
|
|
case "middle":
|
|
case "center":
|
|
default: offset = round((paneLen - togLen) / 2); // 'default' catches typos
|
|
}
|
|
}
|
|
else { // togAlign = number
|
|
var x = parseInt(togAlign, 10); //
|
|
if (togAlign >= 0) offset = x;
|
|
else offset = paneLen - togLen + x; // NOTE: x is negative!
|
|
}
|
|
}
|
|
|
|
if (dir === "horz") { // north/south
|
|
var width = cssW($T, togLen);
|
|
$T.css({
|
|
width: width // account for borders & padding
|
|
, height: cssH($T, spacing) // ditto
|
|
, left: offset // TODO: VERIFY that toggler positions correctly for ALL values
|
|
, top: 0
|
|
});
|
|
// CENTER the toggler content SPAN
|
|
$T.children(".content").each(function(){
|
|
$TC = $(this);
|
|
$TC.css("marginLeft", round((width-$TC.outerWidth())/2)); // could be negative
|
|
});
|
|
}
|
|
else { // east/west
|
|
var height = cssH($T, togLen);
|
|
$T.css({
|
|
height: height // account for borders & padding
|
|
, width: cssW($T, spacing) // ditto
|
|
, top: offset // POSITION the toggler
|
|
, left: 0
|
|
});
|
|
// CENTER the toggler content SPAN
|
|
$T.children(".content").each(function(){
|
|
$TC = $(this);
|
|
$TC.css("marginTop", round((height-$TC.outerHeight())/2)); // could be negative
|
|
});
|
|
}
|
|
|
|
// remove ALL hover classes
|
|
removeHover( 0, $T );
|
|
}
|
|
|
|
// DONE measuring and sizing this resizer/toggler, so can be 'hidden' now
|
|
if (!state.initialized && (o.initHidden || s.isHidden)) {
|
|
$R.hide();
|
|
if ($T) $T.hide();
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
/**
|
|
* @param {(string|Object)} evt_or_pane
|
|
*/
|
|
, enableClosable = function (evt_or_pane) {
|
|
if (!isInitialized()) return;
|
|
var pane = evtPane.call(this, evt_or_pane)
|
|
, $T = $Ts[pane]
|
|
, o = options[pane]
|
|
;
|
|
if (!$T) return;
|
|
o.closable = true;
|
|
$T .bind("click."+ sID, function(evt){ evt.stopPropagation(); toggle(pane); })
|
|
.css("visibility", "visible")
|
|
.css("cursor", "pointer")
|
|
.attr("title", state[pane].isClosed ? o.tips.Open : o.tips.Close) // may be blank
|
|
.show();
|
|
}
|
|
/**
|
|
* @param {(string|Object)} evt_or_pane
|
|
* @param {boolean=} [hide=false]
|
|
*/
|
|
, disableClosable = function (evt_or_pane, hide) {
|
|
if (!isInitialized()) return;
|
|
var pane = evtPane.call(this, evt_or_pane)
|
|
, $T = $Ts[pane]
|
|
;
|
|
if (!$T) return;
|
|
options[pane].closable = false;
|
|
// is closable is disable, then pane MUST be open!
|
|
if (state[pane].isClosed) open(pane, false, true);
|
|
$T .unbind("."+ sID)
|
|
.css("visibility", hide ? "hidden" : "visible") // instead of hide(), which creates logic issues
|
|
.css("cursor", "default")
|
|
.attr("title", "");
|
|
}
|
|
|
|
|
|
/**
|
|
* @param {(string|Object)} evt_or_pane
|
|
*/
|
|
, enableSlidable = function (evt_or_pane) {
|
|
if (!isInitialized()) return;
|
|
var pane = evtPane.call(this, evt_or_pane)
|
|
, $R = $Rs[pane]
|
|
;
|
|
if (!$R || !$R.data('draggable')) return;
|
|
options[pane].slidable = true;
|
|
if (state[pane].isClosed)
|
|
bindStartSlidingEvents(pane, true);
|
|
}
|
|
/**
|
|
* @param {(string|Object)} evt_or_pane
|
|
*/
|
|
, disableSlidable = function (evt_or_pane) {
|
|
if (!isInitialized()) return;
|
|
var pane = evtPane.call(this, evt_or_pane)
|
|
, $R = $Rs[pane]
|
|
;
|
|
if (!$R) return;
|
|
options[pane].slidable = false;
|
|
if (state[pane].isSliding)
|
|
close(pane, false, true);
|
|
else {
|
|
bindStartSlidingEvents(pane, false);
|
|
$R .css("cursor", "default")
|
|
.attr("title", "");
|
|
removeHover(null, $R[0]); // in case currently hovered
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @param {(string|Object)} evt_or_pane
|
|
*/
|
|
, enableResizable = function (evt_or_pane) {
|
|
if (!isInitialized()) return;
|
|
var pane = evtPane.call(this, evt_or_pane)
|
|
, $R = $Rs[pane]
|
|
, o = options[pane]
|
|
;
|
|
if (!$R || !$R.data('draggable')) return;
|
|
o.resizable = true;
|
|
$R.draggable("enable");
|
|
if (!state[pane].isClosed)
|
|
$R .css("cursor", o.resizerCursor)
|
|
.attr("title", o.tips.Resize);
|
|
}
|
|
/**
|
|
* @param {(string|Object)} evt_or_pane
|
|
*/
|
|
, disableResizable = function (evt_or_pane) {
|
|
if (!isInitialized()) return;
|
|
var pane = evtPane.call(this, evt_or_pane)
|
|
, $R = $Rs[pane]
|
|
;
|
|
if (!$R || !$R.data('draggable')) return;
|
|
options[pane].resizable = false;
|
|
$R .draggable("disable")
|
|
.css("cursor", "default")
|
|
.attr("title", "");
|
|
removeHover(null, $R[0]); // in case currently hovered
|
|
}
|
|
|
|
|
|
/**
|
|
* Move a pane from source-side (eg, west) to target-side (eg, east)
|
|
* If pane exists on target-side, move that to source-side, ie, 'swap' the panes
|
|
*
|
|
* @param {(string|Object)} evt_or_pane1 The pane/edge being swapped
|
|
* @param {string} pane2 ditto
|
|
*/
|
|
, swapPanes = function (evt_or_pane1, pane2) {
|
|
if (!isInitialized()) return;
|
|
var pane1 = evtPane.call(this, evt_or_pane1);
|
|
// change state.edge NOW so callbacks can know where pane is headed...
|
|
state[pane1].edge = pane2;
|
|
state[pane2].edge = pane1;
|
|
// run these even if NOT state.initialized
|
|
if (false === _runCallbacks("onswap_start", pane1)
|
|
|| false === _runCallbacks("onswap_start", pane2)
|
|
) {
|
|
state[pane1].edge = pane1; // reset
|
|
state[pane2].edge = pane2;
|
|
return;
|
|
}
|
|
|
|
var
|
|
oPane1 = copy( pane1 )
|
|
, oPane2 = copy( pane2 )
|
|
, sizes = {}
|
|
;
|
|
sizes[pane1] = oPane1 ? oPane1.state.size : 0;
|
|
sizes[pane2] = oPane2 ? oPane2.state.size : 0;
|
|
|
|
// clear pointers & state
|
|
$Ps[pane1] = false;
|
|
$Ps[pane2] = false;
|
|
state[pane1] = {};
|
|
state[pane2] = {};
|
|
|
|
// ALWAYS remove the resizer & toggler elements
|
|
if ($Ts[pane1]) $Ts[pane1].remove();
|
|
if ($Ts[pane2]) $Ts[pane2].remove();
|
|
if ($Rs[pane1]) $Rs[pane1].remove();
|
|
if ($Rs[pane2]) $Rs[pane2].remove();
|
|
$Rs[pane1] = $Rs[pane2] = $Ts[pane1] = $Ts[pane2] = false;
|
|
|
|
// transfer element pointers and data to NEW Layout keys
|
|
move( oPane1, pane2 );
|
|
move( oPane2, pane1 );
|
|
|
|
// cleanup objects
|
|
oPane1 = oPane2 = sizes = null;
|
|
|
|
// make panes 'visible' again
|
|
if ($Ps[pane1]) $Ps[pane1].css(_c.visible);
|
|
if ($Ps[pane2]) $Ps[pane2].css(_c.visible);
|
|
|
|
// fix any size discrepancies caused by swap
|
|
resizeAll();
|
|
|
|
// run these even if NOT state.initialized
|
|
_runCallbacks("onswap_end", pane1);
|
|
_runCallbacks("onswap_end", pane2);
|
|
|
|
return;
|
|
|
|
function copy (n) { // n = pane
|
|
var
|
|
$P = $Ps[n]
|
|
, $C = $Cs[n]
|
|
;
|
|
return !$P ? false : {
|
|
pane: n
|
|
, P: $P ? $P[0] : false
|
|
, C: $C ? $C[0] : false
|
|
, state: $.extend(true, {}, state[n])
|
|
, options: $.extend(true, {}, options[n])
|
|
}
|
|
};
|
|
|
|
function move (oPane, pane) {
|
|
if (!oPane) return;
|
|
var
|
|
P = oPane.P
|
|
, C = oPane.C
|
|
, oldPane = oPane.pane
|
|
, c = _c[pane]
|
|
// save pane-options that should be retained
|
|
, s = $.extend(true, {}, state[pane])
|
|
, o = options[pane]
|
|
// RETAIN side-specific FX Settings - more below
|
|
, fx = { resizerCursor: o.resizerCursor }
|
|
, re, size, pos
|
|
;
|
|
$.each("fxName,fxSpeed,fxSettings".split(","), function (i, k) {
|
|
fx[k +"_open"] = o[k +"_open"];
|
|
fx[k +"_close"] = o[k +"_close"];
|
|
fx[k +"_size"] = o[k +"_size"];
|
|
});
|
|
|
|
// update object pointers and attributes
|
|
$Ps[pane] = $(P)
|
|
.data({
|
|
layoutPane: Instance[pane] // NEW pointer to pane-alias-object
|
|
, layoutEdge: pane
|
|
})
|
|
.css(_c.hidden)
|
|
.css(c.cssReq)
|
|
;
|
|
$Cs[pane] = C ? $(C) : false;
|
|
|
|
// set options and state
|
|
options[pane] = $.extend(true, {}, oPane.options, fx);
|
|
state[pane] = $.extend(true, {}, oPane.state);
|
|
|
|
// change classNames on the pane, eg: ui-layout-pane-east ==> ui-layout-pane-west
|
|
re = new RegExp(o.paneClass +"-"+ oldPane, "g");
|
|
P.className = P.className.replace(re, o.paneClass +"-"+ pane);
|
|
|
|
// ALWAYS regenerate the resizer & toggler elements
|
|
initHandles(pane); // create the required resizer & toggler
|
|
|
|
// if moving to different orientation, then keep 'target' pane size
|
|
if (c.dir != _c[oldPane].dir) {
|
|
size = sizes[pane] || 0;
|
|
setSizeLimits(pane); // update pane-state
|
|
size = max(size, state[pane].minSize);
|
|
// use manualSizePane to disable autoResize - not useful after panes are swapped
|
|
manualSizePane(pane, size, true, true); // true/true = skipCallback/noAnimation
|
|
}
|
|
else // move the resizer here
|
|
$Rs[pane].css(c.side, sC.inset[c.side] + (state[pane].isVisible ? getPaneSize(pane) : 0));
|
|
|
|
|
|
// ADD CLASSNAMES & SLIDE-BINDINGS
|
|
if (oPane.state.isVisible && !s.isVisible)
|
|
setAsOpen(pane, true); // true = skipCallback
|
|
else {
|
|
setAsClosed(pane);
|
|
bindStartSlidingEvents(pane, true); // will enable events IF option is set
|
|
}
|
|
|
|
// DESTROY the object
|
|
oPane = null;
|
|
};
|
|
}
|
|
|
|
|
|
/**
|
|
* INTERNAL method 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(), setAsOpen(), setAsClosed()
|
|
* @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 (pane, doPin) {
|
|
if ($.layout.plugins.buttons)
|
|
$.each(state[pane].pins, function (i, selector) {
|
|
$.layout.buttons.setPinState(Instance, $(selector), pane, doPin);
|
|
});
|
|
}
|
|
|
|
; // END var DECLARATIONS
|
|
|
|
/**
|
|
* Capture keys when enableCursorHotkey - toggle pane if hotkey pressed
|
|
*
|
|
* @see document.keydown()
|
|
*/
|
|
function keyDown (evt) {
|
|
if (!evt) return true;
|
|
var code = evt.keyCode;
|
|
if (code < 33) return true; // ignore special keys: ENTER, TAB, etc
|
|
|
|
var
|
|
PANE = {
|
|
38: "north" // Up Cursor - $.ui.keyCode.UP
|
|
, 40: "south" // Down Cursor - $.ui.keyCode.DOWN
|
|
, 37: "west" // Left Cursor - $.ui.keyCode.LEFT
|
|
, 39: "east" // Right Cursor - $.ui.keyCode.RIGHT
|
|
}
|
|
, ALT = evt.altKey // no worky!
|
|
, SHIFT = evt.shiftKey
|
|
, CTRL = evt.ctrlKey
|
|
, CURSOR = (CTRL && code >= 37 && code <= 40)
|
|
, o, k, m, pane
|
|
;
|
|
|
|
if (CURSOR && options[PANE[code]].enableCursorHotkey) // valid cursor-hotkey
|
|
pane = PANE[code];
|
|
else if (CTRL || SHIFT) // check to see if this matches a custom-hotkey
|
|
$.each(_c.borderPanes, function (i, p) { // loop each pane to check its hotkey
|
|
o = options[p];
|
|
k = o.customHotkey;
|
|
m = o.customHotkeyModifier; // if missing or invalid, treated as "CTRL+SHIFT"
|
|
if ((SHIFT && m=="SHIFT") || (CTRL && m=="CTRL") || (CTRL && SHIFT)) { // Modifier matches
|
|
if (k && code === (isNaN(k) || k <= 9 ? k.toUpperCase().charCodeAt(0) : k)) { // Key matches
|
|
pane = p;
|
|
return false; // BREAK
|
|
}
|
|
}
|
|
});
|
|
|
|
// validate pane
|
|
if (!pane || !$Ps[pane] || !options[pane].closable || state[pane].isHidden)
|
|
return true;
|
|
|
|
toggle(pane);
|
|
|
|
evt.stopPropagation();
|
|
evt.returnValue = false; // CANCEL key
|
|
return false;
|
|
};
|
|
|
|
|
|
/*
|
|
* ######################################
|
|
* UTILITY METHODS
|
|
* called externally or by initButtons
|
|
* ######################################
|
|
*/
|
|
|
|
/**
|
|
* Change/reset a pane overflow setting & zIndex to allow popups/drop-downs to work
|
|
*
|
|
* @param {Object=} [el] (optional) Can also be 'bound' to a click, mouseOver, or other event
|
|
*/
|
|
function allowOverflow (el) {
|
|
if (!isInitialized()) return;
|
|
if (this && this.tagName) el = this; // BOUND to element
|
|
var $P;
|
|
if (isStr(el))
|
|
$P = $Ps[el];
|
|
else if ($(el).data("layoutRole"))
|
|
$P = $(el);
|
|
else
|
|
$(el).parents().each(function(){
|
|
if ($(this).data("layoutRole")) {
|
|
$P = $(this);
|
|
return false; // BREAK
|
|
}
|
|
});
|
|
if (!$P || !$P.length) return; // INVALID
|
|
|
|
var
|
|
pane = $P.data("layoutEdge")
|
|
, s = state[pane]
|
|
;
|
|
|
|
// if pane is already raised, then reset it before doing it again!
|
|
// this would happen if allowOverflow is attached to BOTH the pane and an element
|
|
if (s.cssSaved)
|
|
resetOverflow(pane); // reset previous CSS before continuing
|
|
|
|
// if pane is raised by sliding or resizing, or its closed, then abort
|
|
if (s.isSliding || s.isResizing || s.isClosed) {
|
|
s.cssSaved = false;
|
|
return;
|
|
}
|
|
|
|
var
|
|
newCSS = { zIndex: (options.zIndexes.resizer_normal + 1) }
|
|
, curCSS = {}
|
|
, of = $P.css("overflow")
|
|
, ofX = $P.css("overflowX")
|
|
, ofY = $P.css("overflowY")
|
|
;
|
|
// determine which, if any, overflow settings need to be changed
|
|
if (of != "visible") {
|
|
curCSS.overflow = of;
|
|
newCSS.overflow = "visible";
|
|
}
|
|
if (ofX && !ofX.match(/(visible|auto)/)) {
|
|
curCSS.overflowX = ofX;
|
|
newCSS.overflowX = "visible";
|
|
}
|
|
if (ofY && !ofY.match(/(visible|auto)/)) {
|
|
curCSS.overflowY = ofX;
|
|
newCSS.overflowY = "visible";
|
|
}
|
|
|
|
// save the current overflow settings - even if blank!
|
|
s.cssSaved = curCSS;
|
|
|
|
// apply new CSS to raise zIndex and, if necessary, make overflow 'visible'
|
|
$P.css( newCSS );
|
|
|
|
// make sure the zIndex of all other panes is normal
|
|
$.each(_c.allPanes, function(i, p) {
|
|
if (p != pane) resetOverflow(p);
|
|
});
|
|
|
|
};
|
|
/**
|
|
* @param {Object=} [el] (optional) Can also be 'bound' to a click, mouseOver, or other event
|
|
*/
|
|
function resetOverflow (el) {
|
|
if (!isInitialized()) return;
|
|
if (this && this.tagName) el = this; // BOUND to element
|
|
var $P;
|
|
if (isStr(el))
|
|
$P = $Ps[el];
|
|
else if ($(el).data("layoutRole"))
|
|
$P = $(el);
|
|
else
|
|
$(el).parents().each(function(){
|
|
if ($(this).data("layoutRole")) {
|
|
$P = $(this);
|
|
return false; // BREAK
|
|
}
|
|
});
|
|
if (!$P || !$P.length) return; // INVALID
|
|
|
|
var
|
|
pane = $P.data("layoutEdge")
|
|
, s = state[pane]
|
|
, CSS = s.cssSaved || {}
|
|
;
|
|
// reset the zIndex
|
|
if (!s.isSliding && !s.isResizing)
|
|
$P.css("zIndex", options.zIndexes.pane_normal);
|
|
|
|
// reset Overflow - if necessary
|
|
$P.css( CSS );
|
|
|
|
// clear var
|
|
s.cssSaved = false;
|
|
};
|
|
|
|
/*
|
|
* #####################
|
|
* CREATE/RETURN LAYOUT
|
|
* #####################
|
|
*/
|
|
|
|
// validate that container exists
|
|
var $N = $(this).eq(0); // FIRST matching Container element
|
|
if (!$N.length) {
|
|
return _log( options.errors.containerMissing );
|
|
};
|
|
|
|
// Users retrieve Instance of a layout with: $N.layout() OR $N.data("layout")
|
|
// return the Instance-pointer if layout has already been initialized
|
|
if ($N.data("layoutContainer") && $N.data("layout"))
|
|
return $N.data("layout"); // cached pointer
|
|
|
|
// init global vars
|
|
var
|
|
$Ps = {} // Panes x5 - set in initPanes()
|
|
, $Cs = {} // Content x5 - set in initPanes()
|
|
, $Rs = {} // Resizers x4 - set in initHandles()
|
|
, $Ts = {} // Togglers x4 - set in initHandles()
|
|
, $Ms = $([]) // Masks - up to 2 masks per pane (IFRAME + DIV)
|
|
// aliases for code brevity
|
|
, sC = state.container // alias for easy access to 'container dimensions'
|
|
, sID = state.id // alias for unique layout ID/namespace - eg: "layout435"
|
|
;
|
|
|
|
// create Instance object to expose data & option Properties, and primary action Methods
|
|
var Instance = {
|
|
// layout data
|
|
options: options // property - options hash
|
|
, state: state // property - dimensions hash
|
|
// object pointers
|
|
, container: $N // property - object pointers for layout container
|
|
, panes: $Ps // property - object pointers for ALL Panes: panes.north, panes.center
|
|
, contents: $Cs // property - object pointers for ALL Content: contents.north, contents.center
|
|
, resizers: $Rs // property - object pointers for ALL Resizers, eg: resizers.north
|
|
, togglers: $Ts // property - object pointers for ALL Togglers, eg: togglers.north
|
|
// border-pane open/close
|
|
, hide: hide // method - ditto
|
|
, show: show // method - ditto
|
|
, toggle: toggle // method - pass a 'pane' ("north", "west", etc)
|
|
, open: open // method - ditto
|
|
, close: close // method - ditto
|
|
, slideOpen: slideOpen // method - ditto
|
|
, slideClose: slideClose // method - ditto
|
|
, slideToggle: slideToggle // method - ditto
|
|
// pane actions
|
|
, setSizeLimits: setSizeLimits // method - pass a 'pane' - update state min/max data
|
|
, _sizePane: sizePane // method -intended for user by plugins only!
|
|
, sizePane: manualSizePane // method - pass a 'pane' AND an 'outer-size' in pixels or percent, or 'auto'
|
|
, sizeContent: sizeContent // method - pass a 'pane'
|
|
, swapPanes: swapPanes // method - pass TWO 'panes' - will swap them
|
|
, showMasks: showMasks // method - pass a 'pane' OR list of panes - default = all panes with mask option set
|
|
, hideMasks: hideMasks // method - ditto'
|
|
// pane element methods
|
|
, initContent: initContent // method - ditto
|
|
, addPane: addPane // method - pass a 'pane'
|
|
, removePane: removePane // method - pass a 'pane' to remove from layout, add 'true' to delete the pane-elem
|
|
, createChildren: createChildren // method - pass a 'pane' and (optional) layout-options (OVERRIDES options[pane].children
|
|
, refreshChildren: refreshChildren // method - pass a 'pane' and a layout-instance
|
|
// special pane option setting
|
|
, enableClosable: enableClosable // method - pass a 'pane'
|
|
, disableClosable: disableClosable // method - ditto
|
|
, enableSlidable: enableSlidable // method - ditto
|
|
, disableSlidable: disableSlidable // method - ditto
|
|
, enableResizable: enableResizable // method - ditto
|
|
, disableResizable: disableResizable// method - ditto
|
|
// utility methods for panes
|
|
, allowOverflow: allowOverflow // utility - pass calling element (this)
|
|
, resetOverflow: resetOverflow // utility - ditto
|
|
// layout control
|
|
, destroy: destroy // method - no parameters
|
|
, initPanes: isInitialized // method - no parameters
|
|
, resizeAll: resizeAll // method - no parameters
|
|
// callback triggering
|
|
, runCallbacks: _runCallbacks // method - pass evtName & pane (if a pane-event), eg: trigger("onopen", "west")
|
|
// alias collections of options, state and children - created in addPane and extended elsewhere
|
|
, hasParentLayout: false // set by initContainer()
|
|
, children: children // pointers to child-layouts, eg: Instance.children.west.layoutName
|
|
, north: false // alias group: { name: pane, pane: $Ps[pane], options: options[pane], state: state[pane], children: children[pane] }
|
|
, south: false // ditto
|
|
, west: false // ditto
|
|
, east: false // ditto
|
|
, center: false // ditto
|
|
};
|
|
|
|
// create the border layout NOW
|
|
if (_create() === 'cancel') // onload_start callback returned false to CANCEL layout creation
|
|
return null;
|
|
else // true OR false -- if layout-elements did NOT init (hidden or do not exist), can auto-init later
|
|
return Instance; // return the Instance object
|
|
|
|
}
|
|
|
|
|
|
})( jQuery );
|
|
// END Layout - keep internal vars internal!
|
|
|
|
|
|
|
|
// START Plugins - shared wrapper, no global vars
|
|
(function ($) {
|
|
|
|
|
|
/**
|
|
* jquery.layout.state 1.0
|
|
* $Date: 2011-07-16 08:00:00 (Sat, 16 July 2011) $
|
|
*
|
|
* Copyright (c) 2012
|
|
* 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.3.0.rc30.1 or higher
|
|
* @requires: $.ui.cookie (above)
|
|
*
|
|
* @see: 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
|
|
*
|
|
* 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 // 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 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);
|
|
};
|
|
|
|
|
|
// tell Layout that the state plugin is available
|
|
$.layout.plugins.stateManagement = true;
|
|
|
|
// Add State-Management options to layout.defaults
|
|
$.layout.config.optionRootKeys.push("stateManagement");
|
|
$.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");
|
|
|
|
/*
|
|
* 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) {
|
|
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) {
|
|
return parse(JSON);
|
|
function parse (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 _ = $.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 _.readCookie(inst); }
|
|
// deleteCookie
|
|
, deleteCookie: function () { _.deleteCookie(inst); }
|
|
// saveCookie - optionally pass keys-list and cookie-options (hash)
|
|
, saveCookie: function (keys, cookieOpts) { return _.saveCookie(inst, keys, cookieOpts); }
|
|
// loadCookie - readCookie and use to loadState() - returns hash of cookie data
|
|
, loadCookie: function () { return _.loadCookie(inst); }
|
|
// loadState - pass a hash of state to use to update options
|
|
, loadState: function (stateData, opts) { _.loadState(inst, stateData, opts); }
|
|
// readState - returns hash of current layout-state
|
|
, readState: function (keys) { return _.readState(inst, keys); }
|
|
// add JSON utility methods too...
|
|
, encodeJSON: _.encodeJSON
|
|
, decodeJSON: _.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.layout.buttons 1.0
|
|
* $Date: 2011-07-16 08:00:00 (Sat, 16 July 2011) $
|
|
*
|
|
* Copyright (c) 2012
|
|
* 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.3.0.rc30.1 or higher
|
|
*
|
|
* @see: 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 = {
|
|
|
|
/**
|
|
* Searches for .ui-layout-button-xxx elements and auto-binds them as layout-buttons
|
|
*
|
|
* @see _create()
|
|
*
|
|
* @param {Object} inst Layout Instance object
|
|
*/
|
|
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.config.borderPanes, 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 // action btnClass
|
|
* - ui-layout-button-pin-west // action btnClass + pane
|
|
* - ui-layout-button-toggle
|
|
* - ui-layout-button-open
|
|
* - ui-layout-button-close
|
|
*
|
|
* @param {Object} inst Layout Instance object
|
|
* @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.errors.addButtonError
|
|
;
|
|
if (!$E.length) { // element not found
|
|
$.layout.msg(err +" "+ o.errors.selector +": "+ selector, true);
|
|
}
|
|
else if ($.inArray(pane, $.layout.config.borderPanes) < 0) { // invalid 'pane' sepecified
|
|
$.layout.msg(err +" "+ o.errors.pane +": "+ pane, true);
|
|
$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 {Object} inst Layout Instance object
|
|
* @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button"
|
|
* @param {string} action
|
|
* @param {string} pane
|
|
*/
|
|
, bind: function (inst, selector, action, pane) {
|
|
var _ = $.layout.buttons;
|
|
switch (action.toLowerCase()) {
|
|
case "toggle": _.addToggle (inst, selector, pane); break;
|
|
case "open": _.addOpen (inst, selector, pane); break;
|
|
case "close": _.addClose (inst, selector, pane); break;
|
|
case "pin": _.addPin (inst, selector, pane); break;
|
|
case "toggle-slide": _.addToggle (inst, selector, pane, true); break;
|
|
case "open-slide": _.addOpen (inst, selector, pane, true); break;
|
|
}
|
|
return inst;
|
|
}
|
|
|
|
/**
|
|
* Add a custom Toggler button for a pane
|
|
*
|
|
* @param {Object} inst Layout Instance object
|
|
* @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 {Object} inst Layout Instance object
|
|
* @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", inst.options[pane].tips.Open)
|
|
.click(function (evt) {
|
|
inst.open(pane, !!slide);
|
|
evt.stopPropagation();
|
|
});
|
|
return inst;
|
|
}
|
|
|
|
/**
|
|
* Add a custom Close button for a pane
|
|
*
|
|
* @param {Object} inst Layout Instance object
|
|
* @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", inst.options[pane].tips.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 {Object} inst Layout Instance object
|
|
* @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 _ = $.layout.buttons
|
|
, $E = _.get(inst, selector, pane, "pin");
|
|
if ($E.length) {
|
|
var s = inst.state[pane];
|
|
$E.click(function (evt) {
|
|
_.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
|
|
_.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 {Object} inst Layout Instance object
|
|
* @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
|
|
o = inst.options[pane]
|
|
, pin = o.buttonClass +"-pin"
|
|
, side = pin +"-"+ pane
|
|
, UP = pin +"-up "+ side +"-up"
|
|
, DN = pin +"-down "+side +"-down"
|
|
;
|
|
$Pin
|
|
.attr("pin", doPin ? "down" : "up") // logic
|
|
.attr("title", doPin ? o.tips.Unpin : o.tips.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 {Object} inst Layout Instance object
|
|
* @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(inst.state[pane].pins, function (i, selector) {
|
|
$.layout.buttons.setPinState(inst, $(selector), pane, doPin);
|
|
});
|
|
}
|
|
|
|
|
|
, _load: function (inst) {
|
|
var _ = $.layout.buttons;
|
|
// ADD Button methods to Layout Instance
|
|
// Note: sel = jQuery Selector string
|
|
$.extend( inst, {
|
|
bindButton: function (sel, action, pane) { return _.bind(inst, sel, action, pane); }
|
|
// DEPRECATED METHODS
|
|
, addToggleBtn: function (sel, pane, slide) { return _.addToggle(inst, sel, pane, slide); }
|
|
, addOpenBtn: function (sel, pane, slide) { return _.addOpen(inst, sel, pane, slide); }
|
|
, addCloseBtn: function (sel, pane) { return _.addClose(inst, sel, pane); }
|
|
, addPinBtn: function (sel, pane) { return _.addPin(inst, sel, pane); }
|
|
});
|
|
|
|
// init state array to hold pin-buttons
|
|
for (var i=0; i<4; i++) {
|
|
var pane = $.layout.config.borderPanes[i];
|
|
inst.state[pane].pins = [];
|
|
}
|
|
|
|
// auto-init buttons onLoad if option is enabled
|
|
if ( inst.options.autoBindCustomButtons )
|
|
_.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.layout.browserZoom 1.0
|
|
* $Date: 2011-12-29 08:00:00 (Thu, 29 Dec 2011) $
|
|
*
|
|
* Copyright (c) 2012
|
|
* 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.3.0.rc30.1 or higher
|
|
*
|
|
* @see: http://groups.google.com/group/jquery-ui-layout
|
|
*
|
|
* TODO: Extend logic to handle other problematic zooming in browsers
|
|
* TODO: Add hotkey/mousewheel bindings to _instantly_ respond to these zoom event
|
|
*/
|
|
|
|
// tell Layout that the plugin is available
|
|
$.layout.plugins.browserZoom = true;
|
|
|
|
$.layout.defaults.browserZoomCheckInterval = 1000;
|
|
$.layout.optionsMap.layout.push("browserZoomCheckInterval");
|
|
|
|
/*
|
|
* browserZoom methods
|
|
*/
|
|
$.layout.browserZoom = {
|
|
|
|
_init: function (inst) {
|
|
// abort if browser does not need this check
|
|
if ($.layout.browserZoom.ratio() !== false)
|
|
$.layout.browserZoom._setTimer(inst);
|
|
}
|
|
|
|
, _setTimer: function (inst) {
|
|
// abort if layout destroyed or browser does not need this check
|
|
if (inst.destroyed) return;
|
|
var o = inst.options
|
|
, s = inst.state
|
|
// don't need check if inst has parentLayout, but check occassionally in case parent destroyed!
|
|
// MINIMUM 100ms interval, for performance
|
|
, ms = inst.hasParentLayout ? 5000 : Math.max( o.browserZoomCheckInterval, 100 )
|
|
;
|
|
// set the timer
|
|
setTimeout(function(){
|
|
if (inst.destroyed || !o.resizeWithWindow) return;
|
|
var d = $.layout.browserZoom.ratio();
|
|
if (d !== s.browserZoom) {
|
|
s.browserZoom = d;
|
|
inst.resizeAll();
|
|
}
|
|
// set a NEW timeout
|
|
$.layout.browserZoom._setTimer(inst);
|
|
}
|
|
, ms );
|
|
}
|
|
|
|
, ratio: function () {
|
|
var w = window
|
|
, s = screen
|
|
, d = document
|
|
, dE = d.documentElement || d.body
|
|
, b = $.layout.browser
|
|
, v = b.version
|
|
, r, sW, cW
|
|
;
|
|
// we can ignore all browsers that fire window.resize event onZoom
|
|
if ((b.msie && v > 8)
|
|
|| !b.msie
|
|
) return false; // don't need to track zoom
|
|
|
|
if (s.deviceXDPI && s.systemXDPI) // syntax compiler hack
|
|
return calc(s.deviceXDPI, s.systemXDPI);
|
|
// everything below is just for future reference!
|
|
if (b.webkit && (r = d.body.getBoundingClientRect))
|
|
return calc((r.left - r.right), d.body.offsetWidth);
|
|
if (b.webkit && (sW = w.outerWidth))
|
|
return calc(sW, w.innerWidth);
|
|
if ((sW = s.width) && (cW = dE.clientWidth))
|
|
return calc(sW, cW);
|
|
return false; // no match, so cannot - or don't need to - track zoom
|
|
|
|
function calc (x,y) { return (parseInt(x,10) / parseInt(y,10) * 100).toFixed(); }
|
|
}
|
|
|
|
};
|
|
// add initialization method to Layout's onLoad array of functions
|
|
$.layout.onReady.push( $.layout.browserZoom._init );
|
|
|
|
|
|
})( jQuery );
|