Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / ui / file_manager / file_manager / foreground / js / ui / breadcrumbs_controller.js
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.
4
5 'use strict';
6
7 /**
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.
12  * @constructor
13  */
14 function BreadcrumbsController(div, metadataCache, volumeManager) {
15   this.bc_ = div;
16   this.metadataCache_ = metadataCache;
17   this.volumeManager_ = volumeManager;
18   this.entry_ = null;
19
20   /**
21    * Sequence value to skip requests that are out of date.
22    * @type {number}
23    * @private
24    */
25   this.showSequence_ = 0;
26
27   // Register events and seql the object.
28   div.addEventListener('click', this.onClick_.bind(this));
29 }
30
31 /**
32  * Extends cr.EventTarget.
33  */
34 BreadcrumbsController.prototype.__proto__ = cr.EventTarget.prototype;
35
36 /**
37  * Shows breadcrumbs.
38  *
39  * @param {Entry} entry Target entry.
40  */
41 BreadcrumbsController.prototype.show = function(entry) {
42   if (entry === this.entry_)
43     return;
44
45   this.entry_ = entry;
46   this.showSequence_++;
47
48   var queue = new AsyncUtil.Queue();
49   var entries = [];
50   var error = false;
51
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) {
56       error = true;
57       callback();
58       return;
59     }
60
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);
73           else
74             error = true;
75         } else {
76           entries.unshift(currentEntry);
77         }
78         // Finishes traversal since the current is root.
79         callback();
80       });
81       return;
82     }
83
84     entries.unshift(currentEntry);
85     if (!entryLocationInfo.isRootEntry) {
86       currentEntry.getParent(function(parentEntry) {
87         resolveParent(parentEntry, currentEntry, callback);
88       }.bind(this), function() {
89         error = true;
90         callback();
91       });
92     } else {
93       callback();
94     }
95   }.bind(this);
96
97   queue.run(resolveParent.bind(this, entry, null));
98
99   queue.run(function(callback) {
100     // If an error occurred, just skip.
101     if (error) {
102       callback();
103       return;
104     }
105
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);
109     if (!locationInfo)
110       error = true;
111
112     callback();
113   }.bind(this));
114
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 = '';
121       if (!error)
122         this.updateInternal_(entries);
123     }
124     callback();
125   }.bind(this, this.showSequence_));
126 };
127
128 /**
129  * Updates the breadcrumb display.
130  * @param {Array.<Entry>} entries Entries on the target path.
131  * @private
132  */
133 BreadcrumbsController.prototype.updateInternal_ = function(entries) {
134   // Make elements.
135   var doc = this.bc_.ownerDocument;
136   for (var i = 0; i < entries.length; i++) {
137     // Add a component.
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);
142     div.entry = entry;
143     this.bc_.appendChild(div);
144
145     // If this is the last component, break here.
146     if (i === entries.length - 1) {
147       div.classList.add('breadcrumb-last');
148       break;
149     }
150
151     // Add a separator.
152     var separator = doc.createElement('div');
153     separator.className = 'separator';
154     this.bc_.appendChild(separator);
155   }
156
157   this.truncate();
158 };
159
160 /**
161  * Updates breadcrumbs widths in order to truncate it properly.
162  */
163 BreadcrumbsController.prototype.truncate = function() {
164   if (!this.bc_.firstChild)
165    return;
166
167   // Assume style.width == clientWidth (items have no margins or paddings).
168
169   for (var item = this.bc_.firstChild; item; item = item.nextSibling) {
170     item.removeAttribute('style');
171     item.removeAttribute('collapsed');
172   }
173
174   var containerWidth = this.bc_.clientWidth;
175
176   var pathWidth = 0;
177   var currentWidth = 0;
178   var lastSeparator;
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;
184     } else {
185       currentWidth += item.clientWidth;
186     }
187   }
188   if (pathWidth + currentWidth <= containerWidth)
189     return;
190   if (!lastSeparator) {
191     this.bc_.lastChild.style.width = Math.min(currentWidth, containerWidth) +
192                                       'px';
193     return;
194   }
195   var lastCrumbSeparatorWidth = lastSeparator.clientWidth;
196   // Current directory name may occupy up to 70% of space or even more if the
197   // path is short.
198   var maxPathWidth = Math.max(Math.round(containerWidth * 0.3),
199                               containerWidth - currentWidth);
200   maxPathWidth = Math.min(pathWidth, maxPathWidth);
201
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';
213
214     lastSeparator = parentCrumb.previousSibling;
215     if (!lastSeparator)
216       return;
217     collapsedWidth += lastSeparator.clientWidth;
218     maxPathWidth = Math.max(0, maxPathWidth - lastSeparator.clientWidth);
219   }
220
221   pathWidth = 0;
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;
234     } else {
235       // Truncate the last visible crumb.
236       item.style.width = (maxPathWidth - pathWidth) + 'px';
237       pathWidth = maxPathWidth;
238     }
239   }
240
241   currentWidth = Math.min(currentWidth,
242                           containerWidth - pathWidth - collapsedWidth);
243   this.bc_.lastChild.style.width =
244       (currentWidth - lastCrumbSeparatorWidth) + 'px';
245 };
246
247 /**
248  * Hide breadcrumbs div.
249  */
250 BreadcrumbsController.prototype.hide = function() {
251   this.bc_.hidden = true;
252 };
253
254 /**
255  * Handle a click event on a breadcrumb element.
256  * @param {Event} event The click event.
257  * @private
258  */
259 BreadcrumbsController.prototype.onClick_ = function(event) {
260   if (!event.target.classList.contains('breadcrumb-path') ||
261       event.target.classList.contains('breadcrumb-last'))
262     return;
263
264   var newEvent = new Event('pathclick');
265   newEvent.entry = event.target.entry;
266   this.dispatchEvent(newEvent);
267 };