I'm currently in the progress of learning Backbone.js and I'm using the book Developping Backbone Applications.
I have a questions about the reference to HTML elements and how they are stored. For example:
initialize: function() {
this.$input = this.$('#new-todo');
Here the HTML element with ID to-do is stored in the this.$input, why do we use the $ in front of input, is this merely a convention? If I change this.$input to this.input my code works fine. I find this confusing because the book states:
The view.$el property is equivalent to $(view.el) and view.$(selector) is equivalent to $(view.el).find(selector).
I would think that $(view.el) does something completely different than (view.el).
How is this.$input saved in Backbone.js? If I console.log it, it produces:
Object[input#new-todo property value = "" attribute value = "null"]
Could someone give me some insight? :)
Using $ infront of a variable name is just a naming convention. It helps developer in distinguishing variable holding jQuery objects from others.
view.$el is a helper variable provided by Backbone, so that we can use it directly, instead of explicitly forming the jQuery object. Hence view.$el is equivalent to $(view.el).
view.$el is assigned in setElement method:
setElement: function(element, delegate) {
// Some code
this.$el = element instanceof Backbone.$ ? element : Backbone.$(element);
// Some code
}
Backbone.$ is reference to $ global variable exported by jQuery.
view.$(selector) is a method defined in View. It's definition does exactly same as $(view.el).find(selector)
$: function(selector) {
return this.$el.find(selector);
}
Related
I have a custom Angular service which creates a custom DOM node using angular.element(). Meanwhile, since I also want the element to have a set of predefined attributes, I pass a JS object as a second parameter to the function:
var element = angular.element('<node-name />', {
class: "some css class",
onclick: "someClickHandler()"
});
Although this works OK as far as the attribute is not specific to Angular.
The problem is that I'm not able to produce Angular-like dashed-case (don't know what their actual name is) attributes (e.g. ng-click).
For now, if I do:
var element = angular.element('<node-name />', {ngClick: 'someClickHandler'}); // ng-click here is definitely not possible as it leads to a syntax error
it will always result in the DOM node as:
<node-name ngclick="someClickHandler"></node-name>
which doesn't work the Angular way.
So, is there any way that a camel-case attribute be converted to its equivalent dashed-case in the DOM?
Any help would be appreciated.
You don't really need any additional code to convert from camelCase to snake-case (although you could). It's better to use snake-case in the first place if you really want to, just make sure you put property name in quotes, otherwise the name is not valid identifier:
var element = angular.element('<node-name />', {
'ng-click': 'someClickHandler'
});
I have the following view:
return Marionette.ItemView.extend({
el: '<section>',
template: JST['app/scripts/templates/grid.ejs'],
that is called like this:
// a Layout
regions: {
grid: '#grid',
detail: '#detail'
},
onShow: function () {
var detailModel = new DetailModel();
var g = new GridView(detailModel);
this.grid.show(g);
}
The question is: How do I get rid of the surrounding section element ? I tried to omit the el property but that gives me the following strange looking div:
<div productname>
Regards Roger
The surrounding element is required for backbone to work. It is essentially a container/placeholder for your view to sit in, whether its contents have been rendered or not.
If you really insist on not having the container then I would consider resorting to the following:
https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.region.md#set-how-views-el-is-attached
Marionette.Region.prototype.open = function(view){
this.$el.empty().append(view.$el.children());
}
I say 'resorting' because, in my opinion, this is not how Backbone is supposed to be used and may have side-effects. (im not quite sure what will happen when the view in that region tries to re render; what will it's el element be pointing to?)
To expand on Scott's answer, it's probably a very bad idea to try and force the removal of the surronding view tags.
All Backbone views are contained within an DOM element. Given this fact, you have 2 main options:
have Backbone put your view into the default div element
specify which element you want Backbone to wrap your view with, using the el or tagName attributes
If the "extra" tags are creating issues (e.g. you need to generate a specific HTML set for use with a plugin), then you're not defining the wrapping element properly. For more on the subject, take a look at this blog post of mine: http://davidsulc.com/blog/2013/02/03/tutorial-nested-views-using-backbone-marionettes-compositeview/
Edit based on jsFiddle: the reason for your strange behavior is that you were passing a model instance to the initialize function. This is then interpreted as attributes for the view and get set as HTML attributes.
The correct way to provide a model instance to a view is :
new App.FooterView({
model: new App.Model()
})
In other words, you provide a javascript object to the view, with a model property. If you want to learn Marionette basics quickly, check out the free preview to my book: http://samples.leanpub.com/marionette-gentle-introduction-sample.pdf (You'll find how to instantiate a view with a model on pages 15-21)
I'm attempting to learn backbone.js and (by extension) underscore.js, and I'm having some difficulty understanding some of the conventions. While writing a simpel search filter, I thought that something like below would work:
var search_string = new RegExp(query, "i");
var results = _.filter(this, function(data){
return search_string.test(data.get("title"));
}));
But, in fact, for this to work I need to change my filter function to the following:
var search_string = new RegExp(query, "i");
var results = _(this.filter(function(data){
return search_string.test(data.get("title"));
}));
Basically, I want to understand why the second example works, while the first doesn't. Based on the documentation (http://documentcloud.github.com/underscore/#filter) I thought that the former would have worked. Or maybe this just reflects some old jQuery habits of mine... Can anyone explain this for me?
I'd guess that you're using a browser with a native Array#filter implementation. Try these in your console and see what happens:
[].filter.call({ a: 'b' }, function(x) { console.log(x) });
[].filter.call([1, 2], function(x) { console.log(x) });
The first one won't do anything, the second will produce 1 and 2 as output (http://jsfiddle.net/ambiguous/tkRQ3/). The problem isn't that data is empty, the problem is that the native Array#filter doesn't know what to do when applied to non-Array object.
All of Underscore's methods (including filter) use the native implementations if available:
Delegates to the native filter method, if it exists.
So the Array-ish Underscore methods generally won't work as _.m(collection, ...) unless you're using a browser that doesn't provide native implementations.
A Backbone collection is a wrapper for an array of models, the models array is in c.models so you'd want to:
_.filter(this.models, function(data) { ... });
Backbone collections have several Underscore methods mixed in:
Backbone proxies to Underscore.js to provide 28 iteration functions on Backbone.Collection.
and one of those is filter. These proxies apply the Underscore method to the collection's model array so c.filter(...) is the same as _.filter(c.models, ...).
This mixing-in is probably what's confusing the "should I use the native method" checks that Underscore is doing:
if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
You can use _.filter on a plain old object (_.filter({a:'b'}, ...)) and get sensible results but it fails when you _.filter(backbone_collection, ...) because collections already have Underscore methods.
Here's a simple demo to hopefully clarify things: http://jsfiddle.net/ambiguous/FHd3Y/1/
For the same reason that $('#element') works and $#element doesn't. _ is the global variable for the underscore object just like $ is the global variable for the jQuery object.
_() says look in the _ object. _filter says look for a method named _filter.
Using Backbone.js I know it's highly recommended to set a model's property using the set method, and NOT by directly modifying the attributes internal hash.
However, apart from not firing the "change" event automatically, what other cons or "side-effects" are there in modifying the internal hash directly?
The problem I'm facing is that while the set method takes an object literal, I need to assign the left hand side using a variable determined at run-time. Thanks.
myModel.set({
myProperty : myValue; //myProperty is a variable, so this is invalid syntax
})
//vs
myModel.attributes[myProperty] = myValue; //myProperty is a variable that can be evaluated
Well, if you look at the annotated source code, you'll find that set does a lot.
What if you extended Backbone.Model with a function that does it for you:
Backbone.Model.prototype.setByName = function(key, value, options) {
var setter = {};
setter[key] = value;
this.set(setter, options);
};
Then, you can just do what you want directly on the model:
var model = new Backbone.Model();
model.setByName(myProperty, "bar");
That feels like a better solution to me.
Edit
As #earl3s pointed out, this is no longer necessary in more recent versions of Backbone. Today, you can just call model.set(myProperty, "bar") and it does what you want.
In the annotated source code mentioned by Brian Genisio you can read the following lines:
"Handle both "key", value and {key: value} -style arguments.".
So you can just use model.set(myProperty,"bar",options).
Perhaps they've added this feature after the post of Brian Genisio.. i dunno.
I have one Backbone model which has an attribute that is a reference to another Backbone model. For example, a Person has a reference to an Address object.
Person
FirstName
LastName
Address
Street
City
State
Zip
These are classes that extend the Backbone model. So, then if I construct an object like the following...
var address = new Address({ Street: "123 Main", City: "Austin" });
var person = new Person({ FirstName: "John", Address: address });
I cannot seem to figure out how to access it in my Mustache template.
Hi {{FirstName}}, you live in {{Address.City}}.
Obviously does not work. When I look at the internals in Firebug, Address is an object, but the City is an attribute within the attributes object of Address. I cannot find any examples of how to access these attributes of associated objects.
I appreciate any help! Thanks!
I ended up solving this issue with the following approach.
I switched from Mustache.js to Handlebars.js for the templating engine. This allowed me to use path based expressions to access nested or associated objects and their attributes.
Hi {{FirstName}}. You live in {{Address.City}}.
But, I also had to change the way I was passing a JSON object to the template. I was using the toJSON method that is part of the Backbone.Model class. But, this was not generating JSON for the associated Address correctly (for the templating to work.) It was burying the address attributes in a member titled "attributes". So, instead, I ended up doing this...
var jsonForTemplate = JSON.parse(JSON.stringify(person));
This gave me a "raw" version of the objects and their associated objects which the template could access using the syntax shown above. JSON.parse and JSON.stringify are both part of json2.js.
I handled this by making another version of toJSON called deepToJSON that recursively traverses nested models and collections. The return value of that function can then be passed to a handlebars.js template.
Here is the code:
_.extend(Backbone.Model.prototype, {
// Version of toJSON that traverses nested models
deepToJSON: function() {
var obj = this.toJSON();
_.each(_.keys(obj), function(key) {
if (_.isFunction(obj[key].deepToJSON)) {
obj[key] = obj[key].deepToJSON();
}
});
return obj;
}
});
_.extend(Backbone.Collection.prototype, {
// Version of toJSON that traverses nested models
deepToJSON: function() {
return this.map(function(model){ return model.deepToJSON(); });
}
});
Try using Handlebars, a templating engine based on Mustache with nested properties support.
Then it would be as easy as {{Address/City}}.
If you don't want to change your templating engine, you can flatten results from Address object and pass them as properties directly on the Person.
The way to go about the same in Mustache would be as follows:
Hi
{{FirstName}}, you live in {{#Address}}{{City}} {{/Address}}
Hope it helps..