/* * Copyright (C) 2005 - 2011 Jaspersoft Corporation. All rights reserved. * http://www.jaspersoft.com. * * Unless you have purchased a commercial license agreement from Jaspersoft, * the following license terms apply: * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ var baseList = { isResponsive: function(item) { return $(item.up(0)).hasClassName(layoutModule.RESPONSIVE_CLASS); }, isCollapsible: function(item) { return $(item.up(0)).hasClassName(layoutModule.COLLAPSIBLE_CLASS); }, selectItem: function(item) { if (this.isItemDisabled(item)) { return; } $(item).addClassName(layoutModule.SELECTED_CLASS); }, deselectItem: function(item) { $(item).removeClassName(layoutModule.SELECTED_CLASS); }, isItemSelected: function(item) { return $(item).hasClassName(layoutModule.SELECTED_CLASS); }, disableItem: function(item) { buttonManager.disable(item); }, enableItem: function(item) { buttonManager.enable(item); }, isItemDisabled: function(item) { buttonManager.isDisabled(item); }, openItem: function(item) { $(item).removeClassName(layoutModule.CLOSED_CLASS).addClassName(layoutModule.OPEN_CLASS).isOpen = true; }, isItemOpen: function(item) { return ($(item).hasClassName(layoutModule.OPEN_CLASS) || ($(item).isOpen && !$(item).hasClassName(layoutModule.CLOSED_CLASS))); }, closeItem: function(item) { $(item).removeClassName(layoutModule.OPEN_CLASS).addClassName(layoutModule.CLOSED_CLASS).isOpen = false; } }; /////////////////////////////////////////////////////// // Global module for all list related code /////////////////////////////////////////////////////// var dynamicList = { /** * Map of all created lists on current page */ lists: {}, /** * Id of last active list */ activeListId: null, _templateHash: {}, messages: { 'listNItemsSelected': "#{count} items selected" } }; /////////////////////////////////////////////////////// // ListItem /////////////////////////////////////////////////////// /** * Respond on events: dblclick, click, mouseover, mousedown, mouseup, key:down and key:up * Options: * respondOnItemEvents - when false all event will be ignored * excludeFromEventHandling - [selectorPattern1, selectorPattern2] * excludeFromSelectionTriggers - [] * * @param options */ dynamicList.ListItem = function(options) { this._itemId = undefined; this._list = undefined; this.first = false; this.last = false; if (options) { this._value = (options.value) ? options.value : {}; this._label = (options.label) ? options.label : ""; this._subList = options.subList; this._cssClassName = (options.cssClassName) ? options.cssClassName : undefined; this._templateDomId = (options.templateDomId) ? options.templateDomId : undefined; this._respondOnItemEvents = !Object.isUndefined(options.respondOnItemEvents) ? options.respondOnItemEvents : true; this._excludeFromEventHandling = options.excludeFromEventHandling ? options.excludeFromEventHandling : undefined; this._excludeFromSelectionTriggers = options.excludeFromSelectionTriggers ? options.excludeFromSelectionTriggers : undefined; } }; /** * */ dynamicList.ListItem.addVar('DEFAULT_TEMPLATE_DOM_ID', "dynamicListItemTemplate"); dynamicList.ListItem.addVar('DEFAULT_ITEM_ID_PREFIX', "item"); dynamicList.ListItem.addVar('DEFAULT_SUB_LIST_ID_SUFFIX', "SubList"); /////////////////////////////////////////////////////// // Public ListItem methods /////////////////////////////////////////////////////// /** * Gets ID of the item, which is being generated by the list. * @return {Number} ID of the item */ dynamicList.ListItem.addMethod('getId', function() { return this._itemId; }); /** * Sets the list and generates new id in that list. * This method is used by the list to set reference on itself. * * @param list {{@see dynamicList.List}} */ dynamicList.ListItem.addMethod('setList', function(list) { this._list = list; if (this.getList()) { this._itemId = this.getList().getNextItemId(); } }); /** * Gets the list where this item is listed. * * @return {{@see dynamicList.List}} */ dynamicList.ListItem.addMethod('getList', function() { return this._list; }); /** * Sets value of the item. * * @param {String} */ dynamicList.ListItem.addMethod('setValue', function(value) { return this._value = value; }); /** * Gets value of the item. * * @return {String} */ dynamicList.ListItem.addMethod('getValue', function() { return this._value; }); /** * Sets label defined by user. * * @param {Object} */ dynamicList.ListItem.addMethod('setLabel', function(label) { return this._label = label; }); /** * Gets label of the item defined by user. * * @return {Object} */ dynamicList.ListItem.addMethod('getLabel', function() { return this._label; }); /** * Setter and getter for the style of the item. * Changes of the style will be applied after call of method {@see dynamicList.ListItem#refresh} */ dynamicList.ListItem. addMethod('setCssClassName', function(cssClassName) { this._cssClassName = cssClassName; }). addMethod('getCssClassName', function() { return this._cssClassName; }); /** * Sets the ID of DOM element which will be used for rendering of mark up. * Changes of the style will be applied after call of method {@see dynamicList.ListItem#refresh} * * @param templateDomId {String} */ dynamicList.ListItem.addMethod('setTemplateDomId', function(templateDomId) { this._templateDomId = templateDomId; }); /** * Gets the ID of the current template of the item. * Attr * @return {String} */ dynamicList.ListItem.addMethod('getTemplateDomId', function() { return this._templateDomId; }); /** * Render the item specified container. * Use this method when you need re-render item. * * @param container {DOMElement} */ dynamicList.ListItem.addMethod('show', function(container) { if (!container) { return; } this._element = this.processTemplate(this._getTemplate()); this._getElement().setAttribute('id', this._generateId()); this._getElement().setAttribute('tabindex', -1); this.first && this.getList().tabindex && this._getElement().writeAttribute("tabindex", this.getList().tabindex); this._getElement().listItem = this; this.refreshStyle(); var siblings = container.childElements(); var itemIndex = this.index(); var afterIndex = itemIndex - 1; (afterIndex > -1 && afterIndex < siblings.length) ? this._getElement().insert({after: siblings[afterIndex]}) : $(container).insert(this._getElement()); }); /** * */ dynamicList.ListItem.addMethod('refresh', function() { if(!isNotNullORUndefined(this._getElement())) { return; } if(this.getList()) { this._element = this.processTemplate(this._getElement()); this.refreshStyle(); } else { this._getElement().remove(); this._element = null; } }); /** * */ dynamicList.ListItem.addMethod('refreshStyle', function() { var element = this._getElement(); if(element.templateClassName) { element.className = element.templateClassName; } if (this.first) { element.addClassName(layoutModule.FIRST_CLASS); } if (this.last) { element.addClassName(layoutModule.LAST_CLASS); } if (this.isSelected()) { element.addClassName(layoutModule.SELECTED_CLASS); } if (this.isDisabled()) { element.addClassName(layoutModule.DISABLED_CLASS); } if (this.getCssClassName()) { element.addClassName(this.getCssClassName()); } }); /** * */ dynamicList.ListItem.addMethod('isRendered', function() { return isNotNullORUndefined(this._getElement()); }); /** * */ dynamicList.ListItem. addMethod('disable', function() { baseList.disableItem(this._getElement()); }). addMethod('enable', function() { baseList.enableItem(this._getElement()); }). addMethod('isDisabled', function() { return baseList.isItemDisabled(this._getElement()); }); /** * Prepare mark up template of the item before render or process existing mark up when refresh the item. * If template was changed method should be overridden. * * @param {DOMElement} - current item element * @return {DOMElement} - element ready for use */ dynamicList.ListItem.addMethod('processTemplate', function(element) { var wrapper = element.childElements()[0]; wrapper.cleanWhitespace(); var elementsCount = wrapper.childElements().length; if (elementsCount == wrapper.childNodes.length) { wrapper.insert(this.getLabel().escapeHTML()); } else { wrapper.childNodes[elementsCount].data = this.getLabel(); } // $(wrapper.parentNode).writeAttribute("tabIndex",-1); return element; }); /** * focus on this node's element */ dynamicList.ListItem.addMethod('focus', function() { this._getElement().focus(); }); /** * Removes it self from the list. */ dynamicList.ListItem.addMethod('remove', function() { this.getList().removeItems([this]); }); /** * */ dynamicList.ListItem.addMethod('isSelected', function() { return this.getList().isItemSelected(this); }); /** * */ dynamicList.ListItem.addMethod('select', function() { this.getList().selectItem(this, true); }); /** * */ dynamicList.ListItem.addMethod('deselect', function() { this.getList().deselectItem(this); }); dynamicList.ListItem.addMethod('index', function() { this.getList().getItems().indexOf(this); }); /////////////////////////////////////////////////////// // Private ListItem methods /////////////////////////////////////////////////////// /** * Gets or looking for DOM element of this item. * * @return {DOMElement} */ dynamicList.ListItem.addMethod('_getElement', function() { if (!this._element) { var e = $(this._generateId()); this._element = (Object.isElement(e) ? e : undefined); } return this._element; }); dynamicList.ListItem.addMethod('_getTemplate', function() { var id = this._templateDomId; if (!dynamicList._templateHash[id]) { dynamicList._templateHash[id] = id; } var clone = $(dynamicList._templateHash[id]).cloneNode(true); clone.templateId = id; clone.templateClassName = clone.className; return clone; }); dynamicList.ListItem.addMethod('_generateId', function() { return this.getList().getId() + "_" + this.DEFAULT_ITEM_ID_PREFIX + this.getId(); }); dynamicList.ListItem.addMethod('_isElementInExcluded', function(event, item) { var element = event.element(); return this._excludeFromEventHandling && matchAny(element, this._excludeFromEventHandling) != null; }); dynamicList.ListItem.addMethod('_isExcludedFromSelectionTriggers', function(event) { var element = event.element(); return this._excludeFromSelectionTriggers && matchAny(element, this._excludeFromSelectionTriggers) != null; }); /////////////////////////////////////////////////////// // Composite List Item /////////////////////////////////////////////////////// dynamicList.CompositeItem = function (options) { dynamicList.ListItem.call(this, options); this.isComposite = true; this._items = options.items; this._openUp = options.openUp; this._subList = null; this._subListOptions = (options.listOptions) ? options.listOptions : {}; this._listTagName = 'ul'; this.OPEN_HANDLER_PATTERN = (options.openHandlerPattern) ? options.openHandlerPattern : this.OPEN_HANDLER_PATTERN; this.CLOSE_HANDLER_PATTERN = (options.closeHandlerPattern) ? options.closeHandlerPattern : this.CLOSE_HANDLER_PATTERN; }; dynamicList.CompositeItem.prototype = deepClone(dynamicList.ListItem.prototype); dynamicList.CompositeItem.addVar('OPEN_HANDLER_PATTERN', "[openHandler=openHandler]"); dynamicList.CompositeItem.addVar('CLOSE_HANDLER_PATTERN', "[closeHandler=closeHandler]"); dynamicList.CompositeItem.addMethod('getItems', function() { return this._items; }); dynamicList.CompositeItem.addMethod('setItems', function(items) { this._items = items; }); dynamicList.CompositeItem.addMethod('addItem', function(item) { this._items.push(item); }); dynamicList.CompositeItem.addMethod('removeItems', function(items) { this._items = this._items.reject(function(item) { return items.include(item); }); this._subList.removeItems(items); }); dynamicList.CompositeItem.addMethod('show', function(container) { this._listTagName = container.tagName; dynamicList.ListItem.prototype.show.call(this, container); baseList.closeItem(this._getElement()); if(!this._items) { return; } this._showSubList(); }); dynamicList.CompositeItem.addMethod('_showSubList', function() { var id = this._getSubListId(); var subListElement = new Element(this._listTagName, { id: id }); this._getElement().insert(this._openUp ? {top: subListElement} : {bottom: subListElement}); var opts = this._subListOptions; this._subList = new dynamicList.List(id, { responsive: (opts.responsive) ? opts.responsive : this.getList()._responsive, collapsible: (opts.collapsible) ? opts.collapsible : this.getList()._collapsible, multiSelect: (opts.multiSelect) ? opts.multiSelect : this.getList()._multiSelect, cssClassName: (opts.cssClassName) ? opts.cssClassName : this.getList()._cssClassName, listTemplateDomId: (opts.listTemplateDomId) ? opts.listTemplateDomId : this.getList()._listTemplateDomId, itemTemplateDomId: (opts.itemTemplateDomId) ? opts.itemTemplateDomId : this.getList()._itemTemplateDomId, itemCssClassName: (opts.itemCssClassName) ? opts.itemCssClassName : this.getList()._itemCssClassName, comparator: (opts.comparator) ? opts.comparator : this.getList()._comparator, items: this._items }); this._subList._initEvents = function() {}; this._subList.show(); this._subList._parentList = this.getList(); this._subList.getItems().each(function(item) { item.parentItem = this; }.bind(this)); }); dynamicList.CompositeItem.addMethod('refresh', function() { dynamicList.ListItem.prototype.refresh.call(this); if(!this._items) { return; } if (this._subList) { this._subList.refresh(); } else { this._showSubList(); } }); dynamicList.CompositeItem.addMethod('getFirstChild', function() { return this._subList.getItems()[0]; }); dynamicList.CompositeItem.addMethod('refreshStyle', function() { dynamicList.ListItem.prototype.refreshStyle.call(this); if (baseList.isItemOpen(this._getElement())) { baseList.openItem(this._getElement()); } else { baseList.closeItem(this._getElement()); } if(!this._subList) { return; } this._subList.refreshStyle(); }); dynamicList.CompositeItem. addMethod('_isOpenHandler', function(element) { return element.match(this.OPEN_HANDLER_PATTERN); }). addMethod('_isCloseHandler', function(element) {return element.match(this.CLOSE_HANDLER_PATTERN); }); dynamicList.CompositeItem.addMethod('_getSubListId', function() { return this._generateId() + "_" + this.DEFAULT_SUB_LIST_ID_SUFFIX; }); /////////////////////////////////////////////////////// // Templated list item - uses Mustache to process item content as a template. Takes fields of item.getValue() to fill the template /////////////////////////////////////////////////////// dynamicList.TemplatedListItem = function(options) { dynamicList.ListItem.call(this, options); this.tooltipText = options.tooltipText; } //TODO would be nice to be able to get template from uninterpreted script tag, not from DOM part. This would give more flexibility. dynamicList.TemplatedListItem.prototype = new dynamicList.ListItem(); dynamicList.TemplatedListItem.prototype.constructor = dynamicList.TemplatedListItem; dynamicList.TemplatedListItem.prototype.processTemplate = function(element) { var filled = Mustache.to_html(element.innerHTML, this.getValue()); element.innerHTML = filled; if(this.tooltipText != null) { new JSTooltip(element, {text: this.tooltipText}); } return element; } /////////////////////////////////////////////////////// // List Component /////////////////////////////////////////////////////// /** * Dynamically creates items on specified UL element. Supports sorting and ... * * @param id {String} - ID of the UL element, this id will be used as ID of the list. * @param options {JSON Object} * */ dynamicList.List = function(id, options) { this._id = id; this._items = []; this._selectedItems = []; this._lastSelectedItem = null; this._nextId = 1; // private static var this.draggables = []; this._parentList = null; if (options) { this._multiSelect = (options.multiSelect) ? options.multiSelect : false; this._cssClassName = (options.cssClassName) ? options.cssClassName : ""; this._listTemplateDomId = options.listTemplateDomId; this._itemTemplateDomId = options.itemTemplateDomId; this._itemCssClassName = options.itemCssClassName; this._comparator = options.comparator; this._excludeFromEventHandling = options.excludeFromEventHandling; this._excludeFromSelectionTriggers = options.excludeFromSelectionTriggers; this.dragPattern = options.dragPattern; this.selectOnMousedown = options.selectOnMousedown; this.scroll = options.scroll; this.setItems(options.items); } this._createFromTemplate(); this._registerCustomScroll(); dynamicList.activeListId = this.getId(); this._msgNItemsSelected = new Template(dynamicList.messages['listNItemsSelected']); dynamicList.lists[this._id] = this; }; dynamicList.List.addVar('Event', { ITEM_SELECTED: "item:selected", ITEM_UNSELECTED: "item:unselected", ITEM_MOUSEUP: "item:mouseup", ITEM_MOUSEDOWN: "item:mousedown", ITEM_CLICK: "item:click", ITEM_DBLCLICK: "item:dblclick", ITEM_OPEN: "item:open", ITEM_CLOSED: "item:closed", ITEM_CONTEXTMENU: "item:contextmenu", ITEM_BEFORE_SELECT_OR_UNSELECT: "item:beforeSelectOrUnselect" }); dynamicList.List.addVar('DND_WRAPPER_TEMPLATE', "column_two"); dynamicList.List.addVar('DND_ITEM_TEMPLATE', "column_two:resourceName"); /////////////////////////////////////////////////////// // List public methods /////////////////////////////////////////////////////// dynamicList.List.addMethod('getNextItemId', function() { return this._nextId ++; }); /** * Gets ID of the list which can be used to find this list in map {@see dynamicList.lists} * * @return {String} - ID for the list and DOM element */ dynamicList.List.addMethod('getId', function() { return this._id; }); /** * @return {Array} */ dynamicList.List.addMethod('getItems', function() { return this._items; }); /** * Resets list items with new items and sorts items, if comparator is defined. * @param items {Array} - new array of {@link dynamicList.ListItem} */ dynamicList.List.addMethod('setItems', function(items) { if (!items) { return; } this._items = []; this.resetSelected(); this.addItems(items); }); /** * Adds items to array items of list and sorts it, if comparator is defined. * @param items {Array} - array of {@link dynamicList.ListItem} */ dynamicList.List.addMethod('addItems', function(items) { if (!items) { return; } items.compact().each(function(item) { this._prepareListItem(item); this._items.push(item); }.bind(this)); if (this._comparator) { this._items = this._items.sort(this._comparator); } }); /** * Inserts items to appropriate position in array items of list. * * WARNING: Using this function with sortable lists may cause unexpected results * * @param pos {int} - position of element after which new items will be inserted. * @param items {Array} - array of {@link dynamicList.ListItem} */ dynamicList.List.addMethod('insertItems', function(pos, items) { if (!items) { return; } items = items.compact(); items.each(function(item) { this._prepareListItem(item); }.bind(this)); this._items.splice.apply(this._items, [pos, 0].concat(items)); if (this._comparator) { this._items = this._items.sort(this._comparator); } }); /** * Prepare List Item for adding to list. Set List reference, template DOM ID and css class name. */ dynamicList.List.addMethod('_prepareListItem', function(item) { if (!item) { return; } item.setList(this); if (this._itemTemplateDomId && !item.getTemplateDomId()) { // If list has specified template for all items and there is no other template item.setTemplateDomId(this._itemTemplateDomId); } if (this._itemCssClassName && !item.getCssClassName()) { // If list has specified CSS class for all items and the item don't has CSS class item.setCssClassName(this._itemCssClassName); } if (this._excludeFromEventHandling && !item._excludeFromEventHandling) { item._excludeFromEventHandling = this._excludeFromEventHandling; } if (this._excludeFromSelectionTriggers && !item._excludeFromSelectionTriggers) { item._excludeFromSelectionTriggers = this._excludeFromSelectionTriggers; } }); /** * Removes specified items from list * @param items {Array} - array of {@link dynamicList.ListItem} */ dynamicList.List.addMethod('removeItems', function(items) { if (!items || !isArray(items)) { return; } this._items = this._items.reject(function(item) { return items.include(item); }); items.each(function(item) { this.deselectItem(item); }.bind(this)); items.each(function(item) { item.setList(null); item.refresh(); }); }); /** * @param comparator {Function} - */ dynamicList.List.addMethod('sort', function(comparator) { comparator && (this._comparator = comparator); if (this._comparator) { this.getItems().sort(this._comparator); } }); /** * @return {Array} */ dynamicList.List.addMethod('getSelectedItems', function() { return this._selectedItems; }); /** * @param item {dynamicList.ListItem} - */ dynamicList.List.addMethod('isItemSelected', function(item) { return this.getSelectedItems().include(item); }); /** * @param item {dynamicList.ListItem} - */ dynamicList.List.addMethod('selectItem', function(item, isCtrlHeld, isShiftHeld, isContextMenu) { var event = this.fire(this.Event.ITEM_BEFORE_SELECT_OR_UNSELECT, {item: item}); if (event.stopSelectOrUnselect) { return; } // Fix for multiple DnD. // If couple items selected and selectOnMousedown enabled // we need deselect items on mouse up to be able Drag them. if (this._multiSelect && this._selectedItems.length > 1 && this.isItemSelected(item) && !(isCtrlHeld || isShiftHeld || isContextMenu)) { return; } var isContextMenuOnSelected = this.isItemSelected(item) && isContextMenu; var reset = !(this._multiSelect && isCtrlHeld) && !isContextMenuOnSelected; var deselect = this.isItemSelected(item) && isCtrlHeld && !isContextMenuOnSelected; var selectRange = !deselect && isNotNullORUndefined(this._lastSelectedItem) && isShiftHeld; var select = !deselect && !selectRange; if (reset) { this.resetSelected(); } if (deselect && !reset) { this._removeItemFromSelected(item); } if (selectRange) { var start = this._items.indexOf(this._lastSelectedItem); var end = this._items.indexOf(item); var min = Math.min(start, end), max = Math.max(start, end); if (min > -1) { for (var i = min; i <= max; i++) { this._addItemToSelected(this._items[i], false); } } else { this._addItemToSelected(this._items[max], false); } } if (select) { this._addItemToSelected(item, !isShiftHeld); this.cursor = item; } }); /** * @param item {dynamicList.ListItem} - */ dynamicList.List.addMethod('deselectItem', function(item) { this._removeItemFromSelected(item); }); dynamicList.List.addMethod('deselectOthers', function(item, isCtrlHeld, isShiftHeld, isContextMenu) { var event = this.fire(this.Event.ITEM_BEFORE_SELECT_OR_UNSELECT, {item: item}); if (event.stopSelectOrUnselect) { return; } // Fix for multiple DnD. // If couple items selected and selectOnMousedown enabled // we need deselect items on mouse up to be able Drag them. if (this._multiSelect && this._selectedItems.length > 1 && this.isItemSelected(item) && !(isCtrlHeld || isShiftHeld || isContextMenu)) { var items = this._selectedItems.findAll(function(i) { return i != item}); items.each(function(i) { this._removeItemFromSelected(i); }.bind(this)); } }); /** * */ dynamicList.List.addMethod('resetSelected', function(skipParent) { var items = this._selectedItems; this._selectedItems = []; items.each(function(item) { if (item.getList() !== this) { item.getList().resetSelected(true); } item.refreshStyle(); this.fire(this.Event.ITEM_UNSELECTED, {item: item}); }.bind(this)); if (this._parentList && !skipParent) { this._parentList.resetSelected(); } }); /** * @param item - reference item */ dynamicList.List.addMethod('getNextItem', function(item) { var items = this.getItems(); var currentIndex = items.indexOf(item); return ~currentIndex ? this.getItems()[currentIndex + 1] : null; }); /** * @param item - reference item */ dynamicList.List.addMethod('getPreviousItem', function(item) { var items = this.getItems(); var currentIndex = items.indexOf(item); return ~currentIndex ? this.getItems()[currentIndex - 1] : null; }); /** * @param event */ dynamicList.List.addMethod('selectNext', function(event) { var baseEvent = event.memo.targetEvent; var item = (this.cursor)?this.cursor:this.getSelectedItems()[this.getSelectedItems().length-1]; var nextItem = item.getList().getNextItem(item); if (nextItem) { if (isShiftHeld(baseEvent)){ if (this.isItemSelected(nextItem)){ item.getList().deselectItem(item); } else{ this._addItemToSelected(nextItem, false); } } else{ this.resetSelected(); item.getList().selectItem(nextItem); } nextItem._getElement().focus(); this.cursor = nextItem; } }); /** * @param event */ dynamicList.List.addMethod('selectPrevious', function(event) { var baseEvent = event.memo.targetEvent; var item = (this.cursor)?this.cursor:this.getSelectedItems()[0]; var previousItem = item.getList().getPreviousItem(item); if (previousItem) { if (isShiftHeld(baseEvent)){ if (this.isItemSelected(previousItem)){ item.getList().deselectItem(item); } else{ this._addItemToSelected(previousItem, false); } } else{ this.resetSelected(); item.getList().selectItem(previousItem); } previousItem._getElement().focus(); this.cursor = previousItem; } }); /** * @param event */ dynamicList.List.addMethod('selectOutwards', function(event) { var item = this.getSelectedItems()[0]; var element = item._getElement(); var outItem = !baseList.isItemOpen(element) && item.parentItem; if (outItem) { item.deselect(); outItem.select(); outItem._getElement().focus(); } else { baseList.closeItem(element); this.fire(this.Event.ITEM_CLOSED, { targetEvent: event, item: item }); } }); /** * @param event */ dynamicList.List.addMethod('selectInwards', function(event) { var item = this.getSelectedItems()[0]; if(item.isComposite) { var element = item._getElement(); var inItem = baseList.isItemOpen(element) && item.getFirstChild(); if (inItem){ item.deselect(); inItem.select(); element.focus(); } else { baseList.openItem(element); this.fire(this.Event.ITEM_OPEN, { targetEvent: event, item: item }); } } }); /** * Render list items. Use this method when you need to re-render list. */ dynamicList.List.addMethod('show', function() { dynamicList.activeListId = this.getId(); this._getElement().update(); this.getItems().each(function(item, index) { item.first = index === 0; item.last = index === (this.getItems().length - 1); item.show(this._getElement()); }.bind(this)); this.draggables = []; this.scroll && this.scroll.refresh(); this._initEvents(); }); /** * Updates UI from value of the item and remove unused DOM elements (li elements) */ dynamicList.List.addMethod('refresh', function() { this.refreshStyle(); var elements = this._getElement().childElements(); var itemElements = []; this.getItems().each(function(item, index) { item.first = index === 0; item.last = index === (this.getItems().length - 1); if (item.isRendered() ) { if (item.index() != elements.indexOf(item._getElement())) { item._getElement().remove(); item.show(this._getElement()); } else { item.refresh(); } } else { item.show(this._getElement()); } itemElements.push(item._getElement()); }.bind(this)); elements.each(function(e){ if (!itemElements.include(e) && e.parentNode) { e.remove(); } }); //this.scroll && this.scroll.refresh(); }); /** * Refreshing classname of the list */ dynamicList.List.addMethod('refreshStyle', function(clean) { var element = this._getElement(); if(element.templateClassName) { element.className = element.templateClassName; } if(this._cssClassName) { element.addClassName(this._cssClassName); } }); /** * Generates custom event of the list. * * @param eventName */ dynamicList.List.addMethod('fire', function(eventName, memo) { return this._getElement().fire(eventName, memo); }); /** * @param eventName */ dynamicList.List.addMethod('observe', function(eventName, handler) { this._getElement().observe(eventName, handler); }); /** * @param eventName */ dynamicList.List.addMethod('stopObserving', function(eventName, memo) { this._getElement().stopObserving(eventName, handler); }); /////////////////////////////////////////////////////// // List private methods /////////////////////////////////////////////////////// /** * Gets list container * @return {DOMElement} */ dynamicList.List.addMethod('_getElement', function() { if (!this._element) { this._element = $(this.getId()); } return this._element; }); /** * Gets the item from the event in list * * @paran {Event} */ dynamicList.List.addMethod('getItemByEvent', function(event) { if (event) { var element = Event.element(event); //event.originalTarget || event.srcElement; while(element && element.readAttribute && element.readAttribute('id') !== this.getId()) { var item = element.listItem; // if (item && item.getList() != null && item.getList().getId() == this.getId()) { if (item && item.getList() != null) { return item; } else { element = $(element.parentNode); } } } return null; }); dynamicList.List.addMethod('_createFromTemplate', function() { var tabindex = this._getElement().readAttribute("tabindex"); this.tabindex = parseInt(tabindex && tabindex.length > 0 ? tabindex : -1); this._getElement().insert({after: this._getTemplateElement(this._getElement())}); this._getElement().remove(); this._element = null; this._getElement().update(); this.tabindex && this.tabindex.length > 0 && this._getElement().writeAttribute('tabindex', this.tabindex); disableSelectionWithoutCursorStyle(this._getElement()); }); dynamicList.List.addMethod('_getTemplateElement', function(currentElement) { var id = this._listTemplateDomId; if (!dynamicList._templateHash[id]) { dynamicList._templateHash[id] = id; } var clone = $(dynamicList._templateHash[id]).cloneNode(true); clone.writeAttribute("id", this.getId()); //clone.down().writeAttribute("tabIndex", -1); clone.templateId = id; clone.templateClassName = clone.className; cloneCustomAttributes(currentElement, clone); return clone; }); dynamicList.List.addMethod('_addItemToSelected', function(item, remember) { if (item && !this.isItemSelected(item)) { this._selectedItems.push(item); if (remember) { this._lastSelectedItem = item; } // item.focus(); item.refreshStyle(); if (this._parentList) { this._parentList._addItemToSelected(item, remember); } else { this.fire(this.Event.ITEM_SELECTED, {item: item}); } } }); dynamicList.List.addMethod('_removeItemFromSelected', function(item) { if (item && this.isItemSelected(item)) { this._selectedItems = this._selectedItems.without(item); item.refreshStyle(); if (this._parentList) { this._parentList._removeItemFromSelected(item); } else { this.fire(this.Event.ITEM_UNSELECTED, {item: item}); } } }); dynamicList.List.addMethod('_buildDnDOverlay', function(element) { // var wrapper = $(this.DND_WRAPPER_TEMPLATE).cloneNode(true), template = $(this.DND_ITEM_TEMPLATE).cloneNode(true); var items = []; element.setStyle({width: null, height: null}); if (element.items.length > 1) { element.update(this._msgNItemsSelected.evaluate({count: element.items.length})); } else if (element.items.length == 1) { element.update(element.items[0].getLabel()); } }); dynamicList.List.addMethod('_registerCustomScroll', function() { if (!this.scroll && this._getElement()) { var scrollBar = this._getElement().up(layoutModule.SWIPE_SCROLL_PATTERN); if(scrollBar) { var scroll = layoutModule.scrolls.get(scrollBar.identify()); scroll && (this.scroll = scroll); // scroll && (console.log(scroll.wrapper.inspect())); } } }); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // List DnD //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// dynamicList.List.addMethod('createDraggableIfNeeded', function(event) { //make draggable - test in this order - for efficiency //test 1) does the tree have any drag patterns? //test 2) is a draggable already created for the clicked element? //test 3) does clicked element or its ancestors match any draggable patterns? //test 4) is a draggable already created for the clicked element or matching ancestor? //5) if it's complex markup then we go up to parent which matching pattern var thisElem = event.element(); if (this.dragPattern && !this.draggables[thisElem.identify()]) { var matchingElem = matchAny(thisElem, [this.dragPattern], true); if (matchingElem) { //matchingElem = matchingElem.up(this.dragPattern); if (!matchingElem || this.draggables[matchingElem.identify()]) { return; } var item = this.getItemByEvent(event); this.draggables[matchingElem.identify()] = new Draggable(matchingElem, { superghosting: true, mouseOffset: true, onStart: this.setDragStartState.bind(this, item), onEnd: this.setDragEndState.bind(this, item) }); } } }); dynamicList.List.addMethod('setDragStartState', function(item, draggable, event) { var templateClassName = item._getElement().templateClassName; if (templateClassName) { draggable.element.addClassName(templateClassName); } draggable.element.addClassName(layoutModule.DRAGGING_CLASS).addClassName(this.getId()); draggable.element.items = this.getSelectedItems().slice(0); this._buildDnDOverlay(draggable.element); draggable.options.scroll = this._getElement(); draggable.options.scrollSensitivity = layoutModule.SCROLL_SENSITIVITY; Draggables.dragging = this.regionID || true; }); dynamicList.List.addMethod('setDragEndState', function(draggable, event, item) { delete Draggables.dragging; }); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // List event handling //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// dynamicList.List.addMethod('_mouseupHandler', function(event) { var element = event.element(); var item = matchMeOrUp(element, layoutModule.BUTTON_PATTERN) && this.getItemByEvent(event); if(!item || item._isElementInExcluded(event)) { return; } event.listEvent = true; if (item._respondOnItemEvents && !event.isInvoked) { this.fire(this.Event.ITEM_MOUSEUP, { targetEvent: event, item: item }); if(!item._isExcludedFromSelectionTriggers(event)){ var isSelect = !this.selectOnMousedown && !TouchController.element_scrolled && (!isSupportsTouch() || event.changedTouches.length >= 1); isSelect && item.getList().selectItem(item, isMetaHeld(event), isShiftHeld(event), isRightClick(event)); item.getList().deselectOthers(item, isMetaHeld(event), isShiftHeld(event), isRightClick(event)); if(this.twofingers){ this.twofingers = false; var li = jQuery(element).parents('li:first'); li.hasClass('selected') && document.fire(layoutModule.ELEMENT_CONTEXTMENU, {targetEvent: event, node: element}); } } this.createDraggableIfNeeded(event); } event.isInvoked = true; }); dynamicList.List.addMethod('_mousedownHandler', function(event) { event = (event.type == 'dataavailable') ? event.memo.targetEvent : event; var element = event.element(); var item = matchMeOrUp(element, layoutModule.BUTTON_PATTERN) && this.getItemByEvent(event); if(!item || item._isElementInExcluded(event)) { return; } event.listEvent = true; if(event.touches && event.touches.length == 2) { this.twofingers = true; //var li = jQuery(element).parents('li:first'); //li.hasClass('selected') && document.fire(layoutModule.ELEMENT_CONTEXTMENU, {targetEvent: event, node: element}); //return; } else { this.twofingers = false; } if (item._respondOnItemEvents && !event.isInvoked) { this.fire(this.Event.ITEM_MOUSEDOWN, { targetEvent: event, item: item }); if(!item._isExcludedFromSelectionTriggers(event)){ var isSelect = this.selectOnMousedown;// && (!isSupportsTouch() || event.touches.length == 1); isSelect && item.getList().selectItem(item, isMetaHeld(event), isShiftHeld(event), isRightClick(event)); } //item.focus(); } event.isInvoked = true; }); dynamicList.List.addMethod('_mouseoverHandler', function(event) { matchMeOrUp(event.element(), layoutModule.BUTTON_PATTERN) && this.createDraggableIfNeeded(event); }); dynamicList.List.addMethod('_clickHandler', function(event) { var item = matchMeOrUp(event.element(), layoutModule.BUTTON_PATTERN) && this.getItemByEvent(event); if(!item || item._isElementInExcluded(event)) { return; } if (!event.isInvoked) { if (item._respondOnItemEvents) { this.fire(this.Event.ITEM_CLICK, { targetEvent: event, item: item }); } if(!item.isComposite) return; var element = item._getElement(), source = event.element(); if (item._isCloseHandler(source) && baseList.isItemOpen(element)) { baseList.closeItem(element); this.fire(this.Event.ITEM_CLOSED, { targetEvent: event, item: item }); } else if (item._isOpenHandler(source) && !baseList.isItemOpen(element)) { baseList.openItem(element); this.fire(this.Event.ITEM_OPEN, { targetEvent: event, item: item }); } } event.isInvoked = true; }); dynamicList.List.addMethod('_dblclickHandler', function(event) { var item = matchMeOrUp(event.element(), layoutModule.BUTTON_PATTERN) && this.getItemByEvent(event); if(!item || item._isElementInExcluded(event)) { return; } if (item._respondOnItemEvents && !event.isInvoked) { this.fire(this.Event.ITEM_DBLCLICK, { targetEvent: event, item: item }); } event.isInvoked = true; }); dynamicList.List.addMethod('_initEvents', function() { var container = this._getElement(); this.draggables = []; if(isSupportsTouch()) { container.stopObserving('touchstart').observe('touchstart', this._mousedownHandler.bindAsEventListener(this)); //scriptaculous stopped mousedown event but we made it throw this instead container.stopObserving('drag:touchstart').observe('drag:touchstart', this._mousedownHandler.bindAsEventListener(this)); container.stopObserving('touchend').observe('touchend', this._mouseupHandler.bindAsEventListener(this)); } else { container.stopObserving('mouseup').observe('mouseup', this._mouseupHandler.bindAsEventListener(this)); container.stopObserving('mousedown').observe('mousedown', this._mousedownHandler.bindAsEventListener(this)); //scriptaculous stopped mousedown event but we made it throw this instead container.stopObserving('drag:mousedown').observe('drag:mousedown', this._mousedownHandler.bindAsEventListener(this)); } if(!isIPad) container.stopObserving('mouseover').observe('mouseover', this._mouseoverHandler.bindAsEventListener(this)); container.stopObserving('click').observe('click', this._clickHandler.bindAsEventListener(this)); container.stopObserving('dblclick').observe('dblclick', this._dblclickHandler.bindAsEventListener(this)); container.stopObserving('key:down').observe('key:down', this.selectNext.bindAsEventListener(this)); container.stopObserving('key:up').observe('key:up', this.selectPrevious.bindAsEventListener(this)); container.stopObserving('key:right').observe('key:right', this.selectInwards.bindAsEventListener(this)); container.stopObserving('key:left').observe('key:left', this.selectOutwards.bindAsEventListener(this)); });