• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5cr.define('cr.ui', function() {
6  /** @const */ var EventTarget = cr.EventTarget;
7
8  /**
9   * Creates a new selection model that is to be used with lists. This only
10   * allows a single index to be selected.
11   *
12   * @param {number=} opt_length The number items in the selection.
13   *
14   * @constructor
15   * @extends {!cr.EventTarget}
16   */
17  function ListSingleSelectionModel(opt_length) {
18    this.length_ = opt_length || 0;
19    this.selectedIndex = -1;
20
21    // True if any item could be lead or anchor. False if only selected ones.
22    this.independentLeadItem_ = !cr.isMac && !cr.isChromeOS;
23  }
24
25  ListSingleSelectionModel.prototype = {
26    __proto__: EventTarget.prototype,
27
28    /**
29     * The number of items in the model.
30     * @type {number}
31     */
32    get length() {
33      return this.length_;
34    },
35
36    /**
37     * @type {!Array} The selected indexes.
38     */
39    get selectedIndexes() {
40      var i = this.selectedIndex;
41      return i != -1 ? [this.selectedIndex] : [];
42    },
43    set selectedIndexes(indexes) {
44      this.selectedIndex = indexes.length ? indexes[0] : -1;
45    },
46
47    /**
48     * Convenience getter which returns the first selected index.
49     * Setter also changes lead and anchor indexes if value is nonegative.
50     * @type {number}
51     */
52    get selectedIndex() {
53      return this.selectedIndex_;
54    },
55    set selectedIndex(selectedIndex) {
56      var oldSelectedIndex = this.selectedIndex;
57      var i = Math.max(-1, Math.min(this.length_ - 1, selectedIndex));
58
59      if (i != oldSelectedIndex) {
60        this.beginChange();
61        this.selectedIndex_ = i;
62        this.leadIndex = i >= 0 ? i : this.leadIndex;
63        this.endChange();
64      }
65    },
66
67    /**
68     * Selects a range of indexes, starting with {@code start} and ends with
69     * {@code end}.
70     * @param {number} start The first index to select.
71     * @param {number} end The last index to select.
72     */
73    selectRange: function(start, end) {
74      // Only select first index.
75      this.selectedIndex = Math.min(start, end);
76    },
77
78    /**
79     * Selects all indexes.
80     */
81    selectAll: function() {
82      // Select all is not allowed on a single selection model
83    },
84
85    /**
86     * Clears the selection
87     */
88    clear: function() {
89      this.beginChange();
90      this.length_ = 0;
91      this.selectedIndex = this.anchorIndex = this.leadIndex = -1;
92      this.endChange();
93    },
94
95    /**
96     * Unselects all selected items.
97     */
98    unselectAll: function() {
99      this.selectedIndex = -1;
100    },
101
102    /**
103     * Sets the selected state for an index.
104     * @param {number} index The index to set the selected state for.
105     * @param {boolean} b Whether to select the index or not.
106     */
107    setIndexSelected: function(index, b) {
108      // Only allow selection
109      var oldSelected = index == this.selectedIndex_;
110      if (oldSelected == b)
111        return;
112
113      if (b)
114        this.selectedIndex = index;
115      else if (index == this.selectedIndex_)
116        this.selectedIndex = -1;
117    },
118
119    /**
120     * Whether a given index is selected or not.
121     * @param {number} index The index to check.
122     * @return {boolean} Whether an index is selected.
123     */
124    getIndexSelected: function(index) {
125      return index == this.selectedIndex_;
126    },
127
128    /**
129     * This is used to begin batching changes. Call {@code endChange} when you
130     * are done making changes.
131     */
132    beginChange: function() {
133      if (!this.changeCount_) {
134        this.changeCount_ = 0;
135        this.selectedIndexBefore_ = this.selectedIndex_;
136      }
137      this.changeCount_++;
138    },
139
140    /**
141     * Call this after changes are done and it will dispatch a change event if
142     * any changes were actually done.
143     */
144    endChange: function() {
145      this.changeCount_--;
146      if (!this.changeCount_) {
147        if (this.selectedIndexBefore_ != this.selectedIndex_) {
148          var beforeChange = this.createChangeEvent('beforeChange');
149          if (this.dispatchEvent(beforeChange))
150            this.dispatchEvent(this.createChangeEvent('change'));
151          else
152            this.selectedIndex_ = this.selectedIndexBefore_;
153        }
154      }
155    },
156
157    /**
158     * Creates event with specified name and fills its {changes} property.
159     * @param {string} name Event name.
160     */
161    createChangeEvent: function(eventName) {
162      var e = new Event(eventName);
163      var indexes = [this.selectedIndexBefore_, this.selectedIndex_];
164      e.changes = indexes.filter(function(index) {
165        return index != -1;
166      }).map(function(index) {
167        return {
168          index: index,
169          selected: index == this.selectedIndex_
170        };
171      }, this);
172
173      return e;
174    },
175
176    leadIndex_: -1,
177
178    /**
179     * The leadIndex is used with multiple selection and it is the index that
180     * the user is moving using the arrow keys.
181     * @type {number}
182     */
183    get leadIndex() {
184      return this.leadIndex_;
185    },
186    set leadIndex(leadIndex) {
187      var li = this.adjustIndex_(leadIndex);
188      if (li != this.leadIndex_) {
189        var oldLeadIndex = this.leadIndex_;
190        this.leadIndex_ = li;
191        cr.dispatchPropertyChange(this, 'leadIndex', li, oldLeadIndex);
192        cr.dispatchPropertyChange(this, 'anchorIndex', li, oldLeadIndex);
193      }
194    },
195
196    adjustIndex_: function(index) {
197      index = Math.max(-1, Math.min(this.length_ - 1, index));
198      if (!this.independentLeadItem_)
199        index = this.selectedIndex;
200      return index;
201    },
202
203    /**
204     * The anchorIndex is used with multiple selection.
205     * @type {number}
206     */
207    get anchorIndex() {
208      return this.leadIndex;
209    },
210    set anchorIndex(anchorIndex) {
211      this.leadIndex = anchorIndex;
212    },
213
214    /**
215     * Whether the selection model supports multiple selected items.
216     * @type {boolean}
217     */
218    get multiple() {
219      return false;
220    },
221
222    /**
223     * Adjusts the selection after reordering of items in the table.
224     * @param {!Array.<number>} permutation The reordering permutation.
225     */
226    adjustToReordering: function(permutation) {
227      if (this.leadIndex != -1)
228        this.leadIndex = permutation[this.leadIndex];
229
230      var oldSelectedIndex = this.selectedIndex;
231      if (oldSelectedIndex != -1) {
232        this.selectedIndex = permutation[oldSelectedIndex];
233      }
234    },
235
236    /**
237     * Adjusts selection model length.
238     * @param {number} length New selection model length.
239     */
240    adjustLength: function(length) {
241      this.length_ = length;
242    }
243  };
244
245  return {
246    ListSingleSelectionModel: ListSingleSelectionModel
247  };
248});
249