How can I get data from the child of a CompositeView? - backbone.js

I have a CompositeView with a <header> and a <tbody>. I need specific data from the collection that's added to the <tbody> for use in the <header>. Specifically, I need to display the TOTAL NUMBER of records in the collection as well as the TOTAL COST of all models (each model has an amount attribute with a dollar value, like 54.35).
How might I do this?
Here is all relative code:
/*
* Layout
*/
var AppLayout = Backbone.Marionette.Layout.extend({
template: "#customer-view",
regions: {
card: "#customer-card",
quotes: "#customer-quotes",
orders: "#customer-order-history"
}
});
Show.Layout = new AppLayout();
Show.Layout.render();
/*
* ItemView
*/
Show.HistoryItemView = Backbone.Marionette.ItemView.extend({
tagName: "tr",
template: "#customer-history-item"
});
Show.HistoryItemsView = Backbone.Marionette.CompositeView.extend({
template: "#customer-history-wrapper",
itemView: Show.HistoryItemView,
itemViewContainer: "tbody"
});
/*
* Items
*/
var customer_orders = Customers.request("customer:orders", UserID);
var customer_orders_view = new Show.HistoryItemsView({
collection: customer_orders
});
Show.Layout.orders.show(customer_orders_view);
… And the template:
<script type="text/template" id="customer-history-wrapper">
<div class="module collapsible">
<header>
<hgroup>
<h2>Order History</h2>
</hgroup>
<div class="results">
<div class="left"><strong><%= NUMBER OF RECORDS %></strong> Orders</div>
<div class="right">$ <%= TOTAL COST OF RECORDS %></div>
</div>
</header>
<div class="module-content no-pad">
<table class="simple-table six-up" cellpadding="0" cellspacing="0">
<thead>
<tr>
<th>Date</th>
<th>Licensee</th>
<th>Company</th>
<th>Order</th>
<th class="numeric">Total</th>
<th class="cta"> </th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</script>
<%= NUMBER OF RECORDS %> and <%= TOTAL COST OF RECORDS %> is where I'll need to insert this data.
Appreciate any help I can get!!

CompositeViews can have a collection and a model, create a model with the properties that you need, on this case numberofRecors, totalCost,
then on your onBeforeRender function calculate the totals of your model, something like this.
var customer_orders_view = new Show.HistoryItemsView({
model: totals,
collection: customer_orders
});
Show.Layout.orders.show(customer_orders_view);
Show.HistoryItemsView = Backbone.Marionette.CompositeView.extend({
template: "#customer-history-wrapper",
itemView: Show.HistoryItemView,
itemViewContainer: "tbody",
onBeforeRender : function () {
this.model.set({numberOfRecors : this.collection.length});
//here you can set the model values to be displayed.
}
});
you can also pass the model with the values precalculated that way you dont need the on beforeRender function, If you do that when you call show on the region this will call the render function of the compositeView and that will be it. you model will be also rendered alongisde your collection.

Related

Adding form values to a table with backbone and handlebars

The scenario is to add the values entered in form fields into a table on click of Add button. I am new to this both and not sure how data binding works.
My initial html is
<table class="table table-bordered">
<thead>
<tr>
<th>Model</th>
<th>Brand</th>
<th>Year</th>
<th>Action</th>
</tr>
<tr>
<td><input class="form-control" id="modelname"></td>
<td><input class="form-control" id="brandname"></td>
<td><input class="form-control" id="yearname"></td>
<td><button class="btn btn-primary add">Add</button></td>
</tr>
</thead>
<tbody class="product-list">
</tbody>
</table>
</div>
<script type="text/x-handlebars-template" id="product-template">
{{#each []}}
<tr>
<td>{{ model }}</td>
<td>{{ brand }}</td>
<td>{{ year }}</td>
<td><div class="btn btn-primary">Edit</div> <div class="btn btn-danger">Delete</div></td>
</tr>
{{/each}}
</script>
I messed up in js for purpose of using handlebars as
var Product=Backbone.Model.extend({
model:'',
brand:'',
year:''
});
var ProductCollection=Backbone.Collection.extend({
model:Product
});
var modelname= document.getElementById('modelname').value;
var brandname=document.getElementById('brandname').value;
var yearname= document.getElementById('yearname').value;
var ProductView = Backbone.View.extend({
el: '.product-list',
tagName: 'tr',
events:{
"click .add": "create"
},
initialize:function(){
this.render();
},
render:function()
{
var source=$('#product-template').html();
var template=Handlebars.compile(source);
var html=template(this.products.toJSON());
},
create: function(e){
var product=new Product({
model:modelname,
brand:brandname,
year:yearname
})
console.log(product.toJSON);
products.add(product);
modelname="";
yearname="";
brandname="";
}
});
var products=new ProductCollection();
Share me an idea how to proceed. I don't get an error and at the same time, nothing happens! I am very new to backbone. Please tolerate blunders.
I make and example how can achieve that with underscore template and handlebars. Use it for iterating over a collection of models to display a list of products.
Underscore.js
<tbody class="product-list">
<script type="text/template" id="product-template">
<% _.each(products.models, function(product){ %>
<tr>
<td><%= product.get('modelName') %></td>
<td><%= product.get('brand') %></td>
<td><%= product.get('year') %></td>
</tr>
<% }) %>
</script>
</tbody>
In script file define model:
var Product = Backbone.Model.extend({});
Next, define a collection and add those model to the collection:
var ProductList = Backbone.Collection.extend({
model: Product
});
Most of the time we use view in Backbone application to do the rendering:
var ProductView = Backbone.View.extend({
el: '.product-list',
template: _.template($('#product-template').html()),
render: function(products){
this.$el.html(this.template({
products: products
}));
}
});
You can see from working code full app, and see that we call render method from productView and pass it collection as argument: this.productView.render(this.collection)
Now we can use it as a list in template to iterate and display modelName, brand and year for each product in lists.
Working code: jsFiddle
I am particularly asked to use handlebars
Handlebars.js
You have many errors in your code:
define instance of view var products = new ProductView();, instead of that you define instance of ProductCollection();
var html=template(this.products.toJSON()); Cannot read property 'toJSON' of undefined , check initialize method in working example, you need to define collection and listen to him, because every time we add something to collection we want to render ProductView
el: '.table' not el: '.product-list',
var modelname= document.getElementById('modelname').value; var brandname ... - you make them as a global variables, instead of that place that variables inside create() method
replace var html=template(this.products.toJSON()); with $('.product-list').html(template(this.products.toJSON()))
First of all read documentation if something isn't clear: backbone.js and check working example: jsFiddle

How to achieve deprecated CompositeView functionality in Marionette 3+?

As stated in the latest Marionette docs:
CompositeView is deprecated. You should use the replaceElement option on Region.show and
render a CollectionView into a region inside a View to achieve this functionality.
I still can't understand how CompositeView functionality should be achieved now.
Previously, CompositeView was perfect for using with such template:
<script id="table-template" type="text/html">
<table>
<% if (items.length) { %>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Description</th>
</tr>
</thead>
<% } %>
<tbody></tbody>
<tfoot>
<tr>
<td colspan="3">some footer information</td>
</tr>
</tfoot>
</table>
new MyCompositeView({
template: "#table-template",
templateContext: function() {
return { items: this.collection.toJSON() };
}
// ... other options
});
If we decide to use LayoutView instead of CompositeView then we need to code manually a lot of event bindings (for example to show / hide table header based on number of items in collection). This makes things harder.
Are there any clean and not complicated ways to live without CompositeView?
Thank you for any help or advice.
It looks like Marionette 3 is going to get rid of some concepts to make the framework simpler overall, and easier to understand.
Marionette.View in 3 is going to include functionality from what was ItemView and LayoutView. CompositeView is deprecated in favour of just using RegionManager, which is now included into View.
v2 --> v3
View -> AbstractView
ItemView, LayoutView -> View
Here's a quick example app:
var color_data = [ { title:'red' }, { title:'green' }, { title:'blue' } ];
var Color = Backbone.Model.extend({
defaults: { title: '' }
});
var Colors = Backbone.Collection.extend({
model: Color
});
var ColorView = Mn.View.extend({
tagName: 'tr',
template: '#colorTpl'
});
var ColorsView = Mn.CollectionView.extend({
tagName: 'tbody',
childView: ColorView
});
var AppView = Mn.View.extend({
template: '#appTpl',
templateContext: function(){
return {
items: this.collection.toJSON()
};
},
ui: {
input: '.input'
},
regions: {
list: {
selector: 'tbody',
replaceElement: true
},
},
onRender: function(){
this.getRegion('list').show(new ColorsView({
collection: this.collection
}));
},
events: {
'submit form': 'onSubmit'
},
onSubmit: function(e){
e.preventDefault();
this.collection.add({
title: this.ui.input.val()
});
this.ui.input.val('');
}
});
var appView = new AppView({
collection: new Colors(color_data)
});
appView.render().$el.appendTo(document.body);
<script src='http://libjs.surge.sh/jquery2.2.2-underscore1.8.3-backbone1.3.2-radio1.0.4-babysitter0.1.11-marionette3rc1.js'></script>
<script id="colorTpl" type="text/template">
<td><%=title%></td>
<td style="background-color:<%=title%>"> </td>
</script>
<script id="appTpl" type="text/template">
<table width="100%">
<% if(items.length) { %>
<thead>
<tr>
<th width="1%">Title</th>
<th>Color</th>
</tr>
</thead>
<% } %>
<tbody></tbody>
<tfoot>
<tr>
<td colspan="2">
<form><input type="text" class="input" autofocus><input type="submit" value="Add Color"></form>
</td>
</tr>
</tfoot>
</table>
</script>

Displaying Json Data from ng-repeat in multiple containers

Hey I have a question about using ng-repeats. I'm trying to display user data from a large json file in two separate containers. When a user clicks on a row it displays the other piece of the ng-repeat in a different section. If someone could take a look at this fiddle and maybe give me a direction to go in that would be awesome. Thanks!
I'm also using ngTable for the table params, but I don't think that has much to do with the issue.
http://jsfiddle.net/cL5aE/1/
HTML:
<body>
<div ng-controller="myController">
<div class="col-md-6">
<table>
<tr ng-repeat="user in $data" ng-click="loadDetails()">
<td>{{user.name}}</td>
<td>{{user.id}}</td>
</tr>
</table>
</div>
<div class="col-md-6">
<tr ng-repeat="user in $data" ng-show="loadDetails()">
<td>{{user.foo}}</td>
<td>{{user.bar}}</td>
</tr>
</div>
</div>
</body>
Controller:
angular.module('app', ['ngTable']);
app.controller('myController', function ($scope, $http, ngTableParams) {
$http.get('http://myjson.com/data.json')
.success(function (data, status) {
$scope.tableParams = new ngTableParams({
page: 1, // show first page
count: 10, // count per page
sorting: {
CompleteDate: 'asc' // initial sorting
}
}, {
total: data.length, // length of data
getData: function ($defer, params) {
// use build-in angular filter
var orderedData = params.sorting() ? $filter('orderBy')(data, params.orderBy()) : data;
$defer.resolve(orderedData.slice((params.page() - 1) * params.count(), params.page() * params.count()));
}
});
});
$scope.loadDetails = function(data) {
//???
}
});
I would pass the user object as a parameter to a function and assign it a model. That way you don't need to use the ng-repeat in both sections leading to a cleaner and more readable code.
Pass the object you're displaying on the ng-repeat:
<tr ng-repeat="user in $data" ng-click="loadDetails(user)">
Function to load the object to a model property:
$scope.loadDetails = function(user) {
$scope.viewModel.selectedUser = user;
}
And finally assign the model property to the view:
<table>
<tr ng-show="viewModel.selectedUser">
<td>{{viewModel.selectedUser.foo}}</td>
<td>{{viewModel.selectedUser.bar}}</td>
</tr>
</table>
http://jsfiddle.net/jimenezjrs/5Cd32/

Calculate total over repeated cart item totals with angular

I've a simplified shopping cart like the following with a controller for each cart item:
<!DOCTYPE HTML>
<html ng-app="cart">
<div ng-controller="cartCtrl">
<table>
<tr><th>qty</th><th>prize</th><th>total</th></tr>
<tr ng-repeat="item in cartItems" ng-controller="cartItemCtrl">
<td>
<input ng-model="item.qty"/>
</td>
<td>
<input ng-model="item.prize" />
</td>
<td>
{{total}}
</td>
</tr>
</table>
total: {{cartTotal}}
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular.js"></script>
<script>
(function(){
var cart = angular.module('cart', []);
cart.controller('cartCtrl', ['$scope', function($scope){
$scope.cartItems = [{},{},{},{}];
}]);
cart.controller('cartItemCtrl', ['$scope', function($scope){
$scope.$watchCollection('item', function(){
$scope.total = $scope.item.qty * $scope.item.prize;
});
}]);
}());
</script>
</html>
I couldn't make it work in JSFiddle: http://jsfiddle.net/XY3n8/1/
Now I want to to calculate the cart total but I do not want to recalculate the item totals. Instead I want to reuse the already calculated item totals. How? (In my use case the item total calculation is a bit more complex.)
You need to do a "deep" watch on the collection and object to get sub-totals and totals. $watchCollection is better for knowing when things get added/removed. $watch will look for changes in the existing object(s).
For encapsulating the item's total, there are several ways to do this, but I'd probably create an Item model (probably a better name) and inject it via a factory. It removes the need for one of the controllers in this example, but requires you to create a module (which is best practice anyway)
How about this?
var ShoppingCart = angular.module('shoppingCart', []);
ShoppingCart.factory('Item', function() {
function Item() {
this.total = function() {
return (this.qty * this.prize) || 0;
}
}
return Item;
});
ShoppingCart.controller('cartCtrl', function($scope, Item) {
$scope.cartItems = [new Item(), new Item(), new Item(), new Item()];
$scope.$watch('cartItems', function() {
var cartTotal = 0;
$scope.cartItems.forEach(function(item) {
cartTotal += item.total();
});
$scope.cartTotal = cartTotal;
}, true);
});
And the HTML updates slightly. You reference the module name in the ng-app, get rid of the sub-controller, and reference item.total() directly in the view.
<div ng-app="shoppingCart" ng:controller="cartCtrl">
<table>
<tr><th>qty</th><th>prize</th><th>total</th></tr>
<tr ng:repeat="item in cartItems">
<td>
<input ng:model="item.qty"/>
</td>
<td>
<input ng:model="item.prize" />
</td>
<td>
{{item.total()}}
</td>
</tr>
</table>
total: {{cartTotal}}
</div>
Here is a working codepen.
This is an approach using the ng-init directive to extend the model and do the calculations.
http://www.ozkary.com/2015/06/angularjs-calculate-totals-using.html

Knockout foreach binding inside a binding (array within an array)

i have two classes; users and readings. this relates to healthcare to each user has an array of readings:
/*
* Reading class
* containts health readings
*/
function Reading(date,weight,glucose)
{
var self = this;
self.date=ko.observable(date);
self.weight=ko.observable(weight);
self.glucose=ko.observable(glucose);
self.formattedWeight = ko.computed(function(){
var formatted = self.weight();
return formatted+" lb"
});
}
/*
* User class
* contains a user name, date, and an array or readings
*/
function User(name,date,readings) {
var self = this;
self.name = name;
self.date = date;
self.readingsArray = ko.observableArray([
new Reading(99,99)
]);
}
i know how to use a foreach binding to display the information for a reading or a user. but im not sure how to show the readings inside of a user?
is there a way to use a nested foreach binding or a with binding? here is my html:
<h2>Users (<span data-bind="text: users().length"></span>)</h2>
<table>
<thead><tr>
<th>User name</th><th>Date</th></th>
</tr></thead>
<!-- Todo: Generate table body -->
<tbody data-bind="foreach: users">
<tr>
<td><input data-bind="value: name" /></td>
<td><input data-bind="value: date" /></td>
<td data-bind="text: readingsArray"></td>
<td>Remove</td>
</tr>
</tbody>
</table>
<button data-bind="click: addUser">Add User</button>
<h2>Readings (<span data-bind="text: readings().length"></span>)</h2>
<table>
<thead><tr>
<th>Date</th><th>Weight</th><th>Glucose</th>
</tr></thead>
<!-- Todo: Generate table body -->
<tbody data-bind="foreach: readings">
<tr>
<td strong data-bind="text: date"></td>
<td strong data-bind="text: formattedWeight"></td>
<td strong data-bind="text: glucose"></td>
</tr>
</tbody>
</table>
and here is my model if anyone is interested. any help would be greatly appreciated! Thanks in advance!
// Overall viewmodel for this screen, along with initial state
function userHealthModel() {
var self = this;
self.inputWeight = ko.observable();
self.inputDate = ko.observable();
self.inputId = ko.observable();
self.inputGlucose = ko.observable();
// Operations
self.addUser = function(){
self.users.push(new User("",0,0,(new Reading(0,0))));
}
//adds a readings to the edittable array of readings (not yet the reading array in a user)
self.addReading = function(){
var date = self.inputDate();
var weight = self.inputWeight();
var glucose = self.inputGlucose();
if((weight&&date)||(glucose&&date))
{
self.readings.push(new Reading(date,weight,glucose));
}
else{
alert("Please complete the form!")
}
}
self.removeUser = function(userName){self.users.remove(userName)}
//editable data
self.readings = ko.observableArray([
new Reading(12,99,3),new Reading(22,33,2),
new Reading(44,55,3)
]);
self.users = ko.observableArray([
new User("George",2012),
new User("Bindu",2012)
]);
}
ko.applyBindings(new userHealthModel());
I'm not sure how you want the Readings rendered, but here is an example:
http://jsfiddle.net/jearles/aZnzg/
You can simply use another foreach to start a new binding context, and then render the properties as you wish.

Resources