Count or Select Backbone View Instances - javascript

Let's say I'm trying to create a toDo application, where clicking each toDo opens an edit form for each toDoItem. I only want a maximum of one edit form open at any one time, so right now I am doing this in the edit method of the toDoItem view:
edit: function (e) {
e.preventDefault();
if ($('.editForm').length == 0) {
//create form model and view
}
}
That works, but doesn't seem very Backbone-y. Is there are way to select or count all instances of a particular view (in this case, the form-view)?

AFAIK, there are no utilities method in Backbone.View to count instances of a particular Views. Here are some ideas...
Maybe each of your TODO form is tied to a Model? In that case, you can have a model.set/get 'editing' and a collection.isAlreadyEditing() which would filter the models on this field:
(collection.filter(function(model){ return model.get("editing") }).length > 0
That would allow you to use on change:editing events throughout your views to control the logic and have convenient helpers functions in the collection to define some behavior of all those TODO as a whole. This would be one way to implement something closer to a Controller pattern in Backbone.
Another common thing in backbone is to keep an array of all the subviews when you instanciate them, so you could just do a:
_.any(subViews, function(view){return view.editing; })
Assuming that you keep a editing flag in your subviews when it gets toggled.
You can have your views listen to a toggleEdit event with the id or the model or something identifying what is being edited, sometimes the event handler can be as simple as a toggleClass("open", model==this.model)...
I am sure there are millions of other ideas. But counting jQuery selected elements is probably not very high on the list!

Related

Adding a Collection to a ul in backbonejs

I'm just learning js/backbonejs and I have a simple question, Please feel free to direct me to a duplicate.
I have a Collection, I've populated it and I can access it in the console by doing the standard:
collection1.at(1).get('name');
I can also loop through the values by doing:
for(vars i = 0; i < collection1.size(); i++)
{
console.log(collection1.at(i).get('name'));
}
I have four buttons and have views on them and functions that correctly output something to the console when i click on them. When i click on the Show all button i want to display every model in the collection along with the data it has (id,name,fame);
How would i go about doing this? I know i have to have a
<ul id = "gottaChangeThis"></ul>
How would I be able to add something like this to it:
<li><%=id%><%=name%><%=fame%></li>
Any help or redirection would be helpful, Thanks
The basic architecture could include a Backbone.View that accepts your Collection. On render, iterate through the Models in the collection, and for each one render a different Backbone.View (to render the <li>) and append it to the parent <ul> element.
As an alternative, consider using Marionette. It's a Backbone framework/extension that gives you additional objects as a means to eliminate a lot of boilerplate. In your case, you'd want a Marionette.CollectionView with a childView specified. This childView could be a Marionette.ItemView, such that when you render the CollectionView, it automagically instantiates and renders a childView for each Model in your Collection.

Pattern for delegation in modular JS components

I'm looking into breaking a large UI component into smaller pieces, but one thing I'm not entirely sure how to handle and something that I can't seem to find general info about is delegating events from sub-components.
Lets say, for example, you have a todo list and clicking on a todo list will update a sidebar with details about the todo. Right now the code we have is basically 1 file with a template and does all the events for everything. It searches in DOM nodes for data when you click on the delegated handler attached to the wrapper of the list and sidebar. There is potentially thousands of clickable rows.
I'd like something instead that is along the lines of this (this is just pseudo code):
app.controllers.todos = library.controller.extend({
init: function () {
// Store all the todo items in an array
this.todoItems = [];
todoItemsReturnedFromServer.forEach(function (data) {
var todoItem = new app.todo.item(data);
todoItems.push(todoItem);
});
this.todoList = new app.todo.list({data: this.todoItems}); // start with initial data
this.sidebar = new app.sidebar();
},
render: function () {
$('#wrapper').append(this.todoList.render());
$('#sidebar').append(this.sidebar.render());
}
});
So, you'd have a todoList component you could add/remove from and you could have events hooked up which could update the DOM, but is decoupled from the data (i.e. you could not have any DOM and it'd work). Now, in our app, if you click on a todoItem, the todoItem needs to delegate it's event to todoList or higher. I don't want to have 1 click event for every todoItem.
My only idea is to have a "delegate" property on the sub component that the parent takes (if supported) and creates events from. It'd have a hash of events and handlers. If the event handler is already attached it simply ignores it.
Are there other examples or patterns for this type of thing?
Have you tried to use a client-side MVC framework? These are created to solve such problems. I would suggest starting with backbone.js.
Here is a great introductory book. It deals with nested views and models, too:
http://addyosmani.github.io/backbone-fundamentals/#working-with-nested-views
http://addyosmani.github.io/backbone-fundamentals/#managing-models-in-nested-views
http://addyosmani.github.io/backbone-fundamentals/#working-with-nested-models-or-collections

Backbone: reverse collection order with comparator

I'm building a Backbone app which displays a list of books, but when I add a new book, through the Edit view, he goes to the bottom of the list instead to the top. So basically I want to reverse the order of my collection with a Comparator but what I tried it's not working:
comparator: function (model) {
return -model.get('id');
}
Here is a JSFiddle with all the code: http://jsfiddle.net/swayziak/9R9zU/4/
I don't know if the problem is only related with the comparator or if I need to change something more in other part of the code.
Why not a simple prepend instead of append
this.$el.prepend(
Your comparator is looking for the model IDs:
comparator: function (model) {
return -model.get('id');
}
but none of your models have id attributes. Usually the id would come from the server so the server would supply the initial id values when bootstrapping the collection and then new ids would be assigned (by the server) when you save the model.
If you add ids to your data then things will start to make more sense.
You'll also need to adjust your fiddle to:
this.listenTo(this.collection, 'add', this.render);
instead of:
this.listenTo(this.collection, 'add', this.renderBook);
since your editing panel will kill off all the HTML and you'll need to re-render the whole collection.
Once you get past that, you'll find that your Edit link no longer works. That's because you're trying to re-use views while messing around with the content of their el's. Instead, you should:
Stop trying to re-use views, it is almost never worth the hassle.
Give each view its own unique el.
Call view.remove() to get rid of a view before putting a new one in the common container.
Then create the new view, render it, and put its el in the container.
You'll find that since all your views share a common container, you'll no longer need to bind your collection view to the collection's 'add' event, you'll be tossing and rebuilding the whole view instead.

How to access a specific view in backbone.js?

I have a backbone viev which generates a list item view. In the render function it goes through the collection and generates each of the sub item views with some standard code as follows:
render: function () {
_(this.collection.models).each(function(item){
$this.appendItem(item);
}, this);
},
I would like to know how to access a specific view from the item list, say at position 0 or something. I want to be able to trigger a function from the item list view for that specific item.
Well, that depends on how the appendItem function is implemented, I guess is in there where you build the SubViews.
You can store each created SubView in an Array so the Array will offer you a way to manipulate the SubViews.
But if I can offer a piece of advice I would say that this is a code smell from the begining. Instead of trying to manipulate a concrete SubView you can manipulate the Model which is associated with the SubView and make the SubView to be listening to this change.
Then you will start to be thinking in manipulating Models instead of Views.

Backbone.js View removing and unbinding

when my page opens, I call the collection and populate the view:
var pagColl = new pgCollection(e.models);
var pagView = new pgView({collection: pagColl});
Separately (via a Datepicker), I wish to want to populate the same collection with different models and instantiate the view again.
The problem I have is how to close the original pagView and empty the pagColl before I open the new one, as this "ghost view" is creating problems for me. The variables referred to above are local variables? is it that I need to create a global pagColl and reset() this?
well there has been many discussion on this topic actually,
backbone does nothing for you, you will have to do it yourself and this is what you have to take care of:
removing the view (this delegates to jQuery, and jquery removes it from the DOM)
// to be called from inside your view... otherwise its `view.remove();`
this.remove();
this removes the view from the DOM and removes all DOM events bound to it.
removing all backbone events
// to be called from inside the view... otherwise it's `view.unbind();`
this.unbind();
this removes all events bound to the view, if you have a certain event in your view (a button) which delegates to a function that calls this.trigger('myCustomEvent', params);
if you want some idea's on how to implement a system I suggest you read up on Derrick Bailey's blogpost on zombie views: http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/.
another option
another option would be to reuse your current view, and have it re-render or append certain items in the view, bound to the collection's reset event
I was facing the same issue. I call the view.undelegateEvents() method.
Removes all of the view's delegated events. Useful if you want to disable or remove a view from the DOM temporarily.
I use the stopListening method to solve the problem, usually I don't want to remove the entire view from the DOM.
view.stopListening();
Tell an object to stop listening to events. Either call stopListening
with no arguments to have the object remove all of its registered
callbacks ... or be more precise by telling it to remove just the
events it's listening to on a specific object, or a specific event, or
just a specific callback.
http://backbonejs.org/#Events-stopListening
Here's one alternative I would suggest to use, by using Pub/Sub pattern.
You can set up the events bound to the View, and choose a condition for such events.
For example, PubSub.subscribe("EVENT NAME", EVENT ACTIONS, CONDITION); in the condition function, you can check if the view is still in the DOM.
i.e.
var unsubscribe = function() {
return (this.$el.closest("body").length === 0);
};
PubSub.subscribe("addSomething",_.bind(this.addSomething, this), unsubscribe);
Then, you can invoke pub/sub via PubSub.pub("addSomething"); in other places and not to worry about duplicating actions.
Of course, there are trade-offs, but this way not seems to be that difficult.

Categories