ExtJS - Grid filter with multiple columns and multiple values - extjs

I create a grid and a toolbar with Two Menu of menuCheckItem. When i check the menuCheckItem the grid filters even with multiple values and multiple columns.
This working fine, as I have created grid 1st and then the toolbar
this.up('') // Used Instead of Ext.getCmp()
Working FIDDLE - https://fiddle.sencha.com/#view/editor&fiddle/2lop
Now I am trying to create same toolbar along with Menu separately on top 1st and then create grid at below. But while doing this, nly Multiple values is working.
I am trying to filter grid with multiple values as well as multiple columns.
Few things i tried -
// Only Filters One Value at a time with each Columns
store.queryBy(function(record,id){
return (record.get('name') == someValue && record.get('phone') == otherValue);
});
and
// Filters Many Columns with Single Value
filter.add(
property : name, phone
value : "somevalue"
operator : "OR"
);
Is there any way to implement Toolbar 1st and then grid ? And Filter grid with many values and columns simultaneously ?

In this FIDDLE i remade a function(checkchange), which is universal , can be put separately and you'll be able to attach it to every menucheckitem you create. The only thing is that if you add new menucheckitem filter you should name the menucheckitem id with the name of the columnDataIndex-Menu and add this columnDataIndex in menuFilters and thats all.
checkchange: function (checkbox, checked, eOpts) {
var menuFilters = ['name', 'phone'];
var getChecked = function (m) {
var checkedItems = [];
m.items.items.forEach(function (c) {
if (c.checked) {
checkedItems.push(c.text);
}
});
return checkedItems;
};
//
var menus = new Map();
menuFilters.forEach(function (e) {
menus.set(e, Ext.getCmp(e + '-Menu'));
});
//
var fieldValues = [];
menuFilters.forEach(function (e) {
fieldValues.push([e, getChecked(menus.get(e))]);
});
//
var store = checkbox.up('grid').store;
store.clearFilter();
//
if (fieldValues.length > 0) {
store.filterBy(function (record) {
var fV = this.fieldValues;
for (var i = 0; i < fV.length; i++) {
if (fV[i][1].length > 0) {
if (fV[i][1].indexOf(record.get(fV[i][0])) === -1) {
return false;
}
}
}
return true;
}, {
fieldValues: fieldValues
});
}
}

Related

AngularJS server-side multi-column search

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!

Dynamic Context Menu using ui.bootstrap.contextMenu

I'm using 'ui.bootstrap.contextMenu' to build a context menu. When I right click a field, I need to pass in a unique ID, to the controller, and return a list of choices based on the ID. Each list could be different. If I use hardcoded list, it works fine, but I can't seem to generate the list dynamically. What am I missing? Example Code:
`<div>
<textarea ng-model="ctrl.status" context-menu="ctrl.menuOptions (ctrl.id)"></div>
</div>
vm.menuOptions = function(id) = {
var listArray = $scope.list; // array of possible list items based on ID
angular.forEach(listArray, function(value, key) {
if (id === value.id) {
return [
[value.id, function ($itemScope) {
return value.textName;
}],
]
}
}
}`
I'm assuming you're referring to this implementation: https://github.com/Templarian/ui.bootstrap.contextMenu
The function to generate the context menu is passed three things:
$cmScope - the scope of the context menu
event - the event object that triggered the opening of the context menu
modelValue - the value of the ng-model attached to the current object
So, in your example, you can use:
vm.menuOptions = function($cmScope, event, modelValue) = {
var listArray = $scope.list; // array of possible list items based on ID
// ^ Assuming that the $scope here is the scope of the container, not the contextmenu
angular.forEach(listArray, function(value, key) {
if (modelValue === value.id) {
return [
[value.id, function ($itemScope) {
return value.textName;
}],
]
}
}
}

Limit number of selected items in ng-grid

Is there a way to limit the number of selected items in ng-grid?
I did not find a setting to do that, so I am trying to prevent it using a watchCollection in gridOptions.selectedItems.
When the length of selectItems is greater than 5, I try to deselect the last selection but is not working as expected because the item row index seems to be different as rendered:
This is the watcher code:
$scope.$watchCollection('gridOptions.selectedItems', function (newVal, oldVal) {
if ($scope.gridOptions.selectedItems.length > 5) {
for (var i = 0; i < $scope.importableFilesData.length; i++) {
if ($scope.importableFilesData[i] === newVal[0]) {
$scope.gridOptions.selectRow(i, false);
}
}
}
Check your condition in this way. So, you don't have to search last selected row and you can deselect it directly. i've tested it and is working in this way.
$scope.yourGridApi.selection.on.rowSelectionChanged($scope,function(row){
if($scope.yourGrid.gridOptions.selectedItems.length>=5){
row.isSelected = false;
}
});
I have found a solution using beforeSelectionChange property. This property let you set callback that is triggered every time a row is selected. If your return false, the selection will be canceled.
$scope.gridOptions = {
// your grid options ...
beforeSelectionChange: function (row) {
if ($scope.gridOptions.selectedItems.length > 5) {
return false;
}
return true;
}
}

How to remove the summary grouping of particular row in EXTJS

If I apply groupField property in the grid and store, extjs will automatically sort records by groupField.
For example: http://dev.sencha.com/deploy/ext-4.0.0/examples/grid/group-summary-grid.html, it used 'project' as groupField.
My Question is How can I add an extra row, which does not belong to any group, into a grid with groupField. (Like this: http://i59.tinypic.com/30s973l.jpg)
By default group is made for each value from groupField, also for empty groupField (but it appears first). If it is enough for you then you don't have to do anything.
Fiddle: http://jsfiddle.net/tme462tj/
When you want to have behavior like on attached image, then some work has to be done.
To put empty value at the end, you can create convert function for model field (eg: convert: function(v) { return v ? v : defaultDepartment; }). If you want to hide header and summary for this empty group, you can attach event listener to viewready event and hide them using styles:
viewready: function() {
var els = this.getEl().query('.x-grid-group-hd');
els = Ext.Array.filter(els, function(e) { return e.textContent.trim() == defaultDepartment; });
if (els.length !== 1) {
return;
}
var header = Ext.fly(els[0]);
header.setStyle('display', 'none');
var summary = header.up('.x-group-hd-container').down('.x-grid-row-summary');
if (summary) {
summary.setStyle('display', 'none');
}
}
Fiddle: http://jsfiddle.net/hdyokgnx/1/
As per my comment on the accepted solution, this revision makes the grouping header hide permanently for the duration of that grid view.
This solves my problem of needing a grouping grid but with one group that does not have a group header row.
store.load
({
params :
{
arg1 : 1
,arg2 : '2'
}
,callback : function( records, operation, success )
{
var els = me.theGrid.getEl().query('.x-grid-group-hd');
els = Ext.Array.filter( els, function(e)
{
return e.textContent.trim().includes( "*** A SUBSTRING IN GROUPING FIELD ***" );
});
if (els.length !== 1)
{
return;
}
var header = Ext.fly(els[0]);
header.setStyle('display', 'none');
var summary = header.up('.x-group-hd-container').down('.x-grid-row-summary');
if (summary)
{
summary.setStyle('display', 'none');
}
}
});

Ext.ux.LiveSearchGridPanel hide non-matched rows

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

Resources