ng-grid - Keep overall width the same when altering column widths - angularjs

I have an ng-grid with 6 columns in it, and as a default set up each column is 100px, so the grid itself is 600px. The columns are resizable but I want to keep the overall grid width the same, to ensure that there are no horizontal scroll bars. So, for example, if I change the second columns width to 150px I would want the overall width to stay at 600px (so maybe an adjacent cell will change size to 50px) - this way I don't get a scroll bar.
Does anybody know if there is a plugin that can do this/help me accomplish this?
I've included a plunker: http://plnkr.co/edit/4LRHQPg7w2eDMBafvy6b?p=preview
In this example, I would want to keep the table width at 600px, so if I expand "Field 2" you will see "Field 4" go off the edge of the viewable area for the grid and a horizontal scroll bar appear. The behaviour I want is for a different column (probably the adjacent column - "Field 3") to shrink in size automatically, so that the grid stays at 600px and the horizontal scroll bar doesn't appear.

After a lot of time searching the web for an answer, I started with Paul Witherspoon idea of watching the isColumnResizing property and after reading about ng-grid plugins I came up with this plugin solution:
Plunker: http://plnkr.co/edit/Aoyt73oYydIB3JmnYi9O?p=preview
Plugin code:
function anchorLastColumn () {
var self = this;
self.grid = null;
self.scope = null;
self.services = null;
self.init = function (scope, grid, services) {
self.grid = grid;
self.scope = scope;
self.services = services;
self.scope.$watch('isColumnResizing', function (newValue, oldValue) {
if (newValue === false && oldValue === true) { //on stop resizing
var gridWidth = self.grid.rootDim.outerWidth;
var viewportH = self.scope.viewportDimHeight();
var maxHeight = self.grid.maxCanvasHt;
if(maxHeight > viewportH) { // remove vertical scrollbar width
gridWidth -= self.services.DomUtilityService.ScrollW;
}
var cols = self.scope.columns;
var col = null, i = cols.length;
while(col == null && i-- > 0) {
if(cols[i].visible) {
col = cols[i]; // last column VISIBLE
}
}
var sum = 0;
for(var i = 0; i < cols.length - 1; i++) {
if(cols[i].visible) {
sum += cols[i].width;
}
}
if(sum + col.minWidth <= gridWidth) {
col.width = gridWidth - sum; // the last gets the remaining
}
}
});
}
}
and in the controller
$scope.gridOptions = {
data: 'myData',
enableColumnResize: true,
plugins: [new anchorLastColumn()],
columnDefs: $scope.columnDefs
};
It is not a perfect solution but works for me and I hope that it will help others.

I found a way to do this using a watch on the isColumnResizing property:
$scope.$watch('gridOptions.$gridScope.isColumnResizing', function (newValue, oldValue) {
if (newValue === false && oldValue === true) { //on stop resizing
$scope.ColResizeHandler($scope.gridOptions.$gridScope.columns);
}
}, true);
then I was able to resize the columns in the resize handler I created:
$scope.ColResizeHandler = function (columns) {
var origWidth;
var col1 = undefined;
var col2 = undefined;
var widthcol2;
var found = false;
var widthDiff = 0;
angular.forEach(columns, function (value) {
if (col2 == undefined && value.visible) {
if (found) {
origWidth += value.width;
col2 = value;
colSizeLimits(col2, widthDiff);
found = false;
}
if (value.origWidth != undefined && value.origWidth != value.width && col2 == undefined) {
found = true;
col1 = value;
widthDiff = value.width - value.origWidth;
origWidth = value.origWidth;
}
}
});
if (col2 == undefined) {
//this was the last visible column - don't allow resizing
col1.width = origWidth;
}
else {
//ensure limits haven't been blown to cope with reizing
if (col1.width + col2.width != origWidth) {
var diff = (col1.width + col2.width) - origWidth;
colSizeLimits(col1, diff);
}
}
col1.origWidth = col1.width;
col2.origWidth = col2.width;
}
There are 2 issues with this.
1 - if you resize and drag the column sizer outside of the grid (i.e. all the way over and out of the ng-grid viewable area) the isColumnResizing watch doesn't execute when you stop dragging and release the resizer. (I think this may be a bug in ng-grid because it does actually resize the column to where you have dragged the resizer, even if it is outside the grids viewable area, it just doesn't fire the watch code).
2 - if you avoid this issue and just drag within the viewable grid area then the columns will resize but only after you finish dragging the resizer, so the ui looks a little funny (i.e. if I expand a column then the adjacent column will not shrink until I click off the resizer and stop dragging).
I'll be working on these issues and will post any updates/fixes I find.

Related

AngularJS dirPagination Select All Visible Rows

I'm building a CRUD data management project using Angular and the dirPagination directive and need to have a "select all" checkbox that selects all rows that are visible /without/ using jQuery.
I started with this - note - I am climbing the Angular learning curve and am aware that some/all of this may not be "the Angular way" but I also don't want to start fiddling (no pun) with the guts of dirPagination, ergo the dirPagination directive:
<tr dir-paginate-start="item in items|filter:filterFunction()|orderBy:sortPredicate:reverse| itemsPerPage: rowsPerPage">
and the individual row-checkbox
<input type="checkbox" class="rowSelector" ng-model="item.isSelected" ng-change="rowSelect($index, $event)"/> status: {{item.isSelected}}
and the related model elements:
$scope.items = [] //subsequently filled
$scope.rowsPerPage = 5;
$scope.rowSelect = function (ix, $event) {
var checked = (typeof $event == 'undefined') ? false : true;
if (!checked) { $scope.masterCheck = false; }
var rpp = $scope.rowsPerPage;
var p = $scope.__default__currentPage; //dirPagination's current page
var start = ((Math.max(0, p - 1) * rpp));
$scope.items[start + ix].isSelected = checked;
};
that works as expected. Check/uncheck a row and the {{item.isSelected}} value is updated in the model and is displayed beside the checkbox.
Then I added this /outside/ of the dirPagination repeat block:
<input type="checkbox" id="masterCheckbox" ng-model="masterCheck" ng-click="checkAll()" />
and the related function in the model:
$scope.masterCheck = false;
$scope.checkAll = function () {
var rpp = $scope.rowsPerPage;
var p = $scope.__default__currentPage; //dirPagination's current page
var start = ((Math.max(0, p - 1) * rpp));
var checked = $scope.masterCheck == true;
var rows = document.getElementsByClassName("rowSelector");
for (var ix = 0; ix < rows.length; ix++) {
rows[ix].checked = checked;
$scope.items[start + ix].isSelected = checked;
}
}
however in the checkAll() function checking/unchecking the individual rows isn't reflected in the {{item.isSelected}} display of each row.
Explicitly setting the individual item with
$scope.items[start + ix].isSelected = checked;
seems to set the 'isSelected' property of that item within the scope of the checkAll function but the row display does not change.
Clearly I have something wrong perhaps misunderstanding a Scope issue but at this point I'm stumped.
Any help greatly appreciated :-)
The light dawned, finally.
checkAll() as written tried to access each row by calculating its position using dir-paginate's __default__currentPage and Angular's row $index.
Of course that doesn't work because the items[] collection held by dir-paginate has been subjected to filtering and sorting, so while items[] do get checked (item.isSelected = true) the selected items/rows were living on non-visible pages. i.e. - we were selecting the wrong indexes.
One solution is comprised of the following -
The master checkbox
<input type="checkbox" id="masterCheckbox" ng-model="masterCheck" ng-click="checkAll()" />
The row checkbox (note function calls)
<input type="checkbox" class="rowSelector" value="{{sourceIndex('senId',item.senId)}}" ng-model="item.isSelected" ng-click="rowSelect(this)" />
the dir-paginate directive controls tag
<dir-pagination-controls on-page-change="onPageChange(newPageNumber)" max-size="15" direction-links="true" boundary-links="true" pagination-id="" template-url=""></dir-pagination-controls>
and the related $scope values and functions:
$scope.items = [];
$scope.masterCheck = false;
$scope.onPageChange = function (newPageNumber) {
//clear all selections on page change
//dir-paginate provides this hook
$scope.masterCheck = false;
for (var i in $scope.items) {
$scope.items[i].isSelected = false;
}
}
$scope.rowSelect = function (item) {
//if one is unchecked have to turn master off
if (!item.isSelected) $scope.masterCheck = false;
}
$scope.sourceIndex = function (keyName, key) {
//gets the actual items index for the row key
//see here http://stackoverflow.com/questions/21631127/find-the-array-index-of-an-object-with-a-specific-key-value-in-underscore
//for the 'getIndexBy' prototype extension
var ix = $scope.items.getIndexBy(keyName, key);
return ix;
}
$scope.checkAll = function () {
//only visible rows
var boxes = document.getElementsByClassName("rowSelector");
for (var bix in boxes) {
var ix = boxes[bix].value;
$scope.items[ix].isSelected = $scope.masterCheck;
}
}
There is probably a better way but while not highly efficient this one works well enough for common folk.
The $scope.sourceIndex() function stuffs the actual source row index into the row checkbox as its value= attribute value.
The checkAll() function then grabs all visible rows by the "rowSelector" class and then iterates through those grabbing the data index from the checkbox value and setting the appropriate item.isSelected.
The $scope.onPageChange is specified in the dir-Paginate controls directive and ensures that when the page changes, all row selections are cleared.
Happy happy.
Your variable masterCheck is being set to false in rowSelect() and subsequently in checkAll() you are setting checked to masterCheck which then is used to assign isSelected
This line is wrong:
var checked = $scope.masterCheck == true;
Because you want to flip masterCheck so it should be:
$scope.masterCheck = !$scope.masterCheck;
and then
.isSelected = $scope.masterCheck;
You weren't ever setting $scope.masterCheck to true so it was always false and since your isSelected values depended on it, they were always false. Also, this functions as a checkAll/unCheckAll to make it only check all change to the following:
$scope.masterCheck = !$scope.masterCheck;
var checked = $scope.masterCheck == true;

ng-grid delete a row by clicking a button outside the grid

I have an ng-grid which has the Edit and Delete buttons at the bottom of the grid. Both buttons are disabled when no rows are selected.
I want to know what the correct way to delete a row for ng-Grid, when a row is selected.
I could not find any examples from their website or their wiki
I did a quick comparison of the original and the selected... something like this:
angular.forEach($scope.gridOptions.selectedItems, function(index) {
var deleteIndex = $scope.originalResource.indexOf(index);
if (deleteIndex > -1){
$scope.originalResource.splice(deleteIndex,1);
}
});
And then to unselect the rows I did this: $scope.selections.splice(0)
use this it works for both multiple rows or single row selection
$scope.mySelections = [];
$scope.gridOptions = {
data :'data',
selectedItems : $scope.mySelections,
showSelectionCheckbox : true
}
$scope.delItem = function() {
for (var i = 0; i < $scope.mySelections.length; i++) {
var index = $scope.data.indexOf($scope.mySelections[i]);
if (index != -1) {
$scope.data.splice(index, 1);
}
}
}

get column index of hide column in extjs grid panel

I need to get column index of hide column in extjs grid panel
columnhide: function() {
var cell = this.getEl().query('.x-grid-cell-inner');
for(var i = 0; i < cell.length; i++) {
if (i%2 != 0){ // Instead of this i, want to change the style to none for the hide column, so i need to get the column index of hide column in grid panel
cell[i].style.display= "none";
}
}
Using the columnhide listener:
columnhide: function(ct, column, eOpts) {
alert(column.getIndex());
},
Alternatively, you could loop through the grid columns and check the isHidden() property on each column:
Ext.each(grid.columns, function(column, index) {
if (column.isHidden()) {
alert('column at index ' + index + ' is hidden');
}
});
I have a test case set up here: http://jsfiddle.net/cCEh2/

ExtJS CheckboxGroup label wrap

I have a problem with checkboxes. So these checkboxes are in columns, and the longer labels wrap under their checkboxes. Is there any sollution to setting the column width to the longest label? It must be dynamic width. Can you help me?
Here's something you can use to measure all the labels and apply the width of the widest one to all of them http://jsfiddle.net/Wr7Wr/1/
Ext.define('DynamicLabelFormPanel', {
extend: 'Ext.form.Panel',
initComponent: function() {
// Need to create a hidden field to measure the text
var field = new Ext.form.field.Text({fieldLabel: 'Testing', value: 'whateves', renderTo: Ext.getBody(), hidden: true});
var metrics = new Ext.util.TextMetrics(field.labelEl);
var widths = Ext.Array.map(this.items, function(item) {
// Need to acount for the :
return metrics.getWidth(item.fieldLabel + ":");
});
var maxWidth = Math.max.apply(Math, widths);
for (var i = 0; i < this.items.length; i++) {
this.items[i].labelWidth = maxWidth;
}
this.callParent();
}
}
This would probably be better as a plugin, but it shows you what needs to be done

ExtJS 4: Excel-style keyboard navigation in an editable Grid

Using ExtJS 4 I would like to create a grid into which the user will enter a long list of numbers. I would like the user to be able to finish editing one cell by pressing "Enter" and then automatically move to the cell below and start editing.
Ext.grid.plugin.CellEditing seems to work well for the purpose of editing, but I can't find a way to detect and handle the keyUp event.
I tried approaching the matter using the "edit" event with this code:
selType: 'cellmodel',
plugins: [
Ext.create('Ext.grid.plugin.CellEditing', {
clicksToEdit: 1,
listeners: {
edit: function (editor, e, eOpts) {
// If the event is fired by pressing "Enter", the column and row associated with event will
// be the same as the currently selected row and column
if (e.grid.selModel.position.column == e.colIdx && e.grid.selModel.position.row == e.rowIdx){
// Check if we are in the final row
if (e.rowIdx+1 == e.grid.getStore().getTotalCount()){
return true;
}
editor.startEditByPosition({row: e.rowIdx+1, column: e.colIdx});
return false;
}
}
}
})
],
The problem with this approach is that if the user starts editing the cell, then clicks on a GUI element outside of the Grid, the edit event does not distinguish this from the "Enter" key being pressed. Can this be fixed?
Perhaps I should be trying to implement a custom SelectionModel instead? How does one go about starting with that approach?
something like this ???
when editing try using tab, shift + tab, enter, and shift + enter
what i did is override the cellediting and selection.rowmodel
actually, i have had a similar question with you. and #Lionel Chan help me solve it.
and my solution above is base on his idea.
Note : only work with Extjs 4.0.7
EDIT (if my jsfiddle broken, here's the override)
Ext.override(Ext.grid.plugin.CellEditing,{
onSpecialKey: function(ed, field, e) {
var grid = this.grid,sm;
if (e.getKey() === e.TAB) {
e.stopEvent();
sm = grid.getSelectionModel();
if (sm.onEditorTab)sm.onEditorTab(this, e);
}else if(e.getKey() === e.ENTER){
e.stopEvent();
sm = grid.getSelectionModel();
if (sm.onEditorEnter)sm.onEditorEnter(this, e);
}
}
});
Ext.override(Ext.selection.RowModel, {
lastId:null,
onEditorTab: function(ep, e) {
var me = this,
view = me.view,
record = ep.getActiveRecord(),
header = ep.getActiveColumn(),
position = view.getPosition(record, header),
direction = e.shiftKey ? 'left' : 'right',
newPosition = view.walkCells(position, direction, e, false),
newId=newPosition.row,
grid=view.up('gridpanel');
if (me.lastId!=newId && me.lastId!=null){
deltaX = me.lastId<newId? -Infinity : Infinity;
header=grid.headerCt.getHeaderAtIndex(newPosition.column);
if(header){
while(!header.getEditor()){
newPosition= view.walkCells(newPosition,direction, e, false);
header=grid.headerCt.getHeaderAtIndex(newPosition.column);
}
}
}else{
deltaX = ep.context.column.width * (direction== 'right' ? 1 : -1);
}
grid.scrollByDeltaX(deltaX);
me.lastId=newPosition.row;
Ext.defer(function(){
if (newPosition)ep.startEditByPosition(newPosition);
else ep.completeEdit();
}, 100);
},
onEditorEnter:function(ep,e){
var me = this,
view = me.view,
record = ep.getActiveRecord(),
header = ep.getActiveColumn(),
position = view.getPosition(record, header),
direction = e.shiftKey ? 'up' : 'down',
newPosition = view.walkCells(position, direction, e, false),
newId=newPosition.row,
grid=view.up('gridpanel');
deltaY=20 * (direction== 'down' ? 1 : -1);
grid.scrollByDeltaY(deltaY);
me.lastId=newPosition.row;
Ext.defer(function(){
if (newPosition)ep.startEditByPosition(newPosition);
else ep.completeEdit();
}, 100);
}
});
Starting with the suggestion of "Warung Nasi 49",
I Wrote this override:
Ext.define('Override.Ext.grid.plugin.CellEditing',
{
override: 'Ext.grid.plugin.CellEditing',
onSpecialKey: function (ed, field, e) {
var grid = this.grid,
nbCols = grid.columns.length - 1, //start from 0
nbRows = grid.getStore().getTotalCount() - 1, //start from 0
currentCol = this.context.colIdx,
currentRow = this.context.rowIdx;
//forward
if (!e.shiftKey && (e.getKey() === e.ENTER || e.getKey() === e.TAB)) {
e.stopEvent();
//Detect next editable cell
do {
if (this.startEdit(currentRow, currentCol + 1))
break;
else {
currentCol++;
if (currentCol >= nbCols) {
currentRow++;
currentCol = -1;
}
}
} while (currentRow <= nbRows);
}
//backward
if (e.shiftKey && (e.getKey() === e.ENTER || e.getKey() === e.TAB)) {
e.stopEvent();
//Detect previous editable cell
do {
if (this.startEdit(currentRow, currentCol - 1))
break;
else {
currentCol--;
if (currentCol < 0) {
currentRow--;
currentCol = nbCols+1;
}
}
} while (currentRow >=0);
}
}
},
null);
This override allow both TAB and ENTER navigation (forward/backward via SHIFT).
When the last cell of row is reached, the firt editable cell of the next row is focused.

Resources