Backbone view/template confusion - backbone.js

I am converting this http://localtodos.com/ todo example into a address book but it turning out to be harder than I thought. I saving data to localstorage but I can't seem to get it to display the template. In fact it doesn't seem to want to hit the AddressBookView at all. When I hit index in a browser and add a new contact to saves it just wont show the new one or the previously save contacts. Code is below. Thanks for any help.
$(function(){
var AddressBook = Backbone.Model.extend({
defaults: function() {
return {
name: "No contact name provided",
email: "No email address provided"
};
},
});
// Todo Collection
// ---------------
var AddressBookList = Backbone.Collection.extend({
model: AddressBook,
// Save all of the todo items under the `"todos-backbone"` namespace.
// localStorage: new Backbone.LocalStorage("todos-backbone"),
localStorage: new Backbone.LocalStorage("address-book"),
// We keep the Todos in sequential order, despite being saved by unordered
// GUID in the database. This generates the next order number for new items.
nextOrder: function() {
if (!this.length) return 1;
return this.last().get('order') + 1;
},
// Todos are sorted by their original insertion order.
comparator: 'order'
});
// Create our global collection of **Todos**.
var AddressBookEntries = new AddressBookList;
// Todo Item View
// --------------
// The DOM element for a todo item...
var AddressBookView = Backbone.View.extend({
tagName: "li",
// Cache the template function for a single item.
template: _.template($('#item-template').html()),
// The DOM events specific to an item.
events: {
"click .toggle" : "toggleDone",
"dblclick .view" : "edit",
"click a.destroy" : "clear",
"keypress .edit" : "updateOnEnter",
"blur .edit" : "close"
},
// The TodoView listens for changes to its model, re-rendering. Since there's
// a one-to-one correspondence between a **Todo** and a **TodoView** in this
// app, we set a direct reference on the model for convenience.
initialize: function() {
alert("TESST");
this.listenTo(this.model, 'change', this.render);
this.listenTo(this.model, 'destroy', this.remove);
},
// Re-render the titles of the todo item.
render: function() {
console.log("TEST")
this.$el.html(this.template(this.model.toJSON()));
this.input = this.$('.edit');
return this;
},
// Toggle the `"done"` state of the model.
toggleDone: function() {
this.model.toggle();
},
// Switch this view into `"editing"` mode, displaying the input field.
edit: function() {
this.$el.addClass("editing");
this.input.focus();
},
// Close the `"editing"` mode, saving changes to the todo.
close: function() {
var value = this.input.val();
if (!value) {
this.clear();
} else {
this.model.save({email: value});
this.model.save({name: value});
this.$el.removeClass("editing");
}
},
// If you hit `enter`, we're through editing the item.
updateOnEnter: function(e) {
if (e.keyCode == 13) this.close();
},
// Remove the item, destroy the model.
clear: function() {
this.model.destroy();
}
});
// The Application
// ---------------
// Our overall **AppView** is the top-level piece of UI.
var AppView = Backbone.View.extend({
// Instead of generating a new element, bind to the existing skeleton of
// the App already present in the HTML.
el: $("#address-book"),
// Our template for the line of statistics at the bottom of the app.
//statsTemplate: _.template($('#stats-template').html()),
// Delegated events for creating new items, and clearing completed ones.
events: {
"click #save-entry": "createAddressBookEntry",
"click #clear-completed": "clearCompleted",
"click #toggle-all": "toggleAllComplete"
},
// At initialization we bind to the relevant events on the `Todos`
// collection, when items are added or changed. Kick things off by
// loading any preexisting todos that might be saved in *localStorage*.
initialize: function() {
this.input = this.$("#save-entry");
// this.allCheckbox = this.$("#toggle-all")[0];
this.listenTo(AddressBookEntries, 'add', $("#name").addOne);
this.listenTo(AddressBookEntries, 'reset', this.addAll);
this.listenTo(AddressBookEntries, 'all', this.render);
this.footer = this.$('footer');
this.main = $('#main');
AddressBookEntries.fetch();
},
// Re-rendering the App just means refreshing the statistics -- the rest
// of the app doesn't change.
render: function() {
// var done = AddressBookEntries.done().length;
// var remaining = AddressBookEntries.remaining().length;
if (AddressBookEntries.length) {
this.main.show();
this.footer.show();
// this.footer.html(this.statsTemplate({done: done, remaining: remaining}));
} else {
this.main.hide();
this.footer.hide();
}
// this.allCheckbox.checked = !remaining;
},
// Add a single todo item to the list by creating a view for it, and
// appending its element to the `<ul>`.
addOne: function(entry) {
console.log("Second This" + this);
var view = new AddressBookView({model: entry});
this.$("#contact-list").append(view.render().el);
},
// Add all items in the **Todos** collection at once.
addAll: function() {
AddressBookEntries.each(this.addOne, this);
},
// If you hit return in the main input field, create new **Todo** model,
// persisting it to *localStorage*.
createAddressBookEntry: function(e) {
AddressBookEntries.create({name: $("#name").val()});
AddressBookEntries.create({email: $("#email").val()});
$("#email").val('');
$("#name").val('');
},
});
// Finally, we kick things off by creating the **App**.
var App = new AppView;
});
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Backbone.js Todos</title>
<link rel="stylesheet" href="css/bootstrap.min.css" type="text/css" media="screen" title="no title" charset="utf-8">
<link rel="stylesheet" href="css/narrowJumbotron.css" type="text/css" media="screen" title="no title" charset="utf-8">
<link rel="stylesheet" href="css/app.css" type="text/css" media="screen" title="no title" charset="utf-8">
</head>
<body>
<div class="container">
<div class="header">
<ul class="nav nav-pills pull-right">
<li class="active">Home</li>
<li>About</li>
<li>Contact</li>
</ul>
<h3 class="text-muted">Address Book</h3>
</div>
<div class="jumbotron">
<h1>Jumbotron heading</h1>
<p class="lead"></p>
<p><a class="btn btn-lg btn-success" href="#">Sign up today</a></p>
</div>
<div class="row marketing">
<div class="col-lg-6">
<div id="address-book">
<header>
<input id="name" type="text" placeholder="Name">
<input id="email" type="text" placeholder="Email">
<a id="save-entry" class="btn btn-lg btn-success" href="#">Save</a>
</header>
<section id="main">
<ul id="contact-list"></ul>
</section>
</div>
</div>
</div>
</div>
<script src="js/jquery-1.10.1.min.js" type="text/javascript" charset="utf-8"></script>
<script src="js/underscore.js" type="text/javascript" charset="utf-8"></script>
<script src="js/backbone.js" type="text/javascript" charset="utf-8"></script>
<script src="js/backbone.localStorage-min.js" type="text/javascript" charset="utf-8"></script>
<script src="js/app.js" type="text/javascript" charset="utf-8"></script>
<!-- Templates -->
<script type="text/template" id="item-template">
<div class="view">
// <input class="toggle" type="checkbox" <%= done ? 'checked="checked"' : '' %> />
<label><%- name %></label>
<a class="destroy"></a>
</div>
<input class="edit" type="text" value="<%- name %>" />
</script>
</body>
</html>

You say
this.listenTo(AddressBookEntries, 'add', $("#name").addOne);
I think you meant
this.listenTo(AddressBookEntries, 'add', this.addOne);
You would also like to save model like this
this.model.save({email: value, name: value});
otherwise it will add unnecessary rendering and will fire change event twice.

Related

Angularjs 1.4.7 - Clear and set form to pristine state after submission

After submission, my form displays as dirty with a red border and no submission text. I've tried adding various combinations of .$setPristine and/or .$setUntouched to line 34 in app.js with a return of a green border with submission text still in place.
I've read in articles about using $scopes. Not sure if it is required, I'm not familiar with them.
app.js
* the page hello-world auto-reloads the preview on the right c9 panel */
/* global angular */ /* angular is defined in html document as a src'ed js file. linter says to declare as a global in a comment here */
(function(){
// variables are declared at the top of the function's scope
// three default entries to start with
var entries = [{
title: 'Title1',
body: 'This is a test, 1.',
createdOn: 1397490980837
}, {
title: 'Title2',
body: 'This is a test, 2.',
createdOn: 1397490980837
}, {
title: 'Title3',
body: 'This is a test, 3.',
createdOn: 1397490980837
}];
var app = angular.module('blogPosts', []);
app.controller('EntriesController', function(){
// `this` entry, the current entry for this method, is defaulted to an empty object
this.entry = {};
this.entries = entries;
// method is called when adding an entry
this.addEntry = function() {
// does this.entry exist here? good way to find out is with `console.log(this.entry);` or `debugger;`
this.entry.createdOn = Date.now();
entries.push(this.entry);
console.log("entries",entries);
// reset `this.entry` back to an empty object
this.entry.$setPristine();
this.entry = {};
//this.entry.$setPristine = {};
//this.entry.$clearForm = {};
};
});
})();
index.html
<!DOCTYPE html>
<html ng-app="blogPosts">
<head>
<link rel="stylesheet" type="text/css" href="style.css" /><!-- load Bootstrap -->
<link rel="stylesheet" type="text/css" href="https://bootswatch.com/united/bootstrap.min.css" /><!-- load Bootstrap -->
<script src="angular.js"></script><!-- load angular -->
<script type="text/javascript" src="app.js"></script>
</head>
<body ng-controller="EntriesController as entryCtrl">
<div ng-repeat="entry in entryCtrl.entries">
<h3>{{entry.title}}</h3><cite class="clearfix">{{this.entry.createdOn | date}}</cite><br>
{{entry.body}}
</div>
<!-- Entry Form -->
<form name="entryForm"
ng-submit="entryForm.$valid &&entryCtrl.addEntry(entry)"
noValidate>
<!-- Live Preview -->
<blockquote>
<h3>{{entryCtrl.entry.title}}</h3><br>
{{entryCtrl.entry.body}}
<cite class="clearfix">{{this.entry.createdOn | date}}</cite>
</blockquote>
<!-- Entry Form -->
<h4>Submit an Entry</h4>
<fieldset class="form-group">
<input type="title" class="form-control" placeholder="Title" title="Title" ng-model="entryCtrl.entry.title" required/>
</fieldset>
<fieldset class="form-group">
<textarea class="form-control" placeholder="Write your entry.." title="Entry" ng-model="entryCtrl.entry.body" required></textarea>
</fieldset>
<fieldset class="form-group">
<input type="submit" class="btn btn-primary pull-right" value="Submit Entry" />
</fieldset>
</form>
</body>
</html>
CSS
.ng-invalid.ng-dirty {
border-color: red;
}
.ng-valid.ng-dirty {
border-color: green;
}
From Reddit /u/mcassagnes
app.controller('EntriesController', ['$scope', function($scope){
// ...
this.addEntry = function() {
// ...
$scope.entryForm.$setPristine();
};
}]);
Works!
Yeah this in your this.addEntry is not bound to the lexical this. Basically lost its binding. Best to assign this to a variable in your main controller definition. Something like this to abstract this in controllers. Note you'd have to change the way you'd like to use this syntax. In your view it be EntriesController as vm, and instead of variable just by itself it be vm.variable. Here is a style guide to help solidify what I mean. Hope this helps in your future.
app.controller('EntriesController', function() {
// `this` entry, the current entry for this method, is defaulted to an empty object
var vm = this;
vm.entry = {};
vm.entries = entries;
// method is called when adding an entry
vm.addEntry = function() {
// does this.entry exist here? good way to find out is with `console.log(this.entry);` or `debugger;`
this.entry.createdOn = Date.now();
entries.push(this.entry);
console.log("entries", entries);
// reset `this.entry` back to an empty object
vm.entry.$setPristine();
vm.entry = {};
//this.entry.$setPristine = {};
//this.entry.$clearForm = {};
};
});

backbone and underscore template rendering

I'm trying to use backbone to show on a page the result from an API call, I would like to iterate over the collection and create one entry for every element of the collection within my html. It seems I'm missing something cause I see the template tag rendered but none of my items are there. What's the problem with my code?
here the html
<div class="form-group" id="main">
<% _.each(collection, function(car) { %>
<div class="form-group">
<input class="form-control" /><%= car.get("model") %>
</div>
<% }); %>
</div>
and here js
var CarView = Backbone.View.extend({
el: "#main",
template: _.template($("#main").html()),
initialize: function() {
this.render();
},
render: function() {
$(this.el).html(this.template({collection: [{id:1, model:"ford"}, {id:2,model:"kia"}]}));
return this;
}
});
var carView = new CarView();
here the fiddle: https://jsfiddle.net/e5hg6rzp/3/
First of all I suggest you to keep your template in <script type='text'/template> ... </script> tag. Secondly you are using .get() method inside your template on plain objects which are do not have this method. In your example you can access property through a . -
<div class="form-group">
<input class="form-control" /><%= car.model %>
</div>
Check this fiddle
If you want to use Backbone.Collection when you should create Car Collection and Car Model:
var data = [{
id: 1,
model: "ford"
}, {
id: 2,
model: "kia"
}];
var CarView = Backbone.View.extend({
el: "#main",
template: _.template($("#templ").html()),
initialize: function() {
this.render();
},
render: function() {
return this.$el.html(this.template(new CarCollection(data)))
}
});
var CarModel = Backbone.Model.extend({
defaults: {
id: '',
model: ''
}
})
var CarCollection = Backbone.Collection.extend({
model: CarModel
})
var carView = new CarView();
<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.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone.js"></script>
<div class="container">
<div class="form-inline panel panel-default">
<div class="form-group" id="main">
</div>
</div>
</div>
<script type='text/template' id='templ'>
<% _.each(models, function(car) { %>
<div class="form-group">
<input class="form-control" />
<%= car.get('model') %>
</div>
<% }); %>
</script>

Access child scope corresponding to a model variable

I have a list of items and I've rendered them in a template via ng-repeat. Each of them is controlled by an itemController which exposes some behavior for item (e.g. grabing focus). Here is the HTML:
<body ng-controller="mainController">
<button ng-click="addItem()">add</button>
<ul>
<li ng-repeat="item in items" ng-controller="itemController">
<div ng-if="item.isEditing">
<input ng-model="item.name"/>
<button ng-click="item.isEditing=false">done</button>
</div>
<span ng-if="!item.isEditing">{{item.name}}</span>
</li>
</ul>
</body>
In the mainController, I have a function for adding a new item to items. Here is the code for mainController:
app.controller('mainController', function($scope){
$scope.items = [
{
name: "alireza"
},
{
name: "ali"
}
];
$scope.addItem = function(){
$scope.items.push({isEditing: true});
}
});
Whenever I add an item to items array, a corresponding li element is added into the view which is controlled by an instance of itemController, and the corresponding model is the new item I've just added (or maybe the scope of the itemController, which contains item).
Problem:
When I add some item to items, I only have access to item and not the scope of the recently created item. So I can't run some function (like grabFocus) on the scope of new item.
Is it something semantically wrong in my design? What is the canonical approach for this problem?
Plunker link
Here is the plunker link with related comments
You can use $broadcast from the parent scope, along with $on from the child scope, to notify the child scopes of the newly added item. And by passing (as an argument) the $id of the child scope that corresponds to the newly added item, each child catching the event can know whether or not it's the one that needs to have grabFocus() called.
Here's a fork of your Plunker that uses that approach. I wasn't sure what you were trying to accomplish with $element.find(":text").focus(); in your original Plunker, so I tweaked it to toggle a $scope property that in turn controlled a style in the view. The newly added item will be red (by calling its own grabFocus function to toggle the flag to true), and the others will be black (by calling their own loseFocus function to toggle the flag to false).
Modified HTML (just the repeated li):
<li ng-repeat="item in items" ng-controller="itemController">
<div ng-if="item.isEditing">
<input ng-model="item.name"/>
<button ng-click="item.isEditing=false;handleItemAdded($index);">done</button>
</div>
<span ng-if="!item.isEditing" ng-style="{ color: isFocused ? 'red' : 'black' }">{{item.name}}</span>
</li>
Full JavaScript:
var app = angular.module("app",[]);
app.controller('mainController', function($rootScope, $scope){
$scope.items = [ { name: "alireza" }, { name: "ali" } ];
$scope.addItem = function(){
$scope.items.push({isEditing: true});
};
$scope.handleItemAdded = function (index) {
// $rootScope.$broadcast('item-added', { index: index });
for(var cs = $scope.$$childHead; cs; cs = cs.$$nextSibling) {
if (cs.$index === index) {
$rootScope.$broadcast('item-added', { id: cs.$id });
break;
}
}
};
});
app.controller('itemController', function($scope, $element){
$scope.$on('item-added', function (event, args) {
if ($scope.$id === args.id + 1) {
$scope.grabFocus();
} else {
$scope.loseFocus();
}
});
$scope.grabFocus = function() {
$scope.isFocused = true;
};
$scope.loseFocus = function() {
$scope.isFocused = false;
};
});
I changed your approach a little bit by creating an unique id for every input, based on its index number. See code below, hope it helps.
// Code goes here
var app = angular.module("app",[]);
app.controller('mainController', function($scope,$timeout){
$scope.items = [
{
name: "alireza"
},
{
name: "ali"
}
];
$scope.addItem = function(){
$scope.items.push({isEditing: true});
$timeout(function(){
document.getElementById("newItem"+($scope.items.length-1)).focus();
},0)
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<!DOCTYPE html>
<html ng-app="app">
<head>
<script data-require="angular.js#1.3.0" data-semver="1.3.0" src="//code.angularjs.org/1.3.0/angular.js"></script>
<link href="style.css" rel="stylesheet" />
<script src="script.js"></script>
</head>
<body ng-controller="mainController">
<button ng-click="addItem()">add</button>
<ul>
<li ng-repeat="item in items">
<div ng-if="item.isEditing">
<input ng-model="item.name" id="newItem{{$index}}"/>
<button ng-click="item.isEditing=false">done</button>
</div>
<span ng-if="!item.isEditing">{{item.name}}</span>
</li>
</ul>
</body>
</html>

Backbone: reset event not showing anything in li

I'm working with Backbone and for some reason I can't seem to show anything with the reset event. The stranger part is that when I trigger the add event it shows all my collection.
I'm using version 0.9.2 of Backbone:
Here's the code:
var TodoView = Backbone.View.extend({
tagName: 'li',
template: _.template($('#item-template').html()),
render: function(){
this.$el.html(this.template(this.model.toJSON()));
return this; // enable chained calls
}
});
var CoursesPageView = Backbone.View.extend({
el: '.page',
initialize: function () {
this.input = this.$('#new-todo');
todoList.on('add', this.addAll2, this);
todoList.on('reset', this.addAll, this);
console.log("getting ready to fetch localstorage");
todoList.fetch(); // Loads list from local storage
// Also triggers reset event
},
render: function(){
console.log("function: render2");
var template = _.template($("#courses").html(),{});
this.$el.html(template);
},
events: {
'keypress #new-todo': 'createTodoOnEnter'
},
createTodoOnEnter: function(e){
if ( e.which !== 13) { // ENTER_KEY = 13
return;
}
console.log("function: createOnEnter");
todoList.create({title: $('#new-todo').val()}); // triggers add event
console.log("passed to localstorage: " + $('#new-todo').val());
$('#new-todo').val(''); // cleans input box once enter is pressed
},
addOne: function(todo){
var view = new TodoView({model: todo});
console.log("show li");
$('#todo-list').append(view.render().el);
},
addAll: function(){
console.log("reset event triggered");
this.$('#todo-list').html(''); // clean the todo list
todoList.each(this.addOne, this);
},
addAll2: function(){
console.log("add event triggered");
this.$('#todo-list').html(''); // clean the todo list
todoList.each(this.addOne, this);
},
});
and for the index.html file part:
<script type="text/template" id="courses">
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<h4 align="center" style="color:white;">Courses</h4>
</div>
</div>
</div>
<div class="container">
Add Course
<input id="new-todo">
<section id="main">
<ul id="todo-list"></ul>
</section>
</div>
</script>
<script type="text/template" id="item-template">
<div class="view">
<label><%- title %></label>
</div>
</script>

Backbone & jQuery Tmpl - applying style to template item

my backbone view looks like this below and is rendered with the jQuery tmpl library. I want to apply a style to one/all/any of the data items for which
active==1. Any ideas on how to do this?
// backbone view
window.CaseView = Backbone.View.extend({
el: $("#main"),
initialize: function() {
_.bindAll(this, 'render');
this.render();
},
iTemplate: $("#tmplCase").template(),
render: function() {
var that = this;
that.el.fadeOut('fast', function() {
$.tmpl(that.iTemplate, that.model.toJSON()).appendTo(that.el);
that.el.fadeIn('fast');
});
return this;
}
});
// html file
<div id="main"></div>
<script id="tmplCase" type="text/x-jquery-tmpl">
<div class="caseInActive">
<span class="title">${title}</span>
<span class="current_status">${active}</span>
</div>
</script>
you can add if statements to your template:
// html file
<script id="tmplCase" type="text/x-jquery-tmpl">
<div {{if active == 1}}class="caseInActive"{{/if}}>
<span class="title">${title}</span>
<span class="current_status">${active}</span>
</div>
</script>
http://api.jquery.com/template-tag-if/

Resources