I have an ExtJS 6.5.1 app. I am unable to bind a grid column to the viewModel. I am using the same viewModel for a grid && a form.
If I bind the fieldLabel it works. If I bind the grid title to that viewModel that also works. Its just the column header I am unable to bind.
I get the following errors:
Ext.mixin.Bindable.applyBind(): Cannot bind header on Ext.grid.column.Column - missing a setHeader method.
And
this[binding._config.names.set] is not a function
Someone elsewhere was getting similiar errors for development mode because some required classes weren't loading so he was able to resolve it by requiring Ext.data.proxy.*. I tried the same and just "*" but got the same erorrs.
Here is the FIDDLE.
The header config deprecated since version 4.0 use text instead.
Paste bellow code in your FIDDLE it will work and bind perfectly.
CODE SNIPPET
Ext.define('MyApp.view.TestViewModel', {
extend: 'Ext.app.ViewModel',
alias: 'viewmodel.test',
data: {
title: ''
},
constructor: function (config) {
var me = this;
this.callParent(arguments);
me.setStores({
lang: {
fields: ['title'],
proxy: {
type: 'ajax',
url: 'data.json',
reader: {
type: 'json'
}
},
autoLoad: true,
listeners: {
load: function (store, records) {
me.set('title', store.getAt(0).get('title'));
}
}
}
});
}
});
Ext.define('MyApp.view.TestGrid', {
extend: 'Ext.grid.Panel',
title: "MY GRID",
xtype: "mygrid",
viewModel: {
type: 'test'
},
columns: [{
text: "Col1"
}, {
bind: {
text: "{title}"
},
flex: 1
}]
});
Ext.define('MyApp.view.TestForm', {
extend: 'Ext.form.Panel',
layout: 'fit',
title: "MY FORM",
xtype: "myform",
viewModel: {
type: 'test'
},
items: [{
xtype: "textfield",
bind: {
fieldLabel: "{title}"
}
}]
});
Ext.onReady(function () {
Ext.create('Ext.container.Container', {
renderTo: Ext.getBody(),
layout: "fit",
flex: 1,
items: [{
xtype: "myform"
}, {
xtype: "mygrid"
}]
});
});
I have an ExtJS 6.5.1 app and I am just now starting to migrate our app from MVC to MVVM, so I am pretty clueless about VM and VC.
I have a viewModel with an inline store like so:
Ext.define("MYAPP.view.ViewportViewModel",{
extend:"Ext.app.ViewModel",
alias: 'viewmodel.viewport',
constructor: function(config) {
var me = this;
this.callParent(arguments);
me.setStores({
info: {
autoLoad:true,
fields:["TEST"],
proxy:{
type:"ajax",
url:"blah.html",
reader:{
type:"json"
}
}
}
});
}
});
From inside my controller, how can I "get" the store so I can change the URL, reload, pass extraParams etc?
Thanks
You can get your store using this.getViewModel().getStore('info') inside of ViewController.
After getting store you can set another url using store.getProxy().setUrl(), load using store.load() and for sending extra params store.getProxy().extraParams.
Here is example
//this one way
store.load({
url: '{your url here}',
params: {
userid: 22216
}
});
//this another way
store.getProxy().setUrl('{your url here}');
store.getProxy().extraParams = {
userid: 22216
};
store.load();
In this FIDDLE, I have created a demo using view model and view controller. I hope this will help/guide you to achieve your requirement.
CODE SNIPPET
Ext.application({
name: 'Fiddle',
launch: function () {
Ext.define('MyViewController', {
extend: 'Ext.app.ViewController',
alias: 'controller.myview',
onRefreshButtonTap: function () {
var info = this.getViewModel().getStore('info');
info.getProxy().setUrl('data2.json');
info.load();
}
});
Ext.define("ViewportViewModel", {
extend: "Ext.app.ViewModel",
alias: 'viewmodel.myvm',
constructor: function (config) {
var me = this;
this.callParent(arguments);
me.setStores({
info: {
autoLoad: true,
fields: ['name', 'email', 'phone'],
proxy: {
type: 'ajax',
url: 'data1.json',
reader: {
type: 'json',
rootProperty: ''
}
}
}
});
}
});
//creating panel with GRID and FORM
Ext.create({
xtype: 'panel',
controller: 'myview',
title: 'Binding Example',
renderTo: Ext.getBody(),
viewModel: {
type: 'myvm'
},
layout: 'vbox',
items: [{
xtype: 'grid',
flex: 1,
width: '100%',
bind: '{info}',
columns: [{
text: 'Name',
dataIndex: 'name'
}, {
text: 'Email',
dataIndex: 'email',
flex: 1
}, {
text: 'Phone',
dataIndex: 'phone'
}],
listeners: {
itemclick: 'onGridItemClick'
}
}],
tbar:[{
text:'Refresh',
handler:'onRefreshButtonTap'
}]
});
}
});
I binded a placeHolder in 'selectfield' like this:
{
xtype : 'selectfield',
bind : {
store : '{chapters}',
placeHolder : '{chapterPlaceHolder}'
}
}
Now i want to change the data of 'chapterPlaceHolder' in the ViewModel from store listener:
Ext.define('SomeViewModel', {
extend : 'Ext.app.ViewModel',
data : {
chapterPlaceHolder : null
},
stores : {
chapters : {
model : 'model.SiteChapter',
listeners: {
datachanged: function() { how to change the 'chapterPlaceHolder' in data? }
}
}
}
});
Hope i was clear enoght...
Define the event handler on a view controller. View controllers provide a method, getViewModel, to access the view model. The controller should be configured on the same class as the view model. This example assumes that is the select field.
Ext.define('Fiddle.app.ViewModel', {
extend: 'Ext.app.ViewModel',
alias: 'viewmodel.FiddleViewModel',
data: {
chapterPlaceHolder: null
},
stores : {
chapters: {
listeners: {
datachanged: 'dataChangedHandler'
}
}
}
});
Ext.define('Fiddle.app.ViewController', {
extend: 'Ext.app.ViewModel',
alias: 'viewmodel.FiddleViewController',
dataChangedHandler: function(store, eOpts) {
this.getViewModel.set('chapterPlaceHolder', ...);
}
});
{
xtype: 'selectfield',
bind: {
store : '{chapters}',
placeHolder : '{chapterPlaceHolder}'
},
controller: 'FiddleViewController',
viewModel: {
type: 'FiddleViewModel'
}
}
You need to get viewmodel inside of datachanged event. After getting viewmodel you can use get or set to change value of any field inside of view-model.
In this FIDDLE , I have created a demo using your code and put my efforts in same code. I hope this will help/guide you to achieve your requirement.
CODE SNIPPET
Ext.application({
name: 'Fiddle',
launch: function () {
Ext.define('SomeViewModel', {
extend: 'Ext.app.ViewModel',
alias: "viewmodel.demoVM",
data: {
chapterPlaceHolder: null
},
stores: {
chapters: {
listeners: {
datachanged: function () {
var vm = Ext.ComponentQuery.query('#myform')[0].getViewModel();
vm.set('chapterPlaceHolder', 'data changed event called......');
//how to change the 'chapterPlaceHolder' in data ?
}
}
}
}
});
Ext.create('Ext.form.Panel', {
itemId: 'myform',
fullscreen: true,
viewModel: {
type: 'demoVM'
},
defaults: {
margin: 20
},
items: [{
xtype: 'fieldset',
items: [{
xtype: 'selectfield',
autoSelect: false,
bind: {
store: '{chapters}',
placeHolder: '{chapterPlaceHolder}'
}
}]
}, {
xtype: 'button',
text: 'Load Data In store ',
handler: function (btn) {
var vm = btn.up('formpanel').getViewModel();
vm.get('chapters').loadData([{
text: 'First Option',
value: 'first'
}, {
text: 'Second Option',
value: 'second'
}, {
text: 'Third Option',
value: 'third'
}]);
//You can also set like below
//vm.set('chapterPlaceHolder', 'Data loaded on button click......');
}
}]
});
}
});
Let's say I've got a ViewController, ViewModel, and my View. In the View, I've got a form panel that gets a loaded record. When this record loads into the form, I want to hide or show a button based on the record's status field, so I figured do something with binding. However, it looks like binding is limited to only inverting, not actually using an expression. To get a better understanding, take a look at this code:
Ext.application({
name : 'Fiddle',
launch : function() {
Ext.define('User', {
extend: 'Ext.data.Model',
fields: ['name', 'status']
});
Ext.define('UserListController', {
extend : 'Ext.app.ViewController',
alias: 'controller.userlist'
});
Ext.define('UserListViewModel', {
extend: 'Ext.app.ViewModel',
alias: 'viewmodel.userlist'
});
Ext.define('UserList', {
extend: 'Ext.form.Panel',
controller: 'userlist',
viewModel: 'userlist',
tbar: [{
text: 'Add',
reference: 'addButton',
bind: {
//hidden: '{status == 2}'
}
}, {
text: 'Delete',
reference: 'deleteButton',
bind: {
//hidden: '{status == 1}'
}
}],
items: [{
xtype: 'displayfield',
name: 'name',
fieldLabel: 'Name'
}, {
xtype: 'displayfield',
name: 'status',
fieldLabel: 'Status'
}]
});
var myForm = Ext.create('UserList', {
width: 400,
height: 200,
renderTo: Ext.getBody()
});
var record = Ext.create('User', {
name: 'blah',
status: 2
});
myForm.loadRecord(record);
if (record.get('status') === 2) {
myForm.lookupReference('addButton').hide();
}
}
});
As you can see, I'm currently just probing the values of the record to hide the addButton. Is there anyway I can accomplish this with binding or some other approach? It's good to note that I also looked at formulas, but from what I'm understanding, that's only for changing how data is rendered, so it didn't seem like the proper route.
If your record is part of the view model data - use formulas, like:
formulas: {
hideDeleteButton: function (getter) {
return getter('record.status') === 2;
},
hideAddButton: function (getter) {
return getter('record.status') === 1;
}
}
And then in your view you can bind:
{
text: 'Add',
reference: 'addButton',
bind: {
hidden: '{hideAddButton}'
}
}, {
text: 'Delete',
reference: 'deleteButton',
bind: {
hidden: '{hideDeleteButton}'
}
}
A working example: https://fiddle.sencha.com/#fiddle/mcg
In my appliacation , I have a list and detail(form).I want to load data to Detail view(set data to textfields of form) when list item is clicked. For both list and detail, I am getting data from remote server. I am following MVC.
Now, When listItem is clicked, I am able to get data from server and save it to store and also showing detail view. But I am not able to bind data from store to textfields in form.
Model
Ext.define('App.model.Details', {
extend: 'Ext.data.Model',
config: {
fields: [
{name: 'Name', type: 'string'},
{name: 'BillingStreet', type: 'string'},
{name: 'BillingCity', type: 'string'}
]
}
});
Store
Ext.define('App.store.Details', {
extend: 'Ext.data.Store',
config: {
model: 'App.model.Details',
autoLoad :true,
grouper : function(record) {
return record.get('Name')[0];
},
}
});
list view
Ext.define('App.view.View', {
extend: 'Ext.List',
alias:'widget.contactlist',
fullscreen: true,
id: 'contactlist',
config:{
disableSelection:true,
store:'Contacts',
itemTpl:'{Name}',
items:[
{
xtype:'toolbar',
docked:'top',
title:'Leeds List'
}
]
}
});
Detail view
Ext.define("App.view.ListDetail", {
extend: "Ext.form.Panel",
requires: "Ext.form.FieldSet",
alias: "widget.listDetail",
config:{
scrollable:'vertical'
},
initialize: function () {
this.callParent(arguments);
var topToolbar = {
xtype: "toolbar",
docked: "top",
title: "Details"
};
this.add([
topToolbar,
{ xtype: "fieldset",
items: [{
xtype: 'textfield',
store: 'Details',
value : 'Name',
label: 'Name'
},
{
xtype: 'emailfield',
store: 'Details',
value : 'BillingStreet',
label: 'Email'
},
{
xtype: 'passwordfield',
store: 'Details',
value : 'BillingCity',
label: 'Password'
}
]
}
]);
}
});
Controller
Ext.define('App.controller.Main', {
extend: 'Ext.app.Controller',
config: {
refs: {
// We're going to lookup our views by xtype.
contactlist: "contactlist",
contactdetail: "listDetail",
//f_name:"#f_name"
},
control: {
contactlist: {
// The commands fired by the notes list container.
itemtap: "oneditLeadCommand"
}
},
routes: {
'contactlist': 'activateList'
}
},
activateList: function ()
{
Ext.Viewport.animateActiveItem(this.getContactlist(), this.slideRightTransition);
},
slideLeftTransition: { type: 'slide', direction: 'left' },
slideRightTransition: { type: 'slide', direction: 'right' },
oneditLeadCommand: function (list, index, target, record, e, eOpts)
{
console.log("onEditLead"+record.data.Id);
this.activateLeadDetail(record);
},
activateLeadDetail: function (record)
{
var contactDetail = this.getContactdetail();
//console.log("activateLeadDetail"+contactDetail.textfield);
//contactDetail.setRecord(record); // load() is deprecated.
//this.getF_name().setDisplayField("");
store = Ext.StoreMgr.get('Details');
//console.log("activateLeadDetail"+store);
store.setProxy({
type: 'ajax',
url : 'http://10.0.2.2:8080/SalesForce/leads/get-lead-details/00D90000000jvoU!AR4AQB6Xcjz4UNBKf12WOcYHWc31QxK2.fXTcbCvOq.oBosCrjBezhqm8Nqc1hrf8MKK5LjLAu8ZC5IqB1kdpWvJGLdWd5pJ/'+record.data.Id, // the json file that holds all our contact info.
reader: {
type: 'json'
}
});
store.load();
var record1 = Ext.StoreMgr.get('Details').getAt(0);
console.log("activateLeadDetail"+record1);
Ext.StoreMgr.get('Details').each(function(test){
console.log("for loop"+test.data);
});
contactDetail.setRecord(record1);
Ext.Viewport.animateActiveItem(contactDetail, this.slideLeftTransition);
},
// Base Class functions.
launch: function () {
this.callParent(arguments);
console.log("launch");
},
init: function () {
this.callParent(arguments);
console.log("init");
}
})
Please help to bind data to detail view.
So I'm guessing your Contacts Store is defined somewhere else but since this one is working you didn't paste the code here.
So one quick note on the model, where you should always define an idProperty. This is what Sencha use internally to define the "primary key" on your store and therefore work properly when you reload/refresh your store.
Ext.define('App.model.Details', {
extend: 'Ext.data.Model',
config: {
idProperty: 'Name', // Or something from your server maybe?
fields: [
{name: 'Name', type: 'string'},
{name: 'BillingStreet', type: 'string'},
{name: 'BillingCity', type: 'string'}
]
}
});
Secondly, why did you use the initialize method in your ListDetail view when you used the config method in your listView? if you specify the config instead, you will be able to to reuse some of this component in a more easy way somewhere else by doing something like
items: [
{
xtype: 'ListDetail',
anyOtherAttribute: value
}
]
But that's kinda out of scope here. But anyway. So what is wrong here I think is that you have defined a Store for each field of your panel. I'm sorry i can't test my hypothesis, but here is what I would do:
Ext.define("App.view.ListDetail", {
extend: "Ext.form.Panel",
requires: "Ext.form.FieldSet",
alias: "widget.listDetail",
config:{
scrollable:'vertical'
items:[
{
xtype: "toolbar",
docked: "top",
title: "Details"
},
{
xtype: "fieldset",
itemId: 'detailedListFiledset', //Overall, prefer itemId
items: [
{
xtype: 'textfield',
value : 'Name',
label: 'Name' // may be you want placeholders here?
},
{
xtype: 'emailfield',
value : 'BillingStreet',
label: 'Email' // and here..
},
{
xtype: 'passwordfield',
value : 'BillingCity',
label: 'Password' // and here..
}
]
}
]
}
});
Alright, and now the issue seems to be in your controller:
Add a ref to your fieldset
Add a reference to your store
create a afterload callback when your store is loaded (Details)
Either clear the store every time or append data and apply to filter to get the correct record (this is why the idProperty is very useful)
set the record of the fieldset and not the panel
I haven't had the chance to try that, but I'll do it later tonight. But git it a go though.
-- EDIT --
Ok I've finally been abe to code something for you.
A few issues were in your code. I don't really know why you need two stores, but let's say you do. I'm going to give you all the files I used (the two stores, the two models, the three views and the controller). Three main thing were wrong in your code:
you should not load the second store and try to get the record right after. use and 'load' or 'refresh' event for that
SetValues for a form is the correct function to use
You were missing the name property in your form so that the form know to which value of the store/model to bind to the field.
ContactModel:
Ext.define('App.model.Contact', {
extend: 'Ext.data.Model',
config: {
idProperty: 'id',
fields: [
{name: 'id', type: 'int'},
{name: 'name', type: 'string'}
]
}
});
ContactStore:
Ext.define('App.store.ContactStore', {
extend: 'Ext.data.Store',
requires:['App.model.Contact'],
config: {
storeId: 'ContactStore',
model: 'App.model.Contact',
autoLoad :true,
data: [
{id: 0, name: 'Foo'},
{id: 1, name: 'Bar'}
]
}
});
DetailModel:
Ext.define('App.model.Detail', {
extend: 'Ext.data.Model',
config: {
idProperty: 'id',
fields: [
{name: 'id', type: 'int'},
{name: 'name', type: 'string'},
{name: 'billingStreet', type: 'string'},
{name: 'billingCity', type: 'string'}
]
}
});
DetailStore:
Ext.define('App.store.DetailStore', {
extend: 'Ext.data.Store',
config: {
model: 'App.model.Detail',
autoLoad :true,
data: [
{id: 0, name: 'Foo', billingStreet:'here', billingCity: 'Somewhere'},
{id: 1, name: 'Bar', billingStreet:'there', billingCity: 'Somewhere else'}
]
}
});
ContactView:
Ext.define('App.view.ContactList', {
extend: 'Ext.List',
xtype: 'contactList',
fullscreen: true,
config: {
itemId: 'contactList',
store:'ContactStore',
emptyText: 'test',
itemTpl: new Ext.XTemplate(
'{name}'
),
items:[
{
xtype:'toolbar',
docked:'top',
title:'Leeds List'
}
]
}
});
DetailView:
Ext.define('App.view.Detail', {
extend: 'Ext.form.Panel',
requires: ['Ext.form.FieldSet'],
xtype: "detail",
config:{
scrollable:'vertical',
items: [
{
xtype: 'toolbar',
docked: 'top',
title: 'Details'
},
{
xtype: 'fieldset',
itemId: 'detailForm',
items: [{
xtype: 'textfield',
store: 'Details',
name: 'name',
placeHolder : 'Name',
label: 'Name'
},
{
xtype: 'textfield',
store: 'Details',
placeHolder : 'BillingStreet',
name: 'billingStreet',
label: 'BillingStreet'
},
{
xtype: 'textfield',
store: 'Details',
placeHolder : 'BillingCity',
name: 'billingCity',
label: 'BillingCity'
}
]
}
]
}
});
Main view:
Ext.define('App.view.Main', {
extend: 'Ext.Container',
xtype: 'main',
config: {
layout: 'hbox',
items: [
{
xtype: 'contactList',
flex:1
},
{
xtype: 'detail',
flex:2.5
}
]
}
});
Main Controller:
Ext.define('App.controller.Main', {
extend : 'Ext.app.Controller',
requires: [
'Ext.Toolbar',
'Ext.List',
'App.store.ContactStore',
'App.store.DetailStore',
'App.view.Detail',
'App.view.ContactList'
],
config: {
//#private
detailStore: null,
currentListIndex: -1,
views : [
'App.view.ContactList',
'App.view.Detail'
],
refs: {
list: 'contactList',
detail: 'detail',
detailForm: 'detail #detailForm'
},
control: {
list: {
itemtap: 'handleItemTapList'
}
}
},
launch: function() {
var store = Ext.getStore('DetailStore');
store.on('refresh', 'handleDetailStoreLoad', this);
this.setDetailStore(store);
},
handleItemTapList: function(list, index, target, record) {
this.setCurrentListIndex(index);
this.getDetailStore().load();
},
handleDetailStoreLoad: function (store) {
debugger;
var record = store.getAt(this.getCurrentListIndex());
this.getDetail().setValues(record.data);
}
});
We could argue on a few things but I tried to go straight to the point and make it work. If you have more questions please ask but this example is working for me. In my opinion, you might not need the second store, as the contact detail could be nested in the COntact store, and you could use the hasMany property of the store.
use form.setRecord() to load a record into a form
make sure your form elements have name property values that match the model name properties
as you have a store, you will have to get a reference to ONE model to load, using say getAt() or find() methods to get or find a model in your store