forked from SitePen/dgrid
-
Notifications
You must be signed in to change notification settings - Fork 0
/
List.js
846 lines (752 loc) · 29 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
define(["dojo/_base/array","dojo/_base/kernel", "dojo/_base/declare", "dojo/on", "dojo/has", "./util/misc", "dojo/has!touch?./TouchScroll", "xstyle/has-class", "put-selector/put", "dojo/_base/sniff", "xstyle/css!./css/dgrid.css"],
function(arrayUtil, kernel, declare, listen, has, miscUtil, TouchScroll, hasClass, put){
// Add user agent/feature CSS classes
hasClass("mozilla", "opera", "webkit", "ie", "ie-6", "ie-6-7", "quirks", "no-quirks", "touch");
var scrollbarWidth;
// establish an extra stylesheet which addCssRule calls will use,
// plus an array to track actual indices in stylesheet for removal
var
extraSheet = put(document.getElementsByTagName("head")[0], "style"),
extraRules = [],
oddClass = "dgrid-row-odd",
evenClass = "dgrid-row-even";
// keep reference to actual StyleSheet object (.styleSheet for IE < 9)
extraSheet = extraSheet.sheet || extraSheet.styleSheet;
// functions for adding and removing extra style rules.
// addExtraRule is exposed on the List prototype as addCssRule.
function addExtraRule(selector, css){
var index = extraRules.length;
extraRules[index] = (extraSheet.cssRules || extraSheet.rules).length;
extraSheet.addRule ?
extraSheet.addRule(selector, css) :
extraSheet.insertRule(selector + '{' + css + '}', extraRules[index]);
return {
remove: function(){ removeExtraRule(index); }
};
}
function removeExtraRule(index){
var
realIndex = extraRules[index],
i, l = extraRules.length;
if (realIndex === undefined) { return; } // already removed
// remove rule indicated in internal array at index
extraSheet.deleteRule ?
extraSheet.deleteRule(realIndex) :
extraSheet.removeRule(realIndex); // IE < 9
// Clear internal array item representing rule that was just deleted.
// NOTE: we do NOT splice, since the point of this array is specifically
// to negotiate the splicing that occurs in the stylesheet itself!
extraRules[index] = undefined;
// Then update array items as necessary to downshift remaining rule indices.
// Can start at index, since array is sparse but strictly increasing.
for(i = index; i < l; i++){
if(extraRules[i] > realIndex){ extraRules[i]--; }
}
}
function byId(id){
return document.getElementById(id);
}
// var and function for autogenerating ID when one isn't provided
var autogen = 0;
function generateId(){
return "dgrid_" + autogen++;
}
// common functions for class and className setters/getters
// (these are run in instance context)
var spaceRx = / +/g;
function setClass(cls){
// Format input appropriately for use with put...
var putClass = cls ? "." + cls.replace(spaceRx, ".") : "";
// Remove any old classes, and add new ones.
if(this._class){
putClass = "!" + this._class.replace(spaceRx, "!") + putClass;
}
put(this.domNode, putClass);
// 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 = has("ie") < 7 && !has("quirks") ? function(){
// IE6 triggers window.resize on any element resize;
// avoid useless calls (and infinite loop if height: auto).
// The measurement logic here is based on dojo/window logic.
var root, w, h, dims;
if(!this._started){ return; } // no sense calling resize yet
root = document.documentElement;
w = root.clientWidth;
h = root.clientHeight;
dims = this._prevWinDims || [];
if(dims[0] !== w || dims[1] !== h){
this.resize();
this._prevWinDims = [w, h];
}
} :
function(){
if(this._started){ this.resize(); }
};
return declare(TouchScroll ? TouchScroll : 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
// Indicates 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,
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 || put("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;
// handle sort param - TODO: revise @ 1.0 when _sort -> sort
this._sort = params.sort || [];
delete this.sort; // ensure back-compat method isn't shadowed
}else{
this._sort = [];
}
// ensure arrays and hashes are initialized
this.observers = [];
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 && 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,
self = this,
headerNode, spacerNode, bodyNode, footerNode, isRTL;
// 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 = "";
put(domNode, "[role=grid].ui-widget.dgrid.dgrid-" + this.listType);
// Place header node (initially hidden if showHeader is false).
headerNode = this.headerNode = put(domNode,
"div.dgrid-header.dgrid-header-row.ui-widget-header" +
(this.showHeader ? "" : ".dgrid-header-hidden"));
if(has("quirks") || has("ie") < 8){
spacerNode = put(domNode, "div.dgrid-spacer");
}
bodyNode = this.bodyNode = put(domNode, "div.dgrid-scroller");
// firefox 4 until at least 10 adds overflow: auto elements to the tab index by default for some
// reason; force them to be not tabbable
bodyNode.tabIndex = -1;
this.headerScrollNode = put(domNode, "div.dgrid-header-scroll.dgrid-scrollbar-width.ui-widget-header");
// Place footer node (initially hidden if showFooter is false).
footerNode = this.footerNode = put("div.dgrid-footer" +
(this.showFooter ? "" : ".dgrid-footer-hidden"));
put(domNode, footerNode);
if(isRTL){
domNode.className += " dgrid-rtl" + (has("webkit") ? "" : " dgrid-rtl-nonwebkit");
}
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 = put(this.bodyNode, "div.dgrid-content.ui-widget-content");
// add window resize handler, with reference for later removal if needed
this._listeners.push(this._resizeHandle = listen(window, "resize",
miscUtil.throttleDelayed(winResizeHandler, this)));
},
startup: function(){
// summary:
// Called automatically after postCreate if the component is already
// visible; otherwise, should be called manually once placed.
this.inherited(arguments);
if(this._started){ return; } // prevent double-triggering
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,
quirks = has("quirks") || has("ie") < 7;
this.headerScrollNode.style.height = bodyNode.style.marginTop = headerHeight + "px";
bodyNode.style.marginBottom = footerHeight + "px";
if(quirks){
// in IE6 and quirks mode, the "bottom" CSS property is ignored.
// We guard against negative values in case of issues with external CSS.
bodyNode.style.height = ""; // reset first
bodyNode.style.height =
Math.max((this.domNode.offsetHeight - headerHeight - footerHeight), 0) + "px";
if (footerHeight) {
// Work around additional glitch where IE 6 / quirks fails to update
// the position of the bottom-aligned footer; this jogs its memory.
footerNode.style.bottom = '1px';
setTimeout(function(){ footerNode.style.bottom = ''; }, 0);
}
}
if(!scrollbarWidth){
// Measure the browser's scrollbar width using a DIV we'll delete right away
var scrollDiv = put(document.body, "div.dgrid-scrollbar-measure");
scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
put(scrollDiv, "!");
// avoid crazy issues in IE7 only, with certain widgets inside
if(has("ie") === 7){ scrollbarWidth++; }
// add rules that can be used where scrollbar width/height is needed
this.addCssRule(".dgrid-scrollbar-width", "width: " + scrollbarWidth + "px");
this.addCssRule(".dgrid-scrollbar-height", "height: " + scrollbarWidth + "px");
if(scrollbarWidth != 17 && !quirks){
// for modern browsers, we can perform a one-time operation which adds
// a rule to account for scrollbar width in all grid headers.
this.addCssRule(".dgrid-header", "right: " + scrollbarWidth + "px");
// add another for RTL grids
this.addCssRule(".dgrid-rtl-nonwebkit .dgrid-header", "left: " + scrollbarWidth + "px");
}
}
if(quirks){
// old IE doesn't support left + right + width:auto; set width directly
headerNode.style.width = bodyNode.clientWidth + "px";
setTimeout(function(){
// sync up (after the browser catches up with the new width)
headerNode.scrollLeft = bodyNode.scrollLeft;
}, 0);
}
},
addCssRule: addExtraRule,
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 observers = this.observers,
i;
for(i in this._rowIdToObject){
if(this._rowIdToObject[i] != this.columns){
var rowElement = byId(i);
if(rowElement){
this.removeRow(rowElement, true);
}
}
}
// remove any store observers
for(i = 0;i < observers.length; i++){
var observer = observers[i];
observer && observer.cancel();
}
this.observers = [];
this.preload = null;
},
destroy: function(){
// summary:
// Destroys this grid
// remove any event listeners
if(this._listeners){ // guard against accidental subsequent calls to destroy
for(var i = this._listeners.length; i--;){
this._listeners[i].remove();
}
delete this._listeners;
}
this.cleanup();
// destroy DOM
put("!", this.domNode);
},
refresh: function(){
// summary:
// refreshes the contents of the grid
this.cleanup();
this._rowIdToObject = {};
this._autoId = 0;
// make sure all the content has been removed so it can be recreated
this.contentNode.innerHTML = "";
// Ensure scroll position always resets (especially for TouchScroll).
this.scrollTo({ x: 0, y: 0 });
},
newRow: function(object, before, to, options){
if(!before || before.parentNode){
var i = options.start + to;
var row = this.insertRow(object, before ? before.parentNode : this.contentNode, before, i, options);
put(row, ".ui-state-highlight");
setTimeout(function(){
put(row, "!ui-state-highlight");
}, 250);
return row;
}
},
adjustRowIndices: function(firstRow){
if(this.maintainOddEven){
// this traverses through rows to maintain odd/even classes on the rows when indexes shift;
var next = firstRow;
var rowIndex = next.rowIndex;
do{
if(next.rowIndex > -1){
// skip non-numeric, non-rows
if((next.className + ' ').indexOf("dgrid-row ") > -1){
put(next, '.' + (rowIndex % 2 == 1 ? oddClass : evenClass) + '!' + (rowIndex % 2 == 0 ? oddClass : evenClass));
}
next.rowIndex = rowIndex++;
}
}while((next = next.nextSibling) && next.rowIndex != rowIndex);
}
},
renderArray: function(results, beforeNode, options){
// summary:
// This renders an array or collection of objects as rows in the grid, before the
// given node. This will listen for changes in the collection if an observe method
// is available (as it should be if it comes from an Observable data store).
options = options || {};
var self = this,
start = options.start || 0,
row, rows, container;
if(!beforeNode){
this._lastCollection = results;
}
if(results.observe){
// observe the results for changes
var observerIndex = this.observers.push(results.observe(function(object, from, to){
var firstRow, nextNode;
// a change in the data took place
if(from > -1 && rows[from]){
// remove from old slot
row = rows.splice(from, 1)[0];
// check to make the sure the node is still there before we try to remove it, (in case it was moved to a different place in the DOM)
if(row.parentNode == container){
firstRow = row.nextSibling;
if(firstRow){ // it's possible for this to have been already removed if it is in overlapping query results
if(from != to){ // if from and to are identical, it is an in-place update and we don't want to alter the rowIndex at all
firstRow.rowIndex--; // adjust the rowIndex so adjustRowIndices has the right starting point
}
}
self.removeRow(row); // now remove
}
// the removal of rows could cause us to need to page in more items
if(self._processScroll){
self._processScroll();
}
}
if(to > -1){
// Add to new slot (either before an existing row, or at the end)
// First determine the DOM node that this should be placed before.
nextNode = rows[to];
if(!nextNode){
nextNode = rows[to - 1];
if(nextNode){
// Make sure to skip connected nodes, so we don't accidentally
// insert a row in between a parent and its children.
nextNode = (nextNode.connected || nextNode).nextSibling;
}
}
row = self.newRow(object, nextNode, to, options);
if(row){
row.observerIndex = observerIndex;
rows.splice(to, 0, row);
if(!firstRow || to < from){
// the inserted row is first, so we update firstRow to point to it
var previous = row.previousSibling;
// if we are not in sync with the previous row, roll the firstRow back one so adjustRowIndices can sync everything back up.
firstRow = !previous || previous.rowIndex + 1 == row.rowIndex || row.rowIndex == 0 ?
row : previous;
}
}
options.count++;
}
from != to && firstRow && self.adjustRowIndices(firstRow);
}, true)) - 1;
}
var rowsFragment = document.createDocumentFragment();
// now render the results
if(results.map){
rows = results.map(mapEach, console.error);
if(rows.then){
return rows.then(whenDone);
}
}else{
rows = [];
for(var i = 0, l = results.length; i < l; i++){
rows[i] = mapEach(results[i]);
}
}
var lastRow;
function mapEach(object){
lastRow = self.insertRow(object, rowsFragment, null, start++, options);
lastRow.observerIndex = observerIndex;
return lastRow;
}
function whenDone(resolvedRows){
container = beforeNode ? beforeNode.parentNode : self.contentNode;
if(container){
container.insertBefore(rowsFragment, beforeNode || null);
lastRow = resolvedRows[resolvedRows.length - 1];
lastRow && self.adjustRowIndices(lastRow);
}
return (rows = resolvedRows);
}
return whenDone(rows);
},
renderHeader: function(){
// no-op in a plain list
},
_autoId: 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 parentId = options.parentId,
id = this.id + "-row-" + (parentId ? parentId + "-" : "") +
((this.store && this.store.getIdentity) ?
this.store.getIdentity(object) : this._autoId++),
row = byId(id),
previousRow = row && row.previousSibling;
if(!row || // we must create a row if it doesn't exist, or if it previously belonged to a different container
(beforeNode && row.parentNode != beforeNode.parentNode)){
if(row){// if it existed elsewhere in the DOM, we will remove it, so we can recreate it
this.removeRow(row);
}
row = this.renderRow(object, options);
row.className = (row.className || "") + " ui-state-default dgrid-row " + (i % 2 == 1 ? oddClass : evenClass);
// get the row id for easy retrieval
this._rowIdToObject[row.id = id] = object;
}
parent.insertBefore(row, beforeNode || null);
if(previousRow){
// 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);
}
row.rowIndex = i;
return row;
},
renderRow: function(value, options){
// summary:
// Responsible for returning the DOM for a single row in the grid.
return put("div", "" + value);
},
removeRow: function(rowElement, justCleanup){
// 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.
// justCleanup: 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.
rowElement = rowElement.element || rowElement;
delete this._rowIdToObject[rowElement.id];
if(!justCleanup){
put(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){
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 store item
id = this.store.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));
},
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 === 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.
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.
return this.row(this._move(row, steps || 1, "dgrid-row", visible));
},
scrollTo: has("touch") ? function(){
// If TouchScroll is the superclass, defer to its implementation.
return this.inherited(arguments);
} : function(options){
// No TouchScroll; simple implementation which sets scrollLeft/Top.
if(typeof options.x !== "undefined"){
this.bodyNode.scrollLeft = options.x;
}
if(typeof options.y !== "undefined"){
this.bodyNode.scrollTop = options.y;
}
},
getScrollPosition: has("touch") ? function(){
// If TouchScroll is the superclass, defer to its implementation.
return this.inherited(arguments);
} : function(){
// No TouchScroll; return based on scrollLeft/Top.
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 attribute 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 :
[{attribute: property, descending: descending}];
this.refresh();
if(this._lastCollection){
if(property.length){
// if an array was passed in, flatten to just first sort attribute
// for default array sort logic
if(typeof property != "string"){
descending = property[0].descending;
property = property[0].attribute;
}
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);
}
},
// TODO: remove the following two (and rename _sort to sort) in 1.0
sort: function(property, descending){
kernel.deprecated("sort(...)", 'use set("sort", ...) instead', "dgrid 1.0");
this.set("sort", property, descending);
},
_getSort: function(){
return this._sort;
},
_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
put(headerNode, (show ? "!" : ".") + "dgrid-header-hidden");
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;
}
},
setShowHeader: function(show){
kernel.deprecated("setShowHeader(...)", 'use set("showHeader", ...) instead', "dgrid 1.0");
this.set("showHeader", show);
},
_setShowFooter: function(show){
this.showFooter = show;
// add/remove class which has styles for hiding footer
put(this.footerNode, (show ? "!" : ".") + "dgrid-footer-hidden");
this.resize(); // to account for (dis)appearance of footer
}
});
});