GpsGate Server JavaScript API

UI  1.0.0

GpsGate Server JavaScript API > UI > popupmenu.js (source view)
Search:
 
Filters
/**
 * Copyright Franson Technology AB, Sweden, 2009
 * http://gpsgate.com, http://franson.com
 *
 * author Fredrik Blomqvist
 *
 */

/**
 *
 * @module UI
 */

var Franson = Franson || {};
/*
 * namespace
 * //class Franson.UI
 * //static
 */
Franson.UI = Franson.UI || {};


/**
 * todo: expose CSS styling
 * todo: handle hierarchial menus?
 * @class Franson.UI.PopupMenu
 * @constructor
 * @param {string} menuContent
 * @param {string} [mode='stateless'] one of: [ 'stateless', 'checkbox', 'radiobutton' ]
 */
Franson.UI.PopupMenu = function(settings, menuContent)
{
	settings = MochiKit.Base.setdefault(settings, {
		id: 'popupMenu_' + Franson.Util.getUniqueId()
	});

	/** @private @type string */
	this.id = settings.id;

	/** @private */
	this._mode = settings.mode;

	/** @private */
	this._menuContent = menuContent;

	/** @private @type DOM|string */
	this._targetElem = null; // set in attach()

	this._events = [];
};

/*
// todo: better def "language"? (simplified DOM-lang)
menuContent = [
	{ 'text': 'row one' },
	{ 'radio-group': [ // todo: id
		{ 'text': 'radio row 1' },
		{ 'text': 'radio row 2' }
	]},
	{ 'text': 'baasdfa', 'type': 'checkbox' },
	{ 'separator' },
	{ 'text: 'asffa' }
	{ 'checkbox-group': [
		{ 'text': 'asdf' },
		{ 'text': 'saddf' }
	]}
];
*/

Franson.UI.PopupMenu.prototype =
{
	hide: function()
	{
		MochiKit.Style.hideElement(this.id);
	},

	show: function()
	{
		var targetBounds = Franson.DOM.getElementBounds(this._targetElem);

		MochiKit.Style.showElement(this.id);

		var ourBounds = Franson.DOM.getElementBounds(this.id); // or just store this at init time?

		// align on right side (todo: take align flags?)
		MochiKit.Style.setElementPosition(this.id, {
			x: targetBounds.x + targetBounds.w - ourBounds.w,
			y: targetBounds.y + targetBounds.h
		});
	},

	/* @private */
	_popUp: function(e)
	{
		e.stop(); // to prevent 'onoutsideclick' to fire immediately

		// toggle visibility
		if (Franson.DOM.isBlockVisible(this.id))
			this.hide()
		else
			this.show();
	},

	_createRows: function()
	{
		forEach(this._events, disconnect);
		this._events = [];

		MochiKit.DOM.replaceChildNodes($(this.id).firstChild,
		MochiKit.Base.map(
			method(this, function(index_menuRow)
			{
				var index = index_menuRow[0];
				var menuRow = index_menuRow[1];

				return MochiKit.DOM.TR({
						'class': 'clockRow' // use popupclock's CSS for now (why doesn't mouseover/hover work?)
					},
					method(this, function()
					{
						var col0 = null;
						switch (this._mode)
						{
							case 'radio':
								col0 = MochiKit.DOM.INPUT({
									'type': 'radio',
									'id': 'popupMenuRb_' + this.id + '_' + index,
									'name': 'popupMenuStateGroupRb_' + this.id
								});

								if (menuRow.state)
									col0.defaultChecked = menuRow.state; // ok?

								this._events.push(connect(col0, 'onclick', this, function(e)
								{
									e.stopPropagation(); // to allow seeing the change

									for (var i = 0; i < this._menuContent.length; ++i)
										this._menuContent[i].state = false;
									this._menuContent[index].state = e.src().checked;

									signal(this, 'onselect', this._menuContent);
								}));

								break;

							case 'checkbox':
								col0 = MochiKit.DOM.INPUT({
									'id': 'popupMenuCb_' + this.id + '_' + index,
									'type': 'checkbox',
									'name': 'popupMenuStateGroupCb_' + this.id
								});
								col0.defaultChecked = menuRow.state; // why can't this be set directly above? (browser bug? or Mochi?)

								this._events.push(connect(col0, 'onclick', this, function(e)
								{
									e.stopPropagation(); // to allow seeing the change
									this._menuContent[index].state = e.src().checked;
									signal(this, 'onselect', this._menuContent);
								}));

								break;

							case 'stateless':
							default:
								col0 = null;
								break;
						}

						try // IE leak trick
						{
							return [
								MochiKit.DOM.TD({'style': { 'padding': '2px 5px 0px 2px' }},
									col0
								),
								MochiKit.DOM.TD({'style': { 'padding': '2px 5px 0px 2px' }},
									menuRow.text
								)
							];
						}
						finally
						{
							col0 = null; // prevent IE leak
						}
					})
				) // TR
			}),
			MochiKit.Iter.izip(
				MochiKit.Iter.count(),
				this._menuContent
			)
		)
		);
	},

	destroy: function()
	{
		forEach(this._events, disconnect);
		this._events = null;
		disconnectAll(this);

		MochiKit.DOM.removeElement(this.id);
	},

	setMenuContent: function(menuContent)
	{
		this._menuContent = menuContent;
	},

	getMenuContent: function()
	{
		return this._menuContent;
	},

	/**
	 * only run once!
	 * @method attach
	 * @param {DOM} element
	 */
	attach: function(element)
	{
		this._targetElem = element;

		var menuTable =	MochiKit.DOM.TABLE({
			'id': this.id,
			'style': {
				// todo: move to CSS..
				'position': 'absolute', // or use relative? (we're set as child of element anyway, take an offsetPos as param)
				'z-index': 100,
				'background': '#ffffff',
				'border': '1px solid #777',
				'class': 'clockTable', // use popupclock's CSS for now
				'cellspacing': 1,
				'cellpadding': 1,
				'cursor': 'default', // ok?
				'display': 'none' // initially hidden
			}},
			MochiKit.DOM.TBODY(null//,
				//this._createRows()
			) // TBODY
		);

		var body = Franson.DOM.body();
		body.insertBefore(menuTable, body.firstChild);

		this._createRows();

		this._attachEvents();
	},

	// currently assumed to be called after _createRows
	_attachEvents: function()
	{
		MochiKit.Base.extend(this._events, [
			connect(this._targetElem, 'onclick', this, this._popUp),

			connect(this.id, 'onclick', this, function(e)
			{
				this.hide();
				signal(this, 'onselect', this._menuContent);
			}),

			Franson.Event.onOutsideClick(this.id, method(this, function(e)
			{
				if (e.target() != $(this._targetElem))
					this.hide();
			})),

			connect(document, 'onkeydown', this, function(e)
			{
				if (e.key().string == 'KEY_ESCAPE') // add more keys? tab?
					this.hide();
			})
		]);
	},

	//-----

	_saveState: function()
	{
		return {
			id: this.id,
			mode: this._mode,
			rows: this._menuContent
		};
	},

	_restoreState: function(state)
	{
		if (this._mode == state.mode)
		{
			forEach(state.rows, function(rowState)
			{
				var found = false;
				forEach(this._menuContent, function(row)
				{
					if (row.text == rowState.text) // would be nice to be able to use the pharse-keys here...
					{
						found = true;
						row.state = rowState.state;
						throw MochiKit.Iter.StopIteration; // break
					}
				}, this);

				if (!found)
				{
					logWarning('Matching row state not found');
				}
			}, this);

			// .. should rather create a refresh() (shouldn't rely on destroy keeping stuff.. (in this case targetElem)
			if (this._targetElem != null)
			{
				this.attach(this._targetElem);
				signal(this, 'onselect', this._menuContent); // notify observers about new state (always fire?)
			}
		}
	}

};

Copyright © 2009 Franson Technology AB, Sweden. All rights reserved.