Understanding the el in backbone - backbone.js

I don't quite understand how the el works in backbone.
I was under the assumption that el defaulted to body when it wasn't specified. I created a fiddle to illustrate my misunderstanding.
When I specify the el everything works fine. Unspecified returns nothing though.
http://jsfiddle.net/9R9zU/70/
HTML:
<div class="foo">
<p>Foo</p>
</div>
<div class="bar">
</div>
<script id="indexTemplate" type="text/template">
Bar?
</script>
JS:
app = {};
app.Router = Backbone.Router.extend({
routes: {
"" : "index"
},
index: function() {
if (!this.indexView) {
this.indexView = new app.IndexView();
this.indexView.render();
} else {
this.indexView.refresh();
}
}
});
app.IndexView = Backbone.View.extend({
// el: $('.bar'),
template : _.template( $('#indexTemplate').html() ),
render: function() {
this.$el.html(this.template());
return this;
},
refresh: function() {
console.log('we\'ve already been here hombre.')
}
});
var router = new app.Router();
Backbone.history.start();

If you do not specify element in the Backbone view, it will create an html node in memory, render the view into it and bind all event handlers based on that node. Then you will need to manually append it to the dom like this:
$('body').append(this.indexView.render().el);

Related

Read Countless Backbone Tutorials But Still Missing Something

UPDATE
I finally learned Backbone back in late 2017. I'd delete this post but StackOverflow says it's not wise to delete answered questions. Please ignore this question.
I've read countless posts here on StackExchange as well as countless tutorials across the Internet but I seem to be just off from understanding basic Backbone use and implementation.
I'm attempting to build a custom Twitter timeline using pre-filtered JSON that is generated from a PHP file on my work's server.
I feel close but I just can't seem to get things to work. At times I'm able to view 20 tweets in my console but am only able to get 1 tweet to render via my template.
Here is my current Backbone setup:
(function($){
if(!this.hasOwnProperty("app")){ this.app = {}; }
app.global = this;
app.api = {};
app.api.Tweet = Backbone.Model.extend({
defaults: {}
});
app.api.Tweets = Backbone.Collection.extend({
model: usarugby.api.Tweet,
url: "https://custom.path.to/api/tweets/index.php",
parse: function(data){
return data;
}
});
app.api.TweetsView = Backbone.View.extend({
el: $('#tweet-wrap'),
initialize: function(){
_.bindAll(this, 'render');
this.collection = new app.api.Tweets();
this.collection.bind('reset', function(tweets) {
tweets.each(function(){
this.render();
});
});
return this;
},
render: function() {
this.collection.fetch({
success: function(tweets){
var template = _.template($('#tweet-cloud').html());
$(tweets).each(function(i){
$(this).html(template({
'pic': tweets.models[i].attributes.user.profile_image_url,
'text': tweets.models[i].attributes.text,
'meta': tweets.models[i].attributes.created_at
}));
});
$(this.el).append(tweets);
}
});
}
});
new app.api.TweetsView();
}(jQuery));
And here is my current HTML and template:
<div id="header-wrap"></div>
<div id="tweet-wrap"></div>
<script type="text/template" id="tweet-cloud">
<div class="tweet">
<div class="tweet-thumb"><img src="<%= pic %>" /></div>
<div class="tweet-text"><%= text %></div>
<div class="tweet-metadata"><%= meta %></div>
</div>
</script>
<script> if(!window.app) window.app = {}; </script>
I also have a CodePen available for testing. Any advice would be greatly appreciated.
Like the comments suggest, additional reading and code rewrite may be needed. The simplest example for a view rendering multiple views is here adrianmejia's backbone tutorial example.
The snippet below includes an additional view and a couple of added functions along with updating the render and initialize functions. Search for 'cfa' to review changes.
(function($){
if(!this.hasOwnProperty("app")){ this.app = {}; }
app.global = this;
app.api = {};
app.api.Tweet = Backbone.Model.extend({
idAttribute: 'id_str'
});
app.api.Tweets = Backbone.Collection.extend({
model: app.api.Tweet,
url: "https://cdn.usarugby.org/api/tweets/index.php",
parse: function(data){
return data;
}
});
app.api.TweetView = Backbone.View.extend({
tagName: 'div',
template: _.template($('#tweet-cloud').html()),
initialize: function(){
},
render: function(){
var j = {};
j.pic = this.model.get('user').profile_image_url;
j.text = this.model.get('text');
j.meta = this.model.get('meta');
this.$el.html(this.template(j));
return this;
},
});
app.api.TweetsView = Backbone.View.extend({
el: $('#tweet-wrap'),
initialize: function(){
this.collection = new app.api.Tweets();
this.collection.on('reset', this.onReset, this);
this.collection.on('add', this.renderATweet, this);
this.collection.fetch();
},
onReset: function(){
this.$el.html('');
this.collection.each(this.renderATweet, this);
},
renderATweet: function (tweet) {
var tweetView = new app.api.TweetView({ model: tweet });
this.$el.append(tweetView.render().el);
},
});
}(jQuery));
$(document).ready(function(){
new app.api.TweetsView();
});
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.13.1/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>
<script src="https://static.usarugby.org/lib.min.js"></script>
<div id="header-wrap"></div>
<div id="tweet-wrap"></div>
<script type="text/template" id="tweet-cloud">
<div class="tweet">
<div class="tweet-thumb"><img src="<%= pic %>" /></div>
<div class="tweet-text">
<%= text %>
</div>
<div class="tweet-metadata">
<%= meta %>
</div>
</div>
</script>
<div id="footer-wrap"></div>
<script>
if(!window.app) window.app = {};
</script>

View not giving the desired output

<script id="entry-template" type="text/x-handlebars-template">
<h2>This is the template</h2>
{{ count }} items
</script>
<script type="text/javascript">
var MyNamespace = {};
$(document).ready(function () {
MyNamespace.Recipe = Backbone.View.extend({
tagName: "li",
render: function () {
return this.$el.text("Chicken Chilly");
}
})
MyNamespace.MyTagView = Backbone.View.extend({
initialize: function () {
this.render();
},
render: function () {
var template = Handlebars.compile($("#entry-template").html());
this.$el.html(template);
return this;
},
count: 4
});
var View = new MyNamespace.MyTagView();
$("#content").html(View.el);
});
</script>
I get the output as 0 items , instead of 4 items
In this line:
template: template('entry-template'),
you are generating html of your compiled template and 'entry-template' context object (it's not an id, it's value object to your template). After generating that html you assign it to template property of MyNamespace.MyTagView
Later, in the render method, you are calling this template property (which is html) as it was a function:
this.$el.html(this.template(this));
but it is not a function, but a property containing generated html.
You should assign template like this:
template: template,

How to share and bind Model with the Views in Backbone.js

I'm new at Backbone.js. So I want to ask a question about problem which I face with.
I have a User object which is exteded from Backbone.Model. According to the state of this object( authenticated or not), I want to change my views. I have showView method which enable my views to be rendered.
For this structure how I change my HomeView's template and enable it to be rendered according to the model.
I think, just adding below code is not sufficient for this.
this.user.bind('change', this.render);
Any idea or help will be greately appreciated.
My Router
var AppRouter = Backbone.Router.extend({
routes: {
'':'showHome',
'join':'showJoin',
'join/create':'showRegister',
'join/complete':'showLoginComplete',
// Define some URL routes
// 'users': 'showContributors',
// Default
':actions': 'defaultAction'
},
showView: function(view) {
if (this.currentView)
{
this.currentView.close();
$('#tomato').attr("style",";display:none;");
$('#tomato').html(view.render().el);
$("#tomato").fadeIn("slow");
}else{
$('#tomato').html(view.render().el);
}
this.currentView = view;
return view;
},
login_required: function(callback) {
if (this.user.get('is_authenticated')) {
if (callback) callback();
} else {
//route login page...
}
},
login_not_required:function(callback){
if (this.user.get('is_authenticated')) {
//route 404 page...
this.navigate(':actions', true);
} else {
if (callback) callback();
}
},
updateUser:function(){
var $self=this;
$.get(
'/get_identity',
function(response){
// update model...
$self.user.id =response.identity;
//check user every five minutes...
$self.user.fetch({success: function() {
$self.user.set('is_authenticated',true);
setTimeout($self.updateUser, 1000*60*5);
}
},this);
}).fail(function(){
//clear model
$self.user.clear().set($self.user.defaults);
});
}
});
var initialize = function(){
//initialize user and it's checker.
var app_router = new AppRouter;
this.user = new User();
_.bindAll(app_router, 'updateUser');
app_router.updateUser();
app_router.on('route:showHome', function(){
var homeView = new HomeView({user:app_router.user});
app_router.showView(homeView);
});
};
return {
initialize: initialize
};
});
My HomeView
var HomeView = Backbone.View.extend({
initialize:function(){
this.user = this.options.user
}
render: function(){
var headerView = new HeaderView();
var footerView = new FooterView();
this.$el.append(headerView.render().$el);
this.$el.append(homeTemplate);
this.$el.append(footerView.render().$el);
return this;
}
});
You are quite right but... First of all create View Class without explicit model object.
And then create instance with object of your model
var homeView = new HomeView({
model = user;
});
In initializing function of your View (remove this.user of course) with you should have this call
this.model.bind('change',this.render).
I think your first guess of adding a listener to the model was right.
This is the way to do it.
In view:
this.listenTo(this.user, 'change', this.render);
Why do you think this is not the right way to do it?
Some frameworks like MarionetteJS do have a construct called Event Aggregator. It is kind of a Event bus you can listen to. My user for example sends events like 'user:login' on the bus. In the views i can listen on the bus.
Here you have simple example of how it may works.
<html>
<head>
<script type="text/javascript" src="./public/js/jquery-1.9.1.min.js"></script>
<script type="text/javascript" src="./public/js/json2.js"></script>
<script type="text/javascript" src="./public/js/underscore-min.js"></script>
<script type="text/javascript" src="./public/js/backbone-min.js"></script>
<script type="text/javascript">
var User = Backbone.Model.extend({
defaults : {
name : 'Damian0o',
forum : 'stackoverflow'
}
});
var MyView = Backbone.View.extend({
events: {
"change input": "update"
},
initialize : function(){
this.model.on('change',this.updateView,this);
},
render : function(){
this.$el.append('<input id="name" value="' + this.model.get('name') + '">');
this.$el.append('<input id="forum" value="' + this.model.get('forum') + '">');
},
update : function(){
console.log(this.$el.find('input').val());
this.model.set('name',this.$el.find('#name').val());
this.model.set('forum',this.$el.find('#forum').val());
},
updateView : function(){
this.$el.find('#name').val(this.model.get('name'));
this.$el.find('#forum').val(this.model.get('forum'));
}
});
$(function(){
var user = new User();
var view = new MyView({
model : user
});
var view2 = new MyView({
model : user
});
view.render();
view2.render();
$('#div1').append(view.el);
$('#div2').append(view2.el);
});
</script>
</head>
<body>
<div id="div1"></div>
<div id="div2"></div>
</body>
</html>

Clueless on preloading collection from server using Backbone.js and Tastypie

I'm trying to display a list of projects using backbone.js.
Basically, backbone should be able to .fetch() the projects into the Projects collection.
This works, as I can tell from the async request which is filled with projects.
But, how do I approach rendering them on page load? There's not much documentation about using the reset() method for 'bootstrapped models'. Any help is appreciated! Thanks.
app.js:
var oldSync = Backbone.sync;
Backbone.sync = function(method, model, success, error){
var newSuccess = function(resp, status, xhr){
if(xhr.statusText === "CREATED"){
var location = xhr.getResponseHeader('Location');
return $.ajax({
url: location,
success: success
});
}
return success(resp);
};
return oldSync(method, model, newSuccess, error);
};
(function($) {
window.Project = Backbone.Model.extend({});
window.Projects = Backbone.Collection.extend({
model: Project,
url: PROJECT_ENDPOINT,
parse: function(data) {
return data.objects;
}
});
window.ProjectView = Backbone.View.extend({
tagName: 'li' ,
className: 'project',
initialize: function() {
_.bindAll(this, 'render');
this.model.bind('change', this.render);
this.projects = new Projects();
this.projects.fetch(function(data) {
console.log("haha");
});
this.template = _.template($('#project-template').html());
},
render: function() {
var renderedContent = this.template(this.model.toJSON());
$(this.el).html(renderedContent);
return this;
}
});
})(jQuery);
Template:
.........
<script>
PROJECT_ENDPOINT = "{% url api_dispatch_list resource_name="project" %}";
</script>
<script type="text/template" charset="utf-8" id="project-template">
<span class="project-title"><%= title %></span>
</script>
</head>
<body>
<div id="container"></div>
</body>
</html>
You can add bootstrapped models to the template:
<script>
PROJECT_ENDPOINT = "{% url api_dispatch_list resource_name="project" %}";
INITIAL_DATA = <%= collection.to_json %>
</script>
And then in your view constructor replace this.projects.fetch(...) with this.projects.reset(INITIAL_DATA)
I like to set up my apps to have some sort of "start" function that i call with the json data for the preloaded items.
function MyApp(){
this.start = function(initialModels){
var myModels = new MyCollection(initialModels);
var modelsView = new MyModelsView({collection: myModels});
modelsView.render();
// ... other startup code here
}
}
and the html page has a script blog that looks something like this:
<script language="javascript">
var myApp = new MyApp();
myApp.start(<%= mymodel.to_json %>)
</script>
hope that helps

When using Backbone.View a "parent.apply is not a function" error is returned

Consider this markup
<div id="controls" class="controls">
Home -
get -
new
<input type="text" val="" id="input">
</div>
And this piece of javascript:
$(document).ready(function() {
"use strict";
// this is used on my code as root.App,
// but the code was omitted here for clarity purposes
var root = this,
undefined;
var controller = Backbone.Controller.extend({
routes : {
// static
},
});
var view = new Backbone.View.extend({
el : $('#controls'),
events : {
'click a' : 'updateOnEnter'
},
updateOnEnter : function(el) {
alert('sss');
return this;
},
initialize : function() {
_.bindAll(this, 'render', 'updateOnEnter');
},
render : function() {
return this;
}
});
new view;
new controller;
Backbone.history.start();
)};
When view is called (with new view), Firebug fires this error:
parent.apply is not a function
error backbone.js (line 1043): child = function(){ return parent.apply(this, arguments); };
Any ideas to why this is happening? Thanks.
Never mind.
The problem is on line 16 of the above js code:
var view = new Backbone.View.extend({
it should instead be:
var view = Backbone.View.extend({
I'm not deleting this question since somebody may find it useful. The pitfalls of not coming from a CS background, I guess.

Resources