Show:

File: ia\ui\Explorer.js

/** 
 * Class for data trees.
 *
 * Requires data in the form of a hashtable:
 *
 * ["i1"]{id:"i1", label:"Home", type:"branch", children:["i2", "i3", "i4"]}
 * ["i2"]{id:"i2", label:"Indicator 1", type:"branch", parent:"i1", children:["i5", "i6", "i7"]}
 * ["i5"]{id:"i5", label:"2004", type:"leaf", parent:"i2"}
 *
 * @author J Clare
 * @class ia.DataExplorer
 * @constructor
 * @param {String} id The id of the explorer.
 * @param {Function} callbackFunction Called when a selection is made. 
 */
ia.DataExplorer = function(id, callbackFunction)
{			
	this.id = id;
	this._data = undefined;
	this._treeId = "";
	this.closeBranchesOnSelection = true;

	// A div to contain the explorer.
	this.container = $j("<div id='"+id+"' class='ia-explorer'>");

	// A ScrollBox.
	this.$sb = $j("<div id='"+id+"-scrollbox' class='ia-explorer-scrollbox'>");
	this._scrollBox = new ia.ScrollBox(this.$sb);
	this.container.append(this.$sb);
	this.$sb.bind(ia.CLICK_TYPE, function(e) {e.stopPropagation();});

	// A div to contain the data tree.
	this.$tree = $j("<div class='ia-explorer-tree'>");
	this.$sb.append(this.$tree);

	if (callbackFunction) this.callbackFunction = callbackFunction;

	var me = this;
	this.container.resize(function(e){me._size();});
};

/** 
 * The id.
 * 
 * @property id
 * @type String
 */
ia.DataExplorer.prototype.id;

/** 
 * The  callback function.  
 *
 * @property callbackFunction
 * @type Function
 */	
ia.DataExplorer.prototype.callbackFunction;

/** 
 * Should the explorer be hidden after selection.
 *
 * @property hideOnSelection
 * @type Boolean
 */
ia.DataExplorer.prototype.hideOnSelection;

/** 
 * Filter explorer.
 *
 * @property isFilterExplorer
 * @type Boolean
 */
ia.DataExplorer.prototype.isFilterExplorer;

/**
 * The container that holds the object.
 * 
 * @property container
 * @type JQUERY Element
 */
ia.DataExplorer.prototype.container;

/**
 * Indicates that other branches should be closed or remain open when a branch is selected.
 * 
 * @property closeBranchesOnSelection
 * @type Boolean
 * @default false
 */
ia.DataExplorer.prototype.closeBranchesOnSelection = false;

/** 
 * Resizes the tree.
 *
 * @method _size
 * @private
 */
ia.DataExplorer.prototype._size = function()
{
	this.$sb.height(this.container.height());
	this.$sb.width(this.container.width());
};

/** 
 * Sets or gets the data.
 *
 * @method data
 * @param {Object} d The data.
 */
ia.DataExplorer.prototype.data = function(d)
{
	if (d !== undefined) this._data = d;
	else return this._data
};

/** 
 * Refreshes the explorer.
 *
 * @method refresh
 */
ia.DataExplorer.prototype.refresh = function()
{
	if (this._treeId !== "") this.build(this._treeId);
};

/** 
 * Builds the tree for the given item.
 *
 * @method build
 * @param {String} id The item id. 
 */
ia.DataExplorer.prototype.build = function(id)
{
	this._treeId = id;
	if (this._treeId === undefined) this._treeId =  "topLevel";

	this.$tree.empty();
	var o = this._data[this._treeId];
	var n = o.children.length;
	for (var i = 0; i < n; i++) 
	{ 	
		var childId = o.children[i];
		this._buildItem(childId, this.$tree);
	}

	this._scrollBox.refresh();
};

/** 
 * Builds the item for the given id - along with all its children.
 *
 * @method _buildItem
 * @param {String} id The item id. 
 * @param {JQUERY Element} $parentItem The items parent. 
 * @private
 */
ia.DataExplorer.prototype._buildItem = function(id, $parentItem)
{
	var itemId = String(id).replace(/'/g, "#quote#").replace(/"/g, "#double-quote#"); // Fix for quotations breaking ids.

	var o = this._data[id];

	// Explorer item.
	var $explorerItem = $j("<div id='"+itemId+"' class='ia-explorer-item ia-list-item'>");
	$parentItem.append($explorerItem);

	// Item label.
	var $label = $j("<div class='ia-explorer-label'>").html(o.label);
	$explorerItem.append($label);

	var me = this;
	if (o.type === "branch")
	{
		$explorerItem.addClass("ia-explorer-branch-icon");
		$explorerItem.addClass("ia-explorer-branch");

		// Child items.
		var $childGroup = $j("<div class='ia-explorer-group'>");
		$childGroup.hide();
		$parentItem.append($childGroup);

		$explorerItem.data("childGroup", $childGroup);

		(function() // Execute immediately
		{ 
			var $item = $explorerItem;
			var $childGroup = $childGroup;

			if (ia.IS_TOUCH_DEVICE)
			{
				$explorerItem.bind(ia.CLICK_TYPE, function(e) 
				{	
					if (!me._scrollBox.isScrolling)
					{
						e.stopPropagation();
						e.preventDefault();
						me._toggleBranch($item)
					}
				});
			}
			else
			{
				$explorerItem.bind(ia.CLICK_TYPE, function(e) 
				{	
					e.stopPropagation(); 
					me._toggleBranch($item)
				});
			}
		})();

		var n = o.children.length;
		for (var i = 0; i < n; i++) 
		{ 	
			var childId = o.children[i];
			me._buildItem(childId, $childGroup);
		}
	}
	else // leaf.
	{			
		$explorerItem.addClass("ia-explorer-leaf");

		// Notes button
		if (o.href !== undefined)
		{
			var $notesBtn = $j("<div class='ia-explorer-notes-icon'>");
			$explorerItem.prepend($notesBtn);

			(function() // Execute immediately
			{ 
				var link = o.href;

				if (ia.IS_TOUCH_DEVICE)
				{
					$notesBtn.bind(ia.CLICK_TYPE, function(e) 
					{	
						if (!me._scrollBox.isScrolling)
						{
							e.stopPropagation();
							e.preventDefault();
							ia.callFunction(link, "_blank");
						}
					});
				}
				else
				{
					$notesBtn.bind(ia.CLICK_TYPE, function(e) 
					{	
						e.stopPropagation();
						ia.callFunction(link, "_blank");
					});
				}
			})();
		}

		(function() // Execute immediately
		{ 
			var $item = $explorerItem;

			if (ia.IS_TOUCH_DEVICE)
			{
				$explorerItem.bind(ia.CLICK_TYPE, function(e) 
				{	
					if (!me._scrollBox.isScrolling)
					{
						e.stopPropagation();
						e.preventDefault();
						me._selectLeaf($item);
					}
				});
			}
			else
			{
				$explorerItem.bind(ia.CLICK_TYPE, function(e) 
				{	
					e.stopPropagation();
					me._selectLeaf($item);
				});
			}
		})(); 
	}
};

/** 
 * Clears any selected items.
 *
 * @method clearSelection
 */
ia.DataExplorer.prototype.clearSelection = function()
{
	this.$tree.find(".ia-explorer-item").removeClass("ia-explorer-selected-item");
	if (this.isFilterExplorer) this.$tree.find(".ia-explorer-leaf").removeClass("ia-filter-explorer-selected-item");
};

/** 
 * Expands the necessary branches to show an item.
 *
 * @method showItem
 * @param {String} id The item id. 
 */
ia.DataExplorer.prototype.showItem = function(id)
{
	var itemId = String(id).replace(/'/g, "#quote#").replace(/"/g, "#double-quote#"); // Fix for quotations breaking ids.

	var $item = this.$tree.find("div[id='"+itemId+"']");
	if ($item) 
	{
		if ($item.hasClass("ia-explorer-branch"))
		{
			this._openBranch($item);
		}
		else if ($item.hasClass("ia-explorer-leaf"))
		{
			// Make this the selected item.
			this.$tree.find(".ia-explorer-leaf").removeClass("ia-explorer-selected-item");
			$item.addClass("ia-explorer-selected-item");
		}
		if (this._data[id] && this._data[id].parent) this.showItem(this._data[id].parent);
	}
};

/** 
 * Selects a leaf item.
 *
 * @method _selectLeaf
 * @param {JQUERY Element} $item The item. 
 * @private
 */
ia.DataExplorer.prototype._selectLeaf = function($item)
{
	var id = $item.attr("id");

	// Make this the selected item.
	this.$tree.find(".ia-explorer-item").removeClass("ia-explorer-selected-item");
	$item.addClass("ia-explorer-selected-item");

	// Special case for filter explorer
	if (this.isFilterExplorer) 
	{
		if ($item.hasClass("ia-filter-explorer-selected-item"))
		{
			id = "clearFilter";
			$item.removeClass("ia-filter-explorer-selected-item");
		}
		else
		{
			this.$tree.find(".ia-explorer-leaf").removeClass("ia-filter-explorer-selected-item");
			$item.addClass("ia-filter-explorer-selected-item");
		}
	}

	var itemId = String(id).replace(/#quote#/g, "'").replace(/#double-quote#/g, '"'); // Fix for quotations breaking ids.
	if (this.callbackFunction) this.callbackFunction.call(null, itemId);
};

/** 
 * Toggles a branch item.
 *
 * @method _toggleBranch
 * @param {JQUERY Element} $item The item. 
 * @private
 */
ia.DataExplorer.prototype._toggleBranch = function($item)
{
	if ($item.hasClass("ia-explorer-branch"))
	{
		this.$tree.find(".ia-explorer-item").removeClass("ia-explorer-selected-item");

		if ($item.data("childGroup").is(":visible"))  
		{
			this._closeBranch($item);
		}
		else  
		{
			// Make this the selected item.
			$item.addClass("ia-explorer-selected-item");
			this._openBranch($item);
		}
	}
};

/** 
 * Closes a branch item.
 *
 * @method _closeBranch
 * @param {JQUERY Element} $item The item. 
 * @private
 */
ia.DataExplorer.prototype._closeBranch = function($item)
{
	if ($item.hasClass("ia-explorer-branch"))
	{
		// Change to open icon.
		$item.addClass("ia-explorer-branch-icon");
		$item.removeClass("ia-explorer-branch-expanded-icon");

		// Close the child items.
		var me = this;
		$item.data("childGroup").slideUp('fast', function()  {me._size();});
	}
};

/** 
 * Opens a branch item.
 *
 * @method _openBranch
 * @param {JQUERY Element} $item The item. 
 * @private
 */
ia.DataExplorer.prototype._openBranch = function($item)
{
	if ($item.hasClass("ia-explorer-branch"))
	{
		// Close any open siblings.
		if (this.closeBranchesOnSelection)
		{
			$item.siblings(".ia-explorer-branch-expanded-icon").each(function(index) 
			{
				$j(this).addClass("ia-explorer-branch-icon");
				$j(this).removeClass("ia-explorer-branch-expanded-icon");
				$j(this).data("childGroup").slideToggle('fast', function() {});
			});
		}

		// Change to close icon.
		$item.removeClass("ia-explorer-branch-icon");
		$item.addClass("ia-explorer-branch-expanded-icon");

		// Slide open the child items.
		var me = this;
		$item.data("childGroup").slideDown('fast', function()  {me._size();});
	}
};