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

Side by Side Diff: chrome/browser/resources/file_manager/js/file_table.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, 1 month 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) 2012 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 * Namespace for utility functions.
9 */
10 var filelist = {};
11
12 /**
13 * Custom column model for advanced auto-resizing.
14 *
15 * @param {Array.<cr.ui.table.TableColumn>} tableColumns Table columns.
16 * @extends {cr.ui.table.TableColumnModel}
17 * @constructor
18 */
19 function FileTableColumnModel(tableColumns) {
20 cr.ui.table.TableColumnModel.call(this, tableColumns);
21 }
22
23 /**
24 * The columns whose index is less than the constant are resizable.
25 * @const
26 * @type {number}
27 * @private
28 */
29 FileTableColumnModel.RESIZABLE_LENGTH_ = 4;
30
31 /**
32 * Inherits from cr.ui.TableColumnModel.
33 */
34 FileTableColumnModel.prototype.__proto__ =
35 cr.ui.table.TableColumnModel.prototype;
36
37 /**
38 * Minimum width of column.
39 * @const
40 * @type {number}
41 * @private
42 */
43 FileTableColumnModel.MIN_WIDTH_ = 10;
44
45 /**
46 * Sets column width so that the column dividers move to the specified position.
47 * This function also check the width of each column and keep the width larger
48 * than MIN_WIDTH_.
49 *
50 * @private
51 * @param {Array.<number>} newPos Positions of each column dividers.
52 */
53 FileTableColumnModel.prototype.applyColumnPositions_ = function(newPos) {
54 // Check the minimum width and adjust the positions.
55 for (var i = 0; i < newPos.length - 2; i++) {
56 if (newPos[i + 1] - newPos[i] < FileTableColumnModel.MIN_WIDTH_) {
57 newPos[i + 1] = newPos[i] + FileTableColumnModel.MIN_WIDTH_;
58 }
59 }
60 for (var i = newPos.length - 1; i >= 2; i--) {
61 if (newPos[i] - newPos[i - 1] < FileTableColumnModel.MIN_WIDTH_) {
62 newPos[i - 1] = newPos[i] - FileTableColumnModel.MIN_WIDTH_;
63 }
64 }
65 // Set the new width of columns
66 for (var i = 0; i < FileTableColumnModel.RESIZABLE_LENGTH_; i++) {
67 this.columns_[i].width = newPos[i + 1] - newPos[i];
68 }
69 };
70
71 /**
72 * Normalizes widths to make their sum 100% if possible. Uses the proportional
73 * approach with some additional constraints.
74 *
75 * @param {number} contentWidth Target width.
76 * @override
77 */
78 FileTableColumnModel.prototype.normalizeWidths = function(contentWidth) {
79 var totalWidth = 0;
80 var fixedWidth = 0;
81 // Some columns have fixed width.
82 for (var i = 0; i < this.columns_.length; i++) {
83 if (i < FileTableColumnModel.RESIZABLE_LENGTH_)
84 totalWidth += this.columns_[i].width;
85 else
86 fixedWidth += this.columns_[i].width;
87 }
88 var newTotalWidth = Math.max(contentWidth - fixedWidth, 0);
89 var positions = [0];
90 var sum = 0;
91 for (var i = 0; i < FileTableColumnModel.RESIZABLE_LENGTH_; i++) {
92 var column = this.columns_[i];
93 sum += column.width;
94 // Faster alternative to Math.floor for non-negative numbers.
95 positions[i + 1] = ~~(newTotalWidth * sum / totalWidth);
96 }
97 this.applyColumnPositions_(positions);
98 };
99
100 /**
101 * Handles to the start of column resizing by splitters.
102 */
103 FileTableColumnModel.prototype.handleSplitterDragStart = function() {
104 this.columnPos_ = [0];
105 for (var i = 0; i < this.columns_.length; i++) {
106 this.columnPos_[i + 1] = this.columns_[i].width + this.columnPos_[i];
107 }
108 };
109
110 /**
111 * Handles to the end of column resizing by splitters.
112 */
113 FileTableColumnModel.prototype.handleSplitterDragEnd = function() {
114 this.columnPos_ = null;
115 };
116
117 /**
118 * Sets the width of column with keeping the total width of table.
119 * @param {number} columnIndex Index of column that is resized.
120 * @param {number} columnWidth New width of the column.
121 */
122 FileTableColumnModel.prototype.setWidthAndKeepTotal = function(
123 columnIndex, columnWidth) {
124 // Skip to resize 'selection' column
125 if (columnIndex < 0 ||
126 columnIndex >= FileTableColumnModel.RESIZABLE_LENGTH_ ||
127 !this.columnPos_) {
128 return;
129 }
130
131 // Calculate new positions of column splitters.
132 var newPosStart =
133 this.columnPos_[columnIndex] + Math.max(columnWidth,
134 FileTableColumnModel.MIN_WIDTH_);
135 var newPos = [];
136 var posEnd = this.columnPos_[FileTableColumnModel.RESIZABLE_LENGTH_];
137 for (var i = 0; i < columnIndex + 1; i++) {
138 newPos[i] = this.columnPos_[i];
139 }
140 for (var i = columnIndex + 1;
141 i < FileTableColumnModel.RESIZABLE_LENGTH_;
142 i++) {
143 var posStart = this.columnPos_[columnIndex + 1];
144 newPos[i] = (posEnd - newPosStart) *
145 (this.columnPos_[i] - posStart) /
146 (posEnd - posStart) +
147 newPosStart;
148 // Faster alternative to Math.floor for non-negative numbers.
149 newPos[i] = ~~newPos[i];
150 }
151 newPos[columnIndex] = this.columnPos_[columnIndex];
152 newPos[FileTableColumnModel.RESIZABLE_LENGTH_] = posEnd;
153 this.applyColumnPositions_(newPos);
154
155 // Notifiy about resizing
156 cr.dispatchSimpleEvent(this, 'resize');
157 };
158
159 /**
160 * Custom splitter that resizes column with retaining the sum of all the column
161 * width.
162 */
163 var FileTableSplitter = cr.ui.define('div');
164
165 /**
166 * Inherits from cr.ui.TableSplitter.
167 */
168 FileTableSplitter.prototype.__proto__ = cr.ui.TableSplitter.prototype;
169
170 /**
171 * Handles the drag start event.
172 */
173 FileTableSplitter.prototype.handleSplitterDragStart = function() {
174 cr.ui.TableSplitter.prototype.handleSplitterDragStart.call(this);
175 this.table_.columnModel.handleSplitterDragStart();
176 };
177
178 /**
179 * Handles the drag move event.
180 * @param {number} deltaX Horizontal mouse move offset.
181 */
182 FileTableSplitter.prototype.handleSplitterDragMove = function(deltaX) {
183 this.table_.columnModel.setWidthAndKeepTotal(this.columnIndex,
184 this.columnWidth_ + deltaX,
185 true);
186 };
187
188 /**
189 * Handles the drag end event.
190 */
191 FileTableSplitter.prototype.handleSplitterDragEnd = function() {
192 cr.ui.TableSplitter.prototype.handleSplitterDragEnd.call(this);
193 this.table_.columnModel.handleSplitterDragEnd();
194 };
195
196 /**
197 * File list Table View.
198 * @constructor
199 */
200 function FileTable() {
201 throw new Error('Designed to decorate elements');
202 }
203
204 /**
205 * Inherits from cr.ui.Table.
206 */
207 FileTable.prototype.__proto__ = cr.ui.Table.prototype;
208
209 /**
210 * Decorates the element.
211 * @param {HTMLElement} self Table to decorate.
212 * @param {MetadataCache} metadataCache To retrieve metadata.
213 * @param {boolean} fullPage True if it's full page File Manager,
214 * False if a file open/save dialog.
215 */
216 FileTable.decorate = function(self, metadataCache, fullPage) {
217 cr.ui.Table.decorate(self);
218 self.__proto__ = FileTable.prototype;
219 self.metadataCache_ = metadataCache;
220 self.collator_ = Intl.Collator([], {numeric: true, sensitivity: 'base'});
221
222 var columns = [
223 new cr.ui.table.TableColumn('name', str('NAME_COLUMN_LABEL'),
224 fullPage ? 386 : 324),
225 new cr.ui.table.TableColumn('size', str('SIZE_COLUMN_LABEL'),
226 110, true),
227 new cr.ui.table.TableColumn('type', str('TYPE_COLUMN_LABEL'),
228 fullPage ? 110 : 110),
229 new cr.ui.table.TableColumn('modificationTime',
230 str('DATE_COLUMN_LABEL'),
231 fullPage ? 150 : 210)
232 ];
233
234 columns[0].renderFunction = self.renderName_.bind(self);
235 columns[1].renderFunction = self.renderSize_.bind(self);
236 columns[1].defaultOrder = 'desc';
237 columns[2].renderFunction = self.renderType_.bind(self);
238 columns[3].renderFunction = self.renderDate_.bind(self);
239 columns[3].defaultOrder = 'desc';
240
241 var tableColumnModelClass;
242 tableColumnModelClass = FileTableColumnModel;
243 if (self.showCheckboxes) {
244 columns.push(new cr.ui.table.TableColumn('selection',
245 '',
246 50, true));
247 columns[4].renderFunction = self.renderSelection_.bind(self);
248 columns[4].headerRenderFunction =
249 self.renderSelectionColumnHeader_.bind(self);
250 columns[4].fixed = true;
251 }
252
253 var columnModel = Object.create(tableColumnModelClass.prototype, {
254 /**
255 * The number of columns.
256 * @type {number}
257 */
258 size: {
259 /**
260 * @this {FileTableColumnModel}
261 * @return {number} Number of columns.
262 */
263 get: function() {
264 return this.totalSize;
265 }
266 },
267
268 /**
269 * The number of columns.
270 * @type {number}
271 */
272 totalSize: {
273 /**
274 * @this {FileTableColumnModel}
275 * @return {number} Number of columns.
276 */
277 get: function() {
278 return columns.length;
279 }
280 },
281
282 /**
283 * Obtains a column by the specified horizontal position.
284 */
285 getHitColumn: {
286 /**
287 * @this {FileTableColumnModel}
288 * @param {number} x Horizontal position.
289 * @return {object} The object that contains column index, column width,
290 * and hitPosition where the horizontal position is hit in the column.
291 */
292 value: function(x) {
293 for (var i = 0; x >= this.columns_[i].width; i++) {
294 x -= this.columns_[i].width;
295 }
296 if (i >= this.columns_.length)
297 return null;
298 return {index: i, hitPosition: x, width: this.columns_[i].width};
299 }
300 }
301 });
302
303 tableColumnModelClass.call(columnModel, columns);
304 self.columnModel = columnModel;
305 self.setDateTimeFormat(true);
306 self.setRenderFunction(self.renderTableRow_.bind(self,
307 self.getRenderFunction()));
308
309 self.scrollBar_ = MainPanelScrollBar();
310 self.scrollBar_.initialize(self, self.list);
311 // Keep focus on the file list when clicking on the header.
312 self.header.addEventListener('mousedown', function(e) {
313 self.list.focus();
314 e.preventDefault();
315 });
316
317 var handleSelectionChange = function() {
318 var selectAll = self.querySelector('#select-all-checkbox');
319 if (selectAll)
320 self.updateSelectAllCheckboxState_(selectAll);
321 };
322
323 self.relayoutAggregation_ =
324 new AsyncUtil.Aggregation(self.relayoutImmediately_.bind(self));
325
326 Object.defineProperty(self.list_, 'selectionModel', {
327 /**
328 * @this {cr.ui.List}
329 * @return {cr.ui.ListSelectionModel} The current selection model.
330 */
331 get: function() {
332 return this.selectionModel_;
333 },
334 /**
335 * @this {cr.ui.List}
336 */
337 set: function(value) {
338 var sm = this.selectionModel;
339 if (sm)
340 sm.removeEventListener('change', handleSelectionChange);
341
342 util.callInheritedSetter(this, 'selectionModel', value);
343 sm = value;
344
345 if (sm)
346 sm.addEventListener('change', handleSelectionChange);
347 handleSelectionChange();
348 }
349 });
350
351 // Override header#redraw to use FileTableSplitter.
352 self.header_.redraw = function() {
353 this.__proto__.redraw.call(this);
354 // Extend table splitters
355 var splitters = this.querySelectorAll('.table-header-splitter');
356 for (var i = 0; i < splitters.length; i++) {
357 if (splitters[i] instanceof FileTableSplitter)
358 continue;
359 FileTableSplitter.decorate(splitters[i]);
360 }
361 };
362
363 // Save the last selection. This is used by shouldStartDragSelection.
364 self.list.addEventListener('mousedown', function(e) {
365 this.lastSelection_ = this.selectionModel.selectedIndexes;
366 }.bind(self), true);
367 self.list.shouldStartDragSelection =
368 self.shouldStartDragSelection_.bind(self);
369
370 /**
371 * Obtains the index list of elements that are hit by the point or the
372 * rectangle.
373 *
374 * @param {number} x X coordinate value.
375 * @param {number} y Y coordinate value.
376 * @param {=number} opt_width Width of the coordinate.
377 * @param {=number} opt_height Height of the coordinate.
378 * @return {Array.<number>} Index list of hit elements.
379 */
380 self.list.getHitElements = function(x, y, opt_width, opt_height) {
381 var currentSelection = [];
382 var bottom = y + (opt_height || 0);
383 for (var i = 0; i < this.selectionModel_.length; i++) {
384 var itemMetrics = this.getHeightsForIndex_(i);
385 if (itemMetrics.top < bottom && itemMetrics.top + itemMetrics.height >= y)
386 currentSelection.push(i);
387 }
388 return currentSelection;
389 };
390 };
391
392 /**
393 * Sets date and time format.
394 * @param {boolean} use12hourClock True if 12 hours clock, False if 24 hours.
395 */
396 FileTable.prototype.setDateTimeFormat = function(use12hourClock) {
397 this.timeFormatter_ = Intl.DateTimeFormat(
398 [] /* default locale */,
399 {hour: 'numeric', minute: 'numeric',
400 hour12: use12hourClock});
401 this.dateFormatter_ = Intl.DateTimeFormat(
402 [] /* default locale */,
403 {year: 'numeric', month: 'short', day: 'numeric',
404 hour: 'numeric', minute: 'numeric',
405 hour12: use12hourClock});
406 };
407
408 /**
409 * Obtains if the drag selection should be start or not by referring the mouse
410 * event.
411 * @param {MouseEvent} event Drag start event.
412 * @return {boolean} True if the mouse is hit to the background of the list.
413 * @private
414 */
415 FileTable.prototype.shouldStartDragSelection_ = function(event) {
416 // If the shift key is pressed, it should starts drag selection.
417 if (event.shiftKey)
418 return true;
419
420 // If the position values are negative, it points the out of list.
421 // It should start the drag selection.
422 var pos = DragSelector.getScrolledPosition(this.list, event);
423 if (!pos)
424 return false;
425 if (pos.x < 0 || pos.y < 0)
426 return true;
427
428 // If the item index is out of range, it should start the drag selection.
429 var itemHeight = this.list.measureItem().height;
430 // Faster alternative to Math.floor for non-negative numbers.
431 var itemIndex = ~~(pos.y / itemHeight);
432 if (itemIndex >= this.list.dataModel.length)
433 return true;
434
435 // If the pointed item is already selected, it should not start the drag
436 // selection.
437 if (this.lastSelection_.indexOf(itemIndex) != -1)
438 return false;
439
440 // If the horizontal value is not hit to column, it should start the drag
441 // selection.
442 var hitColumn = this.columnModel.getHitColumn(pos.x);
443 if (!hitColumn)
444 return true;
445
446 // Check if the point is on the column contents or not.
447 var item = this.list.getListItemByIndex(itemIndex);
448 switch (this.columnModel.columns_[hitColumn.index].id) {
449 case 'name':
450 var spanElement = item.querySelector('.filename-label span');
451 var spanRect = spanElement.getBoundingClientRect();
452 // The this.list.cachedBounds_ object is set by
453 // DragSelector.getScrolledPosition.
454 if (!this.list.cachedBounds)
455 return true;
456 var textRight =
457 spanRect.left - this.list.cachedBounds.left + spanRect.width;
458 return textRight <= hitColumn.hitPosition;
459 default:
460 return true;
461 }
462 };
463
464 /**
465 * Update check and disable states of the 'Select all' checkbox.
466 * @param {HTMLInputElement} checkbox The checkbox. If not passed, using
467 * the default one.
468 * @private
469 */
470 FileTable.prototype.updateSelectAllCheckboxState_ = function(checkbox) {
471 // TODO(serya): introduce this.selectionModel.selectedCount.
472 checkbox.checked = this.dataModel.length > 0 &&
473 this.dataModel.length == this.selectionModel.selectedIndexes.length;
474 checkbox.disabled = this.dataModel.length == 0;
475 };
476
477 /**
478 * Prepares the data model to be sorted by columns.
479 * @param {cr.ui.ArrayDataModel} dataModel Data model to prepare.
480 */
481 FileTable.prototype.setupCompareFunctions = function(dataModel) {
482 dataModel.setCompareFunction('name',
483 this.compareName_.bind(this));
484 dataModel.setCompareFunction('modificationTime',
485 this.compareMtime_.bind(this));
486 dataModel.setCompareFunction('size',
487 this.compareSize_.bind(this));
488 dataModel.setCompareFunction('type',
489 this.compareType_.bind(this));
490 };
491
492 /**
493 * Render the Name column of the detail table.
494 *
495 * Invoked by cr.ui.Table when a file needs to be rendered.
496 *
497 * @param {Entry} entry The Entry object to render.
498 * @param {string} columnId The id of the column to be rendered.
499 * @param {cr.ui.Table} table The table doing the rendering.
500 * @return {HTMLDivElement} Created element.
501 * @private
502 */
503 FileTable.prototype.renderName_ = function(entry, columnId, table) {
504 var label = this.ownerDocument.createElement('div');
505 label.appendChild(this.renderIconType_(entry, columnId, table));
506 label.entry = entry;
507 label.className = 'detail-name';
508 label.appendChild(filelist.renderFileNameLabel(this.ownerDocument, entry));
509 return label;
510 };
511
512 /**
513 * Render the Selection column of the detail table.
514 *
515 * Invoked by cr.ui.Table when a file needs to be rendered.
516 *
517 * @param {Entry} entry The Entry object to render.
518 * @param {string} columnId The id of the column to be rendered.
519 * @param {cr.ui.Table} table The table doing the rendering.
520 * @return {HTMLDivElement} Created element.
521 * @private
522 */
523 FileTable.prototype.renderSelection_ = function(entry, columnId, table) {
524 var label = this.ownerDocument.createElement('div');
525 label.className = 'selection-label';
526 if (this.selectionModel.multiple) {
527 var checkBox = this.ownerDocument.createElement('input');
528 filelist.decorateSelectionCheckbox(checkBox, entry, this.list);
529 label.appendChild(checkBox);
530 }
531 return label;
532 };
533
534 /**
535 * Render the Size column of the detail table.
536 *
537 * @param {Entry} entry The Entry object to render.
538 * @param {string} columnId The id of the column to be rendered.
539 * @param {cr.ui.Table} table The table doing the rendering.
540 * @return {HTMLDivElement} Created element.
541 * @private
542 */
543 FileTable.prototype.renderSize_ = function(entry, columnId, table) {
544 var div = this.ownerDocument.createElement('div');
545 div.className = 'size';
546 this.updateSize_(
547 div, entry, this.metadataCache_.getCached(entry, 'filesystem'));
548
549 return div;
550 };
551
552 /**
553 * Sets up or updates the size cell.
554 *
555 * @param {HTMLDivElement} div The table cell.
556 * @param {Entry} entry The corresponding entry.
557 * @param {Object} filesystemProps Metadata.
558 * @private
559 */
560 FileTable.prototype.updateSize_ = function(div, entry, filesystemProps) {
561 if (!filesystemProps) {
562 div.textContent = '...';
563 } else if (filesystemProps.size == -1) {
564 div.textContent = '--';
565 } else if (filesystemProps.size == 0 &&
566 FileType.isHosted(entry)) {
567 div.textContent = '--';
568 } else {
569 div.textContent = util.bytesToString(filesystemProps.size);
570 }
571 };
572
573 /**
574 * Render the Type column of the detail table.
575 *
576 * @param {Entry} entry The Entry object to render.
577 * @param {string} columnId The id of the column to be rendered.
578 * @param {cr.ui.Table} table The table doing the rendering.
579 * @return {HTMLDivElement} Created element.
580 * @private
581 */
582 FileTable.prototype.renderType_ = function(entry, columnId, table) {
583 var div = this.ownerDocument.createElement('div');
584 div.className = 'type';
585 div.textContent = FileType.getTypeString(entry);
586 return div;
587 };
588
589 /**
590 * Render the Date column of the detail table.
591 *
592 * @param {Entry} entry The Entry object to render.
593 * @param {string} columnId The id of the column to be rendered.
594 * @param {cr.ui.Table} table The table doing the rendering.
595 * @return {HTMLDivElement} Created element.
596 * @private
597 */
598 FileTable.prototype.renderDate_ = function(entry, columnId, table) {
599 var div = this.ownerDocument.createElement('div');
600 div.className = 'date';
601
602 this.updateDate_(div,
603 this.metadataCache_.getCached(entry, 'filesystem'));
604 return div;
605 };
606
607 /**
608 * Sets up or updates the date cell.
609 *
610 * @param {HTMLDivElement} div The table cell.
611 * @param {Object} filesystemProps Metadata.
612 * @private
613 */
614 FileTable.prototype.updateDate_ = function(div, filesystemProps) {
615 if (!filesystemProps) {
616 div.textContent = '...';
617 return;
618 }
619
620 var modTime = filesystemProps.modificationTime;
621 var today = new Date();
622 today.setHours(0);
623 today.setMinutes(0);
624 today.setSeconds(0);
625 today.setMilliseconds(0);
626
627 /**
628 * Number of milliseconds in a day.
629 */
630 var MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000;
631
632 if (modTime >= today &&
633 modTime < today.getTime() + MILLISECONDS_IN_DAY) {
634 div.textContent = strf('TIME_TODAY', this.timeFormatter_.format(modTime));
635 } else if (modTime >= today - MILLISECONDS_IN_DAY && modTime < today) {
636 div.textContent = strf('TIME_YESTERDAY',
637 this.timeFormatter_.format(modTime));
638 } else {
639 div.textContent =
640 this.dateFormatter_.format(filesystemProps.modificationTime);
641 }
642 };
643
644 /**
645 * Updates the file metadata in the table item.
646 *
647 * @param {Element} item Table item.
648 * @param {Entry} entry File entry.
649 */
650 FileTable.prototype.updateFileMetadata = function(item, entry) {
651 var props = this.metadataCache_.getCached(entry, 'filesystem');
652 this.updateDate_(item.querySelector('.date'), props);
653 this.updateSize_(item.querySelector('.size'), entry, props);
654 };
655
656 /**
657 * Updates list items 'in place' on metadata change.
658 * @param {string} type Type of metadata change.
659 * @param {Object.<sting, Object>} propsMap Map from entry URLs to metadata
660 * properties.
661 */
662 FileTable.prototype.updateListItemsMetadata = function(type, propsMap) {
663 var forEachCell = function(selector, callback) {
664 var cells = this.querySelectorAll(selector);
665 for (var i = 0; i < cells.length; i++) {
666 var cell = cells[i];
667 var listItem = this.list_.getListItemAncestor(cell);
668 var entry = this.dataModel.item(listItem.listIndex);
669 if (entry) {
670 var props = propsMap[entry.toURL()];
671 if (props)
672 callback.call(this, cell, entry, props, listItem);
673 }
674 }
675 }.bind(this);
676 if (type == 'filesystem') {
677 forEachCell('.table-row-cell > .date', function(item, entry, props) {
678 this.updateDate_(item, props);
679 });
680 forEachCell('.table-row-cell > .size', function(item, entry, props) {
681 this.updateSize_(item, entry, props);
682 });
683 } else if (type == 'drive') {
684 // The cell name does not matter as the entire list item is needed.
685 forEachCell('.table-row-cell > .date',
686 function(item, entry, props, listItem) {
687 filelist.updateListItemDriveProps(listItem, props);
688 });
689 }
690 };
691
692 /**
693 * Compare by mtime first, then by name.
694 * @param {Entry} a First entry.
695 * @param {Entry} b Second entry.
696 * @return {number} Compare result.
697 * @private
698 */
699 FileTable.prototype.compareName_ = function(a, b) {
700 return this.collator_.compare(a.name, b.name);
701 };
702
703 /**
704 * Compare by mtime first, then by name.
705 * @param {Entry} a First entry.
706 * @param {Entry} b Second entry.
707 * @return {number} Compare result.
708 * @private
709 */
710 FileTable.prototype.compareMtime_ = function(a, b) {
711 var aCachedFilesystem = this.metadataCache_.getCached(a, 'filesystem');
712 var aTime = aCachedFilesystem ? aCachedFilesystem.modificationTime : 0;
713
714 var bCachedFilesystem = this.metadataCache_.getCached(b, 'filesystem');
715 var bTime = bCachedFilesystem ? bCachedFilesystem.modificationTime : 0;
716
717 if (aTime > bTime)
718 return 1;
719
720 if (aTime < bTime)
721 return -1;
722
723 return this.collator_.compare(a.name, b.name);
724 };
725
726 /**
727 * Compare by size first, then by name.
728 * @param {Entry} a First entry.
729 * @param {Entry} b Second entry.
730 * @return {number} Compare result.
731 * @private
732 */
733 FileTable.prototype.compareSize_ = function(a, b) {
734 var aCachedFilesystem = this.metadataCache_.getCached(a, 'filesystem');
735 var aSize = aCachedFilesystem ? aCachedFilesystem.size : 0;
736
737 var bCachedFilesystem = this.metadataCache_.getCached(b, 'filesystem');
738 var bSize = bCachedFilesystem ? bCachedFilesystem.size : 0;
739
740 if (aSize != bSize) return aSize - bSize;
741 return this.collator_.compare(a.name, b.name);
742 };
743
744 /**
745 * Compare by type first, then by subtype and then by name.
746 * @param {Entry} a First entry.
747 * @param {Entry} b Second entry.
748 * @return {number} Compare result.
749 * @private
750 */
751 FileTable.prototype.compareType_ = function(a, b) {
752 // Directories precede files.
753 if (a.isDirectory != b.isDirectory)
754 return Number(b.isDirectory) - Number(a.isDirectory);
755
756 var aType = FileType.getTypeString(a);
757 var bType = FileType.getTypeString(b);
758
759 var result = this.collator_.compare(aType, bType);
760 if (result != 0)
761 return result;
762
763 return this.collator_.compare(a.name, b.name);
764 };
765
766 /**
767 * Renders table row.
768 * @param {function(Entry, cr.ui.Table)} baseRenderFunction Base renderer.
769 * @param {Entry} entry Corresponding entry.
770 * @return {HTMLLiElement} Created element.
771 * @private
772 */
773 FileTable.prototype.renderTableRow_ = function(baseRenderFunction, entry) {
774 var item = baseRenderFunction(entry, this);
775 filelist.decorateListItem(item, entry, this.metadataCache_);
776 return item;
777 };
778
779 /**
780 * Renders the name column header.
781 * @param {string} name Localized column name.
782 * @return {HTMLLiElement} Created element.
783 * @private
784 */
785 FileTable.prototype.renderNameColumnHeader_ = function(name) {
786 if (!this.selectionModel.multiple)
787 return this.ownerDocument.createTextNode(name);
788
789 var input = this.ownerDocument.createElement('input');
790 input.setAttribute('type', 'checkbox');
791 input.setAttribute('tabindex', -1);
792 input.id = 'select-all-checkbox';
793 input.className = 'common';
794
795 this.updateSelectAllCheckboxState_(input);
796
797 input.addEventListener('click', function(event) {
798 if (input.checked)
799 this.selectionModel.selectAll();
800 else
801 this.selectionModel.unselectAll();
802 event.stopPropagation();
803 }.bind(this));
804
805 var fragment = this.ownerDocument.createDocumentFragment();
806 fragment.appendChild(input);
807 fragment.appendChild(this.ownerDocument.createTextNode(name));
808 return fragment;
809 };
810
811 /**
812 * Renders the selection column header.
813 * @param {string} name Localized column name.
814 * @return {HTMLLiElement} Created element.
815 * @private
816 */
817 FileTable.prototype.renderSelectionColumnHeader_ = function(name) {
818 if (!this.selectionModel.multiple)
819 return this.ownerDocument.createTextNode('');
820
821 var input = this.ownerDocument.createElement('input');
822 input.setAttribute('type', 'checkbox');
823 input.setAttribute('tabindex', -1);
824 input.id = 'select-all-checkbox';
825 input.className = 'common';
826
827 this.updateSelectAllCheckboxState_(input);
828
829 input.addEventListener('click', function(event) {
830 if (input.checked)
831 this.selectionModel.selectAll();
832 else
833 this.selectionModel.unselectAll();
834 event.stopPropagation();
835 }.bind(this));
836
837 var fragment = this.ownerDocument.createDocumentFragment();
838 fragment.appendChild(input);
839 return fragment;
840 };
841
842 /**
843 * Render the type column of the detail table.
844 *
845 * Invoked by cr.ui.Table when a file needs to be rendered.
846 *
847 * @param {Entry} entry The Entry object to render.
848 * @param {string} columnId The id of the column to be rendered.
849 * @param {cr.ui.Table} table The table doing the rendering.
850 * @return {HTMLDivElement} Created element.
851 * @private
852 */
853 FileTable.prototype.renderIconType_ = function(entry, columnId, table) {
854 var icon = this.ownerDocument.createElement('div');
855 icon.className = 'detail-icon';
856 icon.setAttribute('file-type-icon', FileType.getIcon(entry));
857 return icon;
858 };
859
860 /**
861 * Sets the margin height for the transparent preview panel at the bottom.
862 * @param {number} margin Margin to be set in px.
863 */
864 FileTable.prototype.setBottomMarginForPanel = function(margin) {
865 this.list_.style.paddingBottom = margin + 'px';
866 this.scrollBar_.setBottomMarginForPanel(margin);
867 };
868
869 /**
870 * Redraws the UI. Skips multiple consecutive calls.
871 */
872 FileTable.prototype.relayout = function() {
873 this.relayoutAggregation_.run();
874 };
875
876 /**
877 * Redraws the UI immediately.
878 * @private
879 */
880 FileTable.prototype.relayoutImmediately_ = function() {
881 if (this.clientWidth > 0)
882 this.normalizeColumns();
883 this.redraw();
884 cr.dispatchSimpleEvent(this.list, 'relayout');
885 };
886
887 /**
888 * Decorates (and wire up) a checkbox to be used in either a detail or a
889 * thumbnail list item.
890 * @param {HTMLInputElement} input Element to decorate.
891 */
892 filelist.decorateCheckbox = function(input) {
893 var stopEventPropagation = function(event) {
894 if (!event.shiftKey)
895 event.stopPropagation();
896 };
897 input.setAttribute('type', 'checkbox');
898 input.setAttribute('tabindex', -1);
899 input.classList.add('common');
900 input.addEventListener('mousedown', stopEventPropagation);
901 input.addEventListener('mouseup', stopEventPropagation);
902
903 input.addEventListener(
904 'click',
905 /**
906 * @this {HTMLInputElement}
907 */
908 function(event) {
909 // Revert default action and swallow the event
910 // if this is a multiple click or Shift is pressed.
911 if (event.detail > 1 || event.shiftKey) {
912 this.checked = !this.checked;
913 stopEventPropagation(event);
914 }
915 });
916 };
917
918 /**
919 * Decorates selection checkbox.
920 * @param {HTMLInputElement} input Element to decorate.
921 * @param {Entry} entry Corresponding entry.
922 * @param {cr.ui.List} list Owner list.
923 */
924 filelist.decorateSelectionCheckbox = function(input, entry, list) {
925 filelist.decorateCheckbox(input);
926 input.classList.add('file-checkbox');
927 input.addEventListener('click', function(e) {
928 var sm = list.selectionModel;
929 var listIndex = list.getListItemAncestor(this).listIndex;
930 sm.setIndexSelected(listIndex, this.checked);
931 sm.leadIndex = listIndex;
932 if (sm.anchorIndex == -1)
933 sm.anchorIndex = listIndex;
934
935 });
936 // Since we do not want to open the item when tap on checkbox, we need to
937 // stop propagation of TAP event dispatched by checkbox ideally. But all
938 // touch events from touch_handler are dispatched to the list control. So we
939 // have to stop propagation of native touchstart event to prevent list
940 // control from generating TAP event here. The synthetic click event will
941 // select the touched checkbox/item.
942 input.addEventListener('touchstart',
943 function(e) { e.stopPropagation() });
944
945 var index = list.dataModel.indexOf(entry);
946 // Our DOM nodes get discarded as soon as we're scrolled out of view,
947 // so we have to make sure the check state is correct when we're brought
948 // back to life.
949 input.checked = list.selectionModel.getIndexSelected(index);
950 };
951
952 /**
953 * Common item decoration for table's and grid's items.
954 * @param {ListItem} li List item.
955 * @param {Entry} entry The entry.
956 * @param {MetadataCache} metadataCache Cache to retrieve metadada.
957 */
958 filelist.decorateListItem = function(li, entry, metadataCache) {
959 li.classList.add(entry.isDirectory ? 'directory' : 'file');
960 if (FileType.isOnDrive(entry)) {
961 // The metadata may not yet be ready. In that case, the list item will be
962 // updated when the metadata is ready via updateListItemsMetadata.
963 var driveProps = metadataCache.getCached(entry, 'drive');
964 if (driveProps)
965 filelist.updateListItemDriveProps(li, driveProps);
966 }
967
968 // Overriding the default role 'list' to 'listbox' for better
969 // accessibility on ChromeOS.
970 li.setAttribute('role', 'option');
971
972 Object.defineProperty(li, 'selected', {
973 /**
974 * @this {ListItem}
975 * @return {boolean} True if the list item is selected.
976 */
977 get: function() {
978 return this.hasAttribute('selected');
979 },
980
981 /**
982 * @this {ListItem}
983 */
984 set: function(v) {
985 if (v)
986 this.setAttribute('selected');
987 else
988 this.removeAttribute('selected');
989 var checkBox = this.querySelector('input.file-checkbox');
990 if (checkBox)
991 checkBox.checked = !!v;
992 }
993 });
994 };
995
996 /**
997 * Render filename label for grid and list view.
998 * @param {HTMLDocument} doc Owner document.
999 * @param {Entry} entry The Entry object to render.
1000 * @return {HTMLDivElement} The label.
1001 */
1002 filelist.renderFileNameLabel = function(doc, entry) {
1003 // Filename need to be in a '.filename-label' container for correct
1004 // work of inplace renaming.
1005 var box = doc.createElement('div');
1006 box.className = 'filename-label';
1007 var fileName = doc.createElement('span');
1008 fileName.textContent = entry.name;
1009 box.appendChild(fileName);
1010
1011 return box;
1012 };
1013
1014 /**
1015 * Updates grid item or table row for the driveProps.
1016 * @param {cr.ui.ListItem} li List item.
1017 * @param {Object} driveProps Metadata.
1018 */
1019 filelist.updateListItemDriveProps = function(li, driveProps) {
1020 if (li.classList.contains('file')) {
1021 if (driveProps.availableOffline)
1022 li.classList.remove('dim-offline');
1023 else
1024 li.classList.add('dim-offline');
1025 // TODO(mtomasz): Consider adding some vidual indication for files which
1026 // are not cached on LTE. Currently we show them as normal files.
1027 // crbug.com/246611.
1028 }
1029
1030 if (driveProps.customIconUrl) {
1031 var iconDiv = li.querySelector('.detail-icon');
1032 if (!iconDiv)
1033 return;
1034 iconDiv.style.backgroundImage = 'url(' + driveProps.customIconUrl + ')';
1035 }
1036 };
OLDNEW
« no previous file with comments | « chrome/browser/resources/file_manager/js/file_selection.js ('k') | chrome/browser/resources/file_manager/js/file_tasks.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698