Backbone - bind multiple views on one $el (events are duplicated) - javascript

I have a container .js-todoslist for todo lists. Also I have several views TodosListView for each todolist. All TodosListView's are created from parent view and binded to the same el .js-todoslist.
Todos lists are not rendered on initialize, I need to render different Todos' lists by clicking buttons so each list has showTodolist method that shows it.
The problem is, though at the moment only one list can be rendered, all of them are actually initialized. And when I press .add-btn in rendered List, all lists have add function trigged (not only current rendered).
TodosListView = Backbone.View.extend
events:
"click .add-btn": "add"
initialize: (options) ->
self = this
#model = #options.model
# I've tried to undelegate event after initialize, but no luck
# #$el.undelegate('.add-btn', 'click');
# #undelegateEvents()
# show this todolist
showTodolist: ->
#$el.html #template(#model.toJSON())
render: ->
#
add: ->
console.log "Add to Todos list #" + #model.get("id")
How to avoid this (excpt creating different views for each list)? Thanks.

Your problem is that since you are using the same el for different views, when you render a view it does not unbind the events of the previous view.
Before rendering the view, use:
this.undelegateEvents();

Related

Angular Material md-chips not refreshing correctly

I am developing an app that works with a "Widget layout".
The user gets a page which is a Dashboard with different widgets he can interact with, such as a table, graphs etc.
I am adding a global "filter" using <md-chips>, the objective of this is having filters that are shared by all the widgets and can be updated by all the widgets.
My Filter list uses <md-chips>, with read-only and md-removable set to True. So filters can only be deleted, or added by interacting with the widgets (so only added programmatically).
One of the feature of this "module" is to add a new filter on a field when a Graph element is clicked.
Example :
Here is my Filters list before clicking a Graph element
Now I click on a Graph element which is in a Child controller of my Filter controller, it will $emit an event to say to the global controller : "I want to update the filters ! here is the filter to add"
And the Filter Controller will get that event and update his filters accordingly ($scope.tags is the list of filters and the model used in <md-chips>)
// CHIPS
$scope.tags = [];
$scope.readOnly = true;
$scope.removable = true;
// FILTER LISTENER
$scope.$on('filterChildChanged', function (event, filter) {
$scope.tags.push(filter);
console.log($scope.tags);
console.log("Parent event fired !");
});
At that point I would expect the <md-chips>element to refresh, because $scope.tags just got a new filter :
<md-chips
ng-model="tags"
readonly="readOnly"
md-removable="removable"
id="filters">
<md-chip-template>{{$chip.value}}</md-chip-template>
</md-chips>
But instead, nothing happens! and the weird part is, it refreshes when I click on one of the existing chip (I had a "test" chip) !
TESTS :
So when I push a test chip on the array before rendering :
$scope.tags.push({field: "testField", value: "test"});
And click on a bunch of Graph elements, $scope.tags is perfectly updated, BUT the visual stays the same until I select the chip "test", then all the <md-chips> appear just like it triggered some refresh function.
Any hint on why the <md-chips> element is not refreshed as $scope.tags (its model) is updated BUT is updated when a chip is selected ?
I tried to trigger md-on-select to force this behavior to happen every time I add a new filter to $scope.tags but I got no luck so far !
Thanks for reading !
NOTE : I am using the latest version of Angular MATERIAL (HEAD MASTER) see doc here : https://material.angularjs.org/HEAD/ | built from the GitHub repository.
EDIT : The problem comes from $$hashKey not being added to the objects, they are only added when I click on one of the existing tags, I need to find a way to add this $$hashKey attribute when I add a new filter
The problem came from the absence of $$hashKey when I added a new filter to my filters model for my chips ($scope.tags).
So I just needed to change
$scope.tags.push(filter);
to
$scope.$apply(function () {
$scope.tags.push(filter);
});
And here is my console.log test
Good thing I learned :
the <md-chips> directive only knows it needs to be updated if its <chip> objects have a $$hashKey
See this on scope.$apply

Collection of template-switching views. Backbone.js

There is a Bckabone view Product:
Product = Backbone.View.extend({
templateBasic: _.template($("#pcard-basic").html()),
templateFull: _.template($("#pcard-full").html()),
initialize: function() {
this.render(this.templateBasic);
},
// ...
Here's my draft: http://jsfiddle.net/challenger/xQkeP/73
How do I hide/show other views when one of them gets chosen/unchosen to view its full template so it could expand to a full container width.
Should I use a view for an entire collection? How do I deal with event handling?
Thanks!
EDIT
That's my final draft: http://jsfiddle.net/challenger/xQkeP/
But still I'm not sure whether I could achieve the same result in more elegant manner? I just think that hiding siblings is not the best way of resolving it:
viewBasic: function(e) {
e.preventDefault();
this.render(this.templateBasic);
if(this.switchedToFull) {
this.$el.siblings().show();
this.switchedToFull = false;
}
},
viewFull: function(e) {
e.preventDefault();
this.render(this.templateFull);
this.$el.siblings().hide();
this.switchedToFull = true;
}
If I understand correctly you want to display all your models inside your collections in two different ways, leaving the choice of how to present them to the user, right?
One way you can do this is to create a main view where the user choice is made. When the user decides you should trigger a method from that view that renders every model from the collection using a different template. On your main view you should have a container (table, div, ul, etc) where you'll append each of the model view.
So, in the end, you have to views. One acting as a container for the collection that takes care of handling the users choice. Then you have another view to render a single model from the collection. This view has to templates that can be used. On the main view you iterate over the collection creating a new view instance for each model to append in the container using a different template depending on the user decision.

Multiple Collection Instances for a single view?

I'm working on an app, in which there are two panels - left and right panel. The left panel is a list of different items and when any one of it is clicked the right panel displays information about that item.
The right panel has the same view, only the data differs.
How can this be achieved in Backbone. Currently, I'm creating instances of collection for each item and passing it to the view. But all instances are having the same data.
I'm new to Backbone and I've to fix this myself.
If I understand the question correctly, you can do this. There should be 3 Objects, Model,LeftView and RightView. For each of items in left panel, you can instantiate a LeftView and a Model. And once clicked on any of the LeftViews, you can instantiate RightView based on the same model. You can use the same model for RightView as LeftView is also having same data.
From what you're saying, it sounds like you really only want a single collection instance, which will hold the items that you're referring to (model instances in the collection).
There should also be 3 views in this scenario — one for the List (the left panel), one for the individual ListItems in that list and one for displaying information about the Item itself (the right panel).
List
The List view holds a reference to the collection and appends a ListItem view for each item in the collection. You may later want to subscribe to events on the collection to automatically append/remove items from the list when new ones are added/remove from the collection. This is the view where that should be handled.
List = Backbone.View.extend({
el: '#list',
initialize: function(){
_.bindAll(this)
this.collection.each(this.appendItem)
},
appendItem: function(item) {
view = new ListItem({model: item})
$(this.el).append(view.render().el)
}
})
ListItem
The ListItem view holds a reference to a particular model and renders the list item inside the list for that model. It also handles the click event, which renders the Item view for that model.
ListItem = Backbone.View.extend({
tagName: 'li',
events: {
"click" : "showInfo"
},
initialize: function(){
_.bindAll(this)
},
render: function(){
$(this.el).html(renderTemplate("listItem", this.model))
return this
},
showInfo: function(){
view = new Item({model: this.model})
$('#item').html(view.render().el)
}
})
Item
The Item view also holds reference to a particular model and renders information about that model. This is the right panel that you're referring to.
Item = Backbone.View.extend({
initialize: function(){
_.bindAll(this)
},
render: function(){
$(this.el).html(renderTemplate("item", this.model))
return this
}
})
Here is a fiddle to show things in action.

Backbone/Javascript remove element from dom

I have a page with two div classes which is fine. One is the collection the other the item.
But when i want to select edit, i need to remove the item view and replace it with the edit link, this is not happening and its staying there, below is my edit class would be great.
Supernote.Views.Notes ||= {}
class Supernote.Views.Notes.EditView extends Backbone.View
template : JST["backbone/templates/notes/edit"]
events :
"submit #edit-note" : "update"
update : (e) ->
e.preventDefault()
e.stopPropagation()
#model.save(null,
success : (note) =>
#model = note
window.location.hash = "/#{#model.id}"
)
render : ->
$(#el).html(#template(#model.toJSON() ))
this.$("form").backboneLink(#model)
return this
Can you post some more of the code? When you say 'edit link', do you mean something in the DOM, or a url in the address bar.
When you call #model.save, Backbone will automatically update the attributes of the model with attributes returned by the server, so the
#model = note
is not required. But if you are not binding the 'change' event on the model to anything, then the view will not update if the model changes.
Are you using Backbone routers to handle changes in the location.hash?
You can remove a view from the DOM by calling #remove()
You could also call $(#el).replace(...) if you wanted to replace it with something.

Many to many relationship events with backbone.js

I have a many-to-many relationship with two of my backbone.js models implemented using a pivot table on the serverside. I'm trying to figure out how to structure it clientside. My current structure is:
1) I have a Tag model, and a TagView which renders a checkbox and the tag label, I have a checked event on the checkbox which does nothing at the moment.
I have a TagsCollection, which holds a bunch of Tags.
I have a TagsCollectionView, which binds add, reset etc of the TagsCollection, and adds TagViews for the added Tags, renders them, and appends the html to its current html (on reset, the html is reset).
I have a global TagCollection instance which contains all the possible tags
I have a Notes Model which contains an (empty) TagCollection called selectedtags on init.
The server returns an array of selected tagids for each Notes, which I add to its TagCollection.
Now comes the hard part, tying it all together.. my NotesView has its own TagsCollectionView which is bound to the global TagsCollection (so it can list all the Tags).. now, how do I get a checked event on the checkedbox of its sub TagViews to trigger an add to this Notes model's selectedtags? Should I provide a reference to the this Notes model instance to the TagsCollectionView on init which then provides it to all the TagViews it creates, whose checked event then adds/removes items from that model? That's the best way I can figure out how to do this, any other thoughts would be appreciated.
View is only for visual display of model. Please specify the need for TagsCollectionView in more details:
Use change event for checking the checkbox.
I would advise incremental coding. First work with the Tag and TagView, As it works, continue adding collection to hold the Tags. After that you can add Notes. It's a 'divide and conquer' :)
Don't confuse with the whole design, it is very simple when you start.
I can not provide the whole design due to lack of requirement details. but, I think below code should trigger the starting point of your design.
var TagsCollectionView=Backbone.View.extend({
el:$(),
});
var Tag=Backbone.Model.extend({
defaults:{
// define the default properties
}
});
var TagView=Backbone.View.extend({
el:$("body"),
events:{
'change #checkBox':'customFunction'
},
initialize: function(){
_.bindAll(this, 'render','customFunction');
this.render();
},
render:function(){
var tag=new Tag();
// code to render the checkbox and label
},
customFunction:function(){
// whatever you want to do after checking event of checkbox
}
});
// collection to collect all the tags
var TagCollection=Backbone.Collection.extend({
model:Tag
});
var Notes=Backbone.Model.extend({
defaults:{
'tagCollection':TagCollection
}
});
// you do not require TagsCollectionView

Categories

Resources