Angular with filter in different view - angularjs

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

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.

How to pass multiple parameter in angular filter function, not custom filter

I tried hard and visit lot of similar question like this but still unable to solve this issue.
I want to pass extra parameter in angular filter function. I found solution as below but it's not working. I am getting undefined for object which I have used in ng-repeat.
<li ng-repeat="user in users | filter:isStatus(user,secondParam)">{{user.name}}</li>
There are solution for angular custom filter as below but that also not working with angular filter function.
<li ng-repeat="user in users | filter:isStatus:user:secondParam">{{user.name}}</li>
jsFiddle - You can see my problem here.
Will try:
$scope.isStatus = function(secondParam, thirdParam){
return function(user) {
console.log(secondParam);
console.log(thirdParam);
return user.status == $scope.status;
}
Updated version http://jsfiddle.net/4PYZa/282/
According to your case, you can use predicate expression instead of custom filter:
<li ng-repeat="user in users | filter:{status: status, name: name}">{{user.name}}</li>
Take a look at this fiddle: http://jsfiddle.net/ovym2tpr/28/
You can use custom filter in anyway, it just performs not very well especially under nested ng-repeat
How do I call an Angular.js filter with multiple arguments?
AngularJS : Custom filters and ng-repeat
myApp.filter("isStatus ", function() { // register new filter
return function(user, secondParam, thirdParam) { // filter arguments
return user.status == $scope.status; // implementation
};
});
Calling from Template
<li ng-repeat="user in users | isStatus:secondParam">{{user.name}}</li>
It's very simple , just do this
<li ng-repeat="user in users | filter:user | filter : secondParam)">{{user.name}}</li>

What is the "right" way in Angularjs of doing "master-detail" inter directive communication

I have a directive that displays a list of "master" items and when the user clicks on one of these items I want any "details" directives on the page (there could be more than one) to be updated with the details of the currently selected "master" item.
Currently I'm using id and href attributes as a way for a "details" directive to find its corresponding master directive. But my impression is that this is not the angular way, so if it's not, what would be a better solution?
I appreciate that typically when the issue of inter-communication between directives is raised then the obvious solutions are either to use require: "^master-directive" or to use a service, but in this case the directives are not in the same hierarchy and I don't think using a service is appropriate, as it would make the solution more complicated.
This is some illustrative code showing what I'm doing currently.
<div>
<master-list id="master1"></master-list>
</div>
<div>
<details-item href="#master1" ></details-item>
</div>
In the master-list directive when an item is selected I set an attribute to indicate the currently selected master item:
attrs.$set('masterListItemId',item.id);
In the details-item directive's link function I do:
if (attrs.href) {
var id = attrs.href.split('#')[1];
var masterList = angular.element(document.getElementById(id));
if (masterList) {
var ctrl = masterList.controller('masterList');
ctrl.attrs().$observe('masterListItemId',function(value) {
attrs.$set('detailItemId',value);
});
}
}
attrs.$observe('detailItemId',function(id) {
// detail id changed so refresh
});
One aspect that put me off from using a service for inter-directive communication was that it is possible (in my situation) to have multiple 'masterList' elements on the same page and if these were logically related to the same service, the service would end up managing the selection state of multiple masterList elements. If you then consider each masterList element had an associated detailItem how are the right detailItem elements updated to reflect the state of its associated masterList?
<div>
<master-list id="master1"></master-list>
</div>
<div>
<master-list id="master2"></master-list>
</div>
<div>
<details-item href="#master1" ></details-item>
</div>
<div>
<details-item href="#master2" ></details-item>
</div>
Finally I was trying to use directives, rather than using controller code (as has been sensibly suggested) as I'd really like the relationship between a masterList and its associated detailItems to be 'declared' in the html, rather than javascript, so it is obvious how the elements relate to each other by looking at the html alone.
This is particularly important as I have users that have sufficient knowledge to create a html ui using directives, but understanding javascript is a step too far.
Is there a better way of achieving the same thing that is more aligned with the angular way of doing things?
I think I would use a service for this. The service would hold the details data you care about, so it would look something like this.
In your master-list template, you might have something like a list of items:
<ul>
<li ng-repeat"item in items"><a ng-click="select(item)">{{item.name}}</a></li>
</ul>
...or similar.
Then in your directives, you would have (partial code only)
.directive('masterList',function(DetailsService) {
return {
controller: function($scope) {
$scope.select = function(item) {
DetailsService.pick(item); // or however you get and retrieve data
};
}
};
})
.directive('detailsItem',function(DetailsService) {
return {
controller: function($scope) { // you could do this in the link as well
$scope.data = DetailsService.item;
}
};
})
And then use data in your details template:
<div>Details for {{data.name}}</div>
<ul>
<li ng-repeat="detail in data.details">{{detail.description}}</li>
</ul>
Or something like that.
I would not use id or href, instead use a service to retrieve, save and pass the info.
EDIT:
Here is a jsfiddle that does it between 2 controllers but a directive would be the same idea
http://jsfiddle.net/u3u5kte7/
EDIT:
If you want to have multiple masters and details, leave the templates unchanged, but change your directive controllers and services as follows:
.directive('masterList',function(DetailsService) {
return {
controller: function($scope) {
$scope.select = function(item) {
DetailsService.pick($scope.listId,item); // or however you get and retrieve data
};
}
};
})
.directive('detailsItem',function(DetailsService) {
return {
controller: function($scope) { // you could do this in the link as well
$scope.data = DetailsService.get($scope.listId).item;
}
};
})
.factory('DetailsService',function(){
var data = {};
return {
pick: function(id,item) {
data[id] = data[id] || {item:{}};
// set data[id].item to whatever you want here
},
get: function(id) {
data[id] = data[id] || {item:{}};
return data[id];
}
};
})
I would opt for a different approach altogether without directives. Directives are ideal for DOM manipulation. But in this case I would stick to using just the template and a controller that manages all the data and get rid of the directives. Use ng-repeat to repeat the items
Check out this fiddle for an example of this: http://jsfiddle.net/wbrand/2xrne4k3
template:
<div ng-controller="ItemController as ic">
Masterlist:
<ul><li ng-repeat="item in ic.items" ng-click="ic.selected($index)">{{item.prop1}}</li></ul>
Detaillist:
<ul><li ng-repeat="item in ic.items" >
{{item.prop1}}
<span ng-if="item.selected">SELECTED!</span>
</li></ul>
</div>
controller:
angular.module('app',[]).controller('ItemController',function(){
this.items = [{prop1:'some value'},{prop1:'some other value'}]
this.selectedItemIndex;
this.selected = function(index){
this.items.forEach(function(item){
item.selected = false;
})
this.items[index].selected = true
}
})

Globally accessible filter for angular app

In an AngularJS app, I have a view with a list of tasks and a sidebar with a list of filtering options to filter those tasks. Both are in separate controllers.
Sidebar
<div ng-controller="SidebarController">
Completed
</div>
Tasks
<div ng-controller="TaskController">
<tr ng-repeat="task in tasks | filter: search">
<td>task.title</td>
<td>task.status</td>
</tr>
</div>
SidebarController
$scope.search = [];
$scope.showOnlyCompleted = function()
{
// set search filter
$scope.search.status = 'completed';
};
TaskContoller
$scope.tasks = [
{ title: 'do something', status:'todo' },
{ title: 'do something else', status:'completed' }
];
The question is, how can I make it such that the filter, search, can be manipulated through SidebarController, but can still be used to filter the tasks coming from TaskController? It's a scoping problem, because both controllers need to be able to have access to it?
I am thinking the solution might be something injectable, like a service, but I don't really know how to make that work for this case with data-binding?
I saw one nice solution for this here. Basically just create a filterService with some state, inject that to your controllers and bind to that directly in the view.

Binding a model to a backbone client template

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')

Resources