I am following this approach to filter nested json response. I have a nested property like this:
instances:{
instance:[
{cname:'name1', location:'pa', price:40, model:'2014' },
{cname:'name1', location:'ga', price:30 , model:'2014'},
{cname:'name1', location:'ga', price:20, model:'2010' }
]}
I can filter by top level properties using the above mentioned example but not the child properties.
I have modified above example to show nested properties of my json here.http://jsfiddle.net/jackAndy/qygL2m01/4/. I am new to angularjs.
First of all - why You use instances.instance? It it not principally, use players.instances = [];
Use Group functions only 1 time after data loading; Watching filters - it's not necessary in this case;
Function for get filters values (I use underscore uniq function, You can use Your own algorithm for this):
$scope.getFieldsValues = function(field){
var result = [];
for(var i = 0; i < $scope.players.length; i++){
result.push($scope.players[i][field]);
}
return _.uniq(result);
};
Filter for players:
$scope.testFl = function(el){
for(var filter in $scope.filters){
var filterArray = [];
for(var i in $scope.filters[filter]){
if($scope.filters[filter][i]) filterArray.push(i);
}
//You can make array with instances properties & compare with it;
if(filter === 'location'){
if(el.instances && el.instances.length > 0){
var intersection = el.instances.filter(function(n) {
return filterArray.indexOf(n[filter]) != -1
});
} else if(filterArray.length > 0){return false;}
} else {
if(filterArray.length > 0 && filterArray.indexOf(el[filter]) === -1) return false;
}
}
return true;
};
Template:
<li ng-repeat="player in players | filter:testFl" >
Filter for instances:
$scope.testFl2 = function(el){
var filterArray = [];
for(var i in $scope.filters.location){
if($scope.filters.location[i]) filterArray.push(i);
}
return filterArray.length > 0 && filterArray.indexOf(el.location) === -1 ? false : true;
};
Template:
<span ng-repeat="loc in player.instances | filter:testFl2" >
Fiddle for this;
UPDATE:
Function for count:
$scope.getCount = function(field, value){
var obj = {};
obj[field] = value;
return _.where($scope.players, obj).length;
};
Update fiddle - update underscore, add count function;
I hope this will help you;
For answer were used:
Add underscore to jsfiddle;
variable property name in where underscore.js;
I have items which should have multiple (e.g. categories). Now I want to filter my items to these categories.
I think the task is not possible with the filter-directive without using a custom filter, right?
I came up with a solution, but it looks dirty and wrong to me:
$scope.filterList = function (item) {
var found = false;
var allFalse = true;
angular.forEach(item.attributes, function (value, key) {
if ($scope.activeAttributes[value.name] === true) {
found = true;
}
});
angular.forEach($scope.activeAttributes, function (value, key) {
if (value === true) {
allFalse = false;
}
});
$log.log("length: " + Object.keys($scope.activeAttributes).length);
if (found === true || Object.keys($scope.activeAttributes).length === 0 || allFalse === true) {
return true;
}
};
Demo JSFiddle of my code
I thought with Angular, that the code should be simple and most of the work should be done by Angular. What if I need to filter more attributes?
I have create a filter but this filter is not working with array inside array.
'http://plnkr.co/edit/oygy79j3xyoGJmiPHm4g?p=info'
Above plkr link is working demo.
app.filter('checkboxFilter', function($parse) {
var cache = { //create an cache in the closure
result: [],
checkboxData: {}
};
function prepareGroups(checkboxData) {
var groupedSelections = {};
Object.keys(checkboxData).forEach(function(prop) {
//console.log(prop);
if (!checkboxData[prop]) {
return;
} //no need to create a function
var ar = prop.split('=');
//console.log("ar is - "+ar);
if (ar[1] === 'true') {
ar[1] = true;
} //catch booleans
if (ar[1] === 'false') {
ar[1] = false;
} //catch booleans
/* replacing 0 with true for show all offers */
if(ar[0]=='SplOfferAvailable.text'){
ar[1]='true';
}else{
}
//make sure the selection is there!
groupedSelections[ar[0]] = groupedSelections[ar[0]] || [];
//at the value to the group.
groupedSelections[ar[0]].push(ar[1]);
});
return groupedSelections;
}
function prepareChecks(checkboxData) {
var groupedSelections = prepareGroups(checkboxData);
var checks = [];
//console.log(groupedSelections);
Object.keys(groupedSelections).forEach(function(group) {
//console.log("groupedSelections- "+groupedSelections);
//console.log("group- "+group);
var needToInclude = function(item) {
//console.log("item- "+item);
// use the angular parser to get the data for the comparson out.
var itemValue = $parse(group)(item);
var valueArr = groupedSelections[group];
//console.log("valueArr- "+valueArr);
function checkValue(value) { //helper function
return value == itemValue;
}
//check if one of the values is included.
return valueArr.some(checkValue);
};
checks.push(needToInclude); //store the function for later use
});
return checks;
}
return function(input, checkboxData, purgeCache) {
if (!purgeCache) { //can I return a previous 'run'?
// is the request the same as before, and is there an result already?
if (angular.equals(checkboxData, cache.checkboxData) && cache.result.length) {
return cache.result; //Done!
}
}
cache.checkboxData = angular.copy(checkboxData);
var result = []; // this holds the results
//prepare the checking functions just once.
var checks = prepareChecks(checkboxData);
input.every(function(item) {
if (checks.every(function(check) {
return check(item);
})) {
result.push(item);
}
return result.length < 10000000; //max out at 100 results!
});
cache.result = result; //store in chache
return result;
};
});
above code is for check box filter.
when i click on checkbox called "Availability" it does not filter the result.
Please help me out.
Thanks.
I think that the way you are navigating through json is wrong because if you put in this way it works
"Location": "Riyadh",
"AvlStatus": "AVAILABLE"
"Rooms": {.....
You have to go in some way through Rooms and right now I think you're not doing that
I have a massive validate function in my backbone model that could be simplified if only I could detect the attribute I'm testing against. There are a few ideas of how I can think to approach this problem, but they all rely on knowing the attribute name I'm testing, without hardcoding it as I am in the id variable below.
Here is an example:
validate : function(attr){
var t = this;
if(attr.user1DobMonth && attr.user1DobMonth != t.get('user1DobMonth')){
var val = jQuery.trim(attr.user1DobMonth.toLowerCase()),
id = 'user1DobMonth',
error = {
attr : id
};
if(val === 'select'){
return error;
}
}
if(attr.user2DobMonth && attr.user2DobMonth != t.get('user2DobMonth')){
var val = jQuery.trim(attr.user2DobMonth.toLowerCase()),
id = 'user2DobMonth',
error = {
attr : id
};
if(val === 'select'){
return error;
}
}
}
I've been searching around and looking into the documentation, but I'm not sure how to activate filtering via the column drop down the way you have it in normal grid panels.
I've tried to implement the ux.Grid.FilterFeatures but when I apply it to the tree panel, my panel doesn't render properly (all blue panel). I thought it might have something to do with its deferred layout, but when I do a treegrid.hide()/treegrid.show()/treegrid.doLayout(), it doesn't make a difference.
Has anyone gotten the filter feature working with the treepanel? Or has anyone got any suggestions on how to rectify this problem?
I modified the filter example in the Ext-Js site to use it in tree grid.
Ext.define('TreeGridFilter', {
extend: 'Ext.grid.feature.Feature'
, alias: 'feature.treeGridFilter'
, collapseOnClear: true // collapse all nodes when clearing/resetting the filter
, allowParentFolders: false // allow nodes not designated as 'leaf' (and their child items) to be matched by the filter
, treeGrid: null
, filterPropertyNames: new Array()
, filterPropertyValues: new Array()
, filterColumnRenderers: new Array()
, init: function (tree) {
var me = this;
treeGrid = me.tree = tree;
var view = me.view;
var headerCt = view.headerCt;
// Listen for header menu being created
headerCt.on('menucreate', me.onMenuCreate, me);
tree.filter = Ext.Function.bind(me.filter, me);
tree.clearFilter = Ext.Function.bind(me.clearFilter, me);
}
,filter: function (value, property, re, columnRenderer) {
var me = this
, tree = me.tree
, matches = [] // array of nodes matching the search criteria
, root = tree.getRootNode() // root node of the tree
, property = property || 'text' // property is optional - will be set to the 'text' propert of the treeStore record by default
, visibleNodes = [] // array of nodes matching the search criteria + each parent non-leaf node up to root
, viewNode;
me.updateValueForName(property, value, columnRenderer);
if (me.filterPropertyNames.length == 0) { // if the search field is empty
me.clearFilter();
return;
}
tree.expandAll(); // expand all nodes for the the following iterative routines
//iterate over all nodes in the tree in order to evalute them against the search criteria
root.cascadeBy(function (node) {
var numberOfFiltersMatched = 0;
for (var index=0; index < me.filterPropertyNames.length; index++)
{
var propertyName = me.filterPropertyNames[index];
var propertyValue = me.filterPropertyValues[index]
var propertyValueOfNode = node.get(propertyName);
if(me.filterColumnRenderers[index] != false){
var renderingFunction = me.filterColumnRenderers[index];
propertyValueOfNode = renderingFunction(propertyValueOfNode); //Using the renderer function of the column
}
var regExpn = new RegExp(propertyValue, "ig") // the regExp could be modified to allow for case-sensitive, starts with, etc.
if(propertyValueOfNode != null && (propertyValueOfNode+'').match(regExpn)) {
numberOfFiltersMatched++;
}
}
if(numberOfFiltersMatched == me.filterPropertyNames.length){
matches.push(node); // add the node to the matches array
}
});
if (me.allowParentFolders === false) { // if me.allowParentFolders is false (default) then remove any non-leaf nodes from the regex match
Ext.each(matches, function (match) {
if (match == null || !match.isLeaf()) {
Ext.Array.remove(matches, match);
}
});
}
Ext.each(matches, function (item, i, arr) { // loop through all matching leaf nodes
root.cascadeBy(function (node) { // find each parent node containing the node from the matches array
if (node.contains(item) == true) {
visibleNodes.push(node); // if it's an ancestor of the evaluated node add it to the visibleNodes array
}
});
if (me.allowParentFolders === true && !item.isLeaf()) { // if me.allowParentFolders is true and the item is a non-leaf item
item.cascadeBy(function (node) { // iterate over its children and set them as visible
visibleNodes.push(node);
});
}
visibleNodes.push(item); // also add the evaluated node itself to the visibleNodes array
});
root.cascadeBy(function (node) { // finally loop to hide/show each node
viewNode = Ext.fly(tree.getView().getNode(node)); // get the dom element assocaited with each node
if (viewNode) { // the first one is undefined ? escape it with a conditional
viewNode.setVisibilityMode(Ext.Element.DISPLAY); // set the visibility mode of the dom node to display (vs offsets)
viewNode.setVisible(Ext.Array.contains(visibleNodes, node));
}
});
}
, clearFilter: function () {
var me = this
, tree = this.tree
, root = tree.getRootNode();
if (me.collapseOnClear) {
tree.collapseAll(); // collapse the tree nodes
}
root.cascadeBy(function (node) { // final loop to hide/show each node
viewNode = Ext.fly(tree.getView().getNode(node)); // get the dom element assocaited with each node
if (viewNode) { // the first one is undefined ? escape it with a conditional and show all nodes
viewNode.show();
}
});
},
onMenuCreate: function(headerCt, menu) {
var me = this;
menu.on('beforeshow', me.onMenuBeforeShow, me);
},
onMenuBeforeShow: function(menu) {
var me = this;
var currentHeaderFilter = menu.activeHeader.filter;
if(currentHeaderFilter == null){
if(me.menuItem == null){
return;
}
me.menuItem.hide();
me.menuSeparator.hide();
}else if(me.menuItem != null){
me.menuItem.show();
me.menuSeparator.show();
}
if(me.menuItem){
var perviousFilterValue = me.getValueForName(menu.activeHeader.dataIndex);
if(perviousFilterValue == null || perviousFilterValue == ''){
me.menuItem.setRawValue('');
}else{
me.menuItem.setRawValue(perviousFilterValue);
}
}else{
me.menuSeparator = menu.add('-');
var filterTextFiels = new Ext.form.TextField({
itemId: 'filterTextBox',
cls : 'find-icon',
listeners: {
'change': this.onFilterTextChange
}
});
me.menuItem = menu.add(filterTextFiels);
}
me.menuItem.activeDataIndex = menu.activeHeader.dataIndex;
me.menuItem.activeRenderer = menu.activeHeader.renderer;
me.menuItem.width = (currentHeaderFilter == null || currentHeaderFilter.width == null) ? 150 : currentHeaderFilter.width;
},
onFilterTextChange : function (searchMenuItem, value) {
treeGrid.filter(value,searchMenuItem.activeDataIndex, null, searchMenuItem.activeRenderer);
},
updateValueForName : function(property, value, columnRenderer){
var propertyIndex = -1;
for (var index=0; index < this.filterPropertyNames.length; index++)
{
if(property == this.filterPropertyNames[index]){
propertyIndex = index;
break;
}
}
if(propertyIndex >= 0){
if(value == null || value == ''){
this.filterPropertyNames.splice(propertyIndex, 1);
this.filterPropertyValues.splice(propertyIndex, 1);
this.filterColumnRenderers.splice(propertyIndex, 1);
}else{
this.filterPropertyValues[propertyIndex] = value;
}
}else{
propertyIndex = this.filterPropertyNames.length;
this.filterPropertyNames[propertyIndex] = property;
this.filterPropertyValues[propertyIndex] = value;
this.filterColumnRenderers[propertyIndex] = columnRenderer;
}
},
getValueForName : function(property){
var propertyIndex = -1;
for (var index=0; index < this.filterPropertyNames.length; index++)
{
if(property == this.filterPropertyNames[index]){
propertyIndex = index;
break;
}
}
if(propertyIndex >= 0){
return this.filterPropertyValues[propertyIndex];
}else{
return null;
}
}
});
This feature can be used in grid as
var treeGridFilter = {
ftype: "treeGridFilter"
};
var treeGrid= Ext.create('Ext.tree.Panel', {
id : 'tree-grid-id',
title: 'View Tree Grid',
features: [treeGridFilter],
store: store,
renderTo: 'grid-div',
columns: [{
text: 'Column One',
dataIndex: 'columnOne',
filter : {
width: 150
}
},{
text: 'Column Two',
dataIndex: 'columnTwo',
filter : {
width: 100
}
}]
});
Tree stores don't have a filtering function, but I had to create one for an earlier project. Here's an example.
So, as a suggestion you could create your own filter drop menus using Ext.grid.header.Container configs in the tree columns (as covered here in the docs) these could be set-up to call some kind of filter function on the treestore like the one I linked to above.