AngularJS smart-table search within multiple columns - angularjs

Smart-table has a built in functionality to search through all columns (st-search) or through one desired column (st-search="'firstName'). Is it possible to do a search within several columns (not all)?
Example: if I have table like this: name, nickname, address with such data:
John, JJ, Some address
Steve, John, also address
Jane, Jane, John-village
and do a search for 'John' only first two columns should be as result.
Is it possible?

I have a similar problem and I solved using the hints in this post.
Smart Table doc says:
The stSetFilter replaces the filter used when searching through Smart
Table. When the default behavior for stSearch does not meet your
demands, like in a select where one entry is a substring of another,
use a custom filter to achieve your goals.
and:
Note that st-safe-src is required for the select to properly display
all distinct elements in the collection. Should this be omitted, the
select would only contain values from elements visible in table, also
affected by paging.
You can declare your own filter inside table element in HTML:
<table ... st-set-filter="myCustomFilter" class="table table-striped">
...and your can customize your filter (in your app) through a predicate function. It could work in this way:
// filter method, creating `myCustomFilter` a globally
// available filter in our module
.filter('myCustomFilter', ['$filter', function($filter) {
// function that's invoked each time Angular runs $digest()
return function(input, predicate) {
searchValue = predicate['$'];
//console.log(searchValue);
var customPredicate = function(value, index, array) {
console.log(value);
// if filter has no value, return true for each element of the input array
if (typeof searchValue === 'undefined') {
return true;
}
var p0 = value['name'].toLowerCase().indexOf(searchValue.toLowerCase());
var p1 = value['nickname'].toLowerCase().indexOf(searchValue.toLowerCase());
if (p0 > -1 || p1 > -1) {
// return true to include the row in filtered resultset
return true;
} else {
// return false to exclude the row from filtered resultset
return false;
}
}
//console.log(customPredicate);
return $filter('filter')(input, customPredicate, false);
}
}])
I made this little plnkr to see the filter in action

Nope, a workaround is to create you own directive which require the table controller and call its api twice (as the search get added)
directive('stSearch', ['$timeout', function ($timeout) {
return {
require: '^stTable',
scope: {
predicate: '=?stSearch'
},
link: function (scope, element, attr, ctrl) {
var tableCtrl = ctrl;
// view -> table state
element.bind('input', function (evt) {
evt = evt.originalEvent || evt;
tableCtrl.search(evt.target.value, 'column1');
tableCtrl.search(evt.target.value, 'column2');
});
}
};
}]);
you'll find more details in the stSearch direcitve

You can solve the issue by creating the new search enabled table and combining the fields that you might be showing in one column.
example:if you have IdNo1 and IdNo2 as view fileds in One column, you can combine them to add the new element in the table array.
View :
table injection:
table st-table="displayedCollection" st-safe-src="rowSearchCollection"
Search injection:
input type="search" ng-model="idSearch" st-search="idSearch"
Controller:
$scope.rowSearchCollection = [];
vm.searchEnabledTable = function(tableDetails) {
//$scope.rowSearchCollection = angular.copy(tableDetails);
var deferred = $q.defer();
if(_.isArray(tableDetails)) {
_.each(tableDetails, function(element, index, list) {
$scope.rowSearchCollection[index] = angular.copy(element);
$scope.rowSearchCollection[index].idSearch = element.IdNo1+','+element.IdNo2;
});
deferred.resolve("DATA_PROCESSED");
} else {
deferred.reject("NOT_ARRAY");
}
return deferred.promise;
}

the problem with stSetFilter is that the new filter will be to all the
searchs (st-search) that you will use in the table.
Another idea:
If your data rowCollection is not too big,
in the javascript in the init() of the page do something like:
self.rowCollection.forEach(function (element) {
element.NameForFilter = element.name+ ' ' + element.nickName;
});
and then in the input of the th:
st-search=NameForFilter

try st-search="{{firstName + nickname}}". I tried with smart table v2.1.6 and seems working.

Related

Clicking an element inside repeater when another element equals specific input with protractor and jasmine

My HTML structure is this:
<li ng-repeat="m in members">
<div class="col-name">{{m.name}}</div>
<div class="col-trash">
<div class="trash-button"></div>
</div>
</li>
What I want to be able to do is using protractor, click on the trash when m.name equals a specific value.
I've tried things like:
element.all(by.repeater('m in members')).count().then(function (number) {
for (var i = 0 ; i < number ; i++) {
var result = element.all(by.repeater('m in members').row(i));
result.get(0).element(by.binding('m.name')).getAttribute('value').then(function (name) {
if (name == 'John') {
result.get(0).element(by.className('trash-button')).click();
};
});
};
});
This seems like it should work, however, it seems like my function does not even run this.
I've also looked into promises and filters though have not been successful with those either. Keep getting errors.
var array = element.all(by.repeater('m in members'));
array.filter(function (guy) {
guy.getText().then(function (text) {
return text == 'John';
})
}).then(function (selected) {
selected.element(by.className('trash-button')).click()
}
All I would like to do is click on the corresponding trash when looking for a specific member list!
Any help would be much appreciated.
EDIT: suggested I use xpath once I find the correct element, which is fine, the problem is I cannot get the filter function's promise to let me use element(by.xpath...)
If I try:
var array = element.all(by.repeater('m in members'));
array.filter(function (guy) {
guy.getText().then(function (text) {
return text == 'John';
})
}).then(function (selected) {
selected.element(by.xpath('following-sibling::div/div')).click()
}
I get the error:
Failed: Object has no method 'element'
Figured it out. Following the Protractor filter guideline in the API reference and using alecxe's xpath recommendation, this code will click on any element you find after filtering it.
element.all(by.className('col-name')).filter(function (member, index) {
return member.getText().then(function (text) {
return member === 'John';
});
}).then( function (elements) {
elements[0].element(by.xpath('following-sibling::div/div/')).click();
});
You can change the xpath to select different stuff incase your HTML does not look like mine.
It looks like you can avoid searching by repeater, and use by.binding directly. Once you found the value you are interested in, get the following sibling and click it:
element.all(by.binding('m.name')).then(function(elements) {
elements.filter(function(guy) {
guy.getText().then(function (text) {
return text == 'John';
})
}).then(function (m) {
m.element(by.xpath('following-sibling::div/div')).click();
});
});
Or, a pure xpath approach could be:
element(by.xpath('//li/div[#class="col-name" and .="John"]/following-sibling::div/div')).click();

Always display a Key First with Angular

I have an ng-repeat like:
<div ng-repeat="(k, v) in query_p[$index].tags">
I would like it so that if the key (k) is a certain string, say "foo", that string always appears first in the list. It seems the getter option or orderBy only works with arrays. Anyone have an example of how they might achieve this?
Basically you have an unordered object, and you want it to have some kind of order.
To do that you need to create a function that returns some ordered object.
myApp.filter('promote_foo', function() {
return function(object, comp) {
console.log(object);
console.log(comp);
var ordered = [];
for (var key in object) {
var obj = {
key: key,
value: object[key]
};
if (key === comp)
ordered.splice(0,0,obj);
else
ordered.push(obj);
}
console.log(ordered);
return ordered;
};
});
the function takes a parameter which will promote and object if the key matches it. Now I only call it in the controller directly, but you could use it just like any angular filter.
$scope.order = $filter('promote_foo')($scope.data, 'foo');
Also, you can play with the fiddle here.
Hope this helped!

Can I use filter in ng-repeat to filter multiple items for a specific field

I'm working on a filtering solution for a grid. The filter contains multiple checkboxes in a panel. I currently have a working solution that uses the filter built into the ng-repeat directive. However, I haven't found a way to make it filter a single field with multiple parameters. This is what I have:
HTML:
<tr data-ng-repeat="rule in rules | orderBy:sortOrder | filter:filterOrder">
Contoller:
var defaultFilter = {critical:'', score:'', category:'', pass:'false'};
$scope.filterOrder = defaultFilter;
What I would like is to be able to do something like this:
var defaultFilter = {critical:'', score:'', category:'', pass:'false && notRun'};
I'm not sure if this is possible. I've looked for the syntax on it, but I have yet to find it. I know there are ways to put it in the controller, but due to our current project this implementation would be considerably easier.
Assuming you are using the 1.0.x branch, the relevant part of the source code for the filtering on objects is shown in the footnote below. This shows that the standard filter finds the key to test, and then gets var text = (''+expression[key]).toLowerCase(); from your search criteria before calling search. search then does a text search using (indexOf(text) > -1.
Therefore you can't set one of the items in your search criteria to an expression that requires evaluation in the way that you are trying.
Your only other options are to:
use a predicate function as the parameter for the filter (this just needs to return true/false); or
to write your own filter (this just needs to return a new array from the original one).
Predicate function
$scope.myFilterTest(item) { return ... true or false test result ...; }
and:
<tr data-ng-repeat="rule in rules | orderBy:sortOrder | filter:myFilterTest">
custom filter
var someApp=angular.module('myApp', []);
someApp.filter('complexFilter', function() {
return function(input, criteria) {
var result = [];
for (var i = 0; i < input.length; i++) {
if(... multiple tests ...){
result.push(input[i]);
}
}
return result;
}
});
And then:
<tr data-ng-repeat="rule in rules | orderBy:sortOrder | complexFilter:filterOrder">
Footnote:
relevant part of src/ng/filter/filter.js
case "object":
for (var key in expression) {
if (key == '$') {
(function() {
var text = (''+expression[key]).toLowerCase();
if (!text) return;
predicates.push(function(value) {
return search(value, text);
});
})();
} else {
(function() {
var path = key;
var text = (''+expression[key]).toLowerCase();
if (!text) return;
predicates.push(function(value) {
return search(getter(value, path), text);
});
})();
}
}
break;
yes you can achieve this using custom filter, here is the answer for you question.
And fiddle link here
filterMultiple

Adding a computed field to every element of an array in an AngularJS model

I'm pulling an array of users into my AngularJS model from a JSON datasource. This data is being rendered in a table, and I'd like to create a column that is computed from two values of the existing user object, without modifying my underlying data service.
// My model
function UserListCtrl($scope,$http) {
$http.get('users').success(function(data) {
$scope.users = data;
});
};
In my partial template, I know I can do something like this:
<tr ng-repeat="for user in users">
<td>{{user.data / user.count | number:2}}</td>
</td>
But I'd rather add that field into the model, so I can use it like so:
<td>{{user.amplification}}</td>
How do I add the "amplification" field to every user in my model?
As an aside, is it possible to use the orderBy filter on something like this:
<td>{{user.data / user.count | number:2}}</td>
You can eather:
Just after loading user do:
$http.get('users').success(function(data) {
$scope.users = data;
$scope.user.amplification() = function() { return $scope.user.data / $scope.user.count; }
});
And use as {{user.amplification()}}
Anywhere at controller:
$scope.$watch('user', function() {
$scope.userAmplification = $scope.user.data / $scope.user.count;
}, true);
$http.get
Or if user.data/count do not change, do same as 1. but staticly calculate:
$http.get('users').success(function(data) {
$scope.users = data;
$scope.user.amplification = $scope.user.data / $scope.user.count;
});
And OrderBy could be used on any expression (uncluding result of other filter)
If you don't need your amplicification() function to update when the data and count properties on your user update, you can do something like this in your controller:
$scope.users.forEach(function(user) {
user.amplification = function() {
return user.data / user.count;
};
});
Adding a second answer as I feel it's appropriate as it's distinct from my first one.
After a little looking around, I found the method I originally posted falls over if you try to add new rows dynamically, or new elements to the array which depend on the computed value. This is because the $scope.array.forEach() will only run when the controller is created.
The best way to solve this problem is to create a properly defined object which contains the options you want. e.g.
function Task(id, name, prop1, prop2) {
this.id = id;
this.name = name;
this.prop1 = prop1;
this.prop2 = prop2;
this.computedProperty = function () {
return this.prop1 + this.prop2;
};
}
This is far more flexible as each new object created will have the new property.
The only downside is that in your ajax success callback, you'll need to pass each of your users into your 'Users()' constructor.
What worked for me was to add a loop and add the property to each item in that loop. I used a property of the controller but I am sure you can use scope the way you are approaching it in the question.
function(result) {
self.list = result;
angular.forEach(self.list, function(item) {
item.hasDate = function() {
return this.TestDate != null;
}.bind(item); // set this context
});
}
Then in my markup I just used it like this.
<div ng-repeat...>
<div ng-show="item.hasDate()">This item has a date.</div>
</div>

Filter result from Store

I am loading values from a Store, and then filtering it with a condition (for example name == Matt). The code is shown below;
var store = Ext.getStore('mystore');
store.on('load', function() {
store.filter({
filterFn: function(rec) {
return Ext.Array.indexOf(arr, rec.get('name')) > -1;
}
});
});
store.load();
Later on, in another view, i need to use the filtered result (shown above) and filter it against another array of values. How can i do this;
My approach was to load the records again (Paste the above code again - but without the filter part of it). But this is incorrect. So how can i Filter results from the previously filtered result array ?
You can define filters to run on store load in its filters property and setting filterOnLoad property to true.
To filter records against new filter conditions:
store.clearFilter(true); // Clears old filters
store.filter([
{
filterFn: function(rec) {
return Ext.Array.indexOf(arr, rec.get('name')) > -1;
}
}
]);

Resources