]>
git.proxmox.com Git - extjs.git/blob - extjs/classic/classic/test/specs/grid/plugin/RowEditing.js
1 describe('Ext.grid.plugin.RowEditing', function () {
2 var store
, plugin
, grid
, view
, column
,
3 synchronousLoad
= true,
4 proxyStoreLoad
= Ext
.data
.ProxyStore
.prototype.load
,
7 function makeGrid(pluginCfg
, gridCfg
, storeCfg
) {
8 var gridPlugins
= gridCfg
&& gridCfg
.plugins
,
11 store
= new Ext
.data
.Store(Ext
.apply({
12 fields
: ['name', 'email', 'phone'],
14 {'name': 'Lisa', 'email': 'lisa@simpsons.com', 'phone': '555-111-1224'},
15 {'name': 'Bart', 'email': 'bart@simpsons.com', 'phone': '555-222-1234'},
16 {'name': 'Homer', 'email': 'homer@simpsons.com', 'phone': '555-222-1244'},
17 {'name': 'Marge', 'email': 'marge@simpsons.com', 'phone': '555-222-1254'}
22 plugin
= new Ext
.grid
.plugin
.RowEditing(pluginCfg
);
25 plugins
= [].concat(plugin
, gridPlugins
);
26 delete gridCfg
.plugins
;
29 grid
= new Ext
.grid
.Panel(Ext
.apply({
31 {header
: 'Name', dataIndex
: 'name', editor
: 'textfield'},
32 {header
: 'Email', dataIndex
: 'email',
38 {header
: 'Phone', dataIndex
: 'phone'}
41 plugins
: plugins
|| [plugin
],
44 renderTo
: document
.body
50 beforeEach(function() {
51 // Override so that we can control asynchronous loading
52 loadStore
= Ext
.data
.ProxyStore
.prototype.load = function() {
53 proxyStoreLoad
.apply(this, arguments
);
54 if (synchronousLoad
) {
55 this.flushLoad
.apply(this, arguments
);
61 afterEach(function () {
62 // Undo the overrides.
63 Ext
.data
.ProxyStore
.prototype.load
= proxyStoreLoad
;
65 store
= plugin
= grid
= view
= column
= Ext
.destroy(grid
);
68 describe('Widget column', function() {
69 it('should work', function() {
74 {header
: 'Name', dataIndex
: 'name', editor
: 'textfield'},
75 {header
: 'Email', dataIndex
: 'email',
81 {header
: 'Phone', dataIndex
: 'phone'},
83 xtype
: 'widgetcolumn',
87 handler
: onDeleteClick
93 var storeCount
= store
.getCount(),
94 editPos
= new Ext
.grid
.CellContext(view
).setPosition(0, 3),
95 cell
= editPos
.getCell(true);
97 function onDeleteClick(btn
) {
98 var rec
= btn
.getWidgetRecord();
102 // Programatically focus because simulated mousedown event does not focus, so
103 // The tabIndex will NOT be -1, so it will process as if mousedowning on an active widget.
104 view
.getNavigationModel().setPosition(editPos
);
106 // First click should delete the record.
107 // Second click - the dblclick - should not edit being on a focusable widget
108 jasmine
.fireMouseEvent(cell
.firstChild
.firstChild
, 'dblclick');
110 // Some browsers process the first and second click separately and will delete two rows.
111 // So just check that the store size has been reduced.
112 expect(store
.getCount()).toBeLessThan(storeCount
);
114 // Editing should never start; flag should be undefined/falsy
115 expect(plugin
.editing
).not
.toBe(true);
119 describe('should work', function () {
122 afterEach(function () {
126 it('should display the row editor for the grid in editing mode', function () {
129 node
= grid
.view
.getNode(0);
131 jasmine
.fireMouseEvent(Ext
.fly(node
).down('.x-grid-cell-inner', true), 'dblclick');
133 expect(plugin
.editor
).toBeDefined();
134 expect(plugin
.editing
).toBe(true);
138 describe('renderers', function () {
139 it('should be called with the correct scope for the defaultRenderer (column)', function () {
145 {text
: 'Foo', width
: 50,
146 defaultRenderer: function () {
151 {header
: 'Name', dataIndex
: 'name', editor
: 'textfield'},
152 {header
: 'Email', dataIndex
: 'email',
158 {header
: 'Phone', dataIndex
: 'phone'}
162 record
= grid
.store
.getAt(0);
163 column
= grid
.columns
[0];
164 plugin
.startEdit(record
, column
);
166 expect(scope
=== grid
.columns
[0]).toBe(true);
170 describe('starting the edit', function () {
171 var combo
, textfield
, record
, items
;
173 describe('should work', function () {
174 beforeEach(function () {
175 combo
= new Ext
.form
.field
.ComboBox({
178 displayField
: 'name',
190 textfield
= new Ext
.form
.field
.Text();
194 {header
: 'Name', dataIndex
: 'name', editor
: combo
},
195 {header
: 'Email', dataIndex
: 'email',
201 {header
: 'Phone', dataIndex
: 'phone', editor
: textfield
}
205 record
= grid
.store
.getAt(0);
206 column
= grid
.columns
[0];
208 plugin
.startEdit(record
, column
);
211 afterEach(function () {
212 record
= items
= null;
215 describe('initial values', function () {
216 it('should give each editor a dataIndex property', function () {
217 items
= plugin
.editor
.items
;
219 expect(items
.getAt(0).dataIndex
).toBe('name');
220 expect(items
.getAt(1).dataIndex
).toBe('email');
221 expect(items
.getAt(2).dataIndex
).toBe('phone');
224 it('should start the editor with values taken from the model', function () {
225 items
= plugin
.editor
.items
;
227 expect(items
.getAt(0).getValue()).toBe('Lisa');
228 expect(items
.getAt(1).getValue()).toBe('lisa@simpsons.com');
229 expect(items
.getAt(2).getValue()).toBe('555-111-1224');
233 describe('using an existing component as an editor', function () {
234 it('should be able to lookup its value from the corresponding model field', function () {
235 items
= plugin
.editor
.items
;
237 // The combo editor is an existing component.
238 expect(items
.getAt(0).getValue()).toBe('Lisa');
240 // The textfield editor is an existing component.
241 expect(items
.getAt(2).getValue()).toBe('555-111-1224');
246 describe('calling startEdit with different columnHeader values', function () {
247 it('should allow columnHeader to be a Number', function () {
250 record
= grid
.store
.getAt(0);
252 // Will return `true` if the edit was successfully started.
253 expect(plugin
.startEdit(record
, 0)).toBe(true);
256 it('should allow columnHeader to be a Column instance', function () {
259 record
= grid
.store
.getAt(0);
260 column
= grid
.columns
[0];
262 // Will return `true` if the edit was successfully started.
263 expect(plugin
.startEdit(record
, column
)).toBe(true);
266 it('should default to the first visible column if unspecified', function () {
269 record
= grid
.store
.getAt(0);
271 // Will return `true` if the edit was successfully started.
272 expect(plugin
.startEdit(record
)).toBe(true);
276 describe('adding new rows to the view', function () {
277 var viewEl
, count
, record
, editor
;
279 function addRecord(index
) {
283 store
.insert(index
, {name
: 'Homer', email
: 'homer@simpsons.com', phone
: '555-222-1244'});
284 record
= store
.getAt(index
? index
- 1 : 0);
285 plugin
.startEdit(record
, 0);
286 editor
= plugin
.editor
;
288 el
= Ext
.fly(view
.getNode(record
));
290 return new Ext
.util
.Point(el
.getX(), el
.getY());
293 afterEach(function () {
294 count
= viewEl
= record
= editor
= null;
297 it('should be contained by and visible in the view', function () {
302 count
= store
.getCount();
303 viewEl
= view
.getEl();
305 // Add to the beginning.
306 expect(addRecord(0).isContainedBy(viewEl
)).toBe(true);
307 expect(addRecord(0).isContainedBy(viewEl
)).toBe(true);
308 expect(addRecord(0).isContainedBy(viewEl
)).toBe(true);
309 expect(addRecord(0).isContainedBy(viewEl
)).toBe(true);
312 expect(addRecord(count
).isContainedBy(viewEl
)).toBe(true);
313 expect(addRecord(count
).isContainedBy(viewEl
)).toBe(true);
314 expect(addRecord(count
).isContainedBy(viewEl
)).toBe(true);
315 expect(addRecord(count
).isContainedBy(viewEl
)).toBe(true);
318 describe('scrolling into view', function () {
319 function buffered(buffered
) {
320 describe('buffered renderer = ' + buffered
, function () {
321 beforeEach(function () {
327 count
= store
.getCount();
328 viewEl
= view
.getEl();
331 it('should scroll when adding to the beginning', function () {
333 expect(editor
.isVisible()).toBe(true);
334 expect(editor
.context
.record
).toBe(record
);
337 it('should scroll when adding to the end', function () {
338 addRecord(store
.getCount());
339 expect(editor
.isVisible()).toBe(true);
340 expect(editor
.context
.record
).toBe(record
);
351 describe('completing the edit', function () {
352 var combo
, record
, items
;
354 beforeEach(function () {
355 combo
= new Ext
.form
.field
.ComboBox({
358 displayField
: 'name',
372 {header
: 'Name', dataIndex
: 'name', editor
: combo
},
373 {header
: 'Email', dataIndex
: 'email',
382 record
= grid
.store
.getAt(0);
383 column
= grid
.columns
[0];
385 plugin
.startEdit(record
, column
);
388 afterEach(function () {
389 combo
= record
= items
= null;
392 describe('using an existing component as an editor', function () {
393 it('should update the underlying cell and the record', function () {
394 column
.getEditor().setValue('utley');
395 plugin
.editor
.completeEdit();
397 expect(Ext
.fly(grid
.view
.getNode(record
)).down('.x-grid-cell-inner', true).innerHTML
).toBe('utley');
398 expect(store
.getAt(0).get('name')).toBe('utley');
403 describe('canceledit', function () {
404 var editorContext
= {},
407 beforeEach(function () {
410 canceledit: function (editor
, context
) {
411 editorContext
= context
;
416 record
= grid
.store
.getAt(0);
417 column
= grid
.columns
[0];
419 plugin
.startEdit(record
, column
);
422 afterEach(function () {
423 editorContext
= record
= null;
426 it('should be able to get the original value when canceling the edit', function() {
427 column
.getEditor().setValue('baz');
430 expect(editorContext
.originalValues
.name
).toBe('Lisa');
433 it('should be able to get the edited value when canceling the edit', function() {
434 column
.getEditor().setValue('foo');
437 expect(editorContext
.newValues
.name
).toBe('foo');
440 it('should have different values for edited value and original value when canceling', function() {
441 column
.getEditor().setValue('foo');
444 expect(editorContext
.newValues
.name
).not
.toBe(editorContext
.originalValues
.name
);
447 it('should be able to capture falsey values when canceled', function() {
448 column
.getEditor().setValue('');
451 expect(editorContext
.newValues
.name
).toBe('');
455 describe('locked grid', function () {
458 {header
: 'Name', dataIndex
: 'name', width
: 100, locked
: true, editor
: true},
459 {header
: 'Email', dataIndex
: 'email', width
: 100, editor
: true},
460 {header
: 'Phone', dataIndex
: 'phone', width
: 100, editor
: true}
468 beforeEach(function () {
469 makeGrid(null, suiteCfg
);
472 afterEach(function () {
476 it('should display the row editor for the locked grid in editing mode', function () {
477 node
= grid
.lockedGrid
.view
.getNode(0);
479 jasmine
.fireMouseEvent(Ext
.fly(node
).down('.x-grid-cell-inner', true), 'dblclick');
481 plugin
= grid
.findPlugin('rowediting');
483 expect(plugin
.editor
!== null).toBe(true);
484 expect(plugin
.editing
).toBe(true);
487 it('should display the row editor for the normal grid in editing mode', function () {
488 node
= grid
.normalGrid
.view
.getNode(0);
490 jasmine
.fireMouseEvent(Ext
.fly(node
).down('.x-grid-cell-inner', true), 'dblclick');
492 plugin
= grid
.findPlugin('rowediting');
494 expect(plugin
.editor
!== null).toBe(true);
495 expect(plugin
.editing
).toBe(true);
498 describe('with grouping feature', function () {
499 describe('when the activeRecord of the activeEditor has been filtered', function () {
500 // These specs simulate the filtering of the data store when the row editing plugin is active
501 // and over a record that has been filtered after the row editor was activated/started editing.
502 // The bug appeared in KS when the row editor was open and the dataset was filtered by the grid
503 // filter feature. Since the locking partners share the same store, the normal grid can't look
504 // up the record in its store if the locked grid has already filtered the store, which is the
505 // case here. Note that the bug only occurred when the editor was started from the normalGrid,
506 // NOT the lockedGrid (since the store hadn't been filtered yet).
508 // To simulate, simply filter the store after the plugin has been activated. During the filter
509 // operation, it will try to lookup the row record by its internal id in the GroupStore, but it
510 // will fail because the dataset has been filtered and the GroupStore#getByInternalId method will
511 // lookup the record in the data store. The fix is to lookup the record in the snapshot collection,
512 // if it exists. This mimics the solution implemented by v5 which solves this by maintaining another
513 // unfiltered collection, Ext.util.CollectionKey. So, because we can get the record shows that the
514 // bug has been fixed, since the record is being found (regardless of filtering).
517 // Note these specs must use the bufferedrenderer plugin.
518 var normalView
, lockedView
, record
;
520 beforeEach(function () {
523 makeGrid(null, Ext
.applyIf({
525 ftype
: 'groupingsummary',
526 groupHeaderTpl
: '{name}'
528 plugins
: ['bufferedrenderer'],
529 lockedGridConfig
: null,
530 normalGridConfig
: null
535 normalView
= grid
.normalGrid
.view
;
536 lockedView
= grid
.lockedGrid
.view
;
539 afterEach(function () {
540 normalView
= lockedView
= record
= null;
543 describe('activating the editor from the normal view', function () {
544 beforeEach(function () {
545 node
= normalView
.getNode(0);
547 jasmine
.fireMouseEvent(Ext
.fly(node
).down('.x-grid-cell-inner', true), 'dblclick');
549 // Now filter the store. Make sure that the row that's clicked on has been filtered
550 // and is no longer in the filtered data collection. This is what triggered the bug
551 // because the GroupStore is trying to look up the record in the filtered collection.
552 store
.filter('email', /home/);
553 record
= normalView
.getRecord(node
);
556 it('should still be able to lookup the record in the datastore when filtered', function () {
557 expect(record
).toBeDefined();
558 expect(record
.get('email')).toBe('bart@simpsons.com');
561 it('should close the editor', function () {
562 expect(plugin
.editing
).toBe(false);
566 describe('activating the editor from the locked view', function () {
567 beforeEach(function () {
568 node
= lockedView
.getNode(0);
570 jasmine
.fireMouseEvent(Ext
.fly(node
).down('.x-grid-cell-inner', true), 'dblclick');
572 store
.filter('email', /home/);
573 record
= lockedView
.getRecord(node
);
576 it('should still be able to lookup the record in the datastore when filtered', function () {
577 expect(record
).toBeDefined();
578 expect(record
.get('email')).toBe('bart@simpsons.com');
581 it('should close the editor', function () {
582 expect(plugin
.editing
).toBe(false);
589 describe('clicksToEdit', function () {
592 afterEach(function () {
593 node
= record
= null;
596 describe('2 clicks', function () {
597 beforeEach(function () {
601 it('should default to 2', function () {
602 expect(plugin
.clicksToEdit
).toBe(2);
605 it('should begin editing when double-clicked', function () {
606 record
= grid
.store
.getAt(0);
607 node
= grid
.view
.getNodeByRecord(record
);
608 jasmine
.fireMouseEvent(Ext
.fly(node
).down('.x-grid-cell'), 'dblclick');
610 expect(plugin
.editor
).not
.toBeFalsy();
613 it('should not begin editing when single-clicked', function () {
614 record
= grid
.store
.getAt(0);
615 node
= grid
.view
.getNodeByRecord(record
);
616 jasmine
.fireMouseEvent(Ext
.fly(node
).down('.x-grid-cell'), 'click');
618 expect(plugin
.editor
).toBeFalsy();
622 describe('1 click', function () {
623 beforeEach(function () {
629 it('should honor a different number than the default', function () {
630 expect(plugin
.clicksToEdit
).toBe(1);
633 it('should begin editing when single-clicked', function () {
634 record
= grid
.store
.getAt(0);
635 node
= grid
.view
.getNodeByRecord(record
);
636 jasmine
.fireMouseEvent(Ext
.fly(node
).down('.x-grid-cell'), 'click');
638 expect(plugin
.editor
).not
.toBeFalsy();
641 it('should not begin editing when double-clicked', function () {
642 record
= grid
.store
.getAt(0);
643 node
= grid
.view
.getNodeByRecord(record
);
644 jasmine
.fireMouseEvent(Ext
.fly(node
).down('.x-grid-cell'), 'dblclick');
646 expect(plugin
.editor
).not
.toBeFalsy();
651 describe('the RowEditor', function () {
654 afterEach(function () {
658 describe('as textfield', function () {
659 beforeEach(function () {
662 column
= grid
.columns
[0];
663 plugin
.startEdit(store
.getAt(0), column
);
664 field
= column
.field
;
667 it('should start the edit when ENTER is pressed', function () {
670 // First complete the edit (we start an edit in the top-level beforeEach).
671 plugin
.completeEdit();
672 // Let's just do a sanity to make sure we're really not currently editing.
673 expect(plugin
.editing
).toBe(false);
675 node
= view
.body
.query('td', true)[0];
676 jasmine
.fireKeyEvent(node
, 'keydown', 13);
678 waitsFor(function () {
679 return plugin
.editing
;
683 expect(plugin
.editing
).toBe(true);
687 describe('when currently editing', function () {
688 it('should complete the edit when ENTER is pressed', function () {
689 var str
= 'Utley is Top Dog',
690 model
= store
.getAt(0);
692 expect(model
.get('name')).toBe('Lisa');
695 jasmine
.fireKeyEvent(field
.inputEl
, 'keydown', 13);
697 waitsFor(function () {
698 return model
.get('name') === str
;
702 expect(model
.get('name')).toBe(str
);
706 it('should cancel the edit when ESCAPE is pressed', function () {
707 spyOn(plugin
, 'cancelEdit');
709 jasmine
.fireKeyEvent(field
.inputEl
, 'keydown', 27);
711 expect(plugin
.cancelEdit
).toHaveBeenCalled();
716 describe('as textarea', function () {
717 beforeEach(function () {
720 column
= grid
.columns
[1];
721 plugin
.startEdit(store
.getAt(0), column
);
722 field
= column
.field
;
725 it('should start the edit when ENTER is pressed', function () {
728 // First complete the edit (we start an edit in the top-level beforeEach).
729 plugin
.completeEdit();
730 // Let's just do a sanity to make sure we're really not currently editing.
731 expect(plugin
.editing
).toBe(false);
733 node
= view
.body
.query('td', true)[1];
734 jasmine
.fireKeyEvent(node
, 'keydown', 13);
736 expect(plugin
.editing
).toBe(true);
739 describe('when currently editing', function () {
740 it('should complete the edit when ENTER is pressed', function () {
741 spyOn(plugin
, 'completeEdit');
743 jasmine
.fireKeyEvent(field
.inputEl
, 'keydown', 13);
745 expect(plugin
.completeEdit
).toHaveBeenCalled();
748 it('should not cancel the edit when ENTER is pressed', function () {
749 spyOn(plugin
, 'cancelEdit');
751 jasmine
.fireKeyEvent(field
.inputEl
, 'keydown', 13);
753 expect(plugin
.cancelEdit
).not
.toHaveBeenCalled();
756 it('should cancel the edit when ESCAPE is pressed', function () {
757 spyOn(plugin
, 'cancelEdit');
759 jasmine
.fireKeyEvent(field
.inputEl
, 'keydown', 27);
761 expect(plugin
.cancelEdit
).toHaveBeenCalled();