How to display collection to template? - backbone.js

I have been trying to display my data in collection to the template. On my rest console, I can see that the collection is populated; but I am not able to do it in my application. The code is as follows:
In view, the render is like:
render : function() {
var template = _.template(tpl,{
CollectionForTemplate: this.Collection.toJSON(),
this.el.html(template);
},
In view, the function that calls the fetch is as follows:
loadTariffGrids: function(){
//fetch done here
if (Collection.length > 0) {
_.each(Collection, function(Model) {
JSON.stringify(Model);
alert(JSON.stringify(Model));
}, this);
};
this.render;
}});
},
And finally, the template is:
<span>
<% _.each(CollectionForTemplate, function(model) { %>
</span>
<td><%= model.cost %></td>
<% }); %>
Where am I going wrong?

In your render, just use:
CollectionForTemplate: this.Collection;
Modify the view as follows:
var self = this;
//fetch done here
if (Collection.length > 0) {
_.each(Collection, function(Model) {
JSON.stringify(Model);
}, this);
};
self.Collection = Collection;
self.render;
I think the collection that you pass to the template was not getting instanciated. So I use self to store the reference before control passes into the fetch function. I know my explanation is not good; I would like someone more experienced to offer you a better explanation. Hope this helps

Ok #Amateur let's try to solve your need properly.
Attention: all code has been written in the fine air don't expect it to work just out of the box, use it as inspiration.
First of all is not very good idea to use programatic logic in your templates, it is just a design decision, I don't want to look very academic here but I think if you are too flexible with this you can finish with a very difficult to maintenance code.
So, the idea is to manufacture the data so the template has not to make any decision/calculation. And if you need a loop into your template let's use sub-templates.
What I understand from your code example is that you have a Collection like this:
[
{
"id": "1",
"cost": 100,
"name": "Amateur"
},
{
"id": "2",
"cost": 200,
"name": "Other name"
},
{
"id": "3",
"cost": 300,
"name": "Other name again"
},
]
And you want to render something like this:
<table>
<tr>
<td>Name</td>
<td>Cost</td>
</tr>
<tr>
<td>Amateur</td>
<td>100</td>
</tr>
<tr>
<td>Other name</td>
<td>200</td>
</tr>
<tr>
<td>Other name again</td>
<td>300</td>
</tr>
</table>
As I have read in another (duplicated) question you have sent, you also want to only render cost if the name == "Amateur", like this:
<table>
<tr>
<td>Name</td>
<td>Cost</td>
</tr>
<tr>
<td>Amateur</td>
<td>100</td>
</tr>
<tr>
<td>Other name</td>
<td>--</td>
</tr>
<tr>
<td>Other name again</td>
<td>--</td>
</tr>
</table>
Iteraction
For the iteration we are gonna solve it with sub-templates.
Master template:
<script type="text/template" id="template-elements">
<table>
<tr>
<td>Name</td>
<td>Cost</td>
</tr>
</table>
</script>
Sub-template
<script type="text/template" id="template-element">
<tr>
<td><%= name %></td>
<td><%= cost %></td>
</tr>
</script>
In order to make it more elegant lets use also a View for the Collection and a SubView for each Model.
var MyModel = Backbone.Model.extend();
var MyCollection = Backbone.Collection.extend({
model: MyModel
});
var MyModelView = Backbone.View.extend({
template: _.template( $("#template-element").html() ),
render: function(){
this.$el.html( this.template( this.model.toJSON() ) );
return this;
}
});
var MyCollectionView = Backbone.View.extend({
template: _.template( $("#template-elements").html() ),
render: function(){
this.$el.html( this.template() );
this.collection.each( function( model ){
var view = new MyModelView({ model: model });
this.$el.find( "table" ).append( view.render().el );
});
return this;
}
});
Decorators
We have solved the iteration issue, now we are gonna implement the logic that hides the Model.cost when Model.name != "Amateur".
Let's implement a method that returns the attributes of the Model already cooked for the template:
var MyModel = Backbone.Model.extend({
toJSONCooked: function(){
var json = this.toJSON();
if( this.get( "name" ) != "Amateur" ) {
json.cost = "--";
}
return json;
}
});
We can use this method to feed the template:
var MyModelView = Backbone.View.extend({
template: _.template( $("#template-element").html() ),
render: function(){
this.$el.html( this.template( this.model.toJSONCooked() ) );
return this;
}
});

Related

Scoping issue on ng-change function on HTML SELECT in AngularJS

I am trying to implement, in a larger context, exactly what is being done in FIDDLE, shown here:
HTML:
<div ng-controller="MyCtrl">
<select
ng-options="ptoGroup as ptoGroup.classname for ptoGroup in ptoGroupTypes"
ng-model="ptoItem"
ng-change="ptoUpdateUserQueryForm1()">
</select>
{{ptoItem.classname}}
</div>
Javascript
var myApp = angular.module('myApp',[]);
//myApp.directive('myDirective', function() {});
//myApp.factory('myService', function() {});
myApp.controller("MyCtrl", function($scope) {
$scope.ptoGroupTypes = [
{"classname": 'n1'},
{"classname": 'n2'},
{"classname": 'n3'}
];
$scope.ptoUpdateUserQueryForm1 = function() {
console.log("1. new formtype = " + $scope.ptoItem.classname);
};
});
This jsfiddle works great.
In my application, everything works exactly the same way except that inside the $scope.ptoUpdateUserQueryForm function, the value of $scope seems okay but the value of $scope.ptoItem is undefined.
My HTML
<body ng-app="cdu_app">
<div ng-controller="cdu_controller">
<table>
<tr>
<td>Type</td>
<td>
<select
ng-options="ptoGroup as ptoGroup.classname for ptoGroup in ptoGroupTypes"
ng-model="ptoItem"
ng-change="ptoUpdateUserQueryForm()"></select>
</td>
</tr>
<tr>
<td>ptoItem.classname: </td>
<td>{{ ptoItem.classname }}</td>
</tr>
</table>
</div>
</body>
My Javascript
var cduApp = angular.module("cdu_app", []);
cduApp.controller('cdu_controller', function ($scope, $http) {
$scope.ptoGroupTypes = [
{ "classname": "Team" },
{ "classname": "Organization" }
];
$scope.ptoUpdateUserQueryForm = function() {
console.log(" $scope.ptoUpdateUserQueryForm = function()");
console.log("new form type is: " + $scope.ptoItem.classname);
};
});
I am running with Angularjs 1.4.8.
Not sure if that was a type but the object ptoGroupTypes needs to be as
$scope.ptoGroupTypes = [
{ "classname": "Team" },
{ "classname": "Organization" }
];
The closing '}' should be ']'.
Checkout this FIDDLE. It is working.
Just thought of mentioning I changed the name of app from cdu_app to myApp for the sake of Fiddle to work.

Accessing row in ngTable after it was sorted

In this plunk I have an ngTable with three rows. What I need to know is the row number after it was sorted. For example, sort the ngTable by name ascending. You should see in the name column 'aaa', 'bbb' and 'ccc'. Then click on the button, I'm expecting to see the first row updated, however the last row is set with the new values. This happens because the $scope.data array itself is not sorted, but ngTable sorts a copy internally. I need to update the first "visible" row, how can I achieve that?
HTML
<table ng-table="tableParams" class="table table-bordered">
<tbody>
<tr ng-repeat="u in $data">
<td title="'User ID'" sortable="'uid'" >
{{ u.uid }}
</td>
<td title="'Name'" sortable="'nm'" >
{{ u.nm }}
</td>
</tbody>
</table>
Javascript
var app = angular.module('app', ['ngTable']);
app.controller('myCtl', function($scope, NgTableParams) {
$scope.data = [{
uid: 1,
nm: 'ccc'
}, {
uid: 2,
nm: 'bbb'
}, {
uid: 3,
nm: 'aaa'
}];
$scope.tableParams = new NgTableParams({
count: 5
}, {
data: $scope.data
});
$scope.updateRow = function(row){
$scope.data[row].uid = 999;
$scope.data[row].nm = 'XXXXX';
$scope.tableParams.reload();
};
});
You have access to the data in tableParams.data. So to do what you model above, you would just need to do something like this:
$scope.updateRow = function(){
$scope.tableParams.data[0].uid = 999;
$scope.tableParams.data[0].nm = 'XXXXX';
$scope.data = $scope.tableParams.data;
};

Dynamically change size of collection in Backbone.js from HTML Select form

I have this table that has a list of items from an array called books. i.e.
var books = [
{"title": "Harry Potter 1", "author": "J. K. Rowling"},
{"title": "Harry Potter 2", "author": "J. K. Rowling"}
//.... More books
]
I separated my views into a view for a single book row, the entire book list that gets rendered into a table, and also a view for the select form.
var Book = Backbone.Model.extend({});
var BooksCollection = Backbone.Collection.extend({
model: Book
});
var Books = new BooksCollection(books);
//
var SelectView = Backbone.View.extend({
el: ".container",
events: {
"change #num-entries": "updateBookList"
},
initialize: function() {
this.render();
},
render: function() {
this.collection.models.length = $('#num-entries').val();
},
updateBookList: function() {
this.collection.models.length = $('#num-entries').val();
//console.log(this.collections.models.length);
booksview.$el.empty();
booksview.unbind();
booksview.render();
}
});
var selectView = new SelectView({
model: Book,
collection: Books
});
//
var BooksView = Backbone.View.extend({
type: "BooksView",
el: "#books-table",
initialize: function() {
//this.collection.models.length = $('#num-entries').val();
this.render();
},
render: function() {
this.collection.each(function(book) {
var bookView = new BookView({
model: book
});
bookView.render();
this.$el.append(bookView.$el);
}, this)
}
});
var BookView = Backbone.View.extend({
type: "BookView",
className: 'book',
tagName: 'tr',
template: _.template($("#books-template").html()),
events: {
"click .delete": "bookRemove"
},
initialize: function() {
this.render();
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
bookRemove: function(book) {
this.remove();
this.model.trigger('destroy', this.model);
//Books.remove(book);
},
});
var booksview = new BooksView({
collection: Books
})
this is in my html
<div class="container">
<div class="form-group" id="entries-per-page">
<label>Number of Entries:</label>
<select class="form-control" id="num-entries">
<option value="10">Show 10 entries</option>
<option value="25" selected>Show 25 entries</option>
<option value="50">Show 50 entries</option>
<option value="100">Show 100 entries</option>
</select>
</div>
<table class="table table-bordered table-hover">
<thead>
<tr class="header-names">
<th class="book-category">Title</th>
<th class="book-category">Author</th>
<th class="book-category">Meta Data</th>
<th class="book-category">Options</th>
</tr>
</thead>
<tbody id="books-table">
<script type="text/template" id="books-template">
<td><%- title %></td>
<td><%- author %></td>
<td><span class='glyphicon glyphicon-remove delete'></span></td>
</script>
</tbody>
</table>
</div>
Currently my messy code for the select view can make the list collection shrink (so my table goes from 25 entries to 10 for example) but then if i try to make the table display more books i get an error - Uncaught TypeError: Cannot read property 'toJSON' of undefined. I would think using the collection.slice function should help, but I can't figure it out. Also, getting my delete book function to work with this is hard. Can I get any help using the slice method to dynamically change the size of my book array or book collection so the table displays correctly?
When you are modifying the models array directly, you are losing the reference to models, and you would have to re-add the to the collection to show them again.
Instead of changing the actual collection, you should pick those out you need in the render method. This also makes more sense on a conceptual level, since the number of entries to display is a view-concern rather than a data-concern.
Your view will now look something like this:
var BooksView = Backbone.View.extend({
type: "BooksView",
el: "#books-table",
// Define how many books we want to display
booksToShow: 25,
initialize: function() {
this.render();
},
render: function() {
_.each(this.collection.slice(0, this.booksToShow), this.renderBook, this);
},
renderBook: function(bookModel) {
var bookView = new BookView({
model: book
});
bookView.render();
this.$el.append(bookView.$el);
}
});
Then within updateBookList you can call
this.collection.booksToShow = $('#num-entries').val();
Another thing; for good measures sake, you should remember to unbind the individual BookViews as well when you remove them. Preferably by creating a method in BooksView to clean up.

Attach a jQuery Extension containing Angular

I have a grid element that generates the following HTML:
<div id="grid">
<table class="grid test" ng-controller="gridController" width="900">
<thead>
<tr><td class="th-break" colspan="8"> </td></tr>
<tr>
<th>Id</th>
<th>Title</th>
<th>Potential Customer</th>
<th>Est. Close Date</th>
<th>Est. Revenue</th>
<th>Probability</th>
<th>Rating</th>
<th></th>
</tr>
<tr><td class="th-break" colspan="8"> </td></tr>
</thead>
<tbody ui-sortable="sortableOptions" ng-model="opps">
<tr ng-repeat="obj in opps|orderBy:orderByField:reverseSort" style="cursor:move;">
<td>{{obj.id}}</td>
<td>{{obj.title}}</td>
<td>{{obj.customer}}</td>
<td>{{obj.closeDate}}</td>
<td>{{obj.revenue}}</td>
<td>{{obj.probability}}</td>
<td>{{obj.rating}}</td>
<td><i class="fa fa-times" ng-click="delete(obj)"></i></td>
</tr>
</tbody>
</table>
</div>
I created angular code that bound data to the grid and it worked perfectly. However, it was all manual binding and I'm tidying up my code. I want to create a jQuery extension so that I can pass in a ton of options and bind the grid using:
$('div#grid').bindGrid();
Here's my jQuery:
(function ($) {
'use strict';
$.fn.bindGrid = function (options) {
var settings = $.extend({
}, options);
var grid = this;
var gridMod = angular.module('grid', ['ui.sortable']);
gridMod.controller('gridController', function ($scope) {
$scope.orderBy = 'title';
$scope.reverseSort = false;
var list = [
{ 'id': 1, 'title': 'Interested in Product Designer', 'customer': '', 'closeDate': '4/2/2013', 'revenue': '$349,383.00', 'probability': '70', 'rating': 'Hot' },
{ 'id': 2, 'title': 'Interested in Product Designer', 'customer': 'Bold Sales Accessories', 'closeDate': '6/11/2013', 'revenue': '$234,382.00', 'probability': '20', 'rating': 'Cold' },
{ 'id': 3, 'title': 'Interested in Product Designer', 'customer': 'Coho Winery', 'closeDate': '6/18/2013', 'revenue': '$182,796.00', 'probability': '50', 'rating': 'Warm' },
{ 'id': 4, 'title': 'Interested in Plotters', 'customer': 'Daring Farm', 'closeDate': '7/28/2013', 'revenue': '$685,780.00', 'probability': '50', 'rating': 'Warm' }
];
$scope.opps = list;
$scope.delete = function (item) {
var index = $scope.opps.indexOf(item);
$scope.opps.splice(index, 1);
}
$scope.sortableOptions = {
stop: function (e, ui) {
var newOrder = list.map(function (i) {
return i.id;
}).join(', ');
console.log(newOrder);
}
}
});
angular.element(document).ready(function () {
angular.bootstrap(grid, ['grid']);
});
};
}(jQuery));
When I run this, I get "'gridController' not a function, got undefined." I'm sure it has something to do with scope, but not sure what's going on. Any suggestions?
And I don't want to use a directive as I need this to be highly configurable via options passed in through jQuery.
Thanks.
Nevermind, I had a typo. grrhhh, too many late nights. I've edited the code above.

Backbone.js executing _.each loop with single model

I have the following code:
<script type="text/template" id="iterator_template">
<table>
<% _.each(value, function(user) { %>
<tr><td><%= user.name %></td>
<td><%= user.email %></td></tr>
<% }); %>
</tr></table>
</script>
<div id ="iterator_container"></div>
<script type="text/javascript">
(function($){
var UserModel = Backbone.Model.extend({
urlRoot: 'http://localhost/backbone/users',
});
IteratorView = Backbone.View.extend({
el: '#iterator_container',
initialize: function(){
var self = this;
var userDetails = {id:2};
self.user = new UserModel(userDetails);
self.user.fetch({
success: function (user) {
self.render();
}
});
},
render: function(){
var template = _.template( $("#iterator_template").html(), {value: this.user}
);
this.$el.html( template );
},
});
var form_view = new IteratorView();
})(jQuery);
If the server returns:
[{"name":"John Hancock","email":"johnhancock#backbone.com"}] ... 1.
there is no output.
If the server returns:
{"name":"John Hancock","email":"johnhancock#backbone.com"}
the loop executes twice.
I want the loop to execute once with 1. How can this be accomplished?
no need of loop because its passing only one model to template, you should use loop if you pass collection
<script type="text/template" id="iterator_template">
<table>
<tr><td><%= user.name %></td>
<td><%= user.email %></td></tr>
</tr></table>
</script>
backbone expects the response to be array but this value returned from the server
[{"name":"John Hancock","email":"johnhancock#backbone.com"}]
is object , hence it is not working
edit : answer to your comment
write this parse function in your model
parse : function(response){
return response[0];
}
this will return the array from the object and set the values to model
now in success call back of model add to collection
mymodel.fetch({
success : function(){
mycollction.add(mymodel);
}
})
keep the template as it was in the question and now pass this collection as JSON to template

Resources