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

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

Issue 39123003: [Files.app] Split the JavaScript files into subdirectories: common, background, and foreground (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: fixed test failure. Created 7 years, 2 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) 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.
4
5 'use strict';
6
7 ////////////////////////////////////////////////////////////////////////////////
8 // DirectoryTreeUtil
9
10 /**
11 * Utility methods. They are intended for use only in this file.
12 */
13 var DirectoryTreeUtil = {};
14
15 /**
16 * Generate a list of the directory entries for the top level on the tree.
17 * @return {Array.<DirectoryEntry>} Entries for the top level on the tree.
18 */
19 DirectoryTreeUtil.generateTopLevelEntries = function() {
20 var entries = [
21 DirectoryModel.fakeDriveEntry_,
22 DirectoryModel.fakeDriveOfflineEntry_,
23 DirectoryModel.fakeDriveSharedWithMeEntry_,
24 DirectoryModel.fakeDriveRecentEntry_,
25 ];
26
27 for (var i = 0; i < entries.length; i++) {
28 entries[i]['label'] = PathUtil.getRootLabel(entries[i].fullPath);
29 }
30
31 return entries;
32 };
33
34 /**
35 * Checks if the given directory can be on the tree or not.
36 *
37 * @param {string} path Path to be checked.
38 * @return {boolean} True if the path is eligible for the directory tree.
39 * Otherwise, false.
40 */
41 DirectoryTreeUtil.isEligiblePathForDirectoryTree = function(path) {
42 return PathUtil.isDriveBasedPath(path);
43 };
44
45 Object.freeze(DirectoryTreeUtil);
46
47 ////////////////////////////////////////////////////////////////////////////////
48 // DirectoryTreeBase
49
50 /**
51 * Implementation of methods for DirectoryTree and DirectoryItem. These classes
52 * inherits cr.ui.Tree/TreeItem so we can't make them inherit this class.
53 * Instead, we separate their implementations to this separate object and call
54 * it with setting 'this' from DirectoryTree/Item.
55 */
56 var DirectoryItemTreeBaseMethods = {};
57
58 /**
59 * Updates sub-elements of {@code this} reading {@code DirectoryEntry}.
60 * The list of {@code DirectoryEntry} are not updated by this method.
61 *
62 * @param {boolean} recursive True if the all visible sub-directories are
63 * updated recursively including left arrows. If false, the update walks
64 * only immediate child directories without arrows.
65 */
66 DirectoryItemTreeBaseMethods.updateSubElementsFromList = function(recursive) {
67 var index = 0;
68 var tree = this.parentTree_ || this; // If no parent, 'this' itself is tree.
69 while (this.entries_[index]) {
70 var currentEntry = this.entries_[index];
71 var currentElement = this.items[index];
72
73 if (index >= this.items.length) {
74 var item = new DirectoryItem(currentEntry, this, tree);
75 this.add(item);
76 index++;
77 } else if (currentEntry.fullPath == currentElement.fullPath) {
78 if (recursive && this.expanded)
79 currentElement.updateSubDirectories(true /* recursive */);
80
81 index++;
82 } else if (currentEntry.fullPath < currentElement.fullPath) {
83 var item = new DirectoryItem(currentEntry, this, tree);
84 this.addAt(item, index);
85 index++;
86 } else if (currentEntry.fullPath > currentElement.fullPath) {
87 this.remove(currentElement);
88 }
89 }
90
91 var removedChild;
92 while (removedChild = this.items[index]) {
93 this.remove(removedChild);
94 }
95
96 if (index == 0) {
97 this.hasChildren = false;
98 this.expanded = false;
99 } else {
100 this.hasChildren = true;
101 }
102 };
103
104 /**
105 * Finds a parent directory of the {@code entry} in {@code this}, and
106 * invokes the DirectoryItem.selectByEntry() of the found directory.
107 *
108 * @param {DirectoryEntry|Object} entry The entry to be searched for. Can be
109 * a fake.
110 * @return {boolean} True if the parent item is found.
111 */
112 DirectoryItemTreeBaseMethods.searchAndSelectByEntry = function(entry) {
113 for (var i = 0; i < this.items.length; i++) {
114 var item = this.items[i];
115 if (util.isParentEntry(item.entry, entry)) {
116 item.selectByEntry(entry);
117 return true;
118 }
119 }
120 return false;
121 };
122
123 Object.freeze(DirectoryItemTreeBaseMethods);
124
125 ////////////////////////////////////////////////////////////////////////////////
126 // DirectoryItem
127
128 /**
129 * A directory in the tree. Each element represents one directory.
130 *
131 * @param {DirectoryEntry} dirEntry DirectoryEntry of this item.
132 * @param {DirectoryItem|DirectoryTree} parentDirItem Parent of this item.
133 * @param {DirectoryTree} tree Current tree, which contains this item.
134 * @extends {cr.ui.TreeItem}
135 * @constructor
136 */
137 function DirectoryItem(dirEntry, parentDirItem, tree) {
138 var item = cr.doc.createElement('div');
139 DirectoryItem.decorate(item, dirEntry, parentDirItem, tree);
140 return item;
141 }
142
143 /**
144 * @param {HTMLElement} el Element to be DirectoryItem.
145 * @param {DirectoryEntry} dirEntry DirectoryEntry of this item.
146 * @param {DirectoryItem|DirectoryTree} parentDirItem Parent of this item.
147 * @param {DirectoryTree} tree Current tree, which contains this item.
148 */
149 DirectoryItem.decorate =
150 function(el, dirEntry, parentDirItem, tree) {
151 el.__proto__ = DirectoryItem.prototype;
152 (/** @type {DirectoryItem} */ el).decorate(
153 dirEntry, parentDirItem, tree);
154 };
155
156 DirectoryItem.prototype = {
157 __proto__: cr.ui.TreeItem.prototype,
158
159 /**
160 * The DirectoryEntry corresponding to this DirectoryItem. This may be
161 * a dummy DirectoryEntry.
162 * @type {DirectoryEntry|Object}
163 */
164 get entry() {
165 return this.dirEntry_;
166 },
167
168 /**
169 * The element containing the label text and the icon.
170 * @type {!HTMLElement}
171 * @override
172 */
173 get labelElement() {
174 return this.firstElementChild.querySelector('.label');
175 }
176 };
177
178 /**
179 * Calls DirectoryItemTreeBaseMethods.updateSubElementsFromList().
180 *
181 * @param {boolean} recursive True if the all visible sub-directories are
182 * updated recursively including left arrows. If false, the update walks
183 * only immediate child directories without arrows.
184 */
185 DirectoryItem.prototype.updateSubElementsFromList = function(recursive) {
186 DirectoryItemTreeBaseMethods.updateSubElementsFromList.call(this, recursive);
187 };
188
189 /**
190 * Calls DirectoryItemTreeBaseMethods.updateSubElementsFromList().
191 * @param {DirectoryEntry|Object} entry The entry to be searched for. Can be
192 * a fake.
193 * @return {boolean} True if the parent item is found.
194 */
195 DirectoryItem.prototype.searchAndSelectByEntry = function(entry) {
196 return DirectoryItemTreeBaseMethods.searchAndSelectByEntry.call(this, entry);
197 };
198
199 /**
200 * @param {DirectoryEntry} dirEntry DirectoryEntry of this item.
201 * @param {DirectoryItem|DirectoryTree} parentDirItem Parent of this item.
202 * @param {DirectoryTree} tree Current tree, which contains this item.
203 */
204 DirectoryItem.prototype.decorate = function(
205 dirEntry, parentDirItem, tree) {
206 var path = dirEntry.fullPath;
207 var label;
208 label = dirEntry.label ? dirEntry.label : dirEntry.name;
209
210 this.className = 'tree-item';
211 this.innerHTML =
212 '<div class="tree-row">' +
213 ' <span class="expand-icon"></span>' +
214 ' <span class="icon"></span>' +
215 ' <span class="label"></span>' +
216 ' <div class="root-eject"></div>' +
217 '</div>' +
218 '<div class="tree-children"></div>';
219 this.setAttribute('role', 'treeitem');
220
221 this.parentTree_ = tree;
222 this.directoryModel_ = tree.directoryModel;
223 this.parent_ = parentDirItem;
224 this.label = label;
225 this.fullPath = path;
226 this.dirEntry_ = dirEntry;
227 this.fileFilter_ = this.directoryModel_.getFileFilter();
228
229 // Sets hasChildren=false tentatively. This will be overridden after
230 // scanning sub-directories in DirectoryTreeUtil.updateSubElementsFromList.
231 this.hasChildren = false;
232
233 this.addEventListener('expand', this.onExpand_.bind(this), false);
234 var icon = this.querySelector('.icon');
235 icon.classList.add('volume-icon');
236 var iconType = PathUtil.getRootType(path);
237 if (iconType && PathUtil.isRootPath(path))
238 icon.setAttribute('volume-type-icon', iconType);
239 else
240 icon.setAttribute('file-type-icon', 'folder');
241
242 var eject = this.querySelector('.root-eject');
243 eject.hidden = !PathUtil.isUnmountableByUser(path);
244 eject.addEventListener('click',
245 function(event) {
246 event.stopPropagation();
247 if (!PathUtil.isUnmountableByUser(path))
248 return;
249
250 tree.volumeManager.unmount(path, function() {}, function() {});
251 }.bind(this));
252
253 if (this.parentTree_.contextMenuForSubitems)
254 this.setContextMenu(this.parentTree_.contextMenuForSubitems);
255 // Adds handler for future change.
256 this.parentTree_.addEventListener(
257 'contextMenuForSubitemsChange',
258 function(e) { this.setContextMenu(e.newValue); }.bind(this));
259
260 if (parentDirItem.expanded)
261 this.updateSubDirectories(false /* recursive */);
262 };
263
264 /**
265 * Overrides WebKit's scrollIntoViewIfNeeded, which doesn't work well with
266 * a complex layout. This call is not necessary, so we are ignoring it.
267 *
268 * @param {boolean} unused Unused.
269 * @override
270 */
271 DirectoryItem.prototype.scrollIntoViewIfNeeded = function(unused) {
272 };
273
274 /**
275 * Removes the child node, but without selecting the parent item, to avoid
276 * unintended changing of directories. Removing is done externally, and other
277 * code will navigate to another directory.
278 *
279 * @param {!cr.ui.TreeItem} child The tree item child to remove.
280 * @override
281 */
282 DirectoryItem.prototype.remove = function(child) {
283 this.lastElementChild.removeChild(child);
284 if (this.items.length == 0)
285 this.hasChildren = false;
286 };
287
288 /**
289 * Invoked when the item is being expanded.
290 * @param {!UIEvent} e Event.
291 * @private
292 **/
293 DirectoryItem.prototype.onExpand_ = function(e) {
294 this.updateSubDirectories(
295 true /* recursive */,
296 function() {},
297 function() {
298 this.expanded = false;
299 }.bind(this));
300
301 e.stopPropagation();
302 };
303
304 /**
305 * Retrieves the latest subdirectories and update them on the tree.
306 * @param {boolean} recursive True if the update is recursively.
307 * @param {function()=} opt_successCallback Callback called on success.
308 * @param {function()=} opt_errorCallback Callback called on error.
309 */
310 DirectoryItem.prototype.updateSubDirectories = function(
311 recursive, opt_successCallback, opt_errorCallback) {
312 if (util.isFakeDirectoryEntry(this.entry)) {
313 if (opt_errorCallback)
314 opt_errorCallback();
315 return;
316 }
317
318 var sortEntries = function(fileFilter, entries) {
319 entries.sort(function(a, b) {
320 return (a.name.toLowerCase() > b.name.toLowerCase()) ? 1 : -1;
321 });
322 return entries.filter(fileFilter.filter.bind(fileFilter));
323 };
324
325 var onSuccess = function(entries) {
326 this.entries_ = entries;
327 this.redrawSubDirectoryList_(recursive);
328 opt_successCallback && opt_successCallback();
329 }.bind(this);
330
331 var reader = this.entry.createReader();
332 var entries = [];
333 var readEntry = function() {
334 reader.readEntries(function(results) {
335 if (!results.length) {
336 onSuccess(sortEntries(this.fileFilter_, entries));
337 return;
338 }
339
340 for (var i = 0; i < results.length; i++) {
341 var entry = results[i];
342 if (entry.isDirectory)
343 entries.push(entry);
344 }
345 readEntry();
346 }.bind(this));
347 }.bind(this);
348 readEntry();
349 };
350
351 /**
352 * Updates sub-elements of {@code parentElement} reading {@code DirectoryEntry}
353 * with calling {@code iterator}.
354 *
355 * @param {string} changedDirectryPath The path of the changed directory.
356 */
357 DirectoryItem.prototype.updateItemByPath = function(changedDirectryPath) {
358 if (changedDirectryPath === this.entry.fullPath) {
359 this.updateSubDirectories(false /* recursive */);
360 return;
361 }
362
363 for (var i = 0; i < this.items.length; i++) {
364 var item = this.items[i];
365 if (PathUtil.isParentPath(item.entry.fullPath, changedDirectryPath)) {
366 item.updateItemByPath(changedDirectryPath);
367 break;
368 }
369 }
370 };
371
372 /**
373 * Redraw subitems with the latest information. The items are sorted in
374 * alphabetical order, case insensitive.
375 * @param {boolean} recursive True if the update is recursively.
376 * @private
377 */
378 DirectoryItem.prototype.redrawSubDirectoryList_ = function(recursive) {
379 this.updateSubElementsFromList(recursive);
380 };
381
382 /**
383 * Select the item corresponding to the given {@code entry}.
384 * @param {DirectoryEntry|Object} entry The entry to be selected. Can be a fake.
385 */
386 DirectoryItem.prototype.selectByEntry = function(entry) {
387 if (util.isSameEntry(entry, this.entry)) {
388 this.selected = true;
389 return;
390 }
391
392 if (this.searchAndSelectByEntry(entry))
393 return;
394
395 // If the path doesn't exist, updates sub directories and tryes again.
396 this.updateSubDirectories(
397 false /* recursive */,
398 this.searchAndSelectByEntry.bind(this, entry));
399 };
400
401 /**
402 * Executes the assigned action as a drop target.
403 */
404 DirectoryItem.prototype.doDropTargetAction = function() {
405 this.expanded = true;
406 };
407
408 /**
409 * Executes the assigned action. DirectoryItem performs changeDirectory.
410 */
411 DirectoryItem.prototype.doAction = function() {
412 if (this.fullPath != this.directoryModel_.getCurrentDirPath())
413 this.directoryModel_.changeDirectory(this.fullPath);
414 };
415
416 /**
417 * Sets the context menu for directory tree.
418 * @param {cr.ui.Menu} menu Menu to be set.
419 */
420 DirectoryItem.prototype.setContextMenu = function(menu) {
421 if (this.entry && PathUtil.isEligibleForFolderShortcut(this.entry.fullPath))
422 cr.ui.contextMenuHandler.setContextMenu(this, menu);
423 };
424
425 ////////////////////////////////////////////////////////////////////////////////
426 // DirectoryTree
427
428 /**
429 * Tree of directories on the middle bar. This element is also the root of
430 * items, in other words, this is the parent of the top-level items.
431 *
432 * @constructor
433 * @extends {cr.ui.Tree}
434 */
435 function DirectoryTree() {}
436
437 /**
438 * Decorates an element.
439 * @param {HTMLElement} el Element to be DirectoryTree.
440 * @param {DirectoryModel} directoryModel Current DirectoryModel.
441 * @param {VolumeManagerWrapper} volumeManager VolumeManager of the system.
442 */
443 DirectoryTree.decorate = function(el, directoryModel, volumeManager) {
444 el.__proto__ = DirectoryTree.prototype;
445 (/** @type {DirectoryTree} */ el).decorate(directoryModel, volumeManager);
446 };
447
448 DirectoryTree.prototype = {
449 __proto__: cr.ui.Tree.prototype,
450
451 // DirectoryTree is always expanded.
452 get expanded() { return true; },
453 /**
454 * @param {boolean} value Not used.
455 */
456 set expanded(value) {},
457
458 /**
459 * The DirectoryEntry corresponding to this DirectoryItem. This may be
460 * a dummy DirectoryEntry.
461 * @type {DirectoryEntry|Object}
462 * @override
463 **/
464 get entry() {
465 return this.dirEntry_;
466 },
467
468 /**
469 * The DirectoryModel this tree corresponds to.
470 * @type {DirectoryModel}
471 */
472 get directoryModel() {
473 return this.directoryModel_;
474 },
475
476 /**
477 * The VolumeManager instance of the system.
478 * @type {VolumeManager}
479 */
480 get volumeManager() {
481 return this.volumeManager_;
482 },
483 };
484
485 cr.defineProperty(DirectoryTree, 'contextMenuForSubitems', cr.PropertyKind.JS);
486
487 /**
488 * Calls DirectoryItemTreeBaseMethods.updateSubElementsFromList().
489 *
490 * @param {boolean} recursive True if the all visible sub-directories are
491 * updated recursively including left arrows. If false, the update walks
492 * only immediate child directories without arrows.
493 */
494 DirectoryTree.prototype.updateSubElementsFromList = function(recursive) {
495 DirectoryItemTreeBaseMethods.updateSubElementsFromList.call(this, recursive);
496 };
497
498 /**
499 * Calls DirectoryItemTreeBaseMethods.updateSubElementsFromList().
500 * @param {DirectoryEntry|Object} entry The entry to be searched for. Can be
501 * a fake.
502 * @return {boolean} True if the parent item is found.
503 */
504 DirectoryTree.prototype.searchAndSelectByEntry = function(entry) {
505 return DirectoryItemTreeBaseMethods.searchAndSelectByEntry.call(this, entry);
506 };
507
508 /**
509 * Decorates an element.
510 * @param {DirectoryModel} directoryModel Current DirectoryModel.
511 * @param {VolumeManagerWrapper} volumeManager VolumeManager of the system.
512 */
513 DirectoryTree.prototype.decorate = function(directoryModel, volumeManager) {
514 cr.ui.Tree.prototype.decorate.call(this);
515
516 this.directoryModel_ = directoryModel;
517 this.volumeManager_ = volumeManager;
518 this.entries_ = DirectoryTreeUtil.generateTopLevelEntries();
519
520 this.fileFilter_ = this.directoryModel_.getFileFilter();
521 this.fileFilter_.addEventListener('changed',
522 this.onFilterChanged_.bind(this));
523
524 this.directoryModel_.addEventListener('directory-changed',
525 this.onCurrentDirectoryChanged_.bind(this));
526
527 // Add a handler for directory change.
528 this.addEventListener('change', function() {
529 if (this.selectedItem &&
530 (!this.currentEntry_ ||
531 !util.isSameEntry(this.currentEntry_, this.selectedItem.entry))) {
532 this.currentEntry_ = this.selectedItem.entry;
533 this.selectedItem.doAction();
534 return;
535 }
536 }.bind(this));
537
538 this.privateOnDirectoryChangedBound_ =
539 this.onDirectoryContentChanged_.bind(this);
540 chrome.fileBrowserPrivate.onDirectoryChanged.addListener(
541 this.privateOnDirectoryChangedBound_);
542
543 this.scrollBar_ = MainPanelScrollBar();
544 this.scrollBar_.initialize(this.parentNode, this);
545
546 // Once, draws the list with the fake '/drive/' entry.
547 this.redraw(false /* recursive */);
548 // Resolves 'My Drive' entry and replaces the fake with the true one.
549 this.maybeResolveMyDriveRoot_(function() {
550 // After the true entry is resolved, draws the list again.
551 this.redraw(true /* recursive */);
552 }.bind(this));
553 };
554
555 /**
556 * Select the item corresponding to the given entry.
557 * @param {DirectoryEntry|Object} entry The directory entry to be selected. Can
558 * be a fake.
559 */
560 DirectoryTree.prototype.selectByEntry = function(entry) {
561 // If the target directory is not in the tree, do nothing.
562 if (!DirectoryTreeUtil.isEligiblePathForDirectoryTree(entry.fullPath))
563 return;
564
565 this.maybeResolveMyDriveRoot_(function() {
566 if (this.selectedItem && util.isSameEntry(entry, this.selectedItem.entry))
567 return;
568
569 if (this.searchAndSelectByEntry(entry))
570 return;
571
572 this.selectedItem = null;
573 this.updateSubDirectories(
574 false /* recursive */,
575 // Success callback, failure is not handled.
576 function() {
577 if (!this.searchAndSelectByEntry(entry))
578 this.selectedItem = null;
579 }.bind(this));
580 }.bind(this));
581 };
582
583 /**
584 * Resolves the My Drive root's entry, if it is a fake. If the entry is already
585 * resolved to a DirectoryEntry, completionCallback() will be called
586 * immediately.
587 * @param {function()} completionCallback Called when the resolving is
588 * done (or the entry is already resolved), regardless if it is
589 * successfully done or not.
590 * @private
591 */
592 DirectoryTree.prototype.maybeResolveMyDriveRoot_ = function(
593 completionCallback) {
594 var myDriveItem = this.items[0];
595 if (!util.isFakeDirectoryEntry(myDriveItem.entry)) {
596 // The entry is already resolved. Don't need to try again.
597 completionCallback();
598 return;
599 }
600
601 // The entry is a fake.
602 this.directoryModel_.resolveDirectory(
603 myDriveItem.fullPath,
604 function(entry) {
605 if (!util.isFakeDirectoryEntry(entry)) {
606 myDriveItem.dirEntry_ = entry;
607 }
608
609 completionCallback();
610 },
611 completionCallback);
612 };
613
614 /**
615 * Retrieves the latest subdirectories and update them on the tree.
616 * @param {boolean} recursive True if the update is recursively.
617 * @param {function()=} opt_successCallback Callback called on success.
618 * @param {function()=} opt_errorCallback Callback called on error.
619 */
620 DirectoryTree.prototype.updateSubDirectories = function(
621 recursive, opt_successCallback, opt_errorCallback) {
622 this.entries_ = DirectoryTreeUtil.generateTopLevelEntries();
623 this.redraw(recursive);
624 if (opt_successCallback)
625 opt_successCallback();
626 };
627
628 /**
629 * Redraw the list.
630 * @param {boolean} recursive True if the update is recursively. False if the
631 * only root items are updated.
632 */
633 DirectoryTree.prototype.redraw = function(recursive) {
634 this.updateSubElementsFromList(recursive);
635 };
636
637 /**
638 * Invoked when the filter is changed.
639 * @private
640 */
641 DirectoryTree.prototype.onFilterChanged_ = function() {
642 // Returns immediately, if the tree is hidden.
643 if (this.hidden)
644 return;
645
646 this.redraw(true /* recursive */);
647 };
648
649 /**
650 * Invoked when a directory is changed.
651 * @param {!UIEvent} event Event.
652 * @private
653 */
654 DirectoryTree.prototype.onDirectoryContentChanged_ = function(event) {
655 if (event.eventType == 'changed') {
656 var path = util.extractFilePath(event.directoryUrl);
657 if (!DirectoryTreeUtil.isEligiblePathForDirectoryTree(path))
658 return;
659
660 var myDriveItem = this.items[0];
661 myDriveItem.updateItemByPath(path);
662 }
663 };
664
665 /**
666 * Invoked when the current directory is changed.
667 * @param {!UIEvent} event Event.
668 * @private
669 */
670 DirectoryTree.prototype.onCurrentDirectoryChanged_ = function(event) {
671 this.selectByEntry(event.newDirEntry);
672 };
673
674 /**
675 * Sets the margin height for the transparent preview panel at the bottom.
676 * @param {number} margin Margin to be set in px.
677 */
678 DirectoryTree.prototype.setBottomMarginForPanel = function(margin) {
679 this.style.paddingBottom = margin + 'px';
680 this.scrollBar_.setBottomMarginForPanel(margin);
681 };
682
683 /**
684 * Updates the UI after the layout has changed.
685 */
686 DirectoryTree.prototype.relayout = function() {
687 cr.dispatchSimpleEvent(this, 'relayout');
688 };
OLDNEW
« no previous file with comments | « chrome/browser/resources/file_manager/js/directory_model.js ('k') | chrome/browser/resources/file_manager/js/drag_selector.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698