BackboneJS with HandelbarJS: How to access model functions? - backbone.js

I am exploring a setup with HandelbarsJS and Backbone.
This is part of my template:
<a href="#genre/{{ name }}" class="genre-item" data-genre="{{ name }}">
<i class="icon-chevron-{{#if is_selected }}down{{else}}right{{/if}}"></i>
{{ display_name }} ({{ total }})
</a>
Meaning: I want to render a different icon, depending on whether a model is selected or not.
However, I never get the 'icon-chevron-down', but always the 'icon-chevron-right' path.
Any ideas what I am missing?
EDIT
The selection of a Genre is working on model level like this:
MA.Models.Genre = Backbone.Model.extend({
defaults: {
selected: false
},
is_selected: function() {
return (this.get('selected') == true);
},
toggle: function() {
if (this.is_selected()) {
this.set('selected', false);
}
else
{
this.set('selected', true);
}
}
});
MA.Collections.Categories = Backbone.Collection.extend({
model: MA.Models.Genre
});
This could probably be simplified, but I am not getting the selection of the Genre from the remote service, but it is used just as a temporary state change.

Without seeing what is happening in your view it's hard to tell. But you probably have a render function that look's like:
HandlebarsTemplate['templatename'](this.model.toJSON());
toJSON by default only includes model attributes. Also handlebars won't evalute functions on the fly like that.
The easiest solution is to fix the template to look like:
<a href="#genre/{{ name }}" class="genre-item" data-genre="{{ name }}">
<i class="icon-chevron-{{#if selected }}down{{else}}right{{/if}}"></i>
{{ display_name }} ({{ total }})
</a>
I've changed is_selected to selected to use the attribute instead of the function.
The other option is to modify the toJSON function of the model to include the evaluated function:
MA.Models.Genre = Backbone.Model.extend({
defaults: {
selected: false
},
is_selected: function() {
return (this.get('selected') == true);
},
toJSON: function(options) {
var attrs = _.clone(this.attributes);
attrs.is_selected = this.is_selected();
return attrs;
}
});

Related

How to Hide elements based on id in ng-repeate

How to hide an element based on elements id, I tried to hide the element without using:
document.getElementById('lis'+divId).style.visibility = "hidden";
but this worked while using individual elements but in case if I use ng-repeat it does not hide the element.
<li ng-repeat="dashBoardAddWidgetList in dashBoardAddWidgetLists"
ng-click="addTemplate(dashBoardAddWidgetList.widget.widgetType,$index)"
id="lis{{$index}}" >{{dashBoardAddWidgetList.widget.widgetName}}</li>
controller.js
$scope.addTemplate = function(name,divId) {
//$scope.templates.push( { name: 'achPayBankProcess.html', url: '/Templates/dashboard/widgets/achPayBankProcess.html' });
$scope.templates.push( { name: name, url: '/Templates/dashboard/widgets/'+name,isSelected:1});
document.getElementById(divId).style.visibility = "hidden";
$scope.listCount--;
};
Try this simple way like as what is #priyanka said .(take my answer like a key )
$scope.items = [{isshow:false,name:"Me"},{isshow:false,name:"We"},
{isshow:false,name:"I"},{isshow:false,name:"Us"}]
//html
<div ng-repeat="item in items">
<div ng-hide="item.isshow">
{item.name}<button ng-click="ShowOrHide(this.item, $index)">show/hide</button>
</div>
</div>
//click event
$scope.ShowOrHide(context,index)
{
context.ishsow=true;
$scope.items[index]=context;
}
Use ng-hide instead:
<li ng-repeat="dashBoardAddWidgetList in dashBoardAddWidgetLists"
ng-click="addTemplate(dashBoardAddWidgetList)"
id="lis{{$index}}"
ng-hide="dashBoardAddWidgetList.hide">
{{dashBoardAddWidgetList.widget.widgetName}}
</li>
And in your controller:
$scope.addTemplate = function(widgetList, divId) {
var name = widgetList.widget.widgetType;
$scope.templates.push({
name: name,
url: '/Templates/dashboard/widgets/'+name,
isSelected: 1
});
// Set flag for hide on true
widgetList.hide = true;
$scope.listCount--;
};
Use ng-if instead of ng-hide..it will boost performance on ng-repeat because ng-hide will keep hidden element in DOM but ng-if will remove that element form DOM..so we cant modify it in browser inspect
controller.js
$scope.addTemplate = function(name,divId) {
//$scope.templates.push( { name: 'achPayBankProcess.html', url: '/Templates/dashboard/widgets/achPayBankProcess.html' });
$scope.templates.push( { name: name, url: '/Templates/dashboard/widgets/'+name,isSelected:1});
alert('lis'+divId);
document.getElementById('lis'+divId).style.visibility"hidden";
$scope.listCount--;
$scope.isselect=divId;
};
{{dashBoardAddWidgetList.widget.widgetName}}

Model not updating on form submit, using Backbone + Stickit

I'm trying to use the backbone.stickit library to bind my form input to the model but can't seem to get the model to update correctly.
The keyup event appears to work correctly, i can see the value change if i use the "onSet" callback to display it:
bindings: {
'#firstName': {
observe: 'firstName',
onSet: function(val, options) {
$('#output').html(val);
}
}
}
Here is my code (Run it on jsfiddle):
HTML
<div id="view">
<form name="form" id="form">
<input id="firstName" type="text"/>
<input type="submit" id="submit"/>
</form>
<div id="output"></div>
</div>
JavaScript
var app = {
Model: Backbone.Model.extend({
firstName: 'test'
}),
View: Backbone.View.extend({
el: "#view",
initialize: function(){
this.model = new app.Model();
this.render();
},
bindings: {
'#firstName': 'firstName'
},
render: function(){
this.$el.html( this.template );
this.stickit();
},
events: {
"submit #form": "submitForm"
},
submitForm: function(e) {
e.preventDefault();
$('#output').html('output:'+this.model.firstName);
}
})
};
var view = new app.View();
The way of getting a model attribute is usally not by accessing the attribute name as an object property, the way you did it this.model.firstName. personally I know a very few cases of such implemntation. The so called right way to do that is by using get method:
this.model.get("firstName").
This will return the current model value.
I usually define getters and setters for each model I use, so I would do the following:
getFirstName: function(){
return this.get("firstName");
}
Just looks better and more "easy on the eyes" :) (but totally not a must)
Here's an update of your fiddle: http://jsfiddle.net/srhfvs8h/1/

Angular model doesn't scope to variable within child DOM element?

I have this markup:
<div data-ng-model="currentUser.attributes">
<div>{{username}}</div>
</div>
And this is a stripped down version of my controller:
$scope.username = "Alice";
$scope.currentUser = {
attributes: {
username: "Bob"
}
};
I want Bob to display, but instead, I am getting Alice. It works just fine if I use this:
{{currentUser.attributes.username}}
But I don't want to have to scope down to this variable's properties every time I want to access something. How can I get the element to exist within the scope of currentUser.attributes?
While I don't think you should really do this, it is what you're asking for. You can essentially mimic with by using ng-repeat on an array that you populate with the relevant object. For example:
<div ng-repeat="user in [currentUser.attributes]">
{{ user.username }}
</div>
Working plunker: http://plnkr.co/edit/svwYEeWMQXjuAnLkr9Vz?p=preview
Other possible solutions would be to have a service or controller that has functions to get the attributes and return them, cleaning up the syntax of your HTML and making it easier to change backend stuff without breaking your frontend. Your choice.
Edit: I noticed you actually expect to be able to do {{ username }} and get the relevant info, if that's really what you want then I suggest my second proposal. Create functions that return the relevant info.
<div>
{{ getCurrentUserName() }}
</div>
$scope.getCurrentUserName = function() {
return $scope.currentUser.attributes.username;
};
Your call, take it or leave it.
If you want Bob just do the the following in your HTML.
<div>{{current user}}</div>//IGNORE THIS
<div>{{currentUser.attributes.username}}</div>//UPDATED CORRECTED
UPDATED based on clarification.
So in Knockout you do this
<p data-bind="with: currentUser.attributes">
<div data-bind="text: userName></div>
<div data-bind="text: login></div>
<div data-bind="text: bhalBlah></div>
<div data-bind="text: yaddaYadda></div>
</p>
<script type="text/javascript">
ko.applyBindings({
currentUser: {
attributes: {
userName : 'Bob',
login : 't#e',
blahBlah : 'ttttt',
yaddaYadda: 'x'
}
}
});
</script>
Same thing in AngularJS would be
<p ng-controller="myCtrl">
<div>{{currentUser.attributes.userName}}</div>
<div>{{currentUser.attributes.login}}</div>
<div>{{currentUser.attributes.blahBlah}}</div>
<div>{{currentUser.attributes.yaddaYadda}}</div>
</p>
<script type="text/javascript">
angular.module('myApp',[]).controller('myCtrl',function($scope){
$scope = {
currentUser: {
attributes: {
userName : 'Bob',
login : 't#e',
blahBlah : 'ttttt',
yaddaYadda: 'x'
}
};
});
</script>
In this the question is how to avoid how not to repeat the part the full property paths between ** as shown below in angular.
**currentUser.attributes.**userName
**currentUser.attributes.**login
**currentUser.attributes.**blahBlah
**currentUser.attributes.**yaddaYadda
Here is one way see plnkr using ng-init which reduces 'currentUser.attributes' to just 'attr'.
With just attr.<properties> repeated
{{attr.userName}}
{{attr.login}}
{{attr.blahBlah}}
{{attr.yaddaYadda}}
Another way is you restructure your object and flatten it on the $scope.
This is not recommended because now you are putting primitives on to the $scope and are widening the scope with $scope.userName = currentUser.attributes.username. Also your 'repetitive' code is still there just in the Javascript.
In lieu of ng-init
ng-init="attr = currentUser.attributes"
You could also do this in controller
$scope.attr = currentUser.attributes;
This post really got me thinking. I had a theory on how to accomplish this using a directive.
Came up with a proof of concept on plnkr: http://embed.plnkr.co/OJDhpJ1maEdSoPvlbiRA/
If I understand correctly, you want to only display the properties within a given block of your struct.
Given the following struct:
$scope.currentUser = {
attributes: {
username: 'Batman',
age: '99',
address: {
street: 'Bat Cave'
}
}
};
You want to scope things down with something like:
<div scope-with="currentUser.attributes">
Username: {{username}}<br />
Age: {{age}}
<div scope-with="address">
Street: {{street}}
</div>
</div>
Directive:
angular.module('mymodule', [])
.directive('scopeWith', function($interpolate){
return {
restrict: 'A',
scope: {
scopeWith: '='
},
transclude: 'element',
compile: function(tElement, tAttrs, linker) {
return function( scope, element, attr) {
var childScope,
parent = element.parent(),
withBlock = null
;
scope.$watch('scopeWith', function(val){
childScope = scope.$new();
angular.forEach(val, function(val, prop){
childScope[prop] = val;
});
if(withBlock) {
withBlock.el.remove();
withBlock.scope.$destroy();
}
linker(childScope, function(clone){
withBlock = {};
parent.append(clone);
withBlock.el = clone;
withBlock.scope = childScope;
});
}, true);
};
}
};
Use {{currentUser.username}} to show Bob.
The ng-model on the div is irrelevant as it only applies to input elements.

AngularJS - Is there an easy way to set a variable on "sibling" scopes?

I have this problem where I am trying to make a click on a div hide all the other divs of the same "kind". Basically I'd have to, from a child scope set the variable on all other "sibling" scopes.
To illustrate this, I have created the following:
HTML
<div ng-app="myApp">
<div ng-controller="MyCtrl">
<div ng-repeat="model in models" ng-controller="MyChildCtrl">
<a ng-click="toggleVisibility()">toggle {{ model.name }} {{ visibility }}</a>
<div ng-show="visibility">
{{ model.name }}
</div>
</div>
</div>
</div>​
JavaScript
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
console.debug('scope');
$scope.models = [
{ name: 'Felipe', age: 30 },
{ name: 'Fernanda', age: 28 },
{ name: 'Anderson', age: 18 }
];
}
function MyChildCtrl($scope) {
$scope.visibility = false;
$scope.toggleVisibility = function() {
$scope.visibility = !$scope.visibility;
}
}
JSFiddle: http://jsfiddle.net/fcoury/sxAxh/4/
I'd like that, every time I show one of the divs, that all other divs would close, except the clicked one.
Any ideas?
#kolrie while your approach works I would suggest a different solution which doesn't require any changes to the model. The basic idea is to keep a reference to a selected item and calculate viability by comparing a current item (inside ng-repeat) with a selected one.
Using this solution the toggle function would become:
$scope.toggleVisibility = function(model) {
$scope.selected = model;
};
and calculating visibility is as simple as:
$scope.isVisible = function(model) {
return $scope.selected === model;
};
Finally the relevant part of the markup is to be modified as follows:
<div ng-controller="MyCtrl">
<div ng-repeat="model in models">
<a ng-click="toggleVisibility(model)">toggle {{ model.name }} {{ isVisible(model) }}</a>
<div ng-show="isVisible(model)">
{{ model.name }}
</div>
</div>
</div>
Here is a complete jsFiddle: http://jsfiddle.net/XfsPp/
In this solution you can keep your model untouched (important if you want to persist it back easily) and have AngularJS do all the heavy-lifting.
OK, I have added a visible attribute to the model, and I managed to get this done:
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
console.debug('scope');
$scope.models = [
{ name: 'Felipe', age: 30, visible: false },
{ name: 'Fernanda', age: 28, visible: false },
{ name: 'Anderson', age: 18, visible: false }
];
}
function MyChildCtrl($scope) {
$scope.toggleVisibility = function() {
angular.forEach($scope.models, function(model) {
model.visible = false;
});
$scope.model.visible = true;
}
}
Live here: http://jsfiddle.net/fcoury/sxAxh/5/
Is this the most efficient way? Do you think it's a good practice if I inject this visible attribute into my model data after getting it via AJAX?

Backbone & Underscore: unable to pass model to template for evaluation

I'm having trouble with passing model data from a Backbone View to a template in Underscore. I want to pass an array into the template so I can evaluate using _.each.
My code is below:
templateSettings
_.templateSettings = {
interpolate: /\{\{(.+?)\}\}/g,
evaluate: /\{\{([\s\S]+?)\}\}/g
};
Interpolate is {{ }} and evaluate is {[ ]}. Unless my regex is incorrect
View
el: $('#assasinationBackbone'),
events: {
'click #newHitJob': 'addNewHitJob'
},
initialize: function() {
},
addNewHitJob: function() {
var hitMen = new HitManList();
var template = _.template($('#newHitJobTemplate').html());
hitMen.fetch({
success: function() {
$('#newHitJobForm').html(template(hitMen.toJSON()));
return hitMen; //CANNOT REMEMBER WHY I PUT THIS HERE (NO SIDE EFFECTS)
}
});
});
I did not define the template in the view as template: , but instead I defined it inside the addNewHitJob property.
1. Is this correct? I did this because I will have more than one template. The said template is below
Template (in Jade), I can translate to html if need be
.span4#newHitJobForm
script#newHitJobTemplate(type="text/template")
select#names
{[ _.each(hitman, function(name) { ]}
option(value="{{ name._id }}") {{ name.name }}
{[ }); ]}
2. From what I have seen, My problem is with passing hitman to the template, but I am uncertain. Is there something I am missing?
Your evaluate regex isn't right, you want:
evaluate: /\{\[(.+?)\]\}/g
Your evaluate regex is equivalent to your interpolate since [\S\s] should match anything just like . does.
And when you call an Underscore template, you need to give it an object with the names and values for the template, just handing it an array (hitMen.toJSON()) won't do anything useful; you need to give your serialized hitMen a name so that the template can refer to them:
$('#newHitJobForm').html(template({ hitMen: hitMen.toJSON() }));
and then in the template:
{[ _.each(hitMen, function(hitMan) { ]}
option(value="{{ hitMan.cat._id }}") {{ hitMan.name }}
{[ }); ]}
I had to guess about the hitMan.cat._id part.

Resources