How to get support for dot notation/nested objects in backbone model. The plugins that are available are buggy and wondering if backbone would ever support
person = { name : {first: 'hon',last:'son'}}
model = new Backbone.Model(person)
model.get('name.first')
model.set('name.first','bon')
There are two plugins to get the job done:
Backbone Nested
Backbone Deep Model
Both handle getting and setting attributes and change events for dot notation.
I did that if i were you:
var nameObj = model.get('name')
nameObj.first = bon
model.set('name', nameObj)
Related
In my Backbone App i'm trying to merge collections by using the _.union-method from Lodash (Underscore).
So I have the following:
var myCollection = _.union([carsCollection], [motorcycleCollection], [bikeCollection]);
when I do console.log(collection) it gives me [child, child, child] where each child contains an array of the Models from the collection and its attributes. So far so good, my question is now:
How can I display this in a View? I tried:
this.insertView(new View({collection: myCollection }));
but that didnt work...
Does anyone know whta the issue is here?
Backbone collections are not arrays of models, using _.union on them won't produce a collection of models. You have to work with the collection.models and then build a new collection :
var models = _.union(
carsCollection.models,
motorcycleCollection.models,
bikeCollection.models
);
var unitedCollection = new Backbone.Collection(models);
See http://jsfiddle.net/nikoshr/uc5cn/ for a demo
So I was what the best way for all views in an application to have actions performed on an element.
In a non single page application you would run say:
$(document).ready(function() {
$('.autosize').autosize();
});
to apply autosize function to all elements with the autosize class on every page.
Now in a Backbone Marionette app to do this you could perform that in each view with onDomRefresh or similar but for things that affect 90% of views you'd want this to run automatically somehow.
I don't think there's a way that an Application object can listen to all onDomRefresh events which would potentially solve it. I've consider overloading Marionette.MonitorDOMRefreshto add this in but it doesn't feel like a Backbone approach.
Other things I considered were sub-classing each of the marionette views to add mixins for loading different groups of UI elements.
I figured other people must have experienced this scenario so was interested what approaches have been used.
Just make a base View class and inherit from it every view class that needs the autosize enhancement.
var AutosizeBaseView = Backbone.Marionette.ItemView.extend({
onDomRefresh: function(){
this.$('.autosize').autosize();
}
});
then make your classes like this:
var SomeView = AutosizeBaseView.extend({
});
So I couldn't really find any solutions that really solved my problem, despite some helpful chats with #julio_menedez and #marionettejs on Twitter. With a really good idea being using Polymer but wasn't suitable as I need to support older IE's.
So instead I headed into the dangerous world of monkey patching to solve it (Bear in mind I might need to iron out some wrinkles with this still, just finished writing it and not fully tested it - I'll update accordingly)
In Coffeescript: (javascript version at the bottom)
# Monkey patching the Marionette View.. sorry!
# this is the only Marionette view which doesn't have it's own constructor
Marionette.ItemView = Marionette.ItemView.extend
constructor: ->
Marionette.View.prototype.constructor.apply #, Array.prototype.slice.call(arguments, 0)
original_view_constructor = Marionette.View.prototype.constructor
Marionette.View.EventAggregator = event_aggregator = _.extend {}, Backbone.Events
# all the other constructors call this so we can hijack it
Marionette.View.prototype.constructor = ->
event_aggregator.listenTo #, 'all', =>
args_array = Array.prototype.slice.call arguments, 0
event_aggregator.trigger.apply event_aggregator, [ 'view:' + args_array[0], # ].concat(args_array.slice(1))
event_aggregator.stopListening # if args_array[0] == 'close'
original_view_constructor.apply #, Array.prototype.slice.call(arguments, 0)
And then to use I just setup a listener in my application object to catch view events I need. e.g:
#listenTo Marionette.View.EventAggregator, 'view:dom:refresh', (view) ->
view.$('div').css('backgroundColor', 'red');
So in my view these are the pros and cons of this technique:
Pros:
Can listen to all view events without injecting all view classes or subclassing all view classes
Simple to use
Objects don't need to opt-in to using it at all
Cons
Uses monkey patching, dangerous to Marionette API Changes
Uses Marionette namespacing so vulnerable to a future Marionette namespace collision
Takes dealing with views out of view context
Having an event aggregator object isn't something seen elsewhere in Backbone/Marionette (afaiw) so breaks a pattern (update - something similar is seen with Backbone.history)
Anyway I'm welcome to feedback, alternatives, criticism :-) and hope maybe this helps someone else in the same situation
Javascript:
(function() {
var event_aggregator, original_view_constructor;
Marionette.ItemView = Marionette.ItemView.extend({
constructor: function() {
return Marionette.View.prototype.constructor.apply(this, Array.prototype.slice.call(arguments, 0));
}
});
original_view_constructor = Marionette.View.prototype.constructor;
Marionette.View.EventAggregator = event_aggregator = _.extend({}, Backbone.Events);
Marionette.View.prototype.constructor = function() {
var _this = this;
event_aggregator.listenTo(this, 'all', function() {
var args_array;
args_array = Array.prototype.slice.call(arguments, 0);
event_aggregator.trigger.apply(event_aggregator, ['view:' + args_array[0], _this].concat(args_array.slice(1)));
if (args_array[0] === 'close') {
return event_aggregator.stopListening(_this);
}
});
return original_view_constructor.apply(this, Array.prototype.slice.call(arguments, 0));
};
}).call(this);
In CoffeeScript I think you could also do:
extend = (obj, mixin) ->
obj[name] = method for name, method of mixin
obj
include = (klass, mixin) ->
extend klass.prototype, mixin
include Marionette.View,
onDomRefresh: () -> #$('.autosize').autosize()
Which should cover all the view types. Haven't tested this specifically, but just did something very similar to add functionality to Marionette's Layout view. Extend / include pattern at http://arcturo.github.io/library/coffeescript/03_classes.html. Of course this should all be doable in straight up JS too.
UPDATE:
Actually, since we have Underscore available to us we don't need to manually define the include and extend methods. We can just say:
_.extend Marionette.View.prototype,
onDomRefresh: () -> #$('.autosize').autosize()
I want to display a list of images and their respective comments. Like:
Image url | Format | Comments
http://example.com/img.jpg | 1280x420 | [Comment 1], [Comment 2] ...show all ...show all
http://example.com/img2.jpg | 630x590 | [Comment 1], [Comment 2] ...show all
I have two resouces: /images and /comments/{image_id}
What is the recommended way to fetch the comments for each image to be able to display them on the same row? Does Marionette have a helper for this?
In my opinion, these look like a good place to use relational models. Backbone doesn't support these out of the box, so you'll need a plugin. Have a look at Backbone-Relational or supermodel.js. These projects provide better forms of model nesting than the default implementation. From there, use nested composite views to render the collections.
From what I know Marionette does not have such helper. I think you can use something simple like:
var ImageComments = Backbone.Collection.extend({
initialize: function(models, options) {
options || (options = {});
this.imageId = options.imageId;
Backbone.Collection.prototype.initialize.apply(this, arguments);
},
urlRoot: function() {
return 'comments/' + this.imageId;
}
});
var id = 1,
image = new Image({ id: id }),
comments = new ImageComments(null, { imageId: id });
$.when(image.fetch(), comments.fetch()).done(function() {
// .. do your things with image & comments
});
This describes simple case, if that's commonly used in your application you might want to implement your own fetch method (e.g. for image, that will also fetch comments) or use plugins like Backbone-relational or Backbone-associations
You can use nested composite views.
http://davidsulc.com/blog/2013/02/03/tutorial-nested-views-using-backbone-marionettes-compositeview/
You can also do old fashioned in template loops for the comments
http://www.headspring.com/an-underscore-templates-primer/
I want to handle two templates in backbone js. How do I go about doing so? I want to pass in the json for the models in the template?
I have the following:
var json = model.toJSON(), json2 = model2.toJSON();
that.$el.html(_.template(tmpl, json, json2));
but that does not allow me to get the fields from the second json in underscore.
The proper syntax would be
var data = {
modelOne: model.toJSON(),
modelTwo: model2.toJSON()
}
that.$el.html(_.template(tmpl, data));
If the models don't mixed inside template, You can do that : need to create new template for second model, and add to necessary address
var addressToSecondModel = $(that.$el).find("address");
addressToSecondModel.html(_.template(tmpl2, json2));
I'm using the method where on my Backbone collection like so:
var quote = app.Collections.quotes.where({Id: parseInt(id, 10)});
However, to access the only result/Model (as it's by ID, there's only going to be one) - how can I get the actual Model without resorting to using this:
var onlyModel = quote[0] ?
Is there a better way?
A better way is to use get on the collection. http://backbonejs.org/#Collection-get
var quote = app.Collection.quotes.get(parseInt(id, 10));
Backbone proxies Underscore functions on collections and notably findWhere that will return the first match found.
findWhere _.findWhere(list, properties)
Looks through the list and returns the first value that matches all of the key-value pairs listed in properties.
Your query can be written as
var quote = app.Collections.quotes.findWhere({Id: parseInt(id, 10)});
But in your case, if you are indeed looking for the model with a given id, you can directly use the get method
get collection.get(id)
Get a model from a collection, specified by an id, a cid, or by passing in a model.
var quote = app.Collection.quotes.get(id);