/** @module dojo/dnd/Selector **/ define([ "../_base/array", "../_base/declare", "../_base/kernel", "../_base/lang", "../dom", "../dom-construct", "../mouse", "../_base/NodeList", "../on", "../touch", "./common", "./Container" ], function (array, declare, kernel, lang, dom, domConstruct, mouse, NodeList, on, touch, dnd, Container) { // module: // dojo/dnd/Selector /* Container item states: "" - an item is not selected "Selected" - an item is selected "Anchor" - an item is selected, and is an anchor for a "shift" selection */ /*===== var __SelectorArgs = declare([Container.__ContainerArgs], { // singular: Boolean // allows selection of only one element, if true singular: false, // autoSync: Boolean // autosynchronizes the source with its list of DnD nodes, autoSync: false }); =====*/ /** * @class module:dojo/dnd/Selector * @extends module:dojo/dnd/Container */ var Selector = declare("dojo.dnd.Selector", Container, { // summary: // a Selector object, which knows how to select its children /*===== // selection: Set // The set of id's that are currently selected, such that this.selection[id] == 1 // if the node w/that id is selected. Can iterate over selected node's id's like: // | for(var id in this.selection) selection: {}, =====*/ constructor: function (node, params) { // summary: // constructor of the Selector // node: Node||String // node or node's id to build the selector on // params: __SelectorArgs? // a dictionary of parameters if (!params) { params = {}; } this.singular = params.singular; this.autoSync = params.autoSync; // class-specific variables this.selection = {}; this.anchor = null; this.simpleSelection = false; // set up events this.events.push( on(this.node, touch.press, lang.hitch(this, "onMouseDown")), on(this.node, touch.release, lang.hitch(this, "onMouseUp")) ); }, // object attributes (for markup) singular: false, // is singular property // methods getSelectedNodes: function () { // summary: // returns a list (an array) of selected nodes var t = new NodeList(); var e = dnd._empty; for (var i in this.selection) { if (i in e) { continue; } t.push(dom.byId(i)); } return t; // NodeList }, selectNone: function () { // summary: // unselects all items return this._removeSelection()._removeAnchor(); // self }, selectAll: function () { // summary: // selects all items this.forInItems(function (data, id) { this._addItemClass(dom.byId(id), "Selected"); this.selection[id] = 1; }, this); return this._removeAnchor(); // self }, deleteSelectedNodes: function () { // summary: // deletes all selected items var e = dnd._empty; for (var i in this.selection) { if (i in e) { continue; } var n = dom.byId(i); this.delItem(i); domConstruct.destroy(n); } this.anchor = null; this.selection = {}; return this; // self }, forInSelectedItems: function (/*Function*/ f, /*Object?*/ o) { // summary: // iterates over selected items; // see `dojo/dnd/Container.forInItems()` for details o = o || kernel.global; var s = this.selection, e = dnd._empty; for (var i in s) { if (i in e) { continue; } f.call(o, this.getItem(i), i, this); } }, sync: function () { // summary: // sync up the node list with the data map Selector.superclass.sync.call(this); // fix the anchor if (this.anchor) { if (!this.getItem(this.anchor.id)) { this.anchor = null; } } // fix the selection var t = [], e = dnd._empty; for (var i in this.selection) { if (i in e) { continue; } if (!this.getItem(i)) { t.push(i); } } array.forEach(t, function (i) { delete this.selection[i]; }, this); return this; // self }, insertNodes: function (addSelected, data, before, anchor) { // summary: // inserts new data items (see `dojo/dnd/Container.insertNodes()` method for details) // addSelected: Boolean // all new nodes will be added to selected items, if true, no selection change otherwise // data: Array // a list of data items, which should be processed by the creator function // before: Boolean // insert before the anchor, if true, and after the anchor otherwise // anchor: Node // the anchor node to be used as a point of insertion var oldCreator = this._normalizedCreator; this._normalizedCreator = function (item, hint) { var t = oldCreator.call(this, item, hint); if (addSelected) { if (!this.anchor) { this.anchor = t.node; this._removeItemClass(t.node, "Selected"); this._addItemClass(this.anchor, "Anchor"); } else if (this.anchor != t.node) { this._removeItemClass(t.node, "Anchor"); this._addItemClass(t.node, "Selected"); } this.selection[t.node.id] = 1; } else { this._removeItemClass(t.node, "Selected"); this._removeItemClass(t.node, "Anchor"); } return t; }; Selector.superclass.insertNodes.call(this, data, before, anchor); this._normalizedCreator = oldCreator; return this; // self }, destroy: function () { // summary: // prepares the object to be garbage-collected Selector.superclass.destroy.call(this); this.selection = this.anchor = null; }, // mouse events onMouseDown: function (e) { // summary: // event processor for onmousedown // e: Event // mouse event if (this.autoSync) { this.sync(); } if (!this.current) { return; } if (!this.singular && !dnd.getCopyKeyState(e) && !e.shiftKey && (this.current.id in this.selection)) { this.simpleSelection = true; if (mouse.isLeft(e)) { // Accept the left button and stop the event. Stopping the event prevents text selection while // dragging. However, don't stop the event on mobile because that prevents a click event, // and also prevents scroll (see #15838). // For IE we don't stop event when multiple buttons are pressed. e.stopPropagation(); e.preventDefault(); } return; } if (!this.singular && e.shiftKey) { if (!dnd.getCopyKeyState(e)) { this._removeSelection(); } var c = this.getAllNodes(); if (c.length) { if (!this.anchor) { this.anchor = c[0]; this._addItemClass(this.anchor, "Anchor"); } this.selection[this.anchor.id] = 1; if (this.anchor != this.current) { var i = 0, node; for (; i < c.length; ++i) { node = c[i]; if (node == this.anchor || node == this.current) { break; } } for (++i; i < c.length; ++i) { node = c[i]; if (node == this.anchor || node == this.current) { break; } this._addItemClass(node, "Selected"); this.selection[node.id] = 1; } this._addItemClass(this.current, "Selected"); this.selection[this.current.id] = 1; } } } else { if (this.singular) { if (this.anchor == this.current) { if (dnd.getCopyKeyState(e)) { this.selectNone(); } } else { this.selectNone(); this.anchor = this.current; this._addItemClass(this.anchor, "Anchor"); this.selection[this.current.id] = 1; } } else { if (dnd.getCopyKeyState(e)) { if (this.anchor == this.current) { delete this.selection[this.anchor.id]; this._removeAnchor(); } else { if (this.current.id in this.selection) { this._removeItemClass(this.current, "Selected"); delete this.selection[this.current.id]; } else { if (this.anchor) { this._removeItemClass(this.anchor, "Anchor"); this._addItemClass(this.anchor, "Selected"); } this.anchor = this.current; this._addItemClass(this.current, "Anchor"); this.selection[this.current.id] = 1; } } } else { if (!(this.current.id in this.selection)) { this.selectNone(); this.anchor = this.current; this._addItemClass(this.current, "Anchor"); this.selection[this.current.id] = 1; } } } } e.stopPropagation(); e.preventDefault(); }, onMouseUp: function (/*===== e =====*/) { // summary: // event processor for onmouseup // e: Event // mouse event if (!this.simpleSelection) { return; } this.simpleSelection = false; this.selectNone(); if (this.current) { this.anchor = this.current; this._addItemClass(this.anchor, "Anchor"); this.selection[this.current.id] = 1; } }, onMouseMove: function (/*===== e =====*/) { // summary: // event processor for onmousemove // e: Event // mouse event this.simpleSelection = false; }, // utilities onOverEvent: function () { // summary: // this function is called once, when mouse is over our container this.onmousemoveEvent = on(this.node, touch.move, lang.hitch(this, "onMouseMove")); }, onOutEvent: function () { // summary: // this function is called once, when mouse is out of our container if (this.onmousemoveEvent) { this.onmousemoveEvent.remove(); delete this.onmousemoveEvent; } }, _removeSelection: function () { // summary: // unselects all items var e = dnd._empty; for (var i in this.selection) { if (i in e) { continue; } var node = dom.byId(i); if (node) { this._removeItemClass(node, "Selected"); } } this.selection = {}; return this; // self }, _removeAnchor: function () { if (this.anchor) { this._removeItemClass(this.anchor, "Anchor"); this.anchor = null; } return this; } }); return Selector; });