angular filter name as variable - angularjs

I'm designing universal table that reads data and columns from ajax.
In columns description is also filter name which angular should use for a specific column.
But in HTML templates I can't use variables for filter names:/
Is there a solution for that? Or should I code javascript loop with data source?
Here is code example:
<tr ng-repeat="item in data">
<td ng-repeat="col in cols">
{{item[col.source]}}
<span ng-if="col.ngFilter">
{{col.ngFilter}} // ex. "state" filter
{{item[col.source]|col.ngFilter}} //is not working - is looking for "col.ngFilter" not "state" filter.
</span>
</td>
</tr>

You cannot do it in your HTML. First, you need to apply the filter in your controller.
function MyCtrl($scope, $filter) {
$scope.applyFilter = function(model, filter) {
return $filter(filter)(model);
};
}
Then, in your HTML:
Instead of
{{item[col.source]|col.ngFilter}}
use
{{applyFilter(item[col.source], col.ngFilter)}}

For anyone looking to do something like
{{applyFliter(item[col.source], col.ngFilter)}}
where ngFilter might contains some colon separated parameters such as
currency:"USD$":0
I ended up writing this little helper
function applyFilter (model, filter){
if(filter){
var pieces = filter.split(':');
var filterName = pieces[0];
var params = [model];
if(pieces.length>1){
params = params.concat(pieces.slice(1));
}
return $filter(filterName).apply(this,params);
}else{
return model;
}
}

Related

Grails GSP Loop through an index and do somthing with selected lines

In an Index-gsp, I want to be able to select an arbitrary number of lines and then by clicking a link send all those lines to a controller for processing e.g. creating new objects of a different kind.
I've no idea how selection can be done or how to collect these selected lines in a GSP. Maybe I should use a checkbox on each line if that's possible?
It's a list of products which is displayed using a modified index.gsp.
Each product-line has a checkbox in front.
What I want is to make a list of the products that are checked an then transmit this list to a controller.
a part of this index.gsp:
<li><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></li>
<li><g:link class="create" action="create"><g:message code="default.new.label" args="[entityName]" /></g:link></li>
<li><g:link class="create" action="createOffer"><g:message code="default.new.label" args="[entityName]" params="toOffer" /></g:link></li>
</ul>
</div>
<div id="list-prodBuffer" class="content scaffold-list" role="main">
<h1><g:message code="default.list.label" args="[entityName]" /></h1>
<g:if test="${flash.message}">
<div class="message" role="status">${flash.message}</div>
</g:if>
<table>
<thead>
<tr>
<td> Välj</td>
<td> ID</td>
</tr>
</thead>
<tbody>
<g:each in="${prodBufferList}" status="i" var="prodBuffer">
<tr class="${ (i % 2) == 0 ? 'even': 'odd'}">
<td><g:checkBox name="toOffer" value="${prodBuffer.id}" checked="false" /></td>
<td>${prodBuffer.id}</td>
So this not an ordinary form, just a list where I want to use a link to transmit it to the controller.
I'm a beginner and have no idea how to do it.
You can collect all necessary data from page using javascript, and then send all data to your controller for processing.
There are a lot of ways to do it.
For example send via JQuery:
<script>
//some code
var items = [1,2,3];
//some code
$('#add-location').click(function () {
$.ajax({
type: "POST",
url: "${g.createLink(controller:'myController', action: 'myControllerMethod')}",
data: {items: items},
success: function (data) {
console.log(data)
}
});
});
</script>
I will answer this but have to slow down since it feels like i am beginning to write your project:
In gsp you will need to have a hidden field followed by a check box amongst data you are trying to capture, checkbox should contain all the data elements required to build your output.
<g:hiddenField name="userSelection" value=""/>
<g:checkBox name="myCheckBox" id='myCheckBox' value="${instance.id}"
data-field1="${instance.field1}" data-field1="${instance.field1}"
checked="${instance.userSelected?.contains(instance.id)?true:false}" />
In the java script segment of the page you will need to add the following
This will then auto select selection and add to javascript array
// Customized collection of elements used by both selection and search form
$.fn.serializeObject = function() {
if ($("[name='myCheckBox']:checked").size()>0) {
var data=[]
$("[name='myCheckBox']:checked").each(function() {
var field1=$(this).data('field1');
var field2=$(this).data('field2');
data.push({id: this.value, field1:field1, field2:field2 });
});
return data
}
};
Most importantly will your data sit across many different gsp listing pages if so you will need to hack pagination:
//Modify pagination now to capture
$(".pagination a").click(function() {
var currentUrl=$(this).attr('href');
var parsedUrl=$(this).attr('href', currentUrl.replace(/\&userSelection=.*&/, '&').replace(/\&userSelection=\&/, '&'));
var newUrl=parsedUrl.attr('href') + '&userSelection=' + encodeURIComponent($('#userSelection').val());
window.location.href=newUrl
return false;
});
Then in the controller parse the JSON form field and make it into what you want when posted
def u=[]
def m=[:]
if (params.userSelection) {
def item=JSON.parse(params.userSelection)
item?.each {JSONObject i->
// When field1 is null in JSON set it as null properly
if (JSONObject.NULL.equals(i.field1)) {
i.field1=null
}
if (resultsGroup) {
if (!resultsGroup.contains(i.id as Long)) {
u << i
}
} else {
u << i
}
}
m.userSelected=item?.collect{it.id as Long}
m.results=u
}
return m

AngularJS filter with multiple parameters

I want to be able to filter my table with many parameters passed in as an array. Therefore I can build up an array of filter params and pass them in. I don't want to explicitly state what columns to filter against as there could be many columns (some which will be shown and some not).
The HTML looks something like this;
<tr ng-repeat="item in infoData | filter:['param1','param2']">
<td>{{item.data1}}</td>
<td>{{item.data2}}</td>
<td>{{item.data3}}</td>
<td>{{item.data4}}</td>
</tr>
Is there away to filter a table against multiple parameters?
Thanks
This is the quick and dirty way to accomplish what you need.
First create a custom filter in the controller something like this:
$scope.customFilter = function(param1, param2) {
return function(item) {
//return custom stuff here
}
}
then in the html you do this
<tr ng-repeat="item in infoData | filter:customFilter(param1, param2)">
<td>{{item.data1}}</td>
<td>{{item.data2}}</td>
<td>{{item.data3}}</td>
<td>{{item.data4}}</td>
</tr>
this is an example with a custom filter
app.filter('customFilter', function (param1, param2) {
return function (item) {
//return custom stuff here
};
});
and now in the html you do this:
<tr ng-repeat="item in infoData | customFilter(param1, param2)">
<td>{{item.data1}}</td>
<td>{{item.data2}}</td>
<td>{{item.data3}}</td>
<td>{{item.data4}}</td>
</tr>

Angular.js - search filter for object with ng-repeat

I'm apologizing for messy description of my problem. I hope you understand it.
I have this HTML code:
<form>
<input ng-model="attr.query" type="text" placeholder="{{attr.attr_name}}" ng-repeat="attr in attrs">
</form>
<table>
<tr ng-repeat="element in elements">
<td ng-repeat="(key, value) in element">{{value}}</td>
</tr>
</table>
JS controller:
$scope.attrs = [{'descr':'descr1'},{'descr':'descr2'}];
$scope.elements = [{'property1" : 'value1', 'property2' : 'value2'},{'property1" : 'value3', 'property2' : 'value4'}];
I need to filter each by query from input. But i need to filter only with the same attr as in input field.
I have some troubles to apply filter to array of objects.
Thanks
If I understand you correctly (I don't have enough rep to ask in a comment, sorry), you want to filter the data on one or more of several attributes.
The simplest way to do this is probably by defining a custom filter function accessible to your scope. AngularJS's filter filter will happily accept that as an evaluator.
$scope.customFilter = function(item) {
var passed = true;
if(/* the item doesn't pass muster */) {
passed = false;
}
return passed;
}
If it helps, I put together a fiddle to demonstrate. (NB. The query fields are case-sensitive.)

Table filter by predicate

I made a jsfiddle to show what is my problem.
The fisrt part is working in a partial way. See line number 15. I put the predicate in the filter (predicate is l_name) by hand and is working. The table is filtered by Last Name column.
<tr ng-repeat="item in items | filter:{l_name:myInput}">
The second part of the sample is not working when I use the select (model named mySelect2) to choose the predicate where I'm going to filter (see line number 36).
What I'm trying to do is use the select to choose the column by predicate and the input to filter in that column.
<tr ng-repeat="item in items | filter:{mySelect2:myInput2}">
Am I missing something or the binding of the select (mySelect2) must update the filter on the table?
Thanks for the help!
PS: type jo in the input.
Here's a fiddle with some options: http://jsfiddle.net/jgoemat/tgKkD/1/
Option 1 - Search on multiple fields
You can use an object on your model ('search' here) as your filter and separate input boxes for l_name and f_name. This allows you not only to filter on either, but filter on both:
any: <input ng-model="search.$"/><br/>
l_name: <input ng-model="search.l_name"/><br/>
f_name: <input ng-model="search.f_name"/><br/>
<!-- skipping code -->
<tr ng-repeat="item in items|filter:search">
Option 2 - Use a function on your controller
The built-in filter can take a function as an argument that should return true if the object should be included. This function takes the object to be filtered as its only argument and returns true if it should be included. Html:
<tr ng-repeat="item in items|filter:filterFunc">
controller function:
$scope.filterFunc = function(obj) {
// property not specified do we want to filter all instead of skipping filter?
if (!$scope.mySelect)
return obj;
if (obj[$scope.mySelect].toLowerCase().indexOf($scope.myInput.toLowerCase()) >= 0)
return obj;
return false;
};
Option 3 - Create a custom filter
This filter function will take the whole list as an argument and return the filtered list. This does require you to create an angular module and specify it in the ng-app tag like ng-app="MyApp"Html:
<tr ng-repeat="item in items|MyFilter:mySelect:myInput">
Code:
var app = angular.module('MyApp', []);
app.filter('MyFilter', function() {
return function(list, propertyName, value) {
console.log('MyFilter(list, ', propertyName, ', ', value, ')');
// property not specified do we want to filter all instead of skipping filter?
if (!propertyName)
return list;
var newList = [];
var lower = value.toLowerCase();
angular.forEach(list, function(v) {
if (v[propertyName].toLowerCase().indexOf(lower) >= 0)
newList.push(v);
});
return newList;
}
});
Option 4: ng-show
The built-in filter filter expressions don't let you use any expression, but ng-show does so you can just limit visible items like so:
<tr ng-show="item[mySelect].toLowerCase().indexOf(myInput.toLowerCase()) >= 0 || !mySelect" ng-repeat="item in items">
I think option 1 is easy and flexible. If you prefer your drop-down + field UI then I think option 3 is the most useful, and you can re-use it as a dependency in other apps like this:
var app = angular.module("NewApp", ["MyApp"]);
I would just name it something better like 'filterByNamedProperty'. Option 2 is easy but it is tied to your controller. Option 4 is messy and I wouldn't use it.
What about using a custom filter? Users concatenate the property with the criteria (e.g. last:jo). In the filter, split on the colon, and use the first part as the property name and the second part as the criteria.
You may pass scope variables to your filters:
<tr ng-repeat="item in items | filter:myScopeVariable">
This means that you may define your filter object in controller and it will be used by the filter:
$scope.$watch('mySelect2', function(val){
$scope.myScopeVariable = {};
$scope.myScopeVariable[val] = $scope.myInput2;
});
$scope.$watch('myInput2', function(val){
$scope.myScopeVariable = {};
$scope.myScopeVariable[$scope.mySelect2] = $scope.myInput2;
});
Demo Fiddle

How to ng-repeat into html table with multiple levels of json?

I have an object of social media stats. I'm trying to ng-repeat them into a table. Here's my plunker.
HTML:
<table>
<tr ng-repeat="(metric, metricData) in data">
<td>{{metric}}</td>
<td>{{metricData}}</td>
</tr>
</table>
Controller object:
$scope.data = { buzz:0,
Delicious:121,
Facebook:
{
like_count: "6266",
share_count: "20746"
},
GooglePlusOne:429,
LinkedIn:820,
Twitter:4074
};
I run into a problem when I get to the Facebook results. Within the <td> that entire object gets displayed (as it should be with how I have my code setup). But what I'd rather have happen is to repeat through that object and display the key and value in the cell.
I tried doing something looking to see if metricData is an object and doing some sort of ng-repeat on that. But I wasn't having luck with that. Any idea on how I can display the inner object (keys & value) within the cells?
You can define a scope function returning the type of metricData :
$scope.typeOf = function(input) {
return typeof input;
}
And then you can display it according to its type :
<tr ng-repeat="(metric, metricData) in data">
<td>{{metric}}</td>
<td ng-switch on="typeOf(metricData)">
<div ng-switch-when="object">
<div ng-repeat="(key, value) in metricData">
<span>{{key}}</span>
<span>{{value}}</span>
</div>
</div>
<span ng-switch-default>{{metricData}}</span>
</td>
</tr>
You can see it in this Plunker
Sounds like you'll need a specific directive that wires up children to be recursive, take a look at this example: Recursion in Angular directives
What you'd check on is if what you need to repeat is an object and not a value, then add the new element compile it, and start the process over again.
I'm assuming you want each of those values to have their own line but you don't explain exactly how you want it to work. I think the matter would best be handled by passing a clean version of what you want to the ng-repeat directive. I'm assuming you want two rows for facebook in your sample. You could create a filter to flatten the metrics so there are properties "Facebook_like_count" and "Facebook_share_count" (PLUNKER):
app.filter('flatten', function() {
function flattenTo(source, dest, predicate) {
predicate = predicate || '';
angular.forEach(source, function(value, key) {
if (typeof(value) == 'object') {
flattenTo(value, dest, predicate + key + '_');
} else {
dest[predicate + key] = value;
}
});
}
return function(input) {
var obj = {};
flattenTo(input, obj, '');
return obj;
}
});
Then your repeat can use the filter:
<tr ng-repeat="(metric, metricData) in data|flatten">

Resources