The following button click works only in page load, not after routing.
1) display default page
2) Click the "click here" button (This works)
3) Click some other button to route (without reload the page, clear the content, and create a new view give option to route back to the default page)
4) Click the option to move back to the default.
5) Without reload redraw the defult page again.
6) Click the "click here" button (This is not working)
Here is the code
var LPRouter = Backbone.Router.extend({
initialize: function (options) {
this.pageContent = $('#pageContent');
},
routes: {
'AA': 'routerMethod',
'': 'defaultMethod'
},
defaultMethod: function () {
//TODO: Set page title.
var self = this;
self.pageContent.empty();
if (!self.someView) {
self.someView = new SomeView();
self.someView.render();
}
self.pageContent.append(self.someView.el);
/*
Create a view with some link or button which will redirect to routerMethod.
*/
},
routerMethod : function() {
this.pageContent.empty();
/*
Create a view with some link or button which will redirect to defaultMethod.
*/
}
});
var SomeView = Backbone.View.extend({
tagName: "div",
className: "someclass",
initialize: function (options) {
},
hammerEvents: {
'tap .innerclass': 'someMethod'
},
someMethod: function (e) {
alert("did you click me?");
},
render: function () {
this.$el.append("<div class='innerclass'><input type='button' value='click here'/></div>");
return this;
}
});
Your render function will create a new button each time, so the event - which was bound to the original button element - is not bound to the new button element. You can use the delegateEvents() method to rebind the events hash - http://backbonejs.org/#View-delegateEvents
Related
Could anyone explain please how to remove events in order to prevent triggering duplication when clicking browser back button. Or is there any way to undelegate events when initalizing view again. Really stuck how to deal with it.
Pressing back button and then back again causes firing events for multiple times. When saving model form data for instance. Thank you.
var App = {};
// extending models, collections etc.
App.SamplesCollectionView = Backbone.View.extend({
el: '#samples',
template: _.template($('#sample-edit-template').html()),
events: {
'click a.sample-item': 'onEdit'
},
render: function(){
this.$el.append(this.template());
var $sample_list = this.$el.find('ul#sample-list');
this.collection.each(function(sample) {
var rendered = new App.CategoryView({model: sample}).render().el;
$sample_list.append(rendered);
});
},
onEdit: function(e) {
this.undelegateEvents();
// go to edit view
Backbone.history.navigate(e.target.getAttribute('href'), {trigger: true});
return false;
}
});
App.SampleEditView = Backbone.View.extend({
el: '#samples',
template: _.template($('#sample-edit-template').html()),
events: {
'click button.save': 'onSave',
'click button.cancel': 'onCancel',
},
render: function() {
this.$el.append(this.template(this.model.toJSON()));
return this;
},
onSave: function() {
this.undelegateEvents();
var data = Helpers.getFormData(this.$el.find('form'));
this.model.save(data);
// go back to index view
Backbone.history.navigate('/samples', {trigger: true});
return false;
}
});
App.SamplesRouter = Backbone.Router.extend({
routes: {
'samples': 'index',
'samples/edit/:id': 'edit'
},
index: function() {
App.samples = new App.SamplesCollection;
App.samplessView = new App.SamplesCollectionView({collection: App.samples});
},
edit: function(id) {
App.sampleEdit = new App.SampleEdit({id: id});
App.sampleEditView = new App.SampleEditView({model: App.sampleEdit})
}
});
App.samplesRouter = new App.SamplesRouter;
Backbone.history.start({pushState: true, hashChange: false});
The problem is that you have many views pointing to same element #samples. You can't remove one view because if you call view.remove() your other view's element is gone.
And as long as that that element exists in DOM, the view you thought to be gone will exist in memory since the shared element has event handlers referring the view instance.
If you want to delegate display functionality and edit functionality under same element, do it in same view using something like show/hide techniques without creating a new view instance.
Otherwise they should have it's own elements, you shouldn't have two view instances pointing to same element. While switching to a different view, make sure you call it's remove() method which removes the element from DOM and invokes undelegateEvents so that it get's garbage collected properly.
I have a view that contains two sub views, each with their own HTML template.
It works fine for what I have now. However, I now need an event to be fired from one subview to the other.
For example, when the user is in the Edit View, and they forget to click something(checkbox, radio button, or whatever), and they go back to the Display View, I want a warning to show up in that display view template(html) that warns them of the things they missed.
Is this possible? To pass events around like this between sibling views?
Thanks!
Here's the basic code structure I have now:
return Backbone.View.extend({
render: function(SubView) {
SubView = SubView || DisplayView;
this.view = new SubView({
model: this.model
});
this.$el.html(this.view.render().$el);
return this;
}
})
var DisplayView = Backbone.View.extend({
render: function() {
this.$el.html(this.template(this.model.toJSON()));
})
var EditView = Backbone.View.extend ({
toggleDisplay: function () {
this.checkAllItems();
},
checkAllItems: function() {
if (this.$('.engineParts').val().length > 0) {
this.render(DisplayView);
} else {
this.$('.awarning').css('display', 'block'); //warning class in DisplayView template.
this.render(DisplayView);
}
}
})
I would like to call a function when a "load" event is triggered:
events: {
"load #eventPicture" : "_resizeHeaderPic"
}
I don't want to do something like this.$("#eventPicture").on("load", _resizeHeaderPic); because I have a lot of views (it's a Single Page App) and I could go back to show another view before the image was loaded. So, if I then come back to this view I would have two listener for that "load" event. Right? By putting everything in my events hash, I can undelegate properly.
But it seems that "load #eventPicture" does not work. Any suggestion?
You cannot track load event from Backbone events because this event fires only on image instance and doesn't bubble. So Backbone.View's $el cannot track it.
jQuery callback on image load (even when the image is cached)
UPDATE
I would suggest to use another concept (JSFiddle). This is best practice:
var LayoutView = Backbone.View.extend({
el : '[data-container]',
show : function (view) {
// remove current view
this.$view && this.$view.remove();
// save link to the new view
this.$view = view;
// render new view and append to our element
this.$el.html(this.$view.render().el);
}
});
var ImageView = Backbone.View.extend({
template : _.template('<img src="https://fbcdn-sphotos-g-a.akamaihd.net/hphotos-ak-prn2/1375054_4823566966612_1010607077_n.jpg"/>'),
render : function () {
this.$el.html(this.template());
this.$('img').on('load', _.bind(this.onLoad, this));
return this;
},
onLoad : function () {
console.log('onLoad');
}
});
var OtherView = Backbone.View.extend({
template : _.template('lalala'),
render : function () {
this.$el.html(this.template());
return this;
}
});
var Router = Backbone.Router.extend({
routes : {
'other' : 'other',
'*any' : 'image'
},
initialize : function (options) {
this.layout = new LayoutView();
},
other : function () {
this.layout.show(new OtherView());
},
image : function () {
this.layout.show(new ImageView());
}
});
new Router();
Backbone.history.start();
We have researched and tried all we could find but cannot see why button click even is not firing. If we change the view render html color it shows the change, so view render is working okay but when the login button is clicked -> nothing. No error shows in js console. Tried with
button#login_button and #login_button and login_button - all nothing. what are we missing? thks for any help
SessionView = Backbone.View.extend({
el: ('#session'),
initialize:function () {
this.render();
},
render:function () {
if (!session) {
$(this.el).html(
"<button id=\"login_button\" class=\"login_button black\">"+
"Login"+
"</button>");
return this;
} else {
$(this.el).html(
"<button id=\"logout_button\" class=\"login_button black\">"+
"Logout</button>");
return this;
}
},
events: {
"click login_button" : "login",
"click logout_button": "logout"
},
login: function(){
alert("login");
console.log("login dialog");
//var loginView = new LoginView();
//loginView.render().showModal();
},
logout: function(){
alert("You Have Logged Out");
}
});
You should keep '#' in selectors:
events: {
"click #login_button" : "login",
"click #logout_button": "logout"
}
UPDATE
Do you wait for DOM ready to use new on this Backbone view class ?
The fact that your buttons are altered by the html calls means that you do.
As soon as you have checked that, you have to use this.$el instead of $(this.el).
If it still does not work, search for event blocking and be sure ids are unique.
This very simple JSFiddle works with the same conditions you are telling us.
I'm trying to prevent full page reloads using Backbone's pushState. When I call navigate() from my view's event, I see the messages marked // 1 below, but not // 2. In addition, when I try to open the same tab, the page reloads again.
Must I stop the event myself? I tried using jQuery's preventDefault(), which does prevent the page reload, but I haven't seen this documented anywhere.
Below is my current code:
App.Router = Backbone.Router.extend({
routes:{
"analytics":"analytics"
, "realtime":"realtime"
}
, analytics:function(page) {
console.log("analytics route hit: %o", page); // 2
}
, realtime:function(page) {
console.log("realtime route hit: %o", page); // 2
}
});
App.TabSetView = Backbone.View.extend({
initialize:function() {
this.collection.bind("reset", this.render, this);
this.collection.bind("add", this.render, this);
this.collection.bind("change", this.render, this);
this.collection.bind("remove", this.render, this);
}
, events:{
'click li.realtime a': "onRealtime"
, 'click li.analytics a': "onAnalytics"
}
, render:function() {
// omitted for brevity
}
, onAnalytics:function() {
console.log("onAnalytics"); // 1
if (this.collection.activateAnalytics()) {
App.app.navigate("analytics", true);
this.render();
console.log("navigated");
} else {
console.log("do nothing"); // 1
}
}
, onRealtime:function() {
console.log("onRealtime");
if (this.collection.activateRealtime()) {
App.app.navigate("realtime", true);
this.render();
console.log("navigated");
} else {
console.log("do nothing"); // 1
}
}
});
var tabs = ...; // omitted for brevity
var tabSetView = new App.TabSetView({collection: tabs});
var App.app = new App.Router;
Backbone.history.start({pushState:true});
to stop the page reload when a user clicks a link, you have to call e.preventDefault() like you were suggesting.
MyView = Backbone.View.extend({
events: {
"click .some a": "clicked"
},
clicked: function(e){
e.preventDefault();
// do your stuff here
}
});
you're also right that this isn't documented in the backbone docs. events are handled by jQuery, though. so you can assume that any valid jQuery things you would do - such as have an e parameter to an event callback - will work with backbone's events.
as for this:
in addition, when I try to open the same tab, the page reloads again.
are you saying when a user opens a new browser tab to your site's url? if so, then there's nothing you can do about this. when the browser opens the tab it makes the request to the server to load the page.
if you're referring to a "tab" as part of your site's user interface, though, then the use of e.preventDefault() on your link / "tab" clicks should take care of that.
The answer is actually in here https://stackoverflow.com/a/9331734/985383, if you enable pushState you want links to work and not prevent them as suggested above, or well, is not just preventing them. here it is:
initializeRouter: function () {
Backbone.history.start({ pushState: true });
$(document).on('click', 'a:not([data-bypass])', function (evt) {
var href = $(this).attr('href');
var protocol = this.protocol + '//';
if (href.slice(protocol.length) !== protocol) {
evt.preventDefault();
app.router.navigate(href, true);
}
});
}
$('a').click(function(e){
e.preventDefault();
Backbone.history.navigate(e.target.pathname, {trigger: true});
});
Just a follow up to Derick answer.
It worked for me, but to keep it clean, I overwrote the Backbone.View class:
(coffeescript)
class NewBackboneView extends Backbone.View
events:
'click a' : 'pushstateClick'
pushstateClick: (event) ->
event.preventDefault()
Backbone.View = NewBackboneView
So every link from my backbone views have the prevent default.
It depends on how you've generated the HTML mark-up. It looks like you're using anchor tags (<a>), so if those anchor tag href have values or even an empty string, then you need to cancel the default browser behavior otherwise you'll get a page reload. You can cancel the default behaviour using jQuery're event.preventDefault() like you mentioned. Alternatively, if you're not concerned about progressive enhancement or SEO, then you can set your anchor tag href to # or javascript:void(0);, which will also prevent to the page from reloading. e.g.
Click me
or
Click me