Binding a model to a backbone client template - backbone.js

I have the following backbone.js client side template:
<script id="calleeTemplate" type="text/x-jquery-tmpl">
<tr style="background-color: ${StatusColour}">
<td class="responder">${ContactFullName}</td>
<td class="status" style="width:200px">${Status}</td>
<td class="replied">${Replied}</td>
<td class="wauto">${Response}</td>
</tr>
</script>
In order to be able to bind to these properties, I have the following render method of a view:
App.Views.Callees = Backbone.View.extend({
initialize: function () {
},
el: $('#calleesStatuses'),
render: function () {
var col = _.map(this.collection.models, function (item) {
return item.attributes;
});
$('#calleesStatuses').html('');
$('#calleeTemplate').tmpl(col).appendTo(this.el);
}
});
I have to extract the attributes using the underscore _.map function from the model. I think the reason for this is because backbone uses the .get("property") to extract the property value.
This does not seem right to me, am I doing anything wrong?

You're right, you have to transform the data in order to be able to easily use it with tmpl.
However, it's better practice to use toJSON rather than accessing attributes directly. It's probably best to avoid calling the .models directly as well.
You don't need to anyway, backbone collections have a full suite of underscore.js enumerators. So you can cut your transformation down to one line:
var col = this.collection.invoke('toJSON')

Related

Add an action button to DataTables column header in AngularJS

I am trying to add a button to the heading of a column in angular-datatables, which when clicked will run a function. I have tried doing something like this.
DTColumnBuilder.newColumn('name').withTitle(function() {
return '<span>Name</span><button ng-click="runme()">Click me</button>'
})
In the same controller, runme() function is defined as:
$scope.runme = function() {
console.log('clicked');
}
But this is not triggered, it only sort the column data, no matter where i clicked on entire header section.
When you are using this approach you'll need to $compile the content of the <thead> (and anything else injected by DataTables you would like AngularJS to be aware of).
A good place to invoke $compile is in the initComplete callback :
$scope.dtOptions = DTOptionsBuilder.newOptions()
.withOption('initComplete', function() {
$compile(angular.element('thead').contents())($scope)
})
demo -> http://plnkr.co/edit/D72WPqkE3g2UgJTg
Remember to inject $compile to your controller, see for example Working with $compile in angularjs. (Lousy google does not even bother to fix the errors in their docs, so https://docs.angularjs.org/api/ng/service/$compile does not work).
Note: You could also go with static <table> markup
<table>
<thead>
<tr>
<th><span>Name</span><button ng-click="runme()">Click me</button></th>
</tr>
</thead>
<tbody></tbody>
</table>
Then AngularJS will connect $scope.runme to the ng-click, and only if you need additional bindings in the dynamic content inserted by DataTables, a $compile is needed.

Angular $watch just parent object instead of its multiple children

I have a div, listing properties of the object POI = {"address":"Martinsicuro (TE), Italy", "phone":"+39 333 45657", "website':'http://mysite.it"}. The object POI si owned by a Service. The directive's controller has the function getPoi() that gets the POI from the service, and returns it to the directive.
My current HTML is something like this:
<table ng-controller="Controller as ctrl">
<tr> <!-- address -->
<td>{{ctrl.getPoi().address}}</td>
</tr>
<tr> <!-- phone -->
<td>{{ctrl.getPoi().phone}}</td>
</tr>
<tr> <!-- website -->
<td>
<a ng-href="{{ctrl.getPoi().website}}">
{{ctrl.getPoi().website}}
</a>
</td>
</tr>
</table>
The controller
.controller('Controller', function(CurrentPoiService)
{
this.getPoi = function()
{ return CurrentPoiService.POI; }
}
The service
.service('CurrentPoiService', function()
{
this.POI = {"address":"Martinsicuro (TE), Italy", "phone":"+39 333 45657", "website':'http://mysite.it"}
}
In this way I am adding 3 watchers. Is there a way to add just 1 watcher, since it's the same parent object? Here it is a JSFiddle
Thank you
[UPDATE 1]
This is the (still not working) JSFiddle using the solution proposed by #Charlie
[UPDATE 2]
This is the working JSFiddle
As Claies has mentioned in a comment, you should never call your data from
the view through a function this way.
In this scenario you can create a watch for the POI object with the objectEquality argument true to watch the properties of the object in a single $watch. Then find your elements inside the listener and change the value in the way you want.
$scope.$watch('POI', function(){
//Assume that $scope.propertyIndex is the current property to display
angular.element($document[0].querySelector("#myTd" + $scope.propertyIndex)).html(POI.address);
angular.element($document[0].querySelector("#myTd" + $scope.propertyIndex)).html(POI.phone);
//etc...
}, true);
You have a better control this way. But please keep in mind that this method is not suitable if POI is a complex object.
UPDATE:
Here is a working example of showing a random number every second using a watch and a factory. You should be able to learn from this and apply it to your project.
myApp.controller('myController', function($scope, dataSource) {
$scope.poi = {rand: 0};
$scope.$watch('poi', function() {
$('#rand').html($scope.poi.rand);
}, true);
dataSource.open($scope);
});
myApp.factory('dataSource', function($interval) {
return {
open: function(scope){
$interval(function() {
scope.poi.rand = Math.random();
}, 1000);
}
}
});
Try inside your controller :
$scope.POI = ctrl.getPoi();
HTML :
<tr> <!-- address -->
<td>{{POI.address}}</td>
</tr>
<tr> <!-- phone -->
<td>{{POI.phone}}</td>
</tr>

Angular with filter in different view

I have a question concerning Angular and Angular-UI Router. I would like to place a filter in a bootstrap navbar which can vary depending on the current route. I have tried to implement this behaviour with Angular-UI Router but ui-router doesn't support sharing the same controller across multiple views. In their documentation the touch on this topic. See: https://github.com/angular-ui/ui-router/wiki/Multiple-Named-Views.
I have created a plunker with the desired behaviour: http://plnkr.co/edit/W0UgeW
Onto my question: Is there any way I can accomplish this behaviour? I dont necissarly want to do it with Angular-ui Router, if it's at all possible without using extensions I would prefer to do it that way.
Thank you,
Bart
You can create custom filter like:
myapp.filter('myfilter', function() {
return function( items, combo) {
var filtered = [];
console.log(items, combo);
angular.forEach(items, function(item) {
if(item.name == combo){
filtered.push(item);
}
});
return filtered;
};
});
Now to pass combo option to view we can write:
$rootScope.theFilter = '';
Our table will look like:
<table id="searchTextResults">
<tr><th>Name</th><th>Phone</th></tr>
<tr ng-repeat="friend in friends | myfilter:theFilter">
<td>{{friend.name}}</td>
<td>{{friend.phone}}</td>
</tr>
</table>
See Demo Plunker

Backbone Underscore Template

So I'm checking out the changes related to the latest backbone/underscore version. Prior I have a project running with BB 0.5.2 and underscore 1.1.7. I'm noticing something strange with regards to defining a template property within a view in the new version, which gives me reservations in going forward with upgrading.
In my current version I would define a view as such:
var MyView = Backbone.View.extend({
template: _.template($('#exampleTemplate').html()),
initialize: function() {...},
render: function() { $(this.el).html(this.template(someObjectParam)); },
});
However, if I attempt to work in the same manner, using a simplified todo clone attempt as an example, I setup an html with an inline script template as such:
<script>
$(document).ready(function() {
app.init();
});
</script>
<script type="text/template" id="itemViewTemplate">
<div class="item">
<input type="checkbox" name="isComplete" value="<%= item.value %>"/>
<span class="description"><%= item.description %></span>
</div>
</script>
In my included JS file I have:
var ItemView = Backbone.View.extend({
el: 'body',
// Below causes error in underscore template, as the jquery object .html() call
// returns null. Commenting will allow script to work as expected.
templateProp: _.template($('#itemViewTemplate').html()),
initialize: function() {
// Same call to retrieve template html, returns expected template content.
console.log($('#itemViewTemplate').html());
// Defining view template property inside here and calling it,
// properly renders.
this.template = _.template($('#itemViewTemplate').html());
this.$el.html(this.template({item: {value: 1, description: 'Something'}}));
},
});
var app = {
init: function() {
console.log('Fire up the app');
var itemView = new ItemView();
}
}
So I'm left confused as to why defining the template property directly causes the call to retrieve the template html to return a null value, thus breaking the attempt to define an underscore template object (mouthful). However, if the definition is done within the initialize function, the call to retrieve the template html properly finds the template so its contents can be passed to the underscore template. Anyone see what I'm potentially missing?
Thanks in advance!
If this:
var ItemView = Backbone.View.extend({
//...
templateProp: _.template($('#itemViewTemplate').html()),
//...
});
is failing because $('#itemViewTemplate').html() is null, then you have a simple timing problem: you're trying to read the content of #itemViewTemplate before it exists. Your old version should suffer from exactly the same problem.
Either make sure everything is loaded in the right order (i.e. your views after your template <script>s) or compile the template in your view's initialize. You can check for the templateProp in your view's prototype and only compile it on first use if you want:
initialize: function() {
if(!this.constructor.prototype.template)
this.constructor.prototype.template = _.template($('#itemViewTemplate').html());
//...
}
Demo: http://jsfiddle.net/ambiguous/HmP8U/

How does Backbone.JS handle models with calculated attributes

I'm using Backbone.JS with Mustache, so to render my tempaltes I call MyModel.toJSON(). This leaves me with only access to attributes. How can I have some attributes that are always calculated?
I looked at the Backbone.JS documentation and it might work to override validate() but this seems like a hack and may lead to infinite loops.
I also tried making an attribute be a function instead of a value, but Mustache doesn't get a value when I try to use it.
This is how I'm currently doing it. I do the calculations when initializing a model, and adding a listener for changes to the model to recalculate automatically.
...
initialize: function() {
console.log('Lead:initialize');
_.bindAll(this, 'validate', 'calculate');
this.bind('change', this.setCalculations, this);
this.setCalculations();
},
setCalculations: function() {
this.set({ calculations: this.calculate() }, { silent: true });
},
calculate: function() {
// do the calculations and return
},
...
I dont know if i understand the question correctly, but:
Can't You pass the actual model to mustache? so for example when you render
render: ->
rendered_content = #template({model: #model})
$(#.el).html rendered_content
#
You are passing the actual model to template. Then you have a template
<td class="quantity">
<input type="text" value="<%= model.get('quantity') %>" name="quantity" />
</td>
<td>
<%= model.getTotalPrice() %>
</td>
And in model you declare getTotalPrice()
getTotalPrice: ->
total_price = #get('price') * #get('quantity')
total_price + total_price * #get('tax_rate')
I actually never pass #model.toJSON in my templates, alawys the actual model.

Resources