ExtJS, how to bind ViewModel to component config parameters? - javascript

On ExtJS 6.02 I would like to bind a ViewModel to my component config parameters.
I tried what it says here: https://stackoverflow.com/a/27284556 but it doesn't work, it's not binding.
This prints Null instead of 123:
Ext.define('MyApp.viewmodel.Test', {
extend: 'Ext.app.ViewModel',
alias: 'viewmodel.test',
data: {
serverPathData: ''
}
});
Ext.define('MyApp.view.TestFileField', {
extend: 'Ext.form.field.Text',
xtype: 'TestFileField',
viewModel: {
type: 'test'
},
config: {
serverPath: null
},
publishes: 'serverPath',
bind: {
serverPath: '{serverPathData}'
}
, initComponent: function() {
this.getViewModel().set('serverPathData', '123');
this.getViewModel().notify();
console.log(this.getServerPath());
this.callParent()
}
});
Ext.application({
name: 'MyApp',
launch: function() {
var testFileField = Ext.widget({
xtype: 'TestFileField',
renderTo: Ext.getBody()
});
}
});
Using testFileField.getViewModel().notify(); does solve the problem in this example, but in my case it doesn't.
I have a shared viewModel.

Found one solution, if I call this.initBindable(); on initComponent it works:
Ext.define('MyApp.viewmodel.Test', {
extend: 'Ext.app.ViewModel',
alias: 'viewmodel.test',
data: {
serverPathData: ''
}
});
Ext.define('MyApp.view.TestFileField', {
extend: 'Ext.form.field.Text',
xtype: 'TestFileField',
viewModel: {
type: 'test'
},
config: {
serverPath: null
},
publishes: 'serverPath',
bind: {
serverPath: '{serverPathData}'
}
, initComponent: function() {
this.initBindable();
this.getViewModel().set('serverPathData', '123');
this.getViewModel().notify();
console.log(this.getServerPath());
console.log(this.getViewModel().data);
this.callParent();
}
});
Ext.application({
name: 'MyApp',
launch: function() {
var testFileField = Ext.widget({
xtype: 'TestFileField',
renderTo: Ext.getBody()
});
}
});
The problem with this is that this method is private and is already called on beforeRender and afterRender and I don't know if calling it on initComponent or constructor could cause some problem.

Related

XTemplate definition on items property of a ListItem

I'm using Sencha 2.3.0 and I want to have a XTemplate side-to-side to a component (textfield) on a ListItem. The code above works fine for DataView/DataItem, but I want to use the grouped property that is only available on List/ListItem.
The nested Xtemplate gets rendered fine as DataItem. How can I make it work for ListItem? I'm also receptive for solutions that drop this nested structure and use the xtemplate as tpl property directly on the ListItem (of course the textfield with listeners must be implemented as well).
list
Ext.define( 'app.view.myList', {
//extend: 'Ext.dataview.DataView',
extend: 'Ext.dataview.List',
xtype: 'mylist',
requires: [
'app.view.MyItem'
],
config: {
title: "myTitle",
cls: 'mylist',
defaultType: 'myitem',
grouped: true,
store: 'myStore',
useComponents: true,
itemCls: 'myitem',
items: [
{
// some components
}
]
}
});
listitem
Ext.define( 'app.view.myItem', {
//extend: 'Ext.dataview.component.DataItem',
extend: 'Ext.dataview.component.ListItem',
xtype: 'myitem',
config: {
cls: 'myitem',
items: [
{
xtype: 'component',
tpl: new Ext.XTemplate([
'<table cellpadding="0" cellspacing="0" class="myitemXTemplate">',
//some xtemplate content
'</table>'
].join( "" ),
{
compiled: true
})
},
{
label: 'some label',
cls : 'myitemtextfield',
xtype: 'textfield',
name: 'myitemtextfield'
}
]
}
});
Thanks in advance!
Modifed /touch-2.4.2/examples/list/index.html
The model:
Ext.define('model1', {
extend: 'Ext.data.Model',
config: {
fields: [
{name: 'firstName', type: 'string'},
{name: 'lastName', type: 'string'}
]
}
});
The CustomListItem
Ext.define('DataItem', {
extend: 'Ext.dataview.component.ListItem',
xtype: 'basic-dataitem',
requires: [
'Ext.Button',
'Ext.Component',
'Ext.layout.HBox',
'Ext.field.Checkbox'
],
config: {
dataMap : {
/* getFirstname : {
setData : 'firstName'
},*/
getLastname : {
setValue : 'lastName'
}
},
layout: {
type: 'hbox',
height:'200px',
},
firstname: {
cls: 'firstname',
xtype:'component',
data:{data:'hej'},
tpl: new Ext.XTemplate([
'<H1>',
'{data}',
'</H1>'
].join(""),
{
compiled: true
})
},
lastname: {
xtype:'textfield',
css:'lastname'
}
},
applyFirstname : function (config) {
return Ext.factory(config, Ext.Component, this.getFirstname());
},
updateFirstname : function (newName) {
if (newName) {
this.add(newName);
}
},
applyLastname : function (config) {
return Ext.factory(config, Ext.Component, this.getLastname());
},
updateLastname : function (newAge) {
if (newAge) {
this.add(newAge);
}
},
applyFirstName: function (config) {
return Ext.factory(config, 'Ext.Component', this.getLastname());
},
updateRecord: function(record) {
if (!record) {
return;
}
this.getFirstname().setData({data:record.get("firstName")});
this.callParent(arguments);
}
});
The store
var store = Ext.create('Ext.data.Store', {
//give the store some fields
model: 'model1',
//filter the data using the firstName field
sorters: 'firstName',
//autoload the data from the server
autoLoad: true,
//setup the grouping functionality to group by the first letter of the firstName field
grouper: {
groupFn: function (record) {
return record.get('firstName')[0];
}
},
//setup the proxy for the store to use an ajax proxy and give it a url to load
//the local contacts.json file
proxy: {
type: 'ajax',
url: 'contacts.json'
}
});
The list
xtype: 'list',
useSimpleItems: false,
defaultType: 'basic-dataitem',
id: 'list',
ui: 'round',
//bind the store to this list
store: store

ExtJS 5.1.1 Immediately fire binding in ViewModel

Seems like i have some misunderstanding with binding timings. I have a simple combobox with value binded to some object in viewmodel. Selecting new value, firing change event, which fired after setValue method, so my new value is already set, but my viewmodel is not updated yet. When my viewmodel will be updated? I found some information about scheduler, which says i need to run notify() method to immediately apply changes to viewmodel, but it doesn't help me at all.
Ext.define('MyModel', {
extend: 'Ext.data.Model',
idProperty: 'foo',
fields: [{
name: 'bar',
type: 'string'
}]
});
Ext.define('MyViewModel',{
extend: 'Ext.app.ViewModel',
alias: 'viewmodel.my',
data: {
testObj: {
foo: null,
bar: null
}
},
stores:{
combostore: {
model: 'MyModel',
data: [{
foo: '1',
bar: 'qwerty'
},{
foo: '2',
bar: 'ytrewq'
}]
}
}
});
Ext.define('MyViewController', {
extend: 'Ext.app.ViewController',
alias: 'controller.my',
onChange: function() {
var vm = this.getViewModel();
vm.notify();
console.log(vm.get('testObj.foo'));//supposed to be current value
}
});
Ext.application({
name : 'Fiddle',
launch : function() {
Ext.create('Ext.container.Viewport', {
controller: 'my',
viewModel: {
type: 'my'
},
layout : 'vbox',
items : [
{
xtype : 'combo',
valueField: 'foo',
displayField: 'bar',
queryMode: 'local',
bind: {
store: '{combostore}',
value: '{testObj.foo}'
},
listeners:{
change: 'onChange'
}
}
]
});
}
});
Here's fiddle aswell: https://fiddle.sencha.com/#fiddle/r88
I know it is a late response, however I think this problem is still relevant. See this fiddle: https://fiddle.sencha.com/#fiddle/2l6m&view/editor.
Essentially the idea is that you listen to the bound variables changes instead of listening to the combobox selection change (which triggers the re-evaluation of the bound variables).
The key code is in the constructor I added for the view model:
Ext.define('MyViewModel',{
extend: 'Ext.app.ViewModel',
alias: 'viewmodel.my',
// added this to enable the binding
constructor: function () {
var me = this;
me.callParent(arguments);
me.bind('{selectedItem}', function (value) {
console.log('combobox selected item changed (bar value): ' + (value === null ? "null": value.get('bar')));
console.log(me.getView().getController());
});
me.bind('{testObj.foo}', function (value) {
console.log('combobox value (foo value): ' + value);
// you can access the controller
console.log(me.getView().getController());
});
},
data: {
testObj: {
foo: null,
bar: null
},
selectedItem: null,
},
stores:{
combostore: {
model: 'MyModel',
data: [{
foo: '1',
bar: 'qwerty'
},{
foo: '2',
bar: 'ytrewq'
}]
}
}
});
and here is the view (note the binding to the selection):
Ext.application({
name : 'Fiddle',
launch : function() {
Ext.create('Ext.container.Viewport', {
controller: 'my',
viewModel: {
type: 'my'
},
layout : 'vbox',
items : [
{
xtype : 'combo',
valueField: 'foo',
displayField: 'bar',
queryMode: 'local',
bind: {
store: '{combostore}',
value: '{testObj.foo}',
selection: '{selectedItem}'
},
listeners:{
change: 'onChange'
}
}
]
});
}
});
Binding the selection was not necessary but I included it anyway as another option because sometimes you may want to store additional data in the store that is bound to the list.
I hope this helps others.
You should not call notify directly on the viewmodel. It's private:
http://docs.sencha.com/extjs/5.0/5.0.1-apidocs/#!/api/Ext.app.ViewModel-method-notify
Just set the data by calling vm.setData({}).
onChange: function(cb, newValue, oldValue) {
var vm = this.getViewModel();
console.log('newValue', newValue);
console.log('oldValue', oldValue);
this.getViewModel().setData({'testObj': {'foo': newValue}});
console.log(vm.get('testObj.foo'));//supposed to be current value
}
Never the less you could consider the following example, where I use a formula to get the selected model of the combobox. See that I removed the listener, added a reference to the combobox and created a formula to bind deep onto the selected record.
Ext.define('MyModel', {
extend: 'Ext.data.Model',
idProperty: 'foo',
fields: [{
name: 'bar',
type: 'string'
}]
});
Ext.define('MyViewModel',{
extend: 'Ext.app.ViewModel',
alias: 'viewmodel.my',
data: {
testObj: {
foo: null,
bar: null
}
},
stores:{
combostore: {
model: 'MyModel',
data: [{
foo: '1',
bar: 'qwerty'
},{
foo: '2',
bar: 'ytrewq'
}]
}
},
formulas: {
currentRecord: {
bind: {
bindTo: '{myCombo.selection}',
deep: true
},
get: function(record) {
return record;
},
set: function(record) {
this.set('currentRecord', record);
}
}
}
});
Ext.define('MyViewController', {
extend: 'Ext.app.ViewController',
alias: 'controller.my',
onChange: function(cb, newValue, oldValue) {
var vm = this.getViewModel();
console.log('newValue', newValue);
console.log('oldValue', oldValue);
this.getViewModel().setData({'testObj': {'foo': newValue}});
console.log(vm.get('testObj.foo'));//supposed to be current value
}
});
Ext.application({
name : 'Fiddle',
launch : function() {
var vp = Ext.create('Ext.container.Viewport', {
controller: 'my',
viewModel: {
type: 'my'
},
layout : 'vbox',
items : [
{
xtype : 'combo',
reference: 'myCombo',
valueField: 'foo',
displayField: 'bar',
queryMode: 'local',
bind: {
store: '{combostore}',
value: '{testObj.foo}'
}/*,
listeners:{
change: 'onChange'
}*/
},
{
xtype: 'label',
bind: {
text: '{currentRecord.foo}'
}
}
]
});
/* Just an example. You could also select records on the combo itself */
vp.getViewModel().setData({'testObj': {'foo': '2'}});
}
});
Defer would be your savior:
onChange: function() {
var vm = this.getViewModel();
Ext.defer(function(){
console.log(vm.get('testObj.foo'));//supposed to be current value
},10,this);
}
Please use select event instead of change.
I think change event fires even before value is set.

Extjs Menu insert new items with override

I have a menu which is defined like that:
Ext.define('MyApp.FileBrowserContextMenu', {
extend: 'Ext.menu.Menu',
initComponent: function() {
var me = this;
Ext.applyIf(me, {
items: [
{
xtype: 'menuitem',
text: 'Edit',
listeners: {
click: {
fn: me.onMenuitemClick,
scope: me
}
}
},
]
});
me.callParent(arguments);
},
onMenuitemClick: function(item, e, options) {
var server = this.record;
var win = Ext.create('widget.ServerWindow', {
record: server
});
win.show();
}
});
I would like to add new items after the definition, so I try like that:
First I defined the new MenuItem:
Ext.define('MyApp.GitMenuItem', {
extend: 'Ext.menu.Item',
alias: 'widget.gitmenuitem',
text: 'Git',
initComponent: function() {
var me = this;
Ext.applyIf(me, {
menu: {
xtype: 'menu',
items: [
{
xtype: 'menuitem',
text: 'Commit',
listeners: {
click: {
fn: me.onMenuitemClick,
scope: me
}
}
},
]
}
});
me.callParent(arguments);
},
onMenuitemClick: function(item, e, options) {
},
});
Then I try to attach the new menu item:
Ext.override(MyApp.FileBrowserContextMenu, {
initComponent: function () {
var me = this;
this.callParent();
me.items.items.push(Ext.create('widget.gitmenuitem'));
}
});
It seems to work, because the new MenuItem appears, but when I go over, Then new Item should appears but I get this error:
Uncaught TypeError: Cannot set property 'activeChild' of undefined
Any ideads ?
The usual way is the add method: menu.add(menuItem).

Sencha 2.x MVC: Get element by id

I just started using Sencha framework 2.x. This is my app:
app/app.js
Ext.Loader.setConfig({
enabled: true
});
Ext.application({
name: 'App',
controllers: ['Generators'],
models: [],
stores: [],
views: ['Main', 'Generator'],
launch: function() {
Ext.create('App.view.Main');
}
});
app/view/Main.js
Ext.define('App.view.Main', {
extend: 'Ext.NavigationView',
requires: [
'App.view.Generator'
],
config: {
fullscreen: true,
items: [
{
xtype: 'generatorview'
}
]
}
});
app/view/Generator.js
Ext.define('App.view.Generator', {
extend: 'Ext.Container',
xtype: 'generatorview',
id: 'generator',
config: {
layout: 'vbox',
items: [
{
xtype: 'panel',
html: 'My message: <a id="xxxSet">Set</a> :: <span id="xxxMsg">...</span>',
flex: 1
},
{
dock: 'bottom',
xtype: 'toolbar',
items: []
}
]
}
});
app/controller/Generator.js
Ext.define('App.controller.Generators', {
extend: 'Ext.app.Controller',
config: {
refs: {}
},
init: function() {
this.control({
'#xxxSet': { // QUESTION1
tap: 'setMyMessage'
}
});
},
setMyMessage: function() {
'#xxxMsg'.html('Set this message'); // QUESTION2
}
});
As you can see I placed questions in the last part (controller).
QUESTION1: How can I set a tap function to the element (#xxxSet)
defined in the view as HTML content.
QUESTION2: How can I set a
message the the element (#xxxMsg) defined in the view as HTML
content.
xxxSet = id of a button
xxxMsg = id of a message holder
Thx!
You can use Ext#get (which accepts a string which is the id) which will return a instance of Ext.dom.Element. With that, you can use the on method to add listeners (much like control) and then the setHtml method to update the contents.
init: function() {
Ext.get('xxxSet').on({
tap: 'setMyMessage',
scope: this
});
},
setMyMessage: function() {
Ext.get('xxxMsg).setHtml('Hello');
}
If you use itemId, you can not access it with Ext.get(). You can try Ext.ComponentQuery.query() instead of that.
init: function() {
Ext.ComponentQuery.query('#xxxSet').on({
tap: 'setMyMessage',
scope: this
});
},
setMyMessage: function() {
Ext.ComponentQuery.query('#xxxMsg).setHtml('Hello');
}

Extjs Not Showing data from store

I`m learning Extjs and trying to follow this tutorial http://www.sencha.com/learn/the-mvc-application-architecture/
Everything went well..
But when I continue to 'Creating a Model and a Store' and do as written in tutorial then run it.
It give error : Uncaught TypeError: Cannot read property 'items' of undefined
Is there is something I missed up?
Thank you
/app/controller/User.js
Ext.define('AM.controller.Users', {
extend: 'Ext.app.Controller',
stores: [
'Users'],
models: ['User'],
views: [
'user.List',
'user.Edit'],
init: function () {
this.control({
'viewport > panel': {
render: this.onPanelRendered
},
'userlist': {
itemdblclick: this.editUser
}
});
},
editUser: function (grid, record) {
var view = Ext.widget('useredit');
view.down('form').loadRecord(record);
},
onPanelRendered: function () {
console.log('panel rendered');
}
})
/app/model/User.js
Ext.define('AM.model.User', {
extend: 'Ext.data.Model',
fields: ['name', 'email']
});
/app/store/Users.js
Ext.define('AM.store.Users', {
extend: 'Ext.data.Store',
model: 'AM.model.User',
data: [
{name: 'Ed', email: 'ed#sencha.com'},
{name: 'Tommy', email: 'tommy#sencha.com'}
]
});
/app/view/user/Edit.js
Ext.define('AM.view.user.Edit', {
extend: 'Ext.window.Window',
alias : 'widget.useredit',
title : 'Edit User',
layout: 'fit',
autoShow: true,
initComponent: function() {
this.items = [
{
xtype: 'form',
items: [
{
xtype: 'textfield',
name : 'name',
fieldLabel: 'Name'
},
{
xtype: 'textfield',
name : 'email',
fieldLabel: 'Email'
}
]
}
];
this.buttons = [
{
text: 'Save',
action: 'save'
},
{
text: 'Cancel',
scope: this,
handler: this.close
}
];
this.callParent(arguments);
}
});
/app/view/user/List.js
Ext.define('AM.view.user.List',{
extend: 'Ext.grid.Panel',
alias: 'widget.userlist',
store: 'Users',
title: 'All users',
});
/myapp.js
Ext.application({
name: 'AM',
controllers : ['Users'],
appFolder: 'app',
launch: function() {
Ext.create('Ext.container.Viewport', {
layout: 'fit',
items: [
{
xtype: 'userlist',
}
]
});
}
});
/index.html
<!DOCTYPE HTML>
<html>
<head>
<link rel="stylesheet" type="text/css" href="extjs/resources/css/ext-all.css" />
<script type="text/javascript" src="extjs/ext-debug.js"></script>
<script type="text/javascript" src="myapp.js"></script>
</head>
<body>
Loading apps.........
</body>
</html>
Extjs library is located at
/extjs
Just as a syntax based comment, you have an extra comma after 'userlist' in your myapp.js file and also after 'All Users' in your List.js file.
Also, in the comments on that tutorial there is reference to a similar issue where the resolution was to:
define the ‘columns’ property for the ‘List’ view
Try adding this to your list:
columns: [
{header: 'Name', dataIndex: 'name'},
{header: 'Email', dataIndex: 'email'}
]
Okay look like that you want to try the sencha mvc example # examples/app/simple
This example have a bug because the need to explicit load of Ext.container.Viewport on application launch
launch: function() {
Ext.create('Ext.container.Viewport', { //this line do explicit load on Viewport class
So to solve this you need an extra line on your myapp.js at the first line
Ext.Loader.setConfig({enabled:true}); //need to enable dynamically load the Ext.container.Viewport
Ext.application({
name: 'AM',
//paths direct to the Ext src path folder i.e given here if accessed from http://localhost/ext/examples/app/simple/simple.html
paths: {
'Ext': '../../../src/'
},
controllers: [
'Users'
],
....

Categories