Using grid as field in ExtJS - extjs

Binding model property to a form field is pretty easy in ExtJS:
// ...skipped everything until fields config for brevity
}, {
xtype: 'textfield',
bind: '{modelInstance.someField}'
}, { // ...
In this case, modelInstance string field someField will be synchronized to textbox value, thanks to two way binding. And that is great.
What I want to achieve is to get same kind of behavior in the case when model field is not a string but an array. This is the model:
Ext.define('namespace.model.CustomModel', {
fields: ['easyField', {
name: 'hardField',
type: 'auto' // This will be an array during runtime
}],
idProperty: 'easyField'
});
I would like to do something like this:
// ...skipped everything until fields config for brevity,
// assume that viewmodel and everything else are set up as expected
}, {
xtype: 'textfield',
bind: '{modelInstance.easyField}'
}, {
xtype: 'gridfield',
bind: {
gridArray: '{modelInstance.hardField}'
}
}, { // ...
Understandably, I want gridfield component to extend Ext.grid.Panel and synchronize its store data to modelInstance field hardField.
Currently I have this:
Ext.define('namespace.form.field.GridField', {
extends: 'Ext.grid.Panel',
xtype: 'gridfield',
// skip requires for brevity
viewModel: {
stores: {
gridFieldItems: {
type: 'gridfielditems' // Simple in memory store
}
},
data: {
}
},
store: '{gridFieldItems}',
// This config enables binding to take place as it creates getters and setters,
// gridArray is set initially to '{modelInstance.hardField}' as expected
config: {
gridArray: null
},
// This is necessary for this grid-field component to update
// '{modelInstance.hardField}' back in the owners viewModel.
publishes: {
gridArray: true
},
// ???
// bind: {
// gridArray: bind gridArray to store data somehow?
// }
});
Here's the problem:
how do I inject existing modelInstance.hardField array as gridFieldItems store initial data,
how do I bind gridArray config to store data so that it is updates as we go along cruding the grid,
do all of these in an elegant MVVM way without writing a bunch of listeners trying to force syncing between JS objects.
Please provide tested solution which is known to work, I already tried a lot of different ways myself, but without success so far.

Here is a working fiddle to achieve this binding. The easy way is to bind the array field with "data" attribute of a store.
A good suggestion on the work you've done is to avoid defining a viewmodel inside a generic component (gridfield) but use viewmodels only on you application specific views.
On you generic component you should define only configuration attributes with setter/getter/update logics in order to be able to use them with bind. In this case there is no need of custom properties as the store is enough.
edit
To avoid the "easy binding" you can implement the set/get logic of the array in your girdfield component. Fore example using the "updateGridArray" method called by the setter and the "datachanged" event of the store.
The fiddle is updated and the example girdfield uses the cell editing plugin to show the 2-way binding.
fiddle: https://fiddle.sencha.com/#view/editor&fiddle/2a3b
Ext.define('Fiddle.model.CustomModel', {
extend: 'Ext.data.Model',
fields: ['easyField', {
name: 'hardField',
type: 'auto' // This will be an array during runtime
}],
idProperty: 'easyField'
});
Ext.define('Fiddle.fiddleview.CustomViewModel', {
extend: 'Ext.app.ViewModel',
alias: 'viewmodel.fiddleview',
data: {
currentModel: null
}
});
Ext.define('Fiddle.form.field.GridField', {
extend: 'Ext.grid.Panel',
xtype: 'gridfield',
config: {
gridArray: null
},
publishes: [
'selection',
'gridArray'
],
selModel: 'cellmodel',
plugins: {
cellediting: {
clicksToEdit: 1
}
},
columns: [{
text: 'Column 1',
flex: 1,
editor: true,
dataIndex: 'field1'
}],
initComponent: function () {
this.store = {
fields: ['field1'],
data: [],
listeners: {
scope: this,
datachanged: function (store) {
this.setGridArray(store.getRange().map(function (record) {
return record.getData();
}));
}
}
};
this.callParent();
},
updateGridArray: function (gridArray) {
this.getStore().suspendEvent('datachanged');
if (Ext.isEmpty(gridArray)) {
this.getStore().removeAll();
} else {
this.getStore().loadData(gridArray);
}
this.getStore().resumeEvent('datachanged');
}
});
var myView = Ext.create('Ext.container.Container', {
renderTo: Ext.getBody(),
viewModel: 'fiddleview',
items: [{
xtype: 'gridfield',
bind: {
gridArray: '{currentModel.hardField}'
}
}]
});
// Bind on model's array to check two-way update
// It will execute also on cell edit
myView.getViewModel().bind('{currentModel.hardField}', function (value) {
window.alert('model\'s array changed');
});
// The binding is now active, when the "currentModel" in the viewmodel changes, grid rows are refresched
myView.getViewModel().set('currentModel', new Fiddle.model.CustomModel({
hardField: [{
field1: 'value1'
}]
}));
// Also when data changes in the "currentModel"
myView.getViewModel().get('currentModel').get('hardField').push({
field1: 'value2'
});

Related

How to display data from store in Ext.Component ExtJS

I have store which return data from server:
Ext.define('Admin.store.dashboard.NPS', {
extend: 'Ext.data.Store',
alias: 'store.nps',
autoLoad : false,
proxy : {
type: 'api',
url : SITE_URL + '/api/surveys/nps'
},
fields : [
{
type: 'float',
name: 'nps'
}
]
});
And I want to display that data in tpl of Ext.Component:
Ext.define('Admin.view.dashboard.NPSPercent', {
extend: 'Ext.Component',
xtype: 'nps-percent',
bind: {
data: {
nps_percent: '{nps}'
}
},
tpl: new Ext.XTemplate('<div class="percent">+{nps_percent.nps}</div>')
});
I tried bind data to loaded store but it dose not work.
Templates in components expect regular things like arrays, not stores. To render a template using a store as the datasource, use Ext.view.View class instead of just a component.

ExtJs 6: How to bind data between two grids?

I have 3 items in my main panel (Main.js):
Form (display data from selected row of Grid1)
Grid1 -Ext.grid.Panel (which get data from JSON file)
Grid2 -Ext.grid.Panel (should display some of the columns from selected row in Grid1)
All 3 views share same MainModel.js associated with Main.js.
I am able to bind Grid1 data to form using formula:
formulas: {
someVal: {
bind: '{employeeDetails.selection}', //reference to grid1
get: function(item){
return item;
}
},
Form-
items:[
{
xtype: 'form',
title: 'Form',
defaultType: 'textfield',
items: [
{
xtype: 'displayfield',
fieldLabel: 'ID',
bind: '{someVal.id}'
}, //...
But I cannot find any way to do the same between Grid1 and Grid2. I googled for hours. Only source for data for ExtJs grid seems to be store. Essentially is there any way to populate grid other than using store. Can we use bind inside columns or something? Thanks.
EDIT:
updated formula for selection:
myStoreDataFormula: {
bind:{
bindTo:'{employeeDetails.selection}',
deep:true
},
get: function(employee){
if(employee!=null){
var empArray = [];
empArray.push(employee.data);
return empArray;
}
}
}
A somewhat obscure feature when using a store defined inside a viewmodel, is that instead of defining concrete values, you can use the '{ ... }' mustache to bind to other viewmodel fields / formulas, or component configs that are published via their reference (personally I found this most useful for putting path variable's into url of the store's proxy).
Here is an example of grid bound to store, which in turn has it's data bound to a formula:
Ext.define('MyView', {
viewModel: {
stores: {
myStore: {
fields: ['name'],
data: '{myStoreDataFormula}'
}
},
formulas: {
myStoreDataFormula: function(get) {
return [{
name: 'Foo'
}, {
name: 'Bar'
}];
}
}
},
extend: 'Ext.grid.Panel',
xtype: 'MyView',
bind: {
store: '{myStore}'
},
columns:[{
dataIndex: 'name',
flex: 1
}]
});
Ext.application({
name : 'Fiddle',
launch : function() {
Ext.create({
xtype: 'MyView',
width: 300,
height: 300,
renderTo: Ext.getBody()
});
}
});
Yes, this would still have you to have 2 stores, but you can make second grid's store to be fully dependent on the first grid's published selection config.

ExtJS 6 ViewModel store bindings and setters

I am using ExtJS 6 to build a filterable dataview panel, this is my panel : Panel.js
Ext.define('myApp.view.main.widgets.Panel', {
extend: 'Ext.panel.Panel',
....
viewModel: {
type: 'widgets-view-model',
stores: {
widgets: {
type: 'widgets'
}
}
},
items: [{
xtype: 'searchfield',
name: 'widgets-filter',
height: 24,
width: '100%',
bind: {
store: '{store}'
}
}, {
margin: '5 0 0 0',
autoScroll: true,
bodyPadding: 1,
flex: 1,
frame: true,
referenceHolder: true,
bind: {
widgets: '{widgets.data.items}'
},
setWidgets: function (widgets) {
this.lookupReference('the-widget-items-panel').setWidgets(widgets);
},
items: [{
layout: {
type: 'vbox',
pack: 'start',
align: 'stretch'
},
items: [{
reference: 'the-widget-items-panel',
xtype: 'the-widgets'
}]
}]
}]
});
The viewModel does nothing;
Ext.define('myApp.view.main.widgets.WidgetsViewModel', {
extend: 'Ext.app.ViewModel',
alias:'viewmodel.widgets-view-model'
});
In the view controller, I do the following in the view's afterrender event handler;
onAfterRender: function () {
var store = this.view.getViewModel().getStore('widgets');
store.load();
}
At which point, the widgets setter "setWidgets" is called as expected. However when I filter the store using the store's "filterBy" method, I expect the widgets setter "setWidgets" to be called, but it isn't.
I also tried resetting the store by doing the following;
store.removeAll();
store.load(function () {
console.log('reloaded...!')
});
to see if a reload of store data will trigger the widgets setter "setWidgets", but it doesn't.
It appears changes to the viewModel store only triggers calls to setters once only.
QUESTION:
Is this a feature of the framework or am I configuring things incorrectly.
How do I re-configure view/store/viewModel so that the widgets setter "setWidgets" is called for every update/change to the store, i.e. when;
data is loaded
store is filtered
store data changes
store updates
Filtering a store doesn't change the items instance, it only changes the contents - that's why setWidgets isn't being called again. If you want to respond to data changing in the store, you need to listen to events from the store.
Also - binding on widget.data.items is a bad idea; that's plugging into the internal structure of the store. Listen to the events instead:
load
filterchange
datachanged
update

Using single data store for single grid instance which is used everywhere in extjs

SCENARIO
I am having a gridpanel(say grid),which is been used everywhere in my application.Suppose components (panel1 and panel2) are using grid.This grid is using a store(store1).I want to update grid in panel1 without updating grid in panel2.
MY PROBLEM
I dont want to use different stores for grid in panel1 and panel2.
Is their any way this could be achieved.
I tried binding store(with new data) to grid in panel1.But grids in both panel1 and panel2 are getting updated.
If I was not clear enough,Please let me know.Please help me resolve this.Thnks a lot.
**For reference**
/*my grid*/
Ext.define('APP.view.mygrid', {
extend: 'Ext.grid.Panel',
alias: 'widget.mygrid',
store:'store1',
columns: [ ... ]
/*my panels*/
{
xtype:'panel',
items:[
{
xtype :'mygrid',
}
],
xtype:'panel',
items:[
{
xtype :'mygrid',
}
],
}
I assume that you update your grid by some local changes in the store, and you do not make a call to your web service. Then you can try doing this:
store.suspendEvents(false); // false: do not queue events
// do some data changes now
store.resumeEvents();
grid.getView().refresh(); // only on the first grid
You can create the store for your second panel, so try this:
var secondStore=Ext.create('your_store');
items: [
{
xtype: 'gridpanel',
title: 'grid1',
store: 'your_store'
},{
xtype: 'gridpanel',
title: 'grid2',
store: secondStore
}
]
I have resolved this by creating a instace of the store each time when
the grid is called.Please have a look :
initComponent : function() {
this.items = [
{
xtype : 'myPanelgrids'
name : 'panel1' // this is the extra config to identify the grid in added in my panel1
}
];
this.callParent();
},
listeners: {
render: function(){
var store = Ext.create('Ext.data.Store',
{
model: 'App.model.MyModel',
autoLoad: true,
proxy: {
type: 'ajax',
url:'app/data/configdata.json',
reader: {
type: 'json',
root : 'myroot'//maintained a json property file with different roots for grids in panel1 and panel2
}
}
}
);
this.down('grid').reconfigure(store);
}
}
I am updating the store of particular grid from my controller.

Update or Reload Store of ExtJs ComboBox

I'd like to know a way of how to update or reload the list values of a ExtJs ComboBox. For instance, I have a some checkboxes. These checkboxes determine what values the ComboBox should have. So, after selecting some of those, I click the drowndown list (combobox) to see the values.
In short, how can I change the values (store) of a ComboBox already has.
Hope someone can help me
Thanks
I've been using this undocumented ExtJs API function to change the store at runtime:
mycombobox.bindStore(newStore);
as stated by the support team in http://www.sencha.com/forum/showthread.php?40749-Change-Combobox-store-data-update.
Edit: If I want to put the new data when the store is populated, I do something like this
storeMyStore = new Ext.data.Store({
...
listeners: {
load: function(this, records, options) {
cbMyCombo.bindStore( storeMyStore );
}
}
});
It goes a little something like this
{
xtype: 'checkbox',
//configs
listeners : {
checked : function (checkbox, checkedBool) {
var yourCombo = Ext.getCmp(yourComboID);
//I'm not sure what params you will need to reload the comboBox from your
// service but hopfully this will give the jist of things. . .
yourCombo.store.reload(
{
params:
{yourParam : checkedBool},
{yourRowID : rowID}
});
}
}
Here I making a combobox that is updated after a selection is made on another ComboBox.
Basically, the final user can use the two comboboxes to select a main category and a sub-category, which is based on the selection made on the first combobox.
This is the store for First comboBox:
Ext.define("StoreSubject", {
extend: "Ext.data.Model",
fields: [
{
name: 'i_Id'
},
{
name: 's_Value'
}
]
});
var StoreSubject = Ext.create('Ext.data.JsonStore', {
model: 'StoreSubject',
proxy: {
type: 'ajax',
url: '../General/AdministrationDefaultXMLDOM.aspx?qid=15',
reader: {
type: 'json'
}
}
});
StoreSubject.load();
This is the store for Second comboBox:
Ext.define("StoreLanguageGroup", {
extend: "Ext.data.Model",
fields: [
{
name: 'i_Id'
},
{
name: 's_Value'
}
]
});
var StoreLanguageGroup = Ext.create('Ext.data.JsonStore', {
model: 'StoreLanguageGroup',
proxy: {
type: 'ajax',
url: '../General/AdministrationDefaultXMLDOM.aspx?qid=16',
reader: {
type: 'json'
}
}
});
My code for Comobox is looks like this..
First ComboBox :
var cmbSubjectName = Ext.create('Ext.form.field.ComboBox', {
id: 'cmbSubjectName',
fieldLabel: '<b>Subject</b>',
name: 'cmbSubjectName',
valueField: 's_Value',
displayField: 's_Value',
allowBlank: false,
anchor: '80%',
labelWidth: 150,
emptyText: '[--Choose--]',
minChars: 0,
store: StoreSubject,
queryMode: 'local',
typeAhead: true,
listeners: {
'select': {
fn: function (combo, value) {
var strSubjectName = Ext.getCmp('cmbSubjectName').getValue();
Ext.getCmp('cmbLanguageType').clearValue();
Ext.getCmp('cmbLanguageType').getStore().load({
params: {
SubjectName: strSubjectName
}
});
}
}
},
});
This code is used to call and override the combox store (Impotent otherwise it will keep on loading )
Ext.override(Ext.LoadMask, {
onHide: function () {
this.callParent();
}
});
//---------------------------
Second ComboBox :
var cmbLanguageType = Ext.create('Ext.form.field.ComboBox', {
id: 'cmbLanguageType',
fieldLabel: '<b>Language</b>',
multipleSelect: false,
name: 'cmbLanguageType',
valueField: 's_Value',
displayField: 's_Value',
allowBlank: false,
anchor: '80%',
labelWidth: 150,
emptyText: '[--Choose--]',
minChars: 0,
store: StoreLanguageGroup,
queryMode: 'local',
typeAhead: true,
});
Hope this will helps you.. and Please rate my answer
In an event listener on the checkboxes, get a reference to the store that your ComboBox is reading from. Then invoke its add or remove functions to update the data in the store based on what check box is checked Once the updates are complete, invoke a doLayout() on the ComboBox component, it should re-render itself based on the current state of the store.
Although I think there is a way to have it automatically update any time the store updates -- I haven't used that yet.

Resources