I'm using the new Angular UI Grid (that is planned to replace ng-grid).
My data needs some formatting before it's displayed in the table. For instance, my server returns an attribute named "status" as a number, but I want to display it as a nice name.
If status=1 display "Todo", if status=2 display "Doing" etc.
How can this be done in UI Grid?
The preferred method now is to use a cellFilter, rather than a custom template. Custom templates are OK, but they impose more workload on upgrade - you have to check whether new features require modifications to your template.
There is a reasonable example of filters in the tutorials: http://ui-grid.info/docs/#/tutorial/201_editable
Note the cellFilter: 'mapGender' on the gender column, and the filter itself defined further below in the tutorial:
.filter('mapGender', function() {
var genderHash = {
1: 'male',
2: 'female'
};
return function(input) {
if (!input){
return '';
} else {
return genderHash[input];
}
};
})
First step, add a cellTemplate to the column:
$scope.gridOptions.columnDefs = [
{field:'status', displayName: 'Status',cellTemplate: 'statusTemplate.html'}
];
The Template-File should look like this (COL_FIELD is the actual field):
<div style="text-align: center">{{COL_FIELD==1 ? 'Todo' : 'Doing'"}}</div>
Hope, you got the idea! :)
The shortest way is use CellTemplate with appScopeProvider:
vm.gridOptions = {
columnDefs: [
{
field: 'status',
cellTemplate: '<div>{{grid.appScope.formatStatus(row)}</div>'
}
],
appScopeProvider: {
formatStatus: function (row) {
return row.entity.status === 1 ? 'Todo' : 'Doing';
},
}
}
Related
I have a basic grid with a couple of columns that are ranges, i.e. 10 - 50, 0 - 9, etc. and I've written a custom filter on one of the columnDefs;
filter: {
condition: function(searchTerm, cellValue) { ... }
}
The filter works perfectly, but I'd like to strip it out and re-use it, only I can't figure out how.
I've tried defining it in the controller as function rangeFilter(...) and vm.rangeFilter = rangeFilter and then assigning it to the condition as grid.appScope.filterRange(searchTerm, cellValue) but that doesn't work.
I'm not really sure how else I'd do it, I haven't been able to find anything in the documentation or by googling for it.
Here's a plunkr of it in action; http://next.plnkr.co/edit/mbtXzfWqBg8FIALu
As you did, move the function out of the column definitions.
function rangeFilter() {
...
}
And in the column definitions pass a reference to the function in both.
vm.gridOptions = {
...
columnDefs: [
// default
{ field: 'name' },
{ field: 'range', cellFilter: 'range', filter: {condition: rangefilter}},
// I want to reuse the same filter as 'range' for this column somehow...
{ field: 'anotherRange', cellFilter: 'range', filter: {condition: rangefilter}}
],
...
};
Plunker
I need to display only a Date Collection object as a data source in my UI Grid.
Do I need to define a field under ColumnDefs in this case? Also, I need to include a column to delete that particular row, in this case Delete the current Date object.
How can I accomplish this? Below is my code
editor.mySeasonBreaks = {
data: "editor.mySeasons",
columnDefs:
[{ field: "????", visible: true, displayName: "Season Break" },
{
name: 'delete',
displayName: "",
cellTemplate: "<button ng-click="editor.delete(row.entity)" />"
}]
};
In the above code, editor.mySeasons is just a date array object.
Thanks in advance
You could create an object-array with your dates and define the columns as needed. This way you got more control and its easy to adjust/expand.
Now for your row-deletion I created a Plunkr thats showcases a possible solution.
As you suggest you need to add a cellTemplate that references your delete-function
cellTemplate: "<button ng-click=\"grid.appScope.delete(row)\">DELETE ROW</button>"
To access that function, you need to add it to your gridDefinition and ther property is called appScopeProvider
Possible setup would be
appScopeProvider: {
delete : function(row) {
editor.mySeasons.forEach(function(entry, index){
if(entry.myDate === row.entity.myDate) {
editor.mySeasons.splice(index, 1);
}
});
},
}
This is not recommended scenario, but you can use something like this:
$scope.myData=['2015-22-07', '2017-10-08', '2020-17-02'];
$scope.gridOptions = {
data: 'myData',
columnDefs: [
{
displayName: 'Date Array',
cellTemplate: '<div class="ngCellText ng-class="col.colIndex()">{{row.entity}}</div>'
}
]
};
You can test it here.
There are issues with sorting and probably something else.
It's better IMO to translate your array of dates to array of objects:
var res = [];
myData.forEach(function(el){
res.push({date: el});
});
Then specify column as usual:
{ field: 'date', visible: true, displayName: 'Season Break' }
I am using the angular-ui-grid to make an editable grid and have looked over the tutorial here:
http://ui-grid.info/docs/#/tutorial/201_editable
Unlike the example where the options are simple structures like gender male/female, I am trying to bind a complex JSON object in my model. All of the examples I have found are binding to a simple String or Integer object.
Here is an example on Plunker (this code was first taken from the tutorial and then slightly modified to show the problem I am facing):
The core of it is in the columnDef config for the data grid.
{
name: 'product',
field: 'product.name',
enableCellEdit: true,
editType: 'dropdown',
editDropdownOptionsArray: $scope.products,
editableCellTemplate: 'ui-grid/dropdownEditor',
editDropdownIdLabel: 'name',
editDropdownValueLabel: 'name'
}
I have tried changing the values for field and editDropdownIdLabel to a variety of options to no avail. If you look at the 3 console.log lines 51-53 in the sample code you will see that as you change the product selected the name changes but the ID stays the same. I want to be able to pass the whole product object to the backend when ultimately saving changes made in this grid.
I had a quite similar issue and I found a solution which worked for me:
https://github.com/angular-ui/ng-grid/issues/2808
mltroutt's comment on Feb 20 - a generic filter.
based on that, in your pluncker I tried that in the following way:
your product column should be look like this:
name: 'product',
field: 'product.id',
cellFilter: "griddropdown:editDropdownOptionsArray:editDropdownIdLabel:editDropdownValueLabel:row.entity.product.name",
editableCellTemplate: 'ui-grid/dropdownEditor',
editDropdownIdLabel: 'id',
editDropdownValueLabel: 'name',
editDropdownOptionsArray: $scope.products
then after your controller, you have to insert a filter like this:
.filter('griddropdown', function () {
return function (input, map, idField, valueField, initial) {
if (typeof map !== "undefined") {
for (var i = 0; i < map.length; i++) {
if (map[i][idField] == input) {
return map[i][valueField];
}
}
} else if (initial) {
return initial;
}
return input;
};
})
This can be solved by using the same product objects in your data array and the editDropdownOptionsArray array and getting the whole product structure placed into your data structure when a new one is selected. First, in the columnDef change the field to just product instead of product.name, change the editDropdownIdLabel to ref (explained in the next step), and add a cellTemplate as shown.
field: 'product',
editDropdownIdLabel: 'ref',
cellTemplate: '<div>{{row.entity.product.name}}</div>'
Second, create a self-referencing field ref inside each product so that you can return that instead of just id when a selection is made. Also, replace the product inside your data with a reference to the actual product object.
let prods = {};
angular.forEach($scope.products, (product) => {
prods[product.id] = product; // hash for products by id
product.ref = product; // self reference
});
angular.forEach(data, (person) => {
person.product = prods[person.product.id];
});
Now, when an item is selected everything is kept in sync. Also, you are (arguably) actually using the grid and select tools as intended and not creating filters, watchers, etc., etc. to try to fix things up.
Now, if ui-grid had an option (like maybe the default) where the object in the options array would be returned instead of requiring a field of the object to be returned this would be a lot easier and would not create circular data structures. Maybe editDropdownIdLabel: '' or 'self' or null? Or, maybe somebody knows of a better way to get the reference.
Plunker here: http://plnkr.co/edit/wjtuwgvZYIxWpkenJS7a?p=preview and a simpler version based on the gender field example from #bagavathi - http://plnkr.co/edit/JJduek?p=preview
This solution does assume that your product data really is the same and can be shared but is probably a typical use case.
Try this
$scope.gridOptions = {
enableSorting: false,
enableCellEditOnFocus: false,
data: data,
columnDefs: [
{
name: 'product',
field: 'productName',
enableCellEdit: true,
editType: 'dropdown',
editDropdownOptionsArray: $scope.products,
editableCellTemplate: 'ui-grid/dropdownEditor',
editDropdownIdLabel: 'name',
editDropdownValueLabel: 'name'
}
],
onRegisterApi: function (gridApi) {
//set gridApi on scope
$scope.gridApi = gridApi;
gridApi.edit.on.afterCellEdit($scope, function (rowEntity, colDef, newValue, oldValue) {
if (colDef.name === 'product') {
for (var i = 0; i < $scope.products.length; i++) {
var prod = $scope.products[i];
if (prod.name === newValue) {
rowEntity.product = prod;
break;
}
}
calculateBOption(rowEntity);
}
});
}
}
I have a working sort-able grid using the ext 3.4 grid filter plugin. I would like to default the active column to filter true values. User who needs the inactive records could remove the filter. How do I specify a default filter column and value?
Thanks in advance!
colModel: new Ext.grid.ColumnModel({
defaults: {
sortable: true
// How do I specify a default filter value
//
// Only show active records unless the user changes the filter...
},
columns: [{
dataIndex:'f_uid',
id:'f_uid',
header:'ID',
hidden:true
}, {
dataIndex:'f_name',
id:'f_name',
header:'Name',
}, {
xtype:'booleancolumn',
dataIndex:'f_active',
id:'f_active',
header:'Active',
filterable:true,
trueText:'Active',
falseText:'Inactive'
}]
I realise this is an old question but it took me a while to find a solution, therefore I thought I would share.
1) The filter can be set using the value property in the filter.
filter: {
type: 'LIST',
value: ['VALUE TO FILTER']
}
2) In order to initially filter the data use the filterBy() method in the store. This could be defined in the onRender event handler.
this.getStore().load({
scope:this,
callback: function() {
// filter the store
this.getStore().filterBy(function(record, id) {
// true will display the record, false will not
return record.data.DATA_TO_FILTER == 'VALUE TO FILTER ';
});
}
});
The answer was in the Filter.js source code. The filter object within the column definition can be used to configure the default behavior.
}, {
xtype:'booleancolumn',
dataIndex:'f_active',
id:'f_active',
header:'Active',
trueText:'Active',
falseText:'Inactive',
filterable:true,
filter: {
value:1, // 0 is false, 1 is true
active:true // turn on the filter
}
}
I have encountered the same problem and I found that #John's answer is right, I can make it work with the sample http://dev.sencha.com/deploy/ext-4.0.0/examples/grid-filtering/grid-filter-local.html, for the grid-filter-local.js, just add the code like:
grid.getStore().load({
scope:this,
callback: function() {
// filter the store
grid.getStore().filterBy(function(record, id) {
// true will display the record, false will not
return record.data.size === 'small';
});
}
});
before the original code store.load(), and wipe off the store.load().
Then it will only show the record with size equals 'small' at the first load of the web page. Cheers!
I've made a universal helper class that allows you to set any default values in column definition.
https://gist.github.com/Eccenux/ea7332159d5c54823ad7
This should work with both remote and static stores. Note that this also works with filterbar plugin.
So your column item is something like:
{
header: 'Filename',
dataIndex: 'fileName',
filter: {
type: 'string',
// filename that starts with current year
value: Ext.Date.format(new Date(), 'Y'),
active:true
}
},
And then in your window component you just add something like:
initComponent: function() {
this.callParent();
// apply default filters from grid to store
var grid = this.down('grid');
var defaultFilters = Ext.create('Ext.ux.grid.DefaultFilters');
defaultFilters.apply(grid);
},
Using Ext.ux.grid.FiltersFeature, I have remote filters and I am trying to write a function to apply a date filter on a grid column programmatically (rather than clicking on the filter drop down menu in the column header). The first time I run the function the grid store gets reloaded without the filter. When I run the function a second time (and every time thereafter) it works totally fine, the store reloads with the filters. Here is the gist of the function I have:
// a filter object for testing
aFilter = {type: 'date', field: 'a_date_field', comparison: 'gt', value: '2012-03-08 00:00:00'}
var grid = Ext.create('Ext.grid.Panel', {
store: store,
features: [{
ftype: 'filters',
}],
columns[{
header: 'ID',
dataIndex: 'id',
itemId: 'id',
width: 40,
}, {
xtype: 'datecolumn',
header: 'Date',
dataIndex: 'a_date_field',
itemId: 'a_date_field',
width: 75,
format:'j-M-Y',
filterable: true
}],
listeners: {
'afterrender': function() {
// Need to create the filters as soon as the grid renders
// rather than waiting for the user to click on the header
grid.filters.createFilters();
}
},
bbar: [{
text: 'Do a filter',
handler: function() {
// get the filter that is attached to the grid
var gridFilter = grid.filters.getFilter(aFilter.field);
// have to do this to create a menu for this filter
gridFilter.init({dataIndex: aFilter.field, type: aFilter.type, active: true});
// if this column is a date filter column
if (gridFilter.type == 'date') {
var dateValue = Ext.Date.parse(aFilter.value, 'Y-m-d H:i:s');
if (filter.comparison == 'gt') {
gridFilter.setValue({after: dateValue});
} else {
gridFilter.setValue({before: dateValue});
}
}
}
}
});
I also found that this function works the first time if I click on any grid header menu before I run the function.
I've been trying to find out what changes are made to the grid which make the filter work after the first attempt fails or what clicking on any grid header does to make it work. But nothing I add seems to fix it so it will run the first time. Has anyone implemented this successfully?
I have workaround:
bbar: [{
text: 'Do a filter',
handler: function() {
var grid = this.up('grid');
var dateValue = Ext.Date.parse(aFilter.value, 'Y-m-d H:i:s');
var value = aFilter.comparison == 'gt' ? {after: dateValue} : {before: dateValue};
var gridFilter = grid.filters.getFilter(aFilter.field);
if (!gridFilter) {
gridFilter = grid.filters.addFilter({
active: true,
type: aFilter.type,
dataIndex: aFilter.dataIndex,
});
gridFilter.menu.show();
gridFilter.setValue(value);
gridFilter.menu.hide();
} else {
gridFilter.setActive(true);
}
Ext.Function.defer(function(){
gridFilter = grid.filters.getFilter(aFilter.field);
gridFilter.setValue(value);
}, 10);
}
}]
As you can see I actually apply filter 2 times.
As an update, I expanded this function and modified it to work with ExtJS 4.1.1
Here is an example of the function to set grid filters dynamically (without the user needing to click on the menu items). Afterwards, the filtered items will be visible to the user in the grid column header menus as if he clicked on them and set them manually.
The "grid" argument is a grid with FiltersFeature that you want to filter. The other argument is an array of "filter" objects (I'll show an example below), the function simply applies all the passed "filter" objects to the grid.
doGridFilter: function(grid, filters) {
// for each filter object in the array
Ext.each(filters, function(filter) {
var gridFilter = grid.filters.getFilter(filter.field);
gridFilter.setActive(true);
switch(filter.data.type) {
case 'date':
var dateValue = Ext.Date.parse(filter.data.value, 'm/d/Y'),
value;
switch (filter.data.comparison) {
case 'gt' :
value = {after: dateValue};
break;
case 'lt' :
value = {before: dateValue};
break;
case 'eq' :
value = {on: dateValue};
break;
}
gridFilter = log.filters.getFilter(filter.field);
gridFilter.setValue(value);
gridFilter.setActive(true);
break;
case 'numeric':
var value;
switch (filter.data.comparison) {
case 'gt' :
value = {gt: filter.data.value};
break;
case 'lt' :
value = {lt: filter.data.value};
break;
case 'eq' :
value = {eq: filter.data.value};
break;
}
gridFilter = log.filters.getFilter(filter.field);
gridFilter.setValue(value);
gridFilter.setActive(true);
break;
case 'list':
gridFilter = log.filters.getFilter(filter.field);
gridFilter.menu.setSelected(gridFilter.menu.selected, false);
gridFilter.menu.setSelected(filter.data.value.split(','), true);
break;
default :
gridFilter = log.filters.getFilter(filter.field);
gridFilter.setValue(filter.data.value);
break;
}
});
}
Here's an example of a "filter" object array.
// an example of a "filters" argument
[{
field: 'some_list_column_data_index',
data: {
type: 'list',
value: 'item1,item2,item3,item4,item5,item6,item7'
}
}, {
field: 'some_date_column_data_index',
data: {
type: 'date',
comparison: 'gt',
value: '07/07/2007'
}
}]
One caveat, you need to "create" the filters manually before using this function. Normally FiltersFeature grid filters are "created" the first time a user clicks on one of them, that may not happen if the user just wants to apply one of these predefined filters.
That can be handled easily by including this afterrender listener in the gridpanel.
listeners: {
// must create the filters after grid is rendered
afterrender: function(grid) {
grid.filters.createFilters();
}
}
Just add
filter: true
to grid columns description like this:
me.columns = [
{header:"Name", dataIndex:"name", editor:"textfield", filter: true},
];
if you want to get the filter work after the first attempt, first instance create.
Here is something that may be worth looking into. It seems that the filters plugin is listening for menucreate event to initialize the filters. I wonder if menu create event is deferred until necessary and hence the filters don't get initialized?
/**
* #private Handle creation of the grid's header menu. Initializes the filters and listens
* for the menu being shown.
*/
onMenuCreate: function(headerCt, menu) {
var me = this;
me.createFilters(); //<------
menu.on('beforeshow', me.onMenuBeforeShow, me);
},
Do you want to apply grid filter or may be store.filter() capability would suit you better? In this case just filter the store, and grid will display filtered records.
I discovered another way to implement this. It appears that grid features are only bound to the grid after the grid is rendered. This means that any setup of the filter will not take effect until after the grid is rendered. The initial load of the store appears to be initiated before the grid is rendered.
I solved my problem by creating my store with a memory proxy containing no data.
me.store = Ext.create('Ext.data.Store', {
model: 'SummaryData',
data: [],
proxy: {
type: 'memory',
reader: 'array'
},
remoteSort: true,
remoteFilter: true
});
Then set up an afterrender handler on the grid to poke in the correct proxy and initiate a load of the store.
afterrender: function () {
var me = this;
me.store.setProxy({
type: 'ajax',
url : '/print_unallocated/change_site__data',
reader: {
type: 'json',
root: 'rows'
},
listeners: {
exception: function (proxy, response) {
Max.reportException(response);
}
}
});
me.filters.createFilters();
me.store.load();
},
In the source, you can see a comment related to this.
// Call getMenu() to ensure the menu is created, and so, also are the filters. We cannot call
// createFilters() withouth having a menu because it will cause in a recursion to applyState()
// that ends up to clear all the filter values. This is likely to happen when we reorder a column
// and then add a new filter before the menu is recreated.
me.view.headerCt.getMenu();
You can test whether the menu has been created before applying your filter. If it hasn't, do it yourself.
if(!grid.getView().headerCt.menu){
grid.getView().headerCt.getMenu();
}