I have a Backbone app that is working properly, however, when I tried to reorganize the code under a namespace I can't get it to do anything. I can't even trigger events (by clicking on ids) for views that I know are getting initialized (through console log messages), so I'm wondering if I've introduced some fundamental flaw somehow. I'm following a pattern set out by this blog (in french) http://www.atinux.fr/2011/12/10/organiser-son-code-backbone-js-en-modules/
In the main application.js (see below), I instantiate all of the views and models after initiating the app on document ready. One change introduced as a result of creating this namespace was setting the models for the views with this.models.game
this.views.clock_view = new this.Views.clockView({ model: this.models.game});
Inside the modules folder, I had a views.js and a models.js. I created each view and object like this, prefaced with app.Views or app.Models accordingly
app.Views.announceView = Backbone.View.extend({
....
app.Views.optionsView = Backbone.View.extend({
...
This app.Views.optionsView is getting initialized (according to a console.log statement in the initializer) but when I click on #new_game, the console.log in the startNewGame is not getting triggered
'click #new_game': 'startNewGame'
// 'click .action_button': 'startNewGame'
},
startNewGame: function() {
console.log("startNewGame");
this.model.new();
},
As a result of the namespacing, one other key change I made was when I created new views inside one of the other views. Under the previous (non-namespaced app), I created individual question items from a QuestionListView
var view = new QuestionListItemView({ model: game });
but now I'm doing
var view = new app.Views.questionListItemView({ model: app.models.game })
because the instance of the model was saved to this.models.game in application.js, however, I also tried using 'this.models.game'
var view = new app.Views.questionListItemView({ model: this.models.game })
Either way, before the games model gets involved, I can't trigger the startNewGame function outlined above, so it's not solely an issue of how to identify the model.
I also wondered whether i should be using this.Views or app.Views after the 'new' when creating new views from within
var view = new app.Views.questionListItemView({ model: this.models.game })
I'd be grateful if you could help me identify any flaws I've introduced.
application.js
var app = {
// Classes
Collections: {},
Models: {},
Views: {},
// Instances
collections: {},
models: {},
views: {},
init: function () {
this.models.game = new this.Models.game();
this.views.story_view = new this.Views.storyView(); #doesn't have a model
this.views.clock_view = new this.Views.clockView({ model: this.models.game});
this.views.field_view = new this.Views.fieldView({ model: this.models.game});
this.views.options_view = new this.Views.optionsView({ model : this.models.game});
this.views.announcement_view = new this.Views.announceView({ model: this.models.game});
this.views.question_list_view = new this.Views.questionListView({ model : this.models.game});
this.views.question_list_item_view = new this.Views.questionListItemView({ model : this.models.game});
}
};
$(document).ready(function () {
app.init();
}) ;
The options view is getting initialized but I can't trigger the startNewGame function when I click that #id
app.Views.optionsView = Backbone.View.extend({
// el: $("#options"),
el: $("#options"),
initialize: function() {
console.log("app views OptionsView initialized");
// this.model.bind("gameStartedEvent", this.removeGetAnswerButton, this);
this.model.bind("gameStartedEvent", this.disableNewGame, this);
},
events: {
'click #demo': 'startDemo',
'click #new_game': 'startNewGame'
// 'click .action_button': 'startNewGame'
},
startDemo: function(){
console.log("start demo");
this.model.demo();
},
startNewGame: function() {
console.log("startNewGame");
this.model.new();
},
disableNewGame: function(){
$('#new_game').attr("disabled", true);
}
});
Update
My file structure looks like this
<%= javascript_include_tag 'application.js'%>
<%= javascript_include_tag 'modules/models'%>
<%= javascript_include_tag 'modules/views'%>
At the top of the views and models file, I just do something like this
app.Views.optionsView = Backbone.View.extend({
ie.. there is no further document ready. In fact, including another document ready in these files breaks the application.js init
Prior to using the namespace, I defined the element this way in the view
el: $("#options")
which, as was pointed out in the comments to this question, is not the ideal way to do it(see #muistooshort comment below), (even though it worked).
Defining the el this way instead
el: '#options'
got it working, and let Backbone "convert it to a node object" on its own.
Related
This is my second day trying to use backbone and im completely lost. I am following this tutorial - http://net.tutsplus.com/tutorials/javascript-ajax/build-a-contacts-manager-using-backbone-js-part-3/
What I have done is loaded a contacts list and rendered it to the screen, but if you look at my render1 function - this takes a form input and appends it to my template. The problem is that I can't delete these items after they are created - the others can be deleted. help please?
var ContactView = Backbone.View.extend({
tagName: "contacts",
className: "contact-container",
template: $("#contactTemplate").html(),
initialize: function(){
this.model.on('change', this.render, this);
this.model.on('add', this.render1, this);
this.model.on('destroy', this.remove, this);
},
events: {
'click .deleteUser': 'delete'
},
test: function () {
alert("here");
},
delete: function () {
this.model.destroy();
},
render: function () {
console.log(this);
var tmpl = _.template(this.template);
$(this.el).html(tmpl(this.model.toJSON()));
temp = tmpl(this.model.toJSON());
console.log(temp);
return this;
},
render1: function () {
console.log(this);
var tmpl = _.template(this.template);
temp = tmpl(this.model.toJSON());
temp='<contacts class="contact-container">'+temp+'</contacts>';
console.log(temp);
$("#contacts").append(temp);
$(this.el).html(tmpl(this.model.toJSON()));
return this;
}
});
var AddPerson = Backbone.View.extend({
el: $("#addPerson"),
// el: $("form/"),
events: {
'click': 'submit',
'submit': 'submit'
},
submit: function(e) {
// alert("here");
e.preventDefault();
this.collection = new Directory();
// var data = (contacts[0]);
var contact = new Contact(contacts[0]);
var contactView = new ContactView({model: contact});
this.collection.add(contact);
this.collection.add(contactView);
}
});
seasick, there are quite a few issues in this code.
var contact = new Contact(contacts[0]);
var contactView = new ContactView({model: contact});
this.collection.add(contact);
this.collection.add(contactView);
Contact is a Backbone.Model but ContactView is a Backbone.View. Yet, you are adding both to the this.collection (which I assume is a Backbone.Collection of Contact?). See the problem here? In Backbone, there is no such concept of a 'collection of views'. You just get one concept: views, that are tied to a model.
So, here, you create a Contact and you add it to the Collection. That is all! It takes care of the Model part. The rendering part needs to be handled with events and renders.
When you add a model to a collection (this.collection.add(contact)), the collection will trigger a 'add' event, that you can hook to with a .on to create a new ContactView and append it to the DOM somewhere.
So when you write this...
this.model.on('add', this.render1, this);
You are actually saying 'When the Contact model triggers an add event, run render1', which isn't what you want, what you probably want is a collection.on('add', ...). The model will never trigger an add event (well, you could make it trigger one, but it wouldn't be an expected behavior!), the add/remove events are at the collection level.
In other words, you are missing some binding on the collection in your AddPerson view to 'react' to adding a new Contact to the collection. The code of the function bound to the add event should probably look a bit like:
onAdd: function(newContact){
var newContactView = new ContactView({model: newContact});
$("#contacts").append(newContactView.render().el);
}
There are other issues in your code, but I guess an outline of the steps to take would be like:
Remove the binding to add in ContactView: ContactView is only concerned with one contact, not how to manage multiple contacts. This is probably why you are having issues with only some (the first one?) of the contacts 'working'
Move that logic to the AddContact view which seems to be more concerned with the Collection of contacts. Use the collection 'add' event to create new ContactView and append them to the DOM
Hope this helps!
Shouldn't a closed Marionette view re-delegate the defined events (events, modelEvents, CollectionEvents) when rendering again?
It seems as if I have to manually call delegateEvents after closing and re-rendering a view. Otherwise the view won't work as expected.
http://jsfiddle.net/4DCeY/
var app = new Marionette.Application();
app.addRegions({
main: '.main'
});
var MyView = Marionette.ItemView.extend({
template: _.template('Hi, I\'m a view! Foo is: <%= foo %>'),
modelEvents: {
'change': 'onChange'
},
onChange: function() {
alert('change!');
}
});
var Model = Backbone.Model.extend({});
app.addInitializer(function() {
var m = new Model({foo: 'bar'});
var myView = new MyView({
model: m
});
app.main.show(myView);
myView.close();
app.main.show(myView);
m.set({foo: 'baz'});
});
$(document).ready(function(){
app.start();
});
If I understand your question right, there are multiple open github issues about this.
For example:
https://github.com/marionettejs/backbone.marionette/pull/654
https://github.com/marionettejs/backbone.marionette/issues/622
Last time I checked, Derick (the creator of Marionette) didn't feel like reusing closed views should be something regions should do.
So you could
simply create a new view and show that one
manually call delegateEvents - but there was an issue with multiple event bindings that I can't remember right now, so be careful about that one (not at work right now, so can't take a peek at the code, sorry)
write your own region manager
or wait and see if Derick will merge one of the pull requests
a couple of points:
You do not need to call myView.close() Marionette Region will take care of that when you show another view
Marionette.Region will not replace the same view with itself. It will just skip the redundant procedure if you want to test this correctly you need 2 views
If you want a change in model to invoke render you must explicitly write it
I altered the jsfiddle with the following things:
added myView1 and myView2
removed explicit call to myView.close
added a call for this.render() from the onChange function
Here is the corrected jsfiddle http://jsfiddle.net/4DCeY/1/ :
app.addInitializer(function() {
var m = new Model({foo: 'bar'});
var myView1 = new MyView({
model: m
});
var myView2 = new MyView({
model: m
});
app.main.show(myView1);
app.main.show(myView2);
m.set({foo: 'baz'});
});
And:
onChange: function() {
alert('change!');
this.render();
}
i'm building a Backbone/Marionette application to list different sets of cards. the layout has an ItemView on the left side including an input field to add a new set and a CompositeView on the right side to list the card sets.
Cards.module("Set.SideBar", function(SideBar, App) {
SideBar.SideBarView = Backbone.Marionette.ItemView.extend({
template: "#set-sideBar",
className: "well sidebar-nav",
ui: {
saveBtn: "a.saveSet",
setName: "input[type=text]"
},
events: {
"click .saveSet": "saveSet"
},
saveSet: function(ev) {
ev.preventDefault();
var newSetName = this.ui.setName.val().trim();
var newSet = new Cards.Entities.Set({ name: newSetName });
newSet.save();
// How to add the model to the collection?
}
});
});
i'm looking for the best way to add the newSet to the collection of the CompositeView below. is there any clean low coupling solution to deal with that? i'm quite new to backbone.js and can't imagine that this is something totally unordinary, but somehow i'm not able to find an answer to my question in the regarding docs - or just dont understand them.
Cards.module('Set.List', function(List, App) {
List.SetItemView = Backbone.Marionette.ItemView.extend({
tagName: "tr",
template: "#set-list-item"
});
List.SetView = Backbone.Marionette.CompositeView.extend({
tagName: "table",
className: "table table-bordered table-striped table-hover",
template: "#set-list",
itemView: List.SetItemView,
itemViewContainer: "tbody",
modelEvents: {
"change": "modelChanged"
},
initialize: function() {
this.collection.fetch();
}
});
});
thanks in advance for your help!
how i'm doing it now:
thanks for both answers, they were guiding me in the right direction. the collection.create hint was also very useful and solved another problem i was facing!
inside a Marionette.Controller i do something like this and simply share the collection reference:
var setLayout = new Cards.Set.Layout();
Cards.mainRegion.show(setLayout);
var sets = new Cards.Entities.SetCollection();
var listView = new Cards.Set.List.SetView({ collection: sets });
setLayout.listRegion.show(listView);
var sideBarView = new Cards.Set.SideBar.SideBarView({ collection: sets });
setLayout.sideBarRegion.show(sideBarView);
and the new model is simply added by collection.create instead of .save() and .add().
Backbone.Collection.add can be used to add a model to an existing backbone collection.
http://backbonejs.org/#Collection-add
Also, look in to Collection.Create - http://backbonejs.org/#Collection-create
If your model is being persisted, then immediately added to the collection, you can skip your model.save() then collection.add() and just use collection.create(model)
Edit: And as already mentioned, make the collection instance visible from the sidebar view
To keep views decoupled, you can raise events from one view that other view(s) can listen to and handle however they please.
I'm creating an application for Phonegap using Backbone framework and Parse.com as backend service. I create an object with Parse.com (corresponding to Backbone models).
This object has a saveDraftToP() method that calls the Parse.com function save().
After this method is called from a view, I'd like to retrieve the updated object.
To do so I'm binding the 'change' event to the model but the Parse assigned ID is undefined.
Here is the code of the model:
var Match = Parse.Object.extend("Match", {
states: {'DRAFT': 0, 'RUNNING': 1, 'ENDED': 2},
saveDraftToP: function () {
var self = this;
this.save({
user: Parse.User.current(),
ACL: new Parse.ACL(Parse.User.current()),
state: self.states.DRAFT
}, {
success: function (result) {
self = result;
},
error: function (e) {
}
});
}
});`
And here is the code for the view:
var vmNuovaPartita = Parse.View.extend({
template: Handlebars.compile(template),
model: new Match(),
collection: new HintCollection(),
initialize: function () {
this.bind("change:model", console.log(this.model.id) , this);
},
render: function (eventName) {
var match = this.model.toJSON();
$(this.el).html(this.template(match));
return this;
}
});
I'm not quite sure why you have a save function wrapped in another save-like function. :-)
Say you have something like myMatch which is an object.
Through your UI, a button click saves the object data. You can just use myMatch.save({attr:val, ...}) straight out of the box. Backbone (and Parse) by default are optimistic. That means, you it will set the values of the model with the expectation that persisting to the server will succeed.
Thus, you don't need to retrieve anything extra. You already have the model in it's most current state.
To have a model view that responds to these changes, I'd design the view a little differently.
var vmNuovaPartita = Parse.View.extend({
template: Handlebars.compile(template),
initialize: function () {
this.model.on('change', this.render);
},
render: function (eventName) {
var match = this.model.toJSON();
$(this.el).html(this.template(match));
return this;
}
});
var myView = new vmNuovaPartita({
model: myModel
});
I'd initialize the model outside of the view, then pass it in as an option when you generate a new view. When you pass a model in as an option, it's special and will be attached directly to the view ... view.model which you can refer inside your view code as this.model
In the init we place a listener on the model for change events, then fire off a rerender of the view. Or a nicer way to go about this sort of thing is to throw in the newer Backbone Events with the .listenTo() method.
Background:
I am making changes to an application that uses backbone.js with Handlebars as the templating engine. After a change event fires I need to create html that is appended to the current DOM structure which is basically just a spit-out of information that is contained in the model. This change needed to fit in the already established application structure.
Issue:
I have created a new view that uses a Handlebars template and the model to create the html. I then instantiate that view and call the render function and append the output using JQuery. What I am noticing is that when the html is rendered the model that is passed in because attributes on the $el instead of filling in the template (like I think it should).
View I'm altering:
$.hart.TestView = Backbone.View.extend({
tagName: "li",
template: Handlebars.compile($('#templateOne').html()),
initialize: function () {
this.model.on('change', function () {
this.createMoreInfoHtml();
}, this);
},
selectSomething: function () {
this.$el.removeClass('policies');
this.createMoreInfoHtml(); //function created for new view stuff
},
createMoreInfoHtml: function () {
var id = this.$el.attr('data-id', this.model.get("ID"));
$('.info').each(function () {
if ($(this).parent().attr('data-id') == id
$(this).remove();
});
var view = new $.hart.NewView(this.model, Handlebars.compile($("#NewTemplate").html()));
$('h1', this.$el).after(view.render().el);
},
render: function () {
... //render logic
}
});
View I Created:
$.hart.NewView = Backbone.View.extend({
initialize: function (model, template) {
this.model = model;
this.template = template;
},
render: function () {
this.$el.html(this.template({ info: this.model }));
this.$el.addClass('.info');
return this;
}
});
Json the is the model:
{
"PetName":"Asdfasdf",
"DateOfBirth":"3/11/2011 12:00:00 AM",
"IsSpayNeutered":false,
"Sex":"F",
"SpeciesID":2,
"ID":"ac8a42d2-7fa7-e211-8ef8-000c2964b571"
}
The template
<script id="NewTemplate" type="text/html">
<span>Pet Name: </span>
<span>{{this.PetName}}</span>
</script>
So now to the question: What am I doing wrong? Why are the properties of the model being created as attributes on the $el instead of filling in the template? Can someone please direct me as to how to get the results I am looking for?
Let's skip the problem Jack noticed.
The way you're creating your view is just wrong. It may work as you get the expected arguments in the initialize function, but it has unexpected behaviors you don't see. See the View's constructor:
var View = Backbone.View = function(options) {
this.cid = _.uniqueId('view');
this._configure(options || {});
Now let's have a look at this _configure method:
_configure: function(options) {
if (this.options) options = _.extend({}, _.result(this, 'options'), options);
_.extend(this, _.pick(options, viewOptions));
And of course...
var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
Ok here we are... Basically when passing the model as the options argument, you're passing an object with an attributes key (the attributes of your model). But this attributes key is also used in the View to bind attributes to its element! Therefore the behavior your noticed.
Now, other wrong thing. You're compiling your template each time you create a new function, but not using it as a singleton either. Put your template in the view:
$.hart.NewView = Backbone.View.extend({
template: Handlebars.compile($("#NewTemplate").html(),
And change your view's creation to make the whole thing work:
new $.hart.NewView({model: this.model});
Oh, and get rid of this useless initialize method. You're just doing things Backbone already does.