file_table.js revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
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 {VolumeManager} volumeManager To retrieve volume info. 214 * @param {boolean} fullPage True if it's full page File Manager, 215 * False if a file open/save dialog. 216 */ 217FileTable.decorate = function(self, metadataCache, volumeManager, fullPage) { 218 cr.ui.Table.decorate(self); 219 self.__proto__ = FileTable.prototype; 220 self.metadataCache_ = metadataCache; 221 self.volumeManager_ = volumeManager; 222 223 var columns = [ 224 new cr.ui.table.TableColumn('name', str('NAME_COLUMN_LABEL'), 225 fullPage ? 386 : 324), 226 new cr.ui.table.TableColumn('size', str('SIZE_COLUMN_LABEL'), 227 110, true), 228 new cr.ui.table.TableColumn('type', str('TYPE_COLUMN_LABEL'), 229 fullPage ? 110 : 110), 230 new cr.ui.table.TableColumn('modificationTime', 231 str('DATE_COLUMN_LABEL'), 232 fullPage ? 150 : 210) 233 ]; 234 235 columns[0].renderFunction = self.renderName_.bind(self); 236 columns[1].renderFunction = self.renderSize_.bind(self); 237 columns[1].defaultOrder = 'desc'; 238 columns[2].renderFunction = self.renderType_.bind(self); 239 columns[3].renderFunction = self.renderDate_.bind(self); 240 columns[3].defaultOrder = 'desc'; 241 242 var tableColumnModelClass; 243 tableColumnModelClass = FileTableColumnModel; 244 245 var columnModel = Object.create(tableColumnModelClass.prototype, { 246 /** 247 * The number of columns. 248 * @type {number} 249 */ 250 size: { 251 /** 252 * @this {FileTableColumnModel} 253 * @return {number} Number of columns. 254 */ 255 get: function() { 256 return this.totalSize; 257 } 258 }, 259 260 /** 261 * The number of columns. 262 * @type {number} 263 */ 264 totalSize: { 265 /** 266 * @this {FileTableColumnModel} 267 * @return {number} Number of columns. 268 */ 269 get: function() { 270 return columns.length; 271 } 272 }, 273 274 /** 275 * Obtains a column by the specified horizontal position. 276 */ 277 getHitColumn: { 278 /** 279 * @this {FileTableColumnModel} 280 * @param {number} x Horizontal position. 281 * @return {object} The object that contains column index, column width, 282 * and hitPosition where the horizontal position is hit in the column. 283 */ 284 value: function(x) { 285 for (var i = 0; x >= this.columns_[i].width; i++) { 286 x -= this.columns_[i].width; 287 } 288 if (i >= this.columns_.length) 289 return null; 290 return {index: i, hitPosition: x, width: this.columns_[i].width}; 291 } 292 } 293 }); 294 295 tableColumnModelClass.call(columnModel, columns); 296 self.columnModel = columnModel; 297 self.setDateTimeFormat(true); 298 self.setRenderFunction(self.renderTableRow_.bind(self, 299 self.getRenderFunction())); 300 301 self.scrollBar_ = new MainPanelScrollBar(); 302 self.scrollBar_.initialize(self, self.list); 303 // Keep focus on the file list when clicking on the header. 304 self.header.addEventListener('mousedown', function(e) { 305 self.list.focus(); 306 e.preventDefault(); 307 }); 308 309 self.relayoutRateLimiter_ = 310 new AsyncUtil.RateLimiter(self.relayoutImmediately_.bind(self)); 311 312 // Override header#redraw to use FileTableSplitter. 313 self.header_.redraw = function() { 314 this.__proto__.redraw.call(this); 315 // Extend table splitters 316 var splitters = this.querySelectorAll('.table-header-splitter'); 317 for (var i = 0; i < splitters.length; i++) { 318 if (splitters[i] instanceof FileTableSplitter) 319 continue; 320 FileTableSplitter.decorate(splitters[i]); 321 } 322 }; 323 324 // Save the last selection. This is used by shouldStartDragSelection. 325 self.list.addEventListener('mousedown', function(e) { 326 this.lastSelection_ = this.selectionModel.selectedIndexes; 327 }.bind(self), true); 328 self.list.shouldStartDragSelection = 329 self.shouldStartDragSelection_.bind(self); 330 331 /** 332 * Obtains the index list of elements that are hit by the point or the 333 * rectangle. 334 * 335 * @param {number} x X coordinate value. 336 * @param {number} y Y coordinate value. 337 * @param {number=} opt_width Width of the coordinate. 338 * @param {number=} opt_height Height of the coordinate. 339 * @return {Array.<number>} Index list of hit elements. 340 */ 341 self.list.getHitElements = function(x, y, opt_width, opt_height) { 342 var currentSelection = []; 343 var bottom = y + (opt_height || 0); 344 for (var i = 0; i < this.selectionModel_.length; i++) { 345 var itemMetrics = this.getHeightsForIndex_(i); 346 if (itemMetrics.top < bottom && itemMetrics.top + itemMetrics.height >= y) 347 currentSelection.push(i); 348 } 349 return currentSelection; 350 }; 351}; 352 353/** 354 * Sets date and time format. 355 * @param {boolean} use12hourClock True if 12 hours clock, False if 24 hours. 356 */ 357FileTable.prototype.setDateTimeFormat = function(use12hourClock) { 358 this.timeFormatter_ = Intl.DateTimeFormat( 359 [] /* default locale */, 360 {hour: 'numeric', minute: 'numeric', hour12: use12hourClock}); 361 this.dateFormatter_ = Intl.DateTimeFormat( 362 [] /* default locale */, 363 { 364 year: 'numeric', month: 'short', day: 'numeric', 365 hour: 'numeric', minute: 'numeric', hour12: use12hourClock 366 }); 367}; 368 369/** 370 * Obtains if the drag selection should be start or not by referring the mouse 371 * event. 372 * @param {MouseEvent} event Drag start event. 373 * @return {boolean} True if the mouse is hit to the background of the list. 374 * @private 375 */ 376FileTable.prototype.shouldStartDragSelection_ = function(event) { 377 // If the shift key is pressed, it should starts drag selection. 378 if (event.shiftKey) 379 return true; 380 381 // If the position values are negative, it points the out of list. 382 // It should start the drag selection. 383 var pos = DragSelector.getScrolledPosition(this.list, event); 384 if (!pos) 385 return false; 386 if (pos.x < 0 || pos.y < 0) 387 return true; 388 389 // If the item index is out of range, it should start the drag selection. 390 var itemHeight = this.list.measureItem().height; 391 // Faster alternative to Math.floor for non-negative numbers. 392 var itemIndex = ~~(pos.y / itemHeight); 393 if (itemIndex >= this.list.dataModel.length) 394 return true; 395 396 // If the pointed item is already selected, it should not start the drag 397 // selection. 398 if (this.lastSelection_ && this.lastSelection_.indexOf(itemIndex) !== -1) 399 return false; 400 401 // If the horizontal value is not hit to column, it should start the drag 402 // selection. 403 var hitColumn = this.columnModel.getHitColumn(pos.x); 404 if (!hitColumn) 405 return true; 406 407 // Check if the point is on the column contents or not. 408 var item = this.list.getListItemByIndex(itemIndex); 409 switch (this.columnModel.columns_[hitColumn.index].id) { 410 case 'name': 411 var spanElement = item.querySelector('.filename-label span'); 412 var spanRect = spanElement.getBoundingClientRect(); 413 // The this.list.cachedBounds_ object is set by 414 // DragSelector.getScrolledPosition. 415 if (!this.list.cachedBounds) 416 return true; 417 var textRight = 418 spanRect.left - this.list.cachedBounds.left + spanRect.width; 419 return textRight <= hitColumn.hitPosition; 420 default: 421 return true; 422 } 423}; 424 425/** 426 * Render the Name column of the detail table. 427 * 428 * Invoked by cr.ui.Table when a file needs to be rendered. 429 * 430 * @param {Entry} entry The Entry object to render. 431 * @param {string} columnId The id of the column to be rendered. 432 * @param {cr.ui.Table} table The table doing the rendering. 433 * @return {HTMLDivElement} Created element. 434 * @private 435 */ 436FileTable.prototype.renderName_ = function(entry, columnId, table) { 437 var label = this.ownerDocument.createElement('div'); 438 label.appendChild(this.renderIconType_(entry, columnId, table)); 439 label.entry = entry; 440 label.className = 'detail-name'; 441 label.appendChild(filelist.renderFileNameLabel(this.ownerDocument, entry)); 442 return label; 443}; 444 445/** 446 * Render the Size column of the detail table. 447 * 448 * @param {Entry} entry The Entry object to render. 449 * @param {string} columnId The id of the column to be rendered. 450 * @param {cr.ui.Table} table The table doing the rendering. 451 * @return {HTMLDivElement} Created element. 452 * @private 453 */ 454FileTable.prototype.renderSize_ = function(entry, columnId, table) { 455 var div = this.ownerDocument.createElement('div'); 456 div.className = 'size'; 457 this.updateSize_(div, entry); 458 459 return div; 460}; 461 462/** 463 * Sets up or updates the size cell. 464 * 465 * @param {HTMLDivElement} div The table cell. 466 * @param {Entry} entry The corresponding entry. 467 * @private 468 */ 469FileTable.prototype.updateSize_ = function(div, entry) { 470 var filesystemProps = this.metadataCache_.getCached(entry, 'filesystem'); 471 if (!filesystemProps) { 472 div.textContent = '...'; 473 return; 474 } else if (filesystemProps.size === -1) { 475 div.textContent = '--'; 476 return; 477 } else if (filesystemProps.size === 0 && 478 FileType.isHosted(entry)) { 479 var externalProps = this.metadataCache_.getCached(entry, 'external'); 480 if (!externalProps) { 481 var locationInfo = this.volumeManager_.getLocationInfo(entry); 482 if (locationInfo && locationInfo.isDriveBased) { 483 // Should not reach here, since we already have size metadata. 484 // Putting dots just in case. 485 div.textContent = '...'; 486 return; 487 } 488 } else if (externalProps.hosted) { 489 div.textContent = '--'; 490 return; 491 } 492 } 493 494 div.textContent = util.bytesToString(filesystemProps.size); 495}; 496 497/** 498 * Render the Type column of the detail table. 499 * 500 * @param {Entry} entry The Entry object to render. 501 * @param {string} columnId The id of the column to be rendered. 502 * @param {cr.ui.Table} table The table doing the rendering. 503 * @return {HTMLDivElement} Created element. 504 * @private 505 */ 506FileTable.prototype.renderType_ = function(entry, columnId, table) { 507 var div = this.ownerDocument.createElement('div'); 508 div.className = 'type'; 509 div.textContent = FileType.typeToString(FileType.getType(entry)); 510 return div; 511}; 512 513/** 514 * Render the Date column of the detail table. 515 * 516 * @param {Entry} entry The Entry object to render. 517 * @param {string} columnId The id of the column to be rendered. 518 * @param {cr.ui.Table} table The table doing the rendering. 519 * @return {HTMLDivElement} Created element. 520 * @private 521 */ 522FileTable.prototype.renderDate_ = function(entry, columnId, table) { 523 var div = this.ownerDocument.createElement('div'); 524 div.className = 'date'; 525 526 this.updateDate_(div, entry); 527 return div; 528}; 529 530/** 531 * Sets up or updates the date cell. 532 * 533 * @param {HTMLDivElement} div The table cell. 534 * @param {Entry} entry Entry of file to update. 535 * @private 536 */ 537FileTable.prototype.updateDate_ = function(div, entry) { 538 var filesystemProps = this.metadataCache_.getCached(entry, 'filesystem'); 539 if (!filesystemProps) { 540 div.textContent = '...'; 541 return; 542 } 543 544 var modTime = filesystemProps.modificationTime; 545 var today = new Date(); 546 today.setHours(0); 547 today.setMinutes(0); 548 today.setSeconds(0); 549 today.setMilliseconds(0); 550 551 /** 552 * Number of milliseconds in a day. 553 */ 554 var MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000; 555 556 if (isNaN(modTime.getTime())) { 557 // In case of 'Invalid Date'. 558 div.textContent = '--'; 559 } else if (modTime >= today && 560 modTime < today.getTime() + MILLISECONDS_IN_DAY) { 561 div.textContent = strf('TIME_TODAY', this.timeFormatter_.format(modTime)); 562 } else if (modTime >= today - MILLISECONDS_IN_DAY && modTime < today) { 563 div.textContent = strf('TIME_YESTERDAY', 564 this.timeFormatter_.format(modTime)); 565 } else { 566 div.textContent = this.dateFormatter_.format(modTime); 567 } 568}; 569 570/** 571 * Updates the file metadata in the table item. 572 * 573 * @param {Element} item Table item. 574 * @param {Entry} entry File entry. 575 */ 576FileTable.prototype.updateFileMetadata = function(item, entry) { 577 this.updateDate_(item.querySelector('.date'), entry); 578 this.updateSize_(item.querySelector('.size'), entry); 579}; 580 581/** 582 * Updates list items 'in place' on metadata change. 583 * @param {string} type Type of metadata change. 584 * @param {Array.<Entry>} entries Entries to update. 585 */ 586FileTable.prototype.updateListItemsMetadata = function(type, entries) { 587 var urls = util.entriesToURLs(entries); 588 var forEachCell = function(selector, callback) { 589 var cells = this.querySelectorAll(selector); 590 for (var i = 0; i < cells.length; i++) { 591 var cell = cells[i]; 592 var listItem = this.list_.getListItemAncestor(cell); 593 var entry = this.dataModel.item(listItem.listIndex); 594 if (entry && urls.indexOf(entry.toURL()) !== -1) 595 callback.call(this, cell, entry, listItem); 596 } 597 }.bind(this); 598 if (type === 'filesystem') { 599 forEachCell('.table-row-cell > .date', function(item, entry, unused) { 600 this.updateDate_(item, entry); 601 }); 602 forEachCell('.table-row-cell > .size', function(item, entry, unused) { 603 this.updateSize_(item, entry); 604 }); 605 } else if (type === 'external') { 606 // The cell name does not matter as the entire list item is needed. 607 forEachCell('.table-row-cell > .date', function(item, entry, listItem) { 608 var props = this.metadataCache_.getCached(entry, 'external'); 609 filelist.updateListItemExternalProps(listItem, props); 610 }); 611 } 612}; 613 614/** 615 * Renders table row. 616 * @param {function(Entry, cr.ui.Table)} baseRenderFunction Base renderer. 617 * @param {Entry} entry Corresponding entry. 618 * @return {HTMLLiElement} Created element. 619 * @private 620 */ 621FileTable.prototype.renderTableRow_ = function(baseRenderFunction, entry) { 622 var item = baseRenderFunction(entry, this); 623 filelist.decorateListItem(item, entry, this.metadataCache_); 624 return item; 625}; 626 627/** 628 * Render the type column of the detail table. 629 * 630 * Invoked by cr.ui.Table when a file needs to be rendered. 631 * 632 * @param {Entry} entry The Entry object to render. 633 * @param {string} columnId The id of the column to be rendered. 634 * @param {cr.ui.Table} table The table doing the rendering. 635 * @return {HTMLDivElement} Created element. 636 * @private 637 */ 638FileTable.prototype.renderIconType_ = function(entry, columnId, table) { 639 var icon = this.ownerDocument.createElement('div'); 640 icon.className = 'detail-icon'; 641 icon.setAttribute('file-type-icon', FileType.getIcon(entry)); 642 return icon; 643}; 644 645/** 646 * Sets the margin height for the transparent preview panel at the bottom. 647 * @param {number} margin Margin to be set in px. 648 */ 649FileTable.prototype.setBottomMarginForPanel = function(margin) { 650 this.list_.style.paddingBottom = margin + 'px'; 651 this.scrollBar_.setBottomMarginForPanel(margin); 652}; 653 654/** 655 * Redraws the UI. Skips multiple consecutive calls. 656 */ 657FileTable.prototype.relayout = function() { 658 this.relayoutRateLimiter_.run(); 659}; 660 661/** 662 * Redraws the UI immediately. 663 * @private 664 */ 665FileTable.prototype.relayoutImmediately_ = function() { 666 if (this.clientWidth > 0) 667 this.normalizeColumns(); 668 this.redraw(); 669 cr.dispatchSimpleEvent(this.list, 'relayout'); 670}; 671 672/** 673 * Common item decoration for table's and grid's items. 674 * @param {ListItem} li List item. 675 * @param {Entry} entry The entry. 676 * @param {MetadataCache} metadataCache Cache to retrieve metadada. 677 */ 678filelist.decorateListItem = function(li, entry, metadataCache) { 679 li.classList.add(entry.isDirectory ? 'directory' : 'file'); 680 // The metadata may not yet be ready. In that case, the list item will be 681 // updated when the metadata is ready via updateListItemsMetadata. For files 682 // not on an external backend, externalProps is not available. 683 var externalProps = metadataCache.getCached(entry, 'external'); 684 if (externalProps) 685 filelist.updateListItemExternalProps(li, externalProps); 686 687 // Overriding the default role 'list' to 'listbox' for better 688 // accessibility on ChromeOS. 689 li.setAttribute('role', 'option'); 690 691 Object.defineProperty(li, 'selected', { 692 /** 693 * @this {ListItem} 694 * @return {boolean} True if the list item is selected. 695 */ 696 get: function() { 697 return this.hasAttribute('selected'); 698 }, 699 700 /** 701 * @this {ListItem} 702 */ 703 set: function(v) { 704 if (v) 705 this.setAttribute('selected', ''); 706 else 707 this.removeAttribute('selected'); 708 } 709 }); 710}; 711 712/** 713 * Render filename label for grid and list view. 714 * @param {HTMLDocument} doc Owner document. 715 * @param {Entry} entry The Entry object to render. 716 * @return {HTMLDivElement} The label. 717 */ 718filelist.renderFileNameLabel = function(doc, entry) { 719 // Filename need to be in a '.filename-label' container for correct 720 // work of inplace renaming. 721 var box = doc.createElement('div'); 722 box.className = 'filename-label'; 723 var fileName = doc.createElement('span'); 724 fileName.className = 'entry-name'; 725 fileName.textContent = entry.name; 726 box.appendChild(fileName); 727 728 return box; 729}; 730 731/** 732 * Updates grid item or table row for the externalProps. 733 * @param {cr.ui.ListItem} li List item. 734 * @param {Object} externalProps Metadata. 735 */ 736filelist.updateListItemExternalProps = function(li, externalProps) { 737 if (li.classList.contains('file')) { 738 if (externalProps.availableOffline) 739 li.classList.remove('dim-offline'); 740 else 741 li.classList.add('dim-offline'); 742 // TODO(mtomasz): Consider adding some vidual indication for files which 743 // are not cached on LTE. Currently we show them as normal files. 744 // crbug.com/246611. 745 } 746 747 var iconDiv = li.querySelector('.detail-icon'); 748 if (!iconDiv) 749 return; 750 751 if (externalProps.customIconUrl) 752 iconDiv.style.backgroundImage = 'url(' + externalProps.customIconUrl + ')'; 753 else 754 iconDiv.style.backgroundImage = ''; // Back to the default image. 755 756 if (li.classList.contains('directory')) 757 iconDiv.classList.toggle('shared', externalProps.shared); 758}; 759