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")
Related
I'm facing a very weird problem using ListenTo on subviews.
Basically I've a main view that contains multiple subviews.
Those subviews are initialized when their parent view is also initialized.
Some of those subviews are listening to the same global collection.
One of those views is a form that allow me to insert new entries into the collection
The second one is a list of all those entries contained into the collection.
So, basically it looks like this
$('button').on('click'....
app.views.MyMainView = new MyMainView()
//launch new modal window
//with html body = app.views.MyMainView.el
var MyMainView = Backbone.view.extend({
initialize: function(){
// new Form_View()
// new List_View()
}
});
var Form_View = Backbone.view.extend({
//get input value
// create new entrie into the collection
});
var List_View = Backbone.view.extend({
initialize: function(){
// listenTo(this.collection, 'add', this.addOne)
// new List_View()
this.addAll();
},
addAll: function(){...},
addOne: function(model){ .... }
});
The problem is the following:
When the user launch the modal for the first time and we add a new entry to the collection
the listenTo add on the List_View fires as expected.
If I close the modal and the user clicks on 'button' to launch the modal window once again if I add a new entrie the view will fire 2 times the function that is listening to the collection add event.
If i close the modal again and re-open it the function will fire 3 times and so on.
This is weird because I'm creating a new instance of the view and their subviews everytime the user clicks on the 'button'. That's why it doesn't make sense to me.
Any help?
EDIT
I also checked my collection by listening to the 'add' event inside of it.
var MyCollection = Backbone.View.extend({
initialize: function(){
this.listenTo( this, 'add', doSomething );
},
doSomething: function( model ){
//do something fires as it should be firing the event: 1 time per each item inserted
}
})
EDIT 2
var MyMainView = Backbone.View.extend({
.....
close_modal: function(e){
if(e){
e.preventDefault();
}
var viewsLen = this.views.length,
_that = this;
_.each(this.views, function(view, key){
view.remove();
if(key + 1 == viewsLen )
_that.dialog.close();
})
}
...
})
EDIT 3:
ALL CODE
//initialize modal
$('button').on('click', function(){
app.views.storePayment_View = new StorePayment_View();
})
var TMPL_StorePayment = '<div class="store-payment">'
+ '<div class="store-payment-header">'
+ '<div class="client"></div>'
+ '<div class="status"></div>'
+ '</div>'
+ '<div class="payment-form"></div>'
+ '<div class="payment-list"></div>'
+ '<div class="x-form-actions">'
+ '</div>'
+ '</div>';
var StorePayment_View = Backbone.View.extend({
views: {},
wrappers: {},
collections: {},
events: {
"click .back": "close_modal",
"click .finish-payment": "finish_payment"
},
initialize: function(){
var _that = this;
this.dialog = new BootstrapDialog({
title: appLang["h67"],
message: this.$el,
closable: true,
onhide: function(dialogRef){
_that.remove();
}
});
this.dialog.realize();
this.dialog.getModalFooter().hide();
this.dialog.open();
this.$el.html('').append( TMPL_StorePayment );
this.wrappers.$client = this.$el.find('.client');
this.wrappers.$status = this.$el.find('.status');
this.wrappers.$payment_form = this.$el.find('.payment-form');
this.wrappers.$payment_list = this.$el.find('.payment-list');
this.wrappers.$form_actions = this.$el.find('.x-form-actions');
this.render()
},
render: function(){
this.views.StorePaymentForm_View = new StorePaymentForm_View();
this.wrappers.$payment_form.html('').append( this.views.StorePaymentForm_View.el );
this.views.StorePaymentList_View = new StorePaymentList_View();
this.wrappers.$payment_list.html('').append( this.views.StorePaymentList_View.el );
},
close_modal: function(e){
if(e){
e.preventDefault();
}
var viewsLen = this.views.length,
_that = this;
_.each(this.views, function(view, key){
view.remove();
if(key + 1 == viewsLen ){
_that.dialog.close();
}
})
}
})
var StorePaymentForm_View = Backbone.View.extend({
error_tmpl: _.template('<div class="alert alert-warning"><%= message %></div>') ,
template: _.template('<div> <div class="input-field"> <input type="text" class="montant form-control" value="<%= restant %>"> </div> <div class="input-select"> <select name="payment-type"><% _.each(paymentTypeList, function(paymentType){ %> <option value="<%= paymentType.typeMode %>"><%= paymentType.libelle %></option> <% }) %></select> </div> <div class="actions">Add newRemove All </div></div><div class="error_placeholder"></div>'),
events:{
"click .add-new": "add_new",
"click .remove-all": "remove_all"
},
initialize: function(){
this.collection = app.collections.StorePaymentList;
this.listenTo( this.collection, 'add', this.render )
this.listenTo( this.collection, 'destroy', this.render )
this.listenTo( this.collection, 'change', this.render )
this.listenTo( this.collection, 'reset', this.render )
this.render()
},
render: function(){
console.log("RENDER FIRED ON STOREPAYMENTFORM")
var restant = this.collection.getRestant();
if ( restant <= 0){
restant = 0;
}
this.$el.html('').append( this.template( { "restant" : restant, "paymentTypeList": app.collections.PaymentTypeList.toJSON() } ) )
var _that = this;
setTimeout(function(){
_that.$el.find('select').selectBoxIt({
native: true,
autoWidth: false
})
_that.$el.find('input').focus();
}, 50 )
},
add_new: function(e){
console.log("add_new");
if(e){
e.preventDefault();
}
var _that = this,
input_val = this.$el.find('input').val(),
select_val = this.$el.find('select :selected').val(),
libelle = this.$el.find('select :selected').text(),
wasNaN = false;
input_val = parseInt(input_val);
if (isNaN(input_val)){
wasNaN = true;
input_val = 0;
}
if (wasNaN){
_that.$el.find('.error_placeholder').html( _that.error_tmpl( { "message": appLang["h69"] } ) );
} else {
if ( input_val <= 0 ){
_that.$el.find('.error_placeholder').html( _that.error_tmpl( { "message": appLang["h70"] } ) );
} else {
this.collection.add( new StorePaymentModel( { "libelle": libelle , "paymentId": select_val, "montant": input_val } ) )
}
}
},
remove_all: function(e){
if(e){
e.preventDefault();
}
var _that = this;
//dialog are you sure?
var dialog = new BootstrapDialog({
title: "Do you want to continue",
message: "Do you really want to empty your current list of payments?",
buttons: [{
label: appLang["a187"], //cancel
action: function(dialog) {
dialog.close();
}
}, {
label: appLang["a1621"], //ok
cssClass: 'btn-primary',
action: function(dialog) {
_that.collection.reset([]);
dialog.close();
}
}]
})
dialog.realize();
dialog.open();
}
})
var StorePaymentListItem_View = Backbone.View.extend({
events:{
"click .remove": "remove_item",
"click .save": "save"
},
template: _.template( '<%= libelle %> <%= montant %> <i class="fa fa-trash-o"></i>' ),
tagName: 'li',
className: 'list-group-item',
initialize: function(){
this.render()
//console.log("StorePaymentListItem_View initialized")
this.listenTo( this.model, 'hide', this.remove )
},
render: function(){
this.$el.html('').append( this.template( this.model.toJSON() ) )
},
edit: function(){
this.render_edit();
},
save: function(e){
if (e){
e.preventDefault(); e.stopPropagation();
}
this.render();
},
remove_item: function(e){
if (e){
e.preventDefault(); e.stopPropagation();
}
this.model.destroy();
}
})
var StorePaymentList_View = Backbone.View.extend({
$wrapper: $('<ul />', {'class': 'list-group' }),
initialize: function(){
this.$el.html('');
this.collection = app.collections.StorePaymentList;
this.listenTo( this.collection , 'add', this.addOne );
this.listenTo( this.collection , 'change', this.render );
this.listenTo( this.collection , 'reset', this.render );
this.render()
},
render: function(){
var totalItems = this.collection.length;
this.$wrapper.html('')
if (totalItems == 0){
this.appendToRoot();
} else {
this.addAll()
}
},
addAll:function(){
var _that = this,
totalItems = this.collection.length;
this.collection.forEach(function(model, key){
_that.addOne(model)
if (totalItems == key + 1)
_that.appendToRoot();
})
},
addOne:function( model ){
var storePaymentListItem_View = new StorePaymentListItem_View({ model: model });
this.$wrapper.append( storePaymentListItem_View.el );
},
appendToRoot:function(){
this.$el.html('').append( this.$wrapper );
}
})
My guess is closing the modal doesn't call view.remove, and so we end up with a zombie view that is removed from the DOM but still lives in memory listening to events.
Another guess is you never remove the List_View instance, so they are the zombies.
In other words, this is likely to be related to insufficient garbage collection.
This is a guess - it's impossible to tell without seeing the relevant parts of the code.
I have the problem that the event "form:selectedForm" is calling the method "showForm" but when sending this to my view I am getting the following error: TypeError: e[t] is not a function.
This is stated in line 128 in the backbone.js script but I have no clue what he is doing there. It looks like that he is looking for a "to" or "on" event on the collection.
What I am doing wrong here?
MyController = Backbone.Marionette.Controller.extend({
initialize: function(options) {
this.options = options;
this.urls = options.urls;
this.mainRegion = options.mainRegion;
this.view = new MyLayout();
this.mainRegion.show(this.view);
this.view.render();
this.showSelectorView(this.view.formHeader);
},
showSelectorView : function(view) {
var forms = new MyForms();
forms = this.urls.loadForms;
var selectorView = new FormSelectorView({
collection: forms
});
forms.fetch();
this.listenTo(selectorView, "form:selectedForm", this.showForm);
view.show(selectorView);
},
showForm : function(models) {
console.log("showForm");
var form = new FormContentView({
collection: models
});
this.view.form.show(form);
}
});
MyLayout = Backbone.Marionette.Layout.extend({
template: Backbone.Marionette.TemplateCache.get('#content'),
regions: {
formHeader: "#selector",
form: "#formContent",
formContent: "#content",
formFooter: "#save",
formTemplates: "#templates"
}
});
FormSelectorView = Backbone.Marionette.ItemView.extend({
template: Backbone.Marionette.TemplateCache.get('form-selector-template'),
events : {
"click option" : "selectForm"
},
initialize : function() {
this.listenTo(this.collection, "sync", this.render, this);
},
selectForm : function(e) {
e.preventDefault();
var id = $(e.currentTarget).attr("name");
var item = this.collection.get(id);
this.trigger("form:selectedForm", item.attributes.fields);
}
});
I think the error is in your showSelector view function, you are overwriting your forms collection in the second line,
i think your intention in that line was to assing the url of the forms collection so my guess is that this will fix it:
showSelectorView : function(view) {
var forms = new MyForms();
forms.url = this.urls.loadForms; /// Im assuming you were trying to pass the url here
var selectorView = new FormSelectorView({
collection: forms
});
forms.fetch();
this.listenTo(selectorView, "form:selectedForm", this.showForm);
view.show(selectorView);
},
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
I am developing a Backbone web application and I want to know that how can post data from the view
This is my Model:
App.Models.edit = Backbone.Model.extend({
defaults : {
id : undefined,
fname : undefined,
lname : undefined,
phone : undefined,
address : undefined,
email : undefined,
url: undefined,
},
initialize: function(){
this.set({url : '../api/getOne/' + App.CurrentID });
},
getAttrs: function(attr){
return this.get(attr);
}
});
And this is my view:
App.Views.edit = Backbone.View.extend({
el: $("#modal"),
initialize: function(){
App.TplNames.edit = $('body');
App.Tpls.edit('edit');
this.model.bind("reset", this.render, this);
this.render();
},
events: {
'click .btnSave': 'saveDetails',
},
saveDetails: function(){
this.model.save();
//console.log(this.model);
},
render: function(){
var elem = '';
$.each(this.model.models, function(i, k){
var template = _.template( $('#tpl_edit').html(), k.toJSON() );
elem += template;
});
$(this.el).html(elem);
$("#myModal").modal('show');
$("#myModal").on('hidden', function(){
//alert(123);
document.location.href = App.Url + '#view';
});
var attrs = "";
$.each(this.model.models, function(i, k){
attrs = k.toJSON();
});
$("#fname").val(attrs.fname);
$("#lname").val(attrs.lname);
$("#Email").val(attrs.email);
$("#Phone").val(attrs.phone);
$("#Address").val(attrs.address);
//console.log(attrs);
}
});
And it is my Router
App.Router = Backbone.Router.extend({
routes: {
"" : "def",
"home" : "def",
"view" : "getAll",
"edit/:id" : "edit",
"add" : "addContact",
},
def: function(){
this.mainModel = new App.Collections.Main();
this.mainView = new App.Views.Main({model: this.mainModel});
//this.mainModel.fetch();
},
edit: function(id){
App.CurrentID = id;
var contactCollObj = new App.Collections.edit();
viewObj = new App.Views.edit({model: contactCollObj});
contactCollObj.fetch();
viewObj.render();
//console.log(contactCollObj);
},
getAll: function(){
//alert(123);
var collObj = new App.Collections.View();
var viewObj = new App.Views.View({model: collObj});
collObj.fetch();
},
addContact: function(){
//var model = new App.Collections.AddContact();
model = new App.Models.AddContact();
var view = new App.Views.AddContact({model: model});
//view.render();
}
});
var app = new App.Router();
Backbone.history.start();
And when I want to save the model, It generates an error:
this.model.save is not a function
Every thing is okay except the above...
In your router you pass collection to App.Collections.edit view as model:
var contactCollObj = new App.Collections.edit();
viewObj = new App.Views.edit({model: contactCollObj});
That is why you cannot call save(). save() is only available for a model not a collection.
You probably want to initialize view with collection
viewObj = new App.Views.edit({collection: contactCollObj});
And then also modify some of your view code accordingly.
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.