1 // ==========================================================================
  2 // Project:   SproutCore - JavaScript Application Framework
  3 // Copyright: ©2006-2011 Strobe Inc. and contributors.
  4 //            Portions ©2008-2011 Apple, Inc. All rights reserved.
  5 // License:   Licensed under MIT license (see license.js)
  6 // ==========================================================================
  7 
  8 sc_require('mixins/table_delegate');
  9 sc_require('views/table_head');
 10 
 11 /** @class
 12   Deprecated.
 13 
 14   The default SC.TableView has several issues and therefore until a suitable
 15   replacement is developed, this class should be considered deprecated and
 16   should not be used.
 17 
 18   Please try [](https://github.com/jslewis/sctable) for a decent alternative.
 19 
 20   @extends SC.ListView
 21   @extends SC.TableDelegate
 22   @deprecated Version 1.10
 23   @since SproutCore 1.1
 24 */
 25 
 26 SC.TableView = SC.ListView.extend(SC.TableDelegate, {
 27   /** @scope SC.TableView.prototype */
 28 
 29   // ..........................................................
 30   // PROPERTIES
 31   //
 32 
 33   classNames: ['sc-table-view'],
 34 
 35   childViews: "tableHeadView scrollView".w(),
 36 
 37   scrollView: SC.ScrollView.extend({
 38     isVisible: YES,
 39     layout: {
 40       left:   -1,
 41       right:  0,
 42       bottom: 0,
 43       top:    19
 44     },
 45     hasHorizontalScroller: NO,
 46     borderStyle: SC.BORDER_NONE,
 47     contentView: SC.View.extend({
 48     }),
 49 
 50     // FIXME: Hack.
 51     _sv_offsetDidChange: function() {
 52       this.get('parentView')._sctv_scrollOffsetDidChange();
 53     }.observes('verticalScrollOffset', 'horizontalScrollOffset')
 54   }),
 55 
 56   hasHorizontalScroller: NO,
 57   hasVerticalScroller: NO,
 58 
 59   selectOnMouseDown: NO,
 60 
 61   // FIXME: Charles originally had this as an outlet, but that doesn't work.
 62   // Figure out why.
 63   containerView: function() {
 64     var scrollView = this.get('scrollView');
 65     return (scrollView && scrollView.get) ? scrollView.get('contentView') : null;
 66     //return this.get('scrollView').get('contentView');
 67   }.property('scrollView'),
 68 
 69   layout: { left: 0, right: 0, top: 0, bottom: 0 },
 70 
 71   init: function() {
 72     sc_super();
 73 
 74     window.table = this; // DEBUG
 75     //this._sctv_columnsDidChange();
 76   },
 77 
 78 
 79   canReorderContent: NO,
 80 
 81   isInDragMode: NO,
 82 
 83   // ..........................................................
 84   // EVENT RESPONDERS
 85   //
 86 
 87   mouseDownInTableHeaderView: function(evt, header) {
 88     var column = header.get('column');
 89 
 90     if (!column.get('isReorderable') && !column.get('isSortable')) {
 91       return NO;
 92     }
 93 
 94     // Save the mouseDown event so we can use it for mouseUp/mouseDragged.
 95     this._mouseDownEvent = evt;
 96     // Set the timer for switching from a sort action to a reorder action.
 97     this._mouseDownTimer = SC.Timer.schedule({
 98       target: this,
 99       action: '_scthv_enterDragMode',
100       interval: 300
101     });
102 
103     return YES;
104   },
105 
106   mouseUpInTableHeaderView: function(evt, header) {
107     var isInDragMode = this.get('isInDragMode');
108     // Only sort if we're not in drag mode (i.e., short clicks).
109     if (!isInDragMode) {
110       var column = header.get('column');
111       // Change the sort state of the associated column.
112       this.set('sortedColumn', column);
113 
114       var sortState = column.get('sortState');
115       var newSortState = sortState === SC.SORT_ASCENDING ?
116        SC.SORT_DESCENDING : SC.SORT_ASCENDING;
117 
118       column.set('sortState', newSortState);
119     }
120 
121     // Exit drag mode (and cancel any scheduled drag modes).
122     // this._scthv_exitDragMode();
123     this._dragging = false;
124     if (this._mouseDownTimer) {
125       this._mouseDownTimer.invalidate();
126     }
127 
128   },
129 
130   mouseDraggedInTableHeaderView: function(evt, header) {
131     SC.RunLoop.begin();
132     var isInDragMode = this.get('isInDragMode');
133     if (!isInDragMode) return NO;
134 
135     if (!this._dragging) {
136       SC.Drag.start({
137         event:  this._mouseDownEvent,
138         source: header,
139         dragView: this._scthv_dragViewForHeader(),
140         ghost: YES
141         //anchorView: this.get('parentView')
142       });
143       this._dragging = true;
144     }
145 
146     return sc_super();
147     SC.RunLoop.end();
148   },
149 
150 
151   // ..........................................................
152   // COLUMN PROPERTIES
153   //
154 
155   /**
156     A collection of `SC.TableColumn` objects. Modify the array to adjust the
157     columns.
158 
159     @property
160     @type Array
161   */
162   columns: [],
163 
164   /**
165     Which column will alter its size so that the columns fill the available
166     width of the table. If `null`, the last column will stretch.
167 
168     @property
169     @type SC.TableColumn
170   */
171   flexibleColumn: null,
172 
173   /**
174     Which column is currently the "active" column for sorting purposes.
175     Doesn't say anything about sorting direction; for that, read the
176     `sortState` property of the sorted column.
177 
178     @property
179     @type SC.TableColumn
180   */
181   sortedColumn: null,
182 
183   // ..........................................................
184   // HEAD PROPERTIES
185   //
186 
187   /**
188     if YES, the table view will generate a head row at the top of the table
189     view.
190 
191     @property
192     @type Boolean
193   */
194   hasTableHead: YES,
195 
196   /**
197     The view that serves as the head view for the table (if any).
198 
199     @property
200     @type SC.View
201   */
202   tableHeadView: SC.TableHeadView.extend({
203     layout: { top: 0, left: 0, right: 0 }
204   }),
205 
206   /**
207     The height of the table head in pixels.
208 
209     @property
210     @type Number
211   */
212   tableHeadHeight: 18,
213 
214 
215   // ..........................................................
216   // ROW PROPERTIES
217   //
218 
219   /**
220     Whether all rows in the table will have the same pixel height. If so, we
221     can compute offsets very cheaply.
222 
223     @property
224     @type Boolean
225   */
226   hasUniformRowHeights: YES,
227 
228   /**
229     How high each row should be, in pixels.
230 
231     @property
232     @type Number
233   */
234   rowHeight: 18,
235 
236   /**
237     Which view to use for a table row.
238 
239     @property
240     @type SC.View
241   */
242   exampleView: SC.TableRowView,
243 
244   // ..........................................................
245   // DRAG-REORDER MODE
246   //
247 
248   isInColumnDragMode: NO,
249 
250 
251 
252   // ..........................................................
253   // OTHER PROPERTIES
254   //
255 
256   filterKey: null,
257 
258 
259   /**
260     Returns the top offset for the specified content index.  This will take
261     into account any custom row heights and group views.
262 
263     @param {Number} idx the content index
264     @returns {Number} the row offset in pixels
265   */
266 
267   rowOffsetForContentIndex: function(contentIndex) {
268     var top = 0, idx;
269 
270     if (this.get('hasUniformRowHeights')) {
271       return top + (this.get('rowHeight') * contentIndex);
272     } else {
273       for (idx = 0; idx < contentIndex; i++) {
274         top += this.rowHeightForContentIndex(idx);
275       }
276       return top;
277     }
278   },
279 
280   /**
281     Returns the row height for the specified content index.  This will take
282     into account custom row heights and group rows.
283 
284     @param {Number} idx content index
285     @returns {Number} the row height in pixels
286   */
287   rowHeightForContentIndex: function(contentIndex) {
288     if (this.get('hasUniformRowHeights')) {
289       return this.get('rowHeight');
290     } else {
291       // TODO
292     }
293   },
294 
295 
296   /**
297     Computes the layout for a specific content index by combining the current
298     row heights.
299 
300     @param {Number} index content index
301   */
302   layoutForContentIndex: function(index) {
303     return {
304       top:    this.rowOffsetForContentIndex(index),
305       height: this.rowHeightForContentIndex(index),
306       left:   0,
307       right:  0
308     };
309   },
310 
311   createItemView: function(exampleClass, idx, attrs) {
312     // Add a `tableView` attribute to each created row so it has a way to
313     // refer back to this view.
314     attrs.tableView = this;
315     return exampleClass.create(attrs);
316   },
317 
318   clippingFrame: function() {
319     var cv = this.get('containerView'),
320         sv = this.get('scrollView'),
321         f  = this.get('frame');
322 
323     if (!sv.get) {
324       return f;
325     }
326 
327     return {
328       height: f.height,
329       width:  f.width,
330       x:      sv.get('horizontalScrollOffset'),
331       y:      sv.get('verticalScrollOffset')
332     };
333 
334   }.property('frame', 'content').cacheable(),
335 
336   _sctv_scrollOffsetDidChange: function() {
337     this.notifyPropertyChange('clippingFrame');
338   },
339 
340 
341   // ..........................................................
342   // SUBCLASS IMPLEMENTATIONS
343   //
344 
345 
346   computeLayout: function() {
347     var layout = sc_super(),
348         containerView = this.get('containerView'),
349         frame = this.get('frame');
350 
351     var minHeight = layout.minHeight;
352     delete layout.minHeight;
353 
354 
355     // FIXME: In the middle of initialization, the TableView needs to be
356     // reloaded in order to become aware of the proper display state of the
357     // table rows. This is currently the best heuristic I can find to decide
358     // when to do the reload. But the whole thing is a hack and should be
359     // fixed as soon as possible.
360     // var currentHeight = containerView.get('layout').height;
361     // if (currentHeight !== height) {
362     //   this.reload();
363     // }
364 
365     containerView.adjust('minHeight', minHeight);
366     containerView.layoutDidChange();
367 
368     // Set the calculatedHeight used by SC.ScrollView.
369     this.set('calculatedHeight', minHeight);
370 
371     //containerView.adjust('height', height);
372     //containerView.layoutDidChange();
373 
374     this.notifyPropertyChange('clippingFrame');
375     return layout;
376   },
377 
378 
379   // ..........................................................
380   // INTERNAL SUPPORT
381   //
382 
383   // When the columns change, go through all the columns and set their tableContent to be this table's content
384   // TODO: should these guys not just have a binding of this instead?
385   _sctv_columnsDidChange: function() {
386 
387     var columns = this.get('columns'),
388         content = this.get('content'),
389         idx;
390 
391     for (idx = 0; idx < columns.get('length'); idx++) {
392       columns.objectAt(idx).set('tableContent', content);
393     }
394     this.get('tableHeadView')._scthv_handleChildren();
395     this.reload();
396 
397   }.observes('columns'),
398 
399   // Do stuff when our frame size changes.
400   _sctv_adjustColumnWidthsOnResize: function() {
401 
402     var width   = this.get('frame').width;
403     var content = this.get('content'),
404         del = this.delegateFor('isTableDelegate', this.delegate, content);
405 
406     if (this.get('columns').length == 0) return;
407     width = del.tableShouldResizeWidthTo(this, width);
408 
409     var columns = this.get('columns'), totalColumnWidth = 0, idx;
410 
411     for (var idx = 0; idx < columns.length; idx++) {
412       totalColumnWidth += columns.objectAt(idx).get('width');
413     }
414 
415     if (width === 0) width = totalColumnWidth;
416     var flexibleColumn = this.get('flexibleColumn') ||
417       this.get('columns').objectAt(this.get('columns').length - 1);
418     var flexibleWidth = flexibleColumn.get('width') +
419      (width - totalColumnWidth);
420 
421     flexibleColumn.set('width', flexibleWidth);
422   }.observes('frame'),
423 
424   // =============================================================
425   // = This is all terrible, but will have to do in the interim. =
426   // =============================================================
427   _sctv_sortContent: function() {
428     var sortedColumn = this.get('sortedColumn');
429     var sortKey = sortedColumn.get('key');
430     this.set('orderBy', sortKey);
431   },
432 
433   _sctv_sortedColumnDidChange: function() {
434     var columns = this.get('columns'),
435         sortedColumn = this.get('sortedColumn'),
436         column, idx;
437 
438     for (idx = 0; idx < columns.get('length'); idx++) {
439       column = columns.objectAt(idx);
440       if (column !== sortedColumn) {
441         column.set('sortState', null);
442       }
443     }
444 
445     this.invokeOnce('_sctv_sortContent');
446   }.observes('sortedColumn')
447 });
448