Cell content from array of objects: Copy/save as text but display HTML

Tags: #<Tag:0x00007fc66c397cb0>

I want users to see modified HTML content (where applicable) and let them copy/save the table contents as csv in their a raw “stringified” form.

Something like this:

  1. Original data:
    [{name: 'Timmy', url: '#timmy'},{name: 'Jimmy', url: '#jimmy'}]
  2. Cell text: Timmy, Jimmy
  3. Rendered Cell: <a href="#timmy">Timmy</a>, <a href="#jimmy">Jimmy</a>

How I’ve planned it this far:

  1. http://jsfiddle.net/7a9boje3/ - The cells in the second column use a static object as a placeholder in the renderer (line 89) and is therefore just showing duplicate placeholder content at the moment.

  2. If I do nothing to modify the original array contents, every cell of the second column would look something like this: [object Object],[object Object]. You can see what that would look like by uncommenting line 35 and commenting line 36.

  3. So I figured that I could “stringify” the column data (line 28) and that seems to work up to this point. Users will see something like: Timmy, Jimmy and that is also what they get when they copy the cell contents. You can see what that looks like by commenting out the renderer at line 36 (and make sure line 35 is also commented).

  4. Then the plan was to add a custom renderer to generate HTML in its place, something like: <a href="#timmy">Timmy</a>, <a href="#jimmy">Jimmy</a> (lines 36 and 83). Custom renderer would make it work exactly as I wanted, but since I stringified the column data in the previous step, I can’t fetch all the properties I need to generate the HTML exactly as planned. I can only access the source data as a string and not in its original form, which makes it kind of difficult to then fetch the url or any other properties it may have. And again as a reminder, the renderer is currently using the same static placeholder object for all rows (line 89) just to show how I would generate the HTML if I could access the original array of objects.

    I tried a bunch of different ways to get the source data and whatnot (line 94), but it’s just giving me the stringified data.

    If I didn’t edit the column data at all (line 28) I could access the data in the renderer using instance.getSourceDataAtRow(row).people, but then you can’t copy or save the CSV with the cell contents in a stringified format…

Any ideas on how to get past this hurdle?

Hi @paakkoj

If I understand your issue well, do you want a request to add a feature to our copy-paste function?

The crux of the issue is that I can only access the original data in the renderer if I don’t edit the column data, but I need to edit the column data in order to show plain text in column 2 when it’s copied or the table is saved as csv. In the actual project column 2 is not the only column where the data comes from an array. The row count could also be in the thousands.

Worst case scenario: If data for ever single column came from arrays and I just slapped a renderer there without modifying the data beforehand, the user would basically get an empty/useless csv file on export or when copying

To me modifying the data on copy or when saving the csv file seems a little too cumbersome. That would be the absolute last resort as far as I’m concerned.

When I started making this table, I kind of assumed I could replace the column text/contents and still access the original data in the renderer. To me the easiest solution would probably be an ability to set the column text/contents in a way that doesn’t mess with the data (Perhaps in a separate property, like columns: [{text: ''}]) or an additional getter method for the renderer to access the original data. These solutions would be ideal for me.

That said, I’m not exactly sure why getData and getSourceData return effectively the same exact thing. My assumption was that “data” would give me the cell contents and “sourceData” would give me the original source of the data, but I guess not. Maybe if there was a method called getSourceOfSourceDataOfCell() :thinking::wink:

Is there no way to do what I want in the current version?

I missed this last night, but even though I mangle the cell data beforehand and getSourceDataAtCell inside the custom renderer returns a string, getSourceDataAtRow method actually still has the data untouched. It’s just slightly trickier to access.

I ended up doing it like this:

 // To get the data for the current row
 var rowData = instance.getSourceDataAtRow( row, col );
 // To get the key. Unfortunately I need to convert it back to camelcase
 var colKey = _.camelCase( instance.getColHeader( col ) );
 // Now I can access cell data that is not mangled
 var cellData = rowData[ colKey ];

New Jsfiddle: http://jsfiddle.net/2e4L1as7/

A gif where I show the html content placed by the custom renderer and the plain text cell contents. Should be noted that these cells can’t be edited.
handsontable-array-to-plaintext-custom-renderer-html

The question is solved unless you have better ways to do it.

Hi @paakkoj

If you need to edit the cell you have to create a custom editor.

At first glance, this method may look at the inconsistency. However, getSourceDataAtRow returns a single row of the data (array or object, depending on what data format you use) - https://handsontable.com/docs/7.4.2/Core.html#getSourceDataAtRow. And getSourceDataAtCell returns a single value from the data source - https://handsontable.com/docs/7.4.2/Core.html#getSourceDataAtCell.
So I think that these differences between them are quite well documented.

Most importantly, it seems that your solution is clear and properly.

Hi @paakkoj

please let me know if you need any further investigation on that issue or it is solved by Piotr’s tips.

Yea, I pretty much figured it out in my last comment.

I did realize though that I still needed to add toPhysicalRow() and toPhysicalColumn() methods to make sure the right data gets fetched when the rows are filtered: jsfiddle.

The important lines in the jsfiddle are:

  • Line 34, where I basically first break the column/cell data in order to make the column/cell contents plain text. The way I wanted to do this is pretty much an edge case, I think, but this could’ve been avoided if we could set the column/cell text in a different property called text or something, rather than having to forcefully replace data.
  • Line 40, where I then define which custom renderer to use to transform the data into html
  • Line 107, where I use the roundabout way to fetch the column/cell data, because the code in line 34 destroyed the data.

I also wrapped it inside a function:

I couldn’t figure out how else to get the column key, so I’m just using the column header, but because I already converted it using lodash’s _.startCase( header ), I have to convert it back to camelcase to get the actual key back in the variable colKey.

function getTableData( instance, row, col ) {

  const physicalRow = instance.toPhysicalRow( row );
  const physicalCol = instance.toPhysicalColumn( col );
  const rowData = instance.getSourceDataAtRow( physicalRow, physicalCol );
  const colKey = _.camelCase( instance.getColHeader( physicalCol ) ); 
  const cellData = rowData[ colKey ];

  return {
    row: rowData,
    cell: cellData,
    key: colKey,
  }

}

…which I can plop it into the renderer function:

function( instance, td, row, col, prop, value, cellProperties ) {
  var tdData = getTableData( instance, row, col );
  console.log( tdData.cell );   
}

Great. Thank sounds like a good plan.
I just want to add that to make links unable in all cases I recommend adding target="_BLANK". In some cases, your links won’t open without it.