I've got an ExtJs ViewModel where I try to use fomulas. The following code is working as expected:
viewModel: {
formulas: {
isPressed: function (get) {
return get('state.attribute');
}
}
}
The debugger pauses two times in this formula. Once when opening the view and once when the state and property is initialized.
But if I try this:
viewModel: {
formulas: {
isPressed: function (get) {
var x = 'state.attribute';
return get(x);
}
}
}
The debugger only stops when opening the view but not the second time, when everything is initialized.
Edit
I tried to do the following. In my component I've got this config:
config: {
target: null
}
target contains a string like 'state.property' from my parent view which contains the component. Now in this component I want a binding to the value of target but don't want to write:
formulas: {
isPressed: {
bind: '{state.property'},
get: function(property) { ... }
}
}
because the value to bind to should be dynamic. I want to reuse the component in different places. So I tried this, but didn't work:
viewModel: {
formulas: {
isPressed: function (get) {
return get(this.getView().getTarget());
}
}
}
The reason this occurs is because it parses the contents of the function to figure out the dependencies. However it only uses a really naive parser, so things like you described won't be picked up. You can explicitly specify dependencies using bindTo:
const viewModel = new Ext.app.ViewModel({
formulas: {
isNameModified: {
bind: {
bindTo: '{theUser}',
deep: true
},
get: user => user.foo
}
},
data: {
theUser: {
foo: 1
}
}
});
Related
I defined the store and a filter. The ViewModel contains test object I need to filter store items by this object.
Ext.define('XXX.view.XXX.ViewXXXXModel', {
extend: 'Ext.app.ViewModel',
...
stores: {
agreements: {
source: 'XXX',
filters: {
filterFn: function(item) {
return item.some_field !== this.get('test').somevalue;
}
}
}
}
I cannot access the test object of View Model from filter function?
Way too late now, but I just had the same issue, and the cleaner method to do this is by returning filterFn as a formula bind:
For your original example:
stores: {
agreements: {
source: 'XXX',
filters: [{
filterFn: '{storeFilter}'
}]
}
}
},
formulas: {
storeFilter: function(get) {
var somevalue = get('test').somevalue;
return function(item) {
return item.some_field !== this.get('test').somevalue;
};
}
}
Edit:
When I originally wrote this I wasn't aware that Ext continually added extra filters when using setFilters rather than just replacing them all. To get around this, you need to name the filter using an id. In the above example something like this:
filters: [{
id: 'myVMFilterFunction',
filterFn: '{storeFilter}'
}]
Then it replaces the filter as expected
Ideally you would use the declarative filter format in most cases - the granularity ensures that bindings are more specific, triggering appropriate / expected updates when data changes. For example:
stores: {
agreements: {
source: 'XXX',
filters: {
property: 'some_field',
value: '{test.somevalue}',
operator: '!='
}
}
}
If you really want to use imperative code you can inject the view-model scope via a formula:
formulas: {
_this: function(){
return this;
}
}
Then bind it like so:
stores: {
agreements: {
source: 'XXX',
filters: {
scope: '{_this}',
filterFn: function(item){
return item.some_field !== this.get('test.somevalue'));
}
}
}
}
This is a bit of a kludge though and changes to test likely won't be reflected in the store and any visual component tied to it. In this case you'd end up having to manually reload the store or reapply the filters - which kind of defeats the point of MVVM.
I'm using the gpl version of extjs5.1.1 without sencha cmd.
In my application a Grid displays correctly a Store.
In the window, there is a checkbox wich code is :
{
xtype : 'checkbox',
itemId : 'employeeFilter',
value : false,
boxLabel : 'Show inaktiv users',
margin : '0 5 0 5',
listeners: {
change: function(checkbox, newValue, oldValue, eOpts) {
var store = this.up('window').getComponent('employeeGrid').getStore();
if (newValue) {
console.log('clearFilter');
store.clearFilter();
} else {
console.log('rightFilter');
store.rightFilter();
}
}
}
}]
The variable store points to the grid store correctly. I can see the messages 'clearFilter' and 'rightFilter' in the console.
The store code:
Ext.define('Chronos.store.manage.Employees', {
extend : 'Ext.data.Store',
model : 'Chronos.model.manage.Employee',
autoLoad : false,
autoSync : true,
sortOnLoad : false,
pageSize : 0,
rightFilter: function() {
this.filterBy(function(record) {
return record.get('rights') >= 0;
});
},
proxy : {
type : 'ajax', // Ext.data.proxy.Ajax
api : {
create : 'api/management/employees.create.php',
read : 'api/management/employees.read.php',
update : 'api/management/employees.update.php',
destroy : 'api/management/employees.destroy.php'
},
reader : {
type : 'json', // Ext.data.reader.Json
rootProperty : 'records'
},
writer : {
type : 'json', // Ext.data.writer.Json
encode : true,
rootProperty : 'record'
}
}
});
On window call, the checkbox is unchecked and the filter is active because the grid listeners is:
listeners: {
render: function() {
this.getStore().load();
this.getStore().rightFilter(); // <<<<< if the function is called here, the problem exists, if not, the filter works perfectly !
}
},
The first time I check the checkbox the filter is cleared correctly and the message 'clearFilter' appears in the console. When I uncheck it, the message 'rightFilter' appears too, but the filter does not anything in the grid... and the error message "Uncaught TypeError: Cannot read property 'get' of undefined" appears.
Where am I wrong?
Edit: Actually the filter function only works fine once. It does not work any more after clearFilter is called...
I tried with addFilter/removeFilter with the same result.
My next try will be setDisable.
If someone has any (other) idea...
Edit 2: Test case in fiddle
Now I know that the problem comes from function call in the render function. When this call is not done, the checkbox works perfectly, but on show, the checkbox state does not correspond to the display and I want to hide filtered items on show.
Any Idea ?
The filter function cannot be called just after the load, that's why load in render and filter in viewready (in afterrender it doesn't work either). The problem is solved !!
listeners: {
render: function() {
this.getStore().load();
},
viewready: function() {
this.getStore().rightFilter();
}
}
The problem is in this line is with this.
rightFilter: function() {
this.filterBy(function(record) { // <<<<< this line sends an error the second time the function is called
return record.get('rights') >= 0;
});
},
Can you please change the above code to
rightFilter: function(me) {
me.filterBy(function(record) { // <<<<< this line sends an error the second time the function is called
return record.get('rights') >= 0;
});
},
and the grid listener to pass store
listeners: {
render: function() {
this.getStore().load();
this.getStore().rightFilter(this.getStore());
}
},
and the calling from combobox to
if (newValue) {
console.log('clearFilter');
store.clearFilter();
} else {
console.log('rightFilter');
store.rightFilter(store);
}
I am doing form validations in Sencha Touch 2.3. My model looks like following.
Ext.define('net.omobio.dialog.dialogcc.model.StockTransferDetails', {
extend: 'Ext.data.Model',
config: {
fields: ['to_msisdn','to_profile_id','transfer_lob','transfer_item_status','transfer_product','transfer_qty','transfer_req_type','transfer_item_type','transfer_pack_type'],
validations: [
{ type: 'presence', field: 'to_msisdn' },
{ type: 'presence', field: 'to_profile_id' },
{ type: 'exclusion', field: 'transfer_lob', list: ['null'] },
{ type: 'exclusion', field: 'transfer_req_type', list: ['null'] },
{ type: 'exclusion', field: 'transfer_item_type', list: ['null'] },
{ type: 'exclusion', field: 'transfer_pack_type', list: ['null'] }
]
}
});
Following is a code segment that I use in my controller to remove validations from hidden form fields but no luck.
var form1 = me.getStockTransferRequestPage();
var model = Ext.create("net.omobio.dialog.dialogcc.model.StockTransferDetails", form1.getValues());
// validate form fields
var errors = model.validate();
if (!errors.isValid()) {
// loop through validation errors and generate a message to the user
errors.each(function (errorObj){
//errorString += errorObj.getField() + " " + errorObj.getMessage();
console.log('7777777777777777777 '+errorObj.getField());
if (!Ext.getCmp(errorObj.getField().toString()).isHidden()) {
var s = Ext.String.format('field[name={0}]',errorObj.getField());
form1.down(s).addCls('invalidField');
}
});
Ext.Msg.alert('','stock_transfer.errors.required_fields_empty');
}
I would be much appreciated if anyone could help me to solve this.
Thank you
so there are multiple ways to achieve this, my preference even though some folks won't like it, but it will always work.
I did the following override to solve this problem, tried my best not to affect the normal flow of validation.the first two overrides have to be added somewhere to your overrides folder, you only have to add them once for the whole app.
Ext.Define('Ext.form.field.BaseOverride', {
override: 'Ext.form.field,Base',
/* traverse up and look for a hidden Parent/Ancestor */
isParentHidden: function () {
return this.up('[hidden=true]');
}
/* override isValid basic method to consider skipValidateWhenHidden property, when skipValidateWhenHidden is set to true code should check if the elementor it's Parent/Ancestors is hidden */
isValid: function () {
var me = this,
disabled = me.disabled,
isHidden = me.isHidden(),
skipValidateWhenHidden = !!me.skipValidateWhenHidden,
validate = me.forceValidation || !disabled,
isValid = validate ? me.validateValue(me.processRawValue(me.getRawValue())) : disabled;
if (isValid || !skipValidateWhenHidden) {
return isValid;
}
if (skipValidateWhenHidden) {
isHidden = isHidden ? true : me.isParentHidden();
if (isHidden) {
return skipValidateWhenHidden;
}
}
return isValid;
}
});
and eventually you'll be able to do the following, which is set the property to true on any field so if its not visible for the user, it will survive validation
{
itemId: 'City',
cls: 'AddressCity',
xtype: 'textfield',
emptyText: emptyCityText,
skipValidateWhenHidden: true,
},
another approach is to add a show()/Hide() listener on the fields container to enable/disable the children, disabling the fields would make them skip validation, but i'm not a big fan of managing button states and wiring listeners.
Note
Ext.getCmp() takes component id
errorObj.getField().toString() returns model field name, It won't
return id of the component (hidden) field.
So if model field name matches with hidden field id, It will work. Otherwise it won't work.
i have simple 'gridpanel' with 'tbar' like this
Ext.define('Ext.abc.grid', {
extend: 'Ext.grid.Panel',
type:1,
tbar:[
{
text:'title1',
class :'a1',
handler:function(type){
if (this.type == 1) { // button not 1
Ext.query(".a2").setDisabled(false);
}
},{
text:'title2',
class :'a2',
handler:function(type){
if (this.type == 1) { // button not 1
Ext.query(".a1").setDisabled(false);
}
}
]
});
i try to add class (a1) to button title1 and the some for title2, but when i get class like
Ext.query(".a1").setDisabled(false);
it's not working
and i can't get type = 1 when i click title1, i using this.type but results is 'button' not 1
How can i do that, thanks
You've got several problems here.
First, see sha's answer, you're getting an array as the result of your call to Ext.query(...).
Second, Ext.query returns Ext.dom.Element, which are Ext objects for representing actual DOM elements like div, img, etc. What you want to access, your buttons, are Ext.Component. You can query components with Ext.ComponentQuery.
Then, you're using this.type in your button handler functions, but when these method get called, this will be the button itself (this can be customized using the scope option), not the container on which you set type: 1.
Edit:
Here's how to make your example work:
Ext.define('Ext.abc.Grid', {
extend: 'Ext.grid.Panel'
,type: 1
,tbar: [{
text: 'title1'
,itemId: 'button1'
// just FYI, here the scope (this) is the window, because we are not
// in a method
,scope: this // so this doesn't work
,handler: function() {
// using ComponentQuery to get a reference to the other components
var grid = this.up('grid'), // by xtype
tbar = this.up(), // by relative position
button2 = tbar.down('#button2'); // by itemId
if (grid.type === 1) {
button2.disable();
}
}
}, {
text: 'title2'
,itemId: 'button2'
,handler: function() { ... }
}]
});
Now, reading your mind, here's what I think you actually want to do:
Ext.define('Ext.abc.Grid', {
extend: 'Ext.grid.Panel'
,type: 1
,tbar: [{
text: 'title1'
,itemId: 'button1'
}, {
text: 'title2'
,itemId: 'button2'
}]
// reading in your mind, I guess, this is what you really want to do:
,initComponent: function() {
this.callParent();
if (this.type === 1) {
this.down('#button2').disable();
} else {
this.down('#button1').disable();
}
}
});
Ext.query returns you an array http://docs.sencha.com/extjs/4.1.3/#!/api/Ext-method-query
You can't simply call setDisabled() on an array. You need to loop through all elements.
I have a custom data store which stores the information whether the store has been loaded once or not.
/**
* Custom DataStore which stores the information whether data has been loaded once or not.
*/
Ext.example.Store = Ext.extend(Ext.data.Store, {
loaded: false,
initComponent: function() {
this.superclass().initComponent.call(this);
this.addEvents('load','beforeload');
},
isLoaded : function() {
return this.loaded;
},
listeners: {
'load' : function(store,records,options) {
this.loaded = true;
}
}
});
This was working fine until recently I added a 'beforeload' event listener to an instance of this store. The 'load' listener does not get invoked now.
var accountsDataStore = new Ext.example.Store({
id : 'account',
proxy : new Ext.data.HttpProxy({
url : 'app/account/fetch/all',
method : 'post',
api : {
destroy : 'app/account/delete'
}
}),
reader : accountReader,
writer : accountWriter,
remoteSort : true,
sortInfo : {
field : 'createdOn',
direction : "DESC"
},
listeners: {
beforeload: function(store, options) {
var sort = options.params.sort;
// setting the sort parameters to match the property names in the DTO
switch(sort) {
case 'company' :
options.params.sort = 'company.name';
break;
default:
// do nothing
}
}
}
});
What could I be doing wrong here? Also please let me know your suggestions to improve
The problem is that you should never set objects on the prototype, unless you really know what it means (that it will be shared by all instances and can be overridden on a per instance basis)
In Ext JS, I only set config options on the prototype that are only for convenience and may be overridden by the caller.
Your first class Ext.example.Store puts listeners on its prototype.
Then you go and overwrite it in accountsDataStore by passing in a listeners object into the config.
To fix your problem, instead of setting listeners on the prototype, just call this.on('event') from the constructor.
/**
* Custom DataStore which stores the information whether data has been loaded once or not.
*/
Ext.example.Store = Ext.extend(Ext.data.Store, {
loaded: false,
constructor: function() {
this.superclass().constructor.call(this);
// No need to call this, you're not adding any events
// this.addEvents('load','beforeload');
this.on('load', function(store,records,options) {
this.loaded = true;
}, this);
},
isLoaded : function() {
return this.loaded;
}
});
The documentation for the "beforeload" event states:
"Fires before a request is made for a new data object. If the
beforeload handler returns false the load action will be canceled."
You could trying returning true from your beforeload listener to ensure the load action still runs.
store.totalCount
if loaded, this property return a number
else
this property is undefined
(extjs-4.1.0-rc1)
Ok, I think there is a problem with scope. Please try it this way:
listeners: {
'load' : {
fn : function(store,records,options) {
console.log(this, this.loaded);
this.loaded = true;
},
scope : this
}
}
Or you can use:
listeners: {
'load' : function(store,records,options) {
store.loaded = true;
}
}