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.
Related
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.
I am trying to use multiselector from EXTJS 6.5.2
This is the code that I am using to create multiselector with the values that I need
{
xtype: 'multiselector',
title: 'I caktohet:',
name: 'Caktohen[]',
bind: '{userfullname.value}',
fieldName: 'userfullname',
viewConfig: {
deferEmptyText: false,
emptyText: 'Askush i zgjedhur'
},
search: {
field: 'userfullname',
model: 'InTenders.model.UserModel',
store: {
type: 'users',
sorters: 'userfullname',
// proxy: {
// type: 'rest',
// limitParam: null,
// url: 'api/User'
// }
}
}
}
When I call form = win.down('form') records I can get all values except the multiselector values, they show like this on console.
Anyone can help me or guide me how to get the values?
Thank you.
//Code that I'm trying to get multiselector items and save them
saveTenderForm: function (button, e, eOpts) {
var userMultiSelector = Ext.getCmp('AssignedUsers'); //save assigned users
var selectedUsers = userMultiSelector.getStore().getData(); //get store and put them in array
var me = this,
win = button.up('window'),
form = win.down('form'),
// formApplyUpload = this.getFormApplyUpload(),
// var ko = win.items.items[0].items.items[0].value;
recordtenderUsers = Ext.create('InTenders.model.TenderSaveModel');
// recordtenderUsers = form.getRecord();
// record = form.getRecord(),
values = form.getValues();
// appFile = this.getApplicationFile(),
// callbacks;
recordtenderUsers.set(values);
recordtenderUsers.set('tenderUsers',selectedUsers.items);
// // me.showMask();
// if (form.isValid()) {
recordtenderUsers.save({
success: function (recordtenderUsers, operation) {
win.close();
me.hideMask();
},
failure: function (recordtenderUsers, operation) {
me.hideMask();
}
});
You can get value using multiselector.down('grid') this will return you the grid. And grid have method getSelection().
In this FIDDLE, I have created a demo. I hope this will help/guide you to achieve your requirement.
CODE SNIPPET
Ext.application({
name: 'Fiddle',
launch: function () {
Ext.create({
xtype: 'form',
renderTo: Ext.getBody(),
items: [{
xtype: 'multiselector',
title: 'Multi selector example',
fieldName: 'text',
viewConfig: {
deferEmptyText: false,
emptyText: 'No value selected'
},
search: {
field: 'text',
store: {
fields: [{
name: 'text',
type: 'string'
}],
data: [{
text: 'ABC'
}, {
text: 'ABC 1'
}, {
text: 'ABC 2 '
}, {
text: 'ABC 3'
}, {
text: 'ABC 4'
}]
}
}
}, {
xtype: 'button',
text: 'Get Value',
margin:15,
handler: function (btn) {
var multiselector = btn.up('form').down('multiselector');
if (multiselector.down('grid')) {
multiselector.down('grid').getSelection().forEach(rec => {
console.log(rec.get('text'));
});
}
}
}]
});
}
});
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
Below is my code to display three comboboxes, which will be Filter by severity, start release and end release. When I refresh the page I want comboboxes to remember what was selected earlier. Now it shows the current release in both the comboboxes.
Any help on this
launch: function() {
var that = this;
this.down('#SevFilter').add({
xtype: 'rallyattributecombobox',
cls: 'filter',
itemId: 'severity',
model: 'Defect',
labelWidth: 117,
fieldLabel : 'Filter By Severity:',
field: 'Severity',
allEntryText: '-- ALL --',
allowNoEntry: true,
_insertNoEntry: function(){
var record;
var doesNotHaveAllEntry = this.store.count() < 1 || this.store.getAt(0).get(this.displayField) !== this.allEntrylText;
if (doesNotHaveAllEntry) {
record = Ext.create(this.store.model);
console.log("record value", record);
record.set(this.displayField, this.allEntryText);
record.set(this.valueField, "-1");
this.store.insert(0, record);
}
/*var doesNotHaveNoEntry = this.store.count() < 2 || this.store.getAt(1).get(this.displayField) !== this.noEntryText;
if (doesNotHaveNoEntry) {
record = Ext.create(this.store.model);
record.set(this.displayField, this.noEntryText);
record.set(this.valueField, null);
this.store.insert(1, record);
}*/
},
listeners: {
//ready: this._onSevComboBoxLoad,
select: this._onSevComboBoxSelect,
scope: this
}
});
var button = this.down('#goButton');
button.on('click', this.goClicked, this);
this.down('#SevFilter').add({
xtype: 'rallyreleasecombobox',
//multiSelect: true,
itemId: 'priorityComboBox',
fieldLabel: 'Release Start:',
model: 'release',
width: 400,
valueField: 'ReleaseStartDate',
displayField: 'Name',
// multiSelect: true,
//field: 'Name',
_removeFunction: function(){
console.log("this.store");
},
listeners: {
//select: this._onSelect,
select: this._onFirstReleaseSelect,
scope: this
}
});
this.down('#SevFilter').add({
xtype: 'rallyreleasecombobox',
itemId: 'priorityComboBox2',
fieldLabel: 'Release End:',
model: 'release',
//multiSelect: true,
stateId: 'rally.technicalservices.trend.defect.release',
stateful: true,
stateEvents: ['change'],
width: 400,
valueField: 'ReleaseDate',
displayField: 'Name',
listeners: {
change: function(box) {
var start_date = this.down('#priorityComboBox2').getDisplayField();
this.logger.log(start_date);
},
ready: this._removeFutureReleases,
select: this._onSecondReleaseSelect,
// ready: this._onLoad,
scope: this
},
});
},
In javascript you may use localstorage.
Here is an app example that retains State and Release selections in respective compboboxes when page is refreshed:
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
items: [
{html:'Select a Filter checkbox to filter the grid'},
{
xtype: 'container',
itemId: 'StateFilter'
},
{
xtype: 'container',
itemId: 'ReleaseFilter'
}
],
launch: function() {
document.body.style.cursor='default';
this._createFilterBox('State');
this._createFilterBox('Release');
},
_createFilterBox: function(property){
this.down('#'+property+'Filter').add({
xtype: 'checkbox',
cls: 'filter',
boxLabel: 'Filter table by '+property,
id: property+'Checkbox',
scope: this,
handler: this._setStorage
});
this.down('#'+property+'Filter').add({
xtype: 'rallyattributecombobox',
cls: 'filter',
id: property+'Combobox',
model: 'Defect',
field: property,
value: localStorage.getItem(property+'Filtervalue'), //setting default value
listeners: {
select: this._setStorage,
ready: this._setStorage,
scope: this
}
});
},
_setStorage: function() {
localStorage.setItem('StateFiltervalue',Ext.getCmp('StateCombobox').getValue());
localStorage.setItem('ReleaseFiltervalue',Ext.getCmp('ReleaseCombobox').getValue());
console.log('localStorage State: ', localStorage.StateFiltervalue,', localStorage Release:', localStorage.ReleaseFiltervalue);
this._getFilter();
},
_getFilter: function() {
var filter = Ext.create('Rally.data.wsapi.Filter',{property: 'Requirement', operator: '=', value: 'null'});
filter=this._checkFilterStatus('State',filter);
filter=this._checkFilterStatus('Release',filter);
if (this._myGrid === undefined) {
this._makeGrid(filter);
}
else{
this._myGrid.store.clearFilter(true);
this._myGrid.store.filter(filter);
}
},
_checkFilterStatus: function(property,filter){
if (Ext.getCmp(property+'Checkbox').getValue()) {
var filterString=Ext.getCmp(property+'Combobox').getValue()+'';
var filterArr=filterString.split(',');
var propertyFilter=Ext.create('Rally.data.wsapi.Filter',{property: property, operator: '=', value: filterArr[0]});
var i=1;
while (i < filterArr.length){
propertyFilter=propertyFilter.or({
property: property,
operator: '=',
value: filterArr[i]
});
i++;
}
filter=filter.and(propertyFilter);
}
return filter;
},
_makeGrid:function(filter){
this._myGrid = Ext.create('Rally.ui.grid.Grid', {
itemId:'defects-grid',
columnCfgs: [
'FormattedID',
'Name',
'State',
'Release'
],
context: this.getContext(),
storeConfig: {
model: 'defect',
context: this.context.getDataContext(),
filters: filter
}
});
this.add(this._myGrid);
}
});
The source is available here.
You could use Sencha localstorage proxy. This way you can keep consistency in your project and use a localstorage based store across all your code.
You can use stateful and stateId configurations to enable last selection. Here is an example of my code. Here what I am doing is to create a custom combobox that will show two options: Platform and program. Then for stateId, you can use any string that you want:
_createCategoryFilter: function()
{
// The data store containing the list of states
var platformTypeList = Ext.create('Ext.data.Store',
{
fields: ['abbr', 'name'],
data : [
{"abbr":"PLAN", "name":"Platform"},
{"abbr":"CURR", "name":"Program"},
//...
]
});
// Create the combo box, attached to the states data store
var platformTypeFilter = Ext.create('Ext.form.ComboBox',
{
fieldLabel: 'Select category',
store: platformTypeList,
queryMode: 'local',
displayField: 'name',
valueField: 'abbr',
stateful: true,
stateId: 'categoryFilter',
listeners:
{
afterrender: function(param)
{
console.log('afterrender - category param is', param.rawValue);
CategoryFilterValue = param.rawValue;
LoadInformation();
},
select: function(param)
{
console.log('select - category param is', param.rawValue);
CategoryFilterValue = param.rawValue;
},
scope: this,
}
});
this.add(platformTypeFilter);
},
I'm teaching myself ExtJS by building a really simple 'scrum' development tracking application. I'm currently displaying the "Backlog" as a grid panel that displays the properties of the card(user story).
Card.js (Card model)
Ext.define('AM.model.Card', {
extend: 'Ext.data.Model',
fields: [
'id',
'name',
'priority_id',
...
]
});
Priority.js (Priority model)
Ext.define('AM.model.Priority', {
extend: 'Ext.data.Model',
fields: [
'id',
'name',
'short_name'
]
});
So the data for the card will look something like this:
backlogcards.json (data)
{
success: true,
backlogcards: [
{
id: 1,
name: 'ONEs',
priority_id: 2,
...
},
{
id: 2,
name: 'TWOs',
priority_id: 3,
...
}
]
}
And the priorities looks like this:
priorities.json (data)
{
success: true,
priorities: [
{
id : 1,
name : "High",
short_name : "H"
},
{
id : 2,
name : "Medium",
short_name : "M"
},
{
id : 3,
name : "Low",
short_name : "L"
}
]
}
So ideally what I would like to do is have the grid panel display the short_name for the corresponding priority_id. When the item is clicked on to be edited inline, a combo box will be displayed that shows the name property. I'm half way there already.
BacklogList.js (view)
Ext.define('AM.view.card.BacklogList', {
extend: 'Ext.grid.Panel',
alias: 'widget.backlogcardlist',
title: 'Backlog',
store: 'BacklogCards',
selType: 'cellmodel',
plugins: [
Ext.create('Ext.grid.plugin.CellEditing', {
clicksToEdit: 1
})
],
columns: [
{ header: 'ID', dataIndex: 'id' },
{ header: 'Name', dataIndex: 'name', field: 'textfield' },
{
header: 'Priority',
dataIndex: 'priority_id',
width: 130,
field: {
xtype: 'combobox',
typeAhead: true,
store: 'Priorities',
displayField: 'name',
valueField: 'id',
listClass: 'x-combo-list-small'
}
}
]
});
So I know the 'dataIndex' property is what I need to modify in order to change the display, but I'm not sure how to tie those two stores together.
As you can see above, priority is being displayed as a number instead of the short_name.
Is this a situation where I would need to use associations? (I only know OF them) Sencha Docs
Thank you!
EDIT1: Oh I realize I could 'hard code' a renderer property that does this change, but I would like to avoid that and instead use values from the priorities store.
renderer: function(value){
if (value==3)
{
return "L";
}
else if (value==2)
{
return "M";
}
else
{
return "H";
}
},
EDIT2 for Evan:
Priorities store
Ext.define('AM.store.Priorities', {
extend: 'Ext.data.Store',
model: 'AM.model.Priority',
autoLoad: true,
proxy: {
type: 'ajax',
api: {
read: 'app/data/priorities.json',
update: 'app/data/updateUsers.json'
},
reader: {
type: 'json',
root: 'priorities',
successProperty: 'success'
}
}
});
The store.each refers to this store, right? If so, how do I perform the each operation on it?
I tried changing the declaration line to:
var test = Ext.define('AM.store.Priorities', {
And then tried changing your code to test.each but was unsuccessful.
Thanks again!
You need to use a renderer, however there's nothing stopping you from looping over the values in the priorities store and checking, something like:
renderer: function(value) {
var display = '';
store.each(function(rec){
if (rec.get('id') === value) {
display = rec.get('name');
return false;
}
});
return display;
}