view models and binding
TRANSCRIPT
Sencha Inc. ©2015
ViewModelsand Data Binding
Evan TrimboliSr. Software Engineer, Sencha
Sencha Inc. ©2015
Sencha Inc. ©2015
Why ViewModels
•Automate connection between data and view• Keep view in sync with data• Update data with user input
•Declarative instead of imperative
Sencha Inc. ©2015
MVVM
ViewViewMode
l Model
Business LogicandData
Presentation and view logic
Binding
Sencha Inc. ©2015
Binding
Sencha Inc. ©2015
Binding Essentials
•The “bind descriptor” is a value that describes the desired data.•The “bound value” is the desired data delivered by the ViewModel once it becomes available.•The bound value will be delivered again whenever it changes.
Sencha Inc. ©2015
Direct Binding
var vm = new Ext.app.ViewModel();
var b = vm.bind('{foo}', function (v) { console.log(v);});
vm.set('foo', 42); // b.setValue(‘42’);
b.destroy();
1
2
3
Bind descripto
r
Bound value
> 42
Binding instance
4
Marks dependencies as dirty
Sencha Inc. ©2015
Negated Binding
vm.bind('{!x}', function (v) { console.log(v);});
vm.set('x', 'world');
Bind descripto
r
> false
Sencha Inc. ©2015
Binding Templates
vm.bind('Hello {x:capitalize}', function (v) { console.log(v);});
vm.set('x', 'world');
Bind descripto
r
> Hello World
Sencha Inc. ©2015
Objects and Arrays
vm.bind({ x: 'x={x}', y: ['{y}'] }, function (v) { console.log(v);});
vm.set('x', 42);// wait for it...
vm.set('y', 13);
Bind descripto
r
> { x: 'x=42', y: [13] }
Sencha Inc. ©2015
Bind Optionsvm.set(‘foo’, {bar: 1});
vm.bind('{foo}', this.onFoo, this, { deep: true, single: true, twoWay: false});
// Without deep, foo won’t changevm.set(‘foo.bar’, 100);
Deliver changes to child object properties
Deliver just the initial
value (once available)
Don’t write-back changes, mark binding
readOnly
Sencha Inc. ©2015
Bind Concepts in Depth
•Bind descriptor – the data you want.•Bind token – the replaceable parts (“{foo}”) of a bind descriptor (single name or dot path).•Dot path – the traversal of objects and properties starting at the root of the ViewModel.•Bound value – the value delivered by the ViewModel as the result of a bind request.
Sencha Inc. ©2015
Bind Descriptor
•String with one token (“{foo}”) or direct bind.•String template (“Hello {foo}”).•Object whose values are bind descriptors. {foo: “{bar}”}•Array whose elements are bind descriptors.[“{foo}”, “{bar}”]
Sencha Inc. ©2015
Bind Tokens
•Simple identifiers (“{foo}”) to select top-level values in the ViewModel.•A “dot-path” to the desired value (such as “{ticket.reporter.name}”).•Formatters such as “Hi {foo:capitalize}” or “Amount {num:round(2)}”.•Negated token (“{!foo}”).
Sencha Inc. ©2015
Dot Path
•Normal object property.• Field from a data record (“{user.name}” not “{user.data.name}”).•The associated record of a “to-one” association (“{order.user.name}”).•The associated store of a “to many” association (“{user.orders}”).
Sencha Inc. ©2015
Bound Value
•The exact value from the VM if a “direct bind” (just one token).•The boolean negation of the value if a negated direct bind (such as “{!foo}”).•A string with all tokens replaced and formatted (such as “{num:round(2)}”).•An object or array with all property values or elements replaced by their bound value.•A store or record instance.
Sencha Inc. ©2015
Componentsand
ViewModels
Sencha Inc. ©2015
Config Binding
Ext.define('App.view.Main', { ...
items: [{ xtype: 'button', bind: { text: 'Sign out, {user.name}' } }]});
bind config
Sencha Inc. ©2015
Config Binding Options
Ext.define('App.view.Main', { ... items: [{ xtype: 'button', bind: { text: { bindTo: '{foo}', single: true } } }]});
Bind descripto
r
Bind options
Sencha Inc. ©2015
defaultBindProperty
[{ xtype: ‘button’, bind: ‘{theText}’ // text}, { xtype: ‘textfield’, bind: ‘{theValue}’ // value}, { xtype: ‘grid’, bind: ‘{theStore}’ // store}]
•Components have a default bind property, which maps to the most common usage for that component
Sencha Inc. ©2015
Two Way Binding
var p = new Ext.panel.Panel({ bind: { title: ‘{theTitle}’ }, viewModel: { data: {theTitle: ‘Foo’} }}); // Not twoWayBindable, won’t update VMp.setTitle(‘Bar’);
•Configs that cannot be changed by the user are typically not two way bindable by default.
Sencha Inc. ©2015
What ViewModel does that bind use?
Sencha Inc. ©2015
Component Tree
viewport panel toolbar button grid panel panel toolbar button
viewport panel toolbar button grid panel panel toolbar button
ViewModel
ViewModel
Sencha Inc. ©2015
ViewModel Inheritance
viewport
panelpanel
toolbar
button { bind: '{foo}'}
ViewModel
...
Sencha Inc. ©2015
ViewModel
Sencha Inc. ©2015
ViewModel Hierarchy
ViewModel
ViewModel
{ }
{ }
data
prototype
data
parent
Sencha Inc. ©2015
Populating ViewModels
•Direct set() calls•Configs• data• stores• formulas• links
Sencha Inc. ©2015
Data
Sencha Inc. ©2015
ViewModel – Data
Ext.define('App.view.main.Main’,{ extend: 'Ext.panel.Panel', xtype: 'main',
viewModel: { type: 'main', data: { foo: 42 } }});
Ext.define('App.view.main.MainModel', { extend: 'Ext.app.ViewModel',
alias: 'viewmodel.main',
data: { foo: 427, bar: ‘baz’ }});
Sencha Inc. ©2015
ViewModel – Data (2)
Ext.create({ xtype: 'main',
viewModel: { data: { foo: 42 } }});
ViewModel type comes
from the class
Data is merged, “bar” still
exists
Sencha Inc. ©2015
Stores
Sencha Inc. ©2015
ViewModel – Stores
Ext.define('App.view.main.MainModel’, { extend: 'Ext.app.ViewModel', alias: 'viewmodel.main',
stores: { bar: { model: 'User’, filters: [{ property: 'name', value: '{form.search}' }] }...
Object is a bind
descriptor
Sencha Inc. ©2015
ViewModel – Store Types
Ext.define('App.view.main.MainModel', { extend: 'Ext.app.ViewModel', alias: 'viewmodel.main',
stores: { bar: { type: 'personnel', ... } }});
Ext.define('App.store.Personnel', { extend: 'Ext.data.Store',
alias: 'store.personnel',
proxy: ...});
Sencha Inc. ©2015
Formulas
Sencha Inc. ©2015
ViewModel – Formulas
Ext.define('App.view.main.MainModel', { ...
formulas: { twice: function (get) { return 2 * get('x'); }, quad: function (get) { return 2 * get('twice'); }
Use the getter
Formulas can use each
other
Sencha Inc. ©2015
ViewModel – Two-Way Formulas
Ext.define('App.view.main.MainModel', { ...
formulas: { twice: { get: function (get) { return 2 * get('x'); }, set: function (v) { this.set('x', v / 2); } }
Called when
dependency
changes
this == VM
Sencha Inc. ©2015
Links
Sencha Inc. ©2015
ViewModel – Links
Ext.define('App.view.main.MainModel', { ...
links: { ticket: { type: 'Ticket', id: 427 }
Load record or request
from Session
Sencha Inc. ©2015
ViewModel – Links
Ext.create('App.view.form.Form', { ... viewModel: { links: { ticket: theTicketInstance, user: { type: ‘User’, create: { name: ‘Foo Bar’ } } } }}
Already loaded but may copy
Create a new instance.
May also be true to
create empty record
Sencha Inc. ©2015
ViewModel – Links (Ext JS 6)
Ext.define('App.view.main.MainModel', { ...
links: { foo: { a: '{some.object.foo}', b: '{other.thing.bar}' } }}
Object is a bind
descriptor
Sencha Inc. ©2015
The Scheduler
Sencha Inc. ©2015
Shared Scheduler
ViewModel
ViewModel
parent
Scheduler
Sencha Inc. ©2015
Scheduler
Binding[0]
items
Binding[X]
…Data dependencies
Execution
Scheduler
Sencha Inc. ©2015
Running The Scheduler
vm.bind(’Hello {type} {thing:capitalize}', function (v) { console.log(v);});
vm.set(‘thing', 'world');vm.set(‘type’, ‘cool’);
vm.notify(); Update all dirty bound values
Questions & Answers
Thank YOU!
Sencha Inc. ©2015