I need to create a filter in angularjs
My data looks something like this:
[{
name: 'account1',
accounts: [
{
name: 'account2',
accounts: []
},
{
name: 'account3',
accounts: [
{
name: 'account4',
accounts: []
}
]
}
]
}]
I need the filter return the full object if I use account4 for the search text. Or just the first 2 levels if I use account2 etc.
I have searched all over but cant find anything like this and have no idea where to start.
You'll need to create a custom filter to do what you're requesting. A controller filter will only allow you to provide an expression to include or exclude an ng-repeat item.
A custom filter will allow you to modify the model dynamically. You can splice the json object accordingly.
I'll provide an example when I'm back in in front of a pc.
I got it figured out finally. Here is the custom filter I created in case anyone else finds it useful:
.filter('accountsFilter', function() {
return function(items, searchStr) {
function filterAccounts(account, str) {
//if account name matches
if (account.name && account.name.toLowerCase().indexOf(str.toLowerCase()) > -1) {
//expand parent account
if (account.accounts && account.accounts.length > 0) account.expand = true;
//return account
return account;
} else
//account name doesnt match. check sub accounts
if (account.accounts && account.accounts.length > 0) {
//has accounts
var fa = [];
angular.forEach(account.accounts, function(act, k) {
var a = filterAccounts(act, str);
//if account was returned
if (a !== false) {
//add account to filtered accounts
fa.push(act);
}
});
//add accounts to parent account
account.accounts = fa;
//if there are sub-accounts
if (fa.length > 0) {
//make sure account is expanded to show sub accounts
if (account.accounts && account.accounts.length > 0) account.expand = true;
//return account
return account;
//no sub accounts left
} else {
//check and return if main account matches
return filterAccounts(account, str);
}
//no matches
} else {
return false;
}
}
//copy accounts list to prevent original being altered
var accounts = angular.copy(items);
var filtered = [];
//loop through accounts list
angular.forEach(accounts, function(account) {
//check if current account matches
var a = filterAccounts(account, searchStr.name);
if (a) {
//add to filtered list
filtered.push(a);
}
});
return filtered;
};
})
Related
I am building an application in NodeJS and AngularJS.
I am building a multi-column search functionality where the user can type in search keywords into separate searchboxes (at the top of each column) and retrieve the results based on the column.
So far I have a single searchbox that searches all attributes at the same time.
How can I implement multiple individual searchboxes that will return results based on multiple attributes?
Note: I want to implement this on the server-side for performance reasons. (I know that I can simply use HTML attributes | filter:column1 | filter:column2 but want to avoid this technique if possible).
Here is the code I have so far. I am thinking that I need to pass in some sort of "searchBy" variable that is set on the view and then update the search method to search by multiple query/attribute pairs.
//Search service factory
//Initialize filtered items and get search results
function search(items, query) {
this.filteredItems = $filter('filter')(items, function (item) {
for(var attr in item) {
if (searchMatch(item[attr], query))
return true;
}
return false;
});
return this.filteredItems;
}
function searchMatch(haystack, needle) {
if (!needle) {
return true;
}
return haystack.toString().toLowerCase().indexOf(needle.toLowerCase()) !== -1;
};
//Controller
vm.filteredItems = vm.search(vm.unfilteredItems, vm.query);
//View
input(type='text', ng-model='vm.query', ng-change='vm.search(vm.unfilteredItems, vm.query)', placeholder='Search')
I was able to solve this by first creating an array of objects for each search box then repeating those boxes in the view with the ng-repeat attribute.
//Controller
var vm = this;
var vm.unfilteredItems; //data source query removed for brevity
//Initialize search inputs
vm.search_by_inputs = [
{search_column: 'id', search_query: ''},
{search_column: 'requester', search_query: ''},
{search_column: 'dataowner', search_query: ''}
];
function initSearch() {
vm.filtered_items = vm.search(vm.unfiltered_items, vm.search_by_inputs);
}
//View
input.input-large.search-query(type='text', value='{{search_by.search_query}}', ng-model='search_by.search_query' ng-change='vm.initSearch()', placeholder='Search')
The next step is to loop over the search_by_inputs object in the controller and create a new object with only the inputs that have search values entered into the searchboxes in the view. Then in the search method the built-in "filter" component iterates each item, and inside that loop each of the search terms is checked against that value with the column name that matches the property.
/*
* Create new array of objects with only elements that have search values to optimize loop inside filter
* #search_by_inputs array of objects each has a key search_column and a value search_query
*/
function optimizeSearchProperties(search_by_inputs) {
search_by_properties = [];
for (var i = 0, len = search_by_inputs.length; i < len; i++) {
//If this column input box has query text
if (search_by_inputs[i].search_query) {
search_by_properties.push(search_by_inputs[i]);
}
}
return search_by_properties;
}
/*
* #haystack search item
* #needle search term
*/
function searchMatch(haystack, needle) {
if (!needle) {
return true;
}
return haystack.toString().toLowerCase().indexOf(needle.toLowerCase()) !== -1;
}
/*
* Create filtered items object by filtering search results
* #items original array of objects returned by database query result
* #search_by_inputs array of objects each has a key search_column and a value search_query
*/
function search(items, search_by_inputs) {
var search_by_properties = optimizeSearchProperties(search_by_inputs);
//If there are no search properties input by requester then return all items
if (search_by_properties.length === 0) {
this.filtered_items = items;
return this.filtered_items;
}
this.filtered_items = $filter('filter')(items, function (item) {
var search_result = true;
//Loop over all search by input textboxes
for (var n = 0, len = search_by_properties.length; n < len; n++) {
//If there is no query text
if (!search_by_properties[n].search_query) {
//Continue to next element in array
continue;
//Else if element has a property that matches search input column name
} else if (item[search_by_properties[n].search_column]) {
if (!searchMatch(item[search_by_properties[n].search_column], search_by_properties[n].search_query)) {
search_result = false;
break;
}
}
}
return search_result;
});
return this.filtered_items;
}
I would be glad to have some feedback on this solution in terms of optimization, performance, technique, etc. Thanks!
I have a store configured with memory proxy with enablePaging: true. Store's remoteFilter and remoteSort set to true so filtering and sorting requests handled by proxy.
When I filter my store by multiple fields I want to use OR condition, not AND.
Without memory proxy paging I could use remoteFilter: false and custom filter function like:
store.filterBy(function (record)
{
for (var i = 0; i < searchFields.length; ++i) {
if (record.get(searchFields[i]).toLowerCase().indexOf(searchText) !== -1) {
return true;
}
}
return false;
});
But how I can achieve this with enabled paging? Override memory proxy?
Ext.data.proxy.Memory.read use Ext.util.Filter.createFilterFn to create a filter function based on passed Ext.util.Filter[] (with property-value configs):
// Filter the resulting array of records
if (filters && filters.length) {
// Total will be updated by setting records
resultSet.setRecords(records = Ext.Array.filter(records, Ext.util.Filter.createFilterFn(filters)));
resultSet.setTotal(records.length);
}
So, if you want to use different filter logic, you can override memroy proxy read method and use custom function to create filter function:
Ext.define('MyApp.extensions.FilterMemoryProxy', {
extend: 'Ext.data.proxy.Memory',
alias: 'proxy.filtermemory',
createCustomFilterFn: function (filters)
{
if (!filters) {
return Ext.returnTrue;
}
return function (candidate)
{
var items = filters.isCollection ? filters.items : filters,
length = items.length,
i, filter;
for (i = 0; i < length; i++) {
filter = items[i];
if (!filter.getDisabled() && candidate.get(filter.getProperty()).toLowerCase().indexOf(
filter.getValue().toLowerCase()) !== -1) {
return true;
}
}
return false;
};
},
read: function (operation)
{
...
if (operation.process(resultSet, null, null, false) !== false) {
// Filter the resulting array of records
if (filters && filters.length) {
// Total will be updated by setting records
resultSet.setRecords(records = Ext.Array.filter(records, me.createCustomFilterFn(filters)));
resultSet.setTotal(records.length);
}
...
}
}
});
You can override the memory proxy to suit your needs, or, if you only wish to find a string in one of the fields specified in searchFields, you can add a 'virtual' field to the model with the convert method specified.
For example:
// Extend Ext.data.Model or create it. Dealer's choice.
Ext.define('MyLocalModel', {
extend: 'Ext.data.model',
fields: [
...
{
name: 'searchField',
type: 'string',
convert: function(value, record) {
// Get search fields somewhere
var resultArray = [];
for (var i = 0; i < searchFields.length; ++i) {
var searchField = searchFields[i];
var fieldValue = record.get(searchField);
resultArray.push(fieldValue);
}
// Join by a string that would not normally be searched
// so that field values 'foo' and 'bar' are not valid when searching 'ooba'
var result = resultArray.join('**');
return result;
}
}
]
});
All you need to do in the store then is:
store.filter('searchField', searchText);
It's not exactly what you are searching for, but it should get the job done.
Please see bellow code, that I use for filtering data.
listeners: {
keyup: function (e, t, eOpts) {
var text = e.getValue();
var s = Ext.getStore('TempSensorDetailsStore');
s.clearFilter();
if (text) {
s.filterBy(function (rec) {
var str = (rec.get('vehicleNo')).toLowerCase();
var res = str.indexOf(text.toLowerCase());
if (res == 0) {
return true;
}
});
} else {
s.clearFilter();
}
}
}
Above code get filter the data, But not as per my expectations,
Search Result showing record which is matching the first letter of vehicle Number only...it should return the vehicle No if that character is present in Vehicle no
For Example.
Vehicle No.Abc-37046 and if user search 37046 then also it returns vehicle
I got Solution,
There was just need to change code
if (res >- 1)
Instead of
if (res == 0)
I'm using a Ext.ux.LiveSearchGridPanel and i want to hide all rows that have no match.
When the search field is empty, I want all elements to be displayed.
I'm using a Ext.data.Store that contains my data.
I tried to find a config for this, but had no luck.
You can handle search field and filter store by this value. Something like this:
Ext.create('Ext.ux.LiveSearchGridPanel', {
...
listeners: {
afterrender: function() {
var me = this,
store = me.getStore();
me.textField.on('change', function(cmp) {
var searchValue = cmp.getValue();
store.clearFilter(true);
if (!searchValue) {
return;
}
store.filter(function(record) {
// you can filter store by some column
var companyName = record.get('company');
if (!me.caseSensitive) {
companyName = companyName.toLowerCase();
searchValue = searchValue.toLowerCase();
}
if (me.regExpMode) {
var match = companyName.match(searchValue);
return match ? this.indexOf(match[0]) : -1;
}
return companyName.indexOf(searchValue) != -1;
});
})
}
}
});
Look my fiddle example
I am new to programming and am trying to learn angularJS to build a web app. I came across an example contacts manager app and am having difficulty understanding part of it, namely the part that checks if a contact exists or not:
if($scope.newcontact.id == null)
Ok, I get that this checks if this is a new contact, but I don't understand what is happening in logical terms. I read up on null and understand it to mean that the thing being evaluated is known to exist, but does not have value.
So does this mean 'newcontact.id exists but does not have a value'?
Below is the javascript part of the app:
var uid = 1;
function ContactController($scope) {
$scope.contacts = [
{ id:0, 'name': 'Viral',
'email':'hello#gmail.com',
'phone': '123-2343-44'
}
];
$scope.saveContact = function() {
if($scope.newcontact.id == null) {
//if this is new contact, add it in contacts array
$scope.newcontact.id = uid++;
$scope.contacts.push($scope.newcontact);
} else {
//for existing contact, find this contact using id
//and update it.
for(i in $scope.contacts) {
if($scope.contacts[i].id == $scope.newcontact.id) {
$scope.contacts[i] = $scope.newcontact;
}
}
}
//clear the add contact form
$scope.newcontact = {};
}
$scope.delete = function(id) {
//search contact with given id and delete it
for(i in $scope.contacts) {
if($scope.contacts[i].id == id) {
$scope.contacts.splice(i,1);
$scope.newcontact = {};
}
}
}
$scope.edit = function(id) {
//search contact with given id and update it
for(i in $scope.contacts) {
if($scope.contacts[i].id == id) {
//we use angular.copy() method to create
//copy of original object
$scope.newcontact = angular.copy($scope.contacts[i]);
}
}
}
}
if ($scope.newcontact.id == null)
if $scope exists AND $scope has a property called newcontact which also exists AND newcontact has a property called id AND id has not been assigned a value.
By that statement the author assumes (or has previously checked) that $scope exists and is not null and that $scope.newcontact exists and is not null. The explicit check is that id is a property on newcontact that has not been assigned a value.