This repository has been archived on 2018-10-12. You can view files and clone it, but cannot push or open issues or pull requests.
node-task/public/js/ink.treeview.js

245 lines
9.1 KiB
JavaScript
Raw Normal View History

2014-09-24 17:57:25 -04:00
/**
* Elements in a tree structure
* @module Ink.UI.TreeView_1
* @version 1
*/
Ink.createModule('Ink.UI.TreeView', '1', ['Ink.UI.Common_1','Ink.Dom.Event_1','Ink.Dom.Css_1','Ink.Dom.Element_1','Ink.Dom.Selector_1','Ink.Util.Array_1'], function(Common, Event, Css, Element, Selector, InkArray ) {
'use strict';
/**
* Shows elements in a tree structure which can be expanded and contracted.
* A TreeView is built with "node"s and "children". "node"s are `li` tags, and "children" are `ul` tags.
* You can build your TreeView out of a regular UL and LI element structure which you already use to display lists with several levels.
* If you want a node to be open when the TreeView is built, just add the data-open="true" attribute to it.
*
* @class Ink.UI.TreeView
* @constructor
* @version 1
* @param {String|DOMElement} selector Element or selector.
* @param {String} [options] Options object, containing:
* @param {String} [options.node] Selector for the nodes. Defaults to 'li'.
* @param {String} [options.children] Selector for the children. Defaults to 'ul'.
* @param {String} [options.parentClass] CSS classes to be added to parent nodes. Defaults to 'parent'.
* @param {String} [options.openClass] CSS classes to be added to the icon when a parent is open. Defaults to 'fa fa-minus-circle'.
* @param {String} [options.closedClass] CSS classes to be added to the icon when a parent is closed. Defaults to 'fa fa-plus-circle'.
* @param {String} [options.hideClass] CSS Class to toggle visibility of the children. Defaults to 'hide-all'.
* @param {String} [options.iconTag] The name of icon tag. The component tries to find a tag with that name as a direct child of the node. If it doesn't find it, it creates it. Defaults to 'i'.
* @param {Boolean} [options.stopDefault] Flag to stops the default behavior of the click handler. Defaults to true.
* @example
* <ul class="ink-tree-view">
* <li data-open="true"><a href="#">root</a>
* <ul>
* <li><a href="#">child 1</a></li>
* <li><a href="#">child 2</a>
* <ul>
* <li><a href="#">grandchild 2a</a></li>
* <li><a href="#">grandchild 2b</a>
* <ul>
* <li><a href="#">grandgrandchild 1bA</a></li>
* <li><a href="#">grandgrandchild 1bB</a></li>
* </ul>
* </li>
* </ul>
* </li>
* <li><a href="#">child 3</a></li>
* </ul>
* </li>
* </ul>
* <script>
* Ink.requireModules( ['Ink.Dom.Selector_1','Ink.UI.TreeView_1'], function( Selector, TreeView ){
* var treeViewElement = Ink.s('.ink-tree-view');
* var treeViewObj = new TreeView( treeViewElement );
* });
* </script>
*
* @sample Ink_UI_TreeView_1.html
*/
function TreeView() {
Common.BaseUIComponent.apply(this, arguments);
}
TreeView._name = 'TreeView_1';
TreeView._optionDefinition = {
'node': ['String', 'li'],
// [3.0.1] Deprecate this terrible, terrible name
'child': ['String',null],
'children': ['String','ul'],
'parentClass': ['String','parent'],
'openNodeClass': ['String', 'open'],
'openClass': ['String','fa fa-minus-circle'],
'closedClass': ['String','fa fa-plus-circle'],
'hideClass': ['String','hide-all'],
'iconTag': ['String', 'i'],
'stopDefault' : ['Boolean', true]
};
TreeView.prototype = {
/**
* Init function called by the constructor. Sets the necessary event handlers.
*
* @method _init
* @private
*/
_init: function(){
if (this._options.child) {
Ink.warn('Ink.UI.TreeView: options.child is being renamed to options.children.');
this._options.children = this._options.child;
}
this._handlers = {
click: Ink.bindEvent(this._onClick,this)
};
Event.on(this._element, 'click', this._options.node, this._handlers.click);
InkArray.each(Ink.ss(this._options.node, this._element), Ink.bind(function(item){
if( this.isParent(item) ) {
Css.addClassName(item, this._options.parentClass);
var isOpen = this.isOpen(item);
if( !this._getIcon(item) ){
Element.create(this._options.iconTag, { insertTop: item });
}
this._setNodeOpen(item, isOpen);
}
},this));
},
_getIcon: function (node) {
return Ink.s('> ' + this._options.iconTag, node);
},
/**
* Checks if a node is open.
*
* @method isOpen
* @param {DOMElement} node The tree node to check
**/
isOpen: function (node) {
if (!this._getChild(node)) {
throw new Error('not a node!');
}
return Element.data(node).open === 'true' ||
Css.hasClassName(node, this._options.openNodeClass);
},
/**
* Checks if a node is a parent.
*
* @method isParent
* @param {DOMElement} node Node to check
**/
isParent: function (node) {
return Css.hasClassName(node, this._options.parentClass) ||
this._getChild(node) != null;
},
_setNodeOpen: function (node, beOpen) {
var child = this._getChild(node);
if (child) {
Css.setClassName(child, this._options.hideClass, !beOpen);
var icon = this._getIcon(node);
node.setAttribute('data-open', beOpen);
/*
* Don't refactor this to
*
* setClassName(el, className, status); setClassName(el, className, !status);
*
* because it won't work with multiple classes.
*
* Doing:
* setClassName(el, 'fa fa-whatever', true);setClassName(el, 'fa fa-whatever-else', false);
*
* will remove 'fa' although it is a class we want.
*/
var toAdd = beOpen ? this._options.openClass : this._options.closedClass;
var toRemove = beOpen ? this._options.closedClass : this._options.openClass;
Css.removeClassName(icon, toRemove);
Css.addClassName(icon, toAdd);
Css.setClassName(node, this._options.openNodeClass, beOpen);
} else {
Ink.error('Ink.UI.TreeView: node', node, 'is not a node!');
}
},
/**
* Opens one of the tree nodes
*
* Make sure you pass the node's DOMElement
* @method open
* @param {DOMElement} node The node you wish to open.
**/
open: function (node) {
this._setNodeOpen(node, true);
},
/**
* Closes one of the tree nodes
*
* Make sure you pass the node's DOMElement
* @method close
* @param {DOMElement} node The node you wish to close.
**/
close: function (node) {
this._setNodeOpen(node, false);
},
/**
* Toggles a node state
*
* @method toggle
* @param {DOMElement} node The node to toggle.
**/
toggle: function (node) {
if (this.isOpen(node)) {
this.close(node);
} else {
this.open(node);
}
},
_getChild: function (node) {
return Selector.select(this._options.children, node)[0] || null;
},
/**
* Handles the click event (as specified in the _init function).
*
* @method _onClick
* @param {Event} event
* @private
*/
_onClick: function(ev){
/**
* Summary:
* If the clicked element is a "node" as defined in the options, will check if it has any "child".
* If so, will toggle its state and stop the event's default behavior if the stopDefault option is true.
**/
if (!this.isParent(ev.currentTarget) ||
Selector.matchesSelector(ev.target, this._options.node) ||
Selector.matchesSelector(ev.target, this._options.children)) {
return;
}
if (this._options.stopDefault){
ev.preventDefault();
}
this.toggle(ev.currentTarget);
}
};
Common.createUIComponent(TreeView);
return TreeView;
});