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 * @extends cr.EventTarget
9 * @param {HTMLDivElement} div Div container for breadcrumbs.
10 * @param {MetadataCache} metadataCache To retrieve metadata.
11 * @param {VolumeManagerWrapper} volumeManager Volume manager.
14 function BreadcrumbsController(div, metadataCache, volumeManager) {
16 this.metadataCache_ = metadataCache;
17 this.volumeManager_ = volumeManager;
21 * Sequence value to skip requests that are out of date.
25 this.showSequence_ = 0;
27 // Register events and seql the object.
28 div.addEventListener('click', this.onClick_.bind(this));
32 * Extends cr.EventTarget.
34 BreadcrumbsController.prototype.__proto__ = cr.EventTarget.prototype;
39 * @param {Entry} entry Target entry.
41 BreadcrumbsController.prototype.show = function(entry) {
42 if (entry === this.entry_)
48 var queue = new AsyncUtil.Queue();
52 // Obtain entries from the target entry to the root.
53 var resolveParent = function(currentEntry, previousEntry, callback) {
54 var entryLocationInfo = this.volumeManager_.getLocationInfo(currentEntry);
55 if (!entryLocationInfo) {
61 if (entryLocationInfo.isRootEntry &&
62 entryLocationInfo.rootType ===
63 VolumeManagerCommon.RootType.DRIVE_OTHER) {
64 this.metadataCache_.getOne(previousEntry, 'drive', function(result) {
65 if (result && result.sharedWithMe) {
66 // Adds the shared-with-me entry instead.
67 var driveVolumeInfo = entryLocationInfo.volumeInfo;
68 var sharedWithMeEntry =
69 driveVolumeInfo.fakeEntries[
70 VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME];
71 if (sharedWithMeEntry)
72 entries.unshift(sharedWithMeEntry);
76 entries.unshift(currentEntry);
78 // Finishes traversal since the current is root.
84 entries.unshift(currentEntry);
85 if (!entryLocationInfo.isRootEntry) {
86 currentEntry.getParent(function(parentEntry) {
87 resolveParent(parentEntry, currentEntry, callback);
88 }.bind(this), function() {
97 queue.run(resolveParent.bind(this, entry, null));
99 queue.run(function(callback) {
100 // If an error occurred, just skip.
106 // If the path is not under the drive other root, it is not needed to
107 // override root type.
108 var locationInfo = this.volumeManager_.getLocationInfo(entry);
115 // Update DOM element.
116 queue.run(function(sequence, callback) {
117 // Check the sequence number to skip requests that are out of date.
118 if (this.showSequence_ === sequence) {
119 this.bc_.hidden = false;
120 this.bc_.textContent = '';
122 this.updateInternal_(entries);
125 }.bind(this, this.showSequence_));
129 * Updates the breadcrumb display.
130 * @param {Array.<Entry>} entries Entries on the target path.
133 BreadcrumbsController.prototype.updateInternal_ = function(entries) {
135 var doc = this.bc_.ownerDocument;
136 for (var i = 0; i < entries.length; i++) {
138 var entry = entries[i];
139 var div = doc.createElement('div');
140 div.className = 'breadcrumb-path entry-name';
141 div.textContent = util.getEntryLabel(this.volumeManager_, entry);
143 this.bc_.appendChild(div);
145 // If this is the last component, break here.
146 if (i === entries.length - 1) {
147 div.classList.add('breadcrumb-last');
152 var separator = doc.createElement('div');
153 separator.className = 'separator';
154 this.bc_.appendChild(separator);
161 * Updates breadcrumbs widths in order to truncate it properly.
163 BreadcrumbsController.prototype.truncate = function() {
164 if (!this.bc_.firstChild)
167 // Assume style.width == clientWidth (items have no margins or paddings).
169 for (var item = this.bc_.firstChild; item; item = item.nextSibling) {
170 item.removeAttribute('style');
171 item.removeAttribute('collapsed');
174 var containerWidth = this.bc_.clientWidth;
177 var currentWidth = 0;
179 for (var item = this.bc_.firstChild; item; item = item.nextSibling) {
180 if (item.className == 'separator') {
181 pathWidth += currentWidth;
182 currentWidth = item.clientWidth;
183 lastSeparator = item;
185 currentWidth += item.clientWidth;
188 if (pathWidth + currentWidth <= containerWidth)
190 if (!lastSeparator) {
191 this.bc_.lastChild.style.width = Math.min(currentWidth, containerWidth) +
195 var lastCrumbSeparatorWidth = lastSeparator.clientWidth;
196 // Current directory name may occupy up to 70% of space or even more if the
198 var maxPathWidth = Math.max(Math.round(containerWidth * 0.3),
199 containerWidth - currentWidth);
200 maxPathWidth = Math.min(pathWidth, maxPathWidth);
202 var parentCrumb = lastSeparator.previousSibling;
203 var collapsedWidth = 0;
204 if (parentCrumb && pathWidth - maxPathWidth > parentCrumb.clientWidth) {
205 // At least one crumb is hidden completely (or almost completely).
206 // Show sign of hidden crumbs like this:
207 // root > some di... > ... > current directory.
208 parentCrumb.setAttribute('collapsed', '');
209 collapsedWidth = Math.min(maxPathWidth, parentCrumb.clientWidth);
210 maxPathWidth -= collapsedWidth;
211 if (parentCrumb.clientWidth != collapsedWidth)
212 parentCrumb.style.width = collapsedWidth + 'px';
214 lastSeparator = parentCrumb.previousSibling;
217 collapsedWidth += lastSeparator.clientWidth;
218 maxPathWidth = Math.max(0, maxPathWidth - lastSeparator.clientWidth);
222 for (var item = this.bc_.firstChild; item != lastSeparator;
223 item = item.nextSibling) {
224 // TODO(serya): Mixing access item.clientWidth and modifying style and
225 // attributes could cause multiple layout reflows.
226 if (pathWidth + item.clientWidth <= maxPathWidth) {
227 pathWidth += item.clientWidth;
228 } else if (pathWidth == maxPathWidth) {
229 item.style.width = '0';
230 } else if (item.classList.contains('separator')) {
231 // Do not truncate separator. Instead let the last crumb be longer.
232 item.style.width = '0';
233 maxPathWidth = pathWidth;
235 // Truncate the last visible crumb.
236 item.style.width = (maxPathWidth - pathWidth) + 'px';
237 pathWidth = maxPathWidth;
241 currentWidth = Math.min(currentWidth,
242 containerWidth - pathWidth - collapsedWidth);
243 this.bc_.lastChild.style.width =
244 (currentWidth - lastCrumbSeparatorWidth) + 'px';
248 * Hide breadcrumbs div.
250 BreadcrumbsController.prototype.hide = function() {
251 this.bc_.hidden = true;
255 * Handle a click event on a breadcrumb element.
256 * @param {Event} event The click event.
259 BreadcrumbsController.prototype.onClick_ = function(event) {
260 if (!event.target.classList.contains('breadcrumb-path') ||
261 event.target.classList.contains('breadcrumb-last'))
264 var newEvent = new Event('pathclick');
265 newEvent.entry = event.target.entry;
266 this.dispatchEvent(newEvent);