I am building an application using the Backbone Boilerplate, and am having some trouble getting underscore template variables to work. I have a resource named Goal. My Goal View's render function looks like this:
render: function(done) {
var view = this;
namespace.fetchTemplate(this.template, function(tmpl) {
view.el.innerHTML = tmpl();
done(view.el);
});
}
I'm calling it inside of another view, like so:
var Goal = namespace.module("goal");
App.View = Backbone.View.extend({
addGoal: function(done) {
var view = new Goal.Views.GoalList({model: Goal.Model});
view.render(function(el) {
$('#goal-list').append(el);
});
}
});
I'm using local storage to save my data, and it's being added just fine. I can see it in the browser, but for some reason, when I load up the app, and try to fetch existing data, i get this error:
ReferenceError: Can't find variable: title
Where title is the only key I'm storing. It is a direct result of calling:
tmpl();
Any thoughts are greatly appreciated.
Your template is looking for a variable title, probably like this <%- title %>. You need to pass it an object like this tmpl({ title: 'Some title' })
Turns out, I wasn't passing in the model when i created the view, which was making it impossible to get the models data. Once I passed in the model correctly, I could then pass the data to tmpl, as correctly stated by #abraham.
render: function(done) {
var
view = this,
data = this.model.toJSON();
clam.fetchTemplate(this.template, function(tmpl) {
view.el.innerHTML = tmpl(data);
done(view.el);
});
},
Related
Below you can see four basic requireJS files. How can I have multiple Backbone.js views that will all share one collection which has been initiated and fetched elsewhere?
Please note
I am aware I can pass the collection in App.js however I would like to refrain from doing so since I will probably have many collections that will need to be used in many views, and I don't want to pass each of them in App.js.
Collection.js
return Backbone.Collection.extend({
url: '...'
});
App.js
var collection = new Collection();
$.when(collection.fetch()).done(function(){
new View1();
new View2();
});
View1.js
define(['Collection'], function(Collection){
return Backbone.View.extend({
initialize: function(){
console.log('Need the initiated, fetched collection here...');
});
});
});
View2.js
define(['Collection'], function(Collection){
return Backbone.View.extend({
initialize: function(){
console.log('Need the initiated, fetched collection here...');
});
});
});
Quick answer: RequireJS runs function body code once and the return statement alone multiple times. You can, hence, create a Collection Instance and return that to every person who requires it.
Collection.js
var CollectionConstructor = Backbone.Collection.extend({
url: '...'
});
var CollectionInstance = new CollectionConstructor();
return CollectionInstance;
Alternatively, if you want more than one instance of CollectionInstance running around, and don't want to pass it to the views, I don't believe anything short of global variables will help. That would look like:
Globals.js
var AppGlobals = AppGlobals || {};
AppGlobals.oneSet = new Collection ();
AppGlobals.anotherSet = new Collection ();
You can now have your views depend on Globals.js and access them from here. Depending on your use, either of these two should work. Keep in mind that in the second approach, your Collection.js is unmodified.
I have a Backbone multipage app written with the use of RequireJS. Since it's multipage I decided not to use a router as it got too messy. I've tried multiple ways of creating a singleton object to be used throughout the app
var singletonModel= Backbone.Model.extend({
}),
return new singletonModel;
For the above I'm just referencing the singletonModel model in my class using the define method and then calling it as is
this.singleton = singletonModel;
this.singleton.set({'test': 'test'});
On a module on my next page when I then call something similar to
this.singleton = singletonModel;
var test = this.singleton.get('test');
The singleton object seems to get re-initialized and the test object is null
var singletonModel= Backbone.Model.extend({
}, {
singleton: null,
getSingletonModelInst: function () {
singletonModel.singleton =
singletonModel.singleton || new singletonModel;
return singletonModel.singleton;
}
});
return singletonModel;
For the above I'm just referencing the singletonModel model in my class using the define method and then calling it as is
this.singleton = singletonModel.getSingletonModelInst();
this.singleton.set({'test': 'test'});
On a module on my next page when I then call something similar to
this.singleton = singletonModel.getSingletonModelInst();
var test = this.singleton.get('test');
Again it looks like the singleton object is getting re-initialized and the test object is null.
I'm wondering if the issue is because I'm using a multi-page app with no router so state is not been preserved? Has anyone tried using a singleton object in a multi-page app before? If so did you do anything different to how it's implemented on a single-page app?
Thanks,
Derm
Bart's answer is very good, but what it's not saying is how to create a singleton using require.js. The answer is short, simply return an object already instanciated :
define([], function() {
var singleton = function() {
// will be called only once
}
return new singleton()
})
Here we don't have a singleton anymore :
define([], function() {
var klass = function() {
// will be called every time the module is required
}
return klass
})
It's may sound a little ... but, you doing a multi-page application, so when you move to next page, a whole new document was loaded into the browser, and every javascript on it will be loaded too, include require.js and your model. so the require.js was reloaded, and it create your model again, so you got a different model than you thought.
If above was true, my opinion is your model will "live" on a single page, when you jump to then next page, that model was "kill"ed by browser. so If you want see it again, store it on somewhere else, maybe server or localstroe, on the former page. and in the next page load it back from server or localstore, and wrap it into a Backbone model, make it "live" again.
Here is how I implemented a singleton in a recent Backbone/Require application. State is remembered across any number of views.
instances/repoModel.js
define(['models/Repo'],
function(RepoModel){
var repoModel = new RepoModel();
return repoModel;
}
);
models/Repo.js
define(['backbone'],
function(Backbone){
return Backbone.Model.extend({
idAttribute: 'repo_id'
});
}
);
views/SomePage.js
define(['backbone', 'instances/repoModel'],
function(Backbone, repoModel) {
return Backbone.View.extend({
initialize: function() {
repoModel.set('name', 'New Name');
}
});
}
);
I'm trying to working with backbone but I'm missing it's base concepts because this is the first JavaScript MVVM Framework I try.
I've taken a look to some guide but I think I still missing how it should be used.
I'll show my app to get some direction:
// Search.js
var Search = {
Models: {},
Collections: {},
Views: {},
Templates:{}
};
Search.Models.Product = Backbone.Model.extend({
defaults: search.product.defaults || {},
toUrl:function (url) {
// an example method
return url.replace(" ", "-").toLowerCase();
},
initialize:function () {
console.log("initialize Search.Models.Product");
}
});
Search.Views.Product = Backbone.View.extend({
initialize:function () {
console.log("initialize Search.Views.Product");
},
render:function (response) {
console.log("render Search.Views.Product");
console.log(this.model.toJSON());
// do default behavior here
}
});
Search.Models.Manufacturer = Backbone.Model.etc...
Search.Views.Manufacturer = Backbone.View.etc...
then in my web application view:
<head>
<script src="js/jquery.min.js"></script>
<script src="js/underscore.min.js"></script>
<script src="js/backbone/backbone.min.js"></script>
<script src="js/backbone/Search.js"></script>
</head>
<body>
<script>
var search = {};
search.product = {};
search.product.defaults = {
id:0,
container:"#search-results",
type:"product",
text:"<?php echo __('No result');?>",
image:"<?php echo $this->webroot;?>files/product/default.png"
};
$(function(){
var ProductModel = new Search.Models.Product();
var ProductView = new Search.Views.Product({
model:ProductModel,
template:$("#results-product-template"),
render:function (response) {
// do specific view behavior here if needed
console.log('render ProductView override Search.Views.Product');
}
});
function onServerResponse (ajax_data) {
// let's assume there is some callback set for onServerResponse method
ProductView.render(ajax_data);
}
});
</script>
</body>
I think I missing how Backbone new instances are intended to be used for, I thought with Backbone Search.js I should build the base app like Search.Views.Product and extend it in the view due to the situation with ProductView.
So in my example, with render method, use it with a default behavior in the Search.js and with specific behavior in my html view.
After some try, it seems ProductModel and ProductView are just instances and you have to do all the code in the Search.js without creating specific behaviors.
I understand doing it in this way make everything easiest to be kept up to date, but what if I use this app in different views and relative places?
I'm sure I'm missing the way it should be used.
In this guides there is no code used inside the html view, so should I write all the code in the app without insert specific situations?
If not, how I should write the code for specific situations of the html view?
Is it permitted to override methods of my Backbone application?
Basically, you should think of the different parts like this:
templates indicate what should be displayed and where. They are writtent in HTML
views dictate how the display should react to changes in the environment (user clicks, data changing). They are written in javascript
models and collections hold the data and make it easier to work with. For example, if a model is displayed in a view, you can tell the view to refresh when the model's data changes
then, you have javascript code that will create new instances of views with the proper model/collection and display them in the browser
I'm writing a book on Marionette.js, which is a framework to make working with Backbone easier. The first chapters are available in a free sample, and explain the above points in more detail: http://samples.leanpub.com/marionette-gentle-introduction-sample.pdf
demo fiddle (with problem) http://jsfiddle.net/mjmitche/UJ4HN/19/
I have a collection defined like this
var Friends = Backbone.Collection.extend({
model: Friend,
localStorage: new Backbone.LocalStorage("friends-list")
});
As far as I'm aware, that's all I need to do to get local storage to work (in addition to including it below backbone.js)
One thing I wasn't sure about, does the name "friends-list" have to correspond to a DOM element? I'm trying to save the "friends-list" so I called it that in local storage, however, localstorage doesn't seem to require passing a class or an id.
Here's a fiddle where you can see what I'm trying to do http://jsfiddle.net/mjmitche/UJ4HN/19/
On my local site, I'm adding a couple friends, refreshing the page, but the friends are not re-appearing.
Update
I've also done the following in my code on my local site
console.log(Backbone.LocalStorage);
and it's not throwing an error.
My attempt to debug
I tried this code (taken from another SO answer) in the window.AppView but nothing came up in the console.
this.collection.fetch({}, {
success: function (model, response) {
console.log("success");
},
error: function (model, response) {
console.log("error");
}
})
From the fine manual:
Quite simply a localStorage adapter for Backbone. It's a drop-in replacement for Backbone.Sync() to handle saving to a localStorage database.
This LocalStorage plugin is just a replacement for Backbone.Sync so you still have to save your models and fetch your collections.
Since you're not saving anything, you never put anything into your LocalStorage database. You need to save your models:
showPrompt: function() {
var friend_name = prompt("Who is your friend?");
var friend_model = new Friend({
name: friend_name
});
//Add a new friend model to our friend collection
this.collection.add(friend_model);
friend_model.save(); // <------------- This is sort of important.
},
You might want to use the success and error callbacks on that friend_model.save() too.
Since you're not fetching anything, you don't initialize your collection with whatever is in your LocalStorage database. You need to call fetch on your collection and you probably want to bind render to its "reset" event:
initialize: function() {
_.bindAll(this, 'render', 'showPrompt');
this.collection = new Friends();
this.collection.bind('add', this.render);
this.collection.bind('reset', this.render);
this.collection.fetch();
},
You'll also need to update your render to be able to render the whole collection:
render: function() {
var $list = this.$('#friends-list');
$list.empty();
this.collection.each(function(m) {
var newFriend = new FriendView({ model: m });
$list.append(newFriend.render().el);
});
$list.sortable();
return this;
}
You could make this better by moving the "add one model's view" logic to a separate method and bind that method to the collection's "add" event.
And a stripped down and fixed up version of your fiddle: http://jsfiddle.net/ambiguous/haE9K/
I am trying to use iScroll4 inside a backbone.js application. I have several dynamically loaded lists, and I want to initialize iScroll after the appropriate view has loaded.
I'm trying to call 'new iScroll' when the list view finishes loading, but cannot for the life of me figure out how to do this.
Has anyone gotten these two to work together? Is there an example out there of a backbone view initializing a scroller once its element has loaded?
you are correct, you have to load the view first,
or defenately refresh iscroll afterwards
in our applications, we usually use the render method to render the view
and have a postRender method that handles initialization of these extra plugins like iscroll
of course you need some manual work to get it done but this is the gist of it:
var myView = Backbone.View.extend({
// more functions go here, like initialize and stuff... but I left them out because only render & postRender are important for this topic
// lets say we have a render method like this:
render: function() {
var data = this.collection.toJSON();
this.$el.html(Handlebars.templates['spotlightCarousel.tmpl'](data));
return this;
},
// we added the postRender ourself:
postRender: function() {
var noOfSlides = this.collection.size();
$('#carouselscroller').width(noOfSlides * 320);
this.scroller = new IScroll('carouselwrapper', {
snap: true,
momentum: false,
hScrollbar: false
});
}
});
now the calling of these methods
we did this outside our view as we like some view manager to handle this
but it boils down to this
var col = new myCollection();
var view = new myView({ collection: col });
$('#wrapper').html(view.render().$el); // this chaining is only possible due to the render function returning the whole view again.
// here we always test if the view has a postRender function... if so, we call it
if (view.postRender) {
view.postRender();
}