PDA

View Full Version : MVC CellEditing Grid using direct proxy



scgrif32
21 Mar 2013, 1:08 PM
Hello All,

I am trying to use the following examples (Ext.data.writer.Writer Example and Ext.data.writer.Writer JsonP Example) for reference to create a grid that will use the CellEditing plugin.

http://docs.sencha.com/ext-js/4-2/extjs-build/examples/writer/writer.html?theme=classic

I have tried to add the "Add" and "Delete" buttons to the toolbar above the grid and that is when everything went south. When I click on the "Add" button I get the following error in Chrome:



Uncaught RangeError: Maximum call stack size exceeded

Ext.JSON.encodeString
Ext.JSON.doEncode
Ext.JSON.encodeObject
Ext.JSON.doEncode




Several more of the same two strings followed what i've pasted into this thread. It appears a circular reference has occurred but this only happens when I am trying to add the new record to the store. I tried to convert the examples so that they followed the MVC model in my application.

Model


Ext.define('iConnect.model.FunctionCode', {
extend: 'Ext.data.Model',
fields: [
{ name: 'objId', mapping: 'FunctionCodeObjId', type: 'int' },
{ name: 'functionCode', mapping: 'FunctionCode', type: 'string' },
{ name: 'functionCodeDescription', mapping: 'FunctionCodeDescription', type: 'string' },
{ name: 'applicationCode', mapping: 'ApplicationCode', type: 'string' },
{ name: 'fdgUrl', mapping: 'FdgUrl', type: 'string' }
]
});

Store


Ext.define('iConnect.store.FunctionCodes', {
extend: 'Ext.data.Store',
model: 'iConnect.model.FunctionCode',
autoLoad: false,
autoSync: true,
idProperty: 'objId',

proxy: {
type: 'direct',
api: {
create: 'iConnect.FunctionCode.doFunctionCodeMaintenance',
destroy: 'iConnect.FunctionCode.doFunctionCodeMaintenance',
read: 'iConnect.FunctionCode.getFunctionCodeRecords',
update: 'iConnect.FunctionCode.doFunctionCodeMaintenance'
},
reader: {
type: 'json',
root: 'data',
successProperty: 'success',
totalProperty: 'totalCount'
},
writer: {
encode: false,
root: 'data',
type: 'json',
writeAllFields: false
}
}
});

I have a method named: onAddClick() that is called in the controller when the "Add" button is clicked.

Controller


Ext.define('iConnect.controller.miscellaneous.FunctionCode', {
extend: 'Ext.app.Controller',
models: ['FunctionCode'],
stores: ['FunctionCodes'],
views: ['miscellaneous.FunctionCode'],
refs: [
{ ref: 'functionCodeGridPanel', selector: 'functionCodeEditableGrid' }
],

init: function () {
this.control({
'button[action=addFunctionCode]': {
click: this.onAddClick
},
'button[action=deleteFunctionCode]': {
click: this.onDeleteClick
},
'button[action=saveChanges]': {
click: function() {}
},
'functionCodeEditableGrid': {
beforerender: function () { },
selectionchange: this.onSelectChange
}
});
},

onLaunch: function () {
// Extract query string parameters from URL
var queryStringParams = Ext.Object.fromQueryString(window.location.search);
var applicationCode, storeParams = "";

Ext.Object.each(queryStringParams, function(key, value) {
if (key == "SelectedAppCodeObjId") {
var selectedAppCodeObjId = parseInt(value);
storeParams = {
params: {
'appCodeObjId': selectedAppCodeObjId
}
};
}
if (key == "SelectedAppCode") {
applicationCode = value;
}
});
//console.log(storeParams);
// Update page heading with selected Application Code
var applicationCode = queryStringParams.SelectedAppCode;
var pageHeadingContainer = Ext.ComponentQuery.query('container[id="functionCodeTitle"]')[0];
var pageHeadingText = pageHeadingContainer.el.dom.textContent;
pageHeadingContainer.el.update(pageHeadingText + applicationCode);
// Load store based upon passed in parameters
Ext.getStore("FunctionCodes").load(storeParams);
},

onAddClick: function (button) {
var editor, me, panel, rec;
me = this;
panel = button.up('panel');

rec = Ext.create('iConnect.model.FunctionCode', {
ObjId: '',
FunctionCode: '',
FunctionCodeDescription: '',
ApplicationCode: '',
FdgUrl: ''
});

editor = panel.plugins[0];
editor.cancelEdit();

Ext.getStore('FunctionCodes').insert(0, rec);

editor.startEditByPosition({
row: 0,
column: 1
});
},

onDeleteClick: function () {
var selection = this.getView().getSelectionModel().getSelection()[0];
if (selection) {
this.store.remove(selection);
}
},

onSelectChange: function (selModel, selections) {
var me = this;
me.getFunctionCodeGridPanel().setDisabled(selections.length === 0);
}
});

My grid looks like so:

Grid


Ext.define('iConnect.view.miscellaneous.FunctionCode', {
extend: 'Ext.grid.Panel',
alias: 'widget.functionCodeEditableGrid',

initComponent: function () {
var me = this;

Ext.apply(me, {
id: 'functionCodeGridId',
border: true,
store: 'FunctionCodes',
margin: '25 0 0 15',
plugins: [Ext.create('Ext.grid.plugin.CellEditing', {
clicksToEdit: 1
})],
selType: 'cellmodel',
width: 650,
dockedItems: [{
dock: 'top',
xtype: 'toolbar',
items: [{
text: 'Add',
itemId: 'add',
action: 'addFunctionCode'
}, {
text: 'Delete',
itemId: 'delete',
action: 'deleteFunctionCode'
}, {
text: 'Save',
itemId: 'save',
action: 'saveChanges'
}]
}],
columns: [
new Ext.grid.RowNumberer,
{
dataIndex: 'objId',
hidden: true,
menuDisabled: true,
resizable: false,
sortable: true,
text: 'ObjId',
width: 25
}, {
dataIndex: 'functionCode',
editor: {
allowBlank: false,
xtype: 'textfield'
},
hideable: false,
menuDisabled: true,
resizable: false,
sortable: true,
text: 'Function Code',
width: 125
}, {
dataIndex: 'functionCodeDescription',
editor: {
allowBlank: false,
xtype: 'textfield'
},
flex: 1,
hideable: false,
menuDisabled: true,
resizable: false,
sortable: true,
text: 'Function Description'
}, {
dataIndex: 'applicationCode',
hidden: true,
menuDisabled: true,
resizable: false,
sortable: true,
text: 'Application Code',
width: 100
}, {
dataIndex: 'fdgUrl',
hideable: false,
menuDisabled: true,
resizable: false,
sortable: true,
text: 'FDG Code',
width: 100
}]
});

me.callParent(arguments);
}
});


If anyone can help me identify where I messed up that would be a BIG help.

Thanks,

Shawn

scottmartin
22 Mar 2013, 10:45 AM
Have you set a breakpoint in your code to see where you are spinning your wheels?

scgrif32
25 Mar 2013, 9:48 PM
Thank you for responding. I was able to determine once I removed "autoSync: true" from my store configuration I no longer had the issue of Firefox crashing or Chrome throwing "Uncaught RangeError: Maximum call stack size exceeded". However, whenever I try to perform any store operation (create, destroy or update) I receive the same error I was getting when I had "autoSync" set to true.

I am at a total loss. I thought implementing the cellEditing plugin would be straightforward despite using Ext.direct but I am WAY off!

I moved the proxy configuration from the store to the model and added more logic to my controller to manually handle create, destroy and update operations. Clearly these three operations occur whenever I try to sync the store so the proper CRUD operation can take place.

Controller



Ext.Direct.addProvider(iConnect.FunctionCodeHandler);
Ext.define('iConnect.controller.miscellaneous.FunctionCode', {
extend: 'Ext.app.Controller',
models: ['FunctionCode'],
stores: ['FunctionCodes'],
views: ['miscellaneous.FunctionCode'],
refs: [
{ ref: 'functionCodeGridPanel', selector: 'functionCodeEditableGrid' }
],

init: function () {
this.control({
'button[action=addFunctionCode]': {
click: this.onAddClick
},
'button[action=deleteFunctionCode]': {
click: this.onDeleteClick
},
'button[action=saveChanges]': {
click: this.onSaveClick
},
'functionCodeEditableGrid': {

}
});
},

onLaunch: function () {
// Extract query string parameters from URL
var queryStringParams = Ext.Object.fromQueryString(window.location.search);
var applicationCode, storeParams = "";

Ext.Object.each(queryStringParams, function(key, value) {
if (key == "SelectedAppCodeObjId") {
var selectedAppCodeObjId = parseInt(value);
storeParams = {
params: {
'appCodeObjId': selectedAppCodeObjId
}
};
}
if (key == "SelectedAppCode") {
applicationCode = value;
}
});
// Update page heading with selected Application Code
var applicationCode = queryStringParams.SelectedAppCode;
var pageHeadingContainer = Ext.ComponentQuery.query('container[id="functionCodeTitle"]')[0];
var pageHeadingText = pageHeadingContainer.el.dom.textContent;
pageHeadingContainer.el.update(pageHeadingText + applicationCode);
// Load store based upon passed in parameters
Ext.getStore("FunctionCodes").load(storeParams);
},

onAddClick: function (button) {
var me = this;
var objPanel = button.up('gridpanel');

var rec = Ext.create('iConnect.model.FunctionCode', {
ObjId: '',
FunctionCode: '',
FunctionCodeDescription: '',
ApplicationCode: '',
FdgUrl: ''
});

editor = objPanel.plugins[0];
editor.cancelEdit();

Ext.getStore('FunctionCodes').insert(0, rec);

editor.startEditByPosition({
row: 0,
column: 1
});
},

onDeleteClick: function (button) {
var me = this;
var objPanel = button.up('gridpanel');
var sm = objPanel.getView().getSelectionModel();
var selection = sm.getSelection();

if (selection.length > 0) {
Ext.Msg.show({
title: 'Delete Record?',
msg: 'You are performing a perminent operation, this cannot be undone. Proceed?',
buttons: Ext.Msg.YESNO,
icon: Ext.Msg.QUESTION,
fn: function (btn) {
if (btn === 'yes') {
var editor = objPanel.plugins[0];
editor.cancelEdit();

var store = me.getFunctionCodesStore();

store.remove(selection);
store.sync();
/*
store.sync({
success: function (rec, op) {
Ext.Msg.alert(
"Attention Required!",
"Successfully synced store..."
);
},
failure: function (rec, op) {
Ext.Msg.alert(
"Attention Required!",
"Store sync() failed..."
);
}
});
*/
}
}
});
}
else {
// No row was selected to update
Ext.Msg.alert(
"Attention Required!",
"Please select a record to delete."
);
return false;
}
},

onSaveClick: function(button) {
var me = this;
var objPanel = button.up('gridpanel');
var sm = objPanel.getView().getSelectionModel();

var dirtyRecords = [];
var updatedRecordsnewRecords = [];
var updatedRecords = [];

Ext.Msg.alert("Status", "Save all dirty or new records");

var store = me.getFunctionCodesStore();
var newRecords = store.getNewRecords();
var updatedRecords = store.getUpdatedRecords();
var editor = objPanel.plugins[0];
editor.cancelEdit();
// Sync new records (OperationType: Create)
if (newRecords.length > 0) {
console.log(newRecords);
store.sync();
// store.load();
}
// Sync updated records (OperationType: Update)
if (updatedRecords.length > 0) {
console.log(updatedRecords);
}

if (dirtyRecords.length > 0) {
Ext.Msg.alert(
"Attention Required!",
dirtyRecords.length + " records have been updated."
);
// me.getFunctionCodesStore().sync();
}
else {
Ext.Msg.alert(
"Attention Required!",
"No records have been updated."
);
}
}
});

Model



Ext.define('iConnect.model.FunctionCode', {
extend: 'Ext.data.Model',
idProperty: 'objId',
fields: [
{ name: 'objId', mapping: 'FunctionCodeObjId', type: 'int' },
{ name: 'functionCode', mapping: 'FunctionCode', type: 'string' },
{ name: 'functionCodeDescription', mapping: 'FunctionCodeDescription', type: 'string' },
{ name: 'applicationCode', mapping: 'ApplicationCode', type: 'string' },
{ name: 'fdgUrl', mapping: 'FdgUrl', type: 'string' }
],
proxy: {
type: 'direct',
api: {
create: 'iConnect.FunctionCode.doFunctionCodeMaintenance',
destroy: 'iConnect.FunctionCode.doFunctionCodeMaintenance',
read: 'iConnect.FunctionCode.getFunctionCodeRecords',
update: 'iConnect.FunctionCode.doFunctionCodeMaintenance'
},
reader: {
type: 'json',
root: 'data',
successProperty: 'success',
totalProperty: 'totalCount'
},
writer: {
encode: false,
root: 'data',
type: 'json',
writeAllFields: true
}
}
});

Store



Ext.define('iConnect.store.FunctionCodes', { extend: 'Ext.data.Store', model: 'iConnect.model.FunctionCode', autoLoad: false });

View


Ext.define('iConnect.view.miscellaneous.FunctionCode', {

extend: 'Ext.grid.Panel',
alias: 'widget.functionCodeEditableGrid',

initComponent: function () {
var me = this;

me.editing = Ext.create('Ext.grid.plugin.CellEditing', {
clicksToEdit: 2
});

Ext.apply(me, {
id: 'functionCodeGridId',
border: true,
store: 'FunctionCodes',
margin: '25 0 0 15',
multiSelect: true,
plugins: [me.editing],
width: 650,
dockedItems: [{
dock: 'top',
xtype: 'toolbar',
items: [{
text: 'Create',
itemId: 'add',
action: 'addFunctionCode'
}, {
text: 'Delete',
itemId: 'delete',
action: 'deleteFunctionCode'
}, {
text: 'Save',
itemId: 'save',
action: 'saveChanges'
}]
}],
columns: [
new Ext.grid.RowNumberer,
{
dataIndex: 'objId',
hidden: true,
menuDisabled: true,
resizable: false,
sortable: true,
text: 'ObjId',
width: 25
}, {
dataIndex: 'functionCode',
editor: {
allowBlank: false,
xtype: 'textfield'
},
hideable: false,
menuDisabled: true,
resizable: false,
sortable: true,
text: 'Function Code',
width: 125
}, {
dataIndex: 'functionCodeDescription',
editor: {
allowBlank: false,
xtype: 'textfield'
},
flex: 1,
hideable: false,
menuDisabled: true,
resizable: false,
sortable: true,
text: 'Function Description'
}, {
dataIndex: 'applicationCode',
hidden: true,
menuDisabled: true,
resizable: false,
sortable: true,
text: 'Application Code',
width: 100
}, {
dataIndex: 'fdgUrl',
hideable: false,
menuDisabled: true,
resizable: false,
sortable: true,
text: 'FDG Code',
width: 100
}]
});
me.callParent(arguments);
}
});
Does anyone see something I am missing in the code i've included in this post?

Thanks,

Shawn

scottmartin
27 Mar 2013, 5:13 AM
Try commenting out your code in the add function to see what is causing your loop. You should also be able to see where this is happening in the callstack.

I would start with looking at using form.loadRecord(rec) in place of your code:
Ext.getStore('FunctionCodes').insert(0, rec); // inserting to store right away?

You should only update when the user has selected save, correct?
record.set(values) for an update

There are lot of manual calls to the store; sync,load .. This should happen automatically.

Scott.

scgrif32
27 Mar 2013, 10:20 AM
Scott,

I have investigated further and determined that all operations (Create, Destroy, Update) are resulting in this error in Chrome and freezing up Firefox. I noticed that the row data that is modified (create, destroy, update) immediately results in this error but more importantly is wrapped in an array, which I thought was odd.

That resulted in me looking back at other examples of cellEditing (very few and only one using direct in the API tutorials) and I noticed that the row data (model instance - object) being modified looked very different as I stepped through the code then what I was getting from my grid. Could this be the culprit that is resulting in the error?

I have come to this conclusion because the call to sync the store (Ext.getStore('somestore').sync()) is where the error gets thrown. This happens no matter where I call Ext.getStore('somestore').sync().

Now, I am making the manual calls to sync the store because I want to be able to save ALL create/update's. I purposefully did not use "autoSync: true" because that configuration option being enabled is what initially resulted in this error being thrown.

Thank you for your help and I hope others who are scanning this forum will also provide their input if they notice an issue in my code or have experienced this problem before.

Thank you,

Shawn