Four Backbone.js Model questions - javascript

I'm using a Backbone.js to keep track of the state in a visualization application.
The model has attributes such as:
indicatorX : "income"
indicatorY : "emissions"
indicatorXScale : "lin"
indicatorYScale : "log"
year : 1980
layout : {leftPanel : {[...]}, rightPanel : {[...]}}
1. What is a good way of handling "dependent attributes" in a Backbone.js model?
For example, when changing the indicatorX attribute, I'd also like the model to update the indicatorXScale attribute.
2. How can I handle "lingering" model attributes? Example:
The model contains this:
indicatorX : "income"
indicatorXScale : "log"
If only indicatorX is set on the model, the scale should be set to the default:
model.set( {indicatorX : "emissions"} )
if("indicatorX" in changedAttrs){
indicatorXScale = dataSource[indicatorX].defaultScale
}
What if however the user wants to override the default scale which is "lin" in the case of the "emissions" indicator?
model.set( {indicatorX : "emissions", indicatorXScale : log} )
As the model attribute indicatorXScale already is set to "log", the changed attribute is not recorded. How can I then make sure that the defaultScale is not loaded in this case - but instead the one passed to the model?
3. Is it a good idea to let the model use an extra attribute "action" to describe changes in the model?
In this way controllers can listen for one attribute instead of specifying handlers for combinations of attributes. These are the alternatives:
Alt 1. Controller has handlers for specific attributes:
this.model.bind("change:year", this.render);
this.model.bind("change:layout", this.updateLayout);
Alt 2. Controller has handler for model change and render() figures out what to do:
this.model.bind("change", this.render);
render() {
var changedAttributes = this.model.changedAttributes
if (a,b && c in changedAttributes) x()
if (a,d in changedAttributes) y()
}
Alt 3. Let model describe what a combination of attribute changes signify:
this.model.bind("change:action", this.render);
render() {
var changedAttributes = this.model.changedAttributes
var action = this.model.get("action")
if (action == gui_changeIndicator) x()
if (action == gui_opacity) y()
}
4. Are there any pitfalls to watch out for when using objects as attributes in a Backbone.js model?
Is it for example expensive to perform isEqual on the layout state that I try to keep in my model? Also, when setting the model, objects are passed by reference, so it better be a new object for the comparison to work?

1. What is a good way of handling "dependent attributes" in a Backbone.js model? For example, when changing the indicatorX attribute, I'd also like the model to update the indicatorXScale attribute.
IMHO, extend the model and bind into the change events. For example:
MyModel = Backbone.Model.extend({
initialize: function() {
this.bind('change:width', this.updateArea);
this.bind('change:height', this.updateArea);
this.updateArea();
},
updateArea: function () {
this.area = this.get('width') * this.get('height');
}
});
var model = new MyModel({height: 10, width: 10});
console.log(model.area); //100
model.set({width: 15});
console.log(model.area); //150
This is pretty basic, but change events are called per key and as a whole 'change'.. so you can bind into certain changes and update as necessary. If it's a large model with lots of keys that are updated intermittently this is definitely the way to go. If it's just those two keys.. well.. you could probably just bind to the regular ol' change event once.
2. How can I handle "lingering" model attributes?
Override the set method and add in some of your own code. Example:
MyModel = Backbone.Model.extend({
constructor: function (obj) {
this.touched = {}; //Set before the prototype constructor for anything being set
Backbone.Model.prototype.constructor.call(this, obj);
//set after for only things that the program has updated.
return this;
},
set: function(attributes, options) {
if(attributes.keyIWantToListenFor !== undefined && !this.touched.keyIWantToListenFor) {
console.log("keyIWantToListenFor was set, let's fire off the right methods");
this.touched.keyIWantToListenFor = true;
}
Backbone.Model.prototype.set.call(this, attributes, options);
return this;
}
});
var model = new MyModel({height: 10, width: 10});
model.set({keyIWantToListenFor: 15});
This keeps absolute "has the key been set at all" on the model. It may not be quite specific enough for your needs, but it does work.. so feel free to use and hack away at it.
3. Is it a good idea to let the model use an extra attribute "action" to describe changes in the model?
The way that the Backbone folks have it set up is that, as you already know, change:key is specifically for the change event on a certain key. By relying on a change:action you're kind of adding 'gotcha!'s to your code. I don't see how the other two methods are any better than the first, especially considering now you have logic thrown into an event listener to determine what to fire off.. instead of just attaching that code directly to the appropriate listeners. Given a choice, I'd stick with the first one - it is a clear "This key has updated, so we're going to do X". Not a "something has updated so let's go figure out what it is!" and potentially have to go through a dozen if statements or switches.
4. Are there any pitfalls to watch out for when using objects as attributes in a Backbone.js model?
Well, isEqual performs a deep comparison.. so you're running the risk of doing all of that comparison code plus the risk of recursion. So, yes, that could certainly be a pitfall if you're doing it a number of times.
The object by reference is certainly an issue - I've got a nice little hole in the wall where I've put my head through a few times wondering why something changed in a completely unrelated.. oh wait..
To remedy this a bit, you could override the get method to, in cases where it is returning an object, return something like $.extend(true, {}, this.get(key));
Also, you don't really know what exactly changed in the object based on plain Backbone. So, if you're doing lots of 'stuff' on a change (rebuilding a view, etc), you're potentially going to run into performance issues even if all you did was add another attribute to that object and it isn't used for any of said changes. (i.e. set({layout: layoutObj}) vs set({layoutPageTitle: 'blah'}) which may only update the title.. instead of causing the entire view to reload).
Otherwise, at least in the app that I'm working on, we've had no real issues with objects in backbone. They sync fairly well, and it's certainly better than .get('layout.leftPanel[0]') and having some magical translation to make that work. Just be careful of the reference part.
Hope that helps at least a little!

Related

Backbone views which don't know about their container, models to be fetched via AJAX, no UI/UX trade-offs and maintainable code

Since I'm not totally sure on which level my issue actually is to be solved best, I'd like to summarise the path I went and the things I tried first:
It's more or less about $el (I think).
As most basic backbone examples state, I started with having the $el defined within its view, like
Invoice.InvoiceView = Backbone.View.extend({
el: $('#container'),
template: ..,
..
});
It didn't feel right, that the view is supposed to know about its parent (=container). The paragraph 'Decouple Views from other DOM elements' written on http://coenraets.org/blog/2012/01/backbone-js-lessons-learned-and-improved-sample-app/) perfectly puts it into words.
Following this article's advice, I switched to passing $el over to the view while calling the render()-method. Example:
$('#container').html( new WineListView({model: app.wineList}).render().el );
So far so good - but now render() gets called, while it maybe shouldn't (yet).
For example the View asynchronously fetches a model in its initialize()-routine. Adding a binding to reset or sync (e.g. like this.model.bind('sync', this.render, this)) makes sure, render() gets definitely called once the model is fetched, however above stated way, render() still might get called while the model isn't fetched yet.
Not nice, but working(TM), I solved that by checking for the model's existence of its primary key:
render: function() {
if(this.model.get('id')) {
...
}
However, what I didn't expect - and if it really isn't documented (at least I didn't find anything about it) I think it really should be - the fetch operation doesn't seem to be atomic. While the primary key ('id') might be already part of the model, the rest might not, yet. So there's no guarantee the model is fetched completely that way. But that whole checking seemed wrong anyway, so I did some research and got pointed to the deferred.done-callback which sounded exactly what I was looking for, so my code morphed into this:
render: render() {
var self = this;
this.model.deferred.done(function() {
self.model.get('..')
};
return this;
}
..
$('#container').html( new WineListView({model: app.wineList}).render().el);
It works! Nice, hu? Ehrm.. not really. It might be nice from the runtime-flow's point of view, but that code is quite cumbersome (to put it mildly..). But I'd even bite that bullet, if there wouldn't be that little, tiny detail, that this code sets (=replaces) the view instantly, but populates it later (due to the deferred).
Imagine you have two (full-page) views, a show and an edit one - and you'd like to instantly switch between the two (e.g. after hitting save in the edit-view it morphs into the show-view. But using above code it sets (=resets) the view immediately and then renders its content, once the deferred fires (as in, once fetching the model is completed).
This could be a short flickering or a long blank transition page. Either way, not cool.
So, I guess my question is: How to implement views, which don't know about their container, involve models which need to be fetched, views which should be rendered on demand (but only once the model is fetched completely), having no need to accept UI/UX trade-offs and - the cherry on the cake - having maintainable code in the end.
First of all, the first method you found is terrible (hard coding selector in view's constructor)
The second: new WineListView({model: app.wineList}).render().el is very common and ok. This requires you to return the reference to view from render method, and everyone seems to follow this, which is unnecessary.
The best method (imo) is to simply attach the views element to the container, like this
$('#container').html(new WineListView({model: app.wineList}).el);
The WineListView doesn't need to know about where it's going to be used, and whatever is initializing WineListView doesn't need to worry about when to render the WineListView view instance:
because the el is a live reference to an HTML Element, the view instance can modify it anytime it wants to, and the changes will reflect wherever it is attached in DOM/ when it gets attached in DOM.
For example,
WineListView = Backbone.View.extend({
initialize: function(){
this.render(); // maybe call it here
this.model.fetch({
success: _.bind(this,function(){
this.render(); // or maybe here
})
});
}
});
Regarding flickering: this hardly has to do anything with rendering or backbone, it's just that you're replacing one element with another and there will be an emptiness for a tiny bit of time even if your new view renders instantly. You should handle this using general techniques like transitions, loaders etc, or avoid having to switch elements (For example convert labels into inputs in the same view, without switching view)
First off, the linked example is outdated. It's using version 0.9.2,
whereas the current version (2016-04-19) is 1.3.3. I recommend
you have look at the change log and note the differences, there are many.
Using the el property is fine. Like everything though, there's a time and place.
It didn't feel right, that the view is supposed to know about its parent (=container). The paragraph 'Decouple Views from other DOM elements' written on http://coenraets.org/blog/2012/01/backbone-js-lessons-learned-and-improved-sample-app/) perfectly puts it into words.
I wouldn't define an el property on every view, but sometimes it makes sense, such as your example. Which is why, I'm assuming, Backbone allows the use of the el property. If you know container is already in the DOM, why not use it?
You have a few options:
The approach outlined in my original answer, a work-around.
fetch the model, and in the success callback, insert the view element into the DOM:
model.fetch({
success:function() {
$('#container').html(new View({model:model}).render().el);
}
});
Another work-around.
Define an el property on the view and fetch the model in the view initialize function. The new content will be rendered in the container element (also the view), when the content/model data is ready, by ready, I mean when the model has finished fetching from the server.
In short,
If you don't want to define an el property, go with number 1.
If you don't want to let the view fetch the model, go with number 2.
If you want to use the el property, go with number 3.
So, I guess my question is: How to implement views, which don't know about their container
In your example, I would use the el property, it's simple a solution with the least amount of code. Not using the el property here, turns into hacky work-arounds that involve more code (complexity) without adding any value (power).
Here's what the code looks like using el:
var Model = Backbone.Model.extend({url:'/model_url'});
var model = new Model();
// set-up a view
var View = Backbone.View.extend({
el:'#container',
template:'model_template',
initialize:function() {
this.model.fetch();
this.listenTo(this.model,'sync',this.render);
},
render:function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
var view = new View({model:model});
Check out the documentation for el.
Here is an updated working example.
If there is an obvious flicker because, your model takes a noticeable amount of time
to be fetched from the server...maybe you should think about displaying a loading bar/variation thereof
while fetching the model. Otherwise instead of seeing the flicker, it will appear the
application is slow, delayed, or hanging..but in reality - it's waiting to render the next view,
waiting for the model to finish fetching from the server. Sitting on old content, just waiting for
the model to load new data to show new content.

backbone js - reduce calls to the server

Just wondering how people deal stopping multiple external server calls? I'm doing everything in the .complete of the fetch because otherwise when I try to call anything the fetch hasn't completed and nothing is populated in the collection.
I'm new to backbone so I'm probably missing a trick.. but is there a way to do a fetch and store that information somewhere so that you never have to fetch again, you just work off the collection as a variable? All of my information comes from an external site, so I don't want to be making lots of unnecessary external calls if I can. I'm not updating the server or anything, its all just read-only.
What do other people do for a similar set up? Am I missing something silly? Or am I set up badly for this? Here's what I have so far (work in progress)
Oh also: I'm doing the fetch in the router.. is that a bad idea?
http://jsfiddle.net/leapin_leprechaun/b8y6L0rf/
.complete(
//after the fetch has been completed
function(){
//create the initial buttons
//pull the unique leagues out
var uniqueLeagues = _.uniq(matches.pluck("league"));
//pull the unique leagues out
var uniqueDates = _.uniq(matches.pluck("matchDate"));
//pass to info to the relative functions to create buttons
getLeagues(uniqueLeagues);
getMatchDates(uniqueDates);
homeBtn();
fetched = true;
}
); //end complete
Thanks for your time!
This is an often recurring question but the answer is rather simple.
Perhaps I'll make some drawings today, if it helps.
I never took the time to learn UML properly, so forgive me for that.
1. The problem
What you currently have is this:
The problem however is that this isn't very dynamic.
If these 3 functions at the right would require to be executed from different ajax callback functions, they need to be added to any of these callbacks.
Imagine that you want to change the name of any of these 3 functions, it means that your code would break instantly, and you would need to update each of these callbacks.
Your question indicates that you feel that you want to avoid every function to perform the async call separately, which is indeed the case because this creates unnecessary overhead.
2. Event aggregation
The solutions is to implement an event driven approach, which works like this:
This pattern is also called pub/sub (or observer pattern) because there are objects that publish events (in this case on the left) and objects that subscribe (on the right).
With this pattern, you don't need to call every function explicitly after the ajax callback is finished; rather, the objects subscribe to certain events, and execute methods when the event gets triggered. This way you are always certain that the methods will be executed.
Note that when triggering an event, parameters can be passed as well, which allows you to access the collection from the subscribing objects.
3. Backbone implementation
Backbone promotes an event driven approach.
Setting up an event aggregator is simple and can be done as follows:
window.APP = {};
APP.vent = _.extend({}, Backbone.Events);
From the ajax callback, you just trigger an event (you give it any name you want, but by convention, a semi colon is used as a separator):
APP.vent.trigger("some:event", collection);
The three receiving objects subscribe to the event as follows:
APP.vent.on("some:event", function(collection){
console.log(collection.toJSON());
});
And that's basically all.
One thing to take into account is to make sure that when you subscribe to events using "on", you also need to un-subscribe by calling "off", if you no longer need the object.
How to handle that is all up to you in Backbone.js but here is one of options you can take
Creating a View which has body as its el and handle everything.(I usually use Coffee so This might has some syntax errors)
$( document ).ready(function() {
mainView = new MainView({el: "body"});
});
MainView = Backbone.View.extend({
initialize : function(){
this.prepareCollection();
},
prepareCollection : function(collection){
_checker = function(){
if (collection.length === _done) {
this.render();
}
};
_.bind(_checker,this);
collection.each(function(item){
item.fetch(
success : function(){
//you can also initialize router here.
_checker();
}
);
});
},
rener : function(){
//make instance of View whichever you want and you can use colleciton just like variable
}
})

Is it possible to have a mapping for Angular model property names?

I'm currently working on an app whose database schema changes frequently. This rapid change creates a big problem for my front-end Angular code which consumes the backend JSON API (which I don't have much control over) via Restangular; take the following code for example:
<ul>
<li ng-repeat="item in items">
<h2>{{item.label}}</h2>
</li>
</ul>
There will be a lot of template tags like {{item.label}} scattered everywhere in the front-end code, so whenever the property name changes from, say "label" to "item_label", I'll need to remember where those tags are and change all of them. Of course, I could do a project wide search and replace, but that's not really ideal from an DRY stand point and it'll also be a maintenance nightmare.
My question is, does Angular (or Restangular) provide a way to map model property names to custom ones like this in Backbone?
That way, I can just have something like this
{
label: model.item_label
}
then next time when the "item_label" is changed to something else, I can just update it in this configuration object and not worry about all the references in the templates.
Thanks.
The idea with angular is that you can do whatever you want with the model. While this doesn't point you in any specific direction it does give you the opportunity to implement it in your own OO manner. Say you have an app that has a data object called ...Task a model for tasks might look like..
function Task(initJson){
this.name = initJson._name || 'New Task';
this.completed = initJson.is_completed || false;
this.doneDateTime = initJson.datetime || null;
}
Task.prototype = {
save: function(){
//do stuff with this and $http.put/post...
}
create: function(){
//do stuff with this and $http.put/post
}
//....etc
}
All of this might be wrapped up in a factory.
myApp.factory('TaskFactory',function($http){
var Tasks = []; //Or {};
//above constructor function...
//other helper methods...etc
return {
instance: Task,
collection: Tasks,
init: function(){} // get all tasks? run them through the constructor (Task), populate collection
};
})
You could then edit properties on your constructor (one place (for each data type), the only place). Although this isn't ideal if your using things like Restangular or $resource as they not equipped to be a large backing store but they just assume the properties that come across the wire, which for large, changing applications can sometimes be difficult to manage.
I ended up going with Restangular's setResponseExtractor config property based on this FAQ answer.
It looks like this:
Restangular.setResponseExtractor(function(response, operation, what, url) {
var newResponse = response;
angular.forEach(newResponse.items, function(item) {
item.label = item.item_label;
}
return newResponse;
}

How to force knockoutjs to update UI (reevaluate bindings)

(I know there are other questions here asking the same thing; I've tried them and they don't apply here)
I have a collection being displayed by a Knockout JS foreach. For each item, the visible binding is set by call a method, based on something external to the item itself. When the externality changes, I need the UI to be redrawn.
A striped down version can be seen in this Fiddle: http://jsfiddle.net/JamesCurran/2us8m/2/
It starts with a list of four folder names, and displays the ones starting with 'S'.
<ul data-bind="foreach: folders">
<li data-bind="text: $data,
visible:$root.ShowFolder($data)"></li>
</ul>
<button data-bind="click:ToA">A Folders</button>
Clicking the button should display the ones starting with 'A' instead.
self.folders = ko.observableArray(['Active', 'Archive', 'Sent', 'Spam']);
self.letter = 'S';
// Behaviours
self.ShowFolder = function (folder)
{
return folder[0] === self.letter;
}
self.ToA = function ()
{
self.letter = 'A';
}
UPDATE:
After Loic showed me how easily this example could be fixed, I reviewed the differences between this example and my actual code. I'm using an empty object as a dictionary to toggle if an item is selected self.Selected()[item.Id] = !self.Selected()[item.Id];
The object being changed is already an observable. I assumed that Knockout didn't realize that the list is dependent on the external observable, but it does. What Knockout was missing was that the observable was in fact changing. So, the solution was simply:
self.Selected()[item.Id] = !self.Selected()[item.Id];
self.Selected.notifySubscribers();
Here's what I came up with:
What you have to understand is that Knockout is only "answering" to data changes in observables. If an observable changes, it will trigger every object that uses it. By making your self.letter an observable. You can simply change it's value and uses it somewhere like self.letter() and it will automagically redraw when needed.
http://jsfiddle.net/2us8m/3/
function WebmailViewModel() {
// Data
var self = this;
self.folders = ko.observableArray(['Active', 'Archive', 'Sent', 'Spam']);
self.letter = ko.observable('S');
// Behaviours
self.ShowFolder = function (folder)
{
return folder[0] === self.letter();
}
self.ToA = function ()
{
self.letter('A');
}
};
ko.applyBindings(new WebmailViewModel());
In case you have complex bindings, like storing an object inside an observable. If you want to modify that object you have multiple possible choices.
self.Selected()[item.Id] = !self.Selected()[item.Id];
You could change it to this by making everything "observables" but if my memory is right, it can become complicated.
self.Selected()[item.Id](!self.Selected()[item.Id]());
I remember I had one similar issue where I had dependency problem where I had to update a country, region, city. I ended up storing it as list inside an observable to prevent update on individual element change. I had something like this.
var path = PathToCity();
path[0] = 'all';
path[1] = 'all';
PathtoCity(path);
By doing this, the change would be atomic and there will be only one update. I haven't played a lot with knockout for a while. I'm not sure but I do believe that the last time I worked with knockout, it was able to "optimize" and prevent to redraw the whole thing. But be careful because if it is not able to guess that you didn't change many thing, it could redraw the whole observable tree (which could end up pretty bad in term of performance)
In your example, we could use the same behaviour with my modified example:
http://jsfiddle.net/2us8m/4/

Is there a way to achieve master/slave type dependent observables in Knockout JS

I've got 2 fields in my model that have a master/slave type relationship.
If the master updates the slave should take the update too.
If the slave updates the master is unaffected.
I've managed to implement this with a manual subscription - http://jsfiddle.net/ProggerPete/XNUPj/
But I'm wondering if I could achieve the same result without the manual binding. The reason I'm wanting it is I'd prefer not to have to unbind my manual subscriptions when i'm destroying my view.
Cheers,
Peter
Generally, I would say that the manual subscription is the most straightforward approach to your question.
However, it is pretty easy to create your own custom observable that encapsulates this functionality and handles updating both the master and slave in a writeable dependentObservable. It might look something like this:
function customObservable(initialValue) {
var _source = ko.observable(initialValue),
_local = ko.observable(initialValue),
result = ko.dependentObservable({
read: _source,
write: function(newValue) {
_source(newValue);
_local(newValue);
}
});
result.local = _local;
return result;
}
and you would use it like:
var viewModel = {
source: customObservable("sourceValue")
};
The customObservable (call it whatever you want) returns a writeable dependentObservable that will update both values that you can bind against as source. The local value is also exposed as source.local.
So, you would use this in your scenario like: http://jsfiddle.net/rniemeyer/67pDS/
I am not sure how you want to use this functionality though. If you are looking for the ability to accept/cancel edits to an observable, then you might want to look at this custom observable.
Snippet to show disposal in custom binding:
var subscription = oComboBoxModel.value.subscribe(updateBestMatchFromValue, oComboBoxModel);
//handle disposal (if ko.cleanNode is called on the element)
ko.utils.domNodeDisposal.addDisposeCallback(element, function(){
subscription.dispose();
});

Categories