Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(74)

Side by Side Diff: chrome/browser/resources/file_manager/js/volume_list.js

Issue 22382002: Rename VolumeList -> NavigationList in Files.app (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: addressed comment Created 7 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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
5 'use strict';
6
7 /**
8 * A volume list model. This model combines the 2 lists.
9 * @param {cr.ui.ArrayDataModel} volumesList The first list of the model.
10 * @param {cr.ui.ArrayDataModel} pinnedList The second list of the model.
11 * @constructor
12 * @extends {cr.EventTarget}
13 */
14 function VolumeListModel(volumesList, pinnedList) {
15 this.volumesList_ = volumesList;
16 this.pinnedList_ = pinnedList;
17
18 // Generates a combined 'permuted' event from an event of either list.
19 var permutedHandler = function(listNum, e) {
20 var permutedEvent = new Event('permuted');
21 var newPermutation = [];
22 var newLength;
23 if (listNum == 1) {
24 newLength = e.newLength + this.pinnedList_.length;
25 for (var i = 0; i < e.permutation.length; i++) {
26 newPermutation[i] = e.permutation[i];
27 }
28 for (var i = 0; i < this.pinnedList_.length; i++) {
29 newPermutation[i + e.permutation.length] = i + e.newLength;
30 }
31 } else {
32 var volumesLen = this.volumesList_.length;
33 newLength = e.newLength + volumesLen;
34 for (var i = 0; i < volumesLen; i++) {
35 newPermutation[i] = i;
36 }
37 for (var i = 0; i < e.permutation.length; i++) {
38 newPermutation[i + volumesLen] =
39 (e.permutation[i] !== -1) ? (e.permutation[i] + volumesLen) : -1;
40 }
41 }
42
43 permutedEvent.newLength = newLength;
44 permutedEvent.permutation = newPermutation;
45 this.dispatchEvent(permutedEvent);
46 };
47 this.volumesList_.addEventListener('permuted', permutedHandler.bind(this, 1));
48 this.pinnedList_.addEventListener('permuted', permutedHandler.bind(this, 2));
49
50 // Generates a combined 'change' event from an event of either list.
51 var changeHandler = function(listNum, e) {
52 var changeEvent = new Event('change');
53 changeEvent.index =
54 (listNum == 1) ? e.index : (e.index + this.volumesList_.length);
55 this.dispatchEvent(changeEvent);
56 };
57 this.volumesList_.addEventListener('change', changeHandler.bind(this, 1));
58 this.pinnedList_.addEventListener('change', changeHandler.bind(this, 2));
59
60 // 'splice' and 'sorted' events are not implemented, since they are not used
61 // in list.js.
62 }
63
64 /**
65 * VolumeList inherits cr.EventTarget.
66 */
67 VolumeListModel.prototype = {
68 __proto__: cr.EventTarget.prototype,
69 get length() { return this.length_(); }
70 };
71
72 /**
73 * Returns the item at the given index.
74 * @param {number} index The index of the entry to get.
75 * @return {?string} The path at the given index.
76 */
77 VolumeListModel.prototype.item = function(index) {
78 var offset = this.volumesList_.length;
79 if (index < offset) {
80 var entry = this.volumesList_.item(index);
81 return entry ? entry.fullPath : undefined;
82 } else {
83 return this.pinnedList_.item(index - offset);
84 }
85 };
86
87 /**
88 * Type of the item on the volume list.
89 * @enum {number}
90 */
91 VolumeListModel.ItemType = {
92 ROOT: 1,
93 PINNED: 2
94 };
95
96 /**
97 * Returns the type of the item at the given index.
98 * @param {number} index The index of the entry to get.
99 * @return {VolumeListModel.ItemType} The type of the item.
100 */
101 VolumeListModel.prototype.getItemType = function(index) {
102 var offset = this.volumesList_.length;
103 return index < offset ?
104 VolumeListModel.ItemType.ROOT : VolumeListModel.ItemType.PINNED;
105 };
106
107 /**
108 * Returns the number of items in the model.
109 * @return {number} The length of the model.
110 * @private
111 */
112 VolumeListModel.prototype.length_ = function() {
113 return this.volumesList_.length + this.pinnedList_.length;
114 };
115
116 /**
117 * Returns the first matching item.
118 * @param {Entry} item The entry to find.
119 * @param {number=} opt_fromIndex If provided, then the searching start at
120 * the {@code opt_fromIndex}.
121 * @return {number} The index of the first found element or -1 if not found.
122 */
123 VolumeListModel.prototype.indexOf = function(item, opt_fromIndex) {
124 for (var i = opt_fromIndex || 0; i < this.length; i++) {
125 if (item === this.item(i))
126 return i;
127 }
128 return -1;
129 };
130
131 /**
132 * A volume item.
133 * @constructor
134 * @extends {HTMLLIElement}
135 */
136 var VolumeItem = cr.ui.define('li');
137
138 VolumeItem.prototype = {
139 __proto__: HTMLLIElement.prototype,
140 };
141
142 /**
143 * Decorate the item.
144 */
145 VolumeItem.prototype.decorate = function() {
146 // decorate() may be called twice: from the constructor and from
147 // List.createItem(). This check prevents double-decorating.
148 if (this.className)
149 return;
150
151 this.className = 'root-item';
152 this.setAttribute('role', 'option');
153
154 this.iconDiv_ = cr.doc.createElement('div');
155 this.iconDiv_.className = 'volume-icon';
156 this.appendChild(this.iconDiv_);
157
158 this.label_ = cr.doc.createElement('div');
159 this.label_.className = 'root-label';
160 this.appendChild(this.label_);
161
162 cr.defineProperty(this, 'lead', cr.PropertyKind.BOOL_ATTR);
163 cr.defineProperty(this, 'selected', cr.PropertyKind.BOOL_ATTR);
164 };
165
166 /**
167 * Associate a path with this item.
168 * @param {string} path Path of this item.
169 */
170 VolumeItem.prototype.setPath = function(path) {
171 if (this.path_)
172 console.warn('VolumeItem.setPath should be called only once.');
173
174 this.path_ = path;
175
176 var rootType = PathUtil.getRootType(path);
177
178 this.iconDiv_.setAttribute('volume-type-icon', rootType);
179 if (rootType === RootType.REMOVABLE) {
180 this.iconDiv_.setAttribute('volume-subtype',
181 VolumeManager.getInstance().getDeviceType(path));
182 }
183
184 this.label_.textContent = PathUtil.getFolderLabel(path);
185
186 if (rootType === RootType.ARCHIVE || rootType === RootType.REMOVABLE) {
187 this.eject_ = cr.doc.createElement('div');
188 // Block other mouse handlers.
189 this.eject_.addEventListener(
190 'mouseup', function(e) { e.stopPropagation() });
191 this.eject_.addEventListener(
192 'mousedown', function(e) { e.stopPropagation() });
193
194 this.eject_.className = 'root-eject';
195 this.eject_.addEventListener('click', function(event) {
196 event.stopPropagation();
197 cr.dispatchSimpleEvent(this, 'eject');
198 }.bind(this));
199
200 this.appendChild(this.eject_);
201 }
202 };
203
204 /**
205 * Associate a context menu with this item.
206 * @param {cr.ui.Menu} menu Menu this item.
207 */
208 VolumeItem.prototype.maybeSetContextMenu = function(menu) {
209 if (!this.path_) {
210 console.error(
211 'VolumeItem.maybeSetContextMenu must be called after setPath().');
212 return;
213 }
214
215 var isRoot = PathUtil.isRootPath(this.path_);
216 var rootType = PathUtil.getRootType(this.path_);
217 // The context menu is shown on the following items:
218 // - Removable and Archive volumes
219 // - Folder shortcuts
220 if (!isRoot ||
221 (rootType != RootType.DRIVE && rootType != RootType.DOWNLOADS))
222 cr.ui.contextMenuHandler.setContextMenu(this, menu);
223 };
224
225 /**
226 * A volume list.
227 * @constructor
228 * @extends {cr.ui.List}
229 */
230 function VolumeList() {
231 }
232
233 /**
234 * VolumeList inherits cr.ui.List.
235 */
236 VolumeList.prototype.__proto__ = cr.ui.List.prototype;
237
238 /**
239 * @param {HTMLElement} el Element to be DirectoryItem.
240 * @param {DirectoryModel} directoryModel Current DirectoryModel.
241 * @param {cr.ui.ArrayDataModel} pinnedFolderModel Current model of the pinned
242 * folders.
243 */
244 VolumeList.decorate = function(el, directoryModel, pinnedFolderModel) {
245 el.__proto__ = VolumeList.prototype;
246 el.decorate(directoryModel, pinnedFolderModel);
247 };
248
249 /**
250 * @param {DirectoryModel} directoryModel Current DirectoryModel.
251 * @param {cr.ui.ArrayDataModel} pinnedFolderModel Current model of the pinned
252 * folders.
253 */
254 VolumeList.prototype.decorate = function(directoryModel, pinnedFolderModel) {
255 cr.ui.List.decorate(this);
256 this.__proto__ = VolumeList.prototype;
257
258 this.directoryModel_ = directoryModel;
259 this.volumeManager_ = VolumeManager.getInstance();
260 this.selectionModel = new cr.ui.ListSingleSelectionModel();
261
262 this.directoryModel_.addEventListener('directory-changed',
263 this.onCurrentDirectoryChanged_.bind(this));
264 this.selectionModel.addEventListener(
265 'change', this.onSelectionChange_.bind(this));
266 this.selectionModel.addEventListener(
267 'beforeChange', this.onBeforeSelectionChange_.bind(this));
268
269 this.scrollBar_ = new ScrollBar();
270 this.scrollBar_.initialize(this.parentNode, this);
271
272 // Overriding default role 'list' set by cr.ui.List.decorate() to 'listbox'
273 // role for better accessibility on ChromeOS.
274 this.setAttribute('role', 'listbox');
275
276 var self = this;
277 this.itemConstructor = function(path) {
278 return self.renderRoot_(path);
279 };
280
281 this.pinnedItemList_ = pinnedFolderModel;
282
283 this.dataModel =
284 new VolumeListModel(this.directoryModel_.getRootsList(),
285 this.pinnedItemList_);
286 };
287
288 /**
289 * Creates an element of a volume. This method is called from cr.ui.List
290 * internally.
291 * @param {string} path Path of the directory to be rendered.
292 * @return {VolumeItem} Rendered element.
293 * @private
294 */
295 VolumeList.prototype.renderRoot_ = function(path) {
296 var item = new VolumeItem();
297 item.setPath(path);
298
299 var handleClick = function() {
300 if (item.selected && path !== this.directoryModel_.getCurrentDirPath()) {
301 this.directoryModel_.changeDirectory(path);
302 }
303 }.bind(this);
304 item.addEventListener('click', handleClick);
305
306 var handleEject = function() {
307 var unmountCommand = cr.doc.querySelector('command#unmount');
308 // Let's make sure 'canExecute' state of the command is properly set for
309 // the root before executing it.
310 unmountCommand.canExecuteChange(item);
311 unmountCommand.execute(item);
312 };
313 item.addEventListener('eject', handleEject);
314 // TODO(yoshiki): Check if the following touch handler is necessary or not.
315 // If unnecessary, remove it.
316 item.addEventListener(cr.ui.TouchHandler.EventType.TOUCH_START, handleClick);
317
318 if (this.contextMenu_)
319 item.maybeSetContextMenu(this.contextMenu_);
320
321 // If the current directory is already set.
322 if (this.currentVolume_ == path) {
323 setTimeout(function() {
324 this.selectedItem = path;
325 }.bind(this), 0);
326 }
327 return item;
328 };
329
330 /**
331 * Sets a context menu. Context menu is enabled only on archive and removable
332 * volumes as of now.
333 *
334 * @param {cr.ui.Menu} menu Context menu.
335 */
336 VolumeList.prototype.setContextMenu = function(menu) {
337 this.contextMenu_ = menu;
338
339 for (var i = 0; i < this.dataModel.length; i++) {
340 this.getListItemByIndex(i).maybeSetContextMenu(this.contextMenu_);
341 }
342 };
343
344 /**
345 * Selects the n-th volume from the list.
346 * @param {number} index Volume index.
347 * @return {boolean} True for success, otherwise false.
348 */
349 VolumeList.prototype.selectByIndex = function(index) {
350 if (index < 0 || index > this.dataModel.length - 1)
351 return false;
352
353 var newPath = this.dataModel.item(index);
354 if (!newPath)
355 return false;
356
357 // Prevents double-moving to the current directory.
358 if (this.directoryModel_.getCurrentDirEntry().fullPath == newPath)
359 return false;
360
361 this.directoryModel_.changeDirectory(newPath);
362 return true;
363 };
364
365 /**
366 * Handler before root item change.
367 * @param {Event} event The event.
368 * @private
369 */
370 VolumeList.prototype.onBeforeSelectionChange_ = function(event) {
371 if (event.changes.length == 1 && !event.changes[0].selected)
372 event.preventDefault();
373 };
374
375 /**
376 * Handler for root item being clicked.
377 * @param {Event} event The event.
378 * @private
379 */
380 VolumeList.prototype.onSelectionChange_ = function(event) {
381 // This handler is invoked even when the volume list itself changes the
382 // selection. In such case, we shouldn't handle the event.
383 if (this.dontHandleSelectionEvent_)
384 return;
385
386 this.selectByIndex(this.selectionModel.selectedIndex);
387 };
388
389 /**
390 * Invoked when the current directory is changed.
391 * @param {Event} event The event.
392 * @private
393 */
394 VolumeList.prototype.onCurrentDirectoryChanged_ = function(event) {
395 var path = event.newDirEntry.fullPath || this.dataModel.getCurrentDirPath();
396 var newRootPath = PathUtil.getRootPath(path);
397
398 // Synchronizes the volume list selection with the current directory, after
399 // it is changed outside of the volume list.
400
401 // (1) Select the nearest parent directory (including the pinned directories).
402 var bestMatchIndex = -1;
403 var bestMatchSubStringLen = 0;
404 for (var i = 0; i < this.dataModel.length; i++) {
405 var itemPath = this.dataModel.item(i);
406 if (path.indexOf(itemPath) == 0) {
407 if (bestMatchSubStringLen < itemPath.length) {
408 bestMatchIndex = i;
409 bestMatchSubStringLen = itemPath.length;
410 }
411 }
412 }
413 if (bestMatchIndex != -1) {
414 // Not to invoke the handler of this instance, sets the guard.
415 this.dontHandleSelectionEvent_ = true;
416 this.selectionModel.selectedIndex = bestMatchIndex;
417 this.dontHandleSelectionEvent_ = false;
418 return;
419 }
420
421 // (2) Selects the volume of the current directory.
422 for (var i = 0; i < this.dataModel.length; i++) {
423 var itemPath = this.dataModel.item(i);
424 if (PathUtil.getRootPath(itemPath) == newRootPath) {
425 // Not to invoke the handler of this instance, sets the guard.
426 this.dontHandleSelectionEvent_ = true;
427 this.selectionModel.selectedIndex = i;
428 this.dontHandleSelectionEvent_ = false;
429 return;
430 }
431 }
432 };
OLDNEW
« no previous file with comments | « chrome/browser/resources/file_manager/js/test_util.js ('k') | chrome/browser/resources/file_manager/main.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698