I am new to backbone and marionette, and learning that from the book Getting Started with Backbone Marionette by Packpub publication.
I am getting the error as Uncaught NoChildViewError: A "childView" must be specified
and my code is
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Marionette test app</title>
<link rel="stylesheet" media="screen" href="{{ url_for('static', filename='bootstrap.css') }}">
<style type="text/css">
.container {
margin-top: 10px;
}
body {
padding-top: 60px;
padding-bottom: 40px;
}
</style>
</head>
<body>
<div id="container" class="well">
<h2>Marionette Views- CollectionView</h2>
</div>
<script id="categoryTemplate" type="text/template">
<a href="#<%= name%>" ><%= name%> (<%= booksOnCategory %>)</a>
<button class="info">Details</button>
</script>
<script src="{{ url_for('static', filename='jquery.js') }}"></script>
<script src="{{ url_for('static', filename='bootstrap.js') }}"></script>
<script src="{{ url_for('static', filename='underscore.js') }}"></script>
<script src="{{ url_for('static', filename='backbone.js') }}"></script>
<script src="{{ url_for('static', filename='backbone.marionette.js') }}"></script>
<script type="application/javascript">
// lets create new application as below;
var BookStoreApp = new Backbone.Marionette.Application();
var BookStoreController = Backbone.Marionette.Controller.extend({
displayBooks : function(){
console.log("I will display books");
}
});
var BookStoreRouter = Backbone.Marionette.AppRouter.extend({
controller : BookStoreController,
appRoutes : {
"":"displayBooks"
}
});
BookStoreApp.addInitializer(function(){
var controller = new BookStoreController();
var router = new BookStoreRouter({controller: controller});
console.log("Hello from addInit");
});
BookStoreApp.on("initialize:after", function(){
if (Backbone.history){
Backbone.history.start();
console.log("hello from initialize:after.. .");
}
});
BookModel = Backbone.Model.extend({
defaults : {
name : "",
booksOnCategory:0
}
});
BookCollection = Backbone.Collection.extend({
model : BookModel
});
var bookModel = new BookModel({name:"Math", booksOnCategory:3});
var bookModel2 = new BookModel({name:"Art", booksOnCategory:5});
var bookModel3 = new BookModel({name:"Science", booksOnCategory:6});
var bookModel4 = new BookModel({name:"Biology", booksOnCategory:1});
var bookCollection = new BookCollection([bookModel, bookModel2, bookModel3, bookModel4]);
var BookView = Backbone.Marionette.ItemView.extend({
template : '#book-template',
tagName: 'li',
events:{
"mouseenter .info": "showDetails",
"mouseleave .info": "hideDetails"
},
showDetails: function(){
this.$(".info").popover({
title: this.model.get('name'),
content: "we have "+ this.model.get("booksOnCategory") + " left"
});
this.$(".info").popover("show");
},
hideDetails: function(){
this.$(".info").popover("hide");
}
});
CategoriesVieww = Backbone.Marionette.CollectionView.extend({
tagName: 'ul',
className: "unstyled",
itemView: BookView
});
var CategoriesView = new CategoriesVieww({
collection: bookCollection,
el: "#container" });
CategoriesView.render();
BookStoreApp.start();
</script>
</body>
</html>
note: i am testing this app on flask-jinja2 app; so ignore the url_for in script tag; as they are required by the jinja2
Looks like the problem is exactly what the error message says ;) A CollectionView is generated as a sequence of child views, and you need to tell the collection view which view type you want to use for those children.
But you haven't. Perhaps just a typo? Try changing your CategoriesVieww to
CategoriesVieww = Backbone.Marionette.CollectionView.extend({
tagName: 'ul',
className: "unstyled",
childView: BookView // <-- here
});
If you are using collectionView OR a compositeView of Marionette 2.2.0 or above, be aware that the API keywords have changed as follows:
itemView is now called childView
itemViewContainer is now called childViewContainer
BEFORE 2.2.0
CategoriesView = Backbone.Marionette.CompositeView.extend({
template: '#template',
className: "unstyled",
itemView: BookView // <-- here
itemViewContainer: "#books-list"
});
It took me a while to figure it out through Firebug debugger. You can refer to https://docs.google.com/document/d/1fuXb9N5LwmdPn-teMwAo3c8JTx6ifUowbqFY1NNSdp8/edit# for API changes. Not all changes are documented anywhere I can find.
AFTER 2.2.0 :
CategoriesView = Backbone.Marionette.CompositeView.extend({
template: '#template',
className: "unstyled",
childView: BookView // <-- here
childViewContainer: "#books-list"
});
Related
The below example that I have written using backbone.js doesnt work.
I want the single record that I enter in the textboxes to show in the emplist ul.
Can someone help please.
Thanks inadvance.
EmpForm.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Emp Form (Backbone.js)</title>
</head>
<form id="empform" align="center" border="1">
Enter Emp Name:<input type="text" id="empname" placeholder="Enter emp name" size="20"/><br>
Enter Emp Job:<input type="text" id="desig" placeholder="Enter emp designation" size="20"/><br>
<button id="addrecord">Add Record</button>
<hr>
<ul id="emplist">
</ul>
</form>
<script id="emptemplate" type="text/template">
<span><strong><%= empname %></strong><blockquote><%= desig %></span>
</script>
<!-- <script type="text/javascript" src="./jslib/underscore1.5.0.js"></script>
<script type="text/javascript" src="./jslib/jquery1.9.1.js"></script>
<script type="text/javascript" src="./jslib/backbone1.0.0.js"></script> -->
<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.5.2/underscore.js"></script>
<script src="http://code.jquery.com/jquery-1.9.0.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.1.2/backbone.js"></script>
<script type="text/javascript">
(function(){
EmpModel = Backbone.Model.extend({
defaults:{
empname:'guest emp',
desig:'some work'
}
});
EmpView = Backbone.View.extend({
tagName:'emplist',
template:_.template('emptemplate'),
events:{
'click #addrecord':'addRecord'
},
addRecord:function(){
this.render();
},
render: function() {
this.$el.html( this.template(this.model.toJSON()) );
return this;
}
});
var empModel=new window.EmpModel({empname:'sree',desig:'architect'});
var empView=new window.EmpView({model:empModel});
$(document.body).append(empView.render().el);
})();
</script>
</html>
Well. First off, you should definitely review http://backbonejs.org/ and practice some more with javascript in general. But the following should get you started:
var EmpModel = Backbone.Model.extend({
defaults:{
empname:'guest emp',
desig:'some work'
}
});
var EmpView = Backbone.View.extend({
tagName:'form',
el: '#empform',
template: _.template($('#emptemplate').html()),
events:{
'click #addrecord':'addRecord'
},
addRecord:function(){
var empname = $('#empname').val(); // not the best way to do this, btw
var desig = $('#desig').val(); // same thing...
var newModel = new EmpModel({empname: empname, desig: desig});
this.render(newModel);
},
render: function(model) {
this.$el.find('#emplist').append( this.template(model.toJSON()) );
// can also use '... .html( this.template(model.toJSON()) );'
return this;
}
});
var empModel = new EmpModel({empname:'sree',desig:'architect'});
var empView = new EmpView();
empView.render(empModel);
Also add type="button" to the Add Record button.
See fiddle. Cheers!
I still struggling with render my grouped collection. In my console I can see collection with error. I wanna render each category name and each items in this category. Thanks for opinions !!! This will be very helpful....
There is my whole code :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Backbone test</title>
<link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0-rc1/css/bootstrap.min.css" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<header>
</header>
<content>
<div class="jumbotron">
<div class="container">
<h1>My Items!</h1>
</div>
</div>
<div id="items"></div>
</content>
<footer>
</footer>
<script id="allItemTemlate" type="text/template">
<ul>
<% _.each( data, function( category, i ){ %>
<li>
<h3><%= i %></h3>
<ul>
<% _.each( category, function( item ){ %>
<li>
<%= item.title %>
</li>
<% }) %>
</ul>
</li>
<% }) %>
</ul>
</script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.5.1/underscore-min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/backbone.js/1.0.0/backbone-min.js"></script>
<script>
(function() {
window.App = {
Models: {},
Collections: {},
Views: {},
Router: {}
};
window.vent = _.extend({}, Backbone.Events);
})();
// !models.js
App.Models.Item = Backbone.Model.extend({});
// !collections.js
App.Collections.Items = Backbone.Collection.extend({
model: App.Models.Item,
url: 'api/items.json'
});
// !views.js
App.Views.Items = Backbone.View.extend({
el: '#items',
initialize: function() {
this.render();
var groups = _.groupBy(this.collection.toJSON(), 'category');
console.log(groups);
template = _.template( document.getElementById('allItemTemlate').innerHTML, { data : groups } );
},
render: function() {
document.getElementById('items').innerHTML = template;
},
});
// !router.js
App.Router = Backbone.Router.extend({
routes: {
'':'index',
},
index: function() {
console.log('index page !');
},
});
new App.Router;
Backbone.history.start();
App.items = new App.Collections.Items;
App.items.fetch().then(function() {
new App.Views.Items({ collection: App.items });
});
</script>
</body>
</html>
You need to be more aware of execution order. For example if you need a variable in render, you shouldn't call render before setting that variable. I also propose some changes.
App.Views.Items = Backbone.View.extend({
el: '#items',
initialize: function() {
// listen to collection change event and re-render then
this.listenTo( this.collection, "change", this.render );
// changes: this.template instead of global template
// and not executing it yet, but creating a function
this.template = _.template( document.getElementById('allItemTemlate').innerHTML );
this.render();
},
// a separate method to get the groupped collection
getGroups : function(){
return _.groupBy(this.collection.toJSON(), 'category');
},
render: function() {
// here the template gets executed and writtn to element's innerHTML
// also removed getElementById, as it is readily available as this.el
this.el.innerHTML = this.template({ data : this.getGroups() });
},
});
Just trying to make Backbone.js display a simple message on index.html...It fails if I do try with underscore but it will append a message to the quiz_question div element if I try to do something like
questionTemplate: _.template( '<div>Hello <%= msg %></div>')
...What am I missing?
Index.html
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="quiz_question">
<input id="back_id" type="button" value="Back">
<input id="next_id" type="button" value="Next">
</div>
<script type="text/template" id="qtemplate"></script>
<script src="js/jquery-2.0.2.min.js"></script>
<script src="js/underscore-min.js"></script>
<script src="js/backbone-min.js"></script>
<script src="js/backbone.localStorage.js"></script>
<script src="js/questionmodel.js"></script>
<script src="js/questioncollection.js"></script>
<script src="js/questionview.js"></script>
<script src="js/app.js"></script>
<script type="text/template" id="qtemplate">
<div><%= msg %></div>
</script>
</body>
</html>
app.js
var app = app || {};
$(function() {
// Kick things off by creating the **App**.
new app.QuestionView();
});
questionview.js
var app = app || {};
app.QuestionView = Backbone.View.extend({
// Instead of generating a new element, bind to the existing skeleton of
// the App already present in the HTML.
el: '#quiz_question',
// Our template for the line of statistics at the bottom of the app.
questionTemplate: _.template( $('#qtemplate').html() ),
//questionTemplate: _.template( '<div>Hello <%= msg %></div>'),
// Delegated events for displaying new questions, and clearing existing ones
events: {
'click #back_id': 'displayPreviousQuestion',
'click #next_id': 'displayNextQuestion'
},
// The QuestionView listens for changes to its model, re-rendering. Since there's
// a one-to-one correspondence between a **Question** and a **QuestionView** in this
// app, we set a direct reference on the model for convenience.
initialize: function() {
//app.Questions.fetch();
this.render();
},
render: function(){
// render the function using substituting the varible 'who' for 'world!'.
this.$el.append(this.questionTemplate({msg: "hope floats"}));
//***Try putting your name instead of world.
},
displayPreviousQuestion: function() {
},
displayNextQuestion: function() {
}
});
Your page looks like this:
<script src="js/questionview.js"></script>
<!-- ... -->
<script type="text/template" id="qtemplate">
<div><%= msg %></div>
</script>
so questionview.js will be loaded and executed before #qtemplate is in the DOM. Inside questionview.js you have this:
app.QuestionView = Backbone.View.extend({
//...
questionTemplate: _.template( $('#qtemplate').html() ),
so _.template( $('#qtemplate').html() ) will be executed while questionview.js is being loaded and that happens before there is a #qtemplate available. The result is that you end up doing _.template(undefined) and that doesn't do anything useful.
You can wrap the view definition in a $(function() { ... }) to delay its execution until after the DOM is ready or you could delay creating the template function until you need it with something like this:
initialize: function() {
this.questionTemplate = _.template($('#qtemplate').html());
}
in your QuestionView. There are variations on those two basic approaches but that should get you started.
Above answer explain cause very well, but if you want to do quick fix, moving template tag above other script would fix the problem.
I am trying to test backbone.js , however, I am getting TypeError : h.has is not a function error. My HTML Code:
<html>
<head>
<title></title>
</head>
<body>
<h1>My Theater</h1>
<div id="mainContainer"></div>
<input type="text" value="Enter HashTag" id="hashtag" />
<script type="text/template" id="tmplt-Tweets">
<ul>
</ul>
</script>
<script type="text/template" id="tmplt-Tweet">
<div>*******************************************************</div>
<div><%= url %> </div>
<div><%= text %> </div>
<div><%= html %> </div>
<div><%= date %> </div>
<div><%= id %> </div>
<div><%= img %> </div>
<div><%= name %> </div>
<div><%= rt %> </div>
</script>
<script src="scripts/libs/jquery-1.7.1.js" type="text/javascript"></script>
<script src="scripts/libs/underscore.js" type="text/javascript"></script>
<script src="scripts/libs/backbone-min.js" type="text/javascript"></script>
<script src="scripts/main.js" type="text/javascript"></script>
</body>
</html>
BackBone code:
/// <reference path="../../scripts/libs/jquery-1.7.1.js" />
/// <reference path="../../scripts/libs/underscore.js" />
/// <reference path="../../scripts/libs/backbone-min.js" />
var Tweet = {
Models: {},
Collections: {},
Views: {},
Templates:{}
}
Tweet.Models.Movie = Backbone.Model.extend({})
Tweet.Collections.Movies = Backbone.Collection.extend({
model: Tweet.Models.Movie,
initialize: function(){
console.log("No Tweets Yet")
}
});
Tweet.Templates.movies = _.template($("#tmplt-Tweets").html())
Tweet.Views.Movies = Backbone.View.extend({
el: $("#mainContainer"),
template: Tweet.Templates.movies,
//collection: new Theater.Collections.Movies(), //Not needed
initialize: function () {
//_.bindAll(this, "render", "addOne", "addAll");
this.collection.bind("reset", this.render, this);
this.collection.bind("add", this.addOne, this);
},
render: function () {
console.log("render")
console.log(this.collection.length);
$(this.el).html(this.template());
this.addAll();
},
addAll: function () {
console.log("addAll")
this.collection.each(this.addOne);
},
addOne: function (model) {
console.log("addOne")
view = new Tweet.Views.Movie({ model: model });
$("ul", this.el).append(view.render());
}
})
Tweet.Templates.movie = _.template($("#tmplt-Tweet").html())
Tweet.Views.Movie = Backbone.View.extend({
tagName: "li",
template: Tweet.Templates.movie,
//events: { "click .delete": "test" },
initialize: function () {
//_.bindAll(this, 'render', 'test');
this.model.bind('destroy', this.destroyItem, this);
this.model.bind('remove', this.removeItem, this);
},
render: function () {
return $(this.el).append(this.template(this.model.toJSON())) ;
},
removeItem: function (model) {
console.log("Remove - " + model.get("Name"))
this.remove();
}
})
Tweet.Router = Backbone.Router.extend({
routes: {
"": "http://localhost/assignment/index.php" //http://localhost:22257/Theater/theater.htm
},
defaultRoute: function () {
console.log("defaultRoute");
Tweet.movies = new Tweet.Collections.Movies()
new Tweet.Views.Movies({ collection: Tweet.movies }); //Add this line
Tweet.movies.fetch();
console.log(Tweet.movies.length)
}
})
var appRouter = new Tweet.Router();
Backbone.history.start();
Any idea? Thanks
You probably are using an outdated version of underscore library, please update it to fix the issue.
Also update to latest version of backbone.js if you still face any other issue
I had the same issue and I resolved it by rearranging the order of js files to load in script tag. Make sure that jquery.js file is loaded before backbone.js file. This has resolved my issue.
I agree that this issue is most likely caused by some libraries (backbone, underscore or jQuery) being out of date. To resolve please update to the latest, the below worked for me..
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" type="text/javascript"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.3.3/underscore-min.js" type="text/javascript"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/backbone.js/0.9.2/backbone-min.js" type="text/javascript"></script>
I am making a Backbone.js app, in this i got the issue on the events.. and filter
issue 1:
When i click on the list item events not firing... method is not called
issue 2 : in the Students View "getScore" method not called when i am click on the 'highScoreBtn'..
request :
After i filter the high values from 'scored' - how can i high light that 'li' - to adding some class name ('.highlight)..
what is wrong with my code.. or can any one advice me the right way..
My sample JS:
$(function() {
var student = [
{name:"student0",scored:75},{name:"student1",scored:49},{name:"student2",scored:25},
{name:"student3",scored:96}, {name:"student4",scored:42}, {name:"student5",scored:85},
{name:"student6",scored:68}, {name:"student7",scored:19}, {name:"student8",scored:85},
{name:"student9",scored:26}
]
var model = Backbone.Model.extend({
defaults:{
name:"undefined",
scored:"0"
}
});
var collection = Backbone.Collection.extend({
model:model
});
var studentView = Backbone.View.extend({
tagName:'li',
className:"list",
events:{
'click .list':"called"
},
template:_.template($("#studentTemplate").html()),
render:function(){
this.$el.append(this.template(this.model.toJSON()));
return this;
},
called:function(){
alert("i a called")
}
});
var studentsView = Backbone.View.extend({
el:$(".page"),
events:{
"click #highScoreBtn":"showHighScore"
},
initialize:function(){
this.collection = new collection(student);
this.render();
},
render:function(){
var that = this;
_.each(this.collection.models, function(item){
that.$el.find('ul').append(new studentView({model:item}).render().el);
})
},
getHighSocre:function(){
return _.each(this.collection, function(item){
return item.get('scored') > 60;
})
},
showHighScore:function(){
this.getHighSocre();
}
})
var newStudentList = new studentsView();
});
HTML:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge,Chrome=1" />
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1">
<title>User Manager</title>
<link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet">
<style type="text/css">
li{
border-bottom:1px solid gray;
list-style: none;
padding: 10px;
}
ul{
padding:0;
margin:0;
}
li:last-child{
border: 0;
}
</style>
</head>
<body>
<div class="container">
<h1>Student Manager</h1>
<hr>
<div class="page">
<a id="highScoreBtn" class="btn btn-primary" href="#">Show high Score</a>
<hr>
<ul>
</ul>
</div>
</div>
<script id="studentTemplate" type="text/template">
<strong>Name :</strong> <%= name %> <span>Scored:</span> <%= scored %>
</script>
<script type="text/javascript" src="js/lib/jquery-1.9.1.min.js"></script>
<script type="text/javascript" src="js/lib/underscore-min.js"></script>
<script type="text/javascript" src="js/lib/backbone-min.js"></script>
<script type="text/javascript" src="js/student.js"></script>
</body>
</html>
for issue1:
It looks like StudentView are trying to observe itself.
View should observe DOM event of its child element.
var var studentView = Backbone.View.extend({
tagName:'li',
// className:"list", # move to template
events:{
'click .list':"called"
},
....(omitting)
<script id="studentTemplate" type="text/template">
<span class="list"><strong>Name :</strong> <%= name %> <span>Scored:</span> <%= scored %></span>
</script>
for issue2:
Are showHighScore called correctly?
if you add debug code, what is displayed in console of developer tool?
getHighSocre:function(){
console.log('getHighScore is called');
return _.each(this.collection, function(item){
return item.get('scored') > 60;
})
},
showHighScore:function(){
console.log('showHighScore is called');
var highscore = this.getHighSocre();
console.log(highscore);
}
if functions are called, you should add some codes to do something for highlighting in getHighScore function.
-- EDIT --
It may be possible by observing Backbone Model event in StudentView.
This is not a clean way too much, because model's highlight property should be set on model's initialization.
var model = Backbone.Model.extend({
defaults:{
name:"undefined",
scored:"0",
highlight:false
}
});
var studentView = Backbone.View.extend({
...
initialize: function(){
this.model.on("change", this.highlight, this);
},
...
highlight: function(){
if (this.model.get('hilight')){
$('.list', this.el).addClass("hilight");
}
}
})
var studentsView = Backbone.View.extend({
...
getHighSocre:function(){
_.each(this.collection, function(item){
if (item.get('scored') > 60){
item.set(hilight:true);
}
})
},
...
})
I am not sure, but I hope following code help you:
render:function(){
var that = this;
that.$el.find('ul').empty();
_.each(this.collection.models, function(item){
that.$el.find('ul').append(new studentView({model:item}).render().el);
})
},
showHighScore:function(){
this.getHighSocre();
this.render();
}