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