-
Notifications
You must be signed in to change notification settings - Fork 296
/
List.js
869 lines (753 loc) · 28 KB
/
List.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
define([
'dojo/_base/declare',
'dojo/_base/lang',
'dojo/dom-class',
'dojo/dom-construct',
'dojo/on',
'dojo/query',
'dojo/sniff',
'./util/misc'
], function (declare, lang, domClass, domConstruct, listen, query, has, miscUtil) {
// Add user agent/feature CSS classes needed for structural CSS
var featureClasses = [];
if (has('mozilla')) {
featureClasses.push('has-mozilla');
}
if (has('touch')) {
featureClasses.push('has-touch');
}
domClass.add(document.documentElement, featureClasses);
// Add a feature test for pointer (only Dojo 1.10 has pointer-events and MSPointer tests)
has.add('pointer', function (global) {
return 'PointerEvent' in global ? 'pointer' :
'MSPointerEvent' in global ? 'MSPointer' : false;
});
var oddClass = 'dgrid-row-odd',
evenClass = 'dgrid-row-even',
scrollbarWidth, scrollbarHeight;
function byId(id, context) {
// document.getElementById only works for elements in the document
// dojo/query with the context parameter works for descendants of 'context' even when it is not in the document
return query('#' + miscUtil.escapeCssIdentifier(id), context)[0];
}
function cleanupTestElement(element) {
element.className = '';
if (element.parentNode) {
document.body.removeChild(element);
}
}
function getScrollbarSize(element, dimension) {
// Used by has tests for scrollbar width/height
element.className = 'dgrid-scrollbar-measure';
document.body.appendChild(element);
var size = element['offset' + dimension] - element['client' + dimension];
cleanupTestElement(element);
return size;
}
has.add('dom-scrollbar-width', function (global, doc, element) {
return getScrollbarSize(element, 'Width');
});
has.add('dom-scrollbar-height', function (global, doc, element) {
return getScrollbarSize(element, 'Height');
});
has.add('dom-rtl-scrollbar-left', function (global, doc, element) {
var div = document.createElement('div'),
isLeft;
element.className = 'dgrid-scrollbar-measure';
element.setAttribute('dir', 'rtl');
element.appendChild(div);
document.body.appendChild(element);
// position: absolute makes modern IE and Edge always report child's offsetLeft as 0,
// but other browsers factor in the position of the scrollbar if it is to the left.
// All versions of IE and Edge are known to move the scrollbar to the left side for rtl.
isLeft = !!has('ie') || !!has('trident') || /\bEdge\//.test(navigator.userAgent) ||
div.offsetLeft >= has('dom-scrollbar-width');
cleanupTestElement(element);
domConstruct.destroy(div);
element.removeAttribute('dir');
return isLeft;
});
// var and function for autogenerating ID when one isn't provided
var autoId = 0;
function generateId() {
return List.autoIdPrefix + autoId++;
}
// common functions for class and className setters/getters
// (these are run in instance context)
function setClass(cls) {
domClass.replace(this.domNode, cls, this._class || '');
// Store for later retrieval/removal.
this._class = cls;
}
function getClass() {
return this._class;
}
// window resize event handler, run in context of List instance
var winResizeHandler = function () {
if (this._started) {
this.resize();
}
};
var List = declare(null, {
tabableHeader: false,
// showHeader: Boolean
// Whether to render header (sub)rows.
showHeader: false,
// showFooter: Boolean
// Whether to render footer area. Extensions which display content
// in the footer area should set this to true.
showFooter: false,
// maintainOddEven: Boolean
// Whether to maintain the odd/even classes when new rows are inserted.
// This can be disabled to improve insertion performance if odd/even styling is not employed.
maintainOddEven: true,
// cleanAddedRules: Boolean
// Whether to track rules added via the addCssRule method to be removed
// when the list is destroyed. Note this is effective at the time of
// the call to addCssRule, not at the time of destruction.
cleanAddedRules: true,
// addUiClasses: Boolean
// Whether to add jQuery UI classes to various elements in dgrid's DOM.
addUiClasses: true,
// highlightDuration: Integer
// The amount of time (in milliseconds) that a row should remain
// highlighted after it has been updated.
highlightDuration: 250,
// resizeThrottleDelay: Integer
// The delay (in milliseconds) passed to the resizeThrottleMethod.
// A lower value will provide more responsive grid resizing. If there are a large number of grids on
// the page, a higher value can improve performance (or specify 'debounce' for 'resizeThrottleMethod').
resizeThrottleDelay: miscUtil.defaultDelay,
// resizeThrottleMethod: String or Function
// String: the name of a method from dgrid/util/misc ('debounce', 'throttle', 'throttleDelayed') to throttle or debounce the window resize handler.
// Function: a function to throttle or debounce the window resize handler. The function will receive
// two parameters:
// callback (Function): the function to be throttled
// delay (Integer): the value of the resizeThrottleDelay property
// The function must return a function that executes the callback function.
resizeThrottleMethod: 'throttleDelayed',
postscript: function (params, srcNodeRef) {
// perform setup and invoke create in postScript to allow descendants to
// perform logic before create/postCreate happen (a la dijit/_WidgetBase)
var grid = this;
(this._Row = function (id, object, element) {
this.id = id;
this.data = object;
this.element = element;
}).prototype.remove = function () {
grid.removeRow(this.element);
};
if (srcNodeRef) {
// normalize srcNodeRef and store on instance during create process.
// Doing this in postscript is a bit earlier than dijit would do it,
// but allows subclasses to access it pre-normalized during create.
this.srcNodeRef = srcNodeRef =
srcNodeRef.nodeType ? srcNodeRef : byId(srcNodeRef);
}
this.create(params, srcNodeRef);
},
listType: 'list',
create: function (params, srcNodeRef) {
var domNode = this.domNode = srcNodeRef || document.createElement('div'),
cls;
if (params) {
this.params = params;
declare.safeMixin(this, params);
// Check for initial class or className in params or on domNode
cls = params['class'] || params.className || domNode.className;
}
// ensure arrays and hashes are initialized
this.sort = this.sort || [];
this._listeners = [];
this._rowIdToObject = {};
this.postMixInProperties && this.postMixInProperties();
// Apply id to widget and domNode,
// from incoming node, widget params, or autogenerated.
this.id = domNode.id = domNode.id || this.id || generateId();
// Perform initial rendering, and apply classes if any were specified.
this.buildRendering();
if (cls) {
setClass.call(this, cls);
}
this.postCreate();
// remove srcNodeRef instance property post-create
delete this.srcNodeRef;
// to preserve "it just works" behavior, call startup if we're visible
if (this.domNode.offsetHeight) {
this.startup();
}
},
buildRendering: function () {
var domNode = this.domNode,
addUiClasses = this.addUiClasses,
self = this,
headerNode,
bodyNode,
footerNode,
isRTL,
throttledResizeHandler;
// Detect RTL on html/body nodes; taken from dojo/dom-geometry
isRTL = this.isRTL = (document.body.dir || document.documentElement.dir ||
document.body.style.direction).toLowerCase() === 'rtl';
// Clear out className (any pre-applied classes will be re-applied via the
// class / className setter), then apply standard classes/attributes
domNode.className = '';
domNode.setAttribute('role', 'grid');
domClass.add(domNode, 'dgrid dgrid-' + this.listType +
(addUiClasses ? ' ui-widget' : ''));
// Place header node (initially hidden if showHeader is false).
headerNode = this.headerNode = domConstruct.create('div', {
className: 'dgrid-header dgrid-header-row' + (addUiClasses ? ' ui-widget-header' : '') +
(this.showHeader ? '' : ' dgrid-header-hidden')
}, domNode);
bodyNode = this.bodyNode = domConstruct.create('div', {
className: 'dgrid-scroller'
}, domNode);
// Firefox 4+ adds overflow: auto elements to the tab index by default;
// force them to not be tabbable, but restrict this to Firefox,
// since it breaks accessibility support in other browsers
if (has('ff')) {
bodyNode.tabIndex = -1;
}
this.headerScrollNode = domConstruct.create('div', {
className: 'dgrid-header dgrid-header-scroll dgrid-scrollbar-width' +
(addUiClasses ? ' ui-widget-header' : '')
}, domNode);
// Place footer node (initially hidden if showFooter is false).
footerNode = this.footerNode = domConstruct.create('div', {
className: 'dgrid-footer' + (this.showFooter ? '' : ' dgrid-footer-hidden')
}, domNode);
if (isRTL) {
domNode.className += ' dgrid-rtl' +
(has('dom-rtl-scrollbar-left') ? ' dgrid-rtl-swap' : '');
}
listen(bodyNode, 'scroll', function (event) {
if (self.showHeader) {
// keep the header aligned with the body
headerNode.scrollLeft = event.scrollLeft || bodyNode.scrollLeft;
}
// re-fire, since browsers are not consistent about propagation here
event.stopPropagation();
listen.emit(domNode, 'scroll', {scrollTarget: bodyNode});
});
this.configStructure();
this.renderHeader();
this.contentNode = this.touchNode = domConstruct.create('div', {
className: 'dgrid-content' + (addUiClasses ? ' ui-widget-content' : '')
}, this.bodyNode);
if (typeof this.resizeThrottleMethod === 'string' && miscUtil[this.resizeThrottleMethod]) {
throttledResizeHandler = miscUtil[this.resizeThrottleMethod](winResizeHandler, this, this.resizeThrottleDelay);
} else if (typeof this.resizeThrottleMethod === 'function') {
throttledResizeHandler = this.resizeThrottleMethod(lang.hitch(this, winResizeHandler), this.resizeThrottleDelay);
} else {
console.warn('Invalid value specified for resizeThrottleMethod: ' + this.resizeThrottleMethod);
throttledResizeHandler = miscUtil.throttleDelayed(winResizeHandler, this, this.resizeThrottleDelay);
}
// add window resize handler, with reference for later removal if needed
this._resizeHandle = listen(window, 'resize', throttledResizeHandler);
this._listeners.push(this._resizeHandle);
},
postCreate: function () {
},
startup: function () {
// summary:
// Called automatically after postCreate if the component is already
// visible; otherwise, should be called manually once placed.
if (this._started) {
return;
}
this.inherited(arguments);
this._started = true;
this.resize();
// apply sort (and refresh) now that we're ready to render
this.set('sort', this.sort);
},
configStructure: function () {
// does nothing in List, this is more of a hook for the Grid
},
resize: function () {
var bodyNode = this.bodyNode,
headerNode = this.headerNode,
footerNode = this.footerNode,
headerHeight = headerNode.offsetHeight,
footerHeight = this.showFooter ? footerNode.offsetHeight : 0;
this.headerScrollNode.style.height = bodyNode.style.marginTop = headerHeight + 'px';
bodyNode.style.marginBottom = footerHeight + 'px';
if (!scrollbarWidth) {
// Measure the browser's scrollbar width using a DIV we'll delete right away
scrollbarWidth = has('dom-scrollbar-width');
scrollbarHeight = has('dom-scrollbar-height');
// Avoid issues with certain widgets inside in IE7, and
// ColumnSet scroll issues with all supported IE versions
if (has('ie')) {
scrollbarWidth++;
scrollbarHeight++;
}
// add rules that can be used where scrollbar width/height is needed
miscUtil.addCssRule('.dgrid-scrollbar-width', 'width: ' + scrollbarWidth + 'px');
miscUtil.addCssRule('.dgrid-scrollbar-height', 'height: ' + scrollbarHeight + 'px');
if (scrollbarWidth !== 17) {
// for modern browsers, we can perform a one-time operation which adds
// a rule to account for scrollbar width in all grid headers.
miscUtil.addCssRule('.dgrid-header-row', 'right: ' + scrollbarWidth + 'px');
// add another for RTL grids
miscUtil.addCssRule('.dgrid-rtl-swap .dgrid-header-row', 'left: ' + scrollbarWidth + 'px');
}
}
},
addCssRule: function (selector, css) {
// summary:
// Version of util/misc.addCssRule which tracks added rules and removes
// them when the List is destroyed.
var rule = miscUtil.addCssRule(selector, css);
if (this.cleanAddedRules) {
// Although this isn't a listener, it shares the same remove contract
this._listeners.push(rule);
}
return rule;
},
on: function (eventType, listener) {
// delegate events to the domNode
var signal = listen(this.domNode, eventType, listener);
if (!has('dom-addeventlistener')) {
this._listeners.push(signal);
}
return signal;
},
cleanup: function () {
// summary:
// Clears out all rows currently in the list.
var i;
for (i in this._rowIdToObject) {
if (this._rowIdToObject[i] !== this.columns) {
var rowElement = byId(i, this.domNode);
if (rowElement) {
this.removeRow(rowElement, true);
}
}
}
},
destroy: function () {
// summary:
// Destroys this grid
// Remove any event listeners and other such removables
if (this._listeners) { // Guard against accidental subsequent calls to destroy
for (var i = this._listeners.length; i--;) {
this._listeners[i].remove();
}
this._listeners = null;
}
this._started = false;
this.cleanup();
// destroy DOM
domConstruct.destroy(this.domNode);
},
refresh: function () {
// summary:
// refreshes the contents of the grid
this.cleanup();
this._rowIdToObject = {};
this._autoRowId = 0;
// make sure all the content has been removed so it can be recreated
this.contentNode.innerHTML = '';
// Ensure scroll position always resets
this.scrollTo({ x: 0, y: 0 });
},
highlightRow: function (rowElement, delay) {
// summary:
// Highlights a row. Used when updating rows due to store
// notifications, but potentially also useful in other cases.
// rowElement: Object
// Row element (or object returned from the row method) to
// highlight.
// delay: Number
// Number of milliseconds between adding and removing the
// ui-state-highlight class.
var classes = 'dgrid-highlight' + (this.addUiClasses ? ' ui-state-highlight' : '');
rowElement = rowElement.element || rowElement;
domClass.add(rowElement, classes);
setTimeout(function () {
domClass.remove(rowElement, classes);
}, delay || this.highlightDuration);
},
adjustRowIndices: function (firstRow) {
// Traverse through rows to update indexes on shift, i.e. for the odd/even classes.
var next = firstRow;
var rowIndex = next.rowIndex;
if (rowIndex > -1) { // make sure we have a real number in case this is called on a non-row
do {
// Skip non-numeric, non-rows
if (next.rowIndex > -1) {
this.setRowIndex(next, rowIndex++, next.rowIndex);
}
} while ((next = next.nextSibling) && next.rowIndex !== rowIndex);
}
},
renderArray: function (results, beforeNode, options) {
// summary:
// Renders an array of objects as rows, before the given node.
options = options || {};
var self = this,
start = options.start || 0,
rowsFragment = document.createDocumentFragment(),
rows = [],
container,
i = 0,
len = results.length;
if (!beforeNode) {
this._lastCollection = results;
}
// Insert a row for each item into the document fragment
while (i < len) {
rows[i] = this.insertRow(results[i], rowsFragment, null, start++, options);
i++;
}
// Insert the document fragment into the appropriate position
container = beforeNode ? beforeNode.parentNode : self.contentNode;
if (container && container.parentNode &&
(container !== self.contentNode || len)) {
container.insertBefore(rowsFragment, beforeNode || null);
if (len) {
self.adjustRowIndices(rows[len - 1]);
}
}
return rows;
},
renderHeader: function () {
// no-op in a plain list
},
setRowIndex: function(row, rowIndex, oldRowIndex) {
// Update row for changes to the visual row index.
row.rowIndex = rowIndex;
if (this.maintainOddEven) {
var newClass = (rowIndex % 2 === 1 ? oddClass : evenClass)
if (oldRowIndex !== undefined) {
var oldClass = (oldRowIndex % 2 === 1 ? oddClass : evenClass);
if (oldClass !== newClass)
domClass.replace(row, oldClass, newClass);
} else {
domClass.add(row, newClass);
}
}
},
_autoRowId: 0,
insertRow: function (object, parent, beforeNode, i, options) {
// summary:
// Creates a single row in the grid.
// Include parentId within row identifier if one was specified in options.
// (This is used by tree to allow the same object to appear under
// multiple parents.)
var id = this.id + '-row-' + ((this.collection && this.collection.getIdentity) ?
this.collection.getIdentity(object) : this._autoRowId++),
row = byId(id, this.domNode),
previousRow = row && row.previousSibling;
if (row) {
// If it existed elsewhere in the DOM, we will remove it, so we can recreate it
if (row === beforeNode) {
beforeNode = (beforeNode.connected || beforeNode).nextSibling;
}
this.removeRow(row, false, options);
}
row = this.renderRow(object, options);
row.className = (row.className || '') + ' dgrid-row ' +
(this.addUiClasses ? ' ui-state-default' : '');
// Get the row id for easy retrieval
this._rowIdToObject[row.id = id] = object;
parent.insertBefore(row, beforeNode || null);
this.setRowIndex(row, i);
if (previousRow && previousRow.rowIndex !== (row.rowIndex - 1)) {
// In this case, we are pulling the row from another location in the grid,
// and we need to readjust the rowIndices from the point it was removed
this.adjustRowIndices(previousRow);
}
return row;
},
renderRow: function (value) {
// summary:
// Responsible for returning the DOM for a single row in the grid.
// value: Mixed
// Value to render
// options: Object?
// Optional object with additional options
var div = document.createElement('div');
div.appendChild(document.createTextNode(value));
return div;
},
removeRow: function (rowElement, preserveDom) {
// summary:
// Simply deletes the node in a plain List.
// Column plugins may aspect this to implement their own cleanup routines.
// rowElement: Object|DOMNode
// Object or element representing the row to be removed.
// preserveDom: Boolean?
// If true, the row element will not be removed from the DOM; this can
// be used by extensions/plugins in cases where the DOM will be
// massively cleaned up at a later point in time.
// options: Object?
// May be specified with a `rows` property for the purpose of
// cleaning up collection tracking (used by `_StoreMixin`).
rowElement = rowElement.element || rowElement;
delete this._rowIdToObject[rowElement.id];
if (!preserveDom) {
domConstruct.destroy(rowElement);
}
},
row: function (target) {
// summary:
// Get the row object by id, object, node, or event
var id;
if (target instanceof this._Row) {
return target; // No-op; already a row
}
if (target.target && target.target.nodeType) {
// Event
target = target.target;
}
if (target.nodeType) {
// Row element, or child of a row element
var object;
do {
var rowId = target.id;
if ((object = this._rowIdToObject[rowId])) {
return new this._Row(rowId.substring(this.id.length + 5), object, target);
}
target = target.parentNode;
}while (target && target !== this.domNode);
return;
}
if (typeof target === 'object') {
// Assume target represents a collection item
id = this.collection.getIdentity(target);
}
else {
// Assume target is a row ID
id = target;
target = this._rowIdToObject[this.id + '-row-' + id];
}
return new this._Row(id, target, byId(this.id + '-row-' + id, this.domNode));
},
cell: function (target) {
// this doesn't do much in a plain list
return {
row: this.row(target)
};
},
_move: function (item, steps, targetClass, visible) {
var nextSibling, current, element;
// Start at the element indicated by the provided row or cell object.
element = current = item.element;
steps = steps || 1;
do {
// Outer loop: move in the appropriate direction.
if ((nextSibling = current[steps < 0 ? 'previousSibling' : 'nextSibling'])) {
do {
// Inner loop: advance, and dig into children if applicable.
current = nextSibling;
if (current && (current.className + ' ').indexOf(targetClass + ' ') > -1) {
// Element with the appropriate class name; count step, stop digging.
element = current;
steps += steps < 0 ? 1 : -1;
break;
}
// If the next sibling isn't a match, drill down to search, unless
// visible is true and children are hidden.
} while ((nextSibling = (!visible || !current.hidden) &&
current[steps < 0 ? 'lastChild' : 'firstChild']));
}
else {
current = current.parentNode;
if (!current || current === this.bodyNode || current === this.headerNode) {
// Break out if we step out of the navigation area entirely.
break;
}
}
}while (steps);
// Return the final element we arrived at, which might still be the
// starting element if we couldn't navigate further in that direction.
return element;
},
up: function (row, steps, visible) {
// summary:
// Returns the row that is the given number of steps (1 by default)
// above the row represented by the given object.
// row:
// The row to navigate upward from.
// steps:
// Number of steps to navigate up from the given row; default is 1.
// visible:
// If true, rows that are currently hidden (i.e. children of
// collapsed tree rows) will not be counted in the traversal.
// returns:
// A row object representing the appropriate row. If the top of the
// list is reached before the given number of steps, the first row will
// be returned.
if (!row.element) {
row = this.row(row);
}
return this.row(this._move(row, -(steps || 1), 'dgrid-row', visible));
},
down: function (row, steps, visible) {
// summary:
// Returns the row that is the given number of steps (1 by default)
// below the row represented by the given object.
// row:
// The row to navigate downward from.
// steps:
// Number of steps to navigate down from the given row; default is 1.
// visible:
// If true, rows that are currently hidden (i.e. children of
// collapsed tree rows) will not be counted in the traversal.
// returns:
// A row object representing the appropriate row. If the bottom of the
// list is reached before the given number of steps, the last row will
// be returned.
if (!row.element) {
row = this.row(row);
}
return this.row(this._move(row, steps || 1, 'dgrid-row', visible));
},
scrollTo: function (options) {
if (typeof options.x !== 'undefined') {
this.bodyNode.scrollLeft = options.x;
}
if (typeof options.y !== 'undefined') {
this.bodyNode.scrollTop = options.y;
}
},
getScrollPosition: function () {
return {
x: this.bodyNode.scrollLeft,
y: this.bodyNode.scrollTop
};
},
get: function (/*String*/ name /*, ... */) {
// summary:
// Get a property on a List instance.
// name:
// The property to get.
// returns:
// The property value on this List instance.
// description:
// Get a named property on a List object. The property may
// potentially be retrieved via a getter method in subclasses. In the base class
// this just retrieves the object's property.
var fn = '_get' + name.charAt(0).toUpperCase() + name.slice(1);
if (typeof this[fn] === 'function') {
return this[fn].apply(this, [].slice.call(arguments, 1));
}
// Alert users that try to use Dijit-style getter/setters so they don’t get confused
// if they try to use them and it does not work
if (!has('dojo-built') && typeof this[fn + 'Attr'] === 'function') {
console.warn('dgrid: Use ' + fn + ' instead of ' + fn + 'Attr for getting ' + name);
}
return this[name];
},
set: function (/*String*/ name, /*Object*/ value /*, ... */) {
// summary:
// Set a property on a List instance
// name:
// The property to set.
// value:
// The value to set in the property.
// returns:
// The function returns this List instance.
// description:
// Sets named properties on a List object.
// A programmatic setter may be defined in subclasses.
//
// set() may also be called with a hash of name/value pairs, ex:
// | myObj.set({
// | foo: "Howdy",
// | bar: 3
// | })
// This is equivalent to calling set(foo, "Howdy") and set(bar, 3)
if (typeof name === 'object') {
for (var k in name) {
this.set(k, name[k]);
}
}
else {
var fn = '_set' + name.charAt(0).toUpperCase() + name.slice(1);
if (typeof this[fn] === 'function') {
this[fn].apply(this, [].slice.call(arguments, 1));
}
else {
// Alert users that try to use Dijit-style getter/setters so they don’t get confused
// if they try to use them and it does not work
if (!has('dojo-built') && typeof this[fn + 'Attr'] === 'function') {
console.warn('dgrid: Use ' + fn + ' instead of ' + fn + 'Attr for setting ' + name);
}
this[name] = value;
}
}
return this;
},
// Accept both class and className programmatically to set domNode class.
_getClass: getClass,
_setClass: setClass,
_getClassName: getClass,
_setClassName: setClass,
_setSort: function (property, descending) {
// summary:
// Sort the content
// property: String|Array
// String specifying field to sort by, or actual array of objects
// with property and descending properties
// descending: boolean
// In the case where property is a string, this argument
// specifies whether to sort ascending (false) or descending (true)
this.sort = typeof property !== 'string' ? property :
[{property: property, descending: descending}];
this._applySort();
},
_applySort: function () {
// summary:
// Applies the current sort
// description:
// This is an extension point to allow specializations to apply the sort differently
this.refresh();
if (this._lastCollection) {
var sort = this.sort;
if (sort && sort.length > 0) {
var property = sort[0].property,
descending = !!sort[0].descending;
this._lastCollection.sort(function (a, b) {
var aVal = a[property], bVal = b[property];
// fall back undefined values to "" for more consistent behavior
if (aVal === undefined) {
aVal = '';
}
if (bVal === undefined) {
bVal = '';
}
return aVal === bVal ? 0 : (aVal > bVal !== descending ? 1 : -1);
});
}
this.renderArray(this._lastCollection);
}
},
_setShowHeader: function (show) {
// this is in List rather than just in Grid, primarily for two reasons:
// (1) just in case someone *does* want to show a header in a List
// (2) helps address IE < 8 header display issue in List
var headerNode = this.headerNode;
this.showHeader = show;
// add/remove class which has styles for "hiding" header
domClass.toggle(headerNode, 'dgrid-header-hidden', !show);
this.renderHeader();
this.resize(); // resize to account for (dis)appearance of header
if (show) {
// Update scroll position of header to make sure it's in sync.
headerNode.scrollLeft = this.getScrollPosition().x;
}
},
_setShowFooter: function (show) {
this.showFooter = show;
// add/remove class which has styles for hiding footer
domClass.toggle(this.footerNode, 'dgrid-footer-hidden', !show);
this.resize(); // to account for (dis)appearance of footer
}
});
List.autoIdPrefix = 'dgrid_';
return List;
});