I am trying to implement Handsontable into our reporting system. Ive done everything i wanted except one thing. I am highlighting error cell using renderers (simply by setting red color on background). However when i remove row by context menu->remove row, all renderers remain on their x-y positions. I'd like them to follow their rows instead.
$container.handsontable({
data: data,
rowHeaders: true,
colHeaders: true,
minSpareRows: 1,
contextMenu: true,
stretchH: 'all',
comments: true,
cells: function(row, col, prop) {
var cellProperties = {};
{foreach $excelError as $error}
if (row === {$error['row']} && col === {$error['col']}) {
cellProperties.renderer = {$error['renderer']};
cellProperties.comment = {$error['desc']};
}
{/foreach}
if (row === 0) {
cellProperties.renderer = firstRowRenderer;
}
return cellProperties;
}
});
Despite variable name $error it also contents correct row data which just pain cells with white color. The errorRenderer function looks like this (white one is similar to this one)
var errorRenderer = function(instance, td, row, col, prop, value, cellProperties) {
Handsontable.renderers.TextRenderer.apply(this, arguments);
$(td).css({
background: 'red',
color: "white",
fontWeight: "bold"
});
};
Any idea how to fix that? I'd be fine even just with catch of that remove row event as i can call ajax and rerender cells again.
Thank you for any ideas.
EDIT:
Fixed solution:
var cellsProp = new Array();
{foreach $excelError as $error}
cellsProp[{$error['row']}] = new Array();
cellsProp[{$error['row']}][{$error['col']}] = {$error['renderer']|noescape};
{/foreach}
var removing = false;
$container.handsontable({
data: data,
rowHeaders: true,
colHeaders: true,
minSpareRows: 1,
contextMenu: true,
stretchH: 'all',
comments: true,
cells: function(row, col, prop) {
var cellProperties = {};
if (typeof cellsProp[row] != "undefined") {
cellProperties.renderer = cellsProp[row][col];
}
if (row === 0) {
cellProperties.renderer = firstRowRenderer;
}
return cellProperties;
},
beforeRemoveRow: function(index, amount) {
if (removing == false) {
for (x = index; x < cellsProp.length; x++) {
cellsProp[x] = cellsProp[x+1];
}
}
removing = true;
},
afterRemoveRow: function(index, amount) {
removing = false;
}
});
The error is in the PHP. When a row is deleted, your PHP still thinks that a specific row index is in error. If you could you provide a function that would remove a row from your PHP, we can create a function beforeRemoveRow which triggers after a row is removed.
In here we can call your PHP function and give it the row index to remove. Your program should now work.
The function would look like this, and would be added to the list of options on your hot definition:
beforeRemoveRow: function(index, amount) {
phpFunctionRemoveRow(index);
}
EDIT:
With this logic of hard-coded coordinates, removing a row would always give you the behavior of turning (x,y) red. This is what was meant by having an "error" in your php. It means that since you receive the coordinates with errors from PHP, you can't expect the hardcoded index values to change.
Something still doesn't make sense. If you have no interaction with PHP after render (you don't call it after render), and you say that PHP is correct, then does this mean you're never going to be modifying your table? What if someone makes a change and you have to re-validate your table?
If, however, you are capable of doing this, then what I would do is the following:
After a change to the table, call your php function to revalidate. This will change that errors array you have in php but because the cells option is static, you won't see changes. At that point, you'd use:
hotInstance.updateSettings({ cells: getCellsAgain() });
In that function, getCellsAgain(), you'd call your php code again to tell you the new indeces. That's the best you'll get without doing the validation on the JS side.
Related
I prepared a custom renderer for Handsontable in angular 5 but i can not find a way to register the renderer.I can not find any function like 'registerRenderer'.Renderer function is specified below
coverRenderer (instance, td, row, col, prop, value, cellProperties):any
{
var escaped = Handsontable.helper.stringify(value),
button;
button = document.createElement('BUTTON');
button.setAttribute("name","Edit");
//button.onclick = this.editFood(value);
Handsontable.dom.addEvent(button, 'click', () => {
this.editFood(value);
});
Handsontable.dom.empty(td);
td.appendChild(button);
return td;
}
When i load the table it show error like "No registered renderer found under "coverRenderer" name". How can i register the renderer . I am using angular 5
After hours of tries, I did not found a way to register a new renderer. So I tried, when declaring the renderer of the table (I use columns object), something like this.
In my html I have:
<hot-table [data]="data" [colHeaders]="true" [columns]="columns"></hot-table>
In my component this portion of code
columns: object[] = [
{ data: 'data.rowName', readOnly: true, renderer: this.rowRendering},
{ data: 'data.baseInitialMargin'},
]
rowRendering(instance, td, row, col, prop, value, cellProperties) {
console.log("Rendering row ");
Handsontable.renderers.TextRenderer.apply(this, arguments);
if(row % 2 == 0) {
td.style.backgroundColor = '#F5F5F5';
} else {
td.style.backgroundColor = 'white';
}
}
And it works fine for me, I see the pair rows of my column with a different color. After a lot of blasphemy!
in your template you should write
<hot-column (data="{{row}}", title="{{row}}",[renderer]="tableOptions.options.renderer" )></hot-column>
I have a problem with data validation on an editable cell. I make some cells of one column editable based on the values of these cells. Here is the code of the grid:
jQuery("#cart").jqGrid({
datatype: 'local',
autowidth: true,
height: tabHeight,
gridview: true,
shrinkToFit: true,
autoencode: true,
loadtext: "იტვირთება",
multiselect: true,
idPrefix:"b",
colNames: ['დასახელება', 'რაოდენობა მინ.', 'რაოდენობა მაქს.', 'პოზიცია', 'რაოდენობა', 'ფასი'],
colModel: [{
name: 'asktx',
index: 'asktx',
width: 40,
sorttype: "string",
},
{
name: 'menge_min',
index: 'menge_min',
width: 30,
sorttype: "number",
hidden: true
},
{
name: 'menge_max',
index: 'menge_max',
width: 30,
sorttype: "number",
hidden: true
},
{
name: 'srvpos',
index: 'svrpos',
hidden: true
},
{
name: 'menge',
index: 'menge',
width: 30,
sorttype: "number",
editrules: {
required: true, number: true, custom: true, custom_func: checkInterval
},
editable: true,
},
{
name: 'price',
index: 'price',
width: 30,
sorttype: "string",
search: false,
}],
viewrecords: true,
caption: "არჩეული მომსახურებები",
gridComplete: function () {
var $this = $(this), ids = $this.jqGrid('getDataIDs'), i, l = ids.length;
for (i = 0; i < l; i++) {
var rowData = jQuery(this).getRowData(ids[i]);
if (rowData.menge_min != rowData.menge_max && !rowData.menge) {
menge_min = rowData.menge_min;
menge_max = rowData.menge_max;
$this.jqGrid('editRow', ids[i], true);
}
}
}
});
I am using gridComplete to check if values of two cells are equal and if they are not, I am making a column called "menge" editable in that row. I have also done the validation for constant values, like if I need to check whether this menge value is between a and b, I can do that and all is fine, although now I need to validate that field value based on the values of "menge_min" and "menge_max" field values, that are hidden. I see that custom function "checkInterval" can have only two parameters, so I can not pass the row ID there. Is there any way for getting some kind of information about the row which is currently being edited in custom validation function?
I fill the grid based on click event on another grid, here is the validation function for now:
var checkInterval = function (value, colname) {
value = parseInt(value);
mange_min = parseInt(menge_min);
menge_max = parseInt(menge_max);
if (value < menge_min || value > menge_max) {
return [false, "რაოდენობა უნდა იყოს " + menge_min + "-" + menge_max + " ინტერვალში"];
} else {
return [true];
}
}
As for multiple editable rows, it is kind of requirement and the user knows he/she entered a correct value if there is no validation error popup and continue to edit other rows. The jqGrid version is 4.5.1.
here is the select event of the other grid:
onSelectRow: function (id) {
if (id && id !== lastSel) {
jQuery(this).restoreRow(lastSel);
lastSel = id;
var celValue = jQuery(this).getRowData(id);
var rowCount = $("#cart").getGridParam("reccount") + 1;
if (celValue.menge_min == celValue.menge_max )
celValue.menge = celValue.menge_min;
var newID = id + rowCount;
jQuery("#cart").jqGrid('addRowData', newID, celValue);
}
}
I see that your problem is accessing to the content of menge_max in menge_min column inside of custom_func callback.
You current code set menge_min and menge_max to the values from the last row of the grid. The loop inside of gridComplete overwrite the value of the variables.
I would recommend you don't start editing mode for multiple rows. It generates many problems. For example the rows could be modified, but not saved. The sorting and filtering (searching) are blocked in the grid. One can use the possibility only after saving of changes in all rows. Typically one implement starting of editing on click on any cell of the grid. The user sees the starting of editing and he/she can press Enter to save the data. Between editing the rows the user can sort or filter the grid (you can add call of filterToolbar method to create the filter bar). In the way the user can easy find the requested row of data and then edit the data.
The version 4.5.1, which you currently use, is very old. It was published about 3 years ago. You have no callbacks with exception custom_func, which you can use and because you edit multiple rows at the same time.
The simplest way to solve your problems would be updating to free jqGrid 4.13.0. Free jqGrid is the fork of jqGrid which I develop starting with the end 2014. It have enchantment which you need. I implemented alternative way to specify custom validation. Instead of usage custom: true, custom_func: checkInterval one can use custom: checkInterval instead (custom property is not Boolean, but callback function instead). In the case the custom callback would get one parameter options with many properties which you can use. The properties are described here (see the comment for additional information). You could need options.rowid and to use var item = $(this).jqGrid("getLocalRow", options.rowid); to get full item of data where item.menge_max and item.menge_min can be used.
Additionally I would recommend you to use addRow inside of onSelectRow or parent grid. You can use initdata option to specify the data of the grid and no rowID. The documentation of addRow described the default value of rowID incorrectly. The default value of rowID is null, even for the version 4.5.1 (see the line of code), and jqGrid generates unique rowid automatically in the case. You can then remove the code from gridComplete. If you would implement starting of inline editing on selection the rows in the grid then the user can do all what he need without any restrictions.
The last remark. If you would migrate to free jqGrid you can remove menge_min, menge_max and svrpos columns from the grid. Instead of that you can use additionalProperties: ["menge_min", "menge_max", "svrpos"]. It informs free jqGrid to save the value of the properties in the local data, but not place the information in some hidden cells of the grid (no unneeded information in the DOM of the page).
In any way you can remove unneeded sorttype: "string" and index properties from colModel.
I have applied a custom renderer for check boxes in the handsontable.My handsontable has only checkboxes in all of its row and column. The read only property will be applied for the rows specified in the settings of the handsontable, but when I click for the first time on any of the checkbox,it willa loow me to uncheck it.Once one click is done,it is making it readonly
function readonly(instance, td, row, col, prop, value, cellProperties) {
Handsontable.CheckboxCell.renderer.apply(this, arguments);
cellProperties.readOnly = true;
td.style.background = '#ECECF2';
var settings1 = {
data:$scope.secondGridData,
stretchH: 'false',
fillHandle: false,
colWidths: [85,120, 63, 63, 63,63, 63,63, 63,63, 63, 63, 63,63],
comments: true,
contextMenu: false,
className: "htCenter",
cells: function (row, col, prop) {
var cellProperties = {};
if((row>=1())){
cellProperties.renderer = readonly;
}
return cellProperties;
}
Well for one thing, your settings seem to be defined inside your renderer so probably don't do that. And you're supposed to return the td which is why your renderer isn't working. Fix those two things and it should work.
I currently have a rather big Grid and am successfully using the RowExpander plugin to display complementary informations on certain rows. My problem is that it's not all rows that contain the aforementioned complementary informations and I do not wish the RowExpander to be active nor to show it's "+" icon if a particular data store's entry is empty. I tried using the conventional "renderer" property on the RowExpander object, but it did not work.
So basically, how can you have the RowExpander's icon and double click shown and activated only if a certain data store's field != ""?
Thanks in advance! =)
EDIT: I found a solution
As e-zinc stated it, part of the solution (for me at least) was to provide a custom renderer that would check my conditional field. Here is my RowExpander:
this.rowExpander = new Ext.ux.grid.RowExpander({
tpl: ...
renderer: function(v, p, record) {
if (record.get('listeRetourChaqueJour') != "") {
p.cellAttr = 'rowspan="2"';
return '<div class="x-grid3-row-expander"></div>';
} else {
p.id = '';
return ' ';
}
},
expandOnEnter: false,
expandOnDblClick: false
});
Now, the trick here is that for this particular Grid, I chose not to allow the expandOnEnter and expanOnDblClick since the RowExpander will sometimes not be rendered. Also, the CSS class of the grid cell that will hold the "+" icon is 'x-grid3-td-expander'. This is caused by the fact that the CSS class is automatically set to x-grid3-td-[id-of-column]. So, by setting the id to '' only when I'm not rendering the rowExpander, I'm also removing the gray background of the un-rendered cells. So, no double click, no enter, no icon, no gray-background. It really becomes as if there is strictly no RowExpander involved for the columns where my data store field is empty (when I want no RowExpander).
That did the trick for me. For someone that wishes to preserve the ID of the cell, or that wishes to keep the double click and enter events working, there is nothing else to do other than extending the class I guess. Hope this can help other people stuck in the position I was!
As e-zinc stated it, part of the solution (for me at least) was to provide a custom renderer that would check my conditional field. Here is my RowExpander:
this.rowExpander = new Ext.ux.grid.RowExpander({
tpl: ...
renderer: function(v, p, record) {
if (record.get('listeRetourChaqueJour') != "") {
p.cellAttr = 'rowspan="2"';
return '<div class="x-grid3-row-expander"></div>';
} else {
p.id = '';
return ' ';
}
},
expandOnEnter: false,
expandOnDblClick: false
});
Now, the trick here is that for this particular Grid, I chose not to allow the expandOnEnter and expandOnDblClick specifically since the RowExpander will sometimes not be rendered. Also, the CSS class of the grid cell that will hold the "+" icon is 'x-grid3-td-expander'. This is caused by the fact that the CSS class is automatically set to x-grid3-td-[id-of-column]. So, by setting the id to an empty string only when I'm not rendering the rowExpander, I'm also removing the gray background of the cells that won't offer any expanding. So, no double click, no enter, no icon, no gray-background. It really becomes as if there is strictly no RowExpander involved for the columns where my data store field is empty (when I want no RowExpander).
That did the trick for me. For someone that wishes to preserve the ID of the cell, or that wishes to keep the double click and enter events working, there is nothing else to do other than extending the RowExpander class in my opinion. Of course, one could also use Ext.override(), but then all instances of RowExpander would be hit by the override.
I have the same task, there is my solution
var rowExpander = new Ext.ux.grid.RowExpander({
renderer : function(v, p, record){
return record.get('relatedPageCount') > 0 ? '<div class="x-grid3-row-expander"> </div>' : ' ';
}
});
I have overridden render method which test relatedPageCount field in store and render + or white space.
I think I've found a cleaner solution.Give me a feedback pls :)
I extend the toggleRow method of RowExpander and if I match a condition avoid to toggle the row.Otherwise the standard flow continues
Ext.create('customplugins.grid.plugin.ClickRowExpander',{
pluginId : 'rowexpander',
rowBodyTpl : new Ext.XTemplate(
'<p><b>Last Modified By:</b> {usermodify}</p>',
'<p><b>User data:</b> {userdata}</p>',
'<p><b>Correlation ID:</b> {correlationid}</p>',
'<p><b>Description:</b> {descr}</p>'
),
toggleRow : function(rowIdx, record) {
if(record.get('directory')) return false;
customplugins.grid.plugin.ClickRowExpander.prototype.toggleRow.apply(this, arguments);
}
})
This version works in Ext JS 5 and 6 (classic)
One thing is to remove the +/- icon, which can be done via grid viewConfig:
getRowClass: function (record, rowIndex, rowParams, store) {
var yourFieldofChoice = record.get('yourFieldofChoice');
if (yourFieldofChoice == null) {
return 'hide-row-expander';
}
},
Define css for hide-row-expander:
.hide-row-expander .x-grid-row-expander {
visibility: hidden;
}
Now you disable expanding on enter key ('expandOnEnter' config is no longer supported in Ext JS 6) or double click by overriding toggleRow, or if you do not wish the override you create your custom rowexpander built on existing plugin:
Ext.define('RowExpander', {
extend: 'Ext.grid.plugin.RowExpander',
alias: 'plugin.myExpander',
init: function (grid) {
var me = this;
me.grid = grid;
me.callParent(arguments);
},
requiredFields: ['yourFieldofChoice'],
hasRequiredFields: function (rec) {
var valid = false;
Ext.each(this.requiredFields, function (field) {
if (!Ext.isEmpty(rec.get(field))) {
valid = true;
}
});
return valid;
},
toggleRow: function (rowIdx, record) {
var me = this, rec;
rec = Ext.isNumeric(rowIdx)? me.view.getStore().getAt(rowIdx) : me.view.getRecord(rowIdx);
if (me.hasRequiredFields(rec)) {
me.callParent(arguments);
}
}
});
I have handled the beforeexpand event inside the listeners of Ext.ux.grid.RowExpander. beforeexpand method got the whole row data injected. Checking the data conditionally we can return true or false. If we return false it wont expand otherwise it will do.
var expander = new Ext.ux.grid.RowExpander({
tpl: '<div class="ux-row-expander"></div>',
listeners: {
beforeexpand : function(expander, record, body, rowIndex){
var gpdata = record.data.GROUP_VALUES[1].COLUMN_VALUE
if(gpdata == null){
return false;
}
else{
return true;
}
}
}
});
I have a jqGrid where I want all the rows to be in edit mode. Under certain conditions, however, I want a cell in that row to be readonly based on some condition of the row data, but I can't seem to get the grid to bend to my will (yet).
This is what I currently have.
$(grid).addRowData(...); // omitted for clarity
$(grid).jqGrid('editRow',rowid);
if (someCondition){
$(grid).setCell(rowid, 'col1', '', '', {editable: false});
}
The row is added and put into edit mode just as I want, but when it gets to the call to setCell(), it doesn't seem to affect the cell.
Any ideas what I'm doing wrong here?
The grid was already using column formatters for other columns so I decided to go that route. I couldn't get it to not change the entire column to readonly/editable using the method described by Oleg. I also decided to store readonly state as part of the grid cell value.
colModel:
{ name: 'ARNumber', width: 70, editable: false, sortable: false, formatter: 'optionalReadonlyInputCellFormatter'},
setup of my formatter/unformatter:
$.extend($.fn.fmatter, {
optionalReadonlyInputCellFormatter: formatOptionalReadonlyInputCell
});
$.extend($.fn.fmatter.optionalReadonlyInputCellFormatter, {
unformat: unformatOptionalReadonlyInputCell
});
formatter/unformatter functions:
function formatOptionalReadonlyInputCell(cellvalue, options, rowdata) {
var readonly = cellvalue === undefined;
if (readonly)
return displayARNumberInput('');
vals = cellvalue.split(",");
var cellValue = vals[0];
var readonly = !(vals[1] === undefined) || vals[1] == 1;
if (readonly) {
return displayARNumberSpan(cellValue);
}
else {
return displayARNumberInput(cellValue);
}
}
function unformatOptionalReadonlyInputCell(cellvalue, options, cellobject) {
var readonly = (cellvalue == "") ? "0" : "1";
if (readonly == "1") {
return cellvalue + "," + readonly;
}
else {
return $(cellobject).children().val() + "," + readonly;
}
}
function displayARNumberInput(value) {
var element = document.createElement("input");
element.type = "text";
element.value = value;
return element.outerHTML;
}
function displayARNumberSpan(value) {
var element = document.createElement("span");
element.innerText = value;
return element.outerHTML;
}
You should try to modify the property of the cell before you call the editRow method.
Moreover I know only editable attribute for the row which can be "1" or "0" and class "not-editable-row" also for the class. Does the editable attribute for the cell exist?
There is one way which can work. You can modify the editable attribute for the column in the colModel before the call of the editRow method and reset this to the original state after the editRow call. See jqGrid: Enable paging while converting HTML table to grid for an example how to make a dynamic modification in the colModel.
UPDATED: If you already use custom formatter, you can also include that in your code custom editing. An example you will find here Add multiple input elements in a custom edit type field. Custom formatter will be used only to display the data in the grid, but custom_element and custom_value if the row is in the editing mode.