To connect real model data to a view model, mess around with it, validate it, and then save it back to the server is crucial for any modern application. I will help you understand how some of the key features of the Sencha Ext JS classes work together to handle many of the real world challenges. We will take a closer look at the classes and configs that help us consume and handle the more advanced data structures. I will explain how they are connected and how you can tweak them to your needs. The focus will be on view models, data models, data sessions, proxies, stores, and associations, and how they all come together in a real world application.
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
Handle real world data with confidence
1. Handle real world data
with confidence.
Fredric Berling
@FredricBerling
fredric@entence.se
2. • Reading & saving simple data
When we live in a perfect world. Domain model is perfect. We decide everything.
• Real world data and demands
Still simple data but add real world scenarios and demands. Learn how to configure to cope with it.
• Associations
How to handle associate data.
• Multi model scenarios
Learn about Ext.data.Session to track changes in multiple models and associations.
• Errors
2
Agenda
9. resources/getCompanyById.json
• Id field is lowercase
• Date is formatted in a understandable
format
{
id : 100,
companyName : 'Ikea',
address : 'Storgatan 1',
city : 'Stockholm',
country : 'Sweden',
updatedDate : '1973-11-17 05:30'
}
10. The data model
• Fields config are not really needed for
simple data types.
.. except date
• Simple proxy config
Ext.define('CRM.model.CompanyProfile', {
extend: 'Ext.data.Model',
fields:[
{
name : 'updatedDate',
type : 'date’
}
],
proxy: {
url : 'resources/getCompanyById.json'
}
});
11. Data loading
• linkTo function
Creates a link to a record by loading data
from configured proxy and setting it on the
view model with a simple name.
"companyProfile" will be our reference in
all bindings referring to company data
record.
Ext.define('CRM.view.ViewportController', {
extend : 'Ext.app.ViewController',
alias : 'controller.viewportcontroller',
init: function() {
this.getViewModel().linkTo('companyProfile',{
type : 'CRM.model.CompanyProfile',
id : 100
});
}
});
13. Ext.define('CRM.view.Viewport', {
extend : 'Ext.Container',
viewModel : 'viewportmodel',
controller : 'viewportcontroller',
padding : 20,
items: [
'''
]
});
• viewModel
- connects this view to the viewmodel with
alias "viewportmodel"
• controller
- connects this view to the viewController
with alias "viewportcontroller"
View configs
21. Real world data from Customer
• Observations
- id field is not "id"
{
_id : "57bb5d639baeb676ced2b0de",
companyName : 'Ikea',
address : {
street : 'Storgatan 1',
city : 'Stockholm',
country : 'Sweden'
},
updatedDate : '2016-08-23T21:26:08.358Z'
}
22. Real world data from Customer
• Observations
- id field is not "id"
- Some data resides is in objects
{
_id : "57bb5d639baeb676ced2b0de",
companyName : 'Ikea',
address : {
street : 'Storgatan 1',
city : 'Stockholm',
country : 'Sweden'
},
updatedDate : '2016-08-23T21:26:08.358Z'
}
23. Real world data from Customer
• Observations
- id field is not "id"
- Some data resides is in objects
- Date format
{
_id : "57bb5d639baeb676ced2b0de",
companyName : 'Ikea',
address : {
street : 'Storgatan 1',
city : 'Stockholm',
country : 'Sweden'
},
updatedDate : '2016-08-23T21:26:08.358Z'
}
24. Scenarios/Customer demands
• Id must be "_id". We are using Mongo DB.
• Updated date should ignore timezones
• The exact same JSON structure must be sent on save.
• Always send all data on save.
• Save must be on separate url.
24
25. Specify your own id field
idProperty
Consider doing this in abstract base
class or even override Ext.data.Model.
Ext.define('CRM.model.CompanyProfile', {
extend: 'Ext.data.Model',
idProperty:'_id',
fields:[
{
name: 'updatedDate',
type : 'date’
}
],
proxy: {
url : 'resources/getCompanyById.json'
}
});
26. Read data from nested objects
mapping
Specify a dot notated path in the fields
array of data model.
fields:[
{
name : 'street',
mapping : 'address.street',
type : 'string'
},
{
name : 'city',
mapping : 'address.city',
type : 'string'
},
{
name : 'country',
mapping : 'address.country',
type : 'string'
},
...
],
27. Send all fields on save
writeAllFields
Proxy writer configs to ensure all fields
will be sent.
proxy: {
type : 'ajax',
url : '/companies',
reader:{
type:'json'
},
writer:{
type : 'json',
writeAllFields : true
}
}
28. Keep object nesting on save
{
_id : "57bb5d639baeb676ced2b0de",
companyName : 'Ikea',
address : {
street : 'Storgatan 1',
city : 'Stockholm',
country: 'Sweden'
},
updatedDate : '2016-08-23T21:26:08.358Z'
}
29. Keep object nesting on save
expandData
Makes sure the mapped field data sent
back is in a nested format.
nameProperty
Property used to read the key for each
value that will be sent to the server.
proxy: {
type : 'ajax',
url : '/companies',
reader:{
type:'json'
},
writer:{
type : 'json',
writeAllFields : true,
nameProperty : 'mapping',
expandData : true
}
}
31. Date should not add timezone
By default you will get a localized date
in the current browsers timezone
{
_id : "57bb5d639baeb676ced2b0de",
companyName : 'Ikea',
address : {
street : 'Storgatan 1',
city : 'Stockholm',
country: 'Sweden'
},
updatedDate : '2016-08-23T21:26:08.358Z'
}
32. dateFormat
Adding the exact date format will make
sure date is not altered.
fields:[
'''
{
name:'updatedDate',
type:'date',
dateFormat:'Y-m-dTH:i:s.uZ'
}
],
33. Read/Update on separate Url´s
Instead of using the proxy standard url
config, we change to the more versatile
api config
proxy: {
type : 'ajax',
url : '/companies'
reader:{
type:'json'
},
writer:{
type : 'json',
nameProperty : 'mapping',
writeAllFields : true,
expandData : true
}
}
34. Read/Update on separate Url´s
Instead of using the proxy standard url
config, we change to the more versatile
api config
proxy: {
type : 'ajax',
api : {
read: '/companies',
update: '/updateCompanies'
},
reader:{
type:'json'
},
writer:{
type : 'json',
nameProperty : 'mapping',
writeAllFields : true,
expandData : true
}
}
40. Useful?
• Automatically created stores.
Company model function licenses() will return
accociated licenses.
var viewModel = this.getViewModel(),
companyProfile = viewModel.get('companyProfile'),
store = companyProfile.licenses();
41. Useful?
• Automatically created stores.
Company model function licenses() will return
accociated licenses.
• License function getCompany() will
return Company model.
varviewModel = this.getViewModel(),
companyProfile = viewModel.get('licenses'),
store = companyProfile.licenses(),
firstLicense = store.first(),
console.log(firstCompany.getCompany());
// Returns companyProfile
42. Useful?
• Automatically created stores.
Company model function licenses() will return
accociated licenses.
• License function getCompany() will
return Company model.
• Nice bindings.
companyProfile.licenses references
the store
{
xtype : 'grid',
plugins : 'cellediting',
columnLines:true,
bind : {
store:'{companyProfile.licenses}'
},
columns:[
{text: 'Licence', dataIndex:'product'},
{text: 'Amount', dataIndex:'amount'}
]
}
43. • Inline associations
Licence data is array in company JSON and should be saved in same call as company profile.
• Proxy/Remote associations
Licence data is fetched from its own web service and CRUD operations are handled here
43
Possible business scenarios
48. Saving associated data in a big JSON is generally a bad idea
The associated data is probably saved in a table in a database
Empty arrays will have to force delete
48
49. 3 Possible solutions
• Re-load all data after successful
save
- extra call
- safe
onSave:function(){
this.getViewModel().get('companyProfile').save({
callback:function(){
this.getViewModel().linkTo('companyProfile',{
type : 'CRM.model.CompanyProfile',
id : '57bf32fe9baeb676ced2b0e1'
});
},
scope:this
});
}
50. 3 Possible solutions
• Re-load all data after successful
save
- extra call
- safe
• Forced commitChanges()
- no extra call
- response data is ignored.
onSave:function(){
var store = this.getViewModel().get('companyProfile.licenses')
this.getViewModel().get('companyProfile').save({
callback:function(){
store.commitChanges(); },
},
scope:this
});
}
51. 3 Possible solutions
• Re-load all data after successful
save
- extra call
- safe
• Forced commitChanges()
- no extra call
- response data is ignored.
• Code around it
- complex
53. Associated stores will default to a ajax
proxy.
Avoid remote reads by changing this to
a memory proxy.
Ext.define('CRM.model.License', {
extend: 'Ext.data.Model',
fields:[
{name:'product', type:'string'},
{name:'amount', type:'float'},
{
name: 'companyId',
reference: {
parent : 'CRM.model.CompanyProfile',
inverse : {
role:'licenses',
storeConfig:{
proxy:{
type:'memory'
}
}
}
}
. . .
Store config
54. Proxy/Remote associations
Ext.define('CRM.view.ViewportController', {
extend : 'Ext.app.ViewController',
alias : 'controller.viewportcontroller',
onSave:function(){
var vm = this.getViewModel();
vm.get('companyProfile').save();
vm.get('companyProfile.licenses').sync();
},
...
• Call save() on model.
• Call sync() on stores.
55. Proxy/Remote associations
Ext.define('CRM.model.License', {
extend: 'Ext.data.Model',
idProperty:'_id',
fields:[..],
proxy: {
type : 'ajax',
api : {
read : '/licenses',
create : '/addLicenses',
update : '/updateLicenses',
destroy : '/deleteLicenses'
},
writer:{
type : 'json',
writeAllFields : true
}
}
});
• Specify all api´s on associated data
model
56. Proxy/Remote associations
{
xtype: 'grid',
plugins: 'cellediting',
columnLines: true,
tbar: [{text: 'Add', handler: 'addLicense'}],
bind: {
store: '{companyProfile.licenses}'
}
},
• Associated data will only load if store
is used in a binding
60. Ext.data.Session
The primary job of a Session is to manage a collection of records of many different
types and their associations. This often starts by loading records when requested
and culminates when it is time to save to the
60
62. View with multiple models
62
Company
Licenses
Pool cars
Feedback
Data model + association
63. View with multiple models
63
Company
Licenses
Pool cars
Feedback
Data model + association
Store
64. View with multiple models
64
Company
Licenses
Pool cars
Feedback
Data model + association
Store
Data model
65. • getChanges()
- Returns an object describing all of the modified fields, created or dropped records
maintained by this session.
- Used to track if ANY data is dirty
• getSaveBatch()
- Returns an Ext.data.Batch containing the Ext.data.operation.Operation instances that
are needed to save all of the changes in this session
65
Session features
66. One point save onSave:function(){
if(this.getSession().getChanges()){
this.getSession().getSaveBatch().start();
}
},
68. Enable session var store = this.getViewModel().getStore('cars');
store.setSession(this.getSession());
• Enable session in view
• Add stores to session
73. Extra proxy parameters proxy: {
type : 'ajax',
extraParams:{
appName:'CRM'
},
api : {
read: '/companies',
update: '/updateCompanies'
},
reader:{
type:'json'
},
writer:{
type : 'json',
}
}
• Set in proxy using extraParams
config
74. Extra proxy parameters var proxy = Ext.ClassManager.get(<class>).getProxy();
proxy.setExtraParam('appName', 'CRM');
• Set in proxy using extraParams
config
• Set from controller using the
setExtraParam function
78. Handle error response {
"IsSuccess": false,
"ErrorMessage": "Missing company"
}Error returned from the server in a json
response when logic fails or data is
missing.
79. Configure the proxy reader proxy: {
type : 'ajax',
api : {
read: '/companies',
update: '/updateCompanies'
},
reader:{
type:'json',
successProperty:'IsSuccess',
messageProperty:'ErrorMessage'
}
. . .
• successProperty
The property indicating that the operation
was successful or not.
• messageProperty
The property where the error message is
returned.
80. Listen for proxy exception Ext.mixin.Observable.observe(Ext.data.proxy.Server);
Ext.data.proxy.Server.on('exception',
function(proxy, resp, operation){
Ext.Msg.alert('Error', operation.getError());
}
);
• exception
- fires when "success" is false
- fires on HTTP exceptions
• operation.getError()
- returns messageProperty content
82. Please Take the Survey in the Mobile App
• Navigate to this session in the mobile app
• Click on “Evaluate Session”
• Respondents will be entered into a drawing to win one of five $50 Amazon gift cards
Notes de l'éditeur
Its easy to write applications that are simple. You decide everything
Refesh our memories
The world smallest CRM system
Beutiful application
Binding has given us so mych power
Create the actual view package
Lets add some real world
So common. Object/no sql
Dates are from hell. I hate dates. with all my heart
Most data Associate .
Associations enable you to express relationships between different Ext.data.Model
Our product owner demands a grid with licenses
In most databases you reference using a "foreign key"