Store with custom sorting in Sencha Touch - sorting

I have a store + model which is connected to a 3rd party plugin (Ext.ux.TouchGridPanel). The plugin calls the store's sort() method properly with the relevant mapping. Everything is working fine, and the store sorts itself. However, I would prefer to add customer sorting to the store. I have tried adding a sortType field into my model:
Ext.regModel("Transactions", {
fields: [
{
name: 'unit',
type: 'string',
sortType: function(value) {
console.log('PRINT GDAMNIT');
return 0;
}
},
...
]
});
This, however, is not working, and the sortType is not getting called.
TLDR: How to make custom sorting work for stores?

Your store will need a sorter added that will sort on that field before it will call the sortType function.
var store = new Ext.data.Store({
model: 'Transactions',
sorters: [
{
property: 'unit',
direction: 'DESC'
}
]}
);
Sort type converts the value of a field into another value to ensure proper ordering. If you aren't sorting on that field than there is no reason to call that function. You could add the sortDir to the field which would sort the field into ascending/descending order based on the type of the field alone.

A workaround might be to (I know this sounds inefficient but bear with me) add an extra field to your model instances (lets call it sortField) and use that for your sorting function. You can then loop through your model instances in your store applying your custom sorting algorithm and assign a sort value of 0,1,2,3,4,5 etc.. to sortField. Then in your store, you can add 'sorters: 'sortField'... Hope this helps a bit, I'm going through something similar at the current moment.

The custom SortType in Sencha Touch 2 works accordingly, as per http://docs.sencha.com/touch/2-0/#!/api/Ext.data.SortTypes:
Ext.apply(Ext.data.SortTypes, {
asPerson: function(person){
// expects an object with a first and last name property
return person.lastName.toUpperCase() + person.firstName.toLowerCase();
}
});
Ext.define('Employee', {
extend: 'Ext.data.Model',
config: {
fields: [{
name: 'person',
sortType: 'asPerson'
}, {
name: 'salary',
type: 'float' // sortType set to asFloat
}]
}
});

What you're attempting to do might be tricky. Calling store.sort() removes all existing sorters by default (according to Sencha Touch API documentation). To keep the existing sorters you would need to add the sorter to the MixedCollection store.sorters.
Secondly, to call the sort method with a custom sort function, you would need to pass a specific sorterFn instead of property to the Sorter (again, see the API for more details) - but this might prove tricky since the sort call is initiated from the plugin.
Not sure if this helps to solve your problem, but maybe it assists you to look at the right direction.

Related

Extjs: What is the correct way to use Associate Data of a model's field reference?

The Ext.data.Model class represents the backend models. And just like in the server code, some of its fields can be of another declared model type via the reference property. I've found out that using a model's getAssociatedData() function returns an object with all those referenced fields. However they only contain the reference object's data object they are not full fledged initialized Ext.data.Models, which forces a primitive object access and there is no way to use the model's configured proxies etc for loading/saving. Is this the correct/only way of using this functionality? We've also been looking for a way to add columns from referenced fields on a grid but it doesn't seem to work... I'm starting to doubt the usefulness of declaring referenced fields.
Example code:
Ext.define('MyApp.ModelA', {
extend: 'Ext.data.Model',
fields: [{
name: 'modelb',
reference: 'MyApp.ModelB'
}]
});
Ext.define('MyApp.ModelB', {
extend: 'Ext.data.Model',
fields: [{
name: 'modelId',
type: 'int'
}]
});
//...
var modelA = new MyApp.ModelA().load();
var modelB = modelA.getAssociatedData().modelb; //This is the only way to access it.
var modelBId = modelB.get('modelId') //This returns undefined because the function .get doesn't exist.
var modelBId = modelB.id; //This works because it is a simple object property access.
//...
As Chad Peruggia said, it seems that ExtJS creates special getters for reference fields that match the field name. Using getAssociatedData() returns only the primitive form of those objects (only their data values) but using the special getter (in my case getModelb()) it returns a full fledged model initialized with the given data.

AutoForm 5.0.2 nested schema inputs required on update

I have schemas set up so that I can have an array of complex input sets. Something like:
address = {
street:{
type: String
},
city: {
type: String
},
active_address: {
type: Boolean,
optional: true
},
...
}
people: {
name:{
type: String
},
address:{
type: [address],
optional: true,
defaultValue: []
}
}
This way adding an address is optional, but if you add an address all of the address fields are required.
This worked in (I believe it was) version 4.2.2. This still works on insert type autoforms, but not on update type autoforms. Doing an update, none of the fields will submit unless all required fields in the nested schema are also valid.
For reference, I'm creating the form as such:
{{#autoForm collection="people" id=formId type="update" doc=getDocument autosave=true template="autoupdate"}}
{{> afQuickField name='name' template="autoupdate" placeholder="schemaLabel"}}
{{> afQuickField name='address' template="autoupdate"}}
{{/autoForm}}
My templates (autoupdate) I copy-pasted the entirety of bootstrap3 autoform templates and rearranged some of the html to fit my needs. I updated these to the best of my ability according to the 5.0.0 changelog when I updated. It could possibly be in there if someone can think of an attribute in the templates that would cause inconsistent behavior between insert and update that changed in 5.0.0.
More information
I just tried recreating all of my form templates using the bootstrap3 templates from 5.0.2. Still the same behavior.
+
I have a Boolean (checkbox) input in the address schema. Looking in a doc, the address array is populated with [0 : {active_address: false}]
active_address: {
type: Boolean,
optional: true
}
Not sure if that helps...
+
As per #mark's suggestion, I added defaultValue:[]. It fixed the issue... sort of. There are no "open" nested schemas in the update form now, and other values can be changed. If you "add" a nested schema to the form with the add button, that entire form becomes required even if you don't insert any value in any field. This happens regardless of the Boolean type input.
I can nail down the Boolean type input in the nested schema causes that entire nested schema to become necessary to do the insert. Removing the Boolean input caused it to be insertable again. So there's a new problem in the same vein.
This new issue can be found here
I think the best solution is to add a defaultValue: [] to the address field in the schema. The behavior you described in the question (not allowing the update) is actually intended -- read on to see why.
The thing is, this behavior only exists if an array form element has already been added to the form. What I mean is, if you click the minus sign that removes the street, city, etc. inputs from the form, the update succeeds because AutoForm doesn't misinterpret the unchecked checkbox as the user explicitly unchecking the box (and therefore setting the value to false). Setting the defaultValue to an empty array lets AutoForm know to not present the address form unless the user has explicitly clicked the plus sign (i.e, they have an address they want to enter), in which case the behavior of making the street, city, etc. fields required is what you want.
Note that this means you'll have to update the existing documents in your collection that are missing the address field and set it to an empty array. Something like this in the mongo shell:
db.people.update({ "address": { $exists: false } }, { $set: { "address": [] } }, { multi: true })
You'll probably want to make sure that the query is correct by running a find on the selector first.
Edit
If the behavior you want is to show the sub-form without making it required, you can work around the checkbox issue by using the formToDoc hook and filtering out all address objects that only have the active_address field set to false (the field that AutoForm mistakenly adds for us).
AutoForm.addHooks('yourFormId', {
formToDoc: function (doc) {
doc.address = _.reject(doc.address, function (a) {
return !a.street && !a.city && !a.active_address;
});
return doc;
}
});
The formToDoc hook is called every time the form is validated, so you can use it to modify the doc to make it so that AutoForm is never even aware that there is an address sub-field, unless a property of it has been set. Note that if you're using this solution you won't have to add the defaultValue: [] as stated above.

Upon updating, how to compare a model instance with its former state

I'm using Sails.js v0.10.5, but this probably applies to more general MVC lifecycle logics (Ruby on Rails?).
I have two models, say Foo and Baz, linked with a one-to-one association.
Each time that data in a Foo instance changes, some heavy operations must be carried out on a Baz model instance, like the costlyRoutinemethod shown below.
// api/model/Foo.js
module.exports {
attributes: {
name: 'string',
data: 'json',
baz: {
model: 'Baz'
}
},
updateBaz: function(criteria,cb) {
Foo.findOne( criteria, function(err,foo) {
Baz.findOne( foo.baz, function(err,baz) {
baz.data = costlyRoutine( foo.data ); // or whatever
cb();
});
});
}
}
Upon updating an instance of Foo, it therefore makes sense to first test whether data has changed from old object to new. It could be that just name needs to be updated, in which case I'd like to avoid the heavy computation.
When is it best to make that check?
I'm thinking of the beforeUpdate callback, but it will require calling something like Foo.findOne(criteria) to retrieve the current data object. Inefficient? Sub-optimal?
The only optimization I could think of:
You could call costlyRoutine iff relevant fields are being updated.
Apart from that, you could try to cache Foo (using some locality of reference caching, like paging). But that depends on your use case.
Perfect optimization would be really like trying to look into the future :D
You might save a little by using the afterUpdate callback, which would have the Foo object already loaded so you can save a call.
// api/model/Foo.js
module.exports {
attributes: {
...
},
afterUpdate: function(foo,cb) {
Baz.findOne( foo.baz, function(err,baz) {
baz.data = costlyRoutine( foo.data ); // or whatever
cb();
});
}
}
Otherwise as myusuf aswered, if you only need to update based on relevant fields, then you can tap into it in the beforeUpdate callback.
Btw, instance functions should be defined inside attributes prop, lifecycle callbacks outside.

Kendo - creating a custom model property with value from server property

I have a property called Copies which is defined on the server that represents the default number of copies allowed. And I can update this value and it will update an input field on my UI.
however, I would like to be able to reset the Copies property to the original value if the user resets this field on the UI.
My idea was to define a custom property on my kendo datasource model called originalValue that references the Copies property. but this just seems to override the Copies property if I do something like this.
schema: {
data: 'd',
total: function (data) {
return data.d.length;
},
model: {
originalCopies: "Copies"
}
}
how can I go about creating a custom property like this which is basically a immutable clone of my Copies property?
You can try to do it on the server side, just create a separate property "OriginalCopies" and set it to Copies. Once passed to the client side , it will lose its immutability.
Something similar could be done on the client side as well. JSON.stringify your Copies and set
OriginalCopies to the JSON.parse value of the stringified variable as:
var copies = JSON.stringify(data.Copies);
data.OriginalCopies = JSON.parse(copies);

Associated model not updated from nested JSON on update through proxy?

In my Ext JS 4.1 application, I have a model Project with an belongsTo association to ProjectCategory:
Ext.define('MyApp.model.Project', {
extend: 'Ext.data.Model',
fields: ['id', 'project_category_id', 'title'],
belongsTo: {
model: 'MyApp.model.ProjectCategory',
associationKey: 'ProjectCategory',
getterName: 'getCategory'
}
// ...
});
Ext.define('MyApp.model.ProjectCategory', {
extend: 'Ext.data.Model',
fields: ['id', 'title']
});
The project is read via a Direct proxy, and ProjectCategory details are included as nested values in the response (mostly for display purposes). When loading a store, the associated data is read correctly, and I'm able to read the ProjectCategory's title in a grid via a custom renderer:
renderer: function(v, p, rec) {
return v ? rec.getCategory().get('title') : '';
}
However, when editing and saving the parent Project record through form.updateRecord(record), the associated record's fields aren't updated with values from the server response, unlike the Project's "native" fields. So when changing the project_category_id, even though the server will respond with a correctly nested ProjectCategory field containing the new category, getCategory will still return the old category.
Why isn't this updated from the server response, and what do I have to do to update it?
I already found out that in the store's write listener, I have access to the record and the returned association data. But I can't figure out how to update the record silently without triggering any other events.
It seems like I found my solution. Add this to the Project store:
listeners: {
write: function(store, operation, opt) {
var c = operation.records[0].getCategory();
c.set(operation.response.result.ProjectCategory);
c.commit(true);
}
}
The key element is the commit(true) call, which will skip notifying the store/proxy about the changes.
It's still a bummer that this isn't done automatically, though.

Resources