Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / 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 loop;
56   var resolveParent = function(inEntry, callback) {
57     entries.unshift(inEntry);
58     if (!this.volumeManager_.getLocationInfo(inEntry).isRootEntry) {
59       inEntry.getParent(function(parent) {
60         resolveParent(parent, callback);
61       }, function() {
62         error = true;
63         callback();
64       });
65     } else {
66       callback();
67     }
68   }.bind(this);
69   queue.run(resolveParent.bind(null, entry));
70
71   // Override DRIVE_OTHER root to DRIVE_SHARED_WITH_ME root.
72   queue.run(function(callback) {
73     // If an error was occured, just skip.
74     if (error) {
75       callback();
76       return;
77     }
78
79     // If the path is not under the drive other root, it is not needed to
80     // override root type.
81     var locationInfo = this.volumeManager_.getLocationInfo(entry);
82     if (!locationInfo) {
83       error = true;
84       callback();
85       return;
86     }
87     if (locationInfo.rootType !== RootType.DRIVE_OTHER) {
88       callback();
89       return;
90     }
91
92     // Otherwise check the metadata of the directory localted at just under
93     // drive other.
94     if (!entries[1]) {
95       error = true;
96       callback();
97       return;
98     }
99     this.metadataCache_.getOne(entries[1], 'drive', function(result) {
100       if (result && result.sharedWithMe)
101         entries[0] = RootType.DRIVE_SHARED_WITH_ME;
102       else
103         entries.shift();
104       callback();
105     });
106   }.bind(this));
107
108   // Update DOM element.
109   queue.run(function(sequence, callback) {
110     // Check the sequence number to skip requests that are out of date.
111     if (this.showSequence_ === sequence && !error)
112       this.updateInternal_(entries);
113     callback();
114   }.bind(this, this.showSequence_));
115 };
116
117 /**
118  * Updates the breadcrumb display.
119  * TODO(mtomasz): Simplify by passing always Entries (or a fake).
120  * @param {Array.<Entry|RootType>} entries Enries or root types on the target
121  *     path.
122  * @private
123  */
124 BreadcrumbsController.prototype.updateInternal_ = function(entries) {
125   // Make elements.
126   var doc = this.bc_.ownerDocument;
127   for (var i = 0; i < entries.length; i++) {
128     // Add a component.
129     var entry = entries[i];
130     var div = doc.createElement('div');
131     div.className = 'breadcrumb-path';
132     if (entry === RootType.DRIVE_SHARED_WITH_ME) {
133       div.textContent = PathUtil.getRootTypeLabel(
134           RootType.DRIVE_SHARED_WITH_ME);
135     } else {
136       var locationInfo = this.volumeManager_.getLocationInfo(entry);
137       div.textContent = (locationInfo && locationInfo.isRootEntry) ?
138           PathUtil.getRootTypeLabel(locationInfo.rootType) : entry.name;
139     }
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 };