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

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.

Related

Nested Models with separate API calls and separate stores (using custom references)

I'm wondering what's the best practice to do two separate fetches to data that would belong to the same Model. One to get all Users data and a separate one that would request their Traits and add them to each User.
I think I could create a reference in User, to fill the data, but im not sure how to create the custom reference since it should be an array.
export const User = types
.model('User', {
id: types.identifierNumber,
...
traits: types.maybeNull(TraitsbyUserReference),
})
const TraitsbyUserReference = types.maybe(
types.reference(Trait, {
get(identifier: string, parent): {
return (parent as Instance<typeof TraitsStore>).getAllTraits()
},
set(value) {
return value; // this is what doesnt work out because i'm fetching a whole array
},
}),
)
Also, is this a good practice or are there other better ways of getting this result?
Thanks!
In terms of defining the model, you might try switching from maybeNull to an optional array with a default value in your model -
...
traits: types.optional(types.array(Trait), []),
...
As such, the model will always be instantiated with an empty traits collection.
In terms of the TraitsbyUserReference, I am not following what abstraction that you need with the dynamic store look-up. You could create an action (e.g. User.actions(self => ...)) to look-up the traits as a separate api -
getUserTraits(){
/* this will vary based on your implementation of TraitsStore and how it is injected */
const traits = self.TraitsStore.getAllTraits(self.id);
self.traits = traits;
}

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

What must my Kendo datasource schema look like?

given this json?
[
{
"CompanyId":20,
"CompanyName":"Walmart",
"CompanyContacts":[
{
"CompanyId":20,
"FirstName":"Bob",
"LastName":"Green",
"Email":"bob#test.com",
"Phone":"1234567",
"IsActive":false
}
]
}
]
The KendoUI datasource schema.Model does not currently support nested json or json with related entities. It needs flat data. Hopefully in the future the schema.Model will support mapping complex json to flat in the model definition. However you can still use complex data in the grid you just can't define it in a schema.Model definition.
The mapping is actually done in the field definitions of the grid.
In addition see schema docs you can parse your data using the schema.parse or schema.data functions to manually transform your nested data into flat data.
Here is a fiddle example with your data
{
field : "CompanyContacts[0].FirstName",
title: "First Name"
}
Also note, if you don't need parent record CompanyName and CompanyID since you have CompanyID in your CompanyContacts in the way your data is currently defined then you can use the data attribute of the schema to indicate the starting point of your records like so
schema : {
model: mySchema,
data: "CompanyContacts"
},

Knockout Mapping - Fill Observable Arrays keeping Items' methods

I've been facing a problem that is basically the following:
I have a knockout ViewModel which contains observable arrays of items with observable properties and methods.
I need to pull data from the server. The methods need to exist after data is taken from server. So I create a new ViewModel and then update its value from what comes from server. (THIS DOES NOT WORK, THE RESULTING ARRAY HAS NO ITEMS)
If I create, with mapping, a new object using var newObj = ko.mapping.fromJS(data) the resulting Array has items, but its items have no methods. It spoils my Bindings.
The fiddle of my problem: http://jsfiddle.net/claykaboom/R823a/3/ ( It works util you click in "Load Data From The Server" )
The final question is: What is the best way to have items on the final array without making the loading process too cumbersome, such as iterating through every item and filling item's properties in order to keep the previously declared methods?
Thanks,
I changed your code little bit. Check this version of JSFiddle.
var jsonFromServer = '{"ModuleId":1,"Metadatas":[{"Id":1,"MinValue":null,"MaxValue":null,"FieldName":"Teste","SelectedType":"String","SelectedOptionType":null,"IsRequired":true,"Options":[]}]}';
Your code doesnt work because your jsonFromServer variable does not contain methods we need at binding like you described in your question. ( -- > Metadatas )
So we need to define a custom create function for Metadata objects at the mapping process like this :
var mapping = {
'Metadatas': {
create: function(options) {
var newMetaData = new MetadataViewModel(options.parent);
newMetaData.Id(options.data.id);
newMetaData.FieldName(options.data.FieldName);
newMetaData.SelectedType(options.data.SelectedType);
newMetaData.SelectedOptionType(options.data.SelectedOptionType);
newMetaData.IsRequired(options.data.IsRequired);
newMetaData.Options(options.data.Options);
// You can get current viewModel instance via options.parent
// console.log(options.parent);
return newMetaData;
}
}
}
Then i changed your load function to this :
self.LoadDataFromServer = function() {
var jsonFromServer = '{"ModuleId":1,"Metadatas":[{"Id":1,"MinValue":null,"MaxValue":null,"FieldName":"Teste","SelectedType":"String","SelectedOptionType":null,"IsRequired":true,"Options":[]}]}';
ko.mapping.fromJSON(jsonFromServer, mapping, self);
}
You dont have to declare a new viewModel and call ko.applyBindings again. Assigning the updated mapping to current viewModel is enough. For more information check this link. Look out for customizing object construction part.
The final question is: What is the best way to have items on the final
array without making the loading process too cumbersome, such as
iterating through every item and filling item's properties in order to
keep the previously declared methods?
As far as i know there is no easy way to do this with your object implemantation. Your objects are not simple. They contains both data and functions together. So you need to define custom create function for them. But if you can able to separate this like below then you dont have to customize object construction.
For example seperate the MetadataViewModel to two different object :
--> Metadata : which contains only simple data
--> MetadataViewModel : which contains Metadata observableArray and its Metadata manipulator functions
With this structure you can call ko.mapping.fromJSON(newMetaDataArray , {} , MetadataViewModelInstance.MetadataArray) without defining a custom create function at the mapping process.

Store with custom sorting in Sencha Touch

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.

Resources