I want to see totals (or generally any aggregation function) for each group in a grid.
This was requested and closed as done in issue: https://github.com/angular-ui/ng-grid/issues/95 (but unfortunately without any example).
I am able to redefine default template https://github.com/angular-ui/ng-grid/blob/master/src/templates/aggregateTemplate.html by aggregateTemplate config option. But I don't know how to extend it properly.
This template is evaluating in ng-grid scope and I don't know how to trigger my custom aggregation function. I want to create similar function as '''totalChildren''' (from https://github.com/angular-ui/ng-grid/blob/master/src/classes/aggregate.js )
Any ideas?
I found the answer: https://github.com/angular-ui/ng-grid/issues/290
For a completenes here is an example of my aggredate function:
function WorkLoadCtrl($scope, Issue, $routeParams, HttpCache) {
$scope.issues = Issue.jql_search({jql: $routeParams.jql});
$scope.aggFC = function (row) {
var res = 0;
var calculateChildren = function(cur) {
var res = 0;
var remaining;
angular.forEach(cur.children, function(a) {
remaining = a.getProperty('fields.timetracking.remainingEstimateSeconds');
if (remaining) { res += remaining; }
});
return res;
};
var calculateAggChildren = function(cur) {
var res = 0;
res += calculateChildren(cur);
angular.forEach(cur.aggChildren, function(a) {
res += calculateAggChildren(a);
});
return res;
};
return (calculateAggChildren(row) / 3600).toFixed(2);
}
$scope.mySelections = [];
$scope.gridOptions = {
data: 'issues.issues',
columnDefs: [
{field:'key', displayName:'key'},
{field:'fields.assignee.name', displayName:'Assignee'},
{field:'fields.status.name', displayName:'Status'},
{field:'fields.summary', displayName:'Summary'},
{field:'fields.timetracking.remainingEstimate', displayName:'Remaining'},
],
showFooter: true,
showGroupPanel: true,
enablePinning: true,
enableColumnResize: true,
enableColumnReordering: true,
showColumnMenu: true,
showFilter: true,
//jqueryUIDraggable: true, bug: when used grouping stop work, but column reordering start to work:(
selectedItems: $scope.mySelections,
multiSelect: false,
aggregateTemplate: '<div ng-click="row.toggleExpand()" ng-style="rowStyle(row)" class="ngAggregate"> <span class="ngAggregateText">{{row.label CUSTOM_FILTERS}} (count: {{row.totalChildren()}} {{AggItemsLabel}} Remaining: {{aggFC(row)}})</span> <div class="{{row.aggClass()}}"></div> </div>'
};
}
Related
I use refreshCells to update a specific cell style after clicking on it. However, this does not work, and when I set refreshCells({ force: true }), the whole column updates new style that I don't want. Any suggestions please?
vm.gridOptions = {
columnDefs: vm.columnDefs,
rowData: vm.rows,
angularCompileRows: true,
defaultColDef: {
sortable: true,
resizable: true,
},
onCellClicked: onCellClicked,
onGridReady: function (params) {
}
}
function onCellClicked(params) {
const focusedCell = params.api.getFocusedCell();
focusedCell.column.colDef.cellStyle = { 'background-color': '#b7e4ff' };
params.api.refreshCells({
force: true // this updates the whole column, not only the clicked cell
});
}
I solved this by setting the specific rowNodes and columns.
function onCellClicked(params) {
const focusedCell = params.api.getFocusedCell();
const rowNode = params.api.getRowNode(focusedCell.rowIndex);
const column = focusedCell.column.colDef.field;
focusedCell.column.colDef.cellStyle = { 'background-color': '#b7e4ff' };
params.api.refreshCells({
force: true,
columns: [column],
rowNodes: [rowNode]
});
}
Any idea how to Fit row height to content in ng-grid? See that the text is wrapped but not shown as the row has a fix height.
I saw several posts claiming there is no support for that... thought maybe someone can enlighten me...
Thanks.
Here is my code:
this.gridOptions = { data: 'tenants.elements',
selectedItems: this.mySelections,
enableColumnResize: true,
enableColumnReordering: true,
enableRowReordering: true,
showSelectionCheckbox: true,
selectWithCheckboxOnly: true,
showFooter: true,
//sortInfo: {fields: ['percent'], directions: ['desc']},
columnDefs:
[
{width: '7%', field:'apartment', displayName:'דירה'},
{width: '2%', field:'tenant_type', displayName:'-', cellTemplate: '<i ng-class="tenants.getTenantType(row.entity.tenant_type)" class="fa fa-child"></i>'},
{width: '2%', field:'defecto', displayName:'-', cellTemplate: '<i ng-show="row.entity.defecto" class="fa fa-child defecto"></i>'},
{width: '30%', field:'tenant_name', displayName:'שם דייר', cellTemplate: '{{row.entity.tenant_name}}'},
{width: '25%' ,field:'phones', displayName:'טלפונים'},
{width: '25%' ,field:'mails', displayName:'מיילים'}
],
filterOptions: this.filterOptions,
plugins: [new ngGridAutoRowHeightPlugin()]
//plugins: [new ngGridFlexibleHeightPlugin()]
}
Here is a plugin, some code taken from the ngGridFlexibleHeightPlugin
Be warned, that height will be changed on all rows by maximum height
ngGridAutoRowHeightPlugin = function () {
var self = this;
self.grid = null;
self.scope = null;
self.init = function (scope, grid, services) {
self.domUtilityService = services.DomUtilityService;
self.grid = grid;
self.scope = scope;
var recalcHeightForData = function () { setTimeout(innerRecalcForData, 1); };
var innerRecalcForData = function () {
var gridId = self.grid.gridId,
rowHeight = self.grid.config.rowHeight;
$('.' + gridId + ' .ngRow [ng-cell-text]').each(function (index, cellText) {
//+10 for default top and bottom padding of .ngCellText class
rowHeight = Math.max(rowHeight, $(cellText).outerHeight() + 10);
});
if (self.grid.config.rowHeight < rowHeight) {
self.grid.config.rowHeight = rowHeight;
//update grid's scope.rowHeight as vertical bars height depends on it
if (scope.$$phase == '$apply' || scope.$$phase == '$digest') {
updateGridScopeHeight();
} else {
scope.$apply(updateGridScopeHeight);
}
self.domUtilityService.RebuildGrid(self.scope, self.grid);
function updateGridScopeHeight() {
self.grid.$root.scope().rowHeight = rowHeight;
}
}
};
self.scope.catHashKeys = function () {
var hash = '',
idx;
for (idx in self.scope.renderedRows) {
hash += self.scope.renderedRows[idx].$$hashKey;
}
return hash;
};
self.scope.$watch('catHashKeys()', innerRecalcForData);
self.scope.$watch(self.grid.config.data, recalcHeightForData);
};
};
And also, add this style rule to your css (after ng-grid css)
.ngCellText {
white-space: normal !important;
}
Plunker here
I'm using AngularJS to populate my datatable. What I want to know is how can I populate the datatable based on the dropdownlist
This is my dropdownlist
<div>
Get Users with Role:
<select id="ddlRole" data-ng-model="selectedRole" data-ng-change="populateDataTable()" data-ng-options="v.name for (k,v) in roles"></select>
<input type="hidden" value="{{selectedRole}}" />
</div>
This is my angular code
$scope.roles = [
{name: 'XXX' },
{name: 'YYY' }
];
$scope.selectedRole = $scope.roles[0];
//onchange event
$scope.populateDataTable = function () {
$scope.selectedRole = $scope.selectedRole.name;
RefreshDataTable(); //TODO
};
How can I change this to make an ajax call to retreive the data, populate the datatable based on the dropdownlist value and retain the value of dropdownlist as well.
I'm sure we can do this using JQuery but I dont want to mix these and make a mess. Is there any way I can acheive this using AngularJS?
Here is a simple data table directive:
appModule.directive('dataTable', [function () {
return function (scope, element, attrs) {
// apply DataTable options, use defaults if none specified by user
var options = {};
if (attrs.myTable.length > 0) {
options = scope.$eval(attrs.myTable);
} else {
options = {
"bStateSave": true,
"iCookieDuration": 2419200, /* 1 month */
"bJQueryUI": true,
"bPaginate": false,
"bLengthChange": false,
"bFilter": false,
"bInfo": false,
"bDestroy": true
};
}
// Tell the dataTables plugin what columns to use
// We can either derive them from the dom, or use setup from the controller
var explicitColumns = [];
element.find('th').each(function (index, elem) {
explicitColumns.push($(elem).text());
});
if (explicitColumns.length > 0) {
options["aoColumns"] = explicitColumns;
} else if (attrs.aoColumns) {
options["aoColumns"] = scope.$eval(attrs.aoColumns);
}
// aoColumnDefs is dataTables way of providing fine control over column config
if (attrs.aoColumnDefs) {
options["aoColumnDefs"] = scope.$eval(attrs.aoColumnDefs);
}
if (attrs.fnRowCallback) {
options["fnRowCallback"] = scope.$eval(attrs.fnRowCallback);
}
// apply the plugin
var dataTable = element.dataTable(options);
// watch for any changes to our data, rebuild the DataTable
scope.$watch(attrs.aaData, function (value) {
var val = value || null;
if (val) {
dataTable.fnClearTable();
dataTable.fnAddData(scope.$eval(attrs.aaData));
}
});
if (attrs.useParentScope) {
scope.$parent.dataTable = dataTable;
} else {
scope.dataTable = dataTable;
}
};
}]);
Then initialize it in your controller. Override fnServerData method, append your selected value (selected role) and filter data on server side.
$scope.overrideOptions = {
"bStateSave": true,
"iDisplayLength": 8,
"bProcessing": false,
"bServerSide": true,
"sAjaxSource": 'Data/Get',
"bFilter": false,
"bInfo": true,
"bLengthChange": false,
"sServerMethod": 'POST', ,
"fnServerData": function(sUrl, aoData, fnCallback, oSettings) {
var data = {
dataTableRequest: aoData,
selectedDropDownValue: $scope.selectedRole
};
$http.post(sUrl, data).success(function (json) {
if (json.sError) {
oSettings.oApi._fnLog(oSettings, 0, json.sError);
}
$(oSettings.oInstance).trigger('xhr', [oSettings, json]);
fnCallback(json);
});
}
};
var columnDefs = [
{
"mData": "id",
"bSortable": false,
"bSearchable": false,
"aTargets": ['tb-id']
},
{
"mData": "data",
"aTargets": ['tb-data']
}
];
Refresh the datatable on select change.
$scope.populateDataTable = function () {
if ($scope.dataTable) {
$scope.dataTable.fnDraw();
}
};
Html markup:
<table class="display m-t10px" data-table="overrideOptions" ao-column-defs="columnDefs">
<thead>
<tr>
<th class="tb-id"></th>
<th class="tb-data></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
Hope above your code is in controller.
Inject $http and make a $http get or post call
$scope.populateDataTable = function () {
$scope.selectedRole = $scope.selectedRole.name;
$http.get('api/controller', function(result){
//response from the service call in result
});
};
The goal is to use buffered store for the dynamic data set.
The workflow is below:
Some data is already present on server.
Clients uses buffered store & infinite grid to handle the data.
When the application runs the store is loading
and 'load' event scrolls the grid to the last message.
Some records are added to server.
Client gets a push notification and runs store reload.
topic.store.load({addRecords: true});
The load event runs and tries to scroll to the last message again but failes:
TypeError: offsetsTo is null
e = Ext.fly(offsetsTo.el || offsetsTo, '_internal').getXY();
Seems that the grid view doesn't refreshes and doesn't show the added records, only the white spaces on their places.
Any ideas how can I make the grid view refresh correctly?
The store initialization:
Ext.define('orm.data.Store', {
extend: 'Ext.data.Store',
requires: ['orm.data.writer.Writer'],
constructor: function (config) {
Ext.apply(this, config);
this.proxy = Ext.merge(this.proxy, {
type: 'rest',
batchActions: true,
reader: {
type: 'json',
root: 'rows'
},
writer: {
type: 'orm'
}
});
this.callParent(arguments);
}
});
Ext.define('akma.chat.model.ChatMessage', {
extend:'Ext.data.Model',
fields:[
{ name:'id', type:'int', defaultValue : undefined },
{ name:'createDate', type:'date', dateFormat:'Y-m-d\\TH:i:s', defaultValue : undefined },
{ name:'creator', type:'User', isManyToOne : true, defaultValue : undefined },
{ name:'message', type:'string', defaultValue : undefined },
{ name:'nameFrom', type:'string', defaultValue : undefined },
{ name:'topic', type:'Topic', isManyToOne : true, defaultValue : undefined }
],
idProperty: 'id'
});
Ext.define('akma.chat.store.ChatMessages', {
extend: 'orm.data.Store',
requires: ['orm.data.Store'],
alias: 'store.akma.chat.store.ChatMessages',
storeId: 'ChatMessages',
model: 'akma.chat.model.ChatMessage',
proxy: {
url: 'http://localhost:8080/chat/services/entities/chatmessage'
}
});
var store = Ext.create('akma.chat.store.ChatMessages', {
buffered: true,
pageSize: 10,
trailingBufferZone: 5,
leadingBufferZone: 5,
purgePageCount: 0,
scrollToLoadBuffer: 10,
autoLoad: false,
sorters: [
{
property: 'id',
direction: 'ASC'
}
]
});
Grid initialization:
Ext.define('akma.chat.view.TopicGrid', {
alias: 'widget.akma.chat.view.TopicGrid',
extend: 'akma.chat.view.grid.DefaultChatMessageGrid',
requires: ['akma.chat.Chat', 'akma.UIUtils', 'Ext.grid.plugin.BufferedRenderer'],
features: [],
hasPagingBar: false,
height: 500,
loadedMsg: 0,
currentPage: 0,
oldId: undefined,
forceFit: true,
itemId: 'topicGrid',
selModel: {
pruneRemoved: false
},
multiSelect: true,
viewConfig: {
trackOver: false
},
plugins: [{
ptype: 'bufferedrenderer',
pluginId: 'bufferedrenderer',
variableRowHeight: true,
trailingBufferZone: 5,
leadingBufferZone: 5,
scrollToLoadBuffer: 10
}],
tbar: [{
text: 'unmask',
handler: function(){
this.up('#topicGrid').getView().loadMask.hide();
}
}],
constructor: function (config) {
this.topicId = config.topicId;
this.store = akma.chat.Chat.getMessageStoreInstance(this.topicId);
this.topic = akma.chat.Chat.getTopic(this.topicId);
var topicPanel = this;
this.store.on('load', function (store, records) {
var loadedMsg = store.getTotalCount();
var pageSize = store.pageSize;
store.currentPage = Math.ceil(loadedMsg/pageSize);
if (records && records.length > 0) {
var newId = records[0].data.id;
if (topicPanel.oldId) {
var element;
for (var i = topicPanel.oldId; i < newId; i++) {
element = Ext.get(i + '');
topicPanel.blinkMessage(element);
}
}
topicPanel.oldId = records[records.length-1].data.id;
var view = topicPanel.getView();
view.refresh();
topicPanel.getPlugin('bufferedrenderer').scrollTo(store.getTotalCount()-1);
}
});
this.callParent(arguments);
this.on('afterrender', function (grid) {
grid.getStore().load();
});
var me = this;
akma.UIUtils.onPasteArray.push(function (e, it) {
if(e.clipboardData){
var items = e.clipboardData.items;
for (var i = 0; i < items.length; ++i) {
if (items[i].kind == 'file' && items[i].type.indexOf('image/') !== -1) {
var blob = items[i].getAsFile();
akma.chat.Chat.upload(blob, function (event) {
var response = Ext.JSON.decode(event.target.responseText);
var fileId = response.rows[0].id;
me.sendMessage('<img src="/chat/services/file?id=' + fileId + '" />');
})
}
}
}
});
akma.UIUtils.addOnPasteListener();
},
sendMessage: function(message){
if(message){
var topicGrid = this;
Ext.Ajax.request({
method: 'POST',
url: topicGrid.store.proxy.url,
params:{
rows: Ext.encode([{"message":message,"topic":{"id":topicGrid.topicId}}])
}
});
}
},
blinkMessage: function (messageElement) {
if (messageElement) {
var blinking = setInterval(function () {
messageElement.removeCls('red');
messageElement.addCls('yellow');
setTimeout(function () {
messageElement.addCls('red');
messageElement.removeCls('yellow');
}, 250)
}, 500);
setTimeout(function () {
clearInterval(blinking);
messageElement.addCls('red');
messageElement.removeCls('yellow');
}, this.showInterval ? this.showInterval : 3000)
}
},
columns: [ {
dataIndex: 'message',
text: 'Message',
renderer: function (value, p, record) {
var firstSpan = "<span id='" + record.data.id + "'>";
var creator = record.data.creator;
return Ext.String.format('<div style="white-space:normal !important;">{3}{1} : {0}{4}</div>',
value,
creator ? '<span style="color: #' + creator.chatColor + ';">' + creator.username + '</span>' : 'N/A',
record.data.id,
firstSpan,
'</span>'
);
}
}
]
});
upd: Seems that the problem is not in View. The bufferedrenderer plugin ties to scroll to the record.
It runs a callback function:
callback: function(range, start, end) {
me.renderRange(start, end, true);
targetRec = store.data.getRange(recordIdx, recordIdx)[0];
.....
store.data.getRange(recordIdx, recordIdx)[0]
tries to get the last record in the store.
....
Ext.Array.push(result, Ext.Array.slice(me.getPage(pageNumber), sliceBegin, sliceEnd));
getPage returns all records of the given page, but the last record is missing i.e. the store was not updated perfectly.
Any ideas how to fix?
The problem is that store.load() doesn't fill up store PageMap with the new data. The simplest fix is using store.reload() instead.
Maybe you are to early when listening to the load event. I am doing roughly the same in my application (not scrolling to the end, but to some arbitrary record after load). I do the view-refresh and bufferedrender-scrollTo in the callback of the store.load().
Given your code this would look like:
this.on('afterrender', function (grid) {
var store = grid.getStore();
store.load({
callback: function {
// snip
var view = topicPanel.getView();
view.refresh();
topicPanel.getPlugin('bufferedrenderer').scrollTo(store.getTotalCount()-1);
}
});
});
i need to drag from grlRicProd to drop into grlInsOrd
grlIRicProd:
.....
id: 'grlRicProd',
sm: new Ext.grid.RowSelectionModel({
singleSelect: false
}),
enableDragDrop: true, ddGroup: 'grlRicProd-dd',
ddText: 'Prodotto Selezionato',
......
grlInsOrd
....
id: 'grlInsOrd',
enableDragDrop:
true, ddGroup: 'grlInsOrd-dd',
listeners: {
"render": {
fn: function(grid) {
var ddrow = new Ext.dd.DropTarget(Ext.getCmp('grlInsOrd').getEl(), {
ddGroup: 'grlRicProd-dd', // Data come from??
copy: false,
notifyDrop: function(dd, e, data) {
var sm = Ext.getCmp('grlRicProd').getSelectionModel();
var rows = sm.getSelections();
var cindex = dd.getDragData(e).rowIndex; //cindex IS UNDEFINED! WHY?
if (sm.hasSelection()) {
for (i = 0; i < rows.length; i++) {
Ext.getCmp('grlRicProd').store.remove(Ext.getCmp('grlRicProd').store.getById(rows[i].id)); //THIS WORKS FINE
Ext.getCmp('grlInsOrd').insert(cindex,rows[i]);//ERROR cindex is not defined
}
sm.selectRecords(rows);
}
});
}
}
}
how can i solve it??
thanks!
See this example and its code:
http://dev.sencha.com/deploy/ext-4.0.2a/examples/dd/dnd_grid_to_grid.html
It works perfectly! You are doing something which isnt required. the copy, creation and removal is all done automatically.