• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 */
10var 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 */
19function 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 */
29FileTableColumnModel.RESIZABLE_LENGTH_ = 4;
30
31/**
32 * Inherits from cr.ui.TableColumnModel.
33 */
34FileTableColumnModel.prototype.__proto__ =
35    cr.ui.table.TableColumnModel.prototype;
36
37/**
38 * Minimum width of column.
39 * @const
40 * @type {number}
41 * @private
42 */
43FileTableColumnModel.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 */
53FileTableColumnModel.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 */
78FileTableColumnModel.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 */
103FileTableColumnModel.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 */
113FileTableColumnModel.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 */
122FileTableColumnModel.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 */
163var FileTableSplitter = cr.ui.define('div');
164
165/**
166 * Inherits from cr.ui.TableSplitter.
167 */
168FileTableSplitter.prototype.__proto__ = cr.ui.TableSplitter.prototype;
169
170/**
171 * Handles the drag start event.
172 */
173FileTableSplitter.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 */
182FileTableSplitter.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 */
191FileTableSplitter.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 */
200function FileTable() {
201  throw new Error('Designed to decorate elements');
202}
203
204/**
205 * Inherits from cr.ui.Table.
206 */
207FileTable.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 */
216FileTable.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
244  var columnModel = Object.create(tableColumnModelClass.prototype, {
245    /**
246     * The number of columns.
247     * @type {number}
248     */
249    size: {
250      /**
251       * @this {FileTableColumnModel}
252       * @return {number} Number of columns.
253       */
254      get: function() {
255        return this.totalSize;
256      }
257    },
258
259    /**
260     * The number of columns.
261     * @type {number}
262     */
263    totalSize: {
264      /**
265       * @this {FileTableColumnModel}
266       * @return {number} Number of columns.
267       */
268      get: function() {
269        return columns.length;
270      }
271    },
272
273    /**
274     * Obtains a column by the specified horizontal position.
275     */
276    getHitColumn: {
277      /**
278       * @this {FileTableColumnModel}
279       * @param {number} x Horizontal position.
280       * @return {object} The object that contains column index, column width,
281       *     and hitPosition where the horizontal position is hit in the column.
282       */
283      value: function(x) {
284        for (var i = 0; x >= this.columns_[i].width; i++) {
285          x -= this.columns_[i].width;
286        }
287        if (i >= this.columns_.length)
288          return null;
289        return {index: i, hitPosition: x, width: this.columns_[i].width};
290      }
291    }
292  });
293
294  tableColumnModelClass.call(columnModel, columns);
295  self.columnModel = columnModel;
296  self.setDateTimeFormat(true);
297  self.setRenderFunction(self.renderTableRow_.bind(self,
298      self.getRenderFunction()));
299
300  self.scrollBar_ = MainPanelScrollBar();
301  self.scrollBar_.initialize(self, self.list);
302  // Keep focus on the file list when clicking on the header.
303  self.header.addEventListener('mousedown', function(e) {
304    self.list.focus();
305    e.preventDefault();
306  });
307
308  self.relayoutRateLimiter_ =
309      new AsyncUtil.RateLimiter(self.relayoutImmediately_.bind(self));
310
311  // Override header#redraw to use FileTableSplitter.
312  self.header_.redraw = function() {
313    this.__proto__.redraw.call(this);
314    // Extend table splitters
315    var splitters = this.querySelectorAll('.table-header-splitter');
316    for (var i = 0; i < splitters.length; i++) {
317      if (splitters[i] instanceof FileTableSplitter)
318        continue;
319      FileTableSplitter.decorate(splitters[i]);
320    }
321  };
322
323  // Save the last selection. This is used by shouldStartDragSelection.
324  self.list.addEventListener('mousedown', function(e) {
325    this.lastSelection_ = this.selectionModel.selectedIndexes;
326  }.bind(self), true);
327  self.list.shouldStartDragSelection =
328      self.shouldStartDragSelection_.bind(self);
329
330  /**
331   * Obtains the index list of elements that are hit by the point or the
332   * rectangle.
333   *
334   * @param {number} x X coordinate value.
335   * @param {number} y Y coordinate value.
336   * @param {=number} opt_width Width of the coordinate.
337   * @param {=number} opt_height Height of the coordinate.
338   * @return {Array.<number>} Index list of hit elements.
339   */
340  self.list.getHitElements = function(x, y, opt_width, opt_height) {
341    var currentSelection = [];
342    var bottom = y + (opt_height || 0);
343    for (var i = 0; i < this.selectionModel_.length; i++) {
344      var itemMetrics = this.getHeightsForIndex_(i);
345      if (itemMetrics.top < bottom && itemMetrics.top + itemMetrics.height >= y)
346        currentSelection.push(i);
347    }
348    return currentSelection;
349  };
350};
351
352/**
353 * Sets date and time format.
354 * @param {boolean} use12hourClock True if 12 hours clock, False if 24 hours.
355 */
356FileTable.prototype.setDateTimeFormat = function(use12hourClock) {
357  this.timeFormatter_ = Intl.DateTimeFormat(
358        [] /* default locale */,
359        {hour: 'numeric', minute: 'numeric',
360         hour12: use12hourClock});
361  this.dateFormatter_ = Intl.DateTimeFormat(
362        [] /* default locale */,
363        {year: 'numeric', month: 'short', day: 'numeric',
364         hour: 'numeric', minute: 'numeric',
365         hour12: use12hourClock});
366};
367
368/**
369 * Obtains if the drag selection should be start or not by referring the mouse
370 * event.
371 * @param {MouseEvent} event Drag start event.
372 * @return {boolean} True if the mouse is hit to the background of the list.
373 * @private
374 */
375FileTable.prototype.shouldStartDragSelection_ = function(event) {
376  // If the shift key is pressed, it should starts drag selection.
377  if (event.shiftKey)
378    return true;
379
380  // If the position values are negative, it points the out of list.
381  // It should start the drag selection.
382  var pos = DragSelector.getScrolledPosition(this.list, event);
383  if (!pos)
384    return false;
385  if (pos.x < 0 || pos.y < 0)
386    return true;
387
388  // If the item index is out of range, it should start the drag selection.
389  var itemHeight = this.list.measureItem().height;
390  // Faster alternative to Math.floor for non-negative numbers.
391  var itemIndex = ~~(pos.y / itemHeight);
392  if (itemIndex >= this.list.dataModel.length)
393    return true;
394
395  // If the pointed item is already selected, it should not start the drag
396  // selection.
397  if (this.lastSelection_.indexOf(itemIndex) !== -1)
398    return false;
399
400  // If the horizontal value is not hit to column, it should start the drag
401  // selection.
402  var hitColumn = this.columnModel.getHitColumn(pos.x);
403  if (!hitColumn)
404    return true;
405
406  // Check if the point is on the column contents or not.
407  var item = this.list.getListItemByIndex(itemIndex);
408  switch (this.columnModel.columns_[hitColumn.index].id) {
409    case 'name':
410      var spanElement = item.querySelector('.filename-label span');
411      var spanRect = spanElement.getBoundingClientRect();
412      // The this.list.cachedBounds_ object is set by
413      // DragSelector.getScrolledPosition.
414      if (!this.list.cachedBounds)
415        return true;
416      var textRight =
417          spanRect.left - this.list.cachedBounds.left + spanRect.width;
418      return textRight <= hitColumn.hitPosition;
419    default:
420      return true;
421  }
422};
423
424/**
425 * Prepares the data model to be sorted by columns.
426 * @param {cr.ui.ArrayDataModel} dataModel Data model to prepare.
427 */
428FileTable.prototype.setupCompareFunctions = function(dataModel) {
429  dataModel.setCompareFunction('name',
430                               this.compareName_.bind(this));
431  dataModel.setCompareFunction('modificationTime',
432                               this.compareMtime_.bind(this));
433  dataModel.setCompareFunction('size',
434                               this.compareSize_.bind(this));
435  dataModel.setCompareFunction('type',
436                               this.compareType_.bind(this));
437};
438
439/**
440 * Render the Name column of the detail table.
441 *
442 * Invoked by cr.ui.Table when a file needs to be rendered.
443 *
444 * @param {Entry} entry The Entry object to render.
445 * @param {string} columnId The id of the column to be rendered.
446 * @param {cr.ui.Table} table The table doing the rendering.
447 * @return {HTMLDivElement} Created element.
448 * @private
449 */
450FileTable.prototype.renderName_ = function(entry, columnId, table) {
451  var label = this.ownerDocument.createElement('div');
452  label.appendChild(this.renderIconType_(entry, columnId, table));
453  label.entry = entry;
454  label.className = 'detail-name';
455  label.appendChild(filelist.renderFileNameLabel(this.ownerDocument, entry));
456  return label;
457};
458
459/**
460 * Render the Size column of the detail table.
461 *
462 * @param {Entry} entry The Entry object to render.
463 * @param {string} columnId The id of the column to be rendered.
464 * @param {cr.ui.Table} table The table doing the rendering.
465 * @return {HTMLDivElement} Created element.
466 * @private
467 */
468FileTable.prototype.renderSize_ = function(entry, columnId, table) {
469  var div = this.ownerDocument.createElement('div');
470  div.className = 'size';
471  this.updateSize_(
472      div, entry, this.metadataCache_.getCached(entry, 'filesystem'));
473
474  return div;
475};
476
477/**
478 * Sets up or updates the size cell.
479 *
480 * @param {HTMLDivElement} div The table cell.
481 * @param {Entry} entry The corresponding entry.
482 * @param {Object} filesystemProps Metadata.
483 * @private
484 */
485FileTable.prototype.updateSize_ = function(div, entry, filesystemProps) {
486  if (!filesystemProps) {
487    div.textContent = '...';
488  } else if (filesystemProps.size === -1) {
489    div.textContent = '--';
490  } else if (filesystemProps.size === 0 &&
491             FileType.isHosted(entry)) {
492    div.textContent = '--';
493  } else {
494    div.textContent = util.bytesToString(filesystemProps.size);
495  }
496};
497
498/**
499 * Render the Type column of the detail table.
500 *
501 * @param {Entry} entry The Entry object to render.
502 * @param {string} columnId The id of the column to be rendered.
503 * @param {cr.ui.Table} table The table doing the rendering.
504 * @return {HTMLDivElement} Created element.
505 * @private
506 */
507FileTable.prototype.renderType_ = function(entry, columnId, table) {
508  var div = this.ownerDocument.createElement('div');
509  div.className = 'type';
510  div.textContent = FileType.typeToString(FileType.getType(entry));
511  return div;
512};
513
514/**
515 * Render the Date column of the detail table.
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 */
523FileTable.prototype.renderDate_ = function(entry, columnId, table) {
524  var div = this.ownerDocument.createElement('div');
525  div.className = 'date';
526
527  this.updateDate_(div,
528      this.metadataCache_.getCached(entry, 'filesystem'));
529  return div;
530};
531
532/**
533 * Sets up or updates the date cell.
534 *
535 * @param {HTMLDivElement} div The table cell.
536 * @param {Object} filesystemProps Metadata.
537 * @private
538 */
539FileTable.prototype.updateDate_ = function(div, filesystemProps) {
540  if (!filesystemProps) {
541    div.textContent = '...';
542    return;
543  }
544
545  var modTime = filesystemProps.modificationTime;
546  var today = new Date();
547  today.setHours(0);
548  today.setMinutes(0);
549  today.setSeconds(0);
550  today.setMilliseconds(0);
551
552  /**
553   * Number of milliseconds in a day.
554   */
555  var MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000;
556
557  if (isNaN(modTime.getTime())) {
558    // In case of 'Invalid Date'.
559    div.textContent = '--';
560  } else if (modTime >= today &&
561      modTime < today.getTime() + MILLISECONDS_IN_DAY) {
562    div.textContent = strf('TIME_TODAY', this.timeFormatter_.format(modTime));
563  } else if (modTime >= today - MILLISECONDS_IN_DAY && modTime < today) {
564    div.textContent = strf('TIME_YESTERDAY',
565                           this.timeFormatter_.format(modTime));
566  } else {
567    div.textContent = this.dateFormatter_.format(modTime);
568  }
569};
570
571/**
572 * Updates the file metadata in the table item.
573 *
574 * @param {Element} item Table item.
575 * @param {Entry} entry File entry.
576 */
577FileTable.prototype.updateFileMetadata = function(item, entry) {
578  var props = this.metadataCache_.getCached(entry, 'filesystem');
579  this.updateDate_(item.querySelector('.date'), props);
580  this.updateSize_(item.querySelector('.size'), entry, props);
581};
582
583/**
584 * Updates list items 'in place' on metadata change.
585 * @param {string} type Type of metadata change.
586 * @param {Object.<string, Object>} propsMap Map from entry URLs to metadata
587 *     properties.
588 */
589FileTable.prototype.updateListItemsMetadata = function(type, propsMap) {
590  var forEachCell = function(selector, callback) {
591    var cells = this.querySelectorAll(selector);
592    for (var i = 0; i < cells.length; i++) {
593      var cell = cells[i];
594      var listItem = this.list_.getListItemAncestor(cell);
595      var entry = this.dataModel.item(listItem.listIndex);
596      if (entry) {
597        var props = propsMap[entry.toURL()];
598        if (props)
599          callback.call(this, cell, entry, props, listItem);
600      }
601    }
602  }.bind(this);
603  if (type === 'filesystem') {
604    forEachCell('.table-row-cell > .date', function(item, entry, props) {
605      this.updateDate_(item, props);
606    });
607    forEachCell('.table-row-cell > .size', function(item, entry, props) {
608      this.updateSize_(item, entry, props);
609    });
610  } else if (type === 'drive') {
611    // The cell name does not matter as the entire list item is needed.
612    forEachCell('.table-row-cell > .date',
613                function(item, entry, props, listItem) {
614      filelist.updateListItemDriveProps(listItem, props);
615    });
616  }
617};
618
619/**
620 * Compare by mtime first, then by name.
621 * @param {Entry} a First entry.
622 * @param {Entry} b Second entry.
623 * @return {number} Compare result.
624 * @private
625 */
626FileTable.prototype.compareName_ = function(a, b) {
627  return this.collator_.compare(a.name, b.name);
628};
629
630/**
631 * Compare by mtime first, then by name.
632 * @param {Entry} a First entry.
633 * @param {Entry} b Second entry.
634 * @return {number} Compare result.
635 * @private
636 */
637FileTable.prototype.compareMtime_ = function(a, b) {
638  var aCachedFilesystem = this.metadataCache_.getCached(a, 'filesystem');
639  var aTime = aCachedFilesystem ? aCachedFilesystem.modificationTime : 0;
640
641  var bCachedFilesystem = this.metadataCache_.getCached(b, 'filesystem');
642  var bTime = bCachedFilesystem ? bCachedFilesystem.modificationTime : 0;
643
644  if (aTime > bTime)
645    return 1;
646
647  if (aTime < bTime)
648    return -1;
649
650  return this.collator_.compare(a.name, b.name);
651};
652
653/**
654 * Compare by size first, then by name.
655 * @param {Entry} a First entry.
656 * @param {Entry} b Second entry.
657 * @return {number} Compare result.
658 * @private
659 */
660FileTable.prototype.compareSize_ = function(a, b) {
661  var aCachedFilesystem = this.metadataCache_.getCached(a, 'filesystem');
662  var aSize = aCachedFilesystem ? aCachedFilesystem.size : 0;
663
664  var bCachedFilesystem = this.metadataCache_.getCached(b, 'filesystem');
665  var bSize = bCachedFilesystem ? bCachedFilesystem.size : 0;
666
667  if (aSize !== bSize) return aSize - bSize;
668    return this.collator_.compare(a.name, b.name);
669};
670
671/**
672 * Compare by type first, then by subtype and then by name.
673 * @param {Entry} a First entry.
674 * @param {Entry} b Second entry.
675 * @return {number} Compare result.
676 * @private
677 */
678FileTable.prototype.compareType_ = function(a, b) {
679  // Directories precede files.
680  if (a.isDirectory !== b.isDirectory)
681    return Number(b.isDirectory) - Number(a.isDirectory);
682
683  var aType = FileType.typeToString(FileType.getType(a));
684  var bType = FileType.typeToString(FileType.getType(b));
685
686  var result = this.collator_.compare(aType, bType);
687  if (result !== 0)
688    return result;
689
690  return this.collator_.compare(a.name, b.name);
691};
692
693/**
694 * Renders table row.
695 * @param {function(Entry, cr.ui.Table)} baseRenderFunction Base renderer.
696 * @param {Entry} entry Corresponding entry.
697 * @return {HTMLLiElement} Created element.
698 * @private
699 */
700FileTable.prototype.renderTableRow_ = function(baseRenderFunction, entry) {
701  var item = baseRenderFunction(entry, this);
702  filelist.decorateListItem(item, entry, this.metadataCache_);
703  return item;
704};
705
706/**
707 * Render the type column of the detail table.
708 *
709 * Invoked by cr.ui.Table when a file needs to be rendered.
710 *
711 * @param {Entry} entry The Entry object to render.
712 * @param {string} columnId The id of the column to be rendered.
713 * @param {cr.ui.Table} table The table doing the rendering.
714 * @return {HTMLDivElement} Created element.
715 * @private
716 */
717FileTable.prototype.renderIconType_ = function(entry, columnId, table) {
718  var icon = this.ownerDocument.createElement('div');
719  icon.className = 'detail-icon';
720  icon.setAttribute('file-type-icon', FileType.getIcon(entry));
721  return icon;
722};
723
724/**
725 * Sets the margin height for the transparent preview panel at the bottom.
726 * @param {number} margin Margin to be set in px.
727 */
728FileTable.prototype.setBottomMarginForPanel = function(margin) {
729  this.list_.style.paddingBottom = margin + 'px';
730  this.scrollBar_.setBottomMarginForPanel(margin);
731};
732
733/**
734 * Redraws the UI. Skips multiple consecutive calls.
735 */
736FileTable.prototype.relayout = function() {
737  this.relayoutRateLimiter_.run();
738};
739
740/**
741 * Redraws the UI immediately.
742 * @private
743 */
744FileTable.prototype.relayoutImmediately_ = function() {
745  if (this.clientWidth > 0)
746    this.normalizeColumns();
747  this.redraw();
748  cr.dispatchSimpleEvent(this.list, 'relayout');
749};
750
751/**
752 * Common item decoration for table's and grid's items.
753 * @param {ListItem} li List item.
754 * @param {Entry} entry The entry.
755 * @param {MetadataCache} metadataCache Cache to retrieve metadada.
756 */
757filelist.decorateListItem = function(li, entry, metadataCache) {
758  li.classList.add(entry.isDirectory ? 'directory' : 'file');
759  // The metadata may not yet be ready. In that case, the list item will be
760  // updated when the metadata is ready via updateListItemsMetadata. For files
761  // not on Drive, driveProps is not available.
762  var driveProps = metadataCache.getCached(entry, 'drive');
763  if (driveProps)
764    filelist.updateListItemDriveProps(li, driveProps);
765
766  // Overriding the default role 'list' to 'listbox' for better
767  // accessibility on ChromeOS.
768  li.setAttribute('role', 'option');
769
770  Object.defineProperty(li, 'selected', {
771    /**
772     * @this {ListItem}
773     * @return {boolean} True if the list item is selected.
774     */
775    get: function() {
776      return this.hasAttribute('selected');
777    },
778
779    /**
780     * @this {ListItem}
781     */
782    set: function(v) {
783      if (v)
784        this.setAttribute('selected', '');
785      else
786        this.removeAttribute('selected');
787    }
788  });
789};
790
791/**
792 * Render filename label for grid and list view.
793 * @param {HTMLDocument} doc Owner document.
794 * @param {Entry} entry The Entry object to render.
795 * @return {HTMLDivElement} The label.
796 */
797filelist.renderFileNameLabel = function(doc, entry) {
798  // Filename need to be in a '.filename-label' container for correct
799  // work of inplace renaming.
800  var box = doc.createElement('div');
801  box.className = 'filename-label';
802  var fileName = doc.createElement('span');
803  fileName.className = 'entry-name';
804  fileName.textContent = entry.name;
805  box.appendChild(fileName);
806
807  return box;
808};
809
810/**
811 * Updates grid item or table row for the driveProps.
812 * @param {cr.ui.ListItem} li List item.
813 * @param {Object} driveProps Metadata.
814 */
815filelist.updateListItemDriveProps = function(li, driveProps) {
816  if (li.classList.contains('file')) {
817    if (driveProps.availableOffline)
818      li.classList.remove('dim-offline');
819    else
820      li.classList.add('dim-offline');
821    // TODO(mtomasz): Consider adding some vidual indication for files which
822    // are not cached on LTE. Currently we show them as normal files.
823    // crbug.com/246611.
824  }
825
826  var iconDiv = li.querySelector('.detail-icon');
827  if (!iconDiv)
828    return;
829
830  if (driveProps.customIconUrl)
831    iconDiv.style.backgroundImage = 'url(' + driveProps.customIconUrl + ')';
832
833  if (li.classList.contains('directory'))
834    iconDiv.classList.toggle('shared', driveProps.shared);
835};
836