Upstream version 7.36.149.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.bc_.hidden = false;
47   this.bc_.textContent = '';
48   this.showSequence_++;
49
50   var queue = new AsyncUtil.Queue();
51   var entries = [];
52   var error = false;
53
54   // Obtain entries from the target entry to the root.
55   var resolveParent = function(currentEntry, previousEntry, callback) {
56     var entryLocationInfo = this.volumeManager_.getLocationInfo(currentEntry);
57     if (!entryLocationInfo) {
58       error = true;
59       callback();
60       return;
61     }
62
63     if (entryLocationInfo.isRootEntry &&
64         entryLocationInfo.rootType ===
65             VolumeManagerCommon.RootType.DRIVE_OTHER) {
66       this.metadataCache_.getOne(previousEntry, 'drive', function(result) {
67         if (result && result.sharedWithMe) {
68           // Adds the shared-with-me entry instead.
69           var driveVolumeInfo = entryLocationInfo.volumeInfo;
70           var sharedWithMeEntry =
71               driveVolumeInfo.fakeEntries[
72                   VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME];
73           if (sharedWithMeEntry)
74             entries.unshift(sharedWithMeEntry);
75           else
76             error = true;
77         } else {
78           entries.unshift(currentEntry);
79         }
80         // Finishes traversal since the current is root.
81         callback();
82       });
83       return;
84     }
85
86     entries.unshift(currentEntry);
87     if (!entryLocationInfo.isRootEntry) {
88       currentEntry.getParent(function(parentEntry) {
89         resolveParent(parentEntry, currentEntry, callback);
90       }.bind(this), function() {
91         error = true;
92         callback();
93       });
94     } else {
95       callback();
96     }
97   }.bind(this);
98
99   queue.run(resolveParent.bind(this, entry, null));
100
101   queue.run(function(callback) {
102     // If an error was occured, just skip.
103     if (error) {
104       callback();
105       return;
106     }
107
108     // If the path is not under the drive other root, it is not needed to
109     // override root type.
110     var locationInfo = this.volumeManager_.getLocationInfo(entry);
111     if (!locationInfo)
112       error = true;
113
114     callback();
115   }.bind(this));
116
117   // Update DOM element.
118   queue.run(function(sequence, callback) {
119     // Check the sequence number to skip requests that are out of date.
120     if (this.showSequence_ === sequence && !error)
121       this.updateInternal_(entries);
122     callback();
123   }.bind(this, this.showSequence_));
124 };
125
126 /**
127  * Updates the breadcrumb display.
128  * @param {Array.<Entry>} entries Entries on the target path.
129  * @private
130  */
131 BreadcrumbsController.prototype.updateInternal_ = function(entries) {
132   // Make elements.
133   var doc = this.bc_.ownerDocument;
134   for (var i = 0; i < entries.length; i++) {
135     // Add a component.
136     var entry = entries[i];
137     var div = doc.createElement('div');
138     div.className = 'breadcrumb-path';
139     div.textContent = util.getEntryLabel(this.volumeManager_, entry);
140     div.entry = entry;
141     this.bc_.appendChild(div);
142
143     // If this is the last component, break here.
144     if (i === entries.length - 1) {
145       div.classList.add('breadcrumb-last');
146       break;
147     }
148
149     // Add a separator.
150     var separator = doc.createElement('div');
151     separator.className = 'separator';
152     this.bc_.appendChild(separator);
153   }
154
155   this.truncate();
156 };
157
158 /**
159  * Updates breadcrumbs widths in order to truncate it properly.
160  */
161 BreadcrumbsController.prototype.truncate = function() {
162   if (!this.bc_.firstChild)
163    return;
164
165   // Assume style.width == clientWidth (items have no margins or paddings).
166
167   for (var item = this.bc_.firstChild; item; item = item.nextSibling) {
168     item.removeAttribute('style');
169     item.removeAttribute('collapsed');
170   }
171
172   var containerWidth = this.bc_.clientWidth;
173
174   var pathWidth = 0;
175   var currentWidth = 0;
176   var lastSeparator;
177   for (var item = this.bc_.firstChild; item; item = item.nextSibling) {
178     if (item.className == 'separator') {
179       pathWidth += currentWidth;
180       currentWidth = item.clientWidth;
181       lastSeparator = item;
182     } else {
183       currentWidth += item.clientWidth;
184     }
185   }
186   if (pathWidth + currentWidth <= containerWidth)
187     return;
188   if (!lastSeparator) {
189     this.bc_.lastChild.style.width = Math.min(currentWidth, containerWidth) +
190                                       'px';
191     return;
192   }
193   var lastCrumbSeparatorWidth = lastSeparator.clientWidth;
194   // Current directory name may occupy up to 70% of space or even more if the
195   // path is short.
196   var maxPathWidth = Math.max(Math.round(containerWidth * 0.3),
197                               containerWidth - currentWidth);
198   maxPathWidth = Math.min(pathWidth, maxPathWidth);
199
200   var parentCrumb = lastSeparator.previousSibling;
201   var collapsedWidth = 0;
202   if (parentCrumb && pathWidth - maxPathWidth > parentCrumb.clientWidth) {
203     // At least one crumb is hidden completely (or almost completely).
204     // Show sign of hidden crumbs like this:
205     // root > some di... > ... > current directory.
206     parentCrumb.setAttribute('collapsed', '');
207     collapsedWidth = Math.min(maxPathWidth, parentCrumb.clientWidth);
208     maxPathWidth -= collapsedWidth;
209     if (parentCrumb.clientWidth != collapsedWidth)
210       parentCrumb.style.width = collapsedWidth + 'px';
211
212     lastSeparator = parentCrumb.previousSibling;
213     if (!lastSeparator)
214       return;
215     collapsedWidth += lastSeparator.clientWidth;
216     maxPathWidth = Math.max(0, maxPathWidth - lastSeparator.clientWidth);
217   }
218
219   pathWidth = 0;
220   for (var item = this.bc_.firstChild; item != lastSeparator;
221        item = item.nextSibling) {
222     // TODO(serya): Mixing access item.clientWidth and modifying style and
223     // attributes could cause multiple layout reflows.
224     if (pathWidth + item.clientWidth <= maxPathWidth) {
225       pathWidth += item.clientWidth;
226     } else if (pathWidth == maxPathWidth) {
227       item.style.width = '0';
228     } else if (item.classList.contains('separator')) {
229       // Do not truncate separator. Instead let the last crumb be longer.
230       item.style.width = '0';
231       maxPathWidth = pathWidth;
232     } else {
233       // Truncate the last visible crumb.
234       item.style.width = (maxPathWidth - pathWidth) + 'px';
235       pathWidth = maxPathWidth;
236     }
237   }
238
239   currentWidth = Math.min(currentWidth,
240                           containerWidth - pathWidth - collapsedWidth);
241   this.bc_.lastChild.style.width =
242       (currentWidth - lastCrumbSeparatorWidth) + 'px';
243 };
244
245 /**
246  * Hide breadcrumbs div.
247  */
248 BreadcrumbsController.prototype.hide = function() {
249   this.bc_.hidden = true;
250 };
251
252 /**
253  * Handle a click event on a breadcrumb element.
254  * @param {Event} event The click event.
255  * @private
256  */
257 BreadcrumbsController.prototype.onClick_ = function(event) {
258   if (!event.target.classList.contains('breadcrumb-path') ||
259       event.target.classList.contains('breadcrumb-last'))
260     return;
261
262   var newEvent = new Event('pathclick');
263   newEvent.entry = event.target.entry;
264   this.dispatchEvent(newEvent);
265 };