• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1
2
3    Polymer('core-selector', {
4
5      /**
6       * Gets or sets the selected element.  Default to use the index
7       * of the item element.
8       *
9       * If you want a specific attribute value of the element to be
10       * used instead of index, set "valueattr" to that attribute name.
11       *
12       * Example:
13       *
14       *     <core-selector valueattr="label" selected="foo">
15       *       <div label="foo"></div>
16       *       <div label="bar"></div>
17       *       <div label="zot"></div>
18       *     </core-selector>
19       *
20       * In multi-selection this should be an array of values.
21       *
22       * Example:
23       *
24       *     <core-selector id="selector" valueattr="label" multi>
25       *       <div label="foo"></div>
26       *       <div label="bar"></div>
27       *       <div label="zot"></div>
28       *     </core-selector>
29       *
30       *     this.$.selector.selected = ['foo', 'zot'];
31       *
32       * @attribute selected
33       * @type Object
34       * @default null
35       */
36      selected: null,
37
38      /**
39       * If true, multiple selections are allowed.
40       *
41       * @attribute multi
42       * @type boolean
43       * @default false
44       */
45      multi: false,
46
47      /**
48       * Specifies the attribute to be used for "selected" attribute.
49       *
50       * @attribute valueattr
51       * @type string
52       * @default 'name'
53       */
54      valueattr: 'name',
55
56      /**
57       * Specifies the CSS class to be used to add to the selected element.
58       *
59       * @attribute selectedClass
60       * @type string
61       * @default 'core-selected'
62       */
63      selectedClass: 'core-selected',
64
65      /**
66       * Specifies the property to be used to set on the selected element
67       * to indicate its active state.
68       *
69       * @attribute selectedProperty
70       * @type string
71       * @default ''
72       */
73      selectedProperty: '',
74
75      /**
76       * Specifies the attribute to set on the selected element to indicate
77       * its active state.
78       *
79       * @attribute selectedAttribute
80       * @type string
81       * @default 'active'
82       */
83      selectedAttribute: 'active',
84
85      /**
86       * Returns the currently selected element. In multi-selection this returns
87       * an array of selected elements.
88       *
89       * @attribute selectedItem
90       * @type Object
91       * @default null
92       */
93      selectedItem: null,
94
95      /**
96       * In single selection, this returns the model associated with the
97       * selected element.
98       *
99       * @attribute selectedModel
100       * @type Object
101       * @default null
102       */
103      selectedModel: null,
104
105      /**
106       * In single selection, this returns the selected index.
107       *
108       * @attribute selectedIndex
109       * @type number
110       * @default -1
111       */
112      selectedIndex: -1,
113
114      /**
115       * The target element that contains items.  If this is not set
116       * core-selector is the container.
117       *
118       * @attribute target
119       * @type Object
120       * @default null
121       */
122      target: null,
123
124      /**
125       * This can be used to query nodes from the target node to be used for
126       * selection items.  Note this only works if the 'target' property is set.
127       *
128       * Example:
129       *
130       *     <core-selector target="{{$.myForm}}" itemsSelector="input[type=radio]"></core-selector>
131       *     <form id="myForm">
132       *       <label><input type="radio" name="color" value="red"> Red</label> <br>
133       *       <label><input type="radio" name="color" value="green"> Green</label> <br>
134       *       <label><input type="radio" name="color" value="blue"> Blue</label> <br>
135       *       <p>color = {{color}}</p>
136       *     </form>
137       *
138       * @attribute itemsSelector
139       * @type string
140       * @default ''
141       */
142      itemsSelector: '',
143
144      /**
145       * The event that would be fired from the item element to indicate
146       * it is being selected.
147       *
148       * @attribute activateEvent
149       * @type string
150       * @default 'tap'
151       */
152      activateEvent: 'tap',
153
154      /**
155       * Set this to true to disallow changing the selection via the
156       * `activateEvent`.
157       *
158       * @attribute notap
159       * @type boolean
160       * @default false
161       */
162      notap: false,
163
164      ready: function() {
165        this.activateListener = this.activateHandler.bind(this);
166        this.observer = new MutationObserver(this.updateSelected.bind(this));
167        if (!this.target) {
168          this.target = this;
169        }
170      },
171
172      get items() {
173        if (!this.target) {
174          return [];
175        }
176        var nodes = this.target !== this ? (this.itemsSelector ?
177            this.target.querySelectorAll(this.itemsSelector) :
178                this.target.children) : this.$.items.getDistributedNodes();
179        return Array.prototype.filter.call(nodes || [], function(n) {
180          return n && n.localName !== 'template';
181        });
182      },
183
184      targetChanged: function(old) {
185        if (old) {
186          this.removeListener(old);
187          this.observer.disconnect();
188          this.clearSelection();
189        }
190        if (this.target) {
191          this.addListener(this.target);
192          this.observer.observe(this.target, {childList: true});
193          this.updateSelected();
194        }
195      },
196
197      addListener: function(node) {
198        Polymer.addEventListener(node, this.activateEvent, this.activateListener);
199      },
200
201      removeListener: function(node) {
202        Polymer.removeEventListener(node, this.activateEvent, this.activateListener);
203      },
204
205      get selection() {
206        return this.$.selection.getSelection();
207      },
208
209      selectedChanged: function() {
210        this.updateSelected();
211      },
212
213      updateSelected: function() {
214        this.validateSelected();
215        if (this.multi) {
216          this.clearSelection();
217          this.selected && this.selected.forEach(function(s) {
218            this.valueToSelection(s);
219          }, this);
220        } else {
221          this.valueToSelection(this.selected);
222        }
223      },
224
225      validateSelected: function() {
226        // convert to an array for multi-selection
227        if (this.multi && !Array.isArray(this.selected) &&
228            this.selected !== null && this.selected !== undefined) {
229          this.selected = [this.selected];
230        }
231      },
232
233      clearSelection: function() {
234        if (this.multi) {
235          this.selection.slice().forEach(function(s) {
236            this.$.selection.setItemSelected(s, false);
237          }, this);
238        } else {
239          this.$.selection.setItemSelected(this.selection, false);
240        }
241        this.selectedItem = null;
242        this.$.selection.clear();
243      },
244
245      valueToSelection: function(value) {
246        var item = (value === null || value === undefined) ?
247            null : this.items[this.valueToIndex(value)];
248        this.$.selection.select(item);
249      },
250
251      updateSelectedItem: function() {
252        this.selectedItem = this.selection;
253      },
254
255      selectedItemChanged: function() {
256        if (this.selectedItem) {
257          var t = this.selectedItem.templateInstance;
258          this.selectedModel = t ? t.model : undefined;
259        } else {
260          this.selectedModel = null;
261        }
262        this.selectedIndex = this.selectedItem ?
263            parseInt(this.valueToIndex(this.selected)) : -1;
264      },
265
266      valueToIndex: function(value) {
267        // find an item with value == value and return it's index
268        for (var i=0, items=this.items, c; (c=items[i]); i++) {
269          if (this.valueForNode(c) == value) {
270            return i;
271          }
272        }
273        // if no item found, the value itself is probably the index
274        return value;
275      },
276
277      valueForNode: function(node) {
278        return node[this.valueattr] || node.getAttribute(this.valueattr);
279      },
280
281      // events fired from <core-selection> object
282      selectionSelect: function(e, detail) {
283        this.updateSelectedItem();
284        if (detail.item) {
285          this.applySelection(detail.item, detail.isSelected);
286        }
287      },
288
289      applySelection: function(item, isSelected) {
290        if (this.selectedClass) {
291          item.classList.toggle(this.selectedClass, isSelected);
292        }
293        if (this.selectedProperty) {
294          item[this.selectedProperty] = isSelected;
295        }
296        if (this.selectedAttribute && item.setAttribute) {
297          if (isSelected) {
298            item.setAttribute(this.selectedAttribute, '');
299          } else {
300            item.removeAttribute(this.selectedAttribute);
301          }
302        }
303      },
304
305      // event fired from host
306      activateHandler: function(e) {
307        if (!this.notap) {
308          var i = this.findDistributedTarget(e.target, this.items);
309          if (i >= 0) {
310            var item = this.items[i];
311            var s = this.valueForNode(item) || i;
312            if (this.multi) {
313              if (this.selected) {
314                this.addRemoveSelected(s);
315              } else {
316                this.selected = [s];
317              }
318            } else {
319              this.selected = s;
320            }
321            this.asyncFire('core-activate', {item: item});
322          }
323        }
324      },
325
326      addRemoveSelected: function(value) {
327        var i = this.selected.indexOf(value);
328        if (i >= 0) {
329          this.selected.splice(i, 1);
330        } else {
331          this.selected.push(value);
332        }
333        this.valueToSelection(value);
334      },
335
336      findDistributedTarget: function(target, nodes) {
337        // find first ancestor of target (including itself) that
338        // is in nodes, if any
339        while (target && target != this) {
340          var i = Array.prototype.indexOf.call(nodes, target);
341          if (i >= 0) {
342            return i;
343          }
344          target = target.parentNode;
345        }
346      }
347    });
348