I am trying to understand how to handle number of collection instances within my Backbone model. There will be a primary model and that model is composed of multiple instances of collections. The collections themselves are groups of some other model.
Model A
Collection B
Model C
Model C
Collection B
Model C
Model C
Model C
Collection B
Model C
Collection B
might even be empty but will only add more Model C's
Here is the Non-working code...
app.js
(function() {
var App = {};
window.App = App;
var template = function(name) {
return Mustache.compile($('#'+name+'-template').html());
};
App.World = Backbone.Model.extend({
initialize: function() {
var this.continent = new Array();
this.continent[0] = new App.Continent(0);
this.continent[1] = new App.Continent(1);
this.continent[2] = new App.Continent(2);
this.continent[3] = new App.Continent(3);
this.continent[4] = new App.Continent(4);
this.continent[5] = new App.Continent(5);
}
});
App.Continent = Backbone.Collection.extend({
initialize: function(id) {
switch (id) {
case 0:
this.id = "Europe";
case 1:
this.id = "Asia";
case 2:
this.id = "Africa";
case 3:
this.id = "Australia";
case 4:
this.id = "South America";
default:
this.id = "North America";
}
}
});
App.Index = Backbone.View.extend({
template: template('index'),
initialize: function() {
this.world = new App.World();
this.world.on('all', this.render, this);
},
render: function() {
this.$el.html(this.template(this));
return this;
},
bigland: function() {
return this.world;
},
});
App.Router = Backbone.Router.extend({
initialize: function(options) {
this.el = options.el
},
routes: {
"": "index"
},
index: function() {
var index = new App.Index();
this.el.empty();
this.el.append(index.render().el);
}
});
App.boot = function(container) {
container = $(container);
var router = new App.Router({el: container})
Backbone.history.start();
}
})()
index.html
<!DOCTYPE html>
<html>
<head></head>
<body>
<h1>Hello world</h1>
<div id='app'>
Loading...
</div>
<script type="text/x-mustache-template" id="index-template">
<ul>
{{#bigland}}
<li>Hello {{.continent}}</li>
{{/bigland}}
</ul>
</script>
<script src="jquery.js"></script>
<script src="underscore.js"></script>
<script src="backbone.js"></script>
<script src="mustache.js"></script>
<script src="app.js"></script>
<script>$(function() { App.boot($('#app')); });</script>
</body>
</html>
the output I am trying to get from this demo
Hello Europe
Hello Asia
Hello Africa
Hello Australia
Hello South America
Hello North America
Questions:
How do I get this code to work using the model with multiple instances of collections.
Is there a better way structure this model? I am open to using backbone plugins for nested models but like to see some working code.
The next level of experimentation is to enable some instances of Collection B to specialize and utilize different business rules. Any thoughts on how to organize this mess like where to put helper methods? Any best practice on where that logic would reside, I could put it in the main model A, inside the Collection B, or elsewhere?
JS Bin link:
http://jsbin.com/ejohom/1/edit
I only explored a different way to structure your Model and Collection. I created a testData structure which would represent how you are loading this data at init time.
Also you will see a continent finder function in the collection.
Related
In Backbone app, I need to use localstorage to save some history data. And when the app was loaded for the second time, the history data will be loaded to the page
And the app is still the ToDo task app, the difference, I want to support multiple days record.
so the data structure for my app is as following: one collection (DayCollection), and the model of the collection (Daymodel). Here I use backbone relation extension to support the nested relationship, there is the second model (Todo). And Daymodel and Todo has 1:many relationship.
// A simple todo model
var app = {};
Todo = Backbone.RelationalModel.extend({
defaults: {
title: "New Todo",
completed : true
}
});
var Daymodel = Backbone.RelationalModel.extend({
day: 1,
relations: [{
type: Backbone.HasMany,
key: 'agenda',
relatedModel: 'Todo',
}]
});
var DayCollection = Backbone.Collection.extend({
model: Daymodel,
//localStorage: new Backbone.LocalStorage("test")
});
// A view for an individual todo item
var TodoView = Backbone.View.extend({
tagName: "li",
template: _.template( $('#eachitem-template').html() ),
initialize: function() {
this.listenTo(this.model, "change", this.render);
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
// The view for each day panel
var DayView = Backbone.View.extend({
tagName:"div",
template: _.template( $('#eachday-template').html() ),
initialize: function() {
//console.log("call Dayview init")
this.listenTo(this.model, "change", this.render);
this.listenTo(this.model.get("agenda"), "add",this.addNewTodo);
//this.listenTo(this.model,"reset",this.addAll);
//this.model.fetch({reset: true});
//this.model.get("agenda").fetch();
//console.log("current model")
//console.log(this.model);
},
render: function(){
this.$el.html(this.template(this.model.toJSON()));
return this;
},
addNewTodo: function(todo){
//console.log("debugging addNewTodo");
//console.log(todo);
var newTodoView = new TodoView({model:todo})
//console.log("generated new todo view");
//console.log(newTodoView.render().el);
this.$("ul").append(newTodoView.render().el);
//console.log(this.$("ul").html());
},
addAll: function(){
this.model.get("agenda").each(function(eachitem){
var newTodoView = new TodoView({model:eachitem});
this.$("ul").append(newTodoView.render().el);
});
}
});
// The view for the entire application
var AppView = Backbone.View.extend({
el: $('#todoapp'),
events: {
"click #add-todo" : "createTodo",
"click #add-firebase":"addToFirebase"
},
initialize: function() {
this.daylist = this.$("#container"); // the daylist to append to
this.input = this.$("#new-todo"); // the textbox for new todos
// by listening to when the collection changes we
// can add new items in realtime
this.listenTo(this.collection, 'add', this.addOne);
this.listenTo(this.collection,'reset', this.addAll);
//this.collection.fetch({reset:true});
},
addOne: function(todo) {
//console.log("debugging add one more day")
//console.log(todo)
var view = new DayView({model:todo});
this.daylist.append(view.render().el);
},
addAll: function(){
var self = this;
this.collection.each(function(eachday){
self.addOne(eachday)
});
},
createTodo: function(e) {
if (!this.input.val()) { return; }
// create a new location in firebase and save the model data
// this will trigger the listenTo method above and a new todo view
// will be created as well
//this.collection.last().get("agenda").add({
this.collection.last().get("agenda").add({
title: this.input.val()
});
//this.collection.last().save();
this.input.val('');
},
addToFirebase: function(){
//this.collection.add({
this.collection.create({
day : this.collection.length + 1,
agenda: []
});
}
});
// Create a function to kick off our BackboneFire app
function init() {
// The data we are syncing from our remote Firebase database
var collection = new DayCollection();
var app = new AppView({ collection: collection });
}
// When the document is ready, call the init function
$(function() {
init();
});
<div id="todoapp">
<div id="container">
</div>
<input type="text" id="new-todo" placeholder="New Todo" />
<button id="add-todo">Add New Task</button>
<button id="add-firebase">Add New Day</button>
</div>
<script type="text/template" id="eachday-template">
<h3 class="which-day"> day <%= day %></h3>
<ul id="todo-list"></ul>
</script>
<script type="text/template" id="eachitem-template">
<p class="item-content"><%= title %></p>
</script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone-relational/0.10.0/backbone-relational.min.js"></script>
I remove the non-business code, only show the related code here. To make it easier to read the post.
AppView is called with the DayCollection and for each day the DayView will work for that (for each todo, there is another view, I didn't put it here).
And the current status is: the localstorage data is correct (I use debugging method to verify) and the Day level view can work well. The problem, the todo tasks for each day can't be fetched to the page.
In fact I use console.log(this.$("ul").html()); this line to verify, in fact the element content is also correct. It just can't update to view.
so any idea about this?
Edit:
I make use of the Stack snippet tool to show the demo, but I think localstorage can't work within the environment, so I comment off the fetch and save method.
The behavior is: click "Add New Day" button will add a new day panel like day1, day2 and go on. And click the "Add New Task" button will add a new task to the last Day panel.
With localstorage, I hope to show all Days' data with their tasks when the page loaded again.
I am trying to pass the JSON output from one view to another view. But I stuck here as I don't know to how move forward. It seems, some event handling, triggers may help. I searched, but I couldn't figured out! I pasted my complete code. Backbone+underscore+jquery front-end with restful back-end. There 5-7 sub-category level. When we print the main category, it should have a hyperlink to navigate to the next sub-category & the sub-category will have links to another level. I successfully got main-category output. Now the main category view has some JSON values to link to the sub-category. I just need some idea on how to pass the JSON value to the another view(sub-category view) to post it to server to get the sub-category list. Hope, it makes clear.
<body>
<div class="page"></div>
<script src="https://code.jquery.com/jquery-1.8.2.min.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.2/underscore-min.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/0.9.2/backbone-min.js" type="text/javascript"></script>
<script type="text/template" id="user-list-template">
<% _.each(mainCategoryListResponseCollection, function(MainCategoryListResponse) { %>
<p><%= MainCategoryListResponse.get('directoryName') %></p>
<% }); %>
</script>
var MainCategoryListResponse = Backbone.Model.extend({
urlRoot:'/main-dirctory',
defaults:{
"clientCompanyName":"GOD",
"clientCountry":"ALL Country"
"mainCategoryId":"", // This Id from server is used to get the sub-category list
"rootId"":''// This Id from server is used to get the sub-category list
}
});
var MainCategoryListResponseCollection = Backbone.Collection.extend({
model: MainCategoryListResponse,
url: '/main-dirctory'
});
var MainCategoryListView = Backbone.View.extend({
el: '.page',
render: function() {
var that = this;
var mainCategoryListResponseCollection = new MainCategoryListResponseCollection();
// mainCategoryListResponseCollection.set({"typeId": "1"});
mainCategoryListResponseCollection.fetch({
type: 'POST',
data: JSON.stringify({'typeId': '1'}), //Passing the value manually here in the first parent view
contentType: "application/json",
dataType: "json",
success: function (mainCategoryListResponseCollection) {
var template = _.template($('#user-list-template').html(), {mainCategoryListResponseCollection: mainCategoryListResponseCollection.models});
that.$el.html(template);
}
});
}
});
var SubCategoryListView = Backbone.View.extend({
el: '.page',
render: function() {
var that = this;
var subCategoryListResponseCollection = new SubCategoryListResponseCollection();
subCategoryListResponseCollection.fetch({
type: 'POST',
data: JSON.stringify({'???': '???'}), //here I need to pass the dynamic value from the above view. The dynamic value should be like "mainCategoryId": "5", "rootId": "0" which is getting printed in the each loop down here.
contentType: "application/json",
dataType: "json",
success: function (subCategoryListResponseCollection) {
var template = _.template($('#user-list-template').html(), {subCategoryListResponseCollection: subCategoryListResponseCollection.models});
that.$el.html(template);
}
});
}
});
var Router = Backbone.Router.extend({
routes:{
"":"main_directory"
}
});
var MainCategoryListView = new MainCategoryListView();
var router = new Router();
router.on('route:main_directory', function() {
MainCategoryListView.render();
});
Backbone.history.start();
</script>
</body>
I am trying to figure out how to use a router and controller in my Marionette.js application. I am able to start the initial page in my App's start handler, but I can't seem to figure out how to handle other routes. This SPA isn't complex, where I only have three pages for my users. One is a leads table view, a vehicle table view, and a view of a single vehicle. I'm not worried about the single vehicle view until I figure out how this routing works.
// my app
var App = new Marionette.Application({});
// my lead and vehicle model rows
App.vehicleRowView = Marionette.ItemView.extend({
tagName: 'tr',
template: '#vehicle-row-tpl'
});
App.leadRowView = Marionette.ItemView.extend({
tagName: 'tr',
template: '#lead-row-tpl'
});
// composite views for the tables
App.vehicleTableView = Marionette.CompositeView.extend({
tagName: 'div',
className: 'row',
template: '#vehicles-table',
childViewContainer: 'tbody',
childView: App.vehicleRowView
});
App.leadsTableView = Marionette.CompositeView.extend({
tagName: 'div',
className: 'row',
template: '#leads-table',
childViewContainer: 'tbody',
childView: App.leadRowView
});
// controller
var Controller = Marionette.Object.extend({
leads: function() {
var leadstable = new App.leadsTableView({
collection: this.leads
});
App.regions.leads.show(leadstable);
},
vehicles: function() {
console.log('vehicles...');
}
});
// router
var AppRouter = Marionette.AppRouter.extend({
controller: new Controller,
appRoutes: {
'leads': 'leads',
'vehicles': 'vehicles'
}
});
App.router = new AppRouter;
App.vehicles = [];
App.leads = [];
// Start handlers
App.on('before:start', function() {
this.vehicles = new Vehicles();
this.vehicles.fetch();
this.leads = new Leads();
this.leads.fetch();
var appContainerLayoutView = Marionette.LayoutView.extend({
el: '#app-container',
regions: {
vehicles: '#vehicles-content',
leads: '#leads-content'
}
});
this.regions = new appContainerLayoutView();
});
App.on('start', function() {
Backbone.history.start({pushState: true});
var vehiclesLayoutView = new this.vehicleTableView({
collection: this.vehicles
});
App.regions.vehicles.show(vehiclesLayoutView);
});
App.start();
On start, the front page is fine. However, when I go to #leads, my leads table doesn't render. Actually, the route doesn't happen, and the URL changes to /#leads. If I then go to that URL, the table skeleton renders, but not the data. The collections are loaded fine on before:start, and the templates are fine. I have to go to the URL twice, but the table has no data, even though my App.leads collection is loaded fine. My console.log output confirms I am hitting the route, though.
I want to hide the vehicles region when the user goes to the #leads route. When the user goes to #vehicles, I then want to hide my leads table and display the vehicles (same view from my start handler).
I feel like I'm right there, but missing something basic.
By looking at your vehicles and leads regions, I have a suspicion you've misunderstood the role of regions. If you expect them to swap one another, then you would create just one region and have that region .show( new VehiclesView() ); when you want to show vehicles, and .show( new LeadsView() ); when you want the leads to replace the vehicles.
And here's a working example:
var app = new Mn.Application();
var Controller = Mn.Object.extend({
leads: function(){
app.regions.setActive('leads').getRegion('main').show( new LeadsView() );
},
vehicles: function(){
app.regions.setActive('vehicles').getRegion('main').show( new VehiclesView() );
}
});
var VehiclesView = Mn.ItemView.extend({
template: _.template('،°. ˘Ô≈ôﺣ » » »')
});
var LeadsView = Mn.ItemView.extend({
template: _.template("( /.__.)/ (.__.)")
});
var AppLayoutView = Mn.LayoutView.extend({
el: '#app',
regions: { main: 'main' },
events: { 'click nav a': 'onClick' },
onClick: function(evt){
evt.preventDefault();
var viewName = evt.currentTarget.dataset.view;
app.controller[viewName]();
app.router.navigate(viewName);
},
setActive: function(viewName){
/** it might seem that it is easier to just
make the link bold on click, but you would have
to handle it if you want to make it active on page load */
this.$('nav a').
removeClass('active').
filter('[data-view="'+viewName+'"]').
addClass('active');
return this;
}
});
app.on('start',function(){
app.regions = new AppLayoutView();
app.controller = new Controller();
app.router = new Mn.AppRouter({
controller: app.controller
});
Backbone.history.start({pushState: true});
/** show initial content */
app.controller.leads();
app.router.navigate('leads');
});
app.start();
.active { font-weight :bold ;}
<script src='http://code.jquery.com/jquery.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.2.3/backbone.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/backbone.marionette/2.4.3/backbone.marionette.js'></script>
<div id="app">
<nav>
Vehicles
Leads
</nav>
<main></main>
</div>
I am new bee to backbone and i am trying with a below sample
http://jsfiddle.net/naveencgr/L3orucjm/
While loading i am getting this.el.html is not a function, let me know what is the cause of it.
HTML:
<div id="container"></div>
<script type="text/template" id="template">
<input type="text" name="Name" id="Name"></input>
<input type="button" name="Add" id="Add" value="Add"></input>
<div id="list"></div>
</script>
JavaScript:
NameModel = Backbone.Model.extend({
});
var nameModel = new NameModel();
nameModel.set('name','test');
alert(nameModel.get('name'));
NameView = Backbone.View.extend({
tagName:"li",
render: function(){
var template=_.template("<%=name%>", nameModel.toJSON());
return template;
}
});
var nameView = new NameView();
NameViewList = Backbone.View.extend({
initialize: function(){
this.render();
},
render: function(){
var template = _.template($("#template").html(), {});
this.el.html(template);
},
events : {
"click input#Add" :"addName",
},
addName : function() {
var name = $("#Name").val();
if (name.trim().length > 0) {
//nameModel.set("name", name);
$("#list").append(nameView.render());
}
}
});
var nameViewList = new NameViewList({el : $("div#container")});
You have numerous errors in your code. You know about one of them but not the others.
The one you know about is:
this.el.html(template);
A view's el is just a DOM node and that doesn't have an html function. The html function you're trying to use is part of jQuery so you'd want to call it on this.$el (which is just a cached version of $(this.el)):
this.$el.html(template);
Other problems:
Your fiddle is missing vars all over the place; don't say:
NameModel = ...
say:
var NameModel
to avoid accidental globals.
Your NameView is strange. It has tagName: 'li' so presumably it should be creating list elements but the render doesn't do anything with the view's el, it just returns a string of HTML that ends up inside a <div>. That <div> should be a <ul>:
<ul id="list"></ul>
A render function generally populates the view's el and, to allow chaining, returns this:
render: function() {
var template = _.template('<%= name %>');
this.$el.html(template(nameModel.toJSON()));
return this;
}
You are using Underscore's _.template incorrectly. You used to be able to say:
var h = _.template(template_source, data);
to compile the template and fill it in in one step but as of Underscore 1.7, the second argument to _.template is an options object. Now you need to compile and fill in the template in separate steps:
var t = _.template(template_source);
var h = t(data);
You'll see this change in the render above.
The way you're using your NameView is strange. Apparently you are trying to use one NameView to handle multiple names, this would work with your strange NameView#render implementation but it will fall apart once NameView has anything to do or once NameView is updated (as above) to be more conventional. You should create one NameView for each name you're displaying and each NameView should have its own NameModel as its model property. This would make NameView#render look like:
render: function() {
var template = _.template('<%= name %>');
this.$el.html(template(this.model.toJSON()));
return this;
}
and NameViewList#addName would look like:
addName: function() {
var name = this.$("#Name").val();
if(name.trim().length > 0) {
var nameView = new NameView({
model: new NameModel({ name: name })
});
this.$('#list').append(nameView.render().el);
}
}
You'll note that we're using NameView#render's new return value, this x.append(v.render().el) pattern is quite common and idiomatic in Backbone so it is a good practice. You should also notice that the search for #list is now limited to the view's el by using the view's this.$ function; this.$('#list') is equivalent to this.$el.find('#list') and doing things this way helps you keep your views self-contained.
In real life you'd probably put your new NameModel({ name: name }) instances in a collection somewhere and events on that collection would trigger the creation of new NameViews.
Applying all that to your fiddle gives you this functional and more idiomatic version:
http://jsfiddle.net/ambiguous/8x0ma9qo/
I'm trying to create a Backbone Model (lets call it Library) which contains a Collection of other Models (lets call them Books). I am providing a view - LibraryView, which creates an HTML of a book case with a set of books represented by HTML generated by BookView. Also, I am using Handlebars.js as my templating system.
The issue I am experiencing is that my BookView returns weird html on this.el element, before I even pass it through render() function.
LibraryModel Model
var LibraryModel = Backbone.Model.extend({
initialize: function() {
var books = new BookCollection();
_.each(book_data.books, function(value, index) {
books.add(new Book());
});
this.books = books;
}
});
LibraryView View:
var LibraryView = Backbone.View.extend({
el: "#library",
render: function() {
var t = this;
this.model.books.each(function(book, index) {
//create new view for each Book model in the books collection
var view = new BookView(book);
//append HTML produced by the BookView into LibraryView el element
t.$el.append(view.render().$el);
});
return this;
},
initialize: function() {
//snip
}
});
BookView View:
var BookView = Backbone.View.extend({
render: function() {
var viewmodel = this.model;
var source = $("#book-template").html();
var template = Handlebars.compile(source);
var html = template(viewmodel.toJSON());
console.log(html); //prints <div test="wtf" anotherTest="123"><b>wtf</b> 123</div>
this.$el.html(html);
return this;
},
initialize: function(book) {
console.log(this.el.outerHTML); //prints <div test="wtf" anotherTest="123"></div>
this.model = book;
this.listenTo(this.model, "change", this.render);
}
});
Template I am providing is: <b>{{test}}</b> {{anotherTest}}
BookModel Model
var BookModel = Backbone.Model.extend({
defaults: {
test: "wtf",
anotherTest: 123
},
initialize: function() {
//snip
}
});
Basically, the issue I am exeperiencing is that my BookView produces weird HTML where each of my model attributes is attached to the Backbone-generated div, like this:
<div test="wtf" anotherTest="123">
<b>wtf</b> 123
</div>
I am not setting any of the attributes anywhere else in the code - both values are only coming from the defaults.
Also, I confirmed this is not something that Handlebars is doing, as Model-attributes are inserted as HTML-atributes into Backbone generated div of the BookView model (note, I am not providing tagName or el manually, I want Backbone to create a div for me).
So here is where I am stuck. I have a perfectly working list of HTML generated by BookView for each of my models in the list, but for some reason Backbone-generated div wrapper contains each of Model-attributes in its HTML-attributes, like so:
<div id="#library">
<div test="wtf" anotherTest="123"><b>wtf</b> 123</div>
<div test="wtf" anotherTest="123"><b>wtf</b> 123</div>
<div test="wtf" anotherTest="123"><b>wtf</b> 123</div>
</div>
I am really pulling my hair out over this and I have suspicion it has something to do with the fact I am trying to use View-in-a-View.
Have you encountered similar problems before? Have you got good examples of Backbone application where MasterView renders collection of ChildViews?
You are doing a weird thing when creating new BookViews. That view is expecting a BookModel to come in its initialization. However, the initialize() method always expects an object which is called attributes by convention. You should modify your BookView so it matches this:
var BookView = Backbone.View.extend({
render: function() {
// keep the same code
},
initialize: function(attributes) {
this.listenTo(this.model, "change", this.render);
}
});
and in your LibraryView you should use:
var view = new BookView({ model : book });
instead of the previous:
var view = new BookView(film); // which is a typo, and should pass book and not film
So now you get your expected result.
<div id="library">
<div><b>wtf</b> 123</div>
<div><b>wtf</b> 123</div>
<div><b>wtf</b> 123</div>
</div>
Keep in mind that initialize(attributes) will automatically call set with that mapping, so you don't have to call set(attr, value) yourself. With that in mind, we can understand why that view is having two attributes which are in fact your two model's attributes (they are being set() on initialization).