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/** 6 * @fileoverview This implements a table header. 7 */ 8 9cr.define('cr.ui.table', function() { 10 /** @const */ var TableSplitter = cr.ui.TableSplitter; 11 12 /** 13 * Creates a new table header. 14 * @param {Object=} opt_propertyBag Optional properties. 15 * @constructor 16 * @extends {HTMLDivElement} 17 */ 18 var TableHeader = cr.ui.define('div'); 19 20 TableHeader.prototype = { 21 __proto__: HTMLDivElement.prototype, 22 23 table_: null, 24 25 /** 26 * Initializes the element. 27 */ 28 decorate: function() { 29 this.className = 'table-header'; 30 31 this.headerInner_ = this.ownerDocument.createElement('div'); 32 this.headerInner_.className = 'table-header-inner'; 33 this.appendChild(this.headerInner_); 34 this.addEventListener('touchstart', 35 this.handleTouchStart_.bind(this), false); 36 }, 37 38 /** 39 * Updates table header width. Header width depends on list having a 40 * vertical scrollbar. 41 */ 42 updateWidth: function() { 43 // Header should not span over the vertical scrollbar of the list. 44 var list = this.table_.querySelector('list'); 45 this.headerInner_.style.width = list.clientWidth + 'px'; 46 }, 47 48 /** 49 * Resizes columns. 50 */ 51 resize: function() { 52 var cm = this.table_.columnModel; 53 54 var headerCells = this.querySelectorAll('.table-header-cell'); 55 if (headerCells.length != cm.size) { 56 this.redraw(); 57 return; 58 } 59 60 for (var i = 0; i < cm.size; i++) { 61 headerCells[i].style.width = cm.getWidth(i) + 'px'; 62 } 63 this.placeSplitters_(this.querySelectorAll('.table-header-splitter')); 64 }, 65 66 batchCount_: 0, 67 68 startBatchUpdates: function() { 69 this.batchCount_++; 70 }, 71 72 endBatchUpdates: function() { 73 this.batchCount_--; 74 if (this.batchCount_ == 0) 75 this.redraw(); 76 }, 77 78 /** 79 * Redraws table header. 80 */ 81 redraw: function() { 82 if (this.batchCount_ != 0) 83 return; 84 85 var cm = this.table_.columnModel; 86 var dm = this.table_.dataModel; 87 88 this.updateWidth(); 89 this.headerInner_.textContent = ''; 90 91 if (!cm || ! dm) { 92 return; 93 } 94 95 for (var i = 0; i < cm.size; i++) { 96 var cell = this.ownerDocument.createElement('div'); 97 cell.style.width = cm.getWidth(i) + 'px'; 98 cell.className = 'table-header-cell'; 99 if (dm.isSortable(cm.getId(i))) 100 cell.addEventListener('click', 101 this.createSortFunction_(i).bind(this)); 102 103 cell.appendChild(this.createHeaderLabel_(i)); 104 this.headerInner_.appendChild(cell); 105 } 106 this.appendSplitters_(); 107 }, 108 109 /** 110 * Appends column splitters to the table header. 111 */ 112 appendSplitters_: function() { 113 var cm = this.table_.columnModel; 114 var splitters = []; 115 for (var i = 0; i < cm.size; i++) { 116 // splitter should use CSS for background image. 117 var splitter = new TableSplitter({table: this.table_}); 118 splitter.columnIndex = i; 119 splitter.addEventListener('dblclick', 120 this.handleDblClick_.bind(this, i)); 121 122 this.headerInner_.appendChild(splitter); 123 splitters.push(splitter); 124 } 125 this.placeSplitters_(splitters); 126 }, 127 128 /** 129 * Place splitters to right positions. 130 * @param {Array.<HTMLElement>|NodeList} splitters Array of splitters. 131 */ 132 placeSplitters_: function(splitters) { 133 var cm = this.table_.columnModel; 134 var place = 0; 135 for (var i = 0; i < cm.size; i++) { 136 place += cm.getWidth(i); 137 splitters[i].style.webkitMarginStart = place + 'px'; 138 } 139 }, 140 141 /** 142 * Renders column header. Appends text label and sort arrow if needed. 143 * @param {number} index Column index. 144 */ 145 createHeaderLabel_: function(index) { 146 var cm = this.table_.columnModel; 147 var dm = this.table_.dataModel; 148 149 var labelDiv = this.ownerDocument.createElement('div'); 150 labelDiv.className = 'table-header-label'; 151 152 if (cm.isEndAlign(index)) 153 labelDiv.style.textAlign = 'end'; 154 var span = this.ownerDocument.createElement('span'); 155 span.appendChild(cm.renderHeader(index, this.table_)); 156 span.style.padding = '0'; 157 158 if (dm) { 159 if (dm.sortStatus.field == cm.getId(index)) { 160 if (dm.sortStatus.direction == 'desc') 161 span.className = 'table-header-sort-image-desc'; 162 else 163 span.className = 'table-header-sort-image-asc'; 164 } 165 } 166 labelDiv.appendChild(span); 167 return labelDiv; 168 }, 169 170 /** 171 * Creates sort function for given column. 172 * @param {number} index The index of the column to sort by. 173 */ 174 createSortFunction_: function(index) { 175 return function() { 176 this.table_.sort(index); 177 }.bind(this); 178 }, 179 180 /** 181 * Handles the touchstart event. If the touch happened close enough 182 * to a splitter starts dragging. 183 * @param {TouchEvent} e The touch event. 184 */ 185 handleTouchStart_: function(e) { 186 if (e.touches.length != 1) 187 return; 188 var clientX = e.touches[0].clientX; 189 190 var minDistance = TableHeader.TOUCH_DRAG_AREA_WIDTH; 191 var candidate; 192 193 var splitters = this.querySelectorAll('.table-header-splitter'); 194 for (var i = 0; i < splitters.length; i++) { 195 var r = splitters[i].getBoundingClientRect(); 196 if (clientX <= r.left && r.left - clientX <= minDistance) { 197 minDistance = r.left - clientX; 198 candidate = splitters[i]; 199 } 200 if (clientX >= r.right && clientX - r.right <= minDistance) { 201 minDistance = clientX - r.right; 202 candidate = splitters[i]; 203 } 204 } 205 if (candidate) 206 candidate.startDrag(clientX, true); 207 // Splitter itself shouldn't handle this event. 208 e.stopPropagation(); 209 }, 210 211 /** 212 * Handles the double click on a column separator event. 213 * Ajusts column width. 214 * @param {number} index Column index. 215 * @param {Event} e The double click event. 216 */ 217 handleDblClick_: function(index, e) { 218 this.table_.fitColumn(index); 219 } 220 }; 221 222 /** 223 * The table associated with the header. 224 * @type {cr.ui.Table} 225 */ 226 cr.defineProperty(TableHeader, 'table'); 227 228 /** 229 * Rectangular area around the splitters sensitive to touch events 230 * (in pixels). 231 */ 232 TableHeader.TOUCH_DRAG_AREA_WIDTH = 30; 233 234 return { 235 TableHeader: TableHeader 236 }; 237}); 238