Passing arguments to angularjs filters - javascript

Is it possible to pass an argument to the filter function so you can filter by any name?
Something like
$scope.weDontLike = function(item, name) {
console.log(arguments);
return item.name != name;
};

Actually there is another (maybe better solution) where you can use the angular's native 'filter' filter and still pass arguments to your custom filter.
Consider the following code:
<div ng-repeat="group in groups">
<li ng-repeat="friend in friends | filter:weDontLike(group.enemy.name)">
<span>{{friend.name}}</span>
<li>
</div>
To make this work you just define your filter as the following:
$scope.weDontLike = function(name) {
return function(friend) {
return friend.name != name;
}
}
As you can see here, weDontLike actually returns another function which has your parameter in its scope as well as the original item coming from the filter.
It took me 2 days to realise you can do this, haven't seen this solution anywhere yet.
Checkout Reverse polarity of an angular.js filter to see how you can use this for other useful operations with filter.

From what I understand you can't pass an arguments to a filter function (when using the 'filter' filter). What you would have to do is to write a custom filter, sth like this:
.filter('weDontLike', function(){
return function(items, name){
var arrayToReturn = [];
for (var i=0; i<items.length; i++){
if (items[i].name != name) {
arrayToReturn.push(items[i]);
}
}
return arrayToReturn;
};
Here is the working jsFiddle: http://jsfiddle.net/pkozlowski_opensource/myr4a/1/
The other simple alternative, without writing custom filters is to store a name to filter out in a scope and then write:
$scope.weDontLike = function(item) {
return item.name != $scope.name;
};

Actually you can pass a parameter ( http://docs.angularjs.org/api/ng.filter:filter ) and don't need a custom function just for this. If you rewrite your HTML as below it'll work:
<div ng:app>
<div ng-controller="HelloCntl">
<ul>
<li ng-repeat="friend in friends | filter:{name:'!Adam'}">
<span>{{friend.name}}</span>
<span>{{friend.phone}}</span>
</li>
</ul>
</div>
</div>
http://jsfiddle.net/ZfGx4/59/

You can simply do like this
In Template
<span ng-cloak>{{amount |firstFiler:'firstArgument':'secondArgument' }}</span>
In filter
angular.module("app")
.filter("firstFiler",function(){
console.log("filter loads");
return function(items, firstArgument,secondArgument){
console.log("item is ",items); // it is value upon which you have to filter
console.log("firstArgument is ",firstArgument);
console.log("secondArgument ",secondArgument);
return "hello";
}
});

Extending on pkozlowski.opensource's answer and using javascript array's builtin filter method a prettified solution could be this:
.filter('weDontLike', function(){
return function(items, name){
return items.filter(function(item) {
return item.name != name;
});
};
});
Here's the jsfiddle link.
More on Array filter here.

You can pass multiple arguments to angular filter !
Defining my angular app and and an app level variable -
var app = angular.module('filterApp',[]);
app.value('test_obj', {'TEST' : 'test be check se'});
Your Filter will be like :-
app.filter('testFilter', [ 'test_obj', function(test_obj) {
function test_filter_function(key, dynamic_data) {
if(dynamic_data){
var temp = test_obj[key];
for(var property in dynamic_data){
temp = temp.replace(property, dynamic_data[property]);
}
return temp;
}
else{
return test_obj[key] || key;
}
}
test_filter_function.$stateful = true;
return test_filter_function;
}]);
And from HTML you will send data like :-
<span ng-bind="'TEST' | testFilter: { 'be': val, 'se': value2 }"></span>
Here I am sending a JSON object to the filter.
You can also send any kind of data like string or number.
also you can pass dynamic number of arguments to filter ,
in that case you have to use arguments to get those arguments.
For a working demo go here - passing multiple arguments to angular filter

You can simply use | filter:yourFunction:arg
<div ng-repeat="group in groups | filter:weDontLike:group">...</div>
And in js
$scope.weDontLike = function(group) {
//here your condition/criteria
return !!group
}

Related

Angular JS render variable in scope function to view and make comparison

Hi I have been using this tag to change my css style, if the condition totalAsset and sortedAsset are same
<div class="table-row" ng-repeat="x in myData" ng-click="sort()"
ng-class="{'lightblue': x.totalAsset == sortedAsset}">
totalAsset is my data in like this
$scope.myData = [
{
totalAsset: "23557"
},
{
totalAsset: "4512190",
},
{
totalAsset: "2190",
},
{
totalAsset: "1256790",
}
]
i have create a function that self sort the totalAsset
$scope.sort = function (){
$scope.unsortedAsset = $scope.myData.totalAsset;
$scope.sortedAsset=$scope.unsortedAsset.split("").sort().join("");
}
in the logic only the first and last row will become blue the other two rows remain same.
In sort() you directly access $scope.myData.totalAsset. This gets resolved as a reference to the last object in $scope.myData that has a totalAsset member.
Instead you wanted to iterate over all objects in myData. That can be achieved by supplying a parameter to the sort function like in the code below.
$scope.sort = function (totalAsset){
$scope.unsortedAsset = totalAsset;
$scope.sortedAsset=$scope.unsortedAsset.split("").sort().join("");
}
Then you also must call the sort function by supplying the parameter value.
<div class="table-row" ng-repeat="x in myData" ng-click="sort(x.totalAsset)" ng-class="{'lightblue': x.totalAsset == sortedAsset}">

Angular's orderBy filter and an array of object that has only methods

I have an array of objects. The only exposed interface of these objects are their methods.
obj = {
getId : function() { ... },
getName : function() { ... },
getAvgHours : function() { ... }
}
In fact, there are some fields that contain the actual values. But these are generated code so these fields are cryptically named, are buried under another layer of field. The access to their values should be done via supplied functions only.
I'm looking for something like below, but it doesn't work:
<tr ng-repeat="p in people | orderBy:p.getAvgHours()">
I could add a named function getAvgHours(p) { return p.getAvgHours(); } to the scope, which works. But it seems redundant, and I'd need to add a dozen of these functions.
Is there any better way?
You could simply make use of a predicate and pass the method you wish to call as a string.
The markup:
orderBy:fnValue('getId')
And the predicate function:
$scope.fnValue = function (fn) {
return function (obj) {
return obj[fn]();
};
};
Inside the function you return from the predicate, you will have access to the object being sorted, so you would invoke the method there and fetch the value.
DEMO
There may be a better way, but one solution would be to write your own filter that wraps the normal orderBy. Here's an example:
myApp.filter("orderByObjFunc", ["$filter", function($filter) {
return function(input, funcName, reverse) {
input = input.map(function(a) { a.zSortField = a[funcName](); return a; });
return $filter('orderBy')(input, 'zSortField', reverse);
};
}]);
This would save you from making a dozen helper functions. You'd use it like this:
<tr ng-repeat="p in people | orderByObjFunc:'getAvgHours'">
Here's a working fiddle.

How can I make Ember.js handlebars #each iterate over objects?

I'm trying to make the {{#each}} helper to iterate over an object, like in vanilla handlebars. Unfortunately if I use #each on an object, Ember.js version gives me this error:
Assertion failed: The value that #each loops over must be an Array. You passed [object Object]
I wrote this helper in attempt to remedy this:
Ember.Handlebars.helper('every', function (context, options) {
var oArray = [];
for (var k in context) {
oArray.push({
key : k,
value : context[k]
})
}
return Ember.Handlebars.helpers.each(oArray, options);
});
Now, when I attempt to use {{#every}}, I get the following error:
Assertion failed: registerBoundHelper-generated helpers do not support use with Handlebars blocks.
This seems like a basic feature, and I know I'm probably missing something obvious. Can anyone help?
Edit:
Here's a fiddle: http://jsfiddle.net/CbV8X/
Use {{each-in}} helper. You can use it like like {{each}} helper.
Example:
{{#each-in modelWhichIsObject as |key value|}}
`{{key}}`:`{{value}}`
{{/each-in}}
JS Bin demo.
After fiddling with it for a few hours, I came up with this hacky way:
Ember.Handlebars.registerHelper('every', function(context, options) {
var oArray = [], actualData = this.get(context);
for (var k in actualData) {
oArray.push({
key: k,
value: actualData[k]
})
}
this.set(context, oArray);
return Ember.Handlebars.helpers.each.apply(this,
Array.prototype.slice.call(arguments));
});
I don't know what repercussions this.set has, but this seems to work!
Here's a fiddle: http://jsfiddle.net/CbV8X/1/
I've been after similar functionality, and since we're sharing our hacky ways, here's my fiddle for the impatient: http://jsfiddle.net/L6axcob8/1/
This fiddle is based on the one provided by #lxe, with updates by #Kingpin2k, and then myself.
Ember: 1.9.1, Handlebars: 2.0.0, jQuery 2.1.3
Here we are adding a helper called every which can iterate over objects and arrays.
For example this model:
model: function() {
return {
properties: {
foo: 'bar',
zoo: 'zar'
}
};
}
can be iterated with the following handlebars template:
<ul class="properties">
{{#every p in properties}}
<li>{{p.key}} : {{p.value}}</li>
{{/every}}
</ul>
every helper works by creating an array from the objects keys, and then coordinating changes to Ember by way of an ArrayController. Yeah, hacky. This does however, let us add/remove properties to/from an object provided that object supports observation of the [] property.
In my use case I have an Ember.Object derived class which notifies [] when properties are added/removed. I'd recommend looking at Ember.Set for this functionality, although I see that Set been recently deprecated. As this is slightly out of this questions scope I'll leave it as an exercise for the reader. Here's a tip: setUnknownProperty
To be notified of property changes we wrap non-object values in what I've called a DataValueObserver which sets up (currently one way) bindings. These bindings provide a bridge between the values held by our internal ArrayController and the object we are observing.
When dealing with objects; we wrap those in ObjectProxy's so that we can introduce a 'key' member without the need to modify the object itself. Why yes, this does imply that you could use #every recursively. Another exercise for the reader ;-)
I'd recommend having your model be based around Ember.Object to be consistent with the rest of Ember, allowing you to manipulate your model via its get & set handlers. Alternatively, as demonstrated in the fiddle, you can use Em.Get/Em.set to access models, as long as you are consistent in doing so. If you touch your model directly (no get/set), then every won't be notified of your change.
Em.set(model.properties, 'foo', 'asdfsdf');
For completeness here's my every helper:
var DataValueObserver = Ember.Object.extend({
init: function() {
this._super();
// one way binding (for now)
Em.addObserver(this.parent, this.key, this, 'valueChanged');
},
value: function() {
return Em.get(this.parent, this.key);
}.property(),
valueChanged: function() {
this.notifyPropertyChange('value');
}
});
Handlebars.registerHelper("every", function() {
var args = [].slice.call(arguments);
var options = args.pop();
var context = (options.contexts && options.contexts[0]) || this;
Ember.assert("Must be in the form #every foo in bar ", 3 == args.length && args[1] === "in");
options.hash.keyword = args[0];
var property = args[2];
// if we're dealing with an array we can just forward onto the collection helper directly
var p = this.get(property);
if (Ember.Array.detect(p)) {
options.hash.dataSource = p;
return Ember.Handlebars.helpers.collection.call(this, Ember.Handlebars.EachView, options);
}
// create an array that we will manage with content
var array = Em.ArrayController.create();
options.hash.dataSource = array;
Ember.Handlebars.helpers.collection.call(this, Ember.Handlebars.EachView, options);
//
var update_array = function(result) {
if (!result) {
array.clear();
return;
}
// check for proxy object
var result = (result.isProxy && result.content) ? result.content : result;
var items = result;
var keys = Ember.keys(items).sort();
// iterate through sorted array, inserting & removing any mismatches
var i = 0;
for ( ; i < keys.length; ++i) {
var key = keys[i];
var value = items[key];
while (true) {
var old_obj = array.objectAt(i);
if (old_obj) {
Ember.assert("Assume that all objects in our array have a key", undefined !== old_obj.key);
var c = key.localeCompare(old_obj.key);
if (0 === c) break; // already exists
if (c < 0) {
array.removeAt(i); // remove as no longer exists
continue;
}
}
// insert
if (typeof value === 'object') {
// wrap object so we can give it a key
value = Ember.ObjectProxy.create({
content: value,
isProxy: true,
key: key
});
array.insertAt(i, value);
} else {
// wrap raw value so we can give it a key and observe when it changes
value = DataValueObserver.create({
parent: result,
key: key,
});
array.insertAt(i, value);
}
break;
}
}
// remove any trailing items
while (array.objectAt(i)) array.removeAt(i);
};
var should_display = function() {
return true;
};
// use bind helper to call update_array if the contents of property changes
var child_properties = ["[]"];
var preserve_context = true;
return Ember.Handlebars.bind.call(context, property, options, preserve_context, should_display, update_array, child_properties);
});
Inspired by:
How can I make Ember.js handlebars #each iterate over objects?
http://mozmonkey.com/2014/03/ember-getting-the-index-in-each-loops/
https://github.com/emberjs/ember.js/issues/4365
https://gist.github.com/strathmeyer/1371586
Here's that fiddle again if you missed it:
http://jsfiddle.net/L6axcob8/1/

.filter jquery error

I'm having a problem, here is the javascript/jquery
getTextForDisplay: function() {
var displayText = "Select...";
var options = this.dataSource._data;
var selectedOptions = $.filter(options, function(index){
return this.selected;
});
if (selectedOptions.length == 1) {
displayText = "length1";
}
else if (selectedOptions.length > 1) {
displayText = "Multiple...";
}
return displayText;
}
});
so this is in regards to a multi-select dropdown box that has checkboxes, the options variable is an observable array pulling its data from a viewmodel, so what I am trying to do is to display "length1" if only one of the checkboxes is selected and to display "Multiple..." if more than one checkbox is selected, this seems pretty straightforward but I keep getting a error in when I run it. the error is c.replace is not a function and the error is in the jquery.min.js file. If I remove index from the .filter then it still doesn't work but it doesn't error out either.
jQuery doesn't define a jQuery.filter() function (at least, not in the public API). The .filter() it does define is a method for jQuery collections.
Perhaps jQuery.grep() is what you're looking for?
var selectedOptions = $.grep(options, function (option, index) {
return option.selected;
});
There is no such thing as $.filter(), unless you wrote it yourself or are using a plugin.
The correct syntax is
options.filter(function (index) {
...
});
Here's the documentation: http://api.jquery.com/filter/
You aren't properly using the jQuery.filter method, however, there is a documented method that does what you need. You should use documented methods rather than undocumented methods.
$.grep(options, function(){
return this.selected;
});
For this to work, options must be an array-like structure.

Pass variables to AngularJS controller, best practice?

I am brand new to AngularJS and like what I've seen so far, especially the model / view binding. I'd like to make use of that to construct a simple "add to basket" piece of functionality.
This is my controller so far:
function BasketController($scope) {
$scope.products = [];
$scope.AddToBasket = function (Id, name, price, image) {
...
};
}
And this is my HTML:
<a ng-click="AddToBasket('237', 'Laptop', '499.95', '237.png')">Add to basket</a>
Now this works but I highly doubt this is the right way to create a new product object in my model. However this is where my total lack of AngularJS experience comes into play.
If this is not the way to do it, what is best practice?
You could use ng-init in an outer div:
<div ng-init="param='value';">
<div ng-controller="BasketController" >
<label>param: {{value}}</label>
</div>
</div>
The parameter will then be available in your controller's scope:
function BasketController($scope) {
console.log($scope.param);
}
You could create a basket service. And generally in JS you use objects instead of lots of parameters.
Here's an example: http://jsfiddle.net/2MbZY/
var app = angular.module('myApp', []);
app.factory('basket', function() {
var items = [];
var myBasketService = {};
myBasketService.addItem = function(item) {
items.push(item);
};
myBasketService.removeItem = function(item) {
var index = items.indexOf(item);
items.splice(index, 1);
};
myBasketService.items = function() {
return items;
};
return myBasketService;
});
function MyCtrl($scope, basket) {
$scope.newItem = {};
$scope.basket = basket;
}
I'm not very advanced in AngularJS, but my solution would be to use a simple JS class for you cart (in the sense of coffee script) that extend Array.
The beauty of AngularJS is that you can pass you "model" object with ng-click like shown below.
I don't understand the advantage of using a factory, as I find it less pretty that a CoffeeScript class.
My solution could be transformed in a Service, for reusable purpose. But otherwise I don't see any advantage of using tools like factory or service.
class Basket extends Array
constructor: ->
add: (item) ->
#push(item)
remove: (item) ->
index = #indexOf(item)
#.splice(index, 1)
contains: (item) ->
#indexOf(item) isnt -1
indexOf: (item) ->
indexOf = -1
#.forEach (stored_item, index) ->
if (item.id is stored_item.id)
indexOf = index
return indexOf
Then you initialize this in your controller and create a function for that action:
$scope.basket = new Basket()
$scope.addItemToBasket = (item) ->
$scope.basket.add(item)
Finally you set up a ng-click to an anchor, here you pass your object (retreived from the database as JSON object) to the function:
li ng-repeat="item in items"
a href="#" ng-click="addItemToBasket(item)"

Categories

Resources