HandsonTable not rendering all rows - handsontable

HandsonTable is not rendering all rows - it loads only part of all rows. But when I do Ctrl+A and paste into Excel I see all the rows. Why is Handsontable not displaying all the rows?
<hot-table col-headers="true" row-headers="true" datarows="data" context-menu width="1080">
<hot-column ng-repeat="column in columns" data="{{column.data}}"></hot-column>
</hot-table>

To render all rows, just set renderAllRows: true

The current answer does not answer the original question.
Handsontable does not render all cells at once because it is designed to be efficient for very large data sets. It does this using virtual rendering, dynamically modifying the DOM to include only the cells at the scroll position.
The rows virtual rendering can be disabled by setting renderAllRows: true, as described in the docs: "If typed true then virtual rendering mechanism for handsontable will be disabled." Although it will then not be as efficient for large data sets.
You can also change the number of pre-rendered rows and columns instead of rendering them all. From the performance tips,
You can explicitly specify the number of rows and columns to be rendered outside of the visible part of the table. In some cases you can achieve better results by setting a lower number (as less elements get rendered), but sometimes setting a larger number may also work well (as less operations are being made on each scroll event). Tweaking these settings and finding the sweet spot may improve the feeling of your Handsontable implementation.
This is done by setting viewportRowRenderingOffset and viewportColumnRenderingOffset in the handsontable options. These are by default set to auto which lets handsontable try to find the best value, but may be provided an integer value (e.g. viewportRowRenderingOffset: 70, viewportColumnRenderingOffset: 70).

I had the same problem (using HandsOnTable 6.2.1 and the old AngularJS) and customers would start complaining about not being sure if they were at the end of the table or not.
I was able to create two buttons linked to the functions 'scrollToBeginning' and 'scrollToEnd'. This way the user is sure to be at the last line. Three things specific about my answer:
I expose the functions to the DOM using $scope;
I have an object 'goToLine' holding 3 properties (scrollingToEnd: boolean, row: number, col: number), it is used in other functions not posted here;
I have a list of ID referencing HandsOnTable objects stored in $scope.hots.
Here is my raw solution, feel free to adapt / enhance:
$scope.stopScrollingToEnd = function () {
$scope.goToLine.scrollingToEnd = false;
};
$scope.scrollToBeginning = function (goToLine) {
$scope.stopScrollingToEnd();
const hot = $scope.hots[goToLine.id];
hot.scrollViewportTo(0, 0);
};
/**
* Scroll to the end of the List Element.
* We need this functionality because of a bug in HandsOnTable related to its Virtualization process.
* In some cases (complex table), when manually scrolling, the max row is wrong, hence causing major confusion for the user.
* #param {*} goToLine
* #returns
*/
$scope.scrollToEnd = function (goToLine) {
// We scroll to the first line before going to the last to avoid the bug and being sure we get to the last line
$scope.scrollToBeginning(goToLine);
const hot = $scope.hots[goToLine.id];
var numberOfRows = hot.countRows();
// This variable is used to repeat the scrollViewportTo command.
// It is built using the length of `numberOfRows`.
var repeat = numberOfRows ? 1 * Math.ceil(Math.log10(numberOfRows + 1)) : 1;
// Used in other goTo function to avoid conflict.
$scope.goToLine.scrollingToEnd = true;
// FIXME : not supposed to call scrollViewportTo several times... => fixed in recent versions of HandsOnTable ?
for (let n = 0; n < repeat; n++) {
if (!$scope.goToLine.scrollingToEnd) {
return;
}
setTimeout(function () {
if (!$scope.goToLine.scrollingToEnd) {
return;
}
hot.scrollViewportTo(numberOfRows - 1, 0);
}, 500);
}
};

Related

Access html-element from grid in loadComplete

So what I'm trying to accomplish is when the grid is fully loaded, I loop over a certain column that contains checkboxes. Depending on the value of the checkbox I should be able to disable it.
Problem is that I can't access the html element that's there. Am i doing something wrong or overlooking something?
What i've tried:
loadComplete: function() {
// Fetch all the ID's of the rows
var rows = $("#table").getDataIDs();
// Loop over the rows
if(rows.length != 0){
for(i=0; i < rows.length; i++) {
// Get the data so we test on a certain condition
var row = $("#table").jqGrid("getRowData", rows[i]);
if (row.gridCheckbox == 1) {
//disable the element
row.prop("disabled", "disabled");
}
}
}
}
It's important to understand that changing one element on the page follow in the most cases to web browser reflow: validation whether some property (position for example) need be changed in all other elements on the page. If you do changes in the loop then your JavaScript code can be really slow.
Thus it's strictly recommended to reduce the number of changes of the DOM. Especially to reduce the number of changes jqGrid provides rowattr, cellattr and custom formatters. If you need for example to set disabled attribute on some rows then you should now do this in loadComplete, but to use rowattr instead to inform jqGrid that some additional attributes (disabled="disabled") should be set on some rows. jqGrid collect first the string representation of the whole table body and it use one assignment of innerHTML to fill the whole body of the grid in one DOM operation. It improves essentially the performance. See code example in the old answer.

jqGrid: update a row AND have its formatters updated as well

Ideally I would call setRowData and have my cell formatters and rowattr function re-run, but cell formatters and rowattr functions don't re-run when calling setRowData (I don't know why but that's another question maybe), so setRowData isn't really helpful for me.
It seems the next easiest thing to do would be to remove a row and re-add a new one at the same position with the same model. To do that I need to get the rowid of the row above the selected row so that I can call addRowData and specify the ID of the row above in srcrowid and use 'after' for the position. This is what I'm thinking:
$.jgrid.extend({
updateRow: function(rowid, model){
// get index from id
var index = this.jqGrid('getInd', rowid);
// note: my first row's index is 1 (is that normal?)
if ( index == 1 ){
position = 'first';
srcrowid = 'n/a';
}
else{
position = 'after';
srcrowid = _____ how to get rowid of row above selected row _____???
}
// delete row
this.jqGrid('delRowData', rowid);
// insert at index
this.jqGrid('addRowData', rowid, model.attributes, position, srcrowid);
}
});
How can I get the rowid of the row above the selected row? (Is there an easier way? Is this a bad strategy?)
Note: I'm using backbone.js collections and models
I find the best way to change the row is to use setRowData instead of usage delRowData and addRowData. If you know rowid then you can use $("#" + rowid); (or if rowis have special characters like :, . an so on then $("#" + $.jgrid.jqID(rowid));) to get the <tr> element. Then you can use jQuery.addClass, jQuery.css, jQuery.attr to change the attributes of the row.
It's important to understand that jqGrid uses the same methods internally it it's required to modify element of the grid. The main goal of rowattr is another one. During filling of the grid data there are many scenarios. One can create DOM elements for <td> and <td> and insert there in the grid. The main problem is performance in case of working with DOM. It's much slowly as building of strings. Moreover DOM is even much more slowly if the elements are exist on the the HTML page (in opposite to disconnected elements). If one modify one element only from the grid having 500 rows then the position of elements of all other rows need be recalculated.
Because of the problem jqGrid construct the whole body of the grid first in string format and then assign all <tr> and <td> elements using one set of innerHTML. It improves dramatically the performance of filling of the grid. See the answer for additional information. The formatters and callbacks cellattr and rowattr are introduced to allow to customize cell and row attributes during building of grid body in string format. It gives you customization possibilities without reducing of performance.
On the other side if you need to modify the row which are attached already on HTML page then you will have no advantage with working with strings instead of DOM. Because of that I recommend you just use jQuery.addClass, jQuery.css, jQuery.attr directly. If you need to change multiple classes, assign multiple css rules or multiple attributes then you should use one call of above functions. You can use object form of jQuery.css, jQuery.attr for it.
The updateRow extension below works, BUT I ended up not using it. Not so much for the reasons Oleg talked about (which I assume are valid and something you should definitely consider), but because I had a filter provider that was too difficult to sync with (e.g. after soft deleting a row, I need to now determine if new deleted status agrees with current filter...and that's a pretty simple example). So I just defer to the data the filter provider gives me and repopulate the grid each time, which I'm not fond of, but I don't see other easy options.
As far as the extension below goes, here are pros/cons as I see them:
Pros:
easy to use
You just rely on the rowattr functions and cell formatters you already defined. You don't have to write those twice.
Cons:
possibly performance--see Oleg's answer
please add any you see
Unknowns:
performance? I have no idea how much worse it performs. It would be interesting to do benchmarks with different browsers. I saw no problems, but I only have maybe 20 rows. Let's say we're working with 500 rows and adding/removing classes and calling .css() took 30 ms but using the extension took 300 ms on a 'typical' machine/browser. That would be 10 times slower, but for me it would probably be worth it because I don't have to write something twice.
here's the extension:
$.jgrid.extend({
updateRow: function(rowid, data){
// get index from id
var index = this.jqGrid('getInd', rowid);
// note: my first row's index is 1 (is that normal?)
if ( index == 1 ){
position = 'first';
}
else{
position = 'after';
srcrowid = $(this).find('tr#' + rowid).prev()[0].id;
}
// delete row
this.jqGrid('delRowData', rowid);
// insert at index
this.jqGrid('addRowData', rowid, data, position, srcrowid);
}
});

Hiding columns of handsontable from javascript

Is there any way i can hide HOT columns from javascript?
The requirement is such that the column to hide will come as a parameter in javascript and based on that the respective column will show hide accordingly.
The HOT has rowHeaders and colHeaders and the data with 20 columns.
Please advise.
OUTDATED SOLUTION
Ok I founnd a possible solution. I tested it out on my own system but it's actually quite simple.
You should be using a customRenderer in your columns option. Read up about this if you aren't already. The idea is that you're giving each cell its own renderer. In this custom function, you can do something like this:
var colsToHide = [3,4,6]; // hide the fourth, fifth, and seventh columns
function getCustomRenderer() {
return function(instance, td, row, col, prop, value, cellProperties) {
if (colsToHide.indexOf(col) > -1) {
td.hidden = true;
} else {
td.hidden = false;
}
}
}
What this renderer does is hide the cells that the var colsToHide specify. All you do now is add a DOM element that lets the user pick which and so every time the table gets rendered (which happens basically after any change, or manually triggered need be), the cells in the columns specified will be hidden, keeping the data array intact like you described. And when not in colsToHide they are re-rendered so make sure you get that working as well.
Here I implemented it with very basic functionality. Just enter the index of a column into the input fields and watch the magic happen.
http://jsfiddle.net/zekedroid/LkLkd405/2/
Better Solution: handsontable: hide some columns without changing data array/object

Programatically updating underlying data in Slickgrid

I have 6 textboxes at the top of the screen that update an entire column(one textbox per column) based on any changes. I was selecting the columns based on their class (.l#). Here is the code (issues to follow):
function UpdateField() {
var ctrl = this;
var id = parseInt(ctrl.id.replace("item", ""), 10) - 1;
var bound = [".l1", ".l7", ".l8", ".l9"];
var fields = $(bound[id]);
for (var i = 0; i < fields.length; i++)
{
fields[i].innerHTML = $(ctrl).val();
}
};
which is bound to the keyup event for the text areas. Issues are:
1) initially fields.length was -1 as I didn't want to put data in the "add new
row" section at the bottom. However, when running it, I noticed the
final "real" record wasn't being populated. Also, when stepping through, I
noticed that the "new row" field was before the "last row" field.
2) when doing it this way, it is purely superficial: if I double click the field,
the real data hasn't been changed.
so in the grand scheme of things, I know that I was doing it wrong. I'm assuming it involves updating the data and then forcing a render, but I'm not certain.
Figured out how to do it. Modified the original code this way:
function UpdateField() {
var ctrl = this;
var id = parseInt(ctrl.id.replace("item", ""), 10) - 1;
var bound = ['title1', 'title2', 'title3', 'title4'];
var field = bound[id];
for (var i = 0; i < dataView.getLength(); i++)
{
var item = dataView.getItem(i);
item[field] = $(ctrl).val();
dataView.updateItem(i, item);
}
grid.invalidate();
};
I have 6 textboxes (item1-item6) that "bind" to fields in the sense that if I change data in a textbox, it updates all of the rows and any new rows added also have this data.
Parts where the two issues can be explained this way:
1) to work around that, though still it would be a presentational fix and not a real updating of the underlying data, one could force it to ignore if it had the active class attached. Extra work, and not in the "real" direction one is going for (masking the field).
2) It was pretty obvious with the original implementation (though it was all I could figure out via Chrome Dev Tools that I could modify at the time) that it was merely updating a div's content and not actually interacting with the data underneath. Would look nice, and perhaps one could just pull data from the item1-item6 boxes in place of the column if it is submitted, but if someone attempts to modify the cell, they'll be looking at the real data again.

Restyling dynamically styled SlickGrid cells after Sort

Ok, let me explain my scenario more clearly:
When a cell is edited, it becomes 'dirty' and I style it a certain way by adding a CSS class to the cell via javascript.
Then, if the user Sorts the grid, the styling is lost (I believe because all the rows are recreated) and I need a way to restore the styling to the appropriate cell/row after a Sort.
What I attempted to do is add an entry into data[] called 'status' and onCellChange I loop through data[] and match the args.item.Id to appropriate entry in data[].
grid.onCellChange.subscribe(function (e, args) {
var done = false;
for (var i = 0; i < data.length && !done; i++) {
if (data[i].id == args.item.id) {
data[i].status = "dirty";
done = true;
}
}
}
However, onSort I'm not sure how to match the sorted rows to the data array. (Since I have no args.item) I've attempted to do selector statements:
$(".slick-row")
to restyle the correct cells, but I have no way to associate the rows with their entry in data[].
1) There is no need to search for the item in your onCellChange handler - it is available at "args.item".
2) Sorting the "data" array will not wipe out your change to the item in #1.
3) You mention dynamically styling cells. I see no code for that. If your custom formatter is the piece of code that looks at "item.status" and renders it differently if it is dirty, then you don't have to do anything extra. Sorting the data and telling the grid to re-render will preserve the "dirty" cell styles.

Resources