Marionette views not adding a line break after each element - backbone.js

I'm trying to create a simple list using Marionette's CollectionView and ItemView.
iv = Marionette.ItemView.extend({
tagName: "li",
className: "card",
template: _.template("empty-template")
});
cv = Marionette.CollectionView.extend({
tagName: "ul",
className: "card-list",
childView: iv
});
collection = new Backbone.Collection([
{ name: "alice" },
{ name: "bob" },
{ name: "carol" }
);
view = new cv({collection: collection});
view.render()
This works just fine, creates the list with three items and no content (the template is empty). But the on html code produced everything is stuck together, with no line breaks. Like this:
<ul class="card-list"><li class="card"></li><li class="card"></li><li class="card"></li></ul>
Is there a way of getting this instead?
<ul class="card-list">
<li class="card"></li>
<li class="card"></li>
<li class="card"></li>
</ul>
jsFiddle
EDIT: Any type of space would do. The li are inline-block and need to be spaced to be able to use justify on the ul.

Ok this is a guess, but maybe you can just change the default handler of attaching the html. So your "cv" code will look like this:
cv = Marionette.CollectionView.extend({
tagName: "ul",
className: "card-list",
childView: iv,
attachBuffer: function(collectionView, buffer) {
collectionView.$el.append(buffer + '\n');
},
});

Related

Adding dynamic bootstrap tab before the existing 'addition' tab

I'm working with backbone.js and have following listView for <ul> element and a separate tabView for dynamic <li> element. In the render method of listView, I'm creating a new tabView and appending that el to listView el.
var listView = Backbone.View.extend({
//<ul> element for tabs
el: '.nav-tabs',
render: function(model) {
var tabView = new TabView({ model: model });
tabView.render();
$(this.el).append(tabView.el);
}
var TabView = Backbone.View.extend({
//create <li> element to hold each tab
tagName: "li",
className: "currentTab ",
render() {
var html = this.template(this.model.attributes);
$(this.el).append(html);
//creates new div for tab content
var tabContent = new TabContentView({ model: this.model });
tabContent.render();
}
This is fine and works as expected.
To add a new tab dynamically, I have a single li item at the start, so when the user clicks on that li item only new tab creation happens.
Now what I need is to add the newly creating tab before li + element. Currently all the new tabs are getting added only after this + element.
Following is the html of the <ul> tag for reference.
<div id="test">
<ul class="nav nav-tabs ">
<li>+</li>
</ul>
</div>
I tried out changing listView render method like below, but that doesn't work. Rather it just adds the new tab on top of (+) li element itself.
tabView.render();
$(this.el).find(".add-newTab").before(tabView.el);
Any idea how this can be done?
jQuery offers prepend or before methods depending on what you really want.
prepend
<ul class="nav nav-tabs ">
<li>prepending adds element here</li>
<li></li>
<li class="plus">+</li>
</ul>
before
<ul class="nav nav-tabs ">
<li></li>
<li>before adds element here when used on $('.plus')</li>
<li class="plus">+</li>
</ul>
Here's a simplified implementation of your list and tabs:
var TabView = Backbone.View.extend({
//create <li> element to hold each tab
tagName: "li",
className: "currentTab", // why? all tabs will have "currentTab"
initialize: function() {
//creates new div for tab content
this.tabContent = new TabContentView({
model: this.model
});
},
// render should only renders, and should be idempotent.
render: function() {
this.$el.empty().append(tabContent.render().el);
// returning "this" is the default in Backbone, which enables
// chaining method calls.
return this;
}
});
var ListView = Backbone.View.extend({
//<ul> element for tabs
el: '.nav-tabs',
template: '<li class="plus">+</li>',
events: {
"click .add-newTab": "onAddTab",
},
render: function() {
this.$el.empty().append(this.template);
// cache the '+' li element.
this.$plus = this.$('.plus');
return this;
},
onAddTab: function(e) {
var tabView = new TabView({ model: this.model });
// the magic happens here.
// if you want the tab at the beginning of the ul:
this.$el.prepend(tabView.render().el);
// or if you want it at the end, but before the + :
this.$plus.before(tabView.render().el);
},
});
You don't need to use the global jQuery to select elements, Backbone views have their own element pre-scoped and cached accessible through this.$el.
If you really need to find an element inside the view's el, you can select it using this.$('.element-you-want') which is a shortcut for:
$(this.el).find('.element-you-want')

Can't display Backbone/Underscore template in Jade view

I'm trying to link the template in my .jade view to my Backbone model, and it's just not displaying anything inside my template script.
My .jade view:
extends ../layout
block content
.page
script(type="text/template" id="createRecipeTemplate").
<div class="ingredients-pane">
<form id="ingredientForm">
*[form]*
</form>
*[etc.]*
</div>
script(src='/js/myBackboneFile.js')
myBackboneFile.js:
var Recipe = Backbone.Model.extend({
defaults: {*[defaults]*}
});
var Recipes = Backbone.Collection.extend({
url: '/api/recipes'
})
var recipes = new Recipes();
var RecipeView = Backbone.View.extend({
model: new Recipe(),
el: '.page',
initialize: function() {
this.template = _.template($('#createRecipeTemplate').html());
},
events: {
*[events]*
},
*[functions for my events]*,
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
var RecipeView = new RecipeView();
Everything's linked up - if I type 'RecipeView' into the browser console, it gives me:
n {cid: "view2", $el: n.fn.init[1], el: div.page}
I've tried a few variations on the jade template syntax - putting pipes at the beginning of each line, using the dot after the script tag (as above), and just indenting normally (with the template in both standard HTML and Jade syntax each time), but no joy.
Am I missing something obvious?

Backbone - Cannot get model on click using localstorage

I'm trying to setup a little app in backbone where I can add items to a list and, when I click them, they'll be deleted. I've managed to add items to the list but when using model.destroy() nothing happens.
When I console.log the click event on the list models I get:
child {cid: "c0", attributes: Object, _changing: false, _previousAttributes: Object, changed: Object…}
for any item I click.
Code is below:
Html:
<h1>INDEX!</h1>
<form class="add-form">
<input type="text" name="name"/>
<hr />
<button type="submit" class="btn">Submit</button>
</form>
<h2>LIST STUFF</h2>
<ul class="blah">
{{#each indexCollection}}
<li class="li-class">{{name}}</li>
{{/each}}
</ul>
Javascript:
//Local Storage
App.Storage.Local = new Backbone.LocalStorage('localIndexList1-backbone');
//Index Model
App.Models.IndexModel = Backbone.Model.extend({
localStorage: App.Storage.Local,
defualts:{
name:''
},
urlRoot: '/'
});
//Index Collection
App.Collections.IndexCollection = Backbone.Collection.extend({
localStorage: App.Storage.Local,
model: App.Models.IndexModel,
initialize: function(){
console.log('Collection initialised');
},
url: '/'
});
//View for H1 and input form
App.Views.IndexView = Backbone.View.extend({
el: '.page',
events:{
'submit .add-form' : 'addNew',
'click' : 'deleteMe'
},
initialize: function(){
console.log('IndexView initialised');
},
addNew: function(ev){
// ev.preventDefault();
var submitEntry = $(ev.currentTarget).serializeObject();
var newEntry = new App.Models.IndexModel();
newEntry.save(submitEntry, {
success: function(newEntry){
// router.navigate('', {trigger: true});
console.log('SUCESSS!!!!!!!!!');
}
});
},
deleteMe: function(){
console.log(this.model);
//Whatever I put here will not work
}
});
//View for list
App.Views.ListView = Backbone.View.extend({
el: '.page',
initialize: function(){
console.log('ListView initialised');
},
template: Handlebars.compile($('#list').html()),
render: function(){
this.$el.html(this.template);
var that = this;
var indexCollection = new App.Collections.IndexCollection();
indexCollection.fetch({
success:function(indexCollection){
that.$el.html(that.template({indexCollection: indexCollection.toJSON()}));
}
});
}
});
Would anyone be able to help letting me know where I am going wrong?
Thanks!
Where are you creating one IndexView for each of your collection models? You should have an item view, configure its model to be one IndexModel, and move your delete code to that particular view. When you do that, you should also call remove in this item view.
This is why something like Backbone.Marionette helps a lot. Just throw in a CollectionView and you're done.
Think of it like this:
"list view" -> has a collection
"item view" -> has a single model
Anything you need to on the collection level (like adding a new one, re-loading, whatever), do it on your list view. Anything you need on model level (editing, saving, deleting), do it on your item view.

BackboneJS + HandlebarsJS - How to avoid HTML to be rendered multiple times

I have a Backbone application where I trigger a View through an event-click and the Views HTML gets rendered with HandlebarsJS and displays data from a collection. So far it works except that my HTML gets repeated for each entry
My HTML looks like this:
<header>
<span class="blackdot" aria-hidden="true" />
<h1>Overview</h1>
</header>
<div>
<ul>
<li>
{{brand}}
<p>{{country}}</p>
</li>
</ul>
</div>
Right now, it duplicates the whole HTML code-block, including <header> and <h1>-tag for each entry but what I want to achieve is that my HTML would look like this:
<header>
<span class="blackdot" aria-hidden="true" />
<h1>Overview</h1>
</header>
<div>
<ul>
<li>
Audi
<p>Germany</p>
</li>
<li>
Hyundai
<p>South Korea</p>
</li>
<li>
Fiat
<p>Italy</p>
</li>
</ul>
</div>
My backbone View looks like this:
define(['backbone','handlebars', 'text!templates/Cars.html'],
function(Backbone,Handlebars, Template) {
'use strict';
var CarsView = Backbone.View.extend({
template: Handlebars.compile(Template),
events: {
},
initialize: function () {
_.bindAll(this, 'render');
},
render: function() {
var self = this;
self.collection.each(function(model){
self.$el.append(self.template({
brand:model.get('brand'),
country:model.get('country')
})
);
});
return this;
}
});
return CarsView;
}
);
Of course i could define the most of the HTML on the index page, wrap it in a DIV and do display:none and only write the <li>-tag in the Handlebars HTML template, but I want to avoid that and since the data what is returned are strings, I cant do the {{#each}}-thing... so, is there any solution for this?
To add onto what guzmonne said, the reason you are seeing this is because you are looping over the entire template. What you should be doing is taking the <li>'s and making a new template specifically for them. I've modified your existing code to show how something like what you are trying to do can be accomplished.
The CarsView Handlebars template:
<header>
<span class="blackdot" aria-hidden="true" />
<h1>Overview</h1>
</header>
<div>
<ul id="cars_list" />
</div>
The CarsView Backbone View:
var CarsView = Backbone.View.extend({
template: Handlebars.compile(Template),
events: {},
initialize: function () {
_.bindAll(this, 'render');
},
render: function() {
this.$el.html(this.template()); // this now only renders once
this.addAll();
return this;
},
addOne: function(car) {
var view = new CarView({model: car});
this.$("#cars_list").append(view.render().el);
},
addAll: function() {
this.collection.each(this.addOne, this);
}
});
The main differences between what I have done and your original CarsView is that firstly, the template itself doesn't contain any <li>'s. Instead, there is a placeholder ID which I have titled "cars_list". This container will give us an entry point to loop through the cars collection and add each item. Secondly, we aren't looping through the collection and re-rendering the CarsView. Instead, we take what CarsView dropped into the DOM and manually apppend to it from there.
Normally when dealing with collections, you can leverage Backbone's this.listenTo() function, which can take an event such as "reset" or "add" and tie it to a function. Since it would appear that the collection has already been fetched, we simply do this.addAll() after the CarsView template has been rendered. It is in here that the collection is looped and added.
To accomplish this, you will need another Backbone view, and another template...
The CarView Handlebars template:
{{brand}}
<p>{{country}}</p>
The CarView Backbone View:
var CarView = Backbone.View.extend({
tagName: "li",
template: Handlebars.compile(Template),
events: {},
initialize: function () {
// model event listeners (in case these list items can be edited/removed)
this.listenTo(this.model, 'change', this.render);
this.listenTo(this.model, 'destroy', this.remove);
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
The CarView view is short and simple, but takes care of a lot for you. As you can see, this view will generate an <li> tag and take the contents of the model and send it to the handlebars template. No more having to deal with manually fetching model attributes, which is an added bonus.

Backbone View not creating "itself"

I just started learning Backbone and from what I've seen so far when you create a view and you define a tagName and a className the view you create is created inside that element but it doesn't that it works on the code below, could someone please explain to me why? I've spend wayyy too much time on this and my head is spinning.
var app = {};
(function ($) {
app.Todo = Backbone.Model.extend({
defaults : {
name : '',
priority: '',
description: ''
}
});
app.TodoList = Backbone.Collection.extend({
model: app.Todo,
url: '#todolist'
});
app.TodoListView = Backbone.View.extend({
tagName: 'ul',
className: 'todolist',
initialize: function() {
this.template = _.template($('#todolist-template').html());
this.render();
},
render: function() {
this.$el.empty();
this.$el.append(this.template({items: this.collection.toJSON()}));
return this;
}
});
app.todoList = new app.TodoList([
new app.Todo({
name: 'unclog the sink',
priority: '10',
description: 'FIX THE SINK!!!'
}),
new app.Todo({
name: 'get bread',
priority: '0',
description: 'We are out of bread, go get some'
}),
new app.Todo({
name: 'get milk',
priority: '2',
description: 'We are out of milk, go get some'
})
]);
new app.TodoListView({el: $('#container'), collection: app.todoList});
})(jQuery);
template:
<script type="text/template" id="todolist-template">
<% _.each(items, function(item){ %>
<li>
<%= item.name %>
<%= item.description %>
<%= item.priority %>
</li>
<%}); %>
</script>
result:
<div id="container">
<li>unclog the sink FIX THE SINK!!! 10</li>
<li>get bread We are out of bread, go get some 0</li>
<li>get milk We are out of milk, go get some 2</li>
</div>
I am not expert in BackboneJS but trying to resolve the issue. As per backboneJS doc about el property, its always there in to render backbone view. The default element is <div> if you don't specify. You have understood right, that element would be render inside it.
In your code you are providing <div> element as an el, so its overriding your tagName property when creating new object of view. And another thing is you are calling render before creating object which may cause a problem. So as per my opinion, you code should looks like this :
$("$container").html((new app.TodoListView({collection: app.todoList})).render())
You should read about the el property of View, here. When you specify tagName and className it creates the DOM element <ul class="todolist"></ul> but doesn’t append it to the DOM. If the element already exists in the DOM, you can set el as a CSS selector that matches the element.
So in your case your template is getting created in an ul element, but the ul element itself is not added in the DOM.
Try doing following:
NOTE : div with an id as container should already be present in DOM.
View definition:
app.TodoListView = Backbone.View.extend({
el: '#container',
initialize: function() {
this.template = _.template($('#todolist-template').html());
this.render();
},
render: function() {
this.$el.html(this.template({items: this.collection.toJSON()}));
return this;
}
});
Template:
<script type="text/template" id="todolist-template">
<ul class="todolist">
<% _.each(items, function(item){ %>
<li>
<%= item.name %>
<%= item.description %>
<%= item.priority %>
</li>
<%}); %>
</ul>
</script>
View creation :
new app.TodoListView({collection: app.todoList});

Resources