/**
* @description <p>Makes an element resizable</p>
* @namespace YAHOO.util
* @requires yahoo, dom, dragdrop, element, event
* @optional animation
* @module resize
*/
(function() {
var D = YAHOO.util.Dom,
Event = YAHOO.util.Event,
Lang = YAHOO.lang;
/**
* @constructor
* @class Resize
* @extends YAHOO.util.Element
* @description <p>Makes an element resizable</p>
* @param {String/HTMLElement} el The element to make resizable.
* @param {Object} attrs Object liternal containing configuration parameters.
*/
var Resize = function(el, config) {
YAHOO.log('Creating Resize Object', 'info', 'Resize');
var oConfig = {
element: el,
attributes: config || {}
};
Resize.superclass.constructor.call(this, oConfig.element, oConfig.attributes);
};
/**
* @private
* @static
* @property _instances
* @description Internal hash table for all resize instances
* @type Object
*/
Resize._instances = {};
/**
* @static
* @method getResizeById
* @description Get's a resize object by the HTML id of the element associated with the Resize object.
* @return {Object} The Resize Object
*/
Resize.getResizeById = function(id) {
if (Resize._instances[id]) {
return Resize._instances[id];
}
YAHOO.log('No Instance Found', 'error', 'Resize');
return false;
};
YAHOO.extend(Resize, YAHOO.util.Element, {
/**
* @private
* @property CSS_RESIZE
* @description Base CSS class name
* @type String
*/
CSS_RESIZE: 'yui-resize',
/**
* @private
* @property CSS_DRAG
* @description Class name added when dragging is enabled
* @type String
*/
CSS_DRAG: 'yui-draggable',
/**
* @private
* @property CSS_HOVER
* @description Class name used for hover only handles
* @type String
*/
CSS_HOVER: 'yui-resize-hover',
/**
* @private
* @property CSS_PROXY
* @description Class name given to the proxy element
* @type String
*/
CSS_PROXY: 'yui-resize-proxy',
/**
* @private
* @property CSS_WRAP
* @description Class name given to the wrap element
* @type String
*/
CSS_WRAP: 'yui-resize-wrap',
/**
* @private
* @property CSS_KNOB
* @description Class name used to make the knob style handles
* @type String
*/
CSS_KNOB: 'yui-resize-knob',
/**
* @private
* @property CSS_HIDDEN
* @description Class name given to the wrap element to make all handles hidden
* @type String
*/
CSS_HIDDEN: 'yui-resize-hidden',
/**
* @private
* @property CSS_HANDLE
* @description Class name given to all handles, used as a base for single handle names as well.. Handle "t" will get this.CSS_HANDLE + '-t' as well as this.CSS_HANDLE
* @type String
*/
CSS_HANDLE: 'yui-resize-handle',
/**
* @private
* @property CSS_STATUS
* @description Class name given to the status element
* @type String
*/
CSS_STATUS: 'yui-resize-status',
/**
* @private
* @property CSS_GHOST
* @description Class name given to the wrap element when the ghost property is active
* @type String
*/
CSS_GHOST: 'yui-resize-ghost',
/**
* @private
* @property CSS_RESIZING
* @description Class name given to the wrap element when a resize action is taking place.
* @type String
*/
CSS_RESIZING: 'yui-resize-resizing',
/**
* @private
* @property _resizeEvent
* @description The mouse event used to resize with
* @type Event
*/
_resizeEvent: null,
/**
* @private
* @property dd
* @description The <a href="YAHOO.util.DragDrop.html">YAHOO.util.DragDrop</a> instance used if draggable is true
* @type Object
*/
dd: null,
/**
* @private
* @property browser
* @description A copy of the YAHOO.env.ua property
* @type Object
*/
browser: YAHOO.env.ua,
/**
* @private
* @property _locked
* @description A flag to show if the resize is locked
* @type Boolean
*/
_locked: null,
/**
* @private
* @property _positioned
* @description A flag to show if the element is absolutely positioned
* @type Boolean
*/
_positioned: null,
/**
* @private
* @property _dds
* @description An Object containing references to all of the <a href="YAHOO.util.DragDrop.html">YAHOO.util.DragDrop</a> instances used for the resize handles
* @type Object
*/
_dds: null,
/**
* @private
* @property _wrap
* @description The HTML reference of the element wrapper
* @type HTMLElement
*/
_wrap: null,
/**
* @private
* @property _proxy
* @description The HTML reference of the element proxy
* @type HTMLElement
*/
_proxy: null,
/**
* @private
* @property _handles
* @description An object containing references to all of the resize handles.
* @type Object
*/
_handles: null,
/**
* @private
* @property _currentHandle
* @description The string identifier of the currently active handle. e.g. 'r', 'br', 'tl'
* @type String
*/
_currentHandle: null,
/**
* @private
* @property _currentDD
* @description A link to the currently active DD object
* @type Object
*/
_currentDD: null,
/**
* @private
* @property _cache
* @description An lookup table containing key information for the element being resized. e.g. height, width, x position, y position, etc..
* @type Object
*/
_cache: null,
/**
* @private
* @property _active
* @description Flag to show if the resize is active. Used for events.
* @type Boolean
*/
_active: null,
/**
* @private
* @method _createProxy
* @description Creates the proxy element if the proxy config is true
*/
_createProxy: function() {
if (this.get('proxy')) {
YAHOO.log('Creating the Proxy Element', 'info', 'Resize');
this._proxy = document.createElement('div');
this._proxy.className = this.CSS_PROXY;
this._proxy.style.height = this.get('element').clientHeight + 'px';
this._proxy.style.width = this.get('element').clientWidth + 'px';
this._wrap.parentNode.appendChild(this._proxy);
} else {
YAHOO.log('No proxy element, turn off animate config option', 'info', 'Resize');
this.set('animate', false);
}
},
/**
* @private
* @method _createWrap
* @description Creates the wrap element if the wrap config is true. It will auto wrap the following element types: img, textarea, input, iframe, select
*/
_createWrap: function() {
YAHOO.log('Create the wrap element', 'info', 'Resize');
this._positioned = false;
//Force wrap for elements that can't have children
switch (this.get('element').tagName.toLowerCase()) {
case 'img':
case 'textarea':
case 'input':
case 'iframe':
case 'select':
this.set('wrap', true);
YAHOO.log('Auto-wrapping the element (' + this.get('element').tagName.toLowerCase() + ')', 'warn', 'Resize');
break;
}
if (this.get('wrap') === true) {
YAHOO.log('Creating the wrap element', 'info', 'Resize');
this._wrap = document.createElement('div');
this._wrap.id = this.get('element').id + '_wrap';
this._wrap.className = this.CSS_WRAP;
D.setStyle(this._wrap, 'width', this.get('width') + 'px');
D.setStyle(this._wrap, 'height', this.get('height') + 'px');
D.setStyle(this._wrap, 'z-index', this.getStyle('z-index'));
this.setStyle('z-index', 0);
var pos = D.getStyle(this.get('element'), 'position');
D.setStyle(this._wrap, 'position', ((pos == 'static') ? 'relative' : pos));
D.setStyle(this._wrap, 'top', D.getStyle(this.get('element'), 'top'));
D.setStyle(this._wrap, 'left', D.getStyle(this.get('element'), 'left'));
if (D.getStyle(this.get('element'), 'position') == 'absolute') {
this._positioned = true;
YAHOO.log('The element is positioned absolute', 'info', 'Resize');
D.setStyle(this.get('element'), 'position', 'relative');
D.setStyle(this.get('element'), 'top', '0');
D.setStyle(this.get('element'), 'left', '0');
}
var par = this.get('element').parentNode;
par.replaceChild(this._wrap, this.get('element'));
this._wrap.appendChild(this.get('element'));
} else {
this._wrap = this.get('element');
if (D.getStyle(this._wrap, 'position') == 'absolute') {
this._positioned = true;
}
}
if (this.get('draggable')) {
this._setupDragDrop();
}
if (this.get('hover')) {
D.addClass(this._wrap, this.CSS_HOVER);
}
if (this.get('knobHandles')) {
D.addClass(this._wrap, this.CSS_KNOB);
}
if (this.get('hiddenHandles')) {
D.addClass(this._wrap, this.CSS_HIDDEN);
}
D.addClass(this._wrap, this.CSS_RESIZE);
},
/**
* @private
* @method _setupDragDrop
* @description Setup the <a href="YAHOO.util.DragDrop.html">YAHOO.util.DragDrop</a> instance on the element
*/
_setupDragDrop: function() {
YAHOO.log('Setting up the dragdrop instance on the element', 'info', 'Resize');
D.addClass(this._wrap, this.CSS_DRAG);
this.dd = new YAHOO.util.DD(this._wrap, this.get('id') + '-resize', { dragOnly: true, useShim: this.get('useShim') });
this.dd.on('dragEvent', function() {
this.fireEvent('dragEvent', arguments);
}, this, true);
},
/**
* @private
* @method _createHandles
* @description Creates the handles as specified in the config
*/
_createHandles: function() {
YAHOO.log('Creating the handles', 'info', 'Resize');
this._handles = {};
this._dds = {};
var h = this.get('handles');
for (var i = 0; i < h.length; i++) {
YAHOO.log('Creating handle position: ' + h[i], 'info', 'Resize');
this._handles[h[i]] = document.createElement('div');
this._handles[h[i]].id = D.generateId(this._handles[h[i]]);
this._handles[h[i]].className = this.CSS_HANDLE + ' ' + this.CSS_HANDLE + '-' + h[i];
var k = document.createElement('div');
k.className = this.CSS_HANDLE + '-inner-' + h[i];
this._handles[h[i]].appendChild(k);
this._wrap.appendChild(this._handles[h[i]]);
Event.on(this._handles[h[i]], 'mouseover', this._handleMouseOver, this, true);
Event.on(this._handles[h[i]], 'mouseout', this._handleMouseOut, this, true);
this._dds[h[i]] = new YAHOO.util.DragDrop(this._handles[h[i]], this.get('id') + '-handle-' + h, { useShim: this.get('useShim') });
this._dds[h[i]].setPadding(15, 15, 15, 15);
this._dds[h[i]].on('startDragEvent', this._handleStartDrag, this._dds[h[i]], this);
this._dds[h[i]].on('mouseDownEvent', this._handleMouseDown, this._dds[h[i]], this);
}
YAHOO.log('Creating the Status box', 'info', 'Resize');
this._status = document.createElement('span');
this._status.className = this.CSS_STATUS;
document.body.insertBefore(this._status, document.body.firstChild);
},
/**
* @private
* @method _ieSelectFix
* @description The function we use as the onselectstart handler when we start a drag in Internet Explorer
*/
_ieSelectFix: function() {
return false;
},
/**
* @private
* @property _ieSelectBack
* @description We will hold a copy of the current "onselectstart" method on this property, and reset it after we are done using it.
*/
_ieSelectBack: null,
/**
* @private
* @method _setAutoRatio
* @param {Event} ev A mouse event.
* @description This method checks to see if the "autoRatio" config is set. If it is, we will check to see if the "Shift Key" is pressed. If so, we will set the config ratio to true.
*/
_setAutoRatio: function(ev) {
if (this.get('autoRatio')) {
YAHOO.log('Setting up AutoRatio', 'info', 'Resize');
if (ev && ev.shiftKey) {
//Shift Pressed
YAHOO.log('Shift key presses, turning on ratio', 'info', 'Resize');
this.set('ratio', true);
} else {
YAHOO.log('Resetting ratio back to default', 'info', 'Resize');
this.set('ratio', this._configs.ratio._initialConfig.value);
}
}
},
/**
* @private
* @method _handleMouseDown
* @param {Event} ev A mouse event.
* @description This method preps the autoRatio on MouseDown.
*/
_handleMouseDown: function(ev) {
if (this._locked) {
YAHOO.log('Resize Locked', 'info', 'Resize');
return false;
}
if (D.getStyle(this._wrap, 'position') == 'absolute') {
this._positioned = true;
}
if (ev) {
this._setAutoRatio(ev);
}
if (this.browser.ie) {
this._ieSelectBack = document.body.onselectstart;
document.body.onselectstart = this._ieSelectFix;
}
},
/**
* @private
* @method _handleMouseOver
* @param {Event} ev A mouse event.
* @description Adds CSS class names to the handles
*/
_handleMouseOver: function(ev) {
if (this._locked) {
YAHOO.log('Resize Locked', 'info', 'Resize');
return false;
}
//Internet Explorer needs this
D.removeClass(this._wrap, this.CSS_RESIZE);
if (this.get('hover')) {
D.removeClass(this._wrap, this.CSS_HOVER);
}
var tar = Event.getTarget(ev);
if (!D.hasClass(tar, this.CSS_HANDLE)) {
tar = tar.parentNode;
}
if (D.hasClass(tar, this.CSS_HANDLE) && !this._active) {
D.addClass(tar, this.CSS_HANDLE + '-active');
for (var i in this._handles) {
if (Lang.hasOwnProperty(this._handles, i)) {
if (this._handles[i] == tar) {
D.addClass(tar, this.CSS_HANDLE + '-' + i + '-active');
break;
}
}
}
}
//Internet Explorer needs this
D.addClass(this._wrap, this.CSS_RESIZE);
},
/**
* @private
* @method _handleMouseOut
* @param {Event} ev A mouse event.
* @description Removes CSS class names to the handles
*/
_handleMouseOut: function(ev) {
//Internet Explorer needs this
D.removeClass(this._wrap, this.CSS_RESIZE);
if (this.get('hover') && !this._active) {
D.addClass(this._wrap, this.CSS_HOVER);
}
var tar = Event.getTarget(ev);
if (!D.hasClass(tar, this.CSS_HANDLE)) {
tar = tar.parentNode;
}
if (D.hasClass(tar, this.CSS_HANDLE) && !this._active) {
D.removeClass(tar, this.CSS_HANDLE + '-active');
for (var i in this._handles) {
if (Lang.hasOwnProperty(this._handles, i)) {
if (this._handles[i] == tar) {
D.removeClass(tar, this.CSS_HANDLE + '-' + i + '-active');
break;
}
}
}
}
//Internet Explorer needs this
D.addClass(this._wrap, this.CSS_RESIZE);
},
/**
* @private
* @method _handleStartDrag
* @param {Object} args The args passed from the CustomEvent.
* @param {Object} dd The <a href="YAHOO.util.DragDrop.html">YAHOO.util.DragDrop</a> object we are working with.
* @description Resizes the proxy, sets up the <a href="YAHOO.util.DragDrop.html">YAHOO.util.DragDrop</a> handlers, updates the status div and preps the cache
*/
_handleStartDrag: function(args, dd) {
YAHOO.log('startDrag', 'info', 'Resize');
var tar = dd.getDragEl();
if (D.hasClass(tar, this.CSS_HANDLE)) {
if (D.getStyle(this._wrap, 'position') == 'absolute') {
this._positioned = true;
}
this._active = true;
this._currentDD = dd;
if (this._proxy) {
YAHOO.log('Activate proxy element', 'info', 'Resize');
this._proxy.style.visibility = 'visible';
this._proxy.style.zIndex = '1000';
this._proxy.style.height = this.get('element').clientHeight + 'px';
this._proxy.style.width = this.get('element').clientWidth + 'px';
}
for (var i in this._handles) {
if (Lang.hasOwnProperty(this._handles, i)) {
if (this._handles[i] == tar) {
this._currentHandle = i;
var handle = '_handle_for_' + i;
D.addClass(tar, this.CSS_HANDLE + '-' + i + '-active');
dd.on('dragEvent', this[handle], this, true);
dd.on('mouseUpEvent', this._handleMouseUp, this, true);
YAHOO.log('Adding DragEvents to: ' + i, 'info', 'Resize');
break;
}
}
}
D.addClass(tar, this.CSS_HANDLE + '-active');
if (this.get('proxy')) {
YAHOO.log('Posiiton Proxy Element', 'info', 'Resize');
var xy = D.getXY(this.get('element'));
D.setXY(this._proxy, xy);
if (this.get('ghost')) {
YAHOO.log('Add Ghost Class', 'info', 'Resize');
this.addClass(this.CSS_GHOST);
}
}
D.addClass(this._wrap, this.CSS_RESIZING);
this._setCache();
this._updateStatus(this._cache.height, this._cache.width, this._cache.top, this._cache.left);
YAHOO.log('Firing startResize Event', 'info', 'Resize');
this.fireEvent('startResize', { type: 'startresize', target: this});
}
},
/**
* @private
* @method _setCache
* @description Sets up the this._cache hash table.
*/
_setCache: function() {
YAHOO.log('Setting up property cache', 'info', 'Resize');
this._cache.xy = D.getXY(this._wrap);
D.setXY(this._wrap, this._cache.xy);
this._cache.height = this.get('clientHeight');
this._cache.width = this.get('clientWidth');
this._cache.start.height = this._cache.height;
this._cache.start.width = this._cache.width;
this._cache.start.top = this._cache.xy[1];
this._cache.start.left = this._cache.xy[0];
this._cache.top = this._cache.xy[1];
this._cache.left = this._cache.xy[0];
this.set('height', this._cache.height, true);
this.set('width', this._cache.width, true);
},
/**
* @private
* @method _handleMouseUp
* @param {Event} ev A mouse event.
* @description Cleans up listeners, hides proxy element and removes class names.
*/
_handleMouseUp: function(ev) {
this._active = false;
var handle = '_handle_for_' + this._currentHandle;
this._currentDD.unsubscribe('dragEvent', this[handle], this, true);
this._currentDD.unsubscribe('mouseUpEvent', this._handleMouseUp, this, true);
if (this._proxy) {
YAHOO.log('Hide Proxy Element', 'info', 'Resize');
this._proxy.style.visibility = 'hidden';
this._proxy.style.zIndex = '-1';
if (this.get('setSize')) {
YAHOO.log('Setting Size', 'info', 'Resize');
this.resize(ev, this._cache.height, this._cache.width, this._cache.top, this._cache.left, true);
} else {
YAHOO.log('Firing Resize Event', 'info', 'Resize');
this.fireEvent('resize', { ev: 'resize', target: this, height: this._cache.height, width: this._cache.width, top: this._cache.top, left: this._cache.left });
}
if (this.get('ghost')) {
YAHOO.log('Removing Ghost Class', 'info', 'Resize');
this.removeClass(this.CSS_GHOST);
}
}
if (this.get('hover')) {
D.addClass(this._wrap, this.CSS_HOVER);
}
if (this._status) {
D.setStyle(this._status, 'display', 'none');
}
if (this.browser.ie) {
YAHOO.log('Resetting IE onselectstart function', 'info', 'Resize');
document.body.onselectstart = this._ieSelectBack;
}
if (this.browser.ie) {
D.removeClass(this._wrap, this.CSS_RESIZE);
}
for (var i in this._handles) {
if (Lang.hasOwnProperty(this._handles, i)) {
D.removeClass(this._handles[i], this.CSS_HANDLE + '-active');
}
}
if (this.get('hover') && !this._active) {
D.addClass(this._wrap, this.CSS_HOVER);
}
D.removeClass(this._wrap, this.CSS_RESIZING);
D.removeClass(this._handles[this._currentHandle], this.CSS_HANDLE + '-' + this._currentHandle + '-active');
D.removeClass(this._handles[this._currentHandle], this.CSS_HANDLE + '-active');
if (this.browser.ie) {
D.addClass(this._wrap, this.CSS_RESIZE);
}
this._resizeEvent = null;
this._currentHandle = null;
if (!this.get('animate')) {
this.set('height', this._cache.height, true);
this.set('width', this._cache.width, true);
}
YAHOO.log('Firing endResize Event', 'info', 'Resize');
this.fireEvent('endResize', { ev: 'endResize', target: this, height: this._cache.height, width: this._cache.width, top: this._cache.top, left: this._cache.left });
},
/**
* @private
* @method _setRatio
* @param {Number} h The height offset.
* @param {Number} w The with offset.
* @param {Number} t The top offset.
* @param {Number} l The left offset.
* @description Using the Height, Width, Top & Left, it recalcuates them based on the original element size.
* @return {Array} The new Height, Width, Top & Left settings
*/
_setRatio: function(h, w, t, l) {
YAHOO.log('Setting Ratio', 'info', 'Resize');
var oh = h, ow = w;
if (this.get('ratio')) {
var orgH = this._cache.height,
orgW = this._cache.width,
nh = parseInt(this.get('height'), 10),
nw = parseInt(this.get('width'), 10),
maxH = this.get('maxHeight'),
minH = this.get('minHeight'),
maxW = this.get('maxWidth'),
minW = this.get('minWidth');
switch (this._currentHandle) {
case 'l':
h = nh * (w / nw);
h = Math.min(Math.max(minH, h), maxH);
w = nw * (h / nh);
t = (this._cache.start.top - (-((nh - h) / 2)));
l = (this._cache.start.left - (-((nw - w))));
break;
case 'r':
h = nh * (w / nw);
h = Math.min(Math.max(minH, h), maxH);
w = nw * (h / nh);
t = (this._cache.start.top - (-((nh - h) / 2)));
break;
case 't':
w = nw * (h / nh);
h = nh * (w / nw);
l = (this._cache.start.left - (-((nw - w) / 2)));
t = (this._cache.start.top - (-((nh - h))));
break;
case 'b':
w = nw * (h / nh);
h = nh * (w / nw);
l = (this._cache.start.left - (-((nw - w) / 2)));
break;
case 'bl':
h = nh * (w / nw);
w = nw * (h / nh);
l = (this._cache.start.left - (-((nw - w))));
break;
case 'br':
h = nh * (w / nw);
w = nw * (h / nh);
break;
case 'tl':
h = nh * (w / nw);
w = nw * (h / nh);
l = (this._cache.start.left - (-((nw - w))));
t = (this._cache.start.top - (-((nh - h))));
break;
case 'tr':
h = nh * (w / nw);
w = nw * (h / nh);
l = (this._cache.start.left);
t = (this._cache.start.top - (-((nh - h))));
break;
}
oh = this._checkHeight(h);
ow = this._checkWidth(w);
if ((oh != h) || (ow != w)) {
t = 0;
l = 0;
if (oh != h) {
ow = this._cache.width;
}
if (ow != w) {
oh = this._cache.height;
}
}
}
return [oh, ow, t, l];
},
/**
* @private
* @method _updateStatus
* @param {Number} h The new height setting.
* @param {Number} w The new width setting.
* @param {Number} t The new top setting.
* @param {Number} l The new left setting.
* @description Using the Height, Width, Top & Left, it updates the status element with the elements sizes.
*/
_updateStatus: function(h, w, t, l) {
if (this._resizeEvent && (!Lang.isString(this._resizeEvent))) {
YAHOO.log('Updating Status Box', 'info', 'Resize');
if (this.get('status')) {
YAHOO.log('Showing Status Box', 'info', 'Resize');
D.setStyle(this._status, 'display', 'inline');
}
h = ((h === 0) ? this._cache.start.height : h);
w = ((w === 0) ? this._cache.start.width : w);
var h1 = parseInt(this.get('height'), 10),
w1 = parseInt(this.get('width'), 10);
if (isNaN(h1)) {
h1 = parseInt(h, 10);
}
if (isNaN(w1)) {
w1 = parseInt(w, 10);
}
var diffH = (parseInt(h, 10) - h1);