JSWindow.js

Summary

No overview generated for 'JSWindow.js'


/**
 * Window class
 * @constructor
 * @param {Element} element  Container window or contents (if contentExists)
 * @param {int} w  Window width
 * @param {int} h  Window height
 * @param {JSWindowOptions} options  Window options
 * @param {boolean} contentExists  Flag to indicate element points to existing content to be wrapped
 */
var JSWindow = Class.create();
JSWindow.prototype.initialize = function(manager, w, h, l, t, options, contents)
{
	var _this = this;
	this.minWidth = 200;
	this.minHeight = 50;
	this.tabs = new Array();
	this.minTabButtonWidth = 100;
	this.maxTabButtonWidth = 200;
	this.manager = manager;
	this.options = options;
				
	this.container = this.manager.contents.appendChild(document.createElement('DIV'));
	Element.extend(this.container);
	this.innerContainer = this.container.appendChild(document.createElement('DIV'));
	if(!this.options.noCollapse)
	{
		this.slide = this.innerContainer.appendChild(document.createElement('DIV'));
		var nest = this.slide.appendChild(document.createElement('DIV'));
		this.contents = nest.appendChild(document.createElement('DIV'));
	}
	else
	{
		this.contents = this.innerContainer.appendChild(document.createElement('DIV'));
		this.slide = this.contents;
	}
	this.tabList = this.contents.appendChild(document.createElement('UL'));
	this.tabList.className = 'JSWM_tabList'
	Element.extend(this.tabList);

	this.contents.appendChild(new Lineclear());
	if(!contents)
		contents = document.createElement('DIV');
	this.openTab(contents);

	this.container.style.position = 'absolute'; // IE fix
	this.container.addClassName('JSWM_window');
	this.handle = this.innerContainer.insertBefore(document.createElement('DIV'), this.slide);
	this.handle.className = 'JSWM_window_handle';
	handleRight = this.handle.appendChild(document.createElement('DIV'));
	handleRight.className = 'JSWM_window_handle_right'

	if(!this.options.noResize)
	{
		this.resizeNW = this.innerContainer.appendChild(document.createElement('DIV'));
		this.resizeNW.className = 'JSWM_window_resize JSWM_window_resizeNW';
		this.resizeNW.style.position = 'absolute'; // IE fix
		var dragNW = function(drag) {
			var offset = drag.currentDelta ? drag.currentDelta() : Position.positionedOffset(drag);
			_this.setSize(-offset[0], -offset[1], 3, true);
		};
		this.resizeNE = this.innerContainer.appendChild(document.createElement('DIV'));
		this.resizeNE.className = 'JSWM_window_resize JSWM_window_resizeNE';
		this.resizeNE.style.position = 'absolute'; // IE fix
		var dragNE = function(drag) {
			var offset = drag.currentDelta ? drag.currentDelta() : Position.positionedOffset(drag);
			_this.setSize(offset[0] + 10, null, 2);
			_this.setSize(0, -offset[1], 2, true);
		};
		this.resizeSW = this.innerContainer.appendChild(document.createElement('DIV'));
		this.resizeSW.className = 'JSWM_window_resize JSWM_window_resizeSW';
		this.resizeSW.style.position = 'absolute'; // IE fix
		var dragSW = function(drag) {
			var offset = drag.currentDelta ? drag.currentDelta() : Position.positionedOffset(drag);
			_this.setSize(-offset[0], 0, 1, true);
			_this.setSize(null, offset[1] + 10 - 20, 1);
		};
		this.resizeSE = this.innerContainer.appendChild(document.createElement('DIV'));
		this.resizeSE.className = 'JSWM_window_resize JSWM_window_resizeSE';
		this.resizeSE.style.position = 'absolute'; // IE fix
		var dragSE = function(drag) {
			var offset = drag.currentDelta ? drag.currentDelta() : Position.positionedOffset(drag);
			_this.setSize(offset[0] + 10, offset[1] + 10 - 20, 0);
		};
		if(!this.options.noResizeRedraw)
		{
			new Draggable(this.resizeNW, {change: dragNW, starteffect: null, endeffect: null});
			new Draggable(this.resizeNE, {change: dragNE, starteffect: null, endeffect: null});
			new Draggable(this.resizeSW, {change: dragSW, starteffect: null, endeffect: null});
			new Draggable(this.resizeSE, {change: dragSE, starteffect: null, endeffect: null});
		}
		else
		{
			new Draggable(this.resizeNW, {starteffect: null, endeffect: dragNW});
			new Draggable(this.resizeNE, {starteffect: null, endeffect: dragNE});
			new Draggable(this.resizeSW, {starteffect: null, endeffect: dragSW});
			new Draggable(this.resizeSE, {starteffect: null, endeffect: dragSE});
		}
		Event.observe(this.resizeNW, 'mousedown', function() { _this.setActive(); }, false)
		Event.observe(this.resizeNE, 'mousedown', function() { _this.setActive(); }, false)
		Event.observe(this.resizeSW, 'mousedown', function() { _this.setActive(); }, false)
		Event.observe(this.resizeSE, 'mousedown', function() { _this.setActive(); }, false)
		Event.observe(this.handle, 'dblclick', function() { _this.maximise(); }, false)
		Event.observe(window, 'resize', function() { _this.updateMaximise(); });
	}
	
	handleRight.appendChild(new ImageButton(function() { _this.openTab(); }, JSWMImages.add, '+', 'add tab', JSWMImagesHover.add));

	if(!this.options.noCollapse)
	{
		this.slideOptions = {
			afterFinish: function() { _this.redrawTabList(true); _this.redrawShadow(); },
			afterUpdate: function() { _this.redrawShadow(); },
			duration: 0.3,
			queue: {
				position: 'end',
				scope: 'JSWindow' + Math.random()
			}			
		};
		this.expanded = true;
		this.expandButton = new ExpandButton(function() { _this.expand(); });
		this.expandButton.set(true);
		handleRight.appendChild(this.expandButton.getButton());
	}

	if(!this.options.noShadow && pngSupport)
	{
		var shadowContainer = this.container.insertBefore(document.createElement('DIV'), this.innerContainer);
		shadowContainer.className = 'JSWM_shadow_container';

		this.shadowNE = shadowContainer.appendChild(document.createElement('DIV'));
		this.shadowNE.className = 'JSWM_shadowNE';
		this.shadowSW = shadowContainer.appendChild(document.createElement('DIV'));
		this.shadowSW.className = 'JSWM_shadowSW';
		this.shadowSE = shadowContainer.appendChild(document.createElement('DIV'));
		this.shadowSE.className = 'JSWM_shadowSE';

		this.shadowS = shadowContainer.appendChild(document.createElement('DIV'));
		this.shadowS.className = 'JSWM_shadowS';
		this.shadowE = shadowContainer.appendChild(document.createElement('DIV'));
		this.shadowE.className = 'JSWM_shadowE';

		Element.extend(this.innerContainer);
	}

	if(!this.options.noClose)
	{
		var closeButton = handleRight.appendChild(new ImageButton(function() { _this.close(); }, JSWMImages.closeWindow, 'x', 'close', JSWMImagesHover.closeWindow));
		closeButton.className = 'close';
	}

	this.contents.className = 'JSWM_window_contents'

	this.titleLabel = this.handle.appendChild(document.createElement('DIV'));
	this.titleLabel.className = 'JSWM_window_title';
	Element.extend(this.titleLabel);
	if(!this.options.title) this.options.title = '';
	this.setTitle(this.options.title, this.options.icon);
	
	if(!this.options.noDrag)
	{
		this.drag = new Draggable(this.container, {
			handle: _this.handle,
			starteffect: function(){
				var pagePos = Position.cumulativeOffset(_this.container);
				var offsetPos = _this.drag.currentDelta();
				_this.startPos = [pagePos[0] - offsetPos[0], pagePos[1] - offsetPos[1]];
			},
			snap: function(x, y){
				return _this.maximised ? [0, 0] : [x, Math.max(-_this.startPos[1] + _this.manager.margins[0], y)];
			},
			change: function() { _this.onmove(); },
			endeffect: function() { _this.ondrop(); _this.setActive(); }
		});
		Event.observe(this.innerContainer, 'mousedown', function() { _this.setActive(); }, false)
		Event.observe(this.handle, 'mousedown', function() { _this.setActive(); }, false)
	}
	this.handle.appendChild(new Lineclear());
	Element.extend(this.contents);
	switch(String(l).toLowerCase())
	{
		case 'left':
			l = 0;
			break;
		case 'center':
		case 'middle':
			l = (this.manager.getWindowSize().width - w) / 2;
			break;
		case 'right':
			l = this.manager.getWindowSize().width - w - 10;
			break;
	}

	switch(String(t).toLowerCase())
	{
		case 'top':
			t = 0;
			break;
		case 'center':
		case 'middle':
			t = (this.manager.getWindowSize().height - h) / 2;
			break;
		case 'bottom':
			t = this.manager.getWindowSize().height - h - 10;
			break;
	}
	t = Math.max(t, 0);
	this.setPosition(l, t);
	this.setSize(w, h, 0);
};

/**
 * Open a new tab in a window
 * @method
 * @param {Element} contents  Contents to place in tab
 * @param {boolean} force  Add even if contents are already wrapped
 */
JSWindow.prototype.openTab = function(contents, force)
{
	var _this = this;
	contents = $(contents);
	if(!force && contents && this.manager.isWrapped(contents))
		return false;
	var tabContents = document.createElement('DIV');
	if(contents)
		tabContents.appendChild(contents);
	var jsTab = new JSTab(this, tabContents, this.tabs.length + 'long tab name ' + this.tabs.length, 'images/tab.png');
	this.addTab(jsTab)
};

/**
 * Add a tab to the tab manager
 * @method
 * @param {JSTab} jsTab  Tab to add
 */
JSWindow.prototype.addTab = function(jsTab)
{
	jsTab.i = this.tabs.length;
	this.tabs.push(jsTab);
	this.tabList.appendChild(jsTab.getButton());
	this.redrawTabList();
	jsTab.setActive();
	this.contents.appendChild(jsTab.contents);
};

/**
 * Sets a tab as active
 * @method
 * @param {Element} jsTab  Tab to make active
 */
JSWindow.prototype.setActiveTab = function(jsTab)
{
	var redraw = false;
	if(this.fadeTabs)
		var scope = "tab" + Math.random();
	if(this.lastActiveTab && this.lastActiveTab != jsTab)
	{
		if(this.fadeTabs)
		{
			this.lastActiveTab.contents.style.display = 'block';
			new Effect.Fade(this.lastActiveTab.contents, {duration: .2, queue: {position: 'end', scope: scope}});
		}
		else
		{
			this.lastActiveTab.contents.style.display = 'none';
		}
		this.lastActiveTab.tabButton.removeClassName('JSWM_tabButton_active');
		redraw = true;
	}
	if(this.fadeTabs && this.lastActiveTab != jsTab)
	{
		jsTab.contents.style.display = 'none';
		new Effect.Appear(jsTab.contents, {duration: .2, queue: {position: 'end', scope: scope}});
	}
	else
	{
		jsTab.contents.style.display = 'block';
	}
	jsTab.tabButton.addClassName('JSWM_tabButton_active');
	this.lastActiveTab = jsTab;
	if(redraw)
		this.setSize(0, 0, 0, true);
};

/**
 * Redraw the tab list
 * @method
 * @param {boolean} force  Recalculate tab name truncation even if width hasn't changed (for post collapse event) 
 */
JSWindow.prototype.redrawTabList = function(force)
{
	if(this.tabs.length <= 1)
	{
		this.tabList.style.display = 'none';
	}
	else
	{
		this.tabList.style.display = 'block';
		var w = this.getSize().width - 20;
		var tabWidth = Math.floor(w / this.tabs.length);
		tabWidth = Math.min(tabWidth, this.maxTabButtonWidth);
		tabWidth = Math.max(tabWidth, this.minTabButtonWidth);
		tabWidth -= JSWMTabMargins;
		tabsRemoved = 0;
		while(tabWidth * (this.tabs.length - tabsRemoved) > w)
			tabsRemoved++;
			
		var i = 0;
		// remove tabs before active one
		while(this.tabs[i] != this.lastActiveTab && i < this.tabs.length && i < tabsRemoved)
		{
			this.tabs[i].tabButton.style.display = 'none';
			i++;
		}
		// draw as many tabs as can fit
		var drawTo = this.tabs.length - (tabsRemoved - i);
		while(i < drawTo)
		{
			this.tabs[i].tabButton.style.display = 'block';
			var curWidth = parseInt(this.tabs[i].tabButton.style.width);
			if(curWidth != tabWidth || force)
			{
				this.tabs[i].tabButton.style.width = tabWidth + 'px';
				this.tabs[i].setTitle(this.tabs[i].title, this.tabs[i].icon);
			}
			i++;
		}
		// remove remaining tabs
		while(i < this.tabs.length)
		{
			this.tabs[i].tabButton.style.display = 'none';
			i++;
		}
	}
};

/**
 * Expand/collapse a collapsible window
 * @method
 * @param {boolean} expand  Mode to expand to, null to toggle
 */
JSWindow.prototype.expand = function(expand)
{
	if(expand == null || expand != this.expanded)
	{
		if(expand == null)
		{
			this.expanded = !this.expanded;
		}
		else
		{
			this.expanded = expand;
		}
		if(this.expanded)
		{
			new Effect.SlideDown(this.slide, this.slideOptions);
		}
		else
		{
			new Effect.SlideUp(this.slide, this.slideOptions);
		}
	}
	this.expandButton.set(this.expanded);
};

/**
 * Maximise / restore window
 * @method
 */
JSWindow.prototype.maximise = function()
{
	if(this.maximised)
	{
		this.maximised = false;
		this.container.removeClassName('JSWM_window_maximised');
		this.setPosition(this.restorePosition.left, this.restorePosition.top);
		this.setSize(this.restoreSize.width, this.restoreSize.height, 0);
	}
	else
	{
		this.container.addClassName('JSWM_window_maximised');
		this.restoreSize = this.getSize();
		this.restorePosition = this.getPosition();
		this.setPosition(0, 0);
		var windowSize = this.manager.getWindowSize();
		this.setSize(windowSize.width - 2, windowSize.height - 4 - 20, 0);
		this.maximised = true;
	}
};

/**
 * Update maximise size if window is resized
 * @method
 */
JSWindow.prototype.updateMaximise = function()
{
	if(this.maximised)
	{
		var windowSize = this.manager.getWindowSize();
		this.maximised = false;
		this.setSize(windowSize.width - 2, windowSize.height - 4 - 20, 0);
		this.maximised = true;
	}
};

/**
 * Get the current size
 * @method
 * @return {object}  Object containing .width and .height
 */
JSWindow.prototype.getSize  = function()
{
	return {width: parseInt(this.slide.style.width), height: parseInt(this.slide.style.height)};
};

/**
 * Get the current position
 * @method
 * @return {object}  Object containing .left and .top
 */
JSWindow.prototype.getPosition  = function()
{
	return {left: parseInt(this.container.style.left) - this.manager.margins[3], top: parseInt(this.container.style.top) - this.manager.margins[0]};
};

/**
 * Set window as active, shortcut to JSWM.setActiveWindow()
 * @method
 */
JSWindow.prototype.setActive = function()
{
	this.manager.setActiveWindow(this);
};

/**
 * Resize a window
 * @method
 * @param {int} w  New width, null indicates no change
 * @param {int} h  New height, null indicates no change
 * @param {int} fixedCorner  The corner to fix while resizing 0 = NW, 1 = NE, 2 = SW, 3 = SE
 * @param {boolean} relative  Indicates that the supplied size is relative to the current size
 */
JSWindow.prototype.setSize = function(w, h, fixedCorner, relative)
{
	var size = this.getSize();
	if(relative)
	{
		w += size.width;
		h += size.height;
	}
	if(this.maximised)
	{
		w = size.width;
		h = size.height;
	}
	if(w == null) w = size.width;
	if(h == null || !this.expanded) h = size.height;

	w = Math.max(w, this.minWidth);
	h = Math.max(h, this.minHeight);

	this.handle.style.width = w + 'px';
	this.slide.style.width = w + 'px';
	this.slide.style.height = h + 'px';
	this.contents.style.width = w + 'px';
	this.innerContainer.style.width = (w + 2) + 'px';
	this.lastActiveTab.contents.style.width = w + 'px';
	this.lastActiveTab.contents.style.height = (h - (this.tabs.length > 1 ? this.tabList.getHeight() : 0)) + 'px';

	if(fixedCorner % 2 == 1) // right of window fixed
		this.setPosition(size.width - w, 0, true)

	if(fixedCorner >= 2) // bottom of window fixed
		this.setPosition(0, size.height - h, true)

	w += 2; //total horizontal border width
	h += 4; //total vertical border height
	h += 20; //title bar
	this.resizeNW.style.left = '0';
	this.resizeNW.style.top = '0';
	this.resizeNE.style.left = (w - 10) + 'px';
	this.resizeNE.style.top = '0';
	this.resizeSW.style.left = '0';
	this.resizeSW.style.top = (h - 10) + 'px';
	this.resizeSE.style.left = (w - 10) + 'px';
	this.resizeSE.style.top = (h - 10) + 'px';
	this.redrawShadow();
	this.redrawTabList();
	this.setTitle(this.title, this.icon);
};

/**
 * Position the window aboslutely or relatively
 * @method
 * @param {int} l  Distance from the left of the viewport
 * @param {int} t  Distance from the top of the viewport
 * @param {boolean} relative  Indicates that the supplied coordinates a relative to the current position
 */
JSWindow.prototype.setPosition = function(l, t, relative)
{
	if(relative)
	{
		var position = this.getPosition();
		if(l != null)
			l += position.left;
		if(t != null)
			t += position.top;
	}
	if(l != null)
	{
		l += this.manager.margins[3];
		this.container.style.left = l + 'px';
	}
	if(t != null)
	{
		t += this.manager.margins[0]
		this.container.style.top = t + 'px';
	}
};

/**
 * Set the window title
 * @method
 * @param {string} title  The new title
 * @param {string} icon  Window icon uri
 */
JSWindow.prototype.setTitle = function(title, icon)
{
	this.title = title;
 	this.titleLabel.removeChildren();
 	var span = this.titleLabel.appendChild(document.createElement('SPAN'));
	span.appendChild(document.createTextNode(this.title));
	Element.extend(span);
	var titleSpace = this.titleLabel.getWidth() - 20;
	JSWMtruncate(title, span, this.handle, titleSpace, 25);
	this.titleLabel.title = title;
	
	this.icon = icon;
	this.titleLabel.style.backgroundImage = 'url("' + this.icon + '")';
};

/**
 * Fires when component is moved (if set to draggable)
 * @method
 */
JSWindow.prototype.onmove = function(drag) {};

/**
 * Fires when component is dropped (if set to draggable)
 * @method
 */
JSWindow.prototype.ondrop = function(drag) {};

/**
 * Redraws the drop shadow
 * @method
 */
JSWindow.prototype.redrawShadow = function()
{
	if(!this.options.noShadow && pngSupport)
	{
		var w = this.innerContainer.getWidth();
		var h = this.innerContainer.getHeight();
		if(this.expanded)
			h += 2; // combined border width of top and bottom
		this.shadowNE.style.left = w + 'px';
		this.shadowSE.style.left = w + 'px';
		this.shadowE.style.left = w + 'px';
		this.shadowSW.style.top = h + 'px';
		this.shadowSE.style.top = h + 'px';
		this.shadowS.style.top = h + 'px';
		if(w > 6)
			this.shadowS.style.width = (w - 6) + 'px'
		if(h > 6)
			this.shadowE.style.height = (h - 6) + 'px';
	}
};

/**
 * Start of tab dragging, calculate inital positions
 * @method
 * @param {Element} item  Tab being dragged
 */
JSWindow.prototype.dragTabStart = function(item)
{
	this.tabPositions = new Array();
	for(var i = 0; i < this.tabs.length; i++)
	{
		this.tabPositions[i] = Position.positionedOffset(this.tabs[i].tabButton);
	}
	this.start = Position.positionedOffset(item);
};

/**
 * Read serialised object state data into the window
 * @method
 * @param {String} serialData  Object serialisation data
 */
JSWindow.prototype.readObject = function(serialData)
{
	this.setSize(serialData.size.width, serialData.size.height);
	this.setPosition(serialData.position.left, serialData.position.top);
	this.options = serialData.options;
		
	this.expand(serialData.expanded);
	if(serialData.maximised)
	{
		this.maximise();
		this.restoreSize = serialData.restoreSize;
		this.restorePosition = serialData.restorePosition;
	}
	this.contents.style.zIndex = serialData.zIndex;
	this.tabs[0].close()
	for(var i = 0; i < serialData.tabs.length; i++)
	{
		var t = serialData.tabs[i];
		var jsTab = new JSTab(this, document.createElement('DIV'), t.title, t.icon);
		this.addTab(jsTab);
		jsTab.readObject(t);
		if(serialData.lastActiveWindow == i)
			jsWindow.setActive();
	}
};

/**
 * Write object state data for serialisation
 * @method
 * @returns {Object} serialData  Object serialisation data
 */
JSWindow.prototype.writeObject = function()
{
	var serialData = new Object();
	serialData.size = this.getSize();
	serialData.position = this.getPosition();
	serialData.options = this.options;
	serialData.options.title = this.title;
	serialData.options.icon = this.icon;
	if(this.maximised)
	{
		serialData.maximised = this.maximised;
		serialData.restoreSize = this.restoreSize;
		serialData.restorePosition = this.restorePosition;
	}
	
	serialData.expanded = this.expanded;
	serialData.zIndex = this.container.style.zIndex;
	serialData.tabs = new Array();
	for(var i = 0; i < this.tabs.length; i++)
	{
		serialData.tabs[i] = this.tabs[i].writeObject();
		if(this.tabs[i] == this.lastActiveTab)
			serialData.lastActiveTab = i;
	}
	return serialData;
};

/**
 * Close window
 * @method
 */
JSWindow.prototype.close = function()
{
	this.manager.windows = this.manager.windows.without(this);
	this.container.parentNode.removeChild(this.container);
};


Documentation generated by JSDoc on Sun May 6 13:35:11 2007