UNPKG

kekule

Version:

Open source JavaScript toolkit for chemoinformatics

432 lines (421 loc) 12.2 kB
/** * @fileoverview * Base class of container that can hold a set of nested children (e.g., menu, tree view). * @author Partridge Jiang */ /* * requires /lan/classes.js * requires /utils/kekule.utils.js * requires /utils/kekule.domUtils.js * requires /xbrowsers/kekule.x.js * requires /widgets/kekule.widget.base.js */ (function(){ "use strict"; /** @ignore */ Kekule.Widget.HtmlClassNames = Object.extend(Kekule.Widget.HtmlClassNames, { /* Indicate that all children of current item is hidden. */ STATE_COLLAPSED: 'K-State-Collapsed', STATE_EMPTY: 'K-State-Empty', NESTED_CONTAINER: 'K-NestedContainer', NESTED_CONTAINER_ITEM: 'K-NestedContainer-Item' }); /** * An abstract nested container. * @class * @augments Kekule.Widget.BaseWidget * * @property {Bool} itemInitialExpanded */ Kekule.Widget.NestedContainer = Class.create(Kekule.Widget.BaseWidget, /** @lends Kekule.Widget.NestedContainer# */ { /** @private */ CLASS_NAME: 'Kekule.Widget.NestedContainer', /** @private */ DEF_CONTAINER_TAG: 'ul', /** @private */ DEF_ITEM_TAG: 'li', /** @private */ ITEM_DATA_FIELD: '__$itemData__', /** @construct */ initialize: function(/*$super, */parentOrElementOrDocument) { this.tryApplySuper('initialize', [parentOrElementOrDocument]) /* $super(parentOrElementOrDocument) */; }, /** @private */ initProperties: function() { this.defineProp('itemInitialExpanded', {'dataType': DataType.BOOL}); }, /** @ignore */ doCreateRootElement: function(doc) { var result = doc.createElement('div'); return result; }, /** @ignore */ doBindElement: function(/*$super, */element) { this.tryApplySuper('doBindElement', [element]) /* $super(element) */; }, /** @private */ getContainerElemTag: function() { return this.DEF_CONTAINER_TAG; }, /** @private */ getItemElemTag: function() { return this.DEF_ITEM_TAG; }, /** @private */ _createChildItemElem: function() { var result = this.doCreateChildItemElem(); Kekule.HtmlElementUtils.addClass(result, Kekule.Widget.HtmlClassNames.NESTED_CONTAINER_ITEM); Kekule.HtmlElementUtils.addClass(result, Kekule.Widget.HtmlClassNames.STATE_EMPTY); return result; }, /** * Do actual work of _createChildItemElem. * Descendants may override this method. * @private */ doCreateChildItemElem: function() { return this.getDocument().createElement(this.getItemElemTag()); }, /** @private */ _createChildContainerElem: function() { var result = this.doCreateChildContainerElem(); Kekule.HtmlElementUtils.addClass(result, Kekule.Widget.HtmlClassNames.NESTED_CONTAINER); return result; }, /** * Do actual work of _createChildItemElem. * Descendants may override this method. * @private */ doCreateChildContainerElem: function() { return this.getDocument().createElement(this.getContainerElemTag()); }, /** * Returns container element to hold all children in an item. * Descendants can override this method. * @param {HTMLElement} itemElem * @param {Bool} canCreate If true, a new container element will be created if none exists there. * @returns {HTMLElement} * @private */ getChildContainerElem: function(itemElem, canCreate) { if (!itemElem) itemElem = this.getElement(); var elems = itemElem.getElementsByTagName(this.getContainerElemTag()); var result = elems.length? elems[0]: null; if (!result && canCreate) { result = this._createChildContainerElem(); itemElem.appendChild(result); } return result; }, /** * Returns data previously set to itemElem. * @param {HTMLElement} itemElem * @returns {Object} */ getItemData: function(itemElem) { return itemElem[this.ITEM_DATA_FIELD]; }, /** * Set data associated with itemElem. * If data has an array field named "children", child items will automatically be added */ setItemData: function(itemElem, data) { this.doSetItemData(itemElem, data); itemElem[this.ITEM_DATA_FIELD] = data; var childrenData = data.children; if (childrenData && Kekule.ArrayUtils.isArray(childrenData)) { this.clearChildItems(itemElem); for (var i = 0, l = childrenData.length; i < l; ++i) { var childData = childrenData[i]; this.appendChildItem(itemElem, childData); } } return this; }, /** * Do actual work of method setItemData. * Descendants should override this method. * @param {HTMLElement} childItemElem * @param {Object} data * @private */ doSetItemData: function(itemElem, data) { // do nothing here }, /** * Check if there are child under parentItem. * @param {HTMLElement} parentItem * @returns {Bool} */ hasChildItem: function(parentItem) { return this.getChildItemCount(parentItem) > 0; }, /** * Returns child item count in a parent item. * @param {HTMLElement} parentItem * @returns {Int} */ getChildItemCount: function(parentItem) { return this.getChildren(parentItem).length; }, /** * Returns all child item elements/ * @param {HTMLElement} parentItem * @returns {HTMLElementCollection} */ getChildren: function(parentItem) { var containerElem = this.getChildContainerElem(parentItem); if (containerElem) { var result = Kekule.DomUtils.getDirectChildElems(containerElem, this.getItemElemTag()); return result; } else return []; }, /** * Returns child item element at index. * @param {HTMLElement} parentItem * @param {Int} index * @returns {HTMLElement} */ getChildItemAt: function(parentItem, index) { var children = this.getChildren(parentItem); return (children.length && (children.length > index))? children[index]: null; }, /** * Insert a child item with data to parentItemElem. * @param {HTMLElement} parentItemElem Set to null to insert to root directly. * @param {Variant} childItemElemOrData HTML element or object of data. * @param {Variant} indexOrRefItem Int or HTMLElement. * @returns {HTMLElement} Element added. */ insertChildItem: function(parentItemElem, childItemElemOrData, indexOrRefItem) { var result; var containerElem = this.getChildContainerElem(parentItemElem, true); //if (childItemElemOrData instanceof HTMLElement) // an element, HTMLElement may cause undefined class error in IE //if (childItemElemOrData && (childItemElemOrData.tagName && childItemElemOrData.nodeType)) // is element if (childItemElemOrData && Kekule.DomUtils.isElement(childItemElemOrData)) { result = childItemElemOrData; } else // data object { var result = this._createChildItemElem(); if (!this.getItemInitialExpanded()) Kekule.HtmlElementUtils.addClass(result, Kekule.Widget.HtmlClassNames.STATE_COLLAPSED); if (childItemElemOrData) this.setItemData(result, childItemElemOrData); } var refChild = DataType.isSimpleType(indexOrRefItem)? this.getChildren(parentItemElem)[indexOrRefItem]: indexOrRefItem; if (refChild) containerElem.insertBefore(result, refChild); else containerElem.appendChild(result); this.childItemsChanged(parentItemElem); return result; }, /** * Append a child item with data to parentItemElem. * @param {HTMLElement} parentItemElem Set to null to insert to root directly. * @param {Variant} childItemElemOrData HTML element or object of data. * @returns {HTMLElement} Element added. */ appendChildItem: function(parentItemElem, childItemElemOrData) { return this.insertChildItem(parentItemElem, childItemElemOrData); }, /** * Remove a child item from parent. * @param {HTMLElement} parentItemElem * @param {HTMLElement} childItemElem */ removeChildItem: function(parentItemElem, childItemElem) { var container = this.getChildContainerElem(parentItemElem); if (container) { container.removeChild(childItemElem); if (!this.hasChildItem(parentItemElem)) // parent empty, remove container element parentItemElem.removeChild(container); this.childItemsChanged(parentItemElem); } return this; }, /** * Remove a child item from parent. * @param {HTMLElement} parentItemElem * @param {Int} index */ removeChildItemAt: function(parentItemElem, index) { var child = this.getChildItemAt(parentItemElem, index); if (child) this.removeChildItem(parentItemElem, child); return this; }, /** * Clear all child items from parent. * @param {HTMLElement} parentItemElem */ clearChildItems: function(parentItemElem) { var container = this.getChildContainerElem(parentItemElem); if (!parentItemElem) parentItemElem = this.getElement(); if (container) parentItemElem.removeChild(container); this.childItemsChanged(parentItemElem); }, /** * Notify that the child items has been removed or added to parent item. * Descendants can override this method. * @param {HTMLElement} parentItemElem */ childItemsChanged: function(parentItem) { if (parentItem) // not root { var c = Kekule.Widget.HtmlClassNames.STATE_EMPTY; if (this.hasChildItem(parentItem)) Kekule.HtmlElementUtils.removeClass(parentItem, c); else Kekule.HtmlElementUtils.addClass(parentItem, c); } }, /** * Expand an item. * @param {HTMLElement} item */ expandItem: function(item) { Kekule.HtmlElementUtils.removeClass(item, Kekule.Widget.HtmlClassNames.STATE_COLLAPSED); return this; }, /** * Collapse an item. * @param {HTMLElement} item */ collapseItem: function(item) { Kekule.HtmlElementUtils.addClass(item, Kekule.Widget.HtmlClassNames.STATE_COLLAPSED); return this; }, /** * Collapse expanded item or expand collapsed item. * @param {HTMLElement} item */ toggleExpandStateOfItem: function(item) { Kekule.HtmlElementUtils.toggleClass(item, Kekule.Widget.HtmlClassNames.STATE_COLLAPSED); return this; }, /** * Check if element is an child item of container. * @param {HTMLElement} element * @returns {Bool} */ isChildItem: function(element) { return Kekule.HtmlElementUtils.hasClass(element, Kekule.Widget.HtmlClassNames.NESTED_CONTAINER_ITEM); }, /** * Get nearest child item. Element is a child item itself or its child element. * @param {HTMLElement} element * @returns {HTMLElement} */ getBelongedChildItem: function(element) { var result = element; var root = this.getElement(); var body = this.getDocument().body; while ((result !== root) && (result !== body)) { result = result.parentNode; if (this.isChildItem(result)) return result; } return null; }, /** * List all child items in order in rootItem. * @param {HTMLElement} rootItem If not set, this method will returns all items in container. * @returns {Array} */ getAllChildItems: function(rootItem) { var result = []; var containerElem = this.getChildContainerElem(rootItem, false); if (containerElem) { var children = Kekule.DomUtils.getDirectChildElems(containerElem); for (var i = 0, l = children.length; i < l; ++i) { var child = children[i]; if (this.isChildItem(child)) { result.push(child); var items = this.getAllChildItems(child); result = result.concat(items); } } } return result; }, /** * Returns all items between item1 and item2 (including item1 and item2). * @param {HTMLElement} item1 * @param {HTMLElement} item2 * @param {HTMLElement} rootItem Set to null to search in whole widget. * @returns {Array} */ getChildItemRange: function(item1, item2, root) { var allItems = this.getAllChildItems(root); var index1 = allItems.indexOf(item1); var index2 = allItems.indexOf(item2); if ((index1 >= 0) && (index2 >= 0)) { if (index1 > index2) { var reversed = true; var temp = index1; index1 = index2; index2 = temp; } var result = allItems.slice(index1, index2 + 1); if (reversed) result = result.reverse(); return result; } else return []; } }); })();