Sync Through Dependency Injection

We need a mechanism to inject a syncing behavior that can be shared between model and collection. Let’s create a HipsterSync class that holds our data syncing behavior. Ultimately, the various libraries that rely on HipsterSync will invoke a static method HipsterSync.send() to dispatch the CRUD operation. Before looking at that, however, we need a default behavior that can perform Ajax requests.

varying_the_behavior/public/scripts/HipsterSync.dart
 
library​ hipster_sync;
 
import​ ​'dart:html'​;
 
import​ ​'dart:convert'​;
 
class​ HipsterSync {
 
static​ _defaultSync(method, model, {options}) {
 
var​ req = ​new​ HttpRequest();
 
_attachCallbacks(req, options);
 
 
req.open(method, model.url);
 
 
// POST and PUT HTTP request bodies if necessary
 
if​ (method == ​'post'​ || method == ​'put'​) {
 
req.setRequestHeader(​'Content-type'​, ​'application/json'​);
 
req.send(JSON.encode(model.attributes));
 
}
 
else​ {
 
req.send();
 
}
 
}
 
}

That all looks fairly normal now that we have taken the initial Ajax-based app and converted it to an MVC framework. We create an HttpRequest object, open it, and then send the request. New here is the need to support passing request bodies with POST and PUT requests, but a simple conditional suffices to cover this behavior.

All of the classes that will use this sync need to be able to dispatch events upon successful load of the HttpRequest object. The _attachCallbacks static method takes care of this for us.

 
class​ HipsterSync {
 
static​ _defaultSync(method, model, {options}) {
 
var​ req = ​new​ HttpRequest();
 
_attachCallbacks(req, options);
 
// ...
 
}
 
static​ _attachCallbacks(request, options) {
 
if​ (options == null) ​return​;
 
if​ (options.containsKey(​'onLoad'​)) {
 
request.onLoad.listen((event) {
 
var​ req = event.target,
 
json = JSON.decode(req.responseText);
 
options[​'onLoad'​](json);
 
});
 
}
 
}
 
}

This _attachCallbacks method lets us rewrite HipsterModel#save() with an onLoad callback passed via options.

varying_the_behavior/public/scripts/HipsterModel.dart
 
library​ hipster_model;
 
import​ ​'HipsterSync.dart'​;
 
class​ HipsterModel {
 
// ...
 
save({callback}) {
 
HipsterSync.send(​'post'​, ​this​, options: {
 
'onLoad': (attrs) {
 
attributes = attrs;
 
if​ (callback != null) callback(​this​);
 
}
 
});
 
}
 
}

With that, we have delegated data syncing to HipsterSync—the model no longer knows anything about HTTP. The first two arguments to HipsterSync.send() instruct the sync that it should POST when syncing and that the current model should be used to obtain the serialized data to be sent to the back-end store.

At this point, we’re finally ready to look at HipsterSync.send(). As we’d expect, if no alternative sync strategy has been supplied, it invokes a _defaultSync.

 
class​ HipsterSync {
 
// ...
 
static​ send(method, model, {options}) {
 
if​ (_injected_sync == null) {
 
return​ _defaultSync(method, model, options:options);
 
}
 
else​ {
 
return​ _injected_sync(method, model, options:options);
 
}
 
}
 
}

The interesting behavior is that _injected_sync beastie. It may look like another static method, but it is, in fact, a class variable. User libraries can inject behavior into this library via a sync= setter, which expects a function.

varying_the_behavior/old/hipster_sync_injected.dart
 
class​ HipsterSync {
 
static​ ​var​ _injected_sync;
 
static​ ​set​ sync(fn) {
 
_injected_sync = fn;
 
}
 
static​ send(method, model, [options]) {
 
if​ (_injected_sync == null) {
 
return​ _defaultSync(method, model, options:options);
 
}
 
else​ {
 
return​ _injected_sync(method, model, options:options);
 
}
 
}
 
// ...
 
}

The injected function will need to accept the same arguments that _defaultSync does.

Recipe 18Warning: It would make more sense to have a sync setter and  a sync class method. Unfortunately, Dart will throw an “already defined” internal error if a method is declared with the same name as a setter. Hence, we need to declare a sync= setter and a send static method.

With all of this in place, let’s switch our HipsterSync strategy to localStorage. This can be done back in the main entry point for the application. For now, we restrict ourselves to supporting only the GET operations.

varying_the_behavior/old/main_with_local_sync.dart
 
import​ ​'HipsterSync.dart'​;
 
main() {
 
HipsterSync.sync = localSync;
 
// Setup collections and views ...
 
}
 
localSync(method, model, [options]) {
 
if​ (method == ​'get'​) {
 
var​ json = window.localStorage[model.url],
 
data = (json == null) ? {} : JSON.decode(json);
 
if​ (options ​is​ Map && options.containsKey(​'onLoad'​)) {
 
options[​'onLoad'​](data.getValues());
 
}
 
}
 
}

That is pretty nifty. With a single line, it is possible to inject completely different data syncing behavior for the entire framework.

It is worth pointing out that, because of how Dart manages libraries, setting HipsterSync.sync in one location will change it everywhere. In this case, we set it in our main.dart.

 
// main.dart
 
import​ ​'HipsterSync.dart'​;
 
 
main() {
 
HipsterSync.sync = localSync;
 
//
 
}

This will affect the _injected_sync HipsterSync class variable that is seen by HipsterModel.

 
// HipsterModel.dart
 
library​ hipster_model;
 
 
import​ ​'HipsterSync.dart'​;
 
 
class​ HipsterModel {
 
// I see HipsterSync._injected_sync from main.dart
 
}

And the same goes, of course, for HipsterCollection.

 
// HipsterCollection.dart
 
library​ hipster_collection;
 
import​ ​'HipsterSync.dart'​;
 
class​ HipsterCollection {
 
// I see HipsterSync._injected_sync from main.dart
 
}

Each of these files, main.dart, HipsterModel.dart, HipsterCollection.dart, and HipsterSync.dart, are separate files. And yet Dart ensures that the HipsterSync class defined in one is the same that is seen by all. The only equivalent in JavaScript is what Backbone.js does to define its Backbone.sync. Backbone declares a global variable (for example, Backbone) and instructs developers that it needs to be included via <script> tags before all other code. Using something like require.js will get you close to Dart’s behavior, but it is very nice to have this working at the outset of the language rather than attempting to tack it on 18 years after the fact.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.237.123