1 // Copyright 2013 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.
8 * Drag selector used on the file list or the grid table.
9 * TODO(hirono): Support drag selection for grid view. crbug.com/224832
12 function DragSelector() {
14 * Target list of drag selection.
21 * Border element of drag handle.
28 * Start point of dragging.
35 * Start point of dragging.
42 * Indexes of selected items by dragging at the last update.
43 * @type {Array.<number>!}
46 this.lastSelection_ = [];
49 * Indexes of selected items at the start of dragging.
50 * @type {Array.<number>!}
53 this.originalSelection_ = [];
55 // Bind handlers to make them removable.
56 this.onMouseMoveBound_ = this.onMouseMove_.bind(this);
57 this.onMouseUpBound_ = this.onMouseUp_.bind(this);
63 * Flag that shows whether the item is included in the selection or not.
67 DragSelector.SelectionFlag_ = {
68 IN_LAST_SELECTION: 1 << 0,
69 IN_CURRENT_SELECTION: 1 << 1
73 * Obtains the scrolled position in the element of mouse pointer from the mouse
76 * @param {HTMLElement} element Element that has the scroll bars.
77 * @param {Event} event The mouse event.
78 * @return {object} Scrolled position.
80 DragSelector.getScrolledPosition = function(element, event) {
81 if (!element.cachedBounds) {
82 element.cachedBounds = element.getBoundingClientRect();
83 if (!element.cachedBounds)
86 var rect = element.cachedBounds;
88 x: event.clientX - rect.left + element.scrollLeft,
89 y: event.clientY - rect.top + element.scrollTop
94 * Starts drag selection by reacting dragstart event.
95 * This function must be called from handlers of dragstart event.
97 * @this {DragSelector}
98 * @param {cr.ui.List} list List where the drag selection starts.
99 * @param {Event} event The dragstart event.
101 DragSelector.prototype.startDragSelection = function(list, event) {
102 // Precondition check
103 if (!list.selectionModel_.multiple || this.target_)
106 // Set the target of the drag selection
109 // Prevent the default action.
110 event.preventDefault();
112 // Save the start state.
113 var startPos = DragSelector.getScrolledPosition(list, event);
116 this.startX_ = startPos.x;
117 this.startY_ = startPos.y;
118 this.lastSelection_ = [];
119 this.originalSelection_ = this.target_.selectionModel_.selectedIndexes;
121 // Create and add the border element
123 this.border_ = this.target_.ownerDocument.createElement('div');
124 this.border_.className = 'drag-selection-border';
126 this.border_.style.left = this.startX_ + 'px';
127 this.border_.style.top = this.startY_ + 'px';
128 this.border_.style.width = '0';
129 this.border_.style.height = '0';
130 list.appendChild(this.border_);
132 // If no modifier key is pressed, clear the original selection.
133 if (!event.shiftKey && !event.ctrlKey)
134 this.target_.selectionModel_.unselectAll();
136 // Register event handlers.
137 // The handlers are bounded at the constructor.
138 this.target_.ownerDocument.addEventListener(
139 'mousemove', this.onMouseMoveBound_, true);
140 this.target_.ownerDocument.addEventListener(
141 'mouseup', this.onMouseUpBound_, true);
145 * Handles the mousemove event.
147 * @param {MouseEvent} event The mousemove event.
149 DragSelector.prototype.onMouseMove_ = function(event) {
150 // Get the selection bounds.
151 var pos = DragSelector.getScrolledPosition(this.target_, event);
153 left: Math.max(Math.min(this.startX_, pos.x), 0),
154 top: Math.max(Math.min(this.startY_, pos.y), 0),
155 right: Math.min(Math.max(this.startX_, pos.x), this.target_.scrollWidth),
156 bottom: Math.min(Math.max(this.startY_, pos.y), this.target_.scrollHeight)
158 borderBounds.width = borderBounds.right - borderBounds.left;
159 borderBounds.height = borderBounds.bottom - borderBounds.top;
161 // Collect items within the selection rect.
162 var currentSelection = this.target_.getHitElements(
166 borderBounds.height);
167 var pointedElements = this.target_.getHitElements(pos.x, pos.y);
168 var leadIndex = pointedElements.length ? pointedElements[0] : -1;
170 // Diff the selection between currentSelection and this.lastSelection_.
171 var selectionFlag = [];
172 for (var i = 0; i < this.lastSelection_.length; i++) {
173 var index = this.lastSelection_[i];
174 // Bit operator can be used for undefined value.
175 selectionFlag[index] =
176 selectionFlag[index] | DragSelector.SelectionFlag_.IN_LAST_SELECTION;
178 for (var i = 0; i < currentSelection.length; i++) {
179 var index = currentSelection[i];
180 // Bit operator can be used for undefined value.
181 selectionFlag[index] =
182 selectionFlag[index] | DragSelector.SelectionFlag_.IN_CURRENT_SELECTION;
185 // Update the selection
186 this.target_.selectionModel_.beginChange();
187 for (var name in selectionFlag) {
188 var index = parseInt(name);
189 var flag = selectionFlag[name];
190 // The flag may be one of followings:
191 // - IN_LAST_SELECTION | IN_CURRENT_SELECTION
192 // - IN_LAST_SELECTION
193 // - IN_CURRENT_SELECTION
196 // If the flag equals to (IN_LAST_SELECTION | IN_CURRENT_SELECTION),
197 // this is included in both the last selection and the current selection.
198 // We have nothing to do for this item.
200 if (flag == DragSelector.SelectionFlag_.IN_LAST_SELECTION) {
201 // If the flag equals to IN_LAST_SELECTION,
202 // then the item is included in lastSelection but not in currentSelection.
203 // Revert the selection state to this.originalSelection_.
204 this.target_.selectionModel_.setIndexSelected(
205 index, this.originalSelection_.indexOf(index) != -1);
206 } else if (flag == DragSelector.SelectionFlag_.IN_CURRENT_SELECTION) {
207 // If the flag equals to IN_CURRENT_SELECTION,
208 // this is included in currentSelection but not in lastSelection.
209 this.target_.selectionModel_.setIndexSelected(index, true);
212 if (leadIndex != -1) {
213 this.target_.selectionModel_.leadIndex = leadIndex;
214 this.target_.selectionModel_.anchorIndex = leadIndex;
216 this.target_.selectionModel_.endChange();
217 this.lastSelection_ = currentSelection;
219 // Update the size of border
220 this.border_.style.left = borderBounds.left + 'px';
221 this.border_.style.top = borderBounds.top + 'px';
222 this.border_.style.width = borderBounds.width + 'px';
223 this.border_.style.height = borderBounds.height + 'px';
227 * Handle the mouseup event.
229 * @param {MouseEvent} event The mouseup event.
231 DragSelector.prototype.onMouseUp_ = function(event) {
232 this.onMouseMove_(event);
233 this.target_.removeChild(this.border_);
234 this.target_.ownerDocument.removeEventListener(
235 'mousemove', this.onMouseMoveBound_, true);
236 this.target_.ownerDocument.removeEventListener(
237 'mouseup', this.onMouseUpBound_, true);
238 cr.dispatchSimpleEvent(this.target_, 'dragselectionend');
239 this.target_.cachedBounds = null;
241 // The target may select an item by reacting to the mouseup event.
242 // This suppress to the selecting behavior.
243 event.stopPropagation();