best practice for overriding ExtJS model getters & setters - extjs

i want to intercept field get/set on a model so that i can transform the values before they get displayed as well as before they get stored in the model. in this case, i want to store uriencoded data in the model, but display decoded values.
when i directly override the get/set methods i see that they are being used by the proxy to put raw data into the model. i dont want to override that process and i am not even sure how to tell whether it is the proxy loading a model or the ui.
what is the best method for this? convert, btw, appears to be a poor choice. it is not 2-way and live.

Note: I have not tested this code, so use the concepts and comment later if you have questions.
When you define your Ext.data.Model class for your data entities (use Ext.data.Record for Ext JS 3), add a convert function as part of your Ext.data.Field property instance. This can be done for all fields if you want, but probably not recommended.
I'm new to this framework, so one thing I do not know how to do is use the "this.self" notation in the convert function in case you are extending another class in your Model class. For example, if you do not have a default value defined in the Employee class and it inherits from another class (like Employee for example), but the Employee constructor sets that property, it'll take the value defined in the super class (base class or parent class) constructor when you encode the data. Right now, it just gets the EmployeeDeveloper.dataValue property.
Ext.define('EmployeeDeveloper', {
extend: 'Ext.data.Model',
fields: [
{ name: 'name', type: 'string', defaultValue: 'Paul' },
{ name: 'profession', type: 'string', defaultValue: 'Ext JS developer' },
{ name: 'salary', type: 'float', defaultValue: '95000' },
{ name: 'dataValue', type: 'string',
convert: function(value, record) {
// feel free to use value and record parameters in this function
return encodeData(record.get('dataValue')); // you may have to use the "value" parameter instead of record.get('dataValue') if referencing the field in it's own convert function (haven't tested)
}
}
]
});
var empDev = Ext.create(
'EmployeeDeveloper',
{
name: 'MacGyver',
dataValue: 'ABC'
},
'MacGyver',
123
);
var encodedDataValue = empDev.get('dataValue');
Caution about convert: If you are referencing a field that is defined below the convert function signature, the code will not know about it and won't be able to retrieve the value.
OR IF YOU HAVE A CUSTOM CLASS BUT NOT USING THE MODEL CLASS...
Tonight I also read about something else that's kind of neat in Ext JS. They added auto getters and setters in Ext JS 4. The new version of the framework automatically prefixes your property with "get", "set", "reset", or "apply" and capitalizes the first letter of the property inside of the "config" property of your defined class.
In regular JavaScript, they don't have real classes, but Sencha has made it possible to define a class (with the "define" function) and the ability to instantiate an object instance of your user defined class.
For example, suppose you have the following class defined:
Ext.define('Paul.MyClass'), {
extend: 'Ext.Window',
config: {
name: 'Paul'
}
});
If you create an instance of your object like so, you automatically have access to four functions.
var win = Ext.create('Paul.MyClass'); // create instance of MyClass object
var myName = win.getName(); // get name
win.setName('MacGyver'); // set name
win.resetName(); // this resets the value back to the default value ('Paul')
win.applyName(); // this calls custom code
win.show();
I'd suggest overriding the apply* method with the logic you are suggesting. Then calling win.get*(); afterward to get the new value. It might be wise to have an extra encoded field for each field you plan on encoding so you don't have to manipulate your properties every time you access (get or set) your data in your proxies and/or stores.
Ext.define('Paul.MyClass'), {
extend: 'Ext.Window',
config: {
name: 'Paul',
dataValue: 'non encoded value of your data'
}
applyName: function(title) {
this.name = this.name + ' ' + title; // custom logic goes here
}
applyDataValue: function(encodeKey) {
// get the encoded data value
this.self.dataValue = 'encoded data value'; // custom logic goes here
}
});

Related

How do I get array item type in TypeScript using the Reflection API?

I have the following little class in TypeScript, with some public fields decorated:
class Company {
#dataMember
public name: string;
#dataMember
public people: Person[];
}
class Person {
// ...
}
By using reflect metadata, I can determine the types of Company properties name and people: they are the constructor functions String and Array, respectively, which is expected and logical.
My property decorator function:
function decorate(target: Object, propertyKey: string | symbol): void {
var reflectType = Reflect.getMetadata("design:type", target, propertyKey);
// ...
}
But how could I determine the type (constructor function) of array elements? Is it even possible? In the above example, it should be (a reference to) Person.
Note: I need the type reference before instantiation, and because of this, it is impossible to dynamically determine the type using array items: there are no array items, there isn't even an Array instance.
I don't think this is possible as of now. If you see the generated js file, for array of anything, it creates metadata with type as Array without any information on type.
__decorate([
dataMember_1.dataMember,
__metadata('design:type', Array)
], Company.prototype, "people", void 0);
For built-in types, one way I could think of solving this problem is to pass the type in the decorator itself and writing the custom logic in the decorator code.
#dataMember(String)
myProp: Array<String>
For Custom objects, most of the time when the decorator call is fired, the module is not fully loaded. So, one way is to pass the class name and parse it later.
#dataMember("People")
people: People[]

How to setup client side metadata for SPA using breeze

Hello all I am working on a small report library SPA using angular and breeze that allows a user to manage reports they create. I know you can use breeze along with the EF context to build the model by referencing the API.. But how this is being implemented I want to leave EF out of it. The API (WebAPI 2) is basically calling other repositories to do the work almost like an interface. The result coming back is just a json object. I have looked over the Edmunds sample from the Breeze website and I can see how I can build a client model as well as handle the mapping on the return. The current issue I have is I am not certain that I have the mapping in the jsonResultsAdapter correct. Or that I may be missing something on how the mapping is supposed to work.
I'm testing this currently with some mock data I have stubbed into the API until I can get this to work. Once this is bound and mapping I can go against the actual data. Here is what I have so far:
The mock data is a report object that contains an internal collection called labels (tags basically) a report can have multiple tag and from the label it can have multiple reports.
//report dto
public class ReportDto
{
public Int64 ReportId { get; set; }
public string ReportName { get; set; }
public string ReportDescription { get; set; }
public string ReportDateCreated { get; set; }
public string ReportOwner { get; set; }
public IEnumerable<ReportLabelDto> ReportLabels { get; set; }
}
public class ReportLabelDto
{
public Int64 LabelId { get; set; }
public string LabelName { get; set; }
public bool IsPrivate { get; set; }
public bool IsFavorite { get; set; }
public IEnumerable<ReportDto> Reports { get; set; }//placeholder?
}
Here is the code currently being used within the webapi controller which at this time is just for testing:
[Route ("reportlibrary/myreports/{userid}")]
public IEnumerable<ReportDto> GetAllReports(string userId)
{
List<ReportDto> result = new List<ReportDto>();
List<ReportLabelDto> label = new List<ReportLabelDto>();
//create 5 reports
for (int i = 0; i < 5; i++)
{
ReportDto r = new ReportDto();
ReportLabelDto l = new ReportLabelDto();
r.ReportId = i;
r.ReportOwner = "John Smith";
r.ReportDateCreated = DateTime.Now.ToString();
r.ReportDescription = "Report Description # " + i.ToString();
r.ReportName = "Report Description # " + i.ToString();
//generate labels
l.LabelId = i;
l.LabelName = "Special Label" + i.ToString();
l.IsPrivate = true;
l.IsFavorite = false;
label.Add(l);
r.ReportLabels = label;
result.Add(r);
}
return result;
}
The object that is currently coming back looks like this:
[{"ReportId":0,"ReportName":"Report Description # 0","ReportDescription":"Report Description # 0","ReportDateCreated":"12/22/2014 6:32:05 PM","ReportOwner":"John Smith","ReportLabels":[{"LabelId":0,"LabelName":"Special Label0","IsPrivate":true,"IsFavorite":false,"Reports":null},{"LabelId":1,"LabelName":"Special Label1","IsPrivate":true,"IsFavorite":false,"Reports":null},{"LabelId":2,"LabelName":"Special Label2","IsPrivate":true,"IsFavorite":false,"Reports":null},{"LabelId":3,"LabelName":"Special Label3","IsPrivate":true,"IsFavorite":false,"Reports":null},{"LabelId":4,"LabelName":"Special Label4","IsPrivate":true,"IsFavorite":false,"Reports":null}]},{"ReportId":1,"ReportName":"Report Description # 1","ReportDescription":"Report Description # 1","ReportDateCreated":"12/22/2014 6:32:05 PM","ReportOwner":"John Smith","ReportLabels":[{"LabelId":0,"LabelName":"Special Label0","IsPrivate":true,"IsFavorite":false,"Reports":null},{"LabelId":1,"LabelName":"Special Label1","IsPrivate":true,"IsFavorite":false,"Reports":null},{"LabelId":2,"LabelName":"Special Label2","IsPrivate":true,"IsFavorite":false,"Reports":null},{"LabelId":3,"LabelName":"Special Label3","IsPrivate":true,"IsFavorite":false,"Reports":null},{"LabelId":4,"LabelName":"Special Label4","IsPrivate":true,"IsFavorite":false,"Reports":null}]},...]
I have the services and controllers all talking and I can hit the api and get an object returned so I am going to omit that code for right now.
For the js model I defined the report object as follows:
app.factory('model', function () {
var DT = breeze.DataType;
return {
initialize: initialize
}
function initialize(metadataStore) {
metadataStore.addEntityType({
shortName: "Report",
namespace: "Inform",
dataProperties: {
reportid: { dataType: DT.Int64, isPartOfKey: true },
reportname: { dataType: DT.String },
reportdescription: { dataType: DT.String },
reportdatecreated: { dataType: DT.String },
reportowner: { dataType: DT.String },
mappedlabels: { dataType: DT.Undefined },
ishared: { dataType: DT.Bool },
isfavorite: { dataType: DT.Bool }
},
navigationProperties: {
labels: {
entityTypeName: "Label:#Inform", isScalar: false,
associationName: "Report_Labels"
}
}
});
metadataStore.addEntityType({
shortName: "Label",
namespace: "Inform",
dataProperties: {
labelid: { dataType: DT.Int64, isPartOfKey: true },
reportid: { dataType: DT.Int64 },
labelname: { dataType: DT.String },
ispublic: { dataType: DT.Bool },
mappedreports: { dataType: DT.Undefined }
},
navigationProperties: {
labels: {
entityTypeName: "Report:#Inform", isScalar: false,
associationName: "Report_Labels", foreignKeyNames: ["reportid"]
}
}
});
}
})
This is where I think the issue is I don't understand this adapter enough to ensure that I am receiving what I think I am as well as if it is handling the mapping correctly:
/* jsonResultsAdapter: parses report data into entities */
app.value('jsonResultsAdapter',
new breeze.JsonResultsAdapter({
name: "inform",
extractResults: function (data) {
var results = data.results;
if (!results) throw new Error("Unable to resolve 'results' property");
// Parse only the make and model types
return results && (results.reportHolder || results.labelHolder);
},
visitNode: function (node, parseContext, nodeContext) {
//Report parser
if (node.reportid && node.labels) {
node.mappedlabels = node.labels;
node.labels = [];
return { entityType: "Report" }
}
// Label parser
else if (node.labelid && node.reports) {
node.mappedreports = node.reports;
node.mappedreports = [];
return { entityType: "Label" };
}
}
}));
When I step through the code in chrome I can see that an object is returned. with 5 reports and each report has 5 labels ( I know the labels are showing null reports currently). When I set breakpoints within the jsonResultsAdapter I can see the result with 5 objects, but what gets passed back to the service is as a result is null. Can anyone help me verify if the model and mapping is correct or if you see anything out of place in the jsonResultsAdapter. I'd also appreciate any suggestions on things I may want to do different. I feel very black-box right now as I don't see/understand a good way to troubleshoot this mapping piece.
-cheers
Here I'll pick up on some of PW Kad's observations and add a few of my own.
Let's first understand the different roles of metadata and the JsonResultsAdapter.
Metadata is where you define the schema and validation rules for the client-side entity model. It describes the entity objects that Breeze keeps in cache and makes available to your program.
But the metadata have nothing to say about the JSON payload arriving from the server. That's a completely separate and lower level concern. That's the concern of the JsonResultsAdapter.
The JsonResultsAdapter sits in the pipeline between the JSON data arriving from the server as a result of an AJAX call ... and the entities in cache. The JSON data don't have to be shaped like the entities. They don't have to conform to the metadata you wrote. The metadata describe the entities as you would like to consume them. The JSON are the sad reality that the service gives you. The JsonResultsAdapter is there to bridge the gap.
Whether the entity schema conforms to the shape of the JSON payload is anyone's guess. Often the JSON data needs a little tweaking. It's the JsonResultsAdapter's job to manipulate the JSON "nodes" into something that Breeze can map into your entities. The job is easier if the JSON payload closely approximates the entity shape described by your metadata. Let's hope your JSON aligns well with your entities.
Metadata and materialization
Now Breeze does use the metadata when mapping the JSON into the entities. The MetadataStore has a NamingConvention that prescribes how to translate between the client entity property names and the service property names. The "materialization" process expects the JSON emerging from the JsonResultsAdapter to have the expected service property names. That's why I was adamant that the node property names (if you need them) be spelled in PascalCase ... assuming that you are using the standard Breeze camelCase convention and that your service does, in fact, spell property names in PascalCase.
Most C# and Java servers do. Rails and Node servers generally don't; they use camelCase on the server too ... which means you'd want NamingConvention.none if you're consuming feeds from these kinds of servers.
Ideally the JsonResultsAdapter has to do very little. The JSON property names typically map easily and obviously to the entity property names and you can handle whatever translation is needed with a NamingConvention. Such appears to be the case for you (see below).
For sure you're not accomplishing a thing with the code you showed us:
node.ReportId = node.ReportId;
node.ReportName = node.ReportName;
node.ReportDescription = node.ReportDescription;
That is the most elaborate "no op" code I've seen in a very long time. I wonder what you had in mind.
JsonResultsAdapter is often needed when identifying the EntityType corresponding to a JSON node. If you're not sourcing the data from .NET using the Json.Net serializer, your server may not be sending the type name down with the JSON data. Your JSON nodes are missing the $type property that Breeze is looking for by default.
If that's your situation (and it seems it is), your JsonResultsAdapter has to supply the type name.
Apparently, you can do that for your data by examining each node's key property. It seems that key property name contains within itself the distinguishing part of the type name.
Perhaps your JsonResultsAdapter.visitNode method could look like this:
visitNode: function (node, parseContext, nodeContext) {
//Report parser
if (node.ReportId) {
return { entityType: "Report:#Inform" }
}
// Label entity
else if (node.ReportLabels) {
return { entityType: "Label:#Inform" };
}
}
Notice that I included the namespace (:#Inform) in the entityType name property. The namespace is part of each EntityType's full name ... and you must supply it.
Notice that I did NOT do any property name mapping. I didn't see any reason for it. The node property names look just like the entity metadata names ... except for the PascalCasing ... and we take care of that with the NamingConvention.camelCase.
Bad Metadata?
Well actually the node property names do NOT look like the entity property names in your metadata, not even after accounting for the Pascal-to-camel-case conversion. I think this is what PW Kad was pointing out.
The problem is that the entity property names in your metadata are all lower case. Not camelCase; lower case. For example:
reportid: { dataType: DT.Int64, isPartOfKey: true },
reportname: { dataType: DT.String },
reportdescription: ...
Shouldn't they be:
reportId: { dataType: DT.Int64, isPartOfKey: true },
reportName: { dataType: DT.String },
reportDescription: ...
That would correspond nicely to your JSON property names
ReportId
ReportName
ReportDescription
Why would you want all lowercase property names on the client.
You could go all lowercase and write a really wacky custom NamingConvention to navigate between client entity names and service names. That's a lot of work to no purpose in my book.
Why is there no $type in your JSON?
I just scrolled to the top of this question and realized that your server is written in C# and it looks like you're using the Web API.
Why did you not annotate your Web API controller with the [BreezeController] attribute? Doing so would have configured your controller to serialize the data in the manner a Breeze client understands by default. You might not need a JsonResultsAdapter at all.
Don't change the type name!
Looking again I see yet another problem looming ahead. Your server-side class names have the suffix "Dto" but you don't want that suffix on your client type names. You are also changing the type name completely in one case: "ReportLabelDto" to "Label".
Breeze has a naming convention for morphing property names. It doesn't have a naming convention for "entity type" names.
It will be a royal pain if you insist on having different type names on the client and the server. I'm not sure it can be done.
Yes you can morph the entity name in the JsonResultsAdapter. That covers communications on the way in. But you also have to worry about the communications on the way out. The server is not going to be happy when you ask it to save an entity of class "Label" ... which it knows nothing about.
As I write I can't think of an easy way around this. At the moment, Breeze requires that the server-side type name be the same as the client EntityType name. If the type name on the server is "ReportLabelDto", you'll have to name the corresponding EntityType "ReportLabelDto". There is no easy way around that.
Fortunately, unlike property names which show up everywhere, you don't often refer to the EntityType name on the client so calling it "ReportLabelDto" shouldn't be a big deal.
It looks like all of your properties you are defining are not properly camelCased nor PascalCased. Breeze.js will look for properties that match - but unless I am missing something you have defined it will not toLowerCase them.
You need to set your model properties up like this -
ReportName: { dataType: DT.String },
and then in your results adapter you need to check for the property names properly like this -
if (node.ReportId && node.Labels) {
Thanks to PW Kad for pointing me in the right direction. I had forgotten the case sensitivity portion. In addition I went back and reassessed what I was trying to do in the jsonResultsAdapter.js file. I finally realized this file is working similar to automapper and I was under the impression that breeze would internally resolve the mappings. (maybe it does but with the EF context) but when creating client side meta I had to explicitly set the mappings. The updated code now shows:
app.value('jsonResultsAdapter',
new breeze.JsonResultsAdapter({
name: "inform",
extractResults: function (data) {
var results = data.results;
if (!results) throw new Error("Unable to resolve 'results' property");
// Parse only the make and model types
return results;
},
visitNode: function (node, parseContext, nodeContext) {
//Report parser
if (node) {
node.ReportId = node.ReportId;
node.ReportName = node.ReportName;
node.ReportDescription = node.ReportDescription;
node.ReportDateCreated = node.ReportDateCreated;
node.ReportOwner = node.ReportOwner;
node.ReportLabels = node.ReportLabels;
node.ReportLabels = [];
node.IsShared = node.IsShared;
node.IsFavorite = node.IsFavorite;
return { entityType: "Report" }
}
// Label parser
else if (node.ReportLabels) {
node.LabelId = node.LabelId;
node.LabelName = nodel.LabelName;
node.IsPrivate = node.IsPrivate;
node.IsFavorite = node.IsFavorite;
node.Reports = node.Reports;
node.Reports = [];
return { entityType: "Label" };
}
}
}));
I know I still have some tweaking to do on the mappings more than likely or how it is parsed from the API but making this change properly mapped the mock data and allowed it to bind/display within the UI.
Hope this helps

Breeze Angular (Defining Client Side Properties)

Gurus,
Here is my scenario:
I am defining a new client-side property (i.e. fullName) on one my entities using breeze's registerEntityTypeCtor function. The fullName property is coded to check the values of the firstName and lastName properties on the entity to determine it's value. It works when I am doing a query and receiving entities back from the db.
However, when I create a new entity on the client side (calling breeze's createEntity function) or make changes to the firstName or LastName properties without doing a save, then the custom fullName property is never updated until I perform another db pull. With breeze change tracking shouldn't the fullName property update any time either of the name properties changes?
During debug, I noticed that when I use a getter in code: (i.e. var fullName = entity.fullName) -- as I step through the code the ctor is hits the "backingStore" value of my entity which is either default value (using the createEntity) or the last db value, but never the current value of the entity.
What am I missing? Thanks
Here is an example I used for setting up the property:
function registerSpmoleSurvey(metadataStore) {
metadataStore.registerEntityTypeCtor('SpmoleSurvey', spmoleSurvey);
function spmoleSurvey() { }
Object.defineProperty(spmoleSurvey.prototype, 'fullName', {
get: function () {
var ln = this.lastName;
var fn = this.firstName;
return ln ? fn + ' ' + ln : fn;
}
});
}
Look at this page for examples of adding computeds to your Breeze entities -
http://www.breezejs.com/documentation/extending-entities
Pass in an anonymous function as the third parameter that extends the entity.
HI figure out what I was doing wrong...seems that I was a victim of camelCasing and I capitalize the property name inappropriately. Wrong as advertise now :}

Class extending Ext.data.Model - methods .get and .set behaves differently compare to getter/setter

When i am creating the model by extending "Ext.data.Model" class, getter/setter methods are behaving differently than default .get and .set methods available from data.Model
It seems like one can either use getter/setter methods or .get/.set methods, because they seem to be maintaining separate set of fields.
Why is it so? Parden me if question looks silly, i am learning Ext JS an trying to understand how it works. I am using library version ExtJS4.2.1
Class
Ext.define("Ext.model.Invoice", {
extend : "Ext.data.Model",
fields : [{name : 'id'}, {name : 'taxId'}, {name : 'name'}],
config : {
name : 'Tejas',
taxId : '23746'
},
constructor : function(config) {
this.callParent(arguments);
this.initConfig(config);
}
});
HTML
Ext.onReady(function() {
var invoice = Ext.create("Ext.model.Invoice");
console.log("Before, invoice.get('name'):", invoice.get('name'));
console.log("Before, invoice.getName():", invoice.getName());
//Modifying name
invoice.setName("Mr. Smith");
invoice.set("name","Mr. Tony");
console.log("Updating names using setName and set('name')");
console.log("After, invoice.get('name'):", invoice.get('name'));
console.log("After, invoice.getName():", invoice.getName());
});
OUTPUT
Before, invoice.get('name'):
Before, invoice.getName(): Tejas
Updating names using setName and set('name')
After, invoice.get('name'): Mr. Tony
After, invoice.getName(): Mr. Smith
With config configuration property you are defining list of configuration options with their default values, but not default model data.
When instance of object is creating, for each property defined in config is automatically created setter and getter method, and object property with same name as config property.
Ext.data.Model stores model data in its private data property. For example you can try to dump model data for name field by:
console.log(invoice.data.name);
So by setter and getter you access object property, but by model.get() and model.set() you access model's data stored in model's private data property.

Sencha Touch Model: Difference between fields and variables

I have two models but not able to find difference between both
Ext.define('Contact', {
extend: 'Ext.data.Model',
config: {
firstName: null,
lastName: null
}
});
Ext.define('Contact', {
extend: 'Ext.data.Model',
config: {
fields: ['firstName', 'lastName']
}
});
for first one sencha generated set and get method but for second why sencha does not generate set and get methods
The first one defines two properties named firstName and lastName while the second and correct one defines the fields firstName and lastName
When using the first one the model will never be able to provide data operations via get()/set()
Note that a reader requires a field definition to "read" data into the model. You will find the data the reader used to fill the internal data property (used by the getter & setter) in the rawData property.

Resources