i have a backbone view like this:
var newrow=Backbone.View.extend({
el:"<table>",
events:{
'click .edit':'editrow',
'click .delete':'deleterow'
},
render: function()
{
data=this.model.toJSON();
$('table').dataTable().fnAddData([data.name,data.email,data.contact_number,'<span class="edit">Edit</span><span class="delete">Delete</span>']);
return this;
},
editrow:function(){
alert ("edit);
},
deleterow:function(){
alert("delete");
}
})
I need to attach click events with td having spans with class 'edit'. I know this will not work because i am adding rows dynamically using datatable function. I am trying to add rows in a pre-rendered datatable. What may be the solution.
P.S I have already rendered a empty table in some other view. This works fine and datatable gets rows added. But can't figure out how to add click events in elements.
Few things are a bit off in your view. If you fix those, it might work.
Instead of el:"<table>" try tagName:"table". This is the correct way to set up a new element for a view.
Instead of $('table').dataTable(). try this.$el.dataTable(). The former performs a selection for all table elements in you page, and the latter references your view´s (<table>) element.
As long as the elements you add are within the view's el the events should wire up automatically. If it doesn't work for some reason, try calling this.delegateEvents() after inserting new rows.
Related
I have a problem with Backbone and Marionette. I have a CompositeView with a collection where people can a comment, this all works nicely, the comment is added and saved to the server but I don't want the view to update and to show the newly added comment. I have tried this:
App.Views.CommentsView = Backbone.Marionette.CompositeView.extend({
template: '#article-comment-container',
itemViewContainer: 'ul',
itemView: App.Views.CommentView,
collectionEvents: {
"add": "modelAdded"
},
modelAdded: function(){
console.log('Please do nothing!');
}
});
But the item is still rendered into the page on top of my modelAdded function being called. Can I prevent that from happening at some point?
In a different scenario I would like new items to be added to the top of the list and not the bottom. Do I have to override the entire appendHtml method achieve this?
Setting the collection event add simply adds another handler to the queue for that event; it doesn't replace any other events so the default marionette behaviour will still occur.
I assume you're calling the create method on the collection to create your new comment model. If this is the case you simply need to set the silent option to true. Now the add event will not fire and Marionette will not create and render the view for that model. You can do it like this:
commentCollection.create(commentModel, {silent: true});
As for you second question about prepending, yes I would override appendHtml method. Or to keep the method names consistent with what actually happens, create a method called prependHtml and then override the renderItemView method to call prependHtml.
I have a Layout that has several tabs. Clicking one of these tabs will show the appropriate composite view in the page's content region. After navigating back and forth between different tabs I noticed that the composite views have lost their native bindings to render on collection reset and model changes.
Is there a way I should be rebinding the events being used in _initialEvents of a composite view when showing a view for a second time, or should I be creating a new composite view every I show a tab?
Currently I am creating all my views in initialize of my Layout and then using show with the view when a tab is clicked.
initialize: function(){
_.bindAll(this);
// Tabs
this.places_page = new Places_Layout();
},
show_places_page: function(){
this.content.show(this.places_page);
this.places_page.delegateEvents();
},
You don not have to create a Layout/Item/Composite/Collection view each time you switch from tab to tab, on the contrary you can save the content in a variable just the way you are doing, the problem you have is that the variable is being re-declared each time you want to render the content.
The solution is that you have to verify if that variable (this.places_page) is declared if not append it to the view so when you call it more times it will be holding the same layout view without any problem, just note that when you render the main view (the one holding the regions) the nested child views(in regions) will be lost until new navegation through them.
initialize: function(){
_.bindAll(this);
// You can asign a diferent variable for each view so when you call show_places_page it will render with the same view.
if (!this.places_page){
this.places_page = new Places_Layout();
}
// other tab
if (!this.other_page){
this.other_page = new OtherPage_Layout();
}
},
show_places_page: function(){
this.content.show(this.places_page);
this.places_page.delegateEvents();
},
This does not sound like the best approach to me.
You should use the layout's region managers to show views without needing functions like you have defined.
I would go for this approach
var view = new CustomView();
layout.content.show(view);`
then later on:
var newView = new SecondCustomView();
layout.content.show(newView);
If you want to continue down the road that you are on then you would probably be best to use this approach:
initialize: function () {
_.bindAll(this);
},
show_places_page: function () {
var placesLayout = new Places_Layout();
this.content.show(placesLayout);
}
Does that make sense?
Its hard to suggest the best course of action without seeing more structure around this.
Is there a reason that you are creating the views in initialize?
Marionette(v.1) onwords uses Backbone.BabySitter to manage child views .
In your case you do the same.
Just create a containter to store all tab view. Later query the container to return the view you need to display.
this.tabViewsContainer = new Backbone.ChildViewContainer();
this.tabViewContainer.add(new CustomView(),'tab1');
this.tabViewContainer.add(new SecondCustomView(),'tab2');
To Later Show the view just do this
var custv = container.findByCustom("tab1");
this.content.show(custv);
In close method your layout view successfully close all view in container
this.tabViewsContainer.each(function(view){view.close()});
You should not create all the views inside the initialize as this will cause you memory leaks that's why you should do dynamic creation of the views. Also I would suggest create a common function for showing a view in your content region to increase the code re-usability. I would suggest you something like following solution:
//define the regions of your layout view
regions: {
content: '#content'
},
//Maintain a config for the tab content view classes.
contentViews: {
tab1: Tab1View,
tab2: Tab2View,
tab3: Tab3View
},
//keeps all the view instances
viewInstances: {},
/*
* show tab function is called when you click a tab item.
* Consider each tab has a attribute for tab name.
* For example HTML of your one tab is like:
* <div data-tab-name="tab_name">Tab <tab_name></div>
*/
showTab: function (e) {
var tabName = $(e.currentTarget).attr("data-tab-name");
/*
* code for showing selected tab goes here...
*/
//check and create the instance for the content view
if (!this.viewInstances[tabName]) {
this.viewInstances[tabName] = new this.contentViews[tabName]();
}
//Then here you are actually showing the content view
this.content.show(this.viewInstances[tabName]);
this.viewInstances[tabName].delegateEvents(); //this is to rebind events to the view.
}
I have a View called Form that renders either a form to edit a list, or the list itself, depending on what argument is passed to render. I've added event handlers so that the show/edit mode can be toggled. I've taken this out from the code below to keep it simple, but this just gives a bit of context to what the View does in context.
I can instantiate this Form view as a child in another view that requires a form, or the list to be rendered, which I've done in the New view, where it would be rendered as a form.
When I need to save, I call the form:save event, which triggers a routine in the Form view that saves the form, I've just made it call a console.log here to show it works. In my code, I call form:save through an $('a#submit').click binding which binds to navigation buttons that are inserted by an ApplicationView (but I don't think that matters for the purposes of this question.)
Lets say I navigate away from the New view, and I go back to it a number of times. When I hit save, the method runs the number of times I have instantiated and rendered a new Form view.
So far:
I've tried doing unbind() and remove() in a close method on the Form view from the New view with no luck.
I think I may have problems with scoping, but I'm unsure.
I know this isn't related to my navigation bindings.
I think this may be to do with zombie views.
Any pointers to make it only run once?
App.Views.New = Support.CompositeView.extend
initialize: (options) ->
_.bindAll this, 'render'
#model = new App.Models.Item()
render: ->
self = this
form = new App.Views.Form model: #model, collection: #collection
#$el.append form.render().el
setTimeout (->
$('a#submit').click (e) ->
e.preventDefault()
App.eventHandler.trigger 'form:save'
), 0
this
App.Views.Form = Support.CompositeView.extend
initialize: ->
_.bindAll this, 'render', 'save'
App.eventHandler.on 'form:save', #save
render: ->
self = this
# RENDER TEMPLATE HERE
this
save: ->
console.log 'form saved'
I believe your issue is that you are creating a new view each time you want to render the form, but you aren't getting rid of your old view. What you can do is either destroy your old view, or keep a reference to it and instead of creating a new view each time, just pass in the model to the existing view and refresh/rerender the display
I have a view that creates a sub-view per item in the list. Generically let's call them ListView and ListItemView. I have attached an event as follows on ListItemView:
events: {
"click .remove": "removeItem"
}
I have template-generated html for ListItemView that is approximately like the following (swapped lb/rb for {/} so you can see the "illegal" html):
{div class="entry" data-id="this_list_item_id"}
SOME STUFF HERE
{div class="meta"}
{a class="remove" href="javascript:;"}[x]{/a}
{/div}
{/div}
The problem is, when the click on any of the [x]'s, ALL of the ListItemViews trigger their removeItem function. If I have it go off of this model's id, then I drop all the items on the page. If I have it go off the clicked item's parent's parent element to grab the data-id, I get a delete for EACH ListItemView instance. Is there a way to create an instance-specific event that would only trigger a single removeItem?
If I have ListView hold a single instance of ListItemView and reassign the ListItem model and render for each item in the list it works. I only end up with one action (removeItem) being triggered. The problem is, I have to find the click target's parent's parent to find the data-id attr. Personally, I think the below snippet is rather ugly and want a better way.
var that = $($(el.target).parent()).parent();
Any help anyone gives will be greatly appreciated.
It seems like your events hash is on your ListView.
If it is, then you can move the events hash to ListItemView and your removeItem function can be the following
removeItem: function() {
this.model.collection.remove(this.model);
}
If this isn't the case, can you provide your ListView and ListItemView code so I can look at it.
A wild guess but possible; check that your rendered html is valid. It might be possible that the dom is getting in a tiz due to malformed html
I have a simple backbone.js app. I want to render a view into the DOM of the HTML page, this view is a detail view for a model. My HTML page already has the DIV element that I want to render the view into. If I try to render my view like this:
detailView = new RulesPanelView({model : #model})
$("#detail").html(detailView.render().el)
It fails and I get [Object HTMLDivElement] inserted into the DOM, not my rendered HTML.
This is the only way I can get it to work and it seems like a hack:
$("#detail").html('')
detailView = new RulesPanelView({model : #model})
$("#detail").append(detailView.render().el)
Having to empty the HTML of the DIV before rendering so I don't get multiple views rendered inside #detail which is what would happend with append.
Also aren't I creating way too many views this way, just seems cleaner to replace the HTML as in the first code segment?
What is the correct way to render this view?
What you want is to pass the already inserted DOM node to the view as a 'el' option to the constructor:
new RulesPanelView({el: $("#detail")});
This way, it won't render again. You still need to make sure your view's 'render' method will be able to render a correct view from an updated model, though.
The backbone documentation mentions this as a good way to avoid rendering too much stuff at once.
I actually append in the render method of the view. This doesn't work if you want to re-render when models change - but for that I've added a refresh method that render actually calls before appending. I then bind the refresh to the model change (if I need that). So in my View, I do this:
render: function(){
var markup = this.refresh();
$(markup).appendTo('#some-selector');
return this;
},
refresh: function(){
return $(this.el).html($.mustache(this.template, this.model.toJSON()));
},
Not sure if that's the "best", but I think it works pretty well. I've also seen where you have a collection bound to a view that loops through all of the models and renders "sub-views" of the collection view - this provides a nicer programmatic approach than hard-coding where you're going to append.