How to change "data" value in viewmodel from store listener? - extjs

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......');
}
}]
});
}
});

Related

ExtJs panel ViewModel getStore() call returns null instead of returning local ajax store object with URL bound to ViewModel data member

To reproduce this issue, I created a fiddle: https://fiddle.sencha.com/#view/editor&fiddle/3mk0 . The steps to reproduce are as follows:
Open your favorite browser and open the debugging tools to be able to view the JavaScript console
Navigate to the fiddle and run it
Double-click on a row - the grid is empty. Check the JS console: Uncaught TypeError: store is null (I used FF). The crux of the issue is that, in this line, let store = vm.getStore('testStore');, vm.getStore('testStore') returns null the first time. That's what I am trying to understand - why doesn't ExtJs initialize completely the ViewModel and the store, and instead it returns null. The problem is the binding in the url of the proxy. If the store doesn't have any binding in the url, it's going to work the first time as well.
Close the window, and double-click again on a row. This time the grid will show the data.
I know how to fix this - if I issue a vm.notify() before I set the store, it's going to work properly (I added a commented out line in the code).
Here is the source code from the fiddle:
app.js
/// Models
Ext.define('App.model.GroupRecord', {
extend: 'Ext.data.Model',
alias: 'model.grouprecord',
requires: [
'Ext.data.field.Integer',
'Ext.data.field.String'
],
fields: [{
type: 'int',
name: 'id'
}, {
type: 'string',
name: 'description'
}]
});
Ext.define('App.model.SomeRecord', {
extend: 'Ext.data.Model',
requires: [
'Ext.data.field.Integer',
'Ext.data.field.String'
],
fields: [{
type: 'int',
name: 'id'
}, {
type: 'string',
name: 'description'
}, {
type: 'int',
name: 'groupId'
}]
});
//// SomeGridPanel
Ext.define('App.view.SomeGridPanel', {
extend: 'Ext.grid.Panel',
alias: 'widget.somegridpanel',
requires: [
'App.view.SomeGridPanelViewModel',
'App.view.SomeGridPanelViewController',
'Ext.view.Table',
'Ext.grid.column.Number'
],
controller: 'somegridpanel',
viewModel: {
type: 'somegridpanel'
},
bind: {
store: '{testStore}'
},
columns: [{
xtype: 'numbercolumn',
dataIndex: 'id',
text: 'ID',
format: '0'
}, {
xtype: 'gridcolumn',
dataIndex: 'description',
text: 'Description'
}]
});
Ext.define('App.view.SomeGridPanelViewModel', {
extend: 'Ext.app.ViewModel',
alias: 'viewmodel.somegridpanel',
requires: [
'Ext.data.Store',
'Ext.data.proxy.Ajax',
'Ext.data.reader.Json'
],
data: {
groupId: null
},
stores: {
testStore: {
model: 'App.model.SomeRecord',
proxy: {
type: 'ajax',
url: 'data1.json?groupId={groupId}',
reader: {
type: 'json'
}
}
}
}
});
Ext.define('App.view.SomeGridPanelViewController', {
extend: 'Ext.app.ViewController',
alias: 'controller.somegridpanel',
onRefresh: function (groupId) {
console.log('calling the grid panel onrefresh for groupId: ' + groupId);
let vm = this.getViewModel();
console.log(vm);
vm.set('groupId', groupId);
//vm.notify(); // <- uncomment this line to make it work properly
let store = vm.getStore('testStore');
//tore.proxy.extraParams.groupId = groupId;
store.load();
}
});
// TestWindow
Ext.define('App.view.TestWindow', {
extend: 'Ext.window.Window',
alias: 'widget.testwindow',
requires: [
'App.view.TestWindowViewModel',
'App.view.TestWindowViewController',
'App.view.SomeGridPanel',
'Ext.grid.Panel'
],
controller: 'testwindow',
viewModel: {
type: 'testwindow'
},
height: 323,
width: 572,
layout: 'fit',
closeAction: 'hide',
bind: {
title: '{title}'
},
items: [{
xtype: 'somegridpanel',
reference: 'someGrid'
}]
})
Ext.define('App.view.TestWindowViewModel', {
extend: 'Ext.app.ViewModel',
alias: 'viewmodel.testwindow',
data: {
title: 'Test Window'
}
});
Ext.define('App.view.TestWindowViewController', {
extend: 'Ext.app.ViewController',
alias: 'controller.testwindow',
listen: {
controller: {
'*': {
loadData: 'onLoadData'
}
}
},
onLoadData: function (groupRecord) {
console.log('Loading data...');
console.log(groupRecord);
let vm = this.getViewModel();
vm.set('title', 'Group: ' + groupRecord.get('description'));
this.lookup('someGrid').getController().onRefresh(groupRecord.get('id'));
}
});
// ViewPort
Ext.define('App.view.MainViewport', {
extend: 'Ext.container.Viewport',
alias: 'widget.mainviewport',
requires: [
'App.view.MainViewportViewModel',
'App.view.MainViewportViewController',
'Ext.grid.Panel',
'Ext.view.Table',
'Ext.grid.column.Number'
],
controller: 'mainviewport',
viewModel: {
type: 'mainviewport'
},
height: 250,
width: 400,
items: [{
xtype: 'gridpanel',
title: 'Main Grid - double-click on a row',
bind: {
store: '{groupRecords}'
},
columns: [{
xtype: 'numbercolumn',
dataIndex: 'id',
text: 'Group id',
format: '00'
}, {
xtype: 'gridcolumn',
flex: 1,
dataIndex: 'description',
text: 'Group'
}],
listeners: {
rowdblclick: 'onGridpanelRowDblClick'
}
}]
});
Ext.define('App.view.MainViewportViewModel', {
extend: 'Ext.app.ViewModel',
alias: 'viewmodel.mainviewport',
requires: [
'Ext.data.Store',
'Ext.data.proxy.Memory'
],
stores: {
groupRecords: {
model: 'App.model.GroupRecord',
data: [{
id: 1,
description: 'Group 1'
}, {
id: 2,
description: 'Group 2'
}, {
id: 3,
description: 'Group 3'
}, {
id: 4,
description: 'Group 4'
}, {
id: 5,
description: 'Group 5'
}],
proxy: {
type: 'memory'
}
}
}
});
Ext.define('App.view.MainViewportViewController', {
extend: 'Ext.app.ViewController',
alias: 'controller.mainviewport',
onGridpanelRowDblClick: function (tableview, record, element, rowIndex, e, eOpts) {
if (!this._testWindow) {
this._testWindow = Ext.create('widget.testwindow');
}
this._testWindow.show();
this.fireEvent('loadData', record);
}
});
Ext.application({
//name : 'Fiddle',
models: [
'SomeRecord',
'GroupRecord'
],
views: [
'MainViewport',
'TestWindow',
'SomeGridPanel'
],
name: 'App',
launch: function () {
Ext.create('App.view.MainViewport');
}
});
}
data1.json:
function(params, req, Fiddle) {
var range = [];
for (var i = 1; i <= 50; i++) {
var obj = {
id: i,
description: `Item ${i}`,
groupId: Math.floor( (i - 1) / 10) + 1
}
range.push(obj);
}
let allData = range.filter(it => it.groupId === params.groupId )
//console.log(params);
//console.log(req);
return allData;
}
The code is a bit contrived but it follows an issue (though not identical) I had in the real app. In the real app I have a weird intermittent issue, where I have a complex form with subcomponents, and a vm.notify() call made in a subcomponent, fires a method bound to a ViewModel data member (in other words it fires when that data member changes), which in turn tries to refresh a local store in another subcomponent but this subcomponent's ViewModel getStore() call returns null. The proxy of that store has the url property bound to a ViewModel data member. That's the common pattern. It seems to me that stores with proxies that have the url property bound to a ViewModel data property (ex: url: 'data1.json?groupId={groupId}') are not initialized properly by the time the form is rendered, and eventually, after some ViewModel computation cycles, they get initialized finally.
TIA
Update: There was a question below whether the url attribute of the proxy is bindable. I think it is bindable. Sencha Architect shows it as bindable, though, when enabled, it doesn't place the property in a bind object.
I did search the ExtJs 7.6.0 code base for samples where the {...} expressions are used in the url attribute, and I stumbled upon this test case:
packages\core\test\specs\app\ViewModel.js from line 7237:
describe("initial", function() {
it("should not create the store until a required binding is present", function() {
viewModel.setStores({
users: {
model: 'spec.User',
proxy: {
type: 'ajax',
url: '{theUrl}'
}
}
});
notify();
expect(viewModel.getStore('users')).toBeNull();
setNotify('theUrl', '/foo');
var store = viewModel.getStore('users');
expect(store.isStore).toBe(true);
expect(store.getProxy().getUrl()).toBe('/foo');
});
it("should wait for all required bindings", function() {
viewModel.setStores({
users: {
model: 'spec.User',
proxy: {
type: 'ajax',
url: '{theUrl}',
extraParams: {
id: '{theId}'
}
}
}
});
notify();
expect(viewModel.getStore('users')).toBeNull();
setNotify('theUrl', '/foo');
expect(viewModel.getStore('users')).toBeNull();
setNotify('theId', 12);
var store = viewModel.getStore('users');
expect(store.isStore).toBe(true);
expect(store.getProxy().getUrl()).toBe('/foo');
expect(store.getProxy().getExtraParams().id).toBe(12);
});
});
What is interesting is that it is expected in the test that viewmodel getStore would return null before setting the value for theUrl: expect(viewModel.getStore('users')).toBeNull(); !
Maybe it is by design...
Reason for the issue is - triggered event listener is getting executed before the View Model is initialised and grid is rendered
We can use following approach to fix these issues
Add delay before triggering the event
Listen for initViewModel method in controller and then load the store
Or add afterrender event on the grid, read its store and then load it
// Add delay to ensure view model is initialized
Ext.defer(function() {
this.fireEvent('loadData', record);
}, 1000, this);
There is a override function called "init" in each controller so use your initial code in this function. This function invokes every time when UI refresh/added new.
Example:
init: function() {
var vm = this.getViewModel();
var store = vm.data.myCurrentStore;
store.load();
}

Extjs6.2 Modern toolkit- Extend a textbox

I am still learning EXTJs and one of the thing I was trying to to was extend a component. Below is my example:
Ext.define('MyApp.view.CustomTextField',{
extend: 'Ext.field.Text',
xtype: 'customtextfield',
config:
{
fieldID: null,
langID: null
},
initialize: function() {
alert("init"); //1. called before I navigate to view
Call a controller method here
this.callParent(arguments);
},
initComponent: function () {
alert("initComp"); //2. not called at all
Call a controller method here
this.callParent(arguments);
}
I want to call a controller method to validate if user has permission to see this field and accordingly do next actions. I want this validation to happen when I navigate to the view
I used this custom field in my View as:
xtype: 'fieldset',
margin: 10,
bind: '{workOrders}',
title: 'Dispatch Information',
items: [
{
id: 'Tag',
xtype: 'customtextfield',
name: 'Tag',
label: 'Tag',
bind: '{Tag}',
labelAlign: 'top'
},
But the initComponent is never fired.
The initialize is fired to soon ,even before my stores are loaded. How do I properly extend this control?
In ExtJS 6 modern there is no initComponent event for textfield . initComponent event have
in classic for textfield.
For calling events in controller you need to create controller and define to you view.
In this FIDDLE, I have created a demo using form, ViewController, textfield and ViewModel. I hope this will help/guide you to achieve your requirements.
For more details please refer ExtJS Docs.
CODE SNIPPET
Ext.application({
name: 'Fiddle',
launch: function () {
//Define the cutsometext from extending {Ext.field.Text}
Ext.define('CustomText', {
extend: 'Ext.field.Text',
xtype: 'customtext',
labelAlign: 'top',
listeners: {
initialize: 'onInitializeCutsomText'
}
});
Ext.define('FormModel', {
extend: 'Ext.app.ViewModel',
alias: 'viewmodel.formmodel',
data: {
user: {
firstName: 'Narendra',
lastName: 'Jadhav',
email: 'narendrajadhav105#gmail.com'
},
permissionCng: {
firstName: false,
lastName: false,
email: true,
isAdmin: false
}
}
});
//Define the FormController from extending {Ext.app.ViewController}
Ext.define('FormController', {
extend: 'Ext.app.ViewController',
alias: 'controller.formctn',
onInitializeCutsomText: function (textfield) {
var permissionCng = this.getViewModel().get('permissionCng');
// Here is just basic example for disabled textfield on initialize event.
//In your case you can put your requirement.
textfield.setDisabled(permissionCng[textfield.getName()]);
}
});
//Creating form
Ext.create('Ext.form.Panel', {
fullscreen: true,
viewModel: {
type: 'formmodel'
},
controller: 'formctn',
items: [{
xtype: 'fieldset',
title: 'Personal Info',
defaults: {
xtype: 'customtext' //Here I am using {customtext}
},
items: [{
label: 'First Name',
name: 'firstName',
bind: {
value: '{user.firstName}',
//You can also use like property
//hidden:'{permissionCng.firstName}'
}
}, {
label: 'Last Name',
name: 'lastName',
bind: {
value: '{user.lastName}',
//You can also use like property
//hidden:'{permissionCng.firstName}'
}
}, {
label: 'Email Id',
name: 'email',
bind: {
value: '{user.email}',
//You can also use like property
//hidden:'{permissionCng.firstName}'
}
}, {
label: 'Admin Name',
name: 'isAdmin',
bind: {
value: '{user.isAdmin}',
//You can also use like property
hidden: '{!permissionCng.isAdmin}'
}
}]
}]
});
}
});

ExtJS 5.1: Binding record value to component property

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

extract string from a json object

I have an attribute in the extjs code defined as :
datajson: {
'name' : 'Hello',
....
}
Now I need to set the value of a textfield to the name defined inside this attribute.
I have tried the following but its not working:
Ext.getCmp('name').setValue((this.datajson.xyz.name).toString());
The id of the textfield whose value I am trying to set is 'name'
Here is the full code structure.
Ext.define('Test', {
extend : 'Ext.Container',
alias : 'widget.myapp',
datajson:{
'xyz' : {
...
"name" : 'Hello',
...
}
},
initComponent : function() {
this.appcombo = Ext.create('Ext.form.field.ComboBox', {
store: Ext.create('Ext.data.Store', {
fields: ['text'],
data: [
{text: "a"},
{text: "b"},
{text: "c"}
]
}),
listeners: {
select: function(combo, record, index) {
Ext.getCmp('name').setValue((this.datajson.xyz.name).toString());
}
}
});
...
...
//Here we have a textfield within a Panel whose id is 'name'
So basically I am loading the page and then selecting one value in combo box which serves as the event to load the values in the text-fields with the data which is in datajson.
EDIT: More information added.
The scope within your select function no longer contains datajson. To continue to use this.datajson you can pass the required scope into the function using bind
Ext.application({
name: 'Fiddle',
datajson: {
'name': 'Hello'
},
displayName: function (combo, record, index) {
Ext.getCmp('name').setValue((this.datajson.name));
},
launch: function () {
Ext.create('Ext.form.Panel', {
title: 'Contact Info',
width: 300,
bodyPadding: 10,
renderTo: Ext.getBody(),
items: [{
xtype: 'combo',
store: Ext.create('Ext.data.Store', {
fields: ['text'],
data: [{
text: "a"
}, {
text: "b"
}, {
text: "c"
}]
}),
listeners: {
select: this.displayName.bind(this)
}
}, {
xtype: 'textfield',
id: 'name',
fieldLabel: 'Name'
}]
});
}
});

Sencha Touch: How Can I pass Data from a view to Controller and Controller to Next View

I want to pass the an ID from a Tabview (myView1) to a Controller(theController) then after that I want that Controller class to pass data to another Tabview (myView2) at the same Panel. Anyone has any Idea, how can I do this?
You can do like this
in your controller:
config:{
refs:{
myView1: "myView1Ref",
myView2: "myView2Ref"
},
controls:{
myView1:{
idgiven: "onIdGiven"
}
}
},
onIdGiven: function(id){
this.getMyView2().setData(yourData);
}
in MyView1, when you want to give the id, just do
this.fireEvent(yourId);
This is my Tab Panel View class (MyTabPanel)
Ext.define('MyApp.view.MyTabPanel', {
extend: 'Ext.tab.Panel',
config: {
items: [
{
xtype: 'container',
title: 'Tab 1',
id: 'tab1',
items: [
{
xtype: 'textfield',
label: 'User'
},
{
xtype: 'container',
height: 46,
margin: '5px',
items: [
{
xtype: 'button',
ui: 'action',
width: 173,
text: 'Send Data'
}
]
}
]
},
{
xtype: 'container',
title: 'Tab 2',
id: 'tab2',
tpl: [
'{id}'
]
},
{
xtype: 'container',
title: 'Tab 3',
id: 'tab3'
}
]
}
});
This is my Controller :
Ext.define('MyApp.controller.MyController', {
extend: 'Ext.app.Controller',
config: {
refs: {
Tab1: '#tab1',
Tab2: '#tab2',
Tab3: '#tab3'
},
control: {
"Tab2": {
activate: 'onTabpanelActivate1'
}
}
},
onTabpanelActivate1: function(newActiveItem, container, oldActiveItem, eOpts) {
var Rec = [{id:1}]; // this should come from my #tab1 tab and set to #tab2 tab
this.getTab2().setRecord(Rec); // So this is the problem I am facing
}
});
Ext.define('myapp.controller.theController', {
extend: 'Ext.app.Controller',
config: {
refs: {
views: [
'myapp.view.myView1',
'myapp.view.myView2',
],
refMyView1: 'myView1',
refMyView2: 'myView2',
},
control: {
'button[action=submit]':{ // on tab of submit button
tap: 'submitTapHandler'
},
},
},
submitTapHandler: function(){
var view1data = this.getRefMyView1().getValues(); // view1data contain all field data of view1 in record form
//controller to view pass data
var myView2 = Ext.create('myapp.view.myView2');
// create a record that key value have name of fields and value to pass to that field
var record;
// set record key value
myView2.setRecord(record);
}
})

Resources