click event didn't work in backbone - backbone.js

define(["jquery" ,
"underscore" ,
"backbone" ,
"custom",
"wishlist"
],function($ , _ , Backbone, Ice, WishList){
var wishlist1 = new WishList();
var SavedCartView = Backbone.View.extend({
tagName:"div",
initialize : function(){
console.log("hi!");
},
event : {
'click .savedCart' : 'viewEachCart'
},
viewEachCart : function(e){
console.log(1);
},
render : function(){
wishlist1.viewAllSaveCart(106,function(output){
_.each(output,function(row){
$("#webbodycontainer").append('<a class="savedCart" id="'+row.ID+'">'+row.Name + "</a><br>");
});
});
}
});
return SavedCartView;
});
Here is the router :
app_router.on('route:savedCart', function( ){
if(window.currentSection)
window.currentSection.remove();
window.currentSection = new SavedCart({});
$("#webbodycontainer").html(window.currentSection.$el);
window.currentSection.render();
});
console.log(1); didn't work, Any idea what could be causing this. Thanks.

I think this should be events : {

If ".savedCart" is an anchor tag (like this):
Saved Cart
...then you need to have e.preventDefault() in your function so that the link is not activated.
events : {
'click a.savedCart' : 'viewEachCart'
},
viewEachCart : function(e){
e.preventDefault();
console.log(1);
},

Related

How to trigger events in backbone and marionette?

Here is my widget module:
app.module("Widget.MyWidget", function(MyWidget, app, Backbone, Marionette, $, _) {
this.on("start", function(el){
var that = this;
});
this.on("click", function(el){
alert("click");
});
this.renderWidget = function (id) {
var that = this;
var el = '#widget_' + id;
widgetModel = new app.Widget.MyWidget.models.meteo();
widgetModel.getWidget(id)
.fetch({ type: "GET" })
.done(function(){
var widgetView = new that.views.widgetView({model:widgetModel, el : el});
widgetView.render();
})
});
};
});
and here is the Marionette view:
define(['hbs!modules/myWidget'],function (template) {
var meteoView = Marionette.ItemView.extend({
template: template,
events: {
"dblclick" : "open",
"click" : "select",
"contextmenu .icon.doc" : "showMenu",
"click .show_notes" : "toggleNotes",
"click .title .lock" : "editAccessLevel",
"mouseover" : "showTooltip"
},
onShow: function(){
var view = this;
alert("showing");
}
});
return meteoView;
});
Everything works perfectly: the model is loaded and populated with data, the view is rendered on screen with the data.
But, I cannot set any event on the view: I would like to attach a click event to the view: What do I do wrong ?
regards
You must define your callbacks in your view :
var meteoView = Marionette.ItemView.extend({
....
open: function() {},
select: function() {},
showMenu: function() {},
toggleNotes: function() {},
editAccessLevel: function() {},
showTooltip: function() {},
....
}

Backbone.js model.save() fire a "too much recursion" error in underscore

I've got a problem trying to use backbone on saving my Model from a form. Here I want my my view to actually be an editing form:
(function() {
'use strict';
var YachtEditor = {};
window.YachtEditor = YachtEditor;
var template = function(name) {
return Mustache.compile($('#' + name + 'Template').html());
};
YachtEditor.Tank = Backbone.Model.extend({
defaults : {
dCapacity : "",
sType : ""
}
});
YachtEditor.Tanks = Backbone.Collection.extend({
// url: "/rest/tanks",
localStorage: new Store("tanks"),
model : YachtEditor.Tank
});
YachtEditor.TankView = Backbone.View.extend({
template: template("tank"),
events: {
'click .save' : 'save',
'click .remove' : 'remove'
},
initialize: function() {
console.log("initialize tank View :");
console.log(this.model.get("id"));
},
render: function() {
this.$el.html(this.template(this));
return this;
},
save: function() {
console.log('change');
var self = this;
var values = {
sType: self.$("#sType").val(),
dCapacity: self.$("#dCapacity").val()
};
console.log("dCapacity : " + values.dCapacity);
console.log("sType : " + values.sType);
this.model.save(values);
},
remove: function() {
this.model.destroy();
},
dCapacity : function() {
return this.model.get("dCapacity");
},
sType : function() {
return this.model.get("sType");
}
});
YachtEditor.TanksView = Backbone.View.extend({
el: $("div.tankZone"),
template: template("tanks"),
events: {
"click .add" : "addTank",
"click .clear" : "clear"
},
initialize: function() {
this.tanks = new YachtEditor.Tanks();
// this.tanks.on('all', this.render, this);
this.tanks.fetch();
this.render();
},
render: function() {
this.$el.html(this.template(this));
this.tanks.each(this.renderTank, this);
return this;
},
renderTank: function(tank) {
var view = new YachtEditor.TankView({model: tank});
$(".tanks").append(view.render().el);
return this;
},
addTank: function() {
this.tanks.create({});
this.render();
},
clear: function() {
this.tanks.each(function(tank) {
tank.destroy();
});
this.render();
}
});
...
})();
Here is the mustache template i use for each tank
<script id="tankTemplate" type="text/x-mustache-template">
<div class="tankView">
<h1>Tank</h1>
<select id="sType" value="{{ sType }}">
#for(option <- Tank.Type.values().toList) {
<option>#option.toString</option>
}
</select>
<input id="dCapacity" type="text" value="{{ dCapacity }}">
<button class="destroy">x</button>
</div>
</script>
My problem here is that this.model.save() triggers a 'too much recursion' in underscore. js. (chrome is displaying an error also.
Here is the call stack on error:
_.extend
_.clone
_.extend.toJSON
_.extend.save
_.extend.update
Backbone.sync
_.extend.sync
_.extend.save
YachtEditor.TankView.Backbone.View.extend.save
st.event.dispatch
y.handle
I suspect the save to recall the blur event but i cannot find a way to explicit it... Maybe I'm not using backbone as i should?
My problem, aside of some pointed out by Yurui Ray Zhang (thank you), was that I was using a backbone-localstorage.js from an example I found here : git://github.com/ngauthier/intro-to-backbone-js.git
The "too much recursion error" stopped to appear as soon a I replaced it with a storage I found here : https://github.com/jeromegn/Backbone.localStorage
a few things. you defined your tank model as
app.Tank = ...
but in your collection you are referencing it as:
model : YachtEditor.Tank
and in your view, you are trying to assign elements before they are rendered on the page:
this.input = {}
this.input.sType = this.$("#sType");
this.input.dCapacity = this.$("#dCapacity");
I'm not sure how your view is rendered to the page, some people, like me, like to use render() to render the template directly to the page:
render: function() {
this.$el.html(this.template(this));
//done, you should be able to see the form on the page now.
},
some others, will use something else to insert the el, eg:
//in another view
tankView.render().$el.appendTo('body');
but either way, if you want to cache your elements, you need to do it after they are rendered to the page, not in initialize.
//this method is only called after render() is called!
cacheElements: function() {
this.input = {}
this.input.sType = this.$("#sType");
this.input.dCapacity = this.$("#dCapacity");
}
I'd suggest, first, try to fix this things, and then, try to add some console log or debuggers in your readForm method to see if the values are grabbed correctly:
readForm: function() {
var input = this.input;
console.log(input.sType.val());
console.log(input.dCapacity.val());
this.model.save({
sType: input.sType.val(),
dCapacity: input.dCapacity.val()
});
},

event in itemView trigger show CompositeView

In formView inside the newAttributes method i would like to trigger an event that will then show the entry view(both are shown here). In order to do this should I use a controller? It seems no matter how I set the trigger and listenTo commands up I get a different error.
MyApp = new Backbone.Marionette.Application();
MyApp.addRegions({
formBox : '#formBox',
listBox : '#listBox'
});
Entry = Backbone.Model.extend({
defaults: {
DB : 'not specified',
CT : 'not specified',
go : 'not specified'
},
});
EntryList = Backbone.Collection.extend({
model: Entry
});
FormView = Backbone.Marionette.ItemView.extend({
tagName: 'div',
template: '#form-template',
className: 'form',
ui: {
DB: '#DB',
CT: '#CT',
gos: '#gos'
},
events:{
'click #processInput' : 'validateForm'
},
onRender: function(){
console.log("rendering...");
},
validateForm : function(){
var validInput = true;
if(!this.ui.DB.val().trim())
{
validInput = false;
!this.ui.DB.css("background-color","#CC0000");
}
else
{
!this.ui.DB.css("background-color","#ffffff");
}
if(!this.ui.CT.val().trim())
{
validInput = false;
this.ui.CT.css("background-color","#CC0000");
}
else
{
this.ui.CT.css("background-color","#ffffff");
}
if(!this.ui.gos.val().trim())
{
validInput = false;
this.ui.gos.css("background-color","#CC0000");
}
else
{
this.ui.gos.css("background-color","#ffffff");
}
if(validInput)
{
this.hideForm();
this.getEntries(this.ui.DB.val().trim(),
this.ui.CT.val().trim(),this.ui.gos.val().trim());
}
},
newAttributes :function(db,con,gos){
for(go in gos)
{
MyApp.ents.add({DB : db, CT: con, go: gos[go]});
}
//TRIGGER CUSTOM EVENT
},
getEntries : function(db,con,gos){
var gos = gos.split("\n");
for(go in gos)
{
console.log("data bank: " + db + " CT: " + con + " go: |" + gos[go].trim()+"|");
}
this.newAttributes(db,con,gos);
},
hideForm : function(){
this.$el.hide();
}
});
EntryView = Backbone.Marionette.ItemView.extend({
tagName: 'tr',
template: '#entry-template',
className: 'entry',
events: {
'click .delete' : 'destroy'
},
destroy : function(){
this.model.destroy();
}
});
EntriesView = Backbone.Marionette.CompositeView.extend({
tagName: 'table',
template: '#entries-template',
itemView: EntryView,
appendHtml: function(collectionView, itemView){
colleCTionView.$('tbody').append(itemView.el);
}
});
MyApp.addInitializer(function(test){
var entriesView = new EntriesView({
collection: test.DB
});
var formView = new FormView();
//SHOW on load
MyApp.formBox.show(formView);
//SHOW Later with custom event
MyApp.listBox.show(entriesView)
});
$(document).ready(function(){
MyApp.ents = new EntryList([]);
MyApp.start({DB: MyApp.ents});
});
There are 2 ways you can solve your problem.
The cleanest way is to use a controller, and listen for the triggered event on the view instance within the controller. E.g. in your view, have this.trigger("my:event"), then in your controller have myFormViewInstance.on("my:event", function(...){...})
Another option (which wouldn't really be recommended), is to trigger the event at the application level, and listen for it elsewhere also on the application level. I.e. MyApp.trigger("my:event") and MyApp.listen("my:event")

Custom event in backbone.js without bind

I am looking for a better way to implement a PubSub type functionality with Backbone. I currently am achieving it this, but am looking for a better way to go about it.
Sample Routing Code
var AppRouter = Backbone.Router.extend({
customEvents: _.extend({}, Backbone.Events),
routes: {
"login": "login"
},
//Injecting custom event
login: function(){
this.before(function () {
app.showView('#Content', new LoginView({customEvents:this.customEvents}), "login", true);
});
},
//Injecting custom event
otherView: function(){
this.before(function () {
app.showView('#Header', new OtherView({customEvents:this.customEvents}), "login", true);
});
},
..... more code
});
Sample View code. Notice that I am using bind to listen for customEvent. This works fine, but looking for alternative method
LoginView = Backbone.View.extend({
initialize: function(options){
this.customEvents = options.customEvents;
this.customEvents.bind('app:loggedin', this.loggedIn);
},
loggedIn: function(){
console.log("LOG CHANGED");
},
...more code
I'd much rather keep my events with the rest of the events that I am using in my View. Not sure how to achieve this. Should I be extending the View class?
What I'd like to do on my Views
LoginView = Backbone.View.extend({
events: {
"app:loggin" : "loggedIn"
},
loggedIn: function(){
console.log("LOG CHANGED");
},
...more code
Gist : https://gist.github.com/vermilion1/5600032
Demo : http://jsfiddle.net/vpetrychuk/3NVG9/1
Code preview :
// -- BASE VIEW ------------------
var BaseView = Backbone.View.extend({
constructor : function (options) {
Backbone.View.prototype.constructor.apply(this, arguments);
this._eventAggregator = options && options.eventAggregator || this;
this._parseTriggers();
},
_parseTriggers : function () {
_.each(this.triggers || {}, function (fn, event) {
var handler = this[fn];
if (_.isFunction(handler)) {
this.listenTo(this._eventAggregator, event, handler);
}
},
this);
}
});
// -- TEST ------------------
var View = BaseView.extend({
triggers : {
'hello' : 'helloHandler',
'bye' : 'byeHandler'
},
helloHandler : function (name) {
console.log('hello, ' + name);
},
byeHandler : function (name) {
console.log('bye, ' + name);
}
});
var view1 = new View();
view1.trigger('hello', 'dude 1'); // -> hello, dude 1
view1.trigger('bye', 'dude 1'); // -> bye, dude 1
var vent = _.extend({}, Backbone.Events);
var view2 = new View({eventAggregator:vent});
vent.trigger('hello', 'dude 2'); // -> hello, dude 2
vent.trigger('bye', 'dude 2'); // -> bye, dude 2

Backbone, one field not set when calling view.render after model.save

I have the following problem. On a user-event (click on .twitterDefault) I call save event with
twitter : {
handle : handle,
ignore : false
}
Then the success function gets called and I set fields on the model (klout, twitter and tester). All fields are set (logging statements all print out appropiate objects.
However, then I call view.render() and here twitter is not set anymore. I have no idea why, there is no sync happening after the save so twitter does not get overwritten (additionally I made sure twitter is also saved on the server before the success method gets called).
Any help greatly appreciated!
Code as follows (stripped to improve readability)
$(function() {
var ContactModel,
ContactModelCollection,
ContactView,
ContactCollectionView,
contacts,
contactCollectionView;
//base model
ContactModel = Backbone.Model.extend({
defaults : {
},
initialize : function() {
}
});
ContactModelCollection = Backbone.Collection.extend({
model : ContactModel,
url : '/api/contacts',
comparator : function(contact) {
return contact.get('strength_of_relationship');
},
initialize : function() {
}
});
ContactView = Backbone.View.extend({
tagName : 'li', //attempting to create a new element
render: function() {
var compiled_tmpl = _.template($('#contact-template').html());
var html = compiled_tmpl(this.model.toJSON());
console.log('model.get("twitter")=('+JSON.stringify(this.model.get('twitter)'))+')');
console.log('model.get("klout")=('+JSON.stringify(this.model.get('klout'))+')');
console.log('model.get("tester")=('+JSON.stringify(this.model.get('tester'))+')');
this.$el.html(html);
console.log('rendered view successfully)');
return this;
},
initialize: function() {
console.log('contactView initalized');
this.model.bind('change', this.render, this);
this.model.bind('destroy', this.remove, this);
},
events: {
'click .twitterDefault' : 'assignDefaultTwitterHandle',
},
assignDefaultTwitterHandle : function(event) {
var handle = $(event.currentTarget).data('twitter');
this.assignTwitterHandle(handle);
},
assignTwitterHandle : function(handle) {
console.log('model assignTwitterHandle. handle='+handle+')');
var view = this,
model = view.model;
model.save({
twitter : {
handle : handle,
ignore : false
},
id : model.get('id')
}, {
error : function() {
console.log('saving twitter handle failed');
},
success : function(model, response) {
console.log('response=('+JSON.stringify(response)+')');
if(response.error) {
console.log('error on server ='+response.error);
}
if(response.twitter) {
console.log('twitter is set');
var twitter = {
handle : handle,
tweet : response.twitter,
age : new Date()
};
console.log('setting twitter to '+JSON.stringify(twitter));
model.set('twitter', twitter);
model.set('tester', 'tester');
console.log('twitter after setting it = '+JSON.stringify(model.get('twitter')));
console.log('view\'s model twitter after setting it = '+JSON.stringify(view.model.get('twitter')));
}
if(response.klout) {
console.log('klout is set');
var klout = {
topics : response.klout
}
console.log('setting klout to '+JSON.stringify(klout));
model.set('klout', klout);
}
if(response.twitter || response.klout) {
console.log('Rerendering view after setting klout/twitter');
view.render();
}
}
});
}
});
contacts = new ContactModelCollection;
ContactCollectionView = Backbone.View.extend({
el : $('#suggestions-list'),
initialize : function(){
contacts.bind('add', this.addOne, this);
contacts.bind('reset', this.addAll, this);
contacts.bind('all', this.render, this);
},
render : function(){
console.log('contactcollectionview render');
},
addOne : function(contact) {
console.log('addOne');
var view = new ContactView({model: contact});
var el = view.render().el;
console.log('el=('+el+')');
$('#suggestions-list').append(el);
},
addAll : function() {
console.log('addAll');
contacts.each(this.addOne);
}
});
contactCollectionView = new ContactCollectionView;
App.contacts = contacts;
App.contactCollectionView = contactCollectionView; });
I guess the problem is the scope of the render function.
Depending from where is called, this takes a different value.
To warranty that always this is pointing to the View scope, add to your initialize:
_.bindAll(this,"render");
Also, it's not good habit to call view.render manually. You should let the events do their work. Model save already triggers some events. Just listen to them to update your View.

Resources