DataModel Creation and Configuration¶
A dataModel
can be thought of as an enhanced viewModel. It has all the same capabilities but additionally has features which aid in the management of RESTful data.
With a dataModel
, in addition to its viewModel capabilities you can:
- Create a new record on an endpoint
- Retreive data from an endpoint
- Update data on an endpoint
- Delete a record on an endpoint
A typical use case for a dataModel
is when you want to store/retrieve/delete data on a server, and bind that to your UI with a form.
An example of this might be creating an HTML form which can retrieve and create tickets for a help system. You would bind a dataModel
to the form and later on might retrieve and display that form back to the user to edit/modify the ticket. A dataModel
would facilitate the mapping of and retrieval/storage of that data on a RESTful endpoint.
Note
This page outlines the configuration and creation of a dataModel instance itself, for info on CRUD operations (including create requests) see issuing requests.
Creation¶
A dataModel
is a view model object that has been bootstrapped with fw.dataModel.boot()
by passing it the instance along with an optional configuration:
function MyDataModel () { var self = fw.dataModel.boot(this, { // see Configuration below }); self.someValue = fw.observable(); }
In the code above you see the instance bootstrapped as a dataModel
, the boot method will return a reference to the instance.
Configuration¶
When you call fw.dataModel.boot()
you pass it two parameters, the view model instance/object and an optional configuration object. The configuration for a dataModel
has the following options:
function MyDataModel () { // For convenience the boot method returns a reference to the instance. var self = fw.dataModel.boot(this, { namespace: /* see below */, afterRender: /* see below */, afterResolve: /* see below */, onDispose: /* see below */, sequence: /* see below */, idAttribute: /* see below */, url: /* see below */, parse: /* see below */, fetchOptions: /* see below */, requestLull: /* see below */ }); }
All of these options, are optional...you only have to provide the values required for your applications needs.
- namespace (string)
- afterRender (callback)
- afterResolve (callback)
- onDispose (callback)
- sequence (integer | callback)
- idAttribute (string)
- url (string | object)
- parse (callback)
- fetchOptions (object | callback)
- requestLull (integer | callback)
Callback Context
All callback functions execute with the context of the dataModel
instance.
Each of these options and their use is described below:
namespace (string)¶
Footwork provides an easy way to logically separate your modules using string-based identifiers. This configuration value is used to set the dataModel
namespace:
namespace: 'MyDataModel'
Once your dataModel
is bootstrapped its namespace is made available as $namespace
on the object instance:
function MyDataModel () { fw.dataModel.boot(this, { namespace: 'MyDataModel' }); this.$namespace.subscribe('someEvent', function () { // do something... }); }
Namespaces in Footwork are a mechanism used to help keep your application loosely coupled yet highly cohesive. Among other things, namespacing provides hooks for pub/sub communication, dataModel animation, as well as broadcastables / receivables. For more information see namespaces.
afterRender (callback)¶
This callback is triggered after binding and rendering the dataModel
with the DOM (but before afterResolve()
and also before any nested elements are bound/resolved).
It is passed one parameter, the parent DOM element the dataModel
is bound against.
afterRender: function (element) { console.info('My element is', element); }
Prospectively you might use this as a way to startup various 3rd party plugins such as those that work with jQuery.
afterResolve (callback)¶
This callback is triggered after binding the dataModel
with the DOM and all nested components/dataModel/etc have been fully resolved as well.
If you are animating a dataModel
into place then that will only occur once the instance has been resolved. Note that the resolution of your dataModel
only affects when it is animated into place, it does not affect when it is bound or rendered into the DOM (that occurs as soon as possible). If you are not animating your instances into place then you do not need to worry about when it is resolved. For information on how to use the animation features, see animating dataModels.
The afterResolve
callback is passed one parameter, a resolve function. Using the resolve() function you tell Footwork when your instance has been resolved. It can be called in three different ways to specify resolution, depending on your needs:
-
You can call it nothing to mark the current instance as resolved immediately:
afterResolve: function (resolve) { resolve(); // now marked as resolved }
-
You can pass it a promise which Footwork then waits to be fulfulled or rejected:
afterResolve: function (resolve) { var myRequest = fetch(/* ... */); resolve(myRequest); // marked resolved once myRequest resolves/rejects }
-
You can pass it an array of promises which Footwork then waits to be fulfulled or rejected:
afterResolve: function (resolve) { var requests = [ fetch(/* ... */), fetch(/* ... */) ]; resolve(requests); // marked resolved once all of the requests resolve/reject }
-
The resolve callback itself returns a promise which resolves once all promises you pass in have resolved:
afterResolve: function (resolve) { var requests = [ fetch(/* ... */), fetch(/* ... */) ]; resolve(requests).then(function () { console.info('all requests have completed'); }); }
Once your instance has been marked as resolved (along with any siblings that may exist), its parent is then informed (if there is one) by calling its resolve
callback. This continues up the context chain until there are no more left.
Note
Each instance, once marked resolved, will be animated into place if configured to do so (see: animating dataModels).
onDispose (callback)¶
This callback is triggered anytime the dataModel
has its dispose()
method called. It is passed one parameter, the parent DOM element the dataModel
is bound against.
onDispose: function (element) { // custom disposal logic }
Just as with afterRender
this might be used as an API hook for 3rd party plugins. You would do whatever custom disposal logic you might need here.
Note
Footwork will trigger dispose()
automatically if:
- The
dataModel
was bound with a component that was removed from the DOM - The element a
dataModel
was bound against was removed from the DOM
sequence (integer | callback)¶
The value provided will cause Footwork to sequence the animations on all viewModel
instantiated with the same namespace as this one. Essentially this means that if you instantiate a bunch of the same viewModels
then their animations will all be sequenced with a delay between them being the value provided here. This enables you to easily animate in elements in a pleasing way.
Integer Value¶
sequence: 100 // 100 msec between
Callback Function¶
This callback is triggered with each new instance:
sequence: function () { return 100; // 100 msec between }
For more information see animating dataModels and more specifically sequencing animations.
idAttribute (string)¶
This is the attribute used as the primary key (id) of the model. It has the default value of 'id':
idAttribute: 'id'
The idAttribute
is used primarily for two different purposes:
-
If you provide a simple string
url
configuration then theidAttribute
is used to fill-in the remaining url parameters when needed:- As the trailing value added to the
url
when thedataModel
is being read from the server - As the trailing value added to the
url
when thedataModel
is being updated on the server - As the trailing value added to the
url
when thedataModel
is being deleted on the server - See URL Configuration Object for more information.
- As the trailing value added to the
-
The value of your mapped
idAttribute
is used in theisNew
evaluator. This is a flag indicating whether or not yourdataModel
has been persisted to the server yet. It is based off of whether or not the mappedidAttribute
is falsey or not. A falsey value indicates thedataModel
is new (ie: has no id yet, and must be created).
For more information see: issuing requests.
url (string | object)¶
This option specifies the URL to use for RESTful operations.
URL String¶
By providing a string value, Footwork will fill in the details for you based on the current request action:
url: '/path/to/endpoint'
Depending on the desired (CRUD) operation, this will generate different types of requests. The default actions can be modified by specifying a configuration object explicitly.
Note
The default request actions are shown in the following URL Object section.
URL Object¶
You can optionally specify an explicit request for a designated action using a URL object configuration (default requests actions are shown):
url: { // if save() is called and the idAttribute resolves to a falsey value 'create': 'POST /path/to/endpoint', // if fetch() is called and the idAttribute resolves to a non-falsey value 'read': 'GET /path/to/endpoint/:id', // if save() is called and the idAttribute resolves to a non-falsey value 'update': 'PUT /path/to/endpoint/:id', // if destroy() is called and the idAttribute resolves to a non-falsey value 'delete': 'DELETE /path/to/endpoint/:id' }
Action-Specific Callbacks
Note that you can also provide individual callbacks for each request action. The callbacks are evaluated each time a request is issued, and are passed any fetchOptions
that were supplied:
url: { 'create': function (fetchOptions) { return 'POST /path/to/endpoint'; } }
This is how Footwork issues requests for the CRUD operations by default, and it is also how you would alter the requests by more explicitly defining them with an object configuration.
You also might notice the :id at the end of some of these request configurations. Footwork has the capability of interpolating any mapped value into a url, and this is done by specifying one or more :mappedVariable in the url string.
By default Footwork will append/interpolate the value of the idAttribute
onto the end of the url if it is needed. The default idAttribute
configuration value is id (see: idAttribute configuration)...so in this case when a request is made the value of the observable mapped to id would be resolved and appended to all read, update, and delete requests.
You can of course customize this however you like by instead using an explicit URL configuration object as shown above and specifying the action in a REQUEST_METHOD /url/to/resource
fashion as shown.
URL Variable Interpolation
Any mapped value can be interpolated into the url:
function MyDataModel (data) { var self = fw.dataModel.boot(this, { url: { 'read': 'GET /path/:folder/:id' } }); self.id = fw.observable(data.id).map('id', self); self.folder = fw.observable(data.folder).map('folder', self); } var model = new MyDataModel({ id: 1, folder: 'to/endpoint' }); model.fetch(); // GET /path/to/endpoint/1
In the example above the mapped folder
and id
values will be inserted into the URL when a read request is issued (triggered by a call to fetch). The url is re-evaluated with each request.
parse (callback)¶
This callback is run whenever a request returns a response from the server. It is passed the response object as well as the request type, and you should return a response object. Using this you can interject logic between your server and the application, manipulating the data before it is stored in the dataModel
.
As an example, you could use it to remove a wrapper returned durning a fetch:
parse: function (response, requestType) { if (requestType === 'read') { // return the inner object return response.data; } }
fetchOptions (object | callback)¶
Footwork generates requests using ES6 Fetch, using this parameter you can specify options passed to the ES6 Fetch call made when a request is issued. You are able to supply either an object literal or a callback which returns an object literal.
The options object you supply is used to override any options created for the request.
Object Literal¶
fetchOptions: { credentials: 'same-origin' }
Callback Function¶
This function is passed the action
, the dataModel
, and any options
that were passed during construction of the request:
fetchOptions: function (action, dataModel, options) { return { credentials: 'same-origin' }; }
Providing Fetch Options
-
Global Request Options
If you need to provide request options on a global basis then you can specify them on
fw.options.fetchOptions
:fw.options.fetchOptions = { credentials: 'same-origin' };
For more information see: Overriding Request Options
-
Action Specific Request Options
You can provide request options on a per-request basis when using one of the request methods such as
fetch
:dataModel.fetch({ credentials: 'same-origin' });
For more information see: Issuing Requests
requestLull (integer | callback)¶
A configurable lull-time for the CRUD (isCreating
/isReading
/isUpdating
/isDeleting
) observables on the dataModel.
If a non-zero/falsey value is provided then it will be used to specify a minimum duration the CRUD observables are flipped when a request occurs. A common use for the CRUD observables is to bind them to your UI, using them to provide feedback to the user if a request is occuring.
This value makes it so that (for example) when a user .save()'s a dataModel
it will not flip-flop the isUpdating
observable too quickly. Preventing the rapid flip-flop saves the user from having to experience harsh/annoying UI thrashing.
This value defaults to 0 (no delay/lull time).
Integer Value¶
requestlull: 100 // minimum lull-time of 100 msec, affects all request types
Callback Function¶
This callback is passed the operation type.
requestLull: function (requestType) { if(requestType === 'read') { return 100; } else { return 200; } }
The above example will slow down read
(fetch/GET
) operations only 100msec while all others (create, update, destroy) have a longer duration of 200msec. Note that this does not prevent or slow down the request, nor does it keep the data from being written to the dataModel
once the response is received. It only affects when the CRUD flags are flipped.
For more details on the isCreating
/isReading
/isUpdating
/isDeleting
(CRUD) state observable flags see Managing Requests and State.