Backbone.js + Handlebars.js - {{#each}} instead of model view? - backbone.js

I have a Collection called Projects that contains several models of type Project. When I want to list all the projects, I use a ListProjectsView. Its render function generates a ProjectView for each project in the collection, making it possible to delete one single project by binding an event to its name.
However, now I want to use Handlebars for creating templates. With handlebars it easier to display all the collection models, with the each helper, like this:
<ul>
{{#each projects}}
<li>{{this.name}}</li>
{{/each}}
</ul>
This works fine but now I am wondering how can I delete one project, since I am not using ProjectView anymore. Do I need to add an id to each <li> so I can bind an event? Or do I have to use ProjectView to do this?
Thanks in advance.

the logic here will have to run through ListProjectsView. One way is to do as you say and attach an data-id to each li and then in your event handler get the target
deleteProjectHandler : function(evt){
var $target = $(evt.target), id = $target.data('id');
this.deleteProject(id);
}
Or something of the like.

Related

Data-binding in Widgets

I am trying to data-bind a app/Models/mymodel.js in app/widgets/mywidget/widget.xml
<Collection src="mymodel" instance="true" id="aModel" />
I get the following error:
[ERROR] : Script Error Couldn't find module: alloy/widgets/mywidget/models/mymodel for architecture: x86_64
Not specifying WPATH in widget/ctrl.js and widget/style.tss resulting in Alloy.create* methods pick up from app/ controller or models.
Is there a way to specify to use app/Model in widget/xml
Widgets are independent. They're supposed to be shared across apps. So having a dependency on the app, or a specific model, is not the way it is supposed to be and it is designed so it won't work for this reason.
If you've written the widget yourself specifically for this app remove it, and move code to a separate controller.
If you want to share the widget across apps and want to use a collection inside it, make an exported function and provide the collection to it.
In your widget create a model file with a generic name. Include that collection inside your widget.xml. Then in your widget.js create a method to import the collection
exports.setCollection = function(collection){
$.myCollection.reset(collection.models);
}
Then in your controller including the widget:
$.myWidget.setCollection($.myOtherCollection);
This will set all models of the imported collection to the widget collection. Have an ID attribute that doesn't match? Do some converting in the setCollection method so ID does match. That way it is reusable across apps.
For example, your ID attribute is ObjectId, then you this:
exports.setCollection = function(collection, IdAttribute){
_.each(collection, function(model){
model.set({id: model.get(IdAttribute)}, {silent: true});
});
$.myCollection.reset(collection.models);
}
Then in your controller including the widget:
$.myWidget.setCollection($.myOtherCollection,'ObjectId');
Then you've transformed your collection and all should work

how model and view are connected in backbone.js

I am new in Backbone.js and I keep failing to understand how the model and the view are connected.
I played with Angular where things are pretty clear there, how model, view and controller are connected.
I know Angular and Backbone are different and the latter is MV*.
In Backbone I can understand how model and view are created and work, but how are they connected? Seems to me they are seperated.
Please take a minute to explain or point me to a tutorial.
Thanks in advance
EDIT
OK, here is an example. It happens that I read the book that trolle suggests. This is a code from the book's github
I start reading. I understand the Todo model. I understand the TodoList collection. Then I get to the TodoView
creates a new li
uses Underscore template to compile html
defines some functions that imlements later in the same view
defines an initialize function
inside that function
what is this? this.model.bind('change', this.render, this);
how he can magically bind the action change to a model? How the code knows about the model? When he defined the model and how? Just because is there, the code knows that model = Todo model?
How does he do that bind? What am I missing.
This confuses me, so reading AppView view does not help me much
Thanks again
In backbone.js views are used for displaying models in browser.
For example you can have a model object, whose JSON representation resembles the following: {'firstName': 'foo', 'lastName': 'bar' }
And you use view object to map this model to browser DOM.
As a rule, you use view object along with certain template engine.
Templates allows for creating html chunks filled with model's data.
If you are using underscore template function, your template may look something like this:
<div>
<div>First Name: <%= firstName %></div>
<div>Last Name: <%= lastName%></div>
</div>
After merging template with model's data it would be:
<div>
<div>First Name: foo</div>
<div>Last Name: bar</div>
</div>
You can reuse this view object and its template to display another model object, for example {'firstName':'another foo', 'lastName':'another bar'}, so that the result html would be:
<div>
<div>First Name: another foo</div>
<div>Last Name: another bar</div>
</div>
That is one thing about connection between model and view.
Also view object can listen to changes in your model object to render immediately last updates. For example (inside view object):
initialize: function() {this.listenTo(this.model, 'change', this.render);}
In short, views are the logic behind the presentation of the model's data to the user. So in its simplest form, you bind a model to a view through the models change events, so you can update the presentation instantly whenever your data changes. So a simple view would take in a model, create HTML elements based on that models data, insert that html into the DOM and update that HTML whenever the data changes.
You can find a great book full of helpful examples here (free): http://addyosmani.github.io/backbone-fundamentals/
EDIT:
With regards to your updated question about how the view knows about the model, this.model is a reference to the actual model object. You can set the reference to the model when you create the view. That is, when you call your view-constructor to instantiate a view, you could pass in a model. You need to go all the way into the AppView object in the code example to see where this happens, in the addOne method:
addOne: function(todo) {
var view = new TodoView({model: todo});
this.$("#todo-list").append(view.render().el);
}
The function gets a model as a parameter, and when the view is instantiated that model is referenced. So now you have a view that knows about the model, and when the view.render method is called, the view can render it's template with the model data. When you change the data in the model, for instance by using the set method, myModel.set({title: "March 20", content: "In his eyes she eclipses..."});, you trigger the change event for that model. You can see all the built in events for backbone here: http://backbonejs.org/#Events-catalog. The view is listening for that event, just like it could listen for a click event or any other event. In the code in your example the view listenes for a change event from the model. If it hears it it knows that the object behind this.model has changed, and it can then update the DOM with the new data or do something else. In the case of the example it calls this.render, which updates the DOM with the new model data.
I think you want to know about Backbone.Events (http://backbonejs.org/#Events), both Models and Views make use of this module and that's how the view learns about changes in the Model, if you want to learn how this is implemented you can always read the annotated source (http://backbonejs.org/docs/backbone.html#section-19), but more important I think you want to learn about the observer pattern: http://addyosmani.com/resources/essentialjsdesignpatterns/book/#observerpatternjavascript.

Get rid of surrounding elements in Backbone.Marionette

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)

Append backbone EL twice in Ajax Success Not Working

When I try to append my backbone view to two different places at the same time in my success method only the second appending works. Do you know why?
$(content).prepend(this.$el.append(this.template({ data: data })));
$(chat_window).prepend(this.$el.append(this.template({ data: data })));
Each DOM node can have exactly 0 or 1 parent nodes, never more than 1. If you append a node somewhere, it gets removed from it's current parent and then appended to the new parent. What you need here is 2 distinct view instances each with it's own element.
el corresponds to one html element that a backbone view generates. Into that html element you can append more html weather it be another backbone view or a rendered template.
Hence in your case if the el is attached twice it finally stays where it was attached last to the dom tree. If you want attach in multiple places then I guess you should instantiate the backboneview twice.
In my opinion, the view you're talking about should not know about its distant parents or cousins but rather should trigger an event "I have new content" and then the interested views can act upon this the way they want.
That being said there is a difference between a view and its html representation(s), you could design your app so that you get 2 places in the html where you put ".new-content-holder" and pass this selector as the el of your view upon creation. Then the 2 places will be updated at the same time without you explicitly programming it. I sometimes use this technique for example when I want a paginator for a long list to be displayed over and under the list.
Some html :
<div class="content">
<p>Recent comments</>
<ul class="new-content-holder"></ul>
</div>
<div class="chat-room">
Live feed
<ul class="new-content-holder">
<li>a chat message</li>
<li>another chat message</li>
</ul>
</div>
And a view
....
var MessageView = Backbone.View.extend({
template: _.template('<li class="chat-message"><%= message %></li>'),
prependData: function(data){
this.$el.prepend(this.template(data))
},
onMessage: function(message) {
this.prependData({message: message.data})
}
});
....
//And in a super controller of sorts :
var messageView = new MessageView(el: '.new-message-holder')
Again, this is not a very good separation of concerns...but I hope that helps.
I agree with #Peter Lyons, You can not inject the same node into two elements. Ultimately, the node will move to new element. One of the ways is to get HTML from the element you want to inject and inject the same HTML twice. Since html is a string and not a dom element. You can add it as many times and inside as many elements.
Try this one:
var html = this.$el.append(this.template({ data: data })).html();
$(content).prepend(html);
$(chat_window).prepend(html);
I hope you are not using id's on elements inside your template.
PS: I don't know your use case exactly.

How do you structure two nested lists in a sample Backbone.js todo list app?

I'm working on a sample ToDo list project in Backbone and I'd like to understand how the framework would prefer me to organize its views and models in the nested list scenario.
To clarify what I mean by that, my single-page Backbone app should display lists of ToDo lists. From the backend standpoint, there's a List resource and an Item (a single entry in a todo list) resource. Something along the lines of:
Monday chores
Pick up the mail
Do the laundry
Pick up drycleaning
Grocery list
Celery
Beef
You get the idea...
Since mine is a Rails 3.2 app, I'm vaguely following the Railscasts Backbone.js tutorial, so that's where I'm getting the current design from. I would love to know if I'm wildly off the Backbone-prescribed pattern, or if I'm on the right track!
I thus far have:
ListsIndex View //index of all lists
\-- ListsCollection
\-- ListView / Model //individual list
\-- ItemsIndex View //index of items in one list
\-- ItemsCollection
\-- Item View / Model //individual todo item
The flow would be:
On router initialize, fetch() collection of lists on /lists backend route. On the 'reset' event for the collection part of ListsIndex, execute render() on each of the items in the collection, appending to the list index view template.
In the initialize method of each Item View (is this where you'd wire-up the second level fetch?) fetch() the items from the /lists/:id/items backend route into an ItemsCollection specific to that view.
In the same method, instantiate an ItemsIndex object and pass the collection into it. Once again, in ItemsIndex, have a 'reset' event handler for when the collection is populated, at which point it should render each fetched model from the item collection and append them to its own view.
I'm essentially taking the design of the List and mirroring it down one level to its items. The difference is that I no longer have a router to rely on. I therefore use the initialize method of ListView to a similar effect.
Yay / nay? Super wrong? Thanks!
TL:DR; 1) I would bootstrap your initial data instead of a fetch() reset(). 2) You can do a fetch in the initialize of a View as you need it. Or you could load the data at the start. Just remember that if you fetch in the init, the async nature won't have the data ready at render. Not a problem if you have a listener waiting for that sync/add/etc. 3) I don't know what you mean by itemIndex object but you can create objects and add to them collections as you need them. Or you can just bake the in at the start if you know all your lists are going to have a collection eventually. You can reset if you want (fetch automatically does this unless you give it option {add:true}) or just add them in one by one as they come in although reset(), remove prior views, render all views seems to be the common way people do things with a complete fetch().
I think it looks pretty good. The nice thing about Backbone is that you can do it many different ways. For example, your number 2 says to wire up a second fetch() from the view. You could do that if you want to lazy load. Or you could just grab all the data at app start before anything is done. It's really up to you. This is how I might do it.
This is how I might make an app like this (just my preference, I don't know that it's any better or worse or if its the same as you described.)
First I would create a model called ListModel. It would have an id and a name attr. This way, you can create many separate lists, each with their own id that you can fetch individually.
Each ListModel has an ItemsCollection inside of it. This collection has a url based on the ListModel it is a part of. Thus, the collection url for ListModel-1 would be something like /list/1
Finally you have ItemModel which is a resource id and text.
ListCollection
ListModel // Monday Chores
ItemCollection
ItemModel // Mail
ItemModel // Laundry
ItemModel // Drycleaning
ListModel // Grocery
ItemCollection
ItemModel // Celery
ItemModel // Beef
So in this little display you'll notice I didn't put anything to do with views in yet. I don't know if it's more of a conceptual thing but this is what the data hierarchy looks like and your views can be, should be totally independent of it. I wasn't exactly sure how you were including the views up above but I thought this might make it clearer.
As for defining these structures, I think two things.
First, I'd make sure my ListModel is defined in my collection. That way I can use the collection add(hash) to instantiate new models as I produce / add them.
Second, I would define the ListModel so that when one is created, it automatically creates an ItemCollection as a property of that ListModel object (not as an attribute).
So ideally, your ListModels would be like this:
ListModel.ItemCollection
Before the app initializes, I would bootstrap the data in and not fetch(). (This kind of addresses point 1 you make) Ideally, when your Backbone application starts it should have all the necessary data it needs from the get go. I would pass in the head some data like this:
var lists = [listModel-1-hash, listModel-2-hash];
Now when the app fires up, you can instantly create these two lists.
var myLists = new ListCollection();
_.each(lists, function(hash) {
myLists.add(hash); // Assumes you have defined your model in the ListCollection
}
Now your List Collection has all the list models it needs.
Here is where views come in. You can pass in anything to any view. But I might break views down into three things.
AppView, ListModelView, ItemModelView and that's it.
Imagine a structure like this:
<body> // AppView
<ul class="List"> // ListModelView
<li class="Item"></li> // ItemModelView
</ul>
<ul class="List"> // ListModelView
</ul>
</body>
When your start your app and create an AppView, inside AppView you'd generate each ListModelView and append it to the body. Our lists are empty. Maybe when you click on the it lazy loads the items. This is how you'd hook it up.
// In ListModelView
events: {'click':'fetchItems'}
fetchItems: function() {
this.model.itemCollection.fetch(); // Assumes you passed in the ListModel into view
}
So since I bootstrapped the data to begin with, this fetch() call would be your "second" fetch. (I'm addressing point 2 you made.) You can fetch it in your initialize. Just remember that it is an asynchronous function so if you need them at render time, it won't work. But, what you can do is add event listeners to this view that are listening for add events to your itemCollections.
this.model.itemCollection.on('add', this.addItemView, this);
addItemView() will generate new instances of the itemViews and append them.
As for point 3, you can instantiate a collection at that point you need it and throw it into your ListModel. Or you can do what I did and make sure all your models always have an ItemCollection. This depends on your preferences and goals. You probably didn't need all this but I felt like illustrating it out for some reason. I dunno, maybe it helps.

Resources