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