| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /** |
| * @fileoverview This implements a table header. |
| */ |
| |
| cr.define('cr.ui.table', function() { |
| /** @const */ var TableSplitter = cr.ui.TableSplitter; |
| |
| /** |
| * Creates a new table header. |
| * @param {Object=} opt_propertyBag Optional properties. |
| * @constructor |
| * @extends {HTMLDivElement} |
| */ |
| var TableHeader = cr.ui.define('div'); |
| |
| TableHeader.prototype = { |
| __proto__: HTMLDivElement.prototype, |
| |
| table_: null, |
| |
| /** |
| * Initializes the element. |
| */ |
| decorate: function() { |
| this.className = 'table-header'; |
| |
| this.headerInner_ = this.ownerDocument.createElement('div'); |
| this.headerInner_.className = 'table-header-inner'; |
| this.appendChild(this.headerInner_); |
| this.addEventListener('touchstart', |
| this.handleTouchStart_.bind(this), false); |
| }, |
| |
| /** |
| * Updates table header width. Header width depends on list having a |
| * vertical scrollbar. |
| */ |
| updateWidth: function() { |
| // Header should not span over the vertical scrollbar of the list. |
| var list = this.table_.querySelector('list'); |
| this.headerInner_.style.width = list.clientWidth + 'px'; |
| }, |
| |
| /** |
| * Resizes columns. |
| */ |
| resize: function() { |
| var cm = this.table_.columnModel; |
| |
| var headerCells = this.querySelectorAll('.table-header-cell'); |
| if (headerCells.length != cm.size) { |
| this.redraw(); |
| return; |
| } |
| |
| for (var i = 0; i < cm.size; i++) { |
| headerCells[i].style.width = cm.getWidth(i) + 'px'; |
| } |
| this.placeSplitters_(this.querySelectorAll('.table-header-splitter')); |
| }, |
| |
| batchCount_: 0, |
| |
| startBatchUpdates: function() { |
| this.batchCount_++; |
| }, |
| |
| endBatchUpdates: function() { |
| this.batchCount_--; |
| if (this.batchCount_ == 0) |
| this.redraw(); |
| }, |
| |
| /** |
| * Redraws table header. |
| */ |
| redraw: function() { |
| if (this.batchCount_ != 0) |
| return; |
| |
| var cm = this.table_.columnModel; |
| var dm = this.table_.dataModel; |
| |
| this.updateWidth(); |
| this.headerInner_.textContent = ''; |
| |
| if (!cm || ! dm) { |
| return; |
| } |
| |
| for (var i = 0; i < cm.size; i++) { |
| var cell = this.ownerDocument.createElement('div'); |
| cell.style.width = cm.getWidth(i) + 'px'; |
| cell.className = 'table-header-cell'; |
| if (dm.isSortable(cm.getId(i))) |
| cell.addEventListener('click', |
| this.createSortFunction_(i).bind(this)); |
| |
| cell.appendChild(this.createHeaderLabel_(i)); |
| this.headerInner_.appendChild(cell); |
| } |
| this.appendSplitters_(); |
| }, |
| |
| /** |
| * Appends column splitters to the table header. |
| */ |
| appendSplitters_: function() { |
| var cm = this.table_.columnModel; |
| var splitters = []; |
| for (var i = 0; i < cm.size; i++) { |
| // splitter should use CSS for background image. |
| var splitter = new TableSplitter({table: this.table_}); |
| splitter.columnIndex = i; |
| splitter.addEventListener('dblclick', |
| this.handleDblClick_.bind(this, i)); |
| |
| this.headerInner_.appendChild(splitter); |
| splitters.push(splitter); |
| } |
| this.placeSplitters_(splitters); |
| }, |
| |
| /** |
| * Place splitters to right positions. |
| * @param {Array.<HTMLElement>|NodeList} splitters Array of splitters. |
| */ |
| placeSplitters_: function(splitters) { |
| var cm = this.table_.columnModel; |
| var place = 0; |
| for (var i = 0; i < cm.size; i++) { |
| place += cm.getWidth(i); |
| splitters[i].style.webkitMarginStart = place + 'px'; |
| } |
| }, |
| |
| /** |
| * Renders column header. Appends text label and sort arrow if needed. |
| * @param {number} index Column index. |
| */ |
| createHeaderLabel_: function(index) { |
| var cm = this.table_.columnModel; |
| var dm = this.table_.dataModel; |
| |
| var labelDiv = this.ownerDocument.createElement('div'); |
| labelDiv.className = 'table-header-label'; |
| |
| if (cm.isEndAlign(index)) |
| labelDiv.style.textAlign = 'end'; |
| var span = this.ownerDocument.createElement('span'); |
| span.appendChild(cm.renderHeader(index, this.table_)); |
| span.style.padding = '0'; |
| |
| if (dm) { |
| if (dm.sortStatus.field == cm.getId(index)) { |
| if (dm.sortStatus.direction == 'desc') |
| span.className = 'table-header-sort-image-desc'; |
| else |
| span.className = 'table-header-sort-image-asc'; |
| } |
| } |
| labelDiv.appendChild(span); |
| return labelDiv; |
| }, |
| |
| /** |
| * Creates sort function for given column. |
| * @param {number} index The index of the column to sort by. |
| */ |
| createSortFunction_: function(index) { |
| return function() { |
| this.table_.sort(index); |
| }.bind(this); |
| }, |
| |
| /** |
| * Handles the touchstart event. If the touch happened close enough |
| * to a splitter starts dragging. |
| * @param {Event} e The touch event. |
| */ |
| handleTouchStart_: function(e) { |
| e = /** @type {TouchEvent} */ (e); |
| if (e.touches.length != 1) |
| return; |
| var clientX = e.touches[0].clientX; |
| |
| var minDistance = TableHeader.TOUCH_DRAG_AREA_WIDTH; |
| var candidate; |
| |
| var splitters = this.querySelectorAll('.table-header-splitter'); |
| for (var i = 0; i < splitters.length; i++) { |
| var r = splitters[i].getBoundingClientRect(); |
| if (clientX <= r.left && r.left - clientX <= minDistance) { |
| minDistance = r.left - clientX; |
| candidate = splitters[i]; |
| } |
| if (clientX >= r.right && clientX - r.right <= minDistance) { |
| minDistance = clientX - r.right; |
| candidate = splitters[i]; |
| } |
| } |
| if (candidate) |
| candidate.startDrag(clientX, true); |
| // Splitter itself shouldn't handle this event. |
| e.stopPropagation(); |
| }, |
| |
| /** |
| * Handles the double click on a column separator event. |
| * Ajusts column width. |
| * @param {number} index Column index. |
| * @param {Event} e The double click event. |
| */ |
| handleDblClick_: function(index, e) { |
| this.table_.fitColumn(index); |
| } |
| }; |
| |
| /** |
| * The table associated with the header. |
| * @type {cr.ui.Table} |
| */ |
| cr.defineProperty(TableHeader, 'table'); |
| |
| /** |
| * Rectangular area around the splitters sensitive to touch events |
| * (in pixels). |
| */ |
| TableHeader.TOUCH_DRAG_AREA_WIDTH = 30; |
| |
| return { |
| TableHeader: TableHeader |
| }; |
| }); |