Dropdown opening issue inside of ui-grid - angularjs

I get a new row on dropdown Click and am using enableCellEditOnFocus and when I try to open the dropdown it is not opening and alternate dropdowns are working properly. To avoid confusion I altered the code and I am showing that bit of code that is giving me the error and here is my plunker.
This is my ui-grid object
$scope.gridOptions = {
enableCellEditOnFocus: true
};
$scope.gridOptions.columnDefs = [
{ name: 'gender', displayName: 'Gender', editableCellTemplate: 'ui-grid/dropdownEditor', width: '20%',
editDropdownValueLabel: 'gender', editDropdownOptionsArray: [
{ id: 1, gender: 'male' },
{ id: 2, gender: 'female' }
] } ];
I add new row by using this code:
$scope.addNewRow = function () {
$scope.gridOptions.data.push({
"gender": "male"});
};
//adding new row inside of uigrid
$scope.newRow = function (row, columnIndex) {
tempIndexSave = "";
tempIndexSave = _.indexOf($scope.gridOptions.data, row);
var length = $scope.gridOptions.data.length;
if (length - 1 == tempIndexSave) {
$scope.addNewRow();
}
};

The problem is due to these lines where you can see there's a hack for screen readers.
This hack may bring issues similar to yours because it depends on beginCellEdit and afterCellEdit events to be fired evenly and with a fixed pattern:
beginCellEdit
afterCellEdit
beginCellEdit
afterCellEdit
beginCellEdit
...
Adding a new row while already editing a row breaks this pattern since, for the new created row, you are missing a beginCellEdit, so the preventMouseDown function will always fire, preventing user interaction.
I put up a workaround, but I don't advise you using this if you want to keep compatibility with screen readers (see comments before the lines in the file I linked to), because the workaround might break the hack.
The workaround consists of raising a fake beginCellEdit while adding the row, as you can see in this updated plunkr.
...
if (length - 1 == tempIndexSave) {
$scope.addNewRow();
$scope.gridApi.edit.raise.beginCellEdit();
}
...

Related

Angular ui-grid editDropdownOptionsArray depending on current row

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.

Angular Ui-Grid Multi Level Nesting

I am using Angular Ui-grid.Trying to achieve multi level nesting in that. Some one have already raised the issue in uiGrid github. But that solution doesn't seems working.
I have changed the JSON like this. I am trying to form nested sub grid.
Below is my code
$scope.gridOptions = {
expandableRowTemplate: 'expandableRowTemplate.html',
enableGridMenu: true,
expandableRowHeight: 150,
paginationPageSizes: [5, 10, 15],
paginationPageSize: 5,
//subGridVariable will be available in subGrid scope
expandableRowScope: {
subGridVariable: 'subGridScopeVariable'
}
}
$scope.gridOptions.columnDefs = [
{ name: 'id', enableHiding: false },
{ name: 'name' },
{ name: 'age' },
{ name: 'address.city' }
];
$http.get('http://private-7a7085-getfriends1.apiary-mock.com/getfriends')
.success(function (data) {
for (i = 0; i < data.length; i++) {
//for(i = 0; i < 50; i++){
data[i].subGridOptions = {
columnDefs: [{ name: "Id", field: "id" }, { name: "Name", field: "name" }, { name: "Age", field: "id" }, { name: "Address", field: "name" }],
data: data[i].friends,
expandableRowTemplate: 'expandableRowTemplate1.html',
enableGridMenu: true,
expandableRowHeight: 150
}
for (j = 0; j < data[i].friends.length; j++) {
data[i].subNestedGridOptions = {
columnDefs: [{ name: "Id", field: "id" }, { name: "Name", field: "name" }, { name: "Age", field: "id" }, { name: "Address", field: "name" }],
data: data[i].friends[j].friends
}
}
}
$scope.gridOptions.data = data;
});
and second level nested html(expandableRowTemplate1.html) looks like
<div ui-grid="row.entity.subNestedGridOptions" style="height:150px;"></div>
When I pass data to ui-grid for second level nested grid, it throws undefined.
Does any one have success till now to implement Expandable UI Grid with more then 2 or 3 levels. If yes please share plunker or fiddle or detailed explanation would be really helpful!
I have this working now. I assume you have constructed the proper data to bind at 3 levels. I assume that you have a 2 level grid working as well (parent grid's rows expand to show child grids). Here are the key few pieces I added to get this to work:
The top level grid has the directive ui-grid-expandable in the div tag that defines it. This is located in my html view.
<div ui-grid="gridThreeLayer" ui-grid-expandable></div>
The top level grid defines an expandableRowTemplate in its gridOptions (angularJS) .
$scope.gridThreeLayer = { ........
expandableRowTemplate: '..your path.../expandableRowTemplate.html' }
The top level grid's expandableRowTemplate will contain a div tag that defines another "ui-grid" and has the directive "ui-grid-expandable".
<div ui-grid="row.entity.subGridOptions" ui-grid-expandable></div>
The 2nd level grid has its subGridOptions and columnDefs built on the fly when the data is being bound in the angularJS (off an $http.get() for me). At this time, you need to specify an expandableRowTemplate property on that 2nd level grid which tells it to expand and show the 3rd level grid.
for (i = 0; i < response.data.length; i++) {
response.data[i].subGridOptions = { .....
columnDefs: [ ............],
data: response.data[i].IP, // note that this is my 2nd level of data objects - yours will differ
expandableRowTemplate: 'AngularApp/Templates/MultiLevelTemplate.html
The expandableRowTemplate of the 2nd level grid needs to define a div tag with a "ui-grid", instructing the grid to render a nested grid for each row. It does NOT need a directive for expanding (but you could add one to go 4 levels deep). Mine is called MultiLevelTemplate .
<div ui-grid="row.entity.subGridOptions"></div>
Now in the same angularJS block, where you are building each row's gridOptions and columnDefs on the fly, you need to go to another level of iterations through that level of the data. This will allow you to define the subGridOptions and columnDefs fo the 3rd level grid. So you are in the "i" loop, and starting an "x" loop within it. Note that in setting the data source, it is using my own 3 levels of data objects. You need to use your own there.
for (x = 0; x < response.data[i].IP.length; x++) {
response.data[i].IP[x].subGridOptions = {
columnDefs: [........],
response.data[i].IP[x].subGridOptions.data = response.data[i].IP[x].BOM; // my data
If you do all of the above, it should work correctly if your data is set up correctly.

ui-grid dropdown editor with complex json object

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);
}
});
}
}

UI Grid - Formatting Data

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';
},
}
}

Legend Template - Chart

I got this template (default)
<span class="x-legend-item-marker {[values.disabled?'x-legend-inactive':'']}" style="background:{mark};"></span>{name}
that produce this :
I want to have the same template with every of it's functionnality. But, I need one more if-clause to it. I don't want an item to be 'legendarize' if it's value is 0.
Here is the complete code
{
xtype: 'container',
title: 'Chart',
iconCls: 'chart',
itemId: 'chart_Tab',
layout: {
type: 'fit'
},
items: [
{
xtype: 'polar',
itemId: 'pie',
colors: [
'#115fa6',
'#94ae0a',
'#a61120',
'#ff8809',
'#ffd13e',
'#a61187',
'#24ad9a',
'#7c7474',
'#a66111',
'#222222',
'#115ea6',
'#94cc0a',
'#b61120',
'#dd8809',
'#11d13e',
'#a68887',
'#94df9d',
'#7f74f4',
'#112341',
'#abcdef1'
],
store: 'relativedata',
series: [
{
type: 'pie',
label: {
textBaseline: 'middle',
textAlign: 'center',
font: '9px Helvetica'
},
labelField: 'strName',
labelOverflowPadding: 0,
xField: 'numValue'
}
],
interactions: [
{
type: 'rotate'
}
],
listeners: [
{
fn: function(element, eOpts) {
var relStore = Ext.getStore('relativedata');
var eleStore = Ext.getStore('element');
var relModel;
var eleModel;
relStore.removeAll();
//Convert to CO2 qty
for(var i = 0; i< eleStore.getCount();i++)
{
eleModel = eleStore.getAt(i);
relModel = Ext.create(APPNAME + '.model.RelativeElement');
relModel.set('strName',eleModel.get('strName'));
relModel.set('numValue', eleModel.get('numValue')*eleModel.getFactor());
relStore.add(relModel);
}
relStore.sync();
//Hide arrows-legend
this._series[0]._label.attr.hidden=true;
},
event: 'painted'
}
],
legend: {
xtype: 'legend',
docked: 'bottom',
itemId: 'pie_legend',
itemTpl: [
'<span class="x-legend-item-marker {[values.disabled?\'x-legend-inactive\':\'\']}" style="background:{mark};"></span>{name}'
],
maxItemCache: 100,
store: 'element'
}
}
]
}
I ask for help because i'm not that good with templates. I would not dare say I understand everything of the default one actually.
I'm back! Yet, nobody's calling me slim shaddy for that... Unluckily!
So, to answer your initial question, the template you need would be something like the following:
// Configuration of the chart legend
legend: {
// Finally, we can use the value field to customize our templates.
itemTpl: [
'<tpl if="value != 0">', // <= template condition
'<span class="x-legend-item-marker {[values.disabled?\'x-legend-inactive\':\'\']}" style="background:{mark};"></span>{name}',
'</tpl>'
]
// ...
}
Unfortunately, as I've said in my previous comment, quick debugger inspection shows that this value variable, or any equivalence, is not available at the time this template is applied.
Now I'm going to give you a detailed explanation about how I was able to overcome this vexation. In part because this is such an involved hack that you'd better know what you're doing if you decide to apply it, and in part because you'll learn a lot more by witnessing the fishing techniques than by being given the fish right away -- in this case, the fish is not available for retail anyway. And also in a large part, I must confess, because I like to be lyrical about things I've put some energy in, and it's late, and my defenses against self congratulation have gotten a bit weak...
So, looking at Ext.chart.Legend's code shows that there's nothing to be done there, it's just a somewhat lightweight extension of Ext.dataview.Dataview. As such it must have a store bounded to it, which, obviously (and unfortunately), is not the one bound to the chart to provide its data.
Another judicious breakpoint (in the Legend's setStore method) shows that this store comes from Ext.chart.AbstractChart, and in the code of this class we can see two things: a dedicated legend store is created in the constructor, and chart series implement a method to feed this store, namely provideLegendInfo.
We're getting closer to our goal. What we need to do is add a value field to the legend store, and have our serie provide the data for this field. Great!
The wise approach now would be to implement these modifications with the minimal amount of replication of Ext's code... But after having spent an inconsiderate amount of time trying to do that with no luck, I'll just settle for wildly overriding these two methods, and giving the advice to put a big bold warning to check that the code of these methods doesn't change with the next versions of Touch:
if (Ext.getVersion().isGreaterThan('2.2.1')) {
// Give yourself a big warning to check that the overridden methods' code
// bellow has not changed (see further comments).
}
With that out of the way, let's go to the point without any further consideration for future generations.
That is, first we add a value field to the legend store:
/**
* Adds a value field to legend store.
*/
Ext.define(null, {
override: 'Ext.chart.AbstractChart'
// Berk, what a lot of code replication :( Let's just hope that this method's code
// won't change in the future...
,constructor: function() {
var me = this;
me.itemListeners = {};
me.surfaceMap = {};
me.legendStore = new Ext.data.Store({
storeId: this.getId() + '-legendStore',
autoDestroy: true,
fields: [
'id', 'name', 'mark', 'disabled', 'series', 'index'
// Adding my value field
,'value'
]
});
me.suspendLayout();
// For whatever reason, AbstractChart doesn't want to call its superclass
// (Ext.draw.Component) constructor and, by using callSuper, skips directly to
// Ext.Container's one. So well... I respect, but I must do it old school since
// callSuper would go to Ext.draw.Component from here.
Ext.Container.prototype.constructor.apply(this, arguments);
// me.callSuper(arguments);
me.refreshLegendStore();
me.getLegendStore().on('updaterecord', 'onUpdateLegendStore', me);
me.resumeLayout();
}
}, function() {
// Post-create functions are not called for overrides in touch as they are
// in ExtJS? Hmm... That would have been the perfect place to issue a big
// warning in case the version has changed, but we'll live with it :(
});
And, second, we make our chart serie feed that value. From your code, I can deduce that you're working with a pie chart, so I'm only giving the code for that, as a matter of illustration... But, if you've followed until here, it should be trivial to implement it for other kind of series. Anyway, here's the code:
/**
* Overrides `provideLegendInfo` to add the value to the legend records.
*
* Here again, let us all cross our fingers very hard, hoping for Sencha's team to not decide
* to add their own extra fields too soon...
*/
Ext.define(null, {
override: 'Ext.chart.series.Pie'
,provideLegendInfo: function(target) {
var store = this.getStore();
if (store) {
var items = store.getData().items,
labelField = this.getLabelField(),
field = this.getField(),
hidden = this.getHidden();
for (var i = 0; i < items.length; i++) {
target.push({
name: labelField ? String(items[i].get(labelField)) : field + " " + i,
mark: this.getStyleByIndex(i).fillStyle || this.getStyleByIndex(i).strokeStyle || 'black',
disabled: hidden[i],
series: this.getId(),
index: i
// Providing actual data value to the legend record
,value: items[i].get(field)
});
}
}
}
});
Let's sum it up. We've got two overrides and a custom template. We could hope that we'd be done by now. But here's what we get:
So, the DataView is adding some markup of its own around the itemTpl's markup. Well, well, well... At this point, I'm tired of tracking Ext's internals and, fortunately (for once!), I envision a quick patch for this. So that is without an hesitation that I'm throwing this CSS rule in:
.x-legend-item:empty {
display: none;
}
And finally we're done. I guess my line of thought and code might be a little tricky to replicate, so let me provide you with a definitive proof that this all works.
In this demo, there is a "metric four" that has a value of 0.
{
'name': 'metric four',
'data': 0
}
But you won't see it. Because that was the point of all this, wasn't it?

Resources