Are there any inherent problems with using CoffeeScript to code backbone.js apps? Did you run into some problems that you couldn't fix or had to use some particularly clunky workarounds?
No problems, really. At least, none that aren't easily worked around.
The problems with using CS are the same problems you might get using CS anywhere:
Debugging is still done in the generated JS
CS requires a pre-processing step that can be awkward at times
The rest of your team might not know CS
There are some odd things about CS (they introduce "classes" but they are not real classes)
In addition, since Backbone dev with Coffeescript is "class" based, you find yourself wanting to separate your classes into separate files, in separate folders. Because of this, you can get into a situation where the classes are defined out of order. For instance, your collection might get defined before your model, which can't happen. For this, I recommend using something that can manage dependencies (imports). I use coffee-toaster but there are several other options (Rails has dependency management built in to the asset pipeline, for instance)
It is my preferred way to write Backbone code. In my opinion, Backbone.js development is actually better in CoffeeScript than in Javascript. To me, they go together like chocolate and peanut butter. (Not everyone likes chocolate/peanut butter... not everyone likes BB/CS)
Class semantics
Backbone development relies heavily on extending prototypes and this is something that is built-in with CoffeeScript. So, where you would extend a View in JS:
App.Models.MyModel = Backbone.View.extend({
render: function() {
...
}
});
The CS alternative is a native experience:
class App.Models.MyModel extends Backbone.Model
render: ->
...
Overriding Functions
Some things you do often in Backbone, like overriding functions becomes much more simple. In Javascript:
constructor: function ( attributes, options ) {
this.constructor.__super__.constructor.apply( this, arguments );
...
}
Becomes:
constructor: (attributes, options) ->
super
"this" Context Binding
The "fat arrow" in CS is really useful when you need to declare the context of a function is "this"
Javascript sets 'this' differently when functions get called back. There are several ways to solve it, but out of the box, it is awkward:
initialize: function() {
this.model.bind('reset', this.render);
},
render: function () {
this.$el.html("<ul></ul>");
this.model.each(this.renderItem); // <--- Fails on "reset" because 'this' is wrong
return this;
}
CoffeeScript has the => token that will automatically bind 'this' to the function when it gets called by anybody:
initialize: ->
#model.bind 'reset', #render
render: =>
#$el.html '<ul></ul>'
#model.each #renderItem # The fat arrow fixed it for you
#
When all is said and done, Backbone.js code is easier to write and is much easier to read when written in CS. At least, that is my opinion.
Good luck!
Both CoffeeScript and Backbone.js were written by the same author (Jeremy Ashkenas). The backbone-on-rails gem generates CoffeeScript by default. Though certain plugins (such as Backbone-relational, which you mentioned) may require additional setup, Backbone itself plays very nicely with CoffeeScript.
CoffeeScript is just a syntax layer on top of JavaScript. It essentially is JavaScript. Anything you can do in JavaScript you can reproduce in CoffeeScript.
Related
I'm doing Angular JS tutorials. I've no clue why this notation have been used. Can anyone explain what is happening here?
Code in the controller.js file:
var vm = this;
vm.openSideBar = openSideBar; // Did not understand this line
function openSideBar() {
$mdSidenav('left').open();
};
The code that you post is purely stylistic and takes advantage of hoisting to enable the bindable functions to be displayed at the top of the file, and the details (implementations) for those to be kept separate and specified further down the file.
Benefits
The benefits of this approach are more apparent as the number of bindable functions in the controller increases:
Easier to read (what's important). Since the purpose of the controller is to support the view and supply it with bindings, the what is more important than the how i.e. What the view can bind to is more important than how the controller goes about it's work. You can see immediately on opening the file what the controller can do for the view.
Easier to find (the details). Having the bindable functions at the top of the file acts as like a contents page for the file. Most IDE's will highlight matching variables if you place the cursor over them and that helps find the implementation details further down the file.
Encourages single responsibility. Your controller should be supplying bindings for a view, and only one view. You can see at the top of your file if you start to build up lots of unrelated bindable functions that serve different purposes that you might be trying to do too much in one controller.
Encourages named functions. This is particularly useful for debugging as it means that you will have less anonymous functions in your application, which in turn makes reading call stacks in developer tools easier.
Further Reading
Check out John Papa's Angular 1 Style Guide which has been approved by the Angular team. You can read all about the thought behind this approach under the heading Bindable Members Up Top in the Controllers section (although it can be applied to factories/services/directives also).
openSideBar was add to view model. it was aviable to run this func from view like <someElement ng-click="$ctrl.openSideBar()"></someElement>
The VM model or way of doing is quite old... before ES6 came along.
Nowadays, I would suggest you learn ES6, take a look at Todd Motto's AngularJS style guide https://github.com/toddmotto/angularjs-styleguide.
But here an answer to your question:
// OLD WAY
function MyAppController() {
var vm = this;
vm.colors = ['red', 'blue', 'green'];
vm.getBlue() {
return vm.colors[1];
}
vm.handleClickEvent(e) {
// this keyword is now the dom element, not your controller
// this is why the VM pattern came to life.
this.classList.add(vm.getBlue());
}
}
// NEW WAY
class MyAppController {
$onInit() {
this.colors = ['red', 'blue', 'green'];
}
getBlue() {
return this.colors[1];
}
handleClickEvent(e) {
// Here this keyword is still your controller, NOT the dom
// element (provided you used angular's ng-click directive
const elem = e.target;
elem.classList.add(this.getBlue);
}
}
With ES6, it is rare that you have to same a reference to this since you can use arrow functions. You should also get familiar with bind(), call(), and apply() methods to control which context you want to apply to functions.
Recently I was thinking about the differences and similarities between Backbone.js and AngularJS.
What I find really convenient in Backbone are the Backbone-Models and the Backbone-Collections. You just have to set the urlRoot and then the communication with the backend-server via Ajax basically works.
Shouldn't it be possible to use just the Backbone-Models and Collections in AngularJS application?
So we would have the best of both worlds two-way data-binding with AngularJS and convenient access to the server-side (or other storage options) through Backbone-Models and Collections.
A quick internet search didn't turn up any site suggesting this usage scenario.
All resources either talk about using either the one or the other framework.
Does someone have experience with using Backbone-Models or Collections with AngularJS.
Wouldn't they complement each other nicely? Am I something missing?
a working binding for example above...
http://jsbin.com/ivumuz/2/edit
it demonstrates a way for working around Backbone Models with AngularJS.
but setters/getters connection would be better.
Had a similar idea in mind and came up with this idea:
Add just a getter and setter for ever model attribute.
Backbone.ngModel = Backbone.Model.extend({
initialize: function (opt) {
_.each(opt, function (value, key) {
Object.defineProperty(this, key, {
get: function () {
return this.get(key)
},
set: function (value) {
this.set(key, value);
},
enumerable: true,
configurable: true
});
}, this);
}
});
See the fiddle: http://jsfiddle.net/HszLj/
I was wondering if anyone had done this too. In my most recent / first angular app, I found Angular to be pretty lacking in models and collections (unless I am missing something of course!). Sure you can pull data from the server using $http or $resource, but what if you want to add custom methods/properties to your models or collections. For example, say you have a collections of cars, and you want to calculate the total cost. Something like this:
With a Backbone Collection, this would be pretty easy to implement:
carCollection.getTotalCost()
But in Angular, you'd probably have to wrap your custom method in a service and pass your collection to it, like this:
carCollectionService.getTotalCost(carCollection)
I like the Backbone approach because it reads cleaner in my opinion. Getting the 2 way data binding is tricky though. Check out this JSBin example.
http://jsbin.com/ovowav/1/edit
When you edit the numbers, collection.totalCost wont update because the car.cost properties are not getting set via model.set().
Instead, I basically used my own constructors/"classes" for models and collections, copied a subset of Backbone's API from Backbone.Model and Backbone.Collection, and modified my custom constructors/classes so that it would work with Angular's data binding.
Try taking a look at restangular.
I have not implemented it anywhere, but I saw a talk on it a few days ago. It seems to solve the same problem in an angular way.
Video: http://www.youtube.com/watch?v=eGrpnt2VQ3s
Valid question for sure.
Lot of limitations with the current implementation of $resource, which among others doesn't have internal collection management like Backbone.Collection. Having written my own collection/resource management layer in angular (using $http, not $resource), I'm now seeing if I can substitute much of the boilerplate internals for backbone collections and models.
So far the fetching and adding part is flawless and saves code, but the binding those backbone models (or the attributes within, rather) to ng-models on inputs for editing is not yet working.
#ericclemmons (github) has done the same thing and got the two to marry well - I'll ask him, get my test working, and post the conclusion...
I was wondering the same-
This is the use-case:
salesforce mobile sdk (hybrid) has a feature called smartstore/smartsync, that expects backbone models/collection ,which gets saved to local storage for offline access .
And you guessed it right, we want to use angularjs for rest of the hybrid app.
Valid question.
-Sree
You should look at the angularJS boilerplate with parse here. Parse is backbone like, but not exactly backbone. Thats where im starting my idea of a angularJS backboneJS project
I am trying to test drive a view event using Jasmine and the problem is probably best explained via code.
The view looks like:
App.testView = Backbone.View.extend({
events: { 'click .overlay': 'myEvent' },
myEvent: function(e) {
console.log('hello world')
}
The test looks something like:
describe('myEvent', function() {
it('should do something', function() {
var view = new App.testView();
view.myEvent();
// assertion will follow
});
});
The problem is that the view.myEvent method is never called (nothing logs to the console). I was trying to avoid triggering from the DOM. Has anyone had similar problems?
(Like I commented in the question, your code looks fine and should work. Your problem is not in the code you posted. If you can expand your code samples and give more info, we can take another look at it. What follows is more general advice on testing Backbone views.)
Calling the event handler function like you do is a legitimate testing strategy, but it has a couple of shortcomings.
It doesn't test that the events are wired up correctly. What you're testing is that the callback does what it's supposed to, but it doesn't test that the action is actually triggered when your user interacts with the page.
If your event handler needs to reference the event argument or the test will not work.
I prefer to test my views all the way from the event:
var view = new View().render();
view.$('.overlay').click();
expect(...).toEqual(...);
Like you said, it's generally not advisable to manipulate DOM in your tests, so this way of testing views requires that view.render does not attach anything to the DOM.
The best way to achieve this is leave the DOM manipulation to the code that's responsible for initializing the view. If you don't set an el property to the view (either in the View.extend definition or in the view constructor), Backbone will create a new, detached DOM node as view.el. This element works just like an attached node - you can manipulate its contents and trigger events on it.
So instead of...
View.extend({el: '#container'});
...or...
new View({el:'#container'});
...you should initialize your views as follows:
var view = new View();
$("#container").html(view.render().el);
Defining your views like this has multiple benefits:
Enables testing views fully without attaching them to DOM.
The views become reusable, you can create multiple instances and render them to different elements.
If your render method does some complicated DOM manipulation, it's faster to perform it on an detached node.
From a responsibility point of view you could argue that a view shouldn't know where it's placed, in the same way a model should not know what collection it should be added to. This enforces better design of view composition.
IMHO, this view rendering pattern is a general best practice, not just a testing-related special case.
The plugin model in Backbone.js is really nice, but one thing I'm wondering about is whether it's possible to use multiple plugins without modifying any of the plugin source.
For example, say I've written two plugins for the Collections:
MyBetterCollection = Backbone.Collection.extend({
coolNewFeature: function () {
console.log('This feature is great.');
}
});
MyWayBetterCollection = Backbone.Collection.extend({
wayCoolerNewFeature: function () {
console.log('This feature is even better.');
}
});
I can see some potential issues already, if, for example, both plugins override something like the add method. But having to modify third-party plugins would be a bummer:
MyWayBetterCollection = MyBetterCollection.extend({
...
});
Is there a good approach to handling this situation?
There isn't an easy way to do this. Since there is no traditional inheritance in JavaScript, it's difficult to provide this facility.
You'd have to find plug ins designed to work with each other or fork them and make them compatible.
You could also provide your own extend method that would use the interceptor pattern or some type of monkey patching to provide access to overwritten methods from previous prototypes that were overwritten.
The two collection you've defined above are totally separate from each other. They simply inherit the methods from Backbone.Controller, and if you defined methods with the similar name, then you overwrite those.
Think about Backbone as a class inheritance in other programming languages. Basically, you extend Backbone.Collection as you would do with other languages.
As a result, you can call the superclass, like this
var MyCollection = Backbone.Collection({
toJSON: function() {
var toJSON = this.constructor.__super__.toJSON.call(this);
toJSON.extra = 'my extra value';
return toJSON;
});
Even if you extend your own collection, the logic remains.
What is the best way to bind events to a Backbone boilerplate application? I've been trying to bind my events directly to the models associated with my views, in my views, but it doesn't seem to be working. I see within 'namespace.js', that there is an app key that extends Backbone.Events like so:
// Keep active application instances namespaced under an app object.
app: _.extend({}, Backbone.Events)
I don't fully understand how to use it...
I was able to get things working without the boilerplate, but it does provide some very cool functionality, so I'd love to be able to use it. Thanks!
ADDED
the code I was using was with the underscore bind method like so:
this.module.bind('change', this.render);
But then, I realized that 'this.model' is returning undefined, and so this doesn't work. I really am not sure how the boilerplate wants me to reference my model from the view.
I'm not sure if it is a typo that you copied from your code or a typo you only entered here, but I believe this.module (which IS undefined) should be this.model, which you also must be sure to pass in when you instantiate your view, of course, as so:
myView = new BBView({model: myModel});
then you can say this.model.bind('change', this.render); or this.model.on('change', this.render); for the most recent version of Backbone
I frequently bind my views to change events on my models in this way.
As for extending Backbone.Events, the way I have used it is to create an independent "event aggregator" that will help connect events between different views on your page. Let's say for example you have some action on one view that needs to set off an action on another view. In this case, you can pass your event aggregator object as an option to each of your views when you instantiate them, so that they can both trigger events or bind to events on a common object (i.e. your event aggregator).
whatsUp = _.extend({}, Backbone.Events) // the aggregator
myFirstView = new FirstBBView ({whatsUp: whatsUp});
(the aggregator shows up as this.options.whatsUp inside the view)
mySecondView = new SecondBBView2 ({whatsUp: whatsUp});
inside FirstBBView:
this.options.whatsUp.bind('specialEvent', function(arg1,arg2) {
// do stuff
});
inside SecondBBView, when something important happens:
this.options.whatsUp.trigger('specialEvent', {arg1: 'some data', arg2: 'more data'});
For a great explanation, see this great article by Derick Bailey