/**
* @description <p>Provides a fixed position unit containing a header, body and footer for use with a YAHOO.widget.Layout instance.</p>
* @namespace YAHOO.widget
* @requires yahoo, dom, element, event, layout
* @optional animation, dragdrop, selector
* @beta
*/
(function() {
var Dom = YAHOO.util.Dom,
Sel = YAHOO.util.Selector,
Event = YAHOO.util.Event,
Lang = YAHOO.lang;
/**
* @constructor
* @class LayoutUnit
* @extends YAHOO.util.Element
* @description <p>Provides a fixed position unit containing a header, body and footer for use with a YAHOO.widget.Layout instance.</p>
* @param {String/HTMLElement} el The element to make a unit.
* @param {Object} attrs Object liternal containing configuration parameters.
*/
var LayoutUnit = function(el, config) {
var oConfig = {
element: el,
attributes: config || {}
};
LayoutUnit.superclass.constructor.call(this, oConfig.element, oConfig.attributes);
};
/**
* @private
* @static
* @property _instances
* @description Internal hash table for all layout unit instances
* @type Object
*/
LayoutUnit._instances = {};
/**
* @static
* @method getLayoutUnitById
* @description Get's a layout unit object by the HTML id of the element associated with the Layout Unit object.
* @return {Object} The Layout Object
*/
LayoutUnit.getLayoutUnitById = function(id) {
if (LayoutUnit._instances[id]) {
return LayoutUnit._instances[id];
}
return false;
};
YAHOO.extend(LayoutUnit, YAHOO.util.Element, {
/**
* @property STR_CLOSE
* @description String used for close button title
* @type {String}
*/
STR_CLOSE: 'Click to close this pane.',
/**
* @property STR_COLLAPSE
* @description String used for collapse button title
* @type {String}
*/
STR_COLLAPSE: 'Click to collapse this pane.',
/**
* @property STR_EXPAND
* @description String used for expand button title
* @type {String}
*/
STR_EXPAND: 'Click to expand this pane.',
/**
* @property browser
* @description A modified version of the YAHOO.env.ua object
* @type Object
*/
browser: null,
/**
* @private
* @property _sizes
* @description A collection of the current sizes of the contents of this Layout Unit
* @type Object
*/
_sizes: null,
/**
* @private
* @property _anim
* @description A reference to the Animation instance used by this LayouUnit
* @type YAHOO.util.Anim
*/
_anim: null,
/**
* @private
* @property _resize
* @description A reference to the Resize instance used by this LayoutUnit
* @type YAHOO.util.Resize
*/
_resize: null,
/**
* @private
* @property _clip
* @description A reference to the clip element used when collapsing the unit
* @type HTMLElement
*/
_clip: null,
/**
* @private
* @property _gutter
* @description A simple hash table used to store the gutter to apply to the Unit
* @type Object
*/
_gutter: null,
/**
* @property header
* @description A reference to the HTML element used for the Header
* @type HTMLELement
*/
header: null,
/**
* @property body
* @description A reference to the HTML element used for the body
* @type HTMLElement
*/
body: null,
/**
* @property footer
* @description A reference to the HTML element used for the footer
* @type HTMLElement
*/
footer: null,
/**
* @private
* @property _collapsed
* @description Flag to determine if the unit is collapsed or not.
* @type Boolean
*/
_collapsed: null,
/**
* @private
* @property _collapsing
* @description A flag set while the unit is being collapsed, used so we don't fire events while animating the size
* @type Boolean
*/
_collapsing: null,
/**
* @private
* @property _lastWidth
* @description A holder for the last known width of the unit
* @type Number
*/
_lastWidth: null,
/**
* @private
* @property _lastHeight
* @description A holder for the last known height of the unit
* @type Number
*/
_lastHeight: null,
/**
* @private
* @property _lastTop
* @description A holder for the last known top of the unit
* @type Number
*/
_lastTop: null,
/**
* @private
* @property _lastLeft
* @description A holder for the last known left of the unit
* @type Number
*/
_lastLeft: null,
/**
* @private
* @property _lastScroll
* @description A holder for the last known scroll state of the unit
* @type Boolean
*/
_lastScroll: null,
/**
* @private
* @property _lastCenetrScroll
* @description A holder for the last known scroll state of the center unit
* @type Boolean
*/
_lastCenterScroll: null,
/**
* @private
* @property _lastScrollTop
* @description A holder for the last known scrollTop state of the unit
* @type Number
*/
_lastScrollTop: null,
/**
* @method resize
* @description Resize either the unit or it's clipped state, also updating the box inside
* @param {Boolean} force This will force full calculations even when the unit is collapsed
* @return {<a href="YAHOO.widget.LayoutUnit.html">YAHOO.widget.LayoutUnit</a>} The LayoutUnit instance
*/
resize: function(force) {
YAHOO.log('Resize', 'info', 'LayoutUnit');
var retVal = this.fireEvent('beforeResize');
if (retVal === false) {
return this;
}
if (!this._collapsing || (force === true)) {
var scroll = this.get('scroll');
this.set('scroll', false);
var hd = this._getBoxSize(this.header),
ft = this._getBoxSize(this.footer),
box = [this.get('height'), this.get('width')];
var nh = (box[0] - hd[0] - ft[0]) - (this._gutter.top + this._gutter.bottom),
nw = box[1] - (this._gutter.left + this._gutter.right);
var wrapH = (nh + (hd[0] + ft[0])),
wrapW = nw;
if (this._collapsed && !this._collapsing) {
this._setHeight(this._clip, wrapH);
this._setWidth(this._clip, wrapW);
Dom.setStyle(this._clip, 'top', this.get('top') + this._gutter.top + 'px');
Dom.setStyle(this._clip, 'left', this.get('left') + this._gutter.left + 'px');
} else if (!this._collapsed || (this._collapsed && this._collapsing)) {
wrapH = this._setHeight(this.get('wrap'), wrapH);
wrapW = this._setWidth(this.get('wrap'), wrapW);
this._sizes.wrap.h = wrapH;
this._sizes.wrap.w = wrapW;
Dom.setStyle(this.get('wrap'), 'top', this._gutter.top + 'px');
Dom.setStyle(this.get('wrap'), 'left', this._gutter.left + 'px');
this._sizes.header.w = this._setWidth(this.header, wrapW);
this._sizes.header.h = hd[0];
this._sizes.footer.w = this._setWidth(this.footer, wrapW);
this._sizes.footer.h = ft[0];
Dom.setStyle(this.footer, 'bottom', '0px');
this._sizes.body.h = this._setHeight(this.body, (wrapH - (hd[0] + ft[0])));
this._sizes.body.w =this._setWidth(this.body, wrapW);
Dom.setStyle(this.body, 'top', hd[0] + 'px');
this.set('scroll', scroll);
this.fireEvent('resize');
}
}
return this;
},
/**
* @private
* @method _setWidth
* @description Sets the width of the element based on the border size of the element.
* @param {HTMLElement} el The HTMLElement to have it's width set
* @param {Number} w The width that you want it the element set to
* @return {Number} The new width, fixed for borders and IE QuirksMode
*/
_setWidth: function(el, w) {
if (el) {
var b = this._getBorderSizes(el);
w = (w - (b[1] + b[3]));
w = this._fixQuirks(el, w, 'w');
Dom.setStyle(el, 'width', w + 'px');
}
return w;
},
/**
* @private
* @method _setHeight
* @description Sets the height of the element based on the border size of the element.
* @param {HTMLElement} el The HTMLElement to have it's height set
* @param {Number} h The height that you want it the element set to
* @return {Number} The new height, fixed for borders and IE QuirksMode
*/
_setHeight: function(el, h) {
if (el) {
var b = this._getBorderSizes(el);
h = (h - (b[0] + b[2]));
h = this._fixQuirks(el, h, 'h');
Dom.setStyle(el, 'height', h + 'px');
}
return h;
},
/**
* @private
* @method _fixQuirks
* @description Fixes the box calculations for IE in QuirksMode
* @param {HTMLElement} el The HTMLElement to set the dimension on
* @param {Number} dim The number of the dimension to fix
* @param {String} side The dimension (h or w) to fix. Defaults to h
* @return {Number} The fixed dimension
*/
_fixQuirks: function(el, dim, side) {
var i1 = 0, i2 = 2;
if (side == 'w') {
i1 = 1;
i2 = 3;
}
if (this.browser.ie && !this.browser.standardsMode) {
//Internet Explorer - Quirks Mode
var b = this._getBorderSizes(el),
bp = this._getBorderSizes(el.parentNode);
if ((b[i1] === 0) && (b[i2] === 0)) { //No Borders, check parent
if ((bp[i1] !== 0) && (bp[i2] !== 0)) { //Parent has Borders
dim = (dim - (bp[i1] + bp[i2]));
}
} else {
if ((bp[i1] === 0) && (bp[i2] === 0)) {
dim = (dim + (b[i1] + b[i2]));
}
}
}
return dim;
},
/**
* @private
* @method _getBoxSize
* @description Get's the elements clientHeight and clientWidth plus the size of the borders
* @param {HTMLElement} el The HTMLElement to get the size of
* @return {Array} An array of height and width
*/
_getBoxSize: function(el) {
var size = [0, 0];
if (el) {
if (this.browser.ie && !this.browser.standardsMode) {
el.style.zoom = 1;
}
var b = this._getBorderSizes(el);
size[0] = el.clientHeight + (b[0] + b[2]);
size[1] = el.clientWidth + (b[1] + b[3]);
}
return size;
},
/**
* @private
* @method _getBorderSizes
* @description Get the CSS border size of the element passed.
* @param {HTMLElement} el The element to get the border size of
* @return {Array} An array of the top, right, bottom, left borders.
*/
_getBorderSizes: function(el) {
var s = [];
el = el || this.get('element');
if (this.browser.ie && !this.browser.standardsMode) {
el.style.zoom = 1;
}
s[0] = parseInt(Dom.getStyle(el, 'borderTopWidth'), 10);
s[1] = parseInt(Dom.getStyle(el, 'borderRightWidth'), 10);
s[2] = parseInt(Dom.getStyle(el, 'borderBottomWidth'), 10);
s[3] = parseInt(Dom.getStyle(el, 'borderLeftWidth'), 10);
//IE will return NaN on these if they are set to auto, we'll set them to 0
for (var i = 0; i < s.length; i++) {
if (isNaN(s[i])) {
s[i] = 0;
}
}
return s;
},
/**
* @private
* @method _createClip
* @description Create the clip element used when the Unit is collapsed
*/
_createClip: function() {
if (!this._clip) {
this._clip = document.createElement('div');
this._clip.className = 'yui-layout-clip yui-layout-clip-' + this.get('position');
this._clip.innerHTML = '<div class="collapse"></div>';
var c = this._clip.firstChild;
c.title = this.STR_EXPAND;
Event.on(c, 'click', this.expand, this, true);
this.get('element').parentNode.appendChild(this._clip);
}
},
/**
* @private
* @method _toggleClip
* @description Toggle th current state of the Clip element and set it's height, width and position
*/
_toggleClip: function() {
if (!this._collapsed) {
//show
var hd = this._getBoxSize(this.header),
ft = this._getBoxSize(this.footer),
box = [this.get('height'), this.get('width')];
var nh = (box[0] - hd[0] - ft[0]) - (this._gutter.top + this._gutter.bottom),
nw = box[1] - (this._gutter.left + this._gutter.right),
wrapH = (nh + (hd[0] + ft[0]));
switch (this.get('position')) {
case 'top':
case 'bottom':
this._setWidth(this._clip, nw);
this._setHeight(this._clip, this.get('collapseSize'));
Dom.setStyle(this._clip, 'left', (this._lastLeft + this._gutter.left) + 'px');
if (this.get('position') == 'bottom') {
Dom.setStyle(this._clip, 'top', ((this._lastTop + this._lastHeight) - (this.get('collapseSize') - this._gutter.top)) + 'px');
} else {
Dom.setStyle(this._clip, 'top', this.get('top') + this._gutter.top + 'px');
}
break;
case 'left':
case 'right':
this._setWidth(this._clip, this.get('collapseSize'));
this._setHeight(this._clip, wrapH);
Dom.setStyle(this._clip, 'top', (this.get('top') + this._gutter.top) + 'px');
if (this.get('position') == 'right') {
Dom.setStyle(this._clip, 'left', (((this._lastLeft + this._lastWidth) - this.get('collapseSize')) - this._gutter.left) + 'px');
} else {
Dom.setStyle(this._clip, 'left', (this.get('left') + this._gutter.left) + 'px');
}
break;
}
Dom.setStyle(this._clip, 'display', 'block');
this.setStyle('display', 'none');
} else {
//Hide
Dom.setStyle(this._clip, 'display', 'none');
}
},
/**
* @method getSizes
* @description Get a reference to the internal sizes object for this unit
* @return {Object} An object of the sizes used for calculations
*/
getSizes: function() {
return this._sizes;
},
/**
* @method toggle
* @description Toggles the Unit, replacing it with a clipped version.
* @return {<a href="YAHOO.widget.LayoutUnit.html">YAHOO.widget.LayoutUnit</a>} The LayoutUnit instance
*/
toggle: function() {
if (this._collapsed) {
this.expand();
} else {
this.collapse();
}
return this;
},
/**
* @method expand
* @description Expand the Unit if it is collapsed.
* @return {<a href="YAHOO.widget.LayoutUnit.html">YAHOO.widget.LayoutUnit</a>} The LayoutUnit instance
*/
expand: function() {
if (!this._collapsed) {
return this;
}
var retVal = this.fireEvent('beforeExpand');
if (retVal === false) {
return this;
}
this._collapsing = true;
this.setStyle('zIndex', this.get('parent')._zIndex + 1);
if (this._anim) {
this.setStyle('display', 'none');
//Animation Fails Here
var attr = {}, s;
switch (this.get('position')) {
case 'left':
case 'right':
this.set('width', this._lastWidth, true);
this.setStyle('width', this._lastWidth + 'px');
this.get('parent').resize(false);
s = this.get('parent').getSizes()[this.get('position')];
this.set('height', s.h, true);
var left = s.l;
attr = {
left: {
to: left
}
};
if (this.get('position') == 'left') {
attr.left.from = (left - s.w);
this.setStyle('left', (left - s.w) + 'px');
}
break;
case 'top':
case 'bottom':
this.set('height', this._lastHeight, true);
this.setStyle('height', this._lastHeight + 'px');
this.get('parent').resize(false);
s = this.get('parent').getSizes()[this.get('position')];
this.set('width', s.w, true);
var top = s.t;
attr = {
top: {
to: top
}
};
if (this.get('position') == 'top') {
this.setStyle('top', (top - s.h) + 'px');
attr.top.from = (top - s.h);
}
break;
}
this._anim.attributes = attr;
var exStart = function() {
this.setStyle('display', 'block');
this.resize(true);
this._anim.onStart.unsubscribe(exStart, this, true);
};
var expand = function() {
this._collapsing = false;
this.setStyle('zIndex', this.get('parent')._zIndex);
this.set('width', this._lastWidth);
this.set('height', this._lastHeight);
this._collapsed = false;
this.resize();
this.set('scroll', this._lastScroll);
if (this._lastScrollTop > 0) {
this.body.scrollTop = this._lastScrollTop;
}
this._anim.onComplete.unsubscribe(expand, this, true);
this.fireEvent('expand');
};
this._anim.onStart.subscribe(exStart, this, true);
this._anim.onComplete.subscribe(expand, this, true);
this._anim.animate();
this._toggleClip();
} else {
this._collapsing = false;
this._toggleClip();
this.setStyle('zIndex', this.get('parent')._zIndex);
this.setStyle('display', 'block');
this.set('width', this._lastWidth);
this.set('height', this._lastHeight);
this._collapsed = false;
this.resize();
this.set('scroll', this._lastScroll);
if (this._lastScrollTop > 0) {
this.body.scrollTop = this._lastScrollTop;
}
this.fireEvent('expand');
}
return this;
},
/**
* @method collapse
* @description Collapse the Unit if it is not collapsed.
* @return {<a href="YAHOO.widget.LayoutUnit.html">YAHOO.widget.LayoutUnit</a>} The LayoutUnit instance
*/
collapse: function() {
if (this._collapsed) {
return this;
}
var retValue = this.fireEvent('beforeCollapse');
if (retValue === false) {
return this;
}
if (!this._clip) {
this._createClip();
}
this._collapsing = true;
var w = this.get('width'),
h = this.get('height'),
attr = {};
this._lastWidth = w;
this._lastHeight = h;
this._lastScroll = this.get('scroll');
this._lastScrollTop = this.body.scrollTop;
this.set('scroll', false, true);
this._lastLeft = parseInt(this.get('element').style.left, 10);
this._lastTop = parseInt(this.get('element').style.top, 10);
if (isNaN(this._lastTop)) {
this._lastTop = 0;
this.set('top', 0);
}
if (isNaN(this._lastLeft)) {
this._lastLeft = 0;
this.set('left', 0);
}
this.setStyle('zIndex', this.get('parent')._zIndex + 1);
var pos = this.get('position');
switch (pos) {
case 'top':
case 'bottom':
this.set('height', (this.get('collapseSize') + (this._gutter.top + this._gutter.bottom)));
attr = {
top: {
to: (this.get('top') - h)
}
};
if (pos == 'bottom') {
attr.top.to = (this.get('top') + h);
}
break;
case 'left':
case 'right':
this.set('width', (this.get('collapseSize') + (this._gutter.left + this._gutter.right)));
attr = {
left: {
to: -(this._lastWidth)
}
};
if (pos == 'right') {
attr.left = {
to: (this.get('left') + w)
};
}
break;
}
if (this._anim) {
this._anim.attributes = attr;
var collapse = function() {
this._collapsing = false;
this._toggleClip();
this.setStyle('zIndex', this.get('parent')._zIndex);
this._collapsed = true;
this.get('parent').resize();
this._anim.onComplete.unsubscribe(collapse, this, true);
this.fireEvent('collapse');
};
this._anim.onComplete.subscribe(collapse, this, true);
this._anim.animate();
} else {
this._collapsing = false;
this.setStyle('display', 'none');
this._toggleClip();
this.setStyle('zIndex', this.get('parent')._zIndex);
this.get('parent').resize();
this._collapsed = true;
this.fireEvent('collapse');
}
return this;
},
/**
* @method close
* @description Close the unit, removing it from the parent Layout.
* @return {<a href="YAHOO.widget.Layout.html">YAHOO.widget.Layout</a>} The parent Layout instance
*/
close: function() {
this.setStyle('display', 'none');
this.get('parent').removeUnit(this);
this.fireEvent('close');
if (this._clip) {
this._clip.parentNode.removeChild(this._clip);
this._clip = null;
}
return this.get('parent');
},
/**
* @private
* @method init
* @description The initalization method inherited from Element.
*/
init: function(p_oElement, p_oAttributes) {
YAHOO.log('init', 'info', 'LayoutUnit');
this._gutter = {
left: 0,
right: 0,
top: 0,
bottom: 0
};
this._sizes = {
wrap: {
h: 0,
w: 0
},
header: {
h: 0,
w: 0
},
body: {
h: 0,
w: 0
},
footer: {
h: 0,
w: 0
}
};
LayoutUnit.superclass.init.call(this, p_oElement, p_oAttributes);
this.browser = this.get('parent').browser;
var id = p_oElement;
if (!Lang.isString(id)) {
id = Dom.generateId(id);
}
LayoutUnit._instances[id] = this;
this.setStyle('position', 'absolute');
this.addClass('yui-layout-unit');
this.addClass('yui-layout-unit-' + this.get('position'));
var header = this.getElementsByClassName('yui-layout-hd', 'div')[0];
if (header) {
this.header = header;
}
var body = this.getElementsByClassName('yui-layout-bd', 'div')[0];
if (body) {
this.body = body;
}
var footer = this.getElementsByClassName('yui-layout-ft', 'div')[0];
if (footer) {
this.footer = footer;
}
this.on('contentChange', this.resize, this, true);
this._lastScrollTop = 0;
this.set('animate', this.get('animate'));
},
/**
* @private
* @method initAttributes
* @description Processes the config
*/
initAttributes: function(attr) {
LayoutUnit.superclass.initAttributes.call(this, attr);
/**
* @private
* @attribute wrap
* @description A reference to the wrap element
* @type HTMLElement
*/
this.setAttributeConfig('wrap', {
value: attr.wrap || null,
method: function(w) {
if (w) {
var id = Dom.generateId(w);
LayoutUnit._instances[id] = this;
}
}
});
/**
* @attribute grids
* @description Set this option to true if you want the LayoutUnit to fix the first layer of YUI CSS Grids (margins)
* @type Boolean
*/
this.setAttributeConfig('grids', {
value: attr.grids || false
});
/**
* @private
* @attribute top
* @description The current top positioning of the Unit
* @type Number
*/
this.setAttributeConfig('top', {
value: attr.top || 0,
validator: Lang.isNumber,
method: function(t) {
if (!this._collapsing) {
this.setStyle('top', t + 'px');
}
}
});
/**
* @private
* @attribute left
* @description The current left position of the Unit
* @type Number
*/
this.setAttributeConfig('left', {
value: attr.left || 0,
validator: Lang.isNumber,
method: function(l) {
if (!this._collapsing) {
this.setStyle('left', l + 'px');
}
}
});
/**
* @attribute minWidth
* @description The minWidth parameter passed to the Resize Utility
* @type Number
*/
this.setAttributeConfig('minWidth', {
value: attr.minWidth || false,
validator: YAHOO.lang.isNumber
});
/**
* @attribute maxWidth
* @description The maxWidth parameter passed to the Resize Utility
* @type Number
*/
this.setAttributeConfig('maxWidth', {
value: attr.maxWidth || false,
validator: YAHOO.lang.isNumber
});
/**
* @attribute minHeight
* @description The minHeight parameter passed to the Resize Utility
* @type Number
*/
this.setAttributeConfig('minHeight', {
value: attr.minHeight || false,
validator: YAHOO.lang.isNumber
});
/**
* @attribute maxHeight
* @description The maxHeight parameter passed to the Resize Utility
* @type Number
*/
this.setAttributeConfig('maxHeight', {
value: attr.maxHeight || false,
validator: YAHOO.lang.isNumber
});
/**
* @attribute height
* @description The height of the Unit
* @type Number
*/
this.setAttributeConfig('height', {
value: attr.height,
validator: Lang.isNumber,
method: function(h) {
if (!this._collapsing) {
this.setStyle('height', h + 'px');
}
}
});
/**
* @attribute width
* @description The width of the Unit
* @type Number
*/
this.setAttributeConfig('width', {
value: attr.width,
validator: Lang.isNumber,
method: function(w) {
if (!this._collapsing) {
this.setStyle('width', w + 'px');
}
}
});
/**
* @attribute position
* @description The position (top, right, bottom, left or center) of the Unit in the Layout
* @type {String}
*/
this.setAttributeConfig('position', {
value: attr.position
});
/**
* @attribute gutter
* @description The gutter that we should apply to the parent Layout around this Unit. Supports standard CSS markup: (2 4 0 5) or (2) or (2 5)
* @type String
*/
this.setAttributeConfig('gutter', {
value: attr.gutter || 0,
validator: YAHOO.lang.isString,
method: function(gutter)