]> git.proxmox.com Git - extjs.git/blob - extjs/classic/classic/test/specs/grid/plugin/BufferedRenderer.js
add extjs 6.0.1 sources
[extjs.git] / extjs / classic / classic / test / specs / grid / plugin / BufferedRenderer.js
1 describe('Ext.grid.plugin.BufferedRenderer', function () {
2 var store, grid, tree, view, plugin,
3 synchronousLoad = true,
4 proxyStoreLoad = Ext.data.ProxyStore.prototype.load,
5 loadStore,
6 treeStoreLoad = Ext.data.TreeStore.prototype.load,
7 loadTreeStore,
8 itIE10p = Ext.isIE9m ? xit : it;
9
10 function createData(total, variableRowHeight, asymmetricRowHeight) {
11 var data = [],
12 i, len, n;
13
14 for (i = 0, len = total || 100; i < len; i++) {
15 n = i + 1;
16
17 data.push({
18 field1: variableRowHeight ? ('<div style="height:' + Ext.Number.randomInt(20, 40)+ 'px">' + n + '</div>') : asymmetricRowHeight ? 40 : n,
19 field2: n,
20 field3: n,
21 field4: n,
22 field5: n
23 });
24 }
25
26 return data;
27 }
28
29 function makeData(n, start) {
30 start = start || 1;
31
32 var data = [],
33 limit = start + n,
34 i;
35
36 for (i = start; i < limit; ++i) {
37 data.push({
38 id: i,
39 name: 'name' + i
40 });
41 }
42 return data;
43 }
44
45 function getData(start, limit) {
46 var end = start + limit,
47 recs = [],
48 i;
49
50 for (i = start; i < end; ++i) {
51 recs.push({
52 id: i,
53 namee: 'name' + i
54 });
55 }
56 return recs;
57 }
58
59 function satisfyRequests(total) {
60 var requests = Ext.Ajax.mockGetAllRequests(),
61 empty = total === 0,
62 request, params, data;
63
64 while (requests.length) {
65 request = requests[0];
66
67 params = request.options.params;
68 data = getData(empty ? 0 : params.start, empty ? 0 : params.limit);
69
70 Ext.Ajax.mockComplete({
71 status: 200,
72 responseText: Ext.encode({
73 total: (total || empty) ? total : 5000,
74 data: data
75 })
76 });
77
78 requests = Ext.Ajax.mockGetAllRequests();
79 }
80 }
81
82 function makeGrid(gridCfg, storeCfg) {
83 if (!storeCfg || !storeCfg.store) {
84 store = new Ext.data.Store(Ext.apply({
85 fields: ['name', 'email', 'phone'],
86 groupField: 'name',
87 data: [
88 {'name': 'Lisa', 'email': 'lisa@simpsons.com', 'phone': '555-111-1224', 'age': 14},
89 {'name': 'Lisa', 'email': 'aunt_lisa@simpsons.com', 'phone': '555-111-1274', 'age': 34},
90 {'name': 'Bart', 'email': 'bart@simpsons.com', 'phone': '555-222-1234', 'age': 12},
91 {'name': 'Homer', 'email': 'homer@simpsons.com', 'phone': '555-222-1244', 'age': 44},
92 {'name': 'Marge', 'email': 'marge@simpsons.com', 'phone': '555-222-1254', 'age': 41}
93 ],
94 autoDestroy: true
95 }, storeCfg));
96 } else {
97 store = storeCfg.store;
98 }
99
100 grid = new Ext.grid.Panel(Ext.apply({
101 columns: [
102 {header: 'Name', dataIndex: 'name', editor: 'textfield'},
103 {header: 'Email', dataIndex: 'email', flex:1,
104 editor: {
105 xtype: 'textfield',
106 allowBlank: false
107 }
108 },
109 {header: 'Phone', dataIndex: 'phone', editor: 'textfield'},
110 {header: 'Age', dataIndex: 'age', editor: 'textfield'}
111 ],
112 store: store,
113 width: 200,
114 height: 400,
115 renderTo: Ext.getBody()
116 }, gridCfg));
117
118 view = grid.view;
119
120 // Extract the buffered renderer from a real TableView, the topmost one might be a Locking pseudo view
121 plugin = grid.down('tableview').bufferedRenderer;
122 }
123
124 function createTreeData(count) {
125 var i = 0,
126 j = 0,
127 children = [],
128 grandKids;
129
130 for (; i < count; i++) {
131 grandKids = [];
132
133 for (j = 1; j < 7; j++) {
134 grandKids.push({
135 treeData: 'Child of ' + i + ', number ' + j,
136 leaf: true
137 });
138 }
139
140 children.push({
141 treeData: i,
142 children: grandKids
143 });
144 }
145
146 return {
147 children: children
148 };
149 }
150
151 function makeTree(treeCfg, count) {
152 store = new Ext.data.TreeStore({
153 fields: ['treeData'],
154 root: createTreeData(count || 20)
155 });
156
157 tree = new Ext.tree.Panel(Ext.apply({
158 columns: [{
159 xtype: 'treecolumn',
160 text: 'Tree Column',
161 width: 300,
162 dataIndex: 'treeData'
163 }],
164 height: 800,
165 width: 500,
166 store: store,
167 rootVisible: false,
168 animate: false,
169 renderTo: Ext.getBody()
170 }, treeCfg || {}));
171
172 view = tree.view;
173 }
174
175 function completeWithData(data) {
176 Ext.Ajax.mockComplete({
177 status: 200,
178 responseText: Ext.JSON.encode(data)
179 });
180 }
181
182 beforeEach(function () {
183 // Override so that we can control asynchronous loading
184 loadStore = Ext.data.ProxyStore.prototype.load = function() {
185 proxyStoreLoad.apply(this, arguments);
186 if (synchronousLoad) {
187 this.flushLoad.apply(this, arguments);
188 }
189 return this;
190 };
191 loadTreeStore = Ext.data.TreeStore.prototype.load = function() {
192 treeStoreLoad.apply(this, arguments);
193 if (synchronousLoad) {
194 this.flushLoad.apply(this, arguments);
195 }
196 return this;
197 };
198
199 MockAjaxManager.addMethods();
200 });
201
202 afterEach(function () {
203 // Undo the overrides.
204 Ext.data.ProxyStore.prototype.load = proxyStoreLoad;
205 Ext.data.TreeStore.prototype.load = treeStoreLoad;
206
207 MockAjaxManager.removeMethods();
208
209 if (grid) {
210 Ext.destroy(grid);
211 }
212
213 if (tree) {
214 Ext.destroy(tree);
215 }
216
217 store = grid = tree = view = plugin = null;
218 });
219
220 describe('updateing scroller when changing width', function() {
221 it('should update the horizontal scroll range', function() {
222 makeGrid();
223 var scroller = view.getScrollable(),
224 maxX = scroller.getMaxPosition().x;
225
226 grid.setWidth(grid.getWidth() - 100);
227
228 // The scroll range should have increased
229 expect(scroller.getMaxPosition().x).toBe(maxX + 100);
230 });
231 });
232
233 describe('autogenerating the plugin', function () {
234 var BR = Ext.grid.plugin.BufferedRenderer;
235
236 it('should create an instance by default', function () {
237 makeGrid();
238
239 expect(plugin instanceof BR).toBe(true);
240 });
241
242 it('should not create an instance when turned off', function () {
243 makeGrid({
244 bufferedRenderer: false
245 });
246
247 expect(plugin instanceof BR).toBe(false);
248 expect(plugin).toBeUndefined();
249 });
250
251 describe('locking grids', function () {
252 var normal, locked;
253
254 afterEach(function () {
255 normal = locked = null;
256 });
257
258 describe('init', function () {
259 function runTests(useBR) {
260 var not = useBR ? 'not' : '';
261
262 describe('buffered rendering = ' + useBR, function () {
263 beforeEach(function () {
264 makeGrid({
265 bufferedRenderer: useBR,
266 columns: [
267 {header: 'Name', dataIndex: 'name', editor: 'textfield'},
268 {header: 'Phone', dataIndex: 'phone', editor: 'textfield', locked: true},
269 {header: 'Age', dataIndex: 'age', editor: 'textfield'}
270 ]
271 });
272
273 normal = grid.normalGrid.bufferedRenderer;
274 locked = grid.lockedGrid.bufferedRenderer;
275 });
276
277 it('should ' + not + ' create an instance on the ownerGrid for locking grids', function () {
278 plugin = grid.bufferedRenderer;
279 expect(plugin instanceof BR).toBe(false);
280 });
281
282 it('should ' + not + ' create an instance by default on each child grid for locking grids', function () {
283 expect(normal instanceof BR).toBe(useBR);
284 expect(locked instanceof BR).toBe(useBR);
285 });
286 });
287 }
288
289 runTests(true);
290 runTests(false);
291 });
292
293 describe('syncing locking partners when scrolling', function () {
294 beforeEach(function () {
295 makeGrid({
296 columns: [
297 {header: 'Name', dataIndex: 'name', editor: 'textfield', locked: true},
298 {header: 'Email', dataIndex: 'email', flex:1,
299 editor: {
300 xtype: 'textfield',
301 allowBlank: false
302 }
303 },
304 {header: 'Phone', dataIndex: 'phone', editor: 'textfield'},
305 {header: 'Age', dataIndex: 'age', editor: 'textfield'}
306 ]
307 }, {
308 data: makeData(1000)
309 });
310
311 normal = grid.normalGrid.bufferedRenderer;
312 locked = grid.lockedGrid.bufferedRenderer;
313 });
314
315 it('should fetch the range', function () {
316 spyOn(normal, 'onRangeFetched').andCallThrough();
317 spyOn(locked, 'onRangeFetched').andCallThrough();
318
319 normal.scrollTo(500);
320
321 expect(normal.onRangeFetched).toHaveBeenCalled();
322 expect(locked.onRangeFetched).toHaveBeenCalled();
323 });
324
325 it('should sync the view els', function () {
326 normal.scrollTo(500);
327
328 expect(normal.bodyTop).toBe(locked.bodyTop);
329 });
330 });
331
332 describe('Load requests during scrolling', function () {
333 var scrollToLoadBufferValue,
334 scrollTimer,
335 Person = Ext.define(null, {
336 extend: 'Ext.data.Model',
337 fields: ['name'],
338 proxy: {
339 type: 'ajax',
340 url: '/foo',
341 reader: {
342 rootProperty: 'data'
343 }
344 }
345 });
346
347 function scrollTheGrid() {
348 // Scroll incrementally until the correct starting point is found
349 if (view && !view.destroyed) {
350 view.scrollBy(null, 100);
351 }
352 scrollTimer = 0;
353 }
354
355 beforeEach(function () {
356 scrollToLoadBufferValue = Ext.grid.plugin.BufferedRenderer.prototype.scrollToLoadBuffer;
357
358 // Make the timeout from attemptLoad call to the doAttemptLoad ten seconds
359 // The doAttemptLoad DelayedTask should not fire between scroll events which take place
360 // every 50 milliseconds
361 Ext.grid.plugin.BufferedRenderer.prototype.scrollToLoadBuffer = 10000;
362 makeGrid({
363 columns: [
364 {header: 'Name', dataIndex: 'name', editor: 'textfield'},
365 {header: 'Phone', dataIndex: 'phone', editor: 'textfield', locked: true},
366 {header: 'Age', dataIndex: 'age', editor: 'textfield'}
367 ]
368 }, {
369 store: new Ext.data.BufferedStore({
370 model: Person,
371 leadingBufferZone: 300,
372 pageSize: 100
373 })
374 });
375
376 // Tell the BufferedRenderer that there are 1000 rows.
377 // We plan not to satisfy any requests from now on so that
378 // on scroll, an attemptLoad call will be made.
379 // the timeout to doAttemptLoad should not expire during the scroll operation
380 store.load();
381 completeWithData({
382 total: 1000,
383 data: makeData(100)
384 });
385 completeWithData({
386 total: 1000,
387 data: makeData(100, 101)
388 });
389 completeWithData({
390 total: 1000,
391 data: makeData(100, 201)
392 });
393 completeWithData({
394 total: 1000,
395 data: makeData(100, 301)
396 });
397 });
398 afterEach(function() {
399 Ext.grid.plugin.BufferedRenderer.prototype.scrollToLoadBuffer = scrollToLoadBufferValue;
400 });
401 it('should not have time between scroll events to fire off any requests', function() {
402 spyOn(plugin, 'doAttemptLoad');
403
404 // Scroll to close enough to the end in a slow manner, 50ms between each scroll.
405 // The doAttemptLoad timer should not timeout and fire off a page request between each scroll.
406 waitsFor(function() {
407 if (plugin.getLastVisibleRowIndex() <= 995) {
408 if (!scrollTimer) {
409 scrollTimer = setTimeout(scrollTheGrid, 50);
410 }
411 } else {
412 return true;
413 }
414 }, 'grid to scroll to end', Ext.isIE ? 40000 : 20000);
415
416 // The atteptLoad timer must never have fired during the scroll.
417 runs(function() {
418 expect(plugin.doAttemptLoad.callCount).toBe(0);
419 });
420 });
421 });
422 });
423 });
424
425 describe("Less data than the computed view size", function() {
426 it("should add new rows at top while scrolled to bottom", function() {
427 var Person = Ext.define(null, {
428 extend: 'Ext.data.Model',
429 fields: ['name'],
430 proxy: {
431 type: 'ajax',
432 url: '/foo',
433 reader: {
434 rootProperty: 'data'
435 }
436 }
437 });
438
439 store = new Ext.data.Store({
440 model: Person,
441 data: makeData(12)
442 });
443
444 grid = new Ext.grid.Panel({
445 width: 600,
446 height: 305,
447 store: store,
448 deferRowRender: false,
449 columns: [{
450 dataIndex: 'id',
451 renderer: function(v) {
452 return '<span style="line-height:25px">' + v + '</span>';
453 },
454 producesHTML: true
455 }, {
456 dataIndex: 'name'
457 }],
458 renderTo: Ext.getBody()
459 });
460 view = grid.getView();
461
462 // Wait until known correct condition is met.
463 // Timeout === test failure.
464 waitsFor(function() {
465 if (view.all.endIndex === store.getCount() - 1) {
466 return true;
467 }
468 else {
469 // Scroll incrementally until the correct starting point is found
470 view.scrollBy(null, 10);
471 }
472 }, 'view is scrolled to the last record');
473
474 runs(function() {
475 store.insert(0, {id: 666, name: 'Old Nick'});
476 view.scrollTo(0, 0);
477
478 var r0 = view.all.item(0, true);
479
480 // Must have been rendered
481 expect(r0).not.toBeNull();
482
483 // The new row zero must have been rendered.
484 expect((r0.innerText || r0.textContent).replace(/\n/g,'').replace(/\r/g,'')).toBe("666Old Nick");
485 });
486 });
487 });
488
489 describe("basic functionality with a buffered store", function() {
490 it("should render rows in order", function() {
491 var Person = Ext.define(null, {
492 extend: 'Ext.data.Model',
493 fields: ['name'],
494 proxy: {
495 type: 'ajax',
496 url: '/foo',
497 reader: {
498 rootProperty: 'data'
499 }
500 }
501 });
502
503 store = new Ext.data.BufferedStore({
504 model: Person,
505 leadingBufferZone: 300,
506 pageSize: 100
507 });
508
509 grid = new Ext.grid.Panel({
510 width: 800,
511 height: 500,
512 store: store,
513 deferRowRender: false,
514 columns: [{
515 dataIndex: 'id'
516 }, {
517 dataIndex: 'name'
518 }],
519 renderTo: Ext.getBody()
520 });
521 store.load();
522 completeWithData({
523 total: 5000,
524 data: makeData(100)
525 });
526 completeWithData({
527 total: 5000,
528 data: makeData(100, 101)
529 });
530 completeWithData({
531 total: 5000,
532 data: makeData(100, 201)
533 });
534 completeWithData({
535 total: 5000,
536 data: makeData(100, 301)
537 });
538 var view = grid.getView(),
539 rows = view.all;
540
541 // Wait until known correct condition is met.
542 // Timeout === test failure.
543 waitsFor(function() {
544 if (rows.startIndex <= 100 && rows.endIndex >= 100) {
545 return true;
546 }
547 else {
548 // Scroll incrementally until the correct starting point is found
549 view.scrollBy(null, 10);
550 }
551 }, 'View to scroll record id 100 into the rendered block', 20000);
552
553 runs(function() {
554 var nodes = view.getNodes(),
555 len = nodes.length,
556 offset = rows.startIndex + 1,
557 i, rec;
558
559 expect(len).toBe(view.bufferedRenderer.viewSize);
560 for (i = 0; i < len; ++i) {
561 rec = view.getRecord(nodes[i]);
562 expect(rec.getId()).toBe(i + offset);
563 }
564
565 });
566 });
567
568 // EXTJS-16140
569 it("should scroll to the bottom of a locking grid with no error", function() {
570 var Person = Ext.define(null, {
571 extend: 'Ext.data.Model',
572 fields: ['name'],
573 proxy: {
574 type: 'ajax',
575 url: '/foo',
576 reader: {
577 rootProperty: 'data'
578 }
579 }
580 }),
581 lockedView,
582 normalView,
583 maxScroll;
584
585 store = new Ext.data.BufferedStore({
586 model: Person,
587 leadingBufferZone: 300,
588 pageSize: 100
589 });
590
591 grid = new Ext.grid.Panel({
592 width: 800,
593 height: 500,
594 store: store,
595 deferRowRender: false,
596 columns: [{
597 dataIndex: 'id',
598 locked: true
599 }, {
600 dataIndex: 'name'
601 }],
602 renderTo: Ext.getBody()
603 });
604 lockedView = grid.lockedGrid.view;
605 normalView = grid.normalGrid.view;
606
607 // Make the buffered renderers respond IMMEDIATELY to the scroll event, so that
608 // the waits(100) will be enough to wait for the newly scrolled-to region to be rendered.
609 lockedView.bufferedRenderer.scrollToLoadBuffer = normalView.bufferedRenderer.scrollToLoadBuffer = 0;
610
611 store.load();
612 completeWithData({
613 total: 5000,
614 data: makeData(100)
615 });
616
617 maxScroll = normalView.getScrollable().getMaxPosition().y;
618 normalView.setScrollY(maxScroll);
619
620 // Important: Simulate Ajax delay before returning data
621 waits(100);
622 runs(function() {
623 satisfyRequests();
624 });
625
626 // Both views must render up to the last row with no error thrown
627 waitsFor(function() {
628 return lockedView.all.endIndex === 4999 && normalView.all.endIndex === 4999;
629 });
630 });
631
632 // EXTJS-17053
633 it('should maintain synchronization when scrolling locked, variable row height grid with keyboard', function() {
634 store = Ext.create('Ext.data.Store', {
635 idProperty: 'index',
636 fields: ['index', 'name', 'email', 'phone', 'isActive', 'eyeColor', 'company', 'gender'],
637 autoLoad: true,
638 data: [{
639 "_id": "54ff69d95aa43325cc4eee9f",
640 "index": 0,
641 "guid": "48c42a75-09a7-4671-86a1-e9a486bee6ab",
642 "isActive": true,
643 "balance": "$3,457.90",
644 "age": 20,
645 "eyeColor": "brown",
646 "name": "Louella Morrison",
647 "gender": "female",
648 "company": "ZBOO",
649 "email": "louellamorrison@zboo.com",
650 "phone": "+1 (803) 483-2686"
651 }, {
652 "_id": "54ff69d918616dbc093ca6cd",
653 "index": 1,
654 "guid": "17b3f923-96b5-40c9-b3c1-721485e5bf80",
655 "isActive": true,
656 "balance": "$1,377.85",
657 "age": 20,
658 "eyeColor": "brown",
659 "name": "Madden Coffey",
660 "gender": "male",
661 "company": "COMDOM",
662 "email": "maddencoffey@comdom.com",
663 "phone": "+1 (850) 534-2345"
664 }, {
665 "_id": "54ff69d91a4917f9643178b0",
666 "index": 2,
667 "guid": "f62da049-ab17-49fd-9f4c-3439d33cbb93",
668 "isActive": false,
669 "balance": "$3,798.71",
670 "age": 22,
671 "eyeColor": "blue",
672 "name": "Chase Crawford",
673 "gender": "male",
674 "company": "PYRAMAX",
675 "email": "chasecrawford@pyramax.com",
676 "phone": "+1 (944) 494-2920"
677 }, {
678 "_id": "54ff69d9a63b6103f9e1530f",
679 "index": 3,
680 "guid": "3640c402-f878-4f97-bbda-564de91d2dde",
681 "isActive": true,
682 "balance": "$2,480.02",
683 "age": 39,
684 "eyeColor": "blue",
685 "name": "Johanna Pollard",
686 "gender": "female",
687 "company": "ZEROLOGY",
688 "email": "johannapollard@zerology.com",
689 "phone": "+1 (813) 512-3311"
690 }, {
691 "_id": "54ff69d9048722389ffde4bb",
692 "index": 4,
693 "guid": "361089ba-e6dc-4701-b316-c7e977b21cb2",
694 "isActive": true,
695 "balance": "$3,973.38",
696 "age": 20,
697 "eyeColor": "brown",
698 "name": "Malone Bentley",
699 "gender": "male",
700 "company": "DIGIPRINT",
701 "email": "malonebentley@digiprint.com",
702 "phone": "+1 (905) 435-2056"
703 }, {
704 "_id": "54ff69d9f75a66fb8e08b335",
705 "index": 5,
706 "guid": "4cec529a-232a-4d5f-bedc-6141adc9f2fa",
707 "isActive": true,
708 "balance": "$3,956.60",
709 "age": 21,
710 "eyeColor": "green",
711 "name": "Jennings Rodgers",
712 "gender": "male",
713 "company": "LUDAK",
714 "email": "jenningsrodgers@ludak.com",
715 "phone": "+1 (833) 543-2230"
716 }, {
717 "_id": "54ff69d974c1757e026721cb",
718 "index": 6,
719 "guid": "6ce0c1e1-adc6-4cd0-b9c9-e2214e63aa8f",
720 "isActive": true,
721 "balance": "$2,874.71",
722 "age": 40,
723 "eyeColor": "green",
724 "name": "Mosley Mcgowan",
725 "gender": "male",
726 "company": "AMTAP",
727 "email": "mosleymcgowan@amtap.com",
728 "phone": "+1 (990) 486-3229"
729 }, {
730 "_id": "54ff69d949a3873336e2e446",
731 "index": 7,
732 "guid": "0f88d544-4aa0-4a05-a236-363c64b40988",
733 "isActive": false,
734 "balance": "$3,277.11",
735 "age": 28,
736 "eyeColor": "green",
737 "name": "Patton Whitaker",
738 "gender": "male",
739 "company": "IMMUNICS",
740 "email": "pattonwhitaker@immunics.com",
741 "phone": "+1 (944) 557-2615"
742 }, {
743 "_id": "54ff69d917f9642ab989e999",
744 "index": 8,
745 "guid": "6577bb0c-f7ed-495f-a3db-7f7d35f6fe49",
746 "isActive": false,
747 "balance": "$1,091.47",
748 "age": 35,
749 "eyeColor": "green",
750 "name": "Neal Rivera",
751 "gender": "male",
752 "company": "NETILITY",
753 "email": "nealrivera@netility.com",
754 "phone": "+1 (816) 590-2358"
755 }, {
756 "_id": "54ff69d94573c1c04f0a9940",
757 "index": 9,
758 "guid": "29be9274-d3cb-461f-a565-2374768689df",
759 "isActive": false,
760 "balance": "$1,885.75",
761 "age": 25,
762 "eyeColor": "brown",
763 "name": "May Barron",
764 "gender": "male",
765 "company": "TELEPARK",
766 "email": "maybarron@telepark.com",
767 "phone": "+1 (958) 486-2915"
768 }, {
769 "_id": "54ff69d9c0e2f4aee9180984",
770 "index": 10,
771 "guid": "479320d7-70ef-4f55-99b9-f3ef3a76b72e",
772 "isActive": false,
773 "balance": "$2,031.57",
774 "age": 36,
775 "eyeColor": "brown",
776 "name": "Janna Howell",
777 "gender": "female",
778 "company": "PYRAMIS",
779 "email": "jannahowell@pyramis.com",
780 "phone": "+1 (823) 423-2342"
781 }, {
782 "_id": "54ff69d96e7f08b674de7cb9",
783 "index": 11,
784 "guid": "9f6df0e4-b92c-4272-ad75-981305e9bb09",
785 "isActive": true,
786 "balance": "$2,279.79",
787 "age": 33,
788 "eyeColor": "green",
789 "name": "Nieves Short",
790 "gender": "male",
791 "company": "PETICULAR",
792 "email": "nievesshort@peticular.com",
793 "phone": "+1 (904) 556-3401"
794 }, {
795 "_id": "54ff69d9fb987df7ecf3d8ab",
796 "index": 12,
797 "guid": "32c80cfd-d880-48a6-a3b0-e687c1e52b30",
798 "isActive": false,
799 "balance": "$3,830.03",
800 "age": 26,
801 "eyeColor": "green",
802 "name": "Tania Gillespie",
803 "gender": "female",
804 "company": "AUSTEX",
805 "email": "taniagillespie@austex.com",
806 "phone": "+1 (969) 527-3521"
807 }, {
808 "_id": "54ff69d9f586c6e7e9a08b4b",
809 "index": 13,
810 "guid": "e6a006c4-1d5e-4a09-9dd6-496426fc4982",
811 "isActive": false,
812 "balance": "$3,411.76",
813 "age": 32,
814 "eyeColor": "blue",
815 "name": "Joyner Suarez",
816 "gender": "male",
817 "company": "GEOFARM",
818 "email": "joynersuarez@geofarm.com",
819 "phone": "+1 (879) 571-3671"
820 }, {
821 "_id": "54ff69d94eaa44d993966048",
822 "index": 14,
823 "guid": "00927bfa-dfd7-4850-bfd7-a2c25c476327",
824 "isActive": true,
825 "balance": "$2,402.44",
826 "age": 35,
827 "eyeColor": "brown",
828 "name": "Hull Cooley",
829 "gender": "male",
830 "company": "ENERVATE",
831 "email": "hullcooley@enervate.com",
832 "phone": "+1 (910) 476-3577"
833 }, {
834 "_id": "54ff69d9b341bfef697e75ac",
835 "index": 15,
836 "guid": "cd6a241c-35a4-4038-ae01-d32640719dc7",
837 "isActive": false,
838 "balance": "$2,889.76",
839 "age": 28,
840 "eyeColor": "green",
841 "name": "Cortez Fleming",
842 "gender": "male",
843 "company": "CIPROMOX",
844 "email": "cortezfleming@cipromox.com",
845 "phone": "+1 (964) 460-3159"
846 }, {
847 "_id": "54ff69d9a1f835d92cb8a017",
848 "index": 16,
849 "guid": "45ad04ed-c154-4f16-a60b-d091e2ab8c13",
850 "isActive": true,
851 "balance": "$1,994.50",
852 "age": 21,
853 "eyeColor": "green",
854 "name": "Hazel Rodriguez",
855 "gender": "female",
856 "company": "SILODYNE",
857 "email": "hazelrodriguez@silodyne.com",
858 "phone": "+1 (962) 492-3107"
859 }, {
860 "_id": "54ff69d93989153e00dc5f02",
861 "index": 17,
862 "guid": "b2a702d1-b47e-4f2b-98f2-4ab2e70b126f",
863 "isActive": true,
864 "balance": "$2,826.88",
865 "age": 30,
866 "eyeColor": "green",
867 "name": "Keri Pearson",
868 "gender": "female",
869 "company": "CENTICE",
870 "email": "keripearson@centice.com",
871 "phone": "+1 (858) 417-3541"
872 }, {
873 "_id": "54ff69d9377beb85134e2761",
874 "index": 18,
875 "guid": "9bbb9bd7-d517-4bbb-a9b6-76f4fab0e5ab",
876 "isActive": false,
877 "balance": "$1,119.17",
878 "age": 24,
879 "eyeColor": "brown",
880 "name": "Daisy Mcconnell",
881 "gender": "female",
882 "company": "MONDICIL",
883 "email": "daisymcconnell@mondicil.com",
884 "phone": "+1 (836) 470-3995"
885 }, {
886 "_id": "54ff69d999e3637d8287287f",
887 "index": 19,
888 "guid": "c2dbece0-7554-4666-9378-805a625789db",
889 "isActive": false,
890 "balance": "$3,855.83",
891 "age": 30,
892 "eyeColor": "blue",
893 "name": "Michelle Espinoza",
894 "gender": "female",
895 "company": "STEELFAB",
896 "email": "michelleespinoza@steelfab.com",
897 "phone": "+1 (882) 508-2376"
898 }, {
899 "_id": "54ff69d93826cfb24af88797",
900 "index": 20,
901 "guid": "20be5f38-dc45-4a2f-8889-e9ba5ade8bed",
902 "isActive": false,
903 "balance": "$1,366.06",
904 "age": 39,
905 "eyeColor": "blue",
906 "name": "Wall Sears",
907 "gender": "male",
908 "company": "PLAYCE",
909 "email": "wallsears@playce.com",
910 "phone": "+1 (846) 505-3782"
911 }, {
912 "_id": "54ff69d97f6a23afdd06849c",
913 "index": 21,
914 "guid": "66fc61ba-38ef-4d49-862d-eb7d5fa68245",
915 "isActive": false,
916 "balance": "$2,379.73",
917 "age": 20,
918 "eyeColor": "green",
919 "name": "Caldwell Cain",
920 "gender": "male",
921 "company": "ORBIXTAR",
922 "email": "caldwellcain@orbixtar.com",
923 "phone": "+1 (831) 456-3297"
924 }, {
925 "_id": "54ff69d970154f7ac17b1438",
926 "index": 22,
927 "guid": "85b6d275-3bff-4475-8c42-0c4f8ec2d385",
928 "isActive": true,
929 "balance": "$2,572.21",
930 "age": 20,
931 "eyeColor": "green",
932 "name": "Viola Mckay",
933 "gender": "female",
934 "company": "SPRINGBEE",
935 "email": "violamckay@springbee.com",
936 "phone": "+1 (901) 412-3479"
937 }, {
938 "_id": "54ff69d98ac67b26bce2d4a8",
939 "index": 23,
940 "guid": "1d7460af-09a7-479a-8476-c3187a85ba8d",
941 "isActive": false,
942 "balance": "$2,467.39",
943 "age": 40,
944 "eyeColor": "blue",
945 "name": "Gutierrez Conway",
946 "gender": "male",
947 "company": "ULTRASURE",
948 "email": "gutierrezconway@ultrasure.com",
949 "phone": "+1 (939) 438-3340"
950 }, {
951 "_id": "54ff69d93a9b81bdb7af6473",
952 "index": 24,
953 "guid": "2da97cff-a16c-47a2-b9b0-d05d7ce8655d",
954 "isActive": false,
955 "balance": "$2,496.66",
956 "age": 20,
957 "eyeColor": "brown",
958 "name": "Chandler Schmidt",
959 "gender": "male",
960 "company": "ZANITY",
961 "email": "chandlerschmidt@zanity.com",
962 "phone": "+1 (822) 461-2247"
963 }, {
964 "_id": "54ff69d99e5b0a9b8db8da2b",
965 "index": 26,
966 "guid": "b8081e85-d0a9-4575-9fdd-83baa0cfe79b",
967 "isActive": false,
968 "balance": "$3,044.12",
969 "age": 21,
970 "eyeColor": "brown",
971 "name": "Bradshaw Ware",
972 "gender": "male",
973 "company": "BEDDER",
974 "email": "bradshawware@bedder.com",
975 "phone": "+1 (932) 496-3718"
976 }, {
977 "_id": "54ff69d9a9827e6a14480e37",
978 "index": 27,
979 "guid": "f0d49a25-fa17-4319-9f6a-9e88bb974755",
980 "isActive": true,
981 "balance": "$1,799.46",
982 "age": 38,
983 "eyeColor": "green",
984 "name": "Krystal Bush",
985 "gender": "female",
986 "company": "ZOLAREX",
987 "email": "krystalbush@zolarex.com",
988 "phone": "+1 (928) 577-3693"
989 }, {
990 "_id": "54ff69d97e57e2cab6272464",
991 "index": 28,
992 "guid": "40860305-ba5a-44ec-9783-21d4a1a4927b",
993 "isActive": true,
994 "balance": "$1,856.24",
995 "age": 20,
996 "eyeColor": "green",
997 "name": "Madeline Grant",
998 "gender": "female",
999 "company": "APEXIA",
1000 "email": "madelinegrant@apexia.com",
1001 "phone": "+1 (895) 521-2877"
1002 }, {
1003 "_id": "54ff69d9431984c9fdb6f25a",
1004 "index": 29,
1005 "guid": "36d78b3f-a9dd-4180-bb66-c12a1147e92d",
1006 "isActive": true,
1007 "balance": "$3,971.70",
1008 "age": 40,
1009 "eyeColor": "brown",
1010 "name": "Flora Ortiz",
1011 "gender": "female",
1012 "company": "ACCUSAGE",
1013 "email": "floraortiz@accusage.com",
1014 "phone": "+1 (992) 561-2107"
1015 }, {
1016 "_id": "54ff69d9c7c52140a4572361",
1017 "index": 30,
1018 "guid": "ba91387f-cab7-4487-a650-1bd94a650306",
1019 "isActive": false,
1020 "balance": "$1,408.31",
1021 "age": 37,
1022 "eyeColor": "brown",
1023 "name": "Bobbie Atkins",
1024 "gender": "female",
1025 "company": "GRAINSPOT",
1026 "email": "bobbieatkins@grainspot.com",
1027 "phone": "+1 (840) 497-2564"
1028 }, {
1029 "_id": "54ff69d982ac77cad23cb28d",
1030 "index": 31,
1031 "guid": "7068b1b5-cb81-4127-b252-ccc3ff8d3b5a",
1032 "isActive": true,
1033 "balance": "$3,375.36",
1034 "age": 39,
1035 "eyeColor": "brown",
1036 "name": "Mayer Osborne",
1037 "gender": "male",
1038 "company": "DEEPENDS",
1039 "email": "mayerosborne@deepends.com",
1040 "phone": "+1 (952) 498-3050"
1041 }, {
1042 "_id": "54ff69d99aefdc9cdd70a0df",
1043 "index": 32,
1044 "guid": "087771d5-4f39-46aa-bad7-7a051e807770",
1045 "isActive": true,
1046 "balance": "$3,345.55",
1047 "age": 38,
1048 "eyeColor": "blue",
1049 "name": "Sullivan Gibson",
1050 "gender": "male",
1051 "company": "AQUASURE",
1052 "email": "sullivangibson@aquasure.com",
1053 "phone": "+1 (833) 463-3464"
1054 }, {
1055 "_id": "54ff69d970a46678753362fc",
1056 "index": 33,
1057 "guid": "23e28ddc-a102-4f7c-b53b-4e3b8e45c64e",
1058 "isActive": false,
1059 "balance": "$2,521.22",
1060 "age": 28,
1061 "eyeColor": "green",
1062 "name": "Nikki Whitley",
1063 "gender": "female",
1064 "company": "FUTURIS",
1065 "email": "nikkiwhitley@futuris.com",
1066 "phone": "+1 (883) 561-2974"
1067 }, {
1068 "_id": "54ff69d9f7ca2fa585ecbac6",
1069 "index": 34,
1070 "guid": "b711bd69-e672-48f5-8138-d3120bbedb40",
1071 "isActive": true,
1072 "balance": "$2,388.02",
1073 "age": 21,
1074 "eyeColor": "green",
1075 "name": "Juana Doyle",
1076 "gender": "female",
1077 "company": "HOUSEDOWN",
1078 "email": "juanadoyle@housedown.com",
1079 "phone": "+1 (847) 428-3271"
1080 }, {
1081 "_id": "54ff69d9295e29d7b81a31be",
1082 "index": 35,
1083 "guid": "d393a87f-0789-4762-8ab8-d0fb66fc0fcb",
1084 "isActive": true,
1085 "balance": "$1,126.53",
1086 "age": 32,
1087 "eyeColor": "green",
1088 "name": "Marci Shaffer",
1089 "gender": "female",
1090 "company": "TRASOLA",
1091 "email": "marcishaffer@trasola.com",
1092 "phone": "+1 (929) 502-2533"
1093 }, {
1094 "_id": "54ff69d9d8f81e674722a7d5",
1095 "index": 36,
1096 "guid": "281bbeea-5bef-4c37-8e2a-22481c9616c3",
1097 "isActive": false,
1098 "balance": "$2,616.22",
1099 "age": 26,
1100 "eyeColor": "green",
1101 "name": "Maynard Watts",
1102 "gender": "male",
1103 "company": "AEORA",
1104 "email": "maynardwatts@aeora.com",
1105 "phone": "+1 (896) 418-3281"
1106 }, {
1107 "_id": "54ff69d98f504a9e6c63dedb",
1108 "index": 37,
1109 "guid": "48698e76-8996-40d2-9abe-3fdd8fe98cf7",
1110 "isActive": true,
1111 "balance": "$3,441.08",
1112 "age": 21,
1113 "eyeColor": "brown",
1114 "name": "Ollie Mooney",
1115 "gender": "female",
1116 "company": "ACCUPRINT",
1117 "email": "olliemooney@accuprint.com",
1118 "phone": "+1 (811) 512-3357"
1119 }, {
1120 "_id": "54ff69d9fc55f5cd34359bf9",
1121 "index": 38,
1122 "guid": "495c9d9e-95d8-48a2-83b4-c0f314f7aa26",
1123 "isActive": true,
1124 "balance": "$1,134.21",
1125 "age": 30,
1126 "eyeColor": "green",
1127 "name": "Mattie Collins",
1128 "gender": "female",
1129 "company": "EARGO",
1130 "email": "mattiecollins@eargo.com",
1131 "phone": "+1 (800) 463-2474"
1132 }, {
1133 "_id": "54ff69d94692077fe1587d2b",
1134 "index": 39,
1135 "guid": "9c259004-63be-4056-b291-7989cd582d41",
1136 "isActive": true,
1137 "balance": "$1,429.64",
1138 "age": 23,
1139 "eyeColor": "brown",
1140 "name": "Weeks Mcdowell",
1141 "gender": "male",
1142 "company": "ORBAXTER",
1143 "email": "weeksmcdowell@orbaxter.com",
1144 "phone": "+1 (913) 562-2677"
1145 }, {
1146 "_id": "54ff69d95c44a1cefcb13a58",
1147 "index": 40,
1148 "guid": "44fa1564-9ef2-4503-b887-5f0f282b1bd3",
1149 "isActive": false,
1150 "balance": "$1,340.36",
1151 "age": 30,
1152 "eyeColor": "brown",
1153 "name": "Hammond Valencia",
1154 "gender": "male",
1155 "company": "IZZBY",
1156 "email": "hammondvalencia@izzby.com",
1157 "phone": "+1 (899) 577-2537"
1158 }, {
1159 "_id": "54ff69d9c513f5b3c8ba15d7",
1160 "index": 41,
1161 "guid": "70c237d2-4a51-4b41-a5e2-e3bc1def56c5",
1162 "isActive": true,
1163 "balance": "$2,979.67",
1164 "age": 39,
1165 "eyeColor": "brown",
1166 "name": "Ramona Fitzgerald",
1167 "gender": "female",
1168 "company": "GOLISTIC",
1169 "email": "ramonafitzgerald@golistic.com",
1170 "phone": "+1 (965) 432-2851"
1171 }, {
1172 "_id": "54ff69d9affa1d1749cd11d5",
1173 "index": 42,
1174 "guid": "80ef3c8e-b6ca-4048-9920-e651fa2a11b0",
1175 "isActive": false,
1176 "balance": "$3,671.11",
1177 "age": 27,
1178 "eyeColor": "blue",
1179 "name": "Hyde Alford",
1180 "gender": "male",
1181 "company": "COFINE",
1182 "email": "hydealford@cofine.com",
1183 "phone": "+1 (806) 588-3800"
1184 }, {
1185 "_id": "54ff69d9bd4f8a5bc1e34937",
1186 "index": 43,
1187 "guid": "3d5ed46a-0b40-4e2d-8438-ada9007f4d49",
1188 "isActive": true,
1189 "balance": "$1,051.71",
1190 "age": 36,
1191 "eyeColor": "brown",
1192 "name": "Sonia Salazar",
1193 "gender": "female",
1194 "company": "RENOVIZE",
1195 "email": "soniasalazar@renovize.com",
1196 "phone": "+1 (855) 547-2046"
1197 }, {
1198 "_id": "54ff69d9c59ba53ca1b15c7f",
1199 "index": 44,
1200 "guid": "fe908aad-5474-4767-baf4-66a4e9fcf106",
1201 "isActive": true,
1202 "balance": "$3,531.45",
1203 "age": 21,
1204 "eyeColor": "blue",
1205 "name": "Johnston Nolan",
1206 "gender": "male",
1207 "company": "VELOS",
1208 "email": "johnstonnolan@velos.com",
1209 "phone": "+1 (950) 510-3191"
1210 }, {
1211 "_id": "54ff69d96252c760ec1787a4",
1212 "index": 45,
1213 "guid": "efd7b2aa-804b-4463-a1e4-7931bcac6a18",
1214 "isActive": true,
1215 "balance": "$2,238.60",
1216 "age": 40,
1217 "eyeColor": "green",
1218 "name": "Salazar Walters",
1219 "gender": "male",
1220 "company": "GEEKNET",
1221 "email": "salazarwalters@geeknet.com",
1222 "phone": "+1 (834) 510-2779"
1223 }, {
1224 "_id": "54ff69d9672f19a45f583e87",
1225 "index": 46,
1226 "guid": "25d3dddd-3983-49ae-b7f4-224e73bdacd9",
1227 "isActive": true,
1228 "balance": "$2,092.02",
1229 "age": 21,
1230 "eyeColor": "brown",
1231 "name": "Emma Mcfarland",
1232 "gender": "female",
1233 "company": "COWTOWN",
1234 "email": "emmamcfarland@cowtown.com",
1235 "phone": "+1 (858) 410-3273"
1236 }, {
1237 "_id": "54ff69d902facac3a9fc9fbf",
1238 "index": 47,
1239 "guid": "e22f488c-a5ad-49d3-b9ec-f3dd9f287318",
1240 "isActive": false,
1241 "balance": "$1,199.34",
1242 "age": 39,
1243 "eyeColor": "blue",
1244 "name": "Lisa Sutton",
1245 "gender": "female",
1246 "company": "EXTRAGEN",
1247 "email": "lisasutton@extragen.com",
1248 "phone": "+1 (815) 583-2428"
1249 }, {
1250 "_id": "54ff69d958481e8cde608526",
1251 "index": 48,
1252 "guid": "aa976349-e7dc-4a38-aae7-e497c986c288",
1253 "isActive": true,
1254 "balance": "$3,680.82",
1255 "age": 37,
1256 "eyeColor": "blue",
1257 "name": "Blackburn Ingram",
1258 "gender": "male",
1259 "company": "EXTREMO",
1260 "email": "blackburningram@extremo.com",
1261 "phone": "+1 (800) 407-3564"
1262 }, {
1263 "_id": "54ff69d9e28a5207c6ce32f2",
1264 "index": 49,
1265 "guid": "e95d0949-d9e5-4665-bee0-29697d6b6e61",
1266 "isActive": true,
1267 "balance": "$3,399.52",
1268 "age": 24,
1269 "eyeColor": "blue",
1270 "name": "Preston Hardy",
1271 "gender": "male",
1272 "company": "PROXSOFT",
1273 "email": "prestonhardy@proxsoft.com",
1274 "phone": "+1 (960) 444-3169"
1275 }, {
1276 "_id": "54ff69d92e96909171f3e1b1",
1277 "index": 50,
1278 "guid": "0d6b5e73-7b1d-4c96-9a16-d73d0e74c364",
1279 "isActive": false,
1280 "balance": "$2,920.49",
1281 "age": 21,
1282 "eyeColor": "green",
1283 "name": "Mia Mcgee",
1284 "gender": "female",
1285 "company": "GEEKOLOGY",
1286 "email": "miamcgee@geekology.com",
1287 "phone": "+1 (921) 464-2944"
1288 }, {
1289 "_id": "54ff69d94e56363d2f4110cb",
1290 "index": 51,
1291 "guid": "c355ad3a-a137-4dc4-9eeb-2ef6c5385076",
1292 "isActive": true,
1293 "balance": "$2,738.07",
1294 "age": 36,
1295 "eyeColor": "brown",
1296 "name": "Doris Mccullough",
1297 "gender": "female",
1298 "company": "PRIMORDIA",
1299 "email": "dorismccullough@primordia.com",
1300 "phone": "+1 (823) 470-2961"
1301 }, {
1302 "_id": "54ff69d99d447d751c48fefc",
1303 "index": 52,
1304 "guid": "564a646d-4bd9-4e4b-bb64-522eea292751",
1305 "isActive": false,
1306 "balance": "$1,966.01",
1307 "age": 30,
1308 "eyeColor": "brown",
1309 "name": "Fernandez Shepard",
1310 "gender": "male",
1311 "company": "PAWNAGRA",
1312 "email": "fernandezshepard@pawnagra.com",
1313 "phone": "+1 (903) 453-3876"
1314 }, {
1315 "_id": "54ff69d949dfc5b655d10e58",
1316 "index": 53,
1317 "guid": "4b07ab75-7a20-4515-9561-10323a712c37",
1318 "isActive": false,
1319 "balance": "$3,447.93",
1320 "age": 38,
1321 "eyeColor": "green",
1322 "name": "Yvette Caldwell",
1323 "gender": "female",
1324 "company": "OLUCORE",
1325 "email": "yvettecaldwell@olucore.com",
1326 "phone": "+1 (861) 417-2291"
1327 }, {
1328 "_id": "54ff69d9e7ab8c4e8029f2d6",
1329 "index": 54,
1330 "guid": "73917fc2-53d6-4331-8ac9-ec65943d8e75",
1331 "isActive": false,
1332 "balance": "$1,949.14",
1333 "age": 25,
1334 "eyeColor": "green",
1335 "name": "Gibson Molina",
1336 "gender": "male",
1337 "company": "STREZZO",
1338 "email": "gibsonmolina@strezzo.com",
1339 "phone": "+1 (809) 485-2161"
1340 }, {
1341 "_id": "54ff69d958fcd9a733351136",
1342 "index": 55,
1343 "guid": "ad310b11-965c-480e-9088-4d65c27d6d04",
1344 "isActive": false,
1345 "balance": "$2,512.16",
1346 "age": 40,
1347 "eyeColor": "brown",
1348 "name": "Hicks Ferguson",
1349 "gender": "male",
1350 "company": "COMTEXT",
1351 "email": "hicksferguson@comtext.com",
1352 "phone": "+1 (897) 565-2845"
1353 }, {
1354 "_id": "54ff69d94a48d9b5a556f495",
1355 "index": 56,
1356 "guid": "17bca210-7a7f-4236-badd-4d52abc4d73f",
1357 "isActive": false,
1358 "balance": "$3,950.51",
1359 "age": 28,
1360 "eyeColor": "brown",
1361 "name": "Dale Stephens",
1362 "gender": "female",
1363 "company": "ZENSOR",
1364 "email": "dalestephens@zensor.com",
1365 "phone": "+1 (867) 474-3050"
1366 }, {
1367 "_id": "54ff69d94f91296292db728e",
1368 "index": 57,
1369 "guid": "1cd712d4-b477-496d-aea6-d8b75f0aebcc",
1370 "isActive": false,
1371 "balance": "$1,385.99",
1372 "age": 37,
1373 "eyeColor": "brown",
1374 "name": "Burris Nash",
1375 "gender": "male",
1376 "company": "BRAINCLIP",
1377 "email": "burrisnash@brainclip.com",
1378 "phone": "+1 (873) 533-2943"
1379 }, {
1380 "_id": "54ff69d9b8edd89a22d08814",
1381 "index": 58,
1382 "guid": "3cab0ec2-bece-43a2-b49f-1213cbd8bbb0",
1383 "isActive": false,
1384 "balance": "$3,652.39",
1385 "age": 37,
1386 "eyeColor": "brown",
1387 "name": "Janie Harrington",
1388 "gender": "female",
1389 "company": "ZISIS",
1390 "email": "janieharrington@zisis.com",
1391 "phone": "+1 (920) 441-3329"
1392 }, {
1393 "_id": "54ff69d90550f9ffe4676013",
1394 "index": 59,
1395 "guid": "536e1f5f-474f-412e-a4ad-258a20a7cb77",
1396 "isActive": false,
1397 "balance": "$3,937.99",
1398 "age": 24,
1399 "eyeColor": "brown",
1400 "name": "Beulah Burch",
1401 "gender": "female",
1402 "company": "PLASTO",
1403 "email": "beulahburch@plasto.com",
1404 "phone": "+1 (904) 452-2143"
1405 }, {
1406 "_id": "54ff69d920a5eed9e3d2cb29",
1407 "index": 60,
1408 "guid": "54a44fee-8614-492f-a1bd-c4a6248fce48",
1409 "isActive": true,
1410 "balance": "$3,024.67",
1411 "age": 25,
1412 "eyeColor": "green",
1413 "name": "Osborne Carter",
1414 "gender": "male",
1415 "company": "ARTIQ",
1416 "email": "osbornecarter@artiq.com",
1417 "phone": "+1 (937) 431-2440"
1418 }, {
1419 "_id": "54ff69d921ee90fcb998a583",
1420 "index": 61,
1421 "guid": "6e899cc3-f935-405d-995c-43635d3ed121",
1422 "isActive": true,
1423 "balance": "$3,189.34",
1424 "age": 37,
1425 "eyeColor": "green",
1426 "name": "Rice Mcclain",
1427 "gender": "male",
1428 "company": "EVENTIX",
1429 "email": "ricemcclain@eventix.com",
1430 "phone": "+1 (924) 448-3107"
1431 }, {
1432 "_id": "54ff69d9c70b2ae884142f6f",
1433 "index": 62,
1434 "guid": "0e62a562-ced9-4922-9985-8c813a647d87",
1435 "isActive": false,
1436 "balance": "$2,135.22",
1437 "age": 34,
1438 "eyeColor": "green",
1439 "name": "Jerri Knowles",
1440 "gender": "female",
1441 "company": "VANTAGE",
1442 "email": "jerriknowles@vantage.com",
1443 "phone": "+1 (926) 566-3957"
1444 }, {
1445 "_id": "54ff69d950ea8f13f7e0adc3",
1446 "index": 63,
1447 "guid": "70e8cbfa-9f24-4e58-b827-9d683765e383",
1448 "isActive": false,
1449 "balance": "$1,360.09",
1450 "age": 24,
1451 "eyeColor": "blue",
1452 "name": "Rose Chapman",
1453 "gender": "male",
1454 "company": "STEELTAB",
1455 "email": "rosechapman@steeltab.com",
1456 "phone": "+1 (922) 514-3065"
1457 }, {
1458 "_id": "54ff69d939f6877321458bf6",
1459 "index": 64,
1460 "guid": "ddb4264b-de31-464b-89ff-dc7b89c120c5",
1461 "isActive": true,
1462 "balance": "$1,331.51",
1463 "age": 32,
1464 "eyeColor": "green",
1465 "name": "Larson Baxter",
1466 "gender": "male",
1467 "company": "HALAP",
1468 "email": "larsonbaxter@halap.com",
1469 "phone": "+1 (968) 451-2570"
1470 }, {
1471 "_id": "54ff69d9e8c4ac69c408d5a5",
1472 "index": 65,
1473 "guid": "92f7f97e-469f-4e61-93ba-58d440f9bdee",
1474 "isActive": false,
1475 "balance": "$3,771.47",
1476 "age": 28,
1477 "eyeColor": "green",
1478 "name": "Sweeney Shepherd",
1479 "gender": "male",
1480 "company": "OVOLO",
1481 "email": "sweeneyshepherd@ovolo.com",
1482 "phone": "+1 (963) 413-2263"
1483 }, {
1484 "_id": "54ff69d97caf29d7106b9a22",
1485 "index": 66,
1486 "guid": "ccff96f2-0dea-4d72-ae9b-5f2c5e1bccf8",
1487 "isActive": false,
1488 "balance": "$3,022.08",
1489 "age": 35,
1490 "eyeColor": "blue",
1491 "name": "Maribel Henry",
1492 "gender": "female",
1493 "company": "CHILLIUM",
1494 "email": "maribelhenry@chillium.com",
1495 "phone": "+1 (814) 489-2410"
1496 }, {
1497 "_id": "54ff69d9ea34002e4fc91d67",
1498 "index": 67,
1499 "guid": "133b0c0a-1517-487c-84b9-b0eb1fc8bfd1",
1500 "isActive": false,
1501 "balance": "$1,995.00",
1502 "age": 20,
1503 "eyeColor": "brown",
1504 "name": "Deann Bray",
1505 "gender": "female",
1506 "company": "VERTON",
1507 "email": "deannbray@verton.com",
1508 "phone": "+1 (847) 540-2423"
1509 }, {
1510 "_id": "54ff69d9837c8279be6854e0",
1511 "index": 68,
1512 "guid": "d562f05e-b605-475e-b7ea-e38cf8c6b32f",
1513 "isActive": true,
1514 "balance": "$1,035.74",
1515 "age": 35,
1516 "eyeColor": "brown",
1517 "name": "Hart Dillon",
1518 "gender": "male",
1519 "company": "SOLAREN",
1520 "email": "hartdillon@solaren.com",
1521 "phone": "+1 (885) 540-3322"
1522 }, {
1523 "_id": "54ff69d975a0df7615047dda",
1524 "index": 69,
1525 "guid": "7ec2a46e-9d61-42ad-9aa0-d06596232252",
1526 "isActive": false,
1527 "balance": "$1,317.78",
1528 "age": 40,
1529 "eyeColor": "green",
1530 "name": "Daphne Mercado",
1531 "gender": "female",
1532 "company": "TERRASYS",
1533 "email": "daphnemercado@terrasys.com",
1534 "phone": "+1 (915) 491-2470"
1535 }, {
1536 "_id": "54ff69d9a6aafdf53cf7899d",
1537 "index": 70,
1538 "guid": "99c23548-6731-4236-a6b1-0455172d931b",
1539 "isActive": false,
1540 "balance": "$3,395.05",
1541 "age": 22,
1542 "eyeColor": "blue",
1543 "name": "Aguilar Ramsey",
1544 "gender": "male",
1545 "company": "SONIQUE",
1546 "email": "aguilarramsey@sonique.com",
1547 "phone": "+1 (918) 407-2528"
1548 }, {
1549 "_id": "54ff69d98368d84760f7ffb4",
1550 "index": 71,
1551 "guid": "919aa130-2a9a-4c18-9f82-a4cc5fa9d18f",
1552 "isActive": true,
1553 "balance": "$3,961.57",
1554 "age": 23,
1555 "eyeColor": "blue",
1556 "name": "Alyssa Garrett",
1557 "gender": "female",
1558 "company": "ZORK",
1559 "email": "alyssagarrett@zork.com",
1560 "phone": "+1 (816) 467-3019"
1561 }, {
1562 "_id": "54ff69d97100f98d64a49826",
1563 "index": 72,
1564 "guid": "32301d04-6bd8-4db2-a1d0-b98ae4a53877",
1565 "isActive": true,
1566 "balance": "$2,245.64",
1567 "age": 27,
1568 "eyeColor": "blue",
1569 "name": "Vickie Castro",
1570 "gender": "female",
1571 "company": "QUOTEZART",
1572 "email": "vickiecastro@quotezart.com",
1573 "phone": "+1 (894) 530-3406"
1574 }, {
1575 "_id": "54ff69d944ae1efb286accdf",
1576 "index": 73,
1577 "guid": "a15615f1-2077-4d7f-800c-f19aad0cf45a",
1578 "isActive": false,
1579 "balance": "$3,610.39",
1580 "age": 26,
1581 "eyeColor": "brown",
1582 "name": "Sheryl Cherry",
1583 "gender": "female",
1584 "company": "TOYLETRY",
1585 "email": "sherylcherry@toyletry.com",
1586 "phone": "+1 (883) 527-2393"
1587 }]
1588 });
1589
1590 grid = Ext.create('Ext.grid.Panel', {
1591 title: 'Simpsons',
1592 store: store,
1593 height: 350,
1594 width: 600,
1595 renderTo: document.body,
1596 columns: [{
1597 text: 'index',
1598 dataIndex: 'index',
1599 locked: true
1600 }, {
1601 text: 'Name',
1602 dataIndex: 'name',
1603 locked: true,
1604 cellWrap: true,
1605 width: 75
1606 }, {
1607 text: 'Email',
1608 dataIndex: 'email',
1609 flex: 1
1610 }, {
1611 text: 'Phone',
1612 dataIndex: 'phone'
1613 }, {
1614 text: 'isActive',
1615 dataIndex: 'isActive'
1616
1617 }, {
1618 text: 'eyeColor',
1619 dataIndex: 'eyeColor'
1620 }, {
1621 text: 'company',
1622 dataIndex: 'company'
1623 }, {
1624 text: 'gender',
1625 dataIndex: 'gender'
1626 }],
1627 region: 'center'
1628 });
1629
1630 var normalView = grid.normalGrid.getView(),
1631 lockedView = grid.lockedGrid.getView(),
1632 lockedScroller = lockedView.getScrollable(),
1633 normalScroller = normalView.getScrollable(),
1634 normalRows = normalView.all,
1635 lockedRows = lockedView.all,
1636 navModel = normalView.getNavigationModel();
1637
1638 waitsFor(function() {
1639 return normalView.all.getCount();
1640 });
1641
1642 runs(function() {
1643 navModel.setPosition(new Ext.grid.CellContext(lockedView).setPosition(0, 0));
1644 });
1645
1646 waitsFor(function() {
1647 var a = Ext.Element.getActiveElement(),
1648 p = navModel.getPosition();
1649
1650 if (p) {
1651 // Scroll only when the last scroll signal has found both views and caused them to update
1652 if (navModel.getCell() && (a === navModel.getCell().dom) && normalRows.startIndex === lockedRows.startIndex && lockedScroller.getPosition().y === normalScroller.getPosition().y) {
1653 jasmine.fireKeyEvent(a, 'keydown', Ext.event.Event.PAGE_DOWN);
1654 }
1655
1656 // Scroll until the end
1657 return (navModel.getPosition().rowIdx === store.getCount() - 1);
1658 }
1659 }, 'down arrow to scroll to the last row. 20 seconds expired', 20000);
1660
1661 runs(function() {
1662 var i;
1663
1664 // Row count must be in sync
1665 expect(lockedRows.getCount()).toBe(normalRows.getCount());
1666
1667 // view sizes must still be in sync
1668 expect(lockedView.bufferedRenderer.viewSize).toBe(normalView.bufferedRenderer.viewSize);
1669
1670 // Every row must be the same height
1671 for (i = normalRows.startIndex; i <= normalRows.endIndex; i++) {
1672 expect(normalRows.item(i).getHeight()).toBe(lockedRows.item(i).getHeight());
1673 }
1674 });
1675 });
1676 });
1677
1678 describe('gridpanel', function () {
1679 describe('locking grid', function () {
1680 function doIt(reconfigure) {
1681 var columns = [{
1682 text: 'Col 1',
1683 dataIndex: 'field1',
1684 width: 100,
1685 locked: true
1686 }, {
1687 text: 'Col 2',
1688 dataIndex: 'field2',
1689 width: 100
1690 }, {
1691 text: 'Col 3',
1692 dataIndex: 'field3',
1693 width: 100
1694 }, {
1695 text: 'Col 4',
1696 dataIndex: 'field4',
1697 width: 100
1698 }, {
1699 text: 'Col 5',
1700 dataIndex: 'field5',
1701 height: 25,
1702 width: 150,
1703 xtype: 'widgetcolumn',
1704 widget: {
1705 xtype: 'progressbarwidget',
1706 height: 25,
1707 textTpl: ['{percent:number("0")}% capacity']
1708 }
1709 }],
1710 nodeCache;
1711
1712 makeGrid({
1713 columns: columns
1714 }, {
1715 fields: ['field1', 'field2', 'field3', 'field4', 'field5'],
1716 data: createData(100)
1717 });
1718
1719 view = grid.view.normalView;
1720 nodeCache = view.all;
1721
1722 if (reconfigure) {
1723 grid.reconfigure(null, columns);
1724 }
1725
1726 waitsFor(function () {
1727 if (nodeCache.endIndex === 99 && view.bufferedRenderer.getLastVisibleRowIndex() === 99) {
1728 return true;
1729 }
1730 else {
1731 // Scroll incrementally until the correct end point is found
1732 view.scrollBy(null, 20);
1733 }
1734 }, 'last node to scroll into view', 10000, 50);
1735
1736 runs(function () {
1737 expect(view.el.down('.x-grid-item-container').getHeight() === view.lockingPartner.el.down('.x-grid-item-container').getHeight()).toBe(true);
1738 });
1739 }
1740
1741 it('should have the same height for each locking partner when scrolling', function () {
1742 doIt(false);
1743 });
1744
1745 it('should have the same height for each locking partner when scrolling after a reconfigure', function () {
1746 doIt(true);
1747 });
1748 });
1749
1750 describe('locking grid with variableRowHeight', function () {
1751 itIE10p('should keep the row heights on both sides synchronized', function() {
1752 var columns = [{
1753 text: 'Col 1',
1754 dataIndex: 'field1',
1755 width: 100,
1756 locked: true,
1757 variableRowHeight: true
1758 }, {
1759 text: 'Col 2',
1760 dataIndex: 'field2',
1761 width: 100
1762 }, {
1763 text: 'Col 3',
1764 dataIndex: 'field3',
1765 width: 100
1766 }, {
1767 text: 'Col 4',
1768 dataIndex: 'field4',
1769 width: 100
1770 }, {
1771 text: 'Col 5',
1772 dataIndex: 'field5',
1773 height: 25,
1774 width: 150,
1775 xtype: 'widgetcolumn',
1776 widget: {
1777 xtype: 'progressbarwidget',
1778 height: 25,
1779 textTpl: ['{percent:number("0")}% capacity']
1780 }
1781 }],
1782 nodeCache,
1783 lockedView,
1784 bufferedRendererInvocationCount = 0,
1785 onSyncHeights = function() {
1786 var lockedItems = lockedView.all.slice(),
1787 normalItems = nodeCache.slice(),
1788 lockedSize = lockedItems.length,
1789 normalSize = normalItems.length,
1790 i,
1791 allEqual = true;
1792
1793 // must be same number of rows
1794 expect(lockedSize).toBe(normalSize);
1795
1796 for (i = 0; allEqual && i < lockedSize; i++) {
1797 allEqual = allEqual && normalItems[i].offsetHeight === lockedItems[i].offsetHeight;
1798 }
1799 // All rows must be same size
1800 expect(allEqual).toBe(true);
1801 bufferedRendererInvocationCount++;
1802 };
1803
1804 // Make grid with small buffer zones.
1805 makeGrid({
1806 columns: columns,
1807 syncRowHeight: true
1808 }, {
1809 fields: ['field1', 'field2', 'field3', 'field4', 'field5'],
1810 data: createData(1000, true)
1811 });
1812
1813 lockedView = grid.view.lockedView;
1814 view = grid.view.normalView;
1815 nodeCache = view.all;
1816
1817 // Set up a sequence on the buffered renderers to check that all rows are always synced
1818 view.bufferedRenderer.syncRowHeights = Ext.Function.createSequence(view.bufferedRenderer.syncRowHeights, onSyncHeights);
1819 lockedView.bufferedRenderer.syncRowHeights = Ext.Function.createSequence(view.bufferedRenderer.syncRowHeights, onSyncHeights);
1820
1821 waitsFor(function () {
1822 var reachedTargetRow = nodeCache.startIndex <= 99 && nodeCache.endIndex >= 99;
1823
1824 // If row 99 is in the nodeCache, we're done
1825 if (reachedTargetRow) {
1826 return view.getScrollY() === lockedView.getScrollY() && nodeCache.startIndex === lockedView.all.startIndex && lockedView.position === view.position && lockedView.bodyTop === view.bodyTop;
1827 }
1828 else {
1829 // Scroll incrementally until the correct end point is found
1830 view.scrollBy(null, 20);
1831 }
1832 }, 'row 99 to be rendered', 20000, 50);
1833
1834 // Must have invoked the row syncher and the two body heights must be the same
1835 runs(function () {
1836
1837 expect(bufferedRendererInvocationCount).toBeGreaterThan(0);
1838 expect(view.el.down('.x-grid-item-container', true).offsetHeight === view.lockingPartner.el.down('.x-grid-item-container', true).offsetHeight).toBe(true);
1839 bufferedRendererInvocationCount = 0;
1840 });
1841
1842 // Now teleport down to neat the bottom
1843 waitsFor(function () {
1844 var reachedTargetRow = view.bufferedRenderer.getLastVisibleRowIndex() > 990;
1845
1846 if (reachedTargetRow) {
1847 return view.getScrollY() === lockedView.getScrollY() && view.all.startIndex === lockedView.all.startIndex;
1848 }
1849 else {
1850 // Scroll in teleporting chunks until the correct end point is found
1851 view.scrollBy(null, 1000);
1852 }
1853 }, 'row 990 to scroll into view', 30000, 100);
1854
1855 // Scrolling is too fast for IE8, need to repaint the grid
1856 // so that measurements below will yield correct values
1857 if (Ext.isIE8) {
1858 runs(function() {
1859 grid.hide();
1860 grid.show();
1861 });
1862 }
1863
1864 // Must have invoked the row syncher and the two body heights must be the same
1865 runs(function () {
1866 expect(bufferedRendererInvocationCount).toBeGreaterThan(0);
1867
1868 var mainHeight = view.el.down('.x-grid-item-container').getHeight(),
1869 partnerHeight = view.lockingPartner.el.down('.x-grid-item-container').getHeight();
1870
1871 expect(partnerHeight).toBe(mainHeight);
1872 bufferedRendererInvocationCount = 0;
1873 });
1874 });
1875 });
1876
1877 describe('locking grid with asymmetricRowHeight', function () {
1878 it('should keep the row heights on both sides synchronized', function() {
1879 // Note that we do NOT set variableRowHeight. All row heights are the same
1880 // even if one side drives the row height, and the sides need syncing.
1881 // This means BufferedRenderer can use simple arithmetic to find first/last visible row index.
1882 var columns = [{
1883 text: 'Col 1',
1884 dataIndex: 'field1',
1885 width: 100,
1886 locked: true
1887 }, {
1888 text: 'Col 2',
1889 dataIndex: 'field2',
1890 width: 100
1891 }, {
1892 text: 'Col 3',
1893 dataIndex: 'field3',
1894 width: 100
1895 }, {
1896 text: 'Col 4',
1897 dataIndex: 'field4',
1898 width: 100
1899 }, {
1900 text: 'Col 5',
1901 dataIndex: 'field5',
1902 height: 25,
1903 width: 150,
1904 xtype: 'widgetcolumn',
1905 widget: {
1906 xtype: 'progressbarwidget',
1907 height: 25,
1908 textTpl: ['{percent:number("0")}% capacity']
1909 }
1910 }],
1911 nodeCache,
1912 lockedView,
1913 bufferedRendererInvocationCount = 0,
1914 onSyncHeights = function() {
1915 var lockedItems = lockedView.all.slice(),
1916 normalItems = nodeCache.slice(),
1917 lockedSize = lockedItems.length,
1918 normalSize = normalItems.length,
1919 i,
1920 allEqual = true;
1921
1922 // must be same number of rows
1923 expect(lockedSize).toBe(normalSize);
1924
1925 for (i = 0; allEqual && i < lockedSize; i++) {
1926 allEqual = allEqual && normalItems[i].offsetHeight === lockedItems[i].offsetHeight;
1927 }
1928 // All rows must be same size
1929 expect(allEqual).toBe(true);
1930 bufferedRendererInvocationCount++;
1931 };
1932
1933 // Make grid with small buffer zones.
1934 makeGrid({
1935 columns: columns,
1936 syncRowHeight: true
1937 }, {
1938 fields: ['field1', 'field2', 'field3', 'field4', 'field5'],
1939 data: createData(1000, false, true),
1940
1941 // Make sure store.isGrouped() returns false
1942 // otherwise variableRowHeight will be detected
1943 groupField: undefined
1944 });
1945
1946 lockedView = grid.view.lockedView;
1947 view = grid.view.normalView;
1948 nodeCache = view.all;
1949
1950 // Set up a sequence on the buffered renderers to check that all rows are always synced
1951 view.bufferedRenderer.syncRowHeights = Ext.Function.createSequence(view.bufferedRenderer.syncRowHeights, onSyncHeights);
1952 lockedView.bufferedRenderer.syncRowHeights = Ext.Function.createSequence(view.bufferedRenderer.syncRowHeights, onSyncHeights);
1953
1954 waitsFor(function () {
1955 var reachedTargetRow = nodeCache.startIndex <= 99 && nodeCache.endIndex >= 99;
1956
1957 // If row 99 is in the nodeCache, we're done
1958 if (reachedTargetRow) {
1959 return view.getScrollY() === lockedView.getScrollY() && nodeCache.startIndex === lockedView.all.startIndex && lockedView.position === view.position && lockedView.bodyTop === view.bodyTop;
1960 }
1961 else {
1962 // Scroll incrementally until the correct end point is found
1963 view.scrollBy(null, 20);
1964 }
1965 }, 'row 99 to be rendered', 20000, 50);
1966
1967 // Must have invoked the row syncher and the two body heights must be the same
1968 runs(function () {
1969 expect(bufferedRendererInvocationCount).toBeGreaterThan(0);
1970 expect(view.el.down('.x-grid-item-container').getHeight() === view.lockingPartner.el.down('.x-grid-item-container').getHeight()).toBe(true);
1971 bufferedRendererInvocationCount = 0;
1972 });
1973
1974 // Now teleport down to neat the bottom
1975 waitsFor(function () {
1976 var reachedTargetRow = view.bufferedRenderer.getLastVisibleRowIndex() > 990;
1977
1978 if (reachedTargetRow) {
1979 return view.getScrollY() === lockedView.getScrollY() && view.all.startIndex === lockedView.all.startIndex;
1980 }
1981 else {
1982 // Scroll in teleporting chunks until the correct end point is found
1983 view.scrollBy(null, 1000);
1984 }
1985 }, 'row 990 to scroll into view', 30000, 100);
1986
1987 // Scrolling is too fast for IE8, need to repaint the grid
1988 // so that measurements below will yield correct values
1989 if (Ext.isIE8) {
1990 runs(function() {
1991 grid.hide();
1992 grid.show();
1993 });
1994 }
1995
1996 // Must have invoked the row syncher and the two body heights must be the same
1997 runs(function () {
1998 expect(bufferedRendererInvocationCount).toBeGreaterThan(0);
1999
2000 var mainHeight = view.el.down('.x-grid-item-container').getHeight(),
2001 partnerHeight = view.lockingPartner.el.down('.x-grid-item-container').getHeight();
2002
2003 expect(partnerHeight).toBe(mainHeight);
2004 bufferedRendererInvocationCount = 0;
2005 });
2006 });
2007 });
2008
2009 describe('reconfiguring', function () {
2010 it('should never return `undefined` records when called in a metachange event', function () {
2011 // When reconfigure is called within a metchange event listener, the view is refreshed and
2012 // `undefined` is returned when AbstractView.getViewRange() -> PageMap.getRange() is called.
2013 // This isn't usually a problem, but if there are data records in the PageMap hash that don't exist
2014 // in a current page then `undefined` will be returned when instead an array is expected. This, of
2015 // course, will throw an exception when the rows are attempted to be created by the template in
2016 // AbstractView.refresh(). See EXTJS-12633.
2017 var successData = {
2018 success: true,
2019 totally: 1000,
2020 data: [{
2021 first: 'First',
2022 last: 'Last'
2023 }, {
2024 first: 'First',
2025 last: 'Last'
2026 }],
2027 metaData: {
2028 root: 'data',
2029 totalProperty: 'totally',
2030 fields: ['first', 'last'],
2031 columns: [{
2032 text: 'First',
2033 dataIndex: 'first'
2034 }, {
2035 text: 'Last',
2036 dataIndex: 'last'
2037 }]
2038 }
2039 },
2040 wasCalled = false;
2041
2042 makeGrid(null, {
2043 data: null,
2044 fields: [],
2045 leadingBufferZone: 50,
2046 pageSize: 25,
2047 proxy: {
2048 type: 'ajax',
2049 url: 'derp'
2050 },
2051 listeners: {
2052 metachange: function (store, meta) {
2053 grid.reconfigure(store, meta.columns);
2054 wasCalled = true;
2055 }
2056 }
2057 });
2058
2059 // Overriding the PageMap method to return a value > 0 when there isn't a representative
2060 // page map will reproduce the bug.
2061 spyOn(store.data, 'getCount').andCallFake(function () {
2062 store.totalCount = 1000;
2063 return 25;
2064 });
2065
2066 store.load();
2067 completeWithData(successData);
2068
2069 expect(wasCalled).toBe(true);
2070 });
2071
2072 describe('with grouping feature', function () {
2073 it('reconfiguring should bind the groupStore to the plugin', function () {
2074 // This test demonstrates that reconfiguring the grid will properly bind the feature's group
2075 // store to the plugin.
2076 //
2077 // This bug only is reproducible when reconfigure is called on a grid with the buffered
2078 // renderer plugin and grouping feature. The bug was that the buffered renderer plugin
2079 // would bind the data store to the plugin rather than the group store (created when
2080 // there's a grouping feature).
2081 //
2082 // See EXTJS-11860 and EXTJS-11892.
2083 makeGrid({
2084 features: [{ftype: 'grouping'}]
2085 });
2086
2087 grid.reconfigure(store);
2088
2089 expect(grid.view.bufferedRenderer.store.isFeatureStore).toBe(true);
2090 });
2091 });
2092 });
2093
2094 describe('refreshing the view', function () {
2095 describe('filtering out all records', function () {
2096 function makeData(len) {
2097 var data = [],
2098 i, str;
2099
2100 len = len || 100;
2101
2102 for (i = 0; i < len; i++) {
2103 str = 'emp_' + i;
2104
2105 data.push({
2106 name: str,
2107 email: str + '@sencha.com',
2108 phone: '1-888-' + i,
2109 age: i
2110 });
2111 }
2112
2113 return data;
2114 }
2115
2116 function runTests(scroll) {
2117 describe('scrolled = ' + scroll, function () {
2118 it('should trigger a view refresh', function () {
2119 var wasCalled = false;
2120
2121 makeGrid({
2122 viewConfig: {
2123 listeners: {
2124 refresh: function () {
2125 wasCalled = true;
2126 }
2127 }
2128 }
2129 });
2130
2131 if (scroll) {
2132 plugin.scrollTo(50);
2133 }
2134
2135 // Filter out all data.
2136 store.filter('name', '______');
2137
2138 expect(wasCalled).toBe(true);
2139 });
2140
2141 it('should reset the view body', function () {
2142 makeGrid(null, {
2143 data: makeData(500)
2144 });
2145
2146 if (scroll) {
2147 plugin.scrollTo(50);
2148
2149 expect(plugin.bodyTop).toBeGreaterThan(0);
2150 expect(plugin.scrollHeight).toBeGreaterThan(0);
2151 }
2152
2153 // Filter out all data.
2154 store.filter('name', '______');
2155
2156 expect(plugin.bodyTop).toBe(0);
2157 expect(plugin.scrollHeight).toBe(0);
2158 });
2159 });
2160 }
2161
2162 runTests(true);
2163 runTests(false);
2164 });
2165 });
2166 });
2167
2168 describe('treepanel', function () {
2169 describe('expanding/collapsing', function () {
2170 it('should always render the view nodes when expanding', function () {
2171 var nodeCache;
2172
2173 makeTree({
2174 height: 300
2175 }, 100);
2176
2177 nodeCache = view.all;
2178
2179 // Scroll until the last tree node is the last in the rendered block.
2180 waitsFor(function () {
2181 if (nodeCache.endIndex === 99 && view.bufferedRenderer.getLastVisibleRowIndex() === 99) {
2182 return true;
2183 }
2184 else {
2185 // Scroll incrementally until the correct end point is found
2186 view.scrollBy(null, 10);
2187 }
2188 }, 'last node to scroll into view', 10000, 50);
2189
2190 // Expanding that last node should append some child nodes to replenish the leading buffer zone.
2191 runs(function () {
2192 // Expand node 99
2193 store.getAt(99).expand();
2194 });
2195
2196 // Scroll until the last of those expanded children is the last in the rendered block.
2197 waitsFor(function () {
2198 if (nodeCache.endIndex === 105) {
2199 return true;
2200 }
2201 else {
2202 // Scroll incrementally until the correct end point is found
2203 view.scrollBy(null, 10);
2204 }
2205 }, 'new last leaf node to scroll into view', 10000, 50);
2206
2207 // Expanding that last node should append the child nodes to the view even though the buffer rendered block is the correct size already
2208 runs(function () {
2209 //expect(view.bufferedRenderer.position).toBe(view.el.dom.scrollTop);
2210 expect(view.getRecord(view.all.last()).get('treeData')).toBe('Child of 99, number 6');
2211
2212 // Now let's collapse the parent node by simulating a click on the elbow node.
2213 jasmine.fireMouseEvent(nodeCache.item(99).down('.x-tree-expander'), 'click');
2214 });
2215 });
2216 });
2217
2218 describe('loadMask config', function () {
2219 it('should create a mask by default if not configured', function () {
2220 makeTree({
2221 store: {
2222 proxy: {
2223 type: 'ajax',
2224 url: 'foo'
2225 }
2226 }
2227 });
2228 expect(view.loadMask instanceof Ext.LoadMask).toBe(true);
2229 });
2230
2231 it('should honor the value if configured', function () {
2232 makeTree({
2233 store: {
2234 proxy: {
2235 type: 'ajax',
2236 url: 'foo'
2237 }
2238 },
2239 viewConfig: {
2240 loadMask: false
2241 }
2242 });
2243 expect(view.loadMask).toBe(false);
2244 });
2245 });
2246
2247 describe('Measuring row height', function() {
2248 it('should measure row height', function() {
2249 makeGrid(null, {
2250 groupField: null,
2251 data: [
2252 {'name': '<div style="height:30px">Lisa</div>', 'email': 'lisa@simpsons.com', 'phone': '555-111-1224', 'age': 14},
2253 {'name': 'Lisa', 'email': 'aunt_lisa@simpsons.com', 'phone': '555-111-1274', 'age': 34},
2254 {'name': 'Bart', 'email': 'bart@simpsons.com', 'phone': '555-222-1234', 'age': 12},
2255 {'name': 'Homer', 'email': 'homer@simpsons.com', 'phone': '555-222-1244', 'age': 44},
2256 {'name': 'Marge', 'email': 'marge@simpsons.com', 'phone': '555-222-1254', 'age': 41}
2257 ]
2258 });
2259
2260 // Should measure the row height be looking at the first row when we do NOT have variableRowHeight: true
2261 // EXTJS-15942 - did not measure, stayed at classic default of 21
2262 var row = view.all.first(),
2263 rowHeight = row.getHeight();
2264
2265 // In IE8 we're adding a bottom border on the rows and shifting the row up
2266 // at -border-width to compensate for that
2267 if (Ext.isIE8) {
2268 rowHeight -= row.getBorderWidth('b');
2269 }
2270
2271 expect(plugin.rowHeight).toBe(rowHeight);
2272 });
2273 });
2274 });
2275
2276 describe('filtering the store', function () {
2277 var Hobbit, store;
2278
2279 afterEach(function() {
2280 Ext.destroy(Hobbit, store);
2281 });
2282
2283 it('should reset the cached position so the grid-item-container is at the top of the view on filter', function () {
2284 Hobbit = Ext.define(null, {
2285 extend: 'Ext.data.Model',
2286 fields: ['name'],
2287 proxy: {
2288 type: 'ajax',
2289 url: '/foo',
2290 reader: {
2291 rootProperty: 'data'
2292 }
2293 }
2294 });
2295
2296 store = new Ext.data.BufferedStore({
2297 model: Hobbit,
2298 remoteGroup: true,
2299 leadingBufferZone: 300,
2300 pageSize: 100,
2301 autoDestroy: true
2302 });
2303
2304 makeGrid({
2305 columns: [{
2306 dataIndex: 'name',
2307 width: 100
2308 }]
2309 }, {
2310 store: store
2311 });
2312
2313 store.load();
2314
2315 completeWithData({
2316 total: 5000,
2317 data: makeData(100)
2318 });
2319
2320 completeWithData({
2321 total: 5000,
2322 data: makeData(100, 101)
2323 });
2324
2325 completeWithData({
2326 total: 5000,
2327 data: makeData(100, 201)
2328 });
2329
2330 completeWithData({
2331 total: 5000,
2332 data: makeData(100, 301)
2333 });
2334
2335 waitsFor(function () {
2336 view.scrollBy(null, 10);
2337 return view.all.startIndex <= 199 && view.all.endIndex >= 199;
2338 }, 'row 199 to scroll into the rendered block', 10000);
2339
2340 runs(function () {
2341 store.addFilter({
2342 property: 'name',
2343 value: 'name212'
2344 });
2345
2346 // Unfortunately, we're testing private properties here :(
2347 expect(plugin.bodyTop).toBe(0);
2348 expect(plugin.position).toBe(0);
2349 });
2350 });
2351 it('should reset the cached position so the grid-item-container is at the top of the view on clearFilter', function () {
2352 var selModel;
2353
2354 Hobbit = Ext.define(null, {
2355 extend: 'Ext.data.Model',
2356 fields: ['name'],
2357 proxy: {
2358 type: 'ajax',
2359 url: '/foo',
2360 reader: {
2361 rootProperty: 'data'
2362 }
2363 }
2364 });
2365
2366 store = new Ext.data.Store({
2367 model: Hobbit,
2368 remoteFilter: false,
2369 autoDestroy: true,
2370 proxy: {
2371 type: 'memory',
2372 data: makeData(5000)
2373 },
2374 autoLoad: true
2375 });
2376
2377 makeGrid({
2378 columns: [{
2379 dataIndex: 'name',
2380 width: 100
2381 }],
2382 selModel: {
2383 type: 'rowmodel',
2384 mode: 'MULTI'
2385 }
2386 }, {
2387 store: store
2388 });
2389 selModel = grid.selModel;
2390
2391 // Wait for first block to be rendered
2392 waitsFor(function() {
2393 return view.all.startIndex === 0 && view.all.getCount();
2394 });
2395
2396 runs(function() {
2397
2398 // Only show the first 2500
2399 store.addFilter({
2400 property: 'id',
2401 value: 2500,
2402 operator: '<='
2403 });
2404
2405 // We have filtered out the top 2500 IDs: 2501-5000
2406 expect(store.getCount()).toBe(2500);
2407
2408 // Click to select first row
2409 jasmine.fireMouseEvent(view.getCellByPosition({row: 0, column: 0}, true), 'click');
2410 expect(view.selModel.getSelection().length).toBe(1);
2411 });
2412
2413 // Scroll all the way to the end
2414 waitsFor(function () {
2415 view.scrollBy(null, 100);
2416 return view.all.endIndex === 2499;
2417 }, 'scroll to end', 10000);
2418
2419 runs(function () {
2420 // Click to select from start to end.
2421 jasmine.fireMouseEvent(view.getCellByPosition({row: 2499, column: 0}, true), 'click', null, null, null, true);
2422 expect(view.selModel.getSelection().length).toBe(2500);
2423
2424 store.remove(selModel.getSelection());
2425
2426 // All records gone. (There are still 2500) filtered out though...
2427 expect(store.getCount()).toBe(0);
2428
2429 // Should have gone to top
2430 expect(view.all.getCount()).toBe(0);
2431 expect(view.getScrollY()).toBe(0);
2432
2433 // Unfortunately, we're testing private properties here :(
2434 expect(plugin.bodyTop).toBe(0);
2435 expect(plugin.position).toBe(0);
2436
2437 store.clearFilter();
2438
2439 // The 2500 filtered out records should jump back in
2440 expect(store.getCount()).toBe(2500);
2441
2442 // Unfortunately, we're testing private properties here :(
2443 expect(plugin.bodyTop).toBe(0);
2444 expect(plugin.position).toBe(0);
2445 });
2446 });
2447 });
2448
2449 describe("reloading store", function() {
2450 describe("from having items to not having items", function() {
2451 it("should not cause an error when reloading", function() {
2452 var store = new Ext.data.BufferedStore({
2453 fields: ['id'],
2454 autoDestroy: true,
2455 proxy: {
2456 type: 'ajax',
2457 url: '/foo',
2458 reader: {
2459 type: 'json',
2460 rootProperty: 'data',
2461 totalProperty: 'total'
2462 }
2463 }
2464 });
2465
2466 makeGrid({
2467 columns: [{
2468 dataIndex: 'name',
2469 width: 100
2470 }]
2471 }, {
2472 store: store
2473 });
2474
2475 store.load();
2476 satisfyRequests();
2477
2478 store.reload();
2479 satisfyRequests(0);
2480
2481 expect(view.getNodes()).toEqual([]);
2482 });
2483
2484 it("should show emptyText if specified", function() {
2485 var store = new Ext.data.BufferedStore({
2486 fields: ['id'],
2487 autoDestroy: true,
2488 proxy: {
2489 type: 'ajax',
2490 url: '/foo',
2491 reader: {
2492 type: 'json',
2493 rootProperty: 'data',
2494 totalProperty: 'total'
2495 }
2496 }
2497 });
2498
2499 makeGrid({
2500 columns: [{
2501 dataIndex: 'name',
2502 width: 100
2503 }],
2504 emptyText: 'Empty'
2505 }, {
2506 store: store
2507 });
2508
2509 store.load();
2510 satisfyRequests();
2511
2512 store.reload();
2513 satisfyRequests(0);
2514
2515 expect(grid.el.down('.' + grid.emptyCls, true)).hasHTML('Empty');
2516 });
2517 });
2518
2519 describe('loading the bound store', function () {
2520 function testLockable(locked) {
2521 makeTree({
2522 columns: [{
2523 xtype: 'treecolumn',
2524 text: 'Tree Column',
2525 width: 300,
2526 locked: locked,
2527 dataIndex: 'task'
2528 }, {
2529 text: 'Task1',
2530 dataIndex: 'task1'
2531 }, {
2532 text: 'Task2',
2533 dataIndex: 'task2'
2534 }, {
2535 text: 'Task3',
2536 dataIndex: 'task3'
2537 }, {
2538 text: 'Task4',
2539 dataIndex: 'task4'
2540 }, {
2541 text: 'Task5',
2542 dataIndex: 'task5'
2543 }],
2544 }, 1000);
2545 }
2546
2547 it('should not scroll the view, non-locked grid', function () {
2548
2549 testLockable(false);
2550
2551 expect(view.el.dom.scrollTop).toBe(0);
2552 store.load();
2553 expect(view.el.dom.scrollTop).toBe(0);
2554 });
2555
2556 it('should not scroll the view, locked grid', function () {
2557 var lockedView, normalView;
2558
2559 testLockable(true);
2560 lockedViewDom = view.lockedView.el.dom;
2561 normalViewDom = view.normalView.el.dom;
2562
2563 expect(lockedViewDom.scrollTop).toBe(0);
2564 expect(normalViewDom.scrollTop).toBe(0);
2565 store.load();
2566 expect(lockedViewDom.scrollTop).toBe(0);
2567 expect(normalViewDom.scrollTop).toBe(0);
2568 });
2569 });
2570 });
2571
2572 describe('Expanding view size', function() {
2573 var window;
2574
2575 afterEach(function() {
2576 window.destroy();
2577 });
2578
2579 it('should scroll to top when view size expands to encapsulate whole dataset', function() {
2580 var Person = Ext.define(null, {
2581 extend: 'Ext.data.Model',
2582 fields: ['name'],
2583 proxy: {
2584 type: 'ajax',
2585 url: '/foo',
2586 reader: {
2587 rootProperty: 'data'
2588 }
2589 }
2590 });
2591
2592 var store = new Ext.data.Store({
2593 model: Person,
2594 proxy: {
2595 type: 'memory',
2596 data: makeData(52)
2597 }
2598 });
2599 store.load();
2600 var store1 = new Ext.data.Store({
2601 model: Person,
2602 proxy: {
2603 type: 'memory',
2604 data: makeData(52)
2605 }
2606 });
2607 store1.load();
2608
2609 var grid = new Ext.grid.Panel({
2610 hideMode: 'offsets',
2611 title: 'Grid 1',
2612 store: store,
2613 deferRowRender: false,
2614 columns: [{
2615 dataIndex: 'id'
2616 }, {
2617 dataIndex: 'name'
2618 }]
2619 });
2620 var grid1 = new Ext.grid.Panel({
2621 hideMode: 'offsets',
2622 title: 'Grid 2',
2623 store: store1,
2624 deferRowRender: false,
2625 columns: [{
2626 dataIndex: 'id'
2627 }, {
2628 dataIndex: 'name'
2629 }]
2630 });
2631
2632 window = new Ext.window.Window({
2633 title: 'Test',
2634 height: 395,
2635 width: 800,
2636 x: 10,
2637 y: 10,
2638 autoShow: true,
2639 maximizable: true,
2640 minimizable: true,
2641 constrain: false,
2642 layout: 'fit',
2643 items: {
2644 xtype: 'tabpanel',
2645 items: [
2646 grid, grid1
2647 ]
2648 }
2649 });
2650 var tabPanel = window.child('tabpanel');
2651
2652 var view = grid.getView();
2653
2654 // Scroll all the way to the end
2655 waitsFor(function () {
2656 view.scrollBy(null, 100);
2657 return view.all.endIndex === view.store.getCount() - 1;
2658 }, 'scroll to end', 10000);
2659
2660 runs(function() {
2661 view.scrollBy(null, 100);
2662
2663 tabPanel.setActiveTab(1);
2664
2665 // inserting at top
2666 grid.store.insert(0,{'title': 'hi',replycount:5});
2667 grid.store.insert(0,{'title': 'hi2',replycount:5});
2668
2669 grid1.store.insert(0,{'title': 'hi',replycount:5});
2670 grid1.store.insert(0,{'title': 'hi2',replycount:5});
2671
2672 tabPanel.setActiveTab(0);
2673
2674 window.setHeight(940);
2675 });
2676
2677 // Scroll all the way to the start
2678 waitsFor(function () {
2679 view.scrollBy(null, -100);
2680 return view.getScrollY() === 0;;
2681 }, 'scroll to top', 10000);
2682
2683 runs(function() {
2684 expect(view.bufferedRenderer.bodyTop).toBe(0);
2685 });
2686 });
2687 });
2688
2689 describe('ensureVisible', function() {
2690 it('should work in a viewready listener', function() {
2691 var done,
2692 columns = [{
2693 text: 'Col 1',
2694 dataIndex: 'field1',
2695 width: 100
2696 }, {
2697 text: 'Col 2',
2698 dataIndex: 'field2',
2699 width: 100
2700 }, {
2701 text: 'Col 3',
2702 dataIndex: 'field3',
2703 width: 100
2704 }, {
2705 text: 'Col 4',
2706 dataIndex: 'field4',
2707 width: 100
2708 }];
2709
2710 makeGrid({
2711 columns: columns,
2712 listeners : {
2713 viewready : function(grid) {
2714 grid.ensureVisible(grid.getStore().last(), {
2715 callback: function() {
2716 done = true;
2717 }
2718 });
2719 }
2720 }
2721 }, {
2722 fields: ['field1', 'field2', 'field3', 'field4'],
2723 data: createData(1000)
2724 });
2725
2726 waitsFor(function() {
2727 return done;
2728 });
2729
2730 // Should have scrolled all the way to the end
2731 expect(view.all.endIndex).toBe(999);
2732 expect(view.bufferedRenderer.getLastVisibleRowIndex()).toBe(999);
2733 });
2734 });
2735 });
2736