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);
}
});
}
}
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 want to have a different dropdown array for each row of my ui-grid. Therefore I need to access the current row. I had something like this in mind but I don't know how to access the current row within columnDefs. Is there an easy way to achieve this?
columnDefs: [
{ name: "id", field: "id"},
{ name: "firends", field: "friends", editableCellTemplate: 'ui-grid/dropdownEditor', editDropdownOptionsArray: findFriendsForId(row.id)
]
var findFriendsForId = function(id) {
// find friends and return them as array
}
Ok I figured it out by myself. There is a function called
editDropdownOptionsFunction.
Here is an example of it.
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 have coded the following:
$scope.gridOptions = {
data: 'myData',
enableCellEdit: true,
multiSelect: false,
columnDefs: [
{ field: 'Id', displayName: 'Id' },
{ field: 'Name', displayName: 'Name', enableCellEdit: true, editableCellTemplate: cellEditableTemplate },
{ field: 'Description', displayName: 'Description', enableCellEdit: true, editableCellTemplate: cellEditableTemplate }
]
};
The myData actually contains four colums Id, Name, Status and Description. Where status is a simple javascript array with three types of status called myStatus.
Is it possible for me to somehow link in the data from myStatus to a field in the ng-grid so I can then select a new value from a select drop down?
Here is output of some experiment.
http://plnkr.co/edit/W1TkRgsp0klhqquxaiyc?p=preview
It seems that you can put select in cell template.
And you can make use of row object to retrieve whatever
you need.
I used row.rowIndex to property access to the original data.
template example:
<div>
<select ng-model="myData[ row.rowIndex ].myStatus">
<option ng-repeat="st in statuses">{{st}}</option>
</select>
</div>
(It would be beutiful if we can write to ogirinal data through row
object. I do not know how.)
The way tosh shimayama are doing it, will not allow for sorting the table in any other order than the model array.
This is kind of an ugly way to do it, but I took a quick look in the source code for ng-grid and found that they use regexp to insert the ng-model. So by using the same variable, COL_FIELD, in your code you can make ng-grid insert the correct model.
<div>
<select ng-model="COL_FIELD">
<option ng-repeat="status in statuses">{{status}}</option>
</select>
</div>
Here is a plunker with a working example:
http://plnkr.co/edit/Yj2qmI?p=preview
A more complete/tidier way to do this in ng-grid 2.x I've included in a plunker here: http://plnkr.co/edit/VABAEu?p=preview, leveraging content from another similar question on stackoverflow here: AngularJS and ng-grid - auto save data to the server after a cell was changed
In summary, my format for the editable field template looks like so:
$scope.statuses = {1: 'active', 2: 'inactive'};
$scope.cellSelectEditableTemplate = '<select ng-class="\'colt\' + col.index"
ng-input="COL_FIELD" ng-model="COL_FIELD"
ng-options="id as name for (id, name) in statuses"
ng-blur="updateEntity(row)" />';
I've provided a blog post that has a more thorough walkthrough of the end-to-end code: http://technpol.wordpress.com/2013/12/06/editable-nggrid-with-both-dropdowns-and-selects/
Ng-grid (ui-grid) 3.0 is close to being released, and offers different ways to do editable grids. I have a post on that here: http://technpol.wordpress.com/2014/08/23/upgrading-to-ng-grid-3-0-ui-grid/
I can't take credit for the entire solution. I just put the pieces together. My goal was to preserve three-way binding.
Template should look something like this:
$scope.cellSelectEditableTemplate = '<select ng-class="\'colt\' + col.index" ng-input="COL_FIELD" ng-model="COL_FIELD" ng-options="value.id as value.label for value in OptionsList}" />';
COL_FIELD of course essential to keep the reference to correct cell.
You can capture the change natively:
$scope.$on('ngGridEventEndCellEdit', function (evt) {
console.log(evt.targetScope.row.entity);
WaitListArray.$save(evt.targetScope.row.entity)
.then(function (ref) {
console.log('saved');
});
});
In this case WaitListArray is my Firebase/AngularFire Array for the table. Using this method, I was able to preserve my tree-way binding.
Field (ng-options):
{
field: 'status',
displayName: 'Status',
enableCellEditOnFocus: true,
editableCellTemplate: $scope.cellSelectEditableTemplate,
cellFilter: 'mapStatus:OptionsList'
}
I added filter to replace id with label for my dropdown values.
.filter('mapStatus', function() {
return function(input, OptionsList) {
var _out = 'unknown';
angular.forEach(OptionsList, function(value, key) {
if (value && value.id == input) {
_out = value.label;
}
});
return _out;
};
})
In the above, OptionsList is my dropdown values array
example: {id:1,label:"label1"}
I found this solution highly reusable. Hopefully, it saves time for someone.
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);
},