How can I get viewModel object in store filter function? - extjs

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.

Related

ExtJs ViewModel: passing variable instead string

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
}
}
});

Recalculate field of model

I have the requirement to recalculate a field in a model on a certain event. I want to calculate the value directly in the model, and not in the view renderers, because otherwise multiple views would also need custom sorting and grouping functions, so the code would bloat massively. I could however use convert instead of calculate, if that helps.
The fields on the model:
fields: [{
name: 'name',
type: 'string'
},{
name: 'localizedName',
calculate: function(data) {
return Globals.localize(data.name);
}
}]
and the function on the store:
onUILocaleChange: function() {
this.each(function(record) {
// force recalculate
});
}
I have made a fiddle that exhibits the behaviour I wish to achieve, but without the bad hacky workaround: https://fiddle.sencha.com/#view/editor&fiddle/2g7g
I would like this to work without line 73-75, in-place in the model.
Just alter your calculate function to send the Globals.language as a parameter:
{
name: 'localizedName',
persist: false,
calculate: function(data) {
return Globals.localize(data.name,Globals.language);
}
}
And then alter your localize function to receive that parameter:
localize: function(v,l) {
return Globals[l][v];
}
And last, just reload the store after changing the Globals.language on button event handler:
handler: function(btn) {
Globals.language = 'de';
btn.up('grid').getStore().load();
}
Delete "onUILocaleChange" entirely.

Sencha Touch 2.3: Remove validations from hidden form fields

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.

Meteor, MongoDB get part of array through subscription

I have a question about how to just get a certain element of an array using MongoDB and MeteorJS. I have the following schema for the user document:
bankList:[
{
id: "34567890987654345678",
name: "xfgchjbkn",
type: "credit"
},
{
id: "09876543456789098767"
name: "65789876t8",
type: "debit"
}
]
I first subscribe to only part of the fields in the array, specifically I gather a list of all the ids. Then I have an edit screen that should subscribe to all of the fields for a specific element in the array with a matching id. I do not want to expose the rest of the array just the single element. Currently, I use the following to first gather a list of just the ids:
Meteor.users.find({_id: this.userId},
{fields:{'bankList.id': 1}});
And the following publication-subscription method to get just a specific element's information:
Publication:
Meteor.publish("userBankAdvanced", function(bankId){
check(bankId,String);
if(this.userId){
return Meteor.users.find({_id:this.userId,"bankList.id": bankId}, {'bankList.$': 1});
}else{
this.ready();
}
});
Subscription:
this.route('edit_account', {
path: '/edit/account/',
waitOn: function(){
if(Session.get("bankId")){
return Meteor.subscribe('userBankAdvanced',Session.get("bankId"));
}
return null;
},
data: function(){
if(Session.get("bankId")){
return Meteor.users.findOne();
}
return null;
},
onBeforeAction: function(){
beforeHooks.isRevise(Session.get("bankId"));
}
});
The subscription method returns all of the elements of the array with all of the information.
I want, for example, just this (not the entire list with all of the information):
bankList:[
{
id: "34567890987654345678",
name: "xfgchjbkn",
type: "credit"
}]
It looks like you're just missing the "fields" specifier in your "userBankAdvanced" publish function. I wrote a test in meteorpad using your example and it seems to work fine. The bank id is hardcoded for simplicity there.
So instead of
return Meteor.users.find({_id:this.userId,"bankList.id": bankId}, {'bankList.$': 1});
try using
return Meteor.users.find({_id:this.userId,"bankList.id": bankId}, {fields: {'bankList.$': 1}});
No luck, in meteor the "fields" option works only one level deep. In other words there's no builtin way to include/exclude subdocument fields.
But not all is lost. You can always do it manually
Meteor.publish("userBankAdvanced", function (bankId) {
var self = this;
var handle = Meteor.users.find({
_id: self.userId, "bankList.id": bankId
}).observeChanges({
added: function (id, fields) {
self.added("users", id, filter(fields, bankId));
},
changed: function (id, fields) {
self.changed("users", id, filter(fields, bankId));
},
removed: function (id) {
self.removed("users", id);
},
});
self.ready();
self.onStop(function () {
handle.stop();
});
});
function filter(fields, bankId) {
if (_.has(fields, 'bankList') {
fields.bankList = _.filter(fields.bankList, function (bank) {
return bank.id === bankId;
});
}
return fields;
}
EDIT I updated the above code to match the question requirements. It turns out though that the Carlos answer is correct as well and it's of course much more simple, so I recommend using that one.

Backgrid formatter adding values from other columns

Is it possible, with BackGrid, to build a custom Cell vía formatter, composing values from hidden columns?
var grid = new Backgrid.Grid({
columns: [
{
name:"half_value_1",
cell:"string",
rendered: false
},
{
name:"half_value_2",
cell:"string",
rendered: false
},
{
name: "composite",
cell: "string",
formatter: _.extend({}, Backgrid.CellFormatter.prototype, {
fromRaw: function (half_value_1, half_value_2) {
return half_value_1 + '/' + half_value_2;
}
})
}],
collection: col
});
Can I get the half_value_1 and half_value_2 inside the fromRaw function?
I think the best way to get the result you want is to use a custom cell rather than a custom formatter. You could do something like this for that particular column:
{
name: "composite",
cell: Backgrid.Cell.extend({
render: function(){
// You have access to the whole model - get values like normal in Backbone
var half_value_1 = this.model.get("half_value_1");
var half_value_2 = this.model.get("half_value_2");
// Put what you want inside the cell (Can use .html if HTML formatting is needed)
this.$el.text( half_value_1 + '/' + half_value_2 );
// MUST do this for the grid to not error out
return this;
}
})
}
That should work perfectly for you - I use it for a handful of grids in my projects. I didn't test this code though, so I may have typos :)
Key

Resources