React-native-render-html: Support <table> tag

Created on 2 Nov 2017  路  61Comments  路  Source: meliorence/react-native-render-html

Hello,

Does it support <table> tag ?

Thank you,

EDIT: solution here

enhancement FAQ candidate help wanted new feature

Most helpful comment

:tada: Good news everyone ; I've been working on this all the day through. @native-html/table-plugin released to npm, with support for onLinkPress and autoheight! :tada:

EDIT: The plugin has been renamed from react-native-render-hmlt-table-bridge to @native-html/table-plugin

All 61 comments

Hi, not at the moment.
This is a good subject of pull request. We would have to add a table render to HTMLRenderers.js.

I think it shouldn't be too hard to have something working, even if it's not perfect.

I can have an initial go at this @Exilz if you'd be happy to review and tidy up my code? We need it for our app so it only makes sense to contribute back.

@wayferer Yes, I'd be happy to review and improve it. However, I don't have much time right now, but I'll definitely look into it once I can.

No worries. I'm jumping around a bit so it might take a while :(

just found that renderers cannot find table in simple example:

123<p/><table><tr><td>111</td></tr></table><p/>345

added debug output to renderRNElements <- cannot find table also

D/ReactNativeJS( 5058): TEST: rawtext [123
D/ReactNativeJS( 5058): TEST: p [no_data
D/ReactNativeJS( 5058): TEST: p [no_data
D/ReactNativeJS( 5058): TEST: rawtext [345
D/ReactNativeJS( 5058): TEST: p [no_data

@inv2004 remove table from the ignored tags array in here

@Exilz I removed the table related tags in ignored tags array.Now it's displaying like text not like the table, for Example, see below code.

screen shot 2018-02-15 at 11 36 27 am

output

Firstname
Lastname
age
Jill
smith50

What should i do for rendering the table

@GopiKrishna10 that's the point of the issue, it has no default renderer that handles <table>.
I told inv2004 to remove it from the ignored array so he can start working on making a renderer.

Ok Thanks @Exilz for your response

@Exilz @wayferer Does anyone know if any progress has been made regarding the table tag?

Not on my end, however, I'm still open to review a PR regarding this feature.

@Exilz PR?

@Reier360 Pull request 馃槃

Oh wow, my bad. Sorry I have never contributed to GitHub so I am not really sure how these things work. I will go read up on the process and see if I can maybe have a swing at it. Thanks.

I don鈥檛 want to discourage anyone, but the table layout algorithm in HTML and CSS is extremely complex. And since Yoga only uses the Flexbox layout algorithm which lays out content in one direction (vs tables which auto-layout in 2 directions), mimicking a tiny subset of it will be quite hard.

Basically you would need to make all cells equal-width so that you can mimick columns.

// equal-width cells, yay!
const defaultCellStyle = {
  flexBasis: 0,
  flexGrow: 1
}

Optionally you could look for a width attribute with a percentage value (old-school, I know) on the top cells and use that as the width for all cells in the corresponding column. That might be doable.

But be careful if your use case is showing tables made for displaying on the Web (especially on larger screens) and edited with a JS WYSIWYG editor: your chances of porting that to React Native elements are roughly zero. ;)

Just want to say thanks so much for this library....

I currently use this in my app on production, "bSteem" - see here for more details (https://busy.org/@janicehung/bsteem-for-ios-and-android-newest-mobile-steemit-app)

Would be really helpful to have tables support, I understand its complex to parse out tables so I might try to fork this and submit when I have time

@jm90m Hi, I also needed the table functionality, I never tried to get tables worked and It would be fantastic if someone can get it working but in my case I just used react natives webview. It has fantastic html render functionalities, and it was pretty decent when it came to rendering.

There are basically two ways to add this feature.

The "proper" one would be to code a renderer that actually uses the content parsed from the HTML and renderers rows and columns with react-native's layout. As fvsch pointed out earlier, this is quite a complex task. You don't realize how much work is put into rendering HTML inside your browser until you try to wrap your mind around the algorithms that are at work just to render a simple page.

A basic implementation should be doable, but I have neither the time nor the use for it right now.

The second solution would be to render a WebView, just like we're rendering <iframe> tags. The performance and user experience would be far worse than a proper native implementation, but this should be fairly easy to do. I guess you could use the staticContentMaxWidth prop to render your iframe, and use horizontal scrolling to display all of your table if it's too big.

This isn't on top of my priority list for this plugin, but if someone were to submit a pull request for this feature, I'd gladly review it.

I've managed to get something working quickly. Unfortunately, it's not a custom renderer, and it's not optimal. I'd prefer a version with nodes alteration, but if you want a quick solution here it is.

I've used a regex that matches all <table>, then replaces the <table> with an <iframe srcdoc> before they're parsed by react-native-render-html.

render() {
  const { html, ...props } = this.props;

  let content = html;
  const tables = html.match(/(<table(?:.|\n)*?<\/table>)/g);

  tables.map((table) => {
    content = content.replace(table, `<iframe srcdoc="${tableStyle + table}"></iframe>`);
  });

  return (
    <HTML
      html={content}
      {...props}
    />
  );
}

@charpeni Thanks for this. I'm working on a similar workaround. What does your 'tableStyle' variable look like?

@charpeni awesome, workaround, I'll have to try this!

Also require this, thanks for the workaround.

Was able to override IGNORED_TAGS https://github.com/archriss/react-native-render-html/blob/master/src/HTMLUtils.js#L44 and made it to the <table> renderer.

import _ from 'lodash'
import HTML from 'react-native-render-html'
import { IGNORED_TAGS } from 'react-native-render-html/src/HTMLUtils'

const tags = _.without(IGNORED_TAGS, 
    'table', 'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr'
)

const renderers = {
    table: (htmlAttribs, children, convertedCSSStyles, passProps) => { ... },
    caption: (htmlAttribs, children, convertedCSSStyles, passProps) => { ... },
    col: (htmlAttribs, children, convertedCSSStyles, passProps) => { ... },
    colgroup: (htmlAttribs, children, convertedCSSStyles, passProps) => { ... },
    tbody: (htmlAttribs, children, convertedCSSStyles, passProps) => { ... },
    td: (htmlAttribs, children, convertedCSSStyles, passProps) => { ... },
    tfoot: (htmlAttribs, children, convertedCSSStyles, passProps) => { ... },
    th: (htmlAttribs, children, convertedCSSStyles, passProps) => { ... },
    thead: (htmlAttribs, children, convertedCSSStyles, passProps) => { ... },
    tr: (htmlAttribs, children, convertedCSSStyles, passProps) => { ... },
}

<HTML ...   ignoredTags={tags} renderers={renderers} />

Now looking for some alternatives to WebView approach, like maybe try and build custom table rendered using react-native-table-component. Anyone aware of existing solutions like this?

@charpeni this regex is not working for me. :(
/(<table(?:.|\n)*?<\/table>)/g

Has anyone got a solution for #187, which would avoid the need for the [forbidden art] of parsing html with regular expressions?

@tclarke-scottlogic by looking at the source code, it looks like we don't have a DOM at this point and all children are already pre-rendered React components. Doubtfully anything could be done here with existing renderers implementation.

I had a __ very simple __ tables, and managed to render as on exampe above
https://github.com/archriss/react-native-render-html/issues/43#issuecomment-414986278

So in my case each <tr> was a <View> styled as flexbox row (flexDirection: 'row',)
and <td> was rendered as just a regular <View></View>

import _ from 'lodash'
import HTML from 'react-native-render-html'
import { IGNORED_TAGS } from 'react-native-render-html/src/HTMLUtils'

const tags = _.without(IGNORED_TAGS, 
    'table', 'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr'
)

const tableDefaultStyle = {
  flex: 1,
  justifyContent: 'flex-start',
}

const tableColumnStyle = {
  ...tableDefaultStyle,
  flexDirection: 'column',
  alignItems: 'stretch'
}

const tableRowStyle = {
  ...tableDefaultStyle,
  flexDirection: 'row',
  alignItems: 'stretch'
}

const tdStyle = {
  ...tableDefaultStyle,
  padding: 2
}

const thStyle = {
  ...tdStyle,
  backgroundColor: '#CCCCCC',
  alignItems: 'center',
}

const renderers = {
    table: (x, c) => <View style={tableColumnStyle}>{c}</View>,
    col: (x, c) => <View style={tableColumnStyle}>{c}</View>,
    colgroup: (x, c) => <View style={tableRowStyle}>{c}</View>,
    tbody: (x, c) => <View style={tableColumnStyle}>{c}</View>,
    tfoot: (x, c) => <View style={tableRowStyle}>{c}</View>,
    th: (x, c) => <View style={thStyle}>{c}</View>,
    thead: (x, c) => <View style={tableRowStyle}>{c}</View>,
    caption: (x, c) => <View style={tableColumnStyle}>{c}</View>,
    tr: (x, c) => <View style={tableRowStyle}>{c}</View>
    td: (x, c) => <View style={tdStyle}>{c}</View>
  }


<HTML ...   ignoredTags={tags} renderers={renderers} />

Note that this approach doesn't renders the table, its rather a workaround to get the data inside tables visible and MAY work only for rendering of very simple tables:

  • No cells width / height is supported
  • No collumn span or row span supported
  • Cells dimentions are not aligned according to the content amount in them.
  • Columns are not actually columns, so cells are not alligned with their colegues from other rows
  • Cells borders are possible, but tricky
  • Table styling isn't trivial task

Getting the tables rendered properly is hard, and WEB browsers actually do a quite sofisticated job on rendering the table layouts.

This flexbox workaround doesn't covers all the tricky cases, primerraly because flexbox does alignment in single dimention (either horisontal or vertical), and for a table you need to do that in two dimentions simutanously (imagine a difference between CSS flexbox vs CSS grids).

React Native supports a quite limited variation of CSS flexbox and it is not possible to do anything like CSS grids here, so not even sure if reliable tables implementation is possible to add in observable future.

I'll give this a go, thanks. Problem is our table has about 8 columns, most of which are wide, so the non-alignment is an issue.
So I was thinking of just passing the table HTML. Not a problem, I'll just need to find a good solution for pulling out the table elements, before rendering, so I can pass them into a separate WebView or other component.

The problem with tables that has a lot of columns is that those are not responsive and look ugly on mobile in portrait orientation. In that case maybe it worth to just show the cells as a stack of blocks.

For example, render each <td> and <tr> as a regular unstyled <View> element, which should make the content responsive. In that case, there will be no table but just a flat list of paragraphs with text.

@webdevbyjoss Can't see that being accepted by the client. It'll be impossible to actually comprehend the content.
Throwing the table into a webview makes the most sense for their needs.

For others to use, this produces a reasonably good quality table, with rows/columns aligned, and that you don't have to give a fixed height to get it to display.

Obviously you may need to adjust to taste, and it's very limited. Thanks to @webdevbyjoss for the base to work off!

            renderers={{
              table: (htmlAttribs, children, style, passProps) => {
                //return makeWebView(++x, item, content);

                return (
                  <ScrollView key={`table${++x}`} horizontal={true}>
                    {children}
                  </ScrollView>
                );
              },
              tr: (html, children, style, passProps) => {
                return (
                  <View
                    key={`tr${++x}`}
                    style={{
                      flex: 1,
                      flexDirection: "row",
                      borderBottomWidth: 1,
                      borderColor: colors.grey6
                    }}
                  >
                    {children}
                  </View>
                );
              },
              th: (attribs, children, style, passProps) => {
                let rowspan = 1;
                if (attribs.colspan) {
                  rowspan = parseInt(attribs.colspan, 10);
                }
                return (
                  <View
                    key={`th${++x}`}
                    style={{
                      width: 200 * rowspan,
                      backgroundColor: productColor(item.product).darker,
                      borderLeftColor: colors.white,
                      borderLeftWidth: passProps.nodeIndex === 0 ? 0 : 1,
                      padding: 6
                    }}
                  >
                    <Text style={{ color: colors.white, fontWeight: "bold" }}>
                      {children}
                    </Text>
                  </View>
                );
              },
              td: (attribs, children, style, passProps) => {
                let rowspan = 1;
                if (attribs.rowspan) {
                  rowspan = parseInt(attribs.rowspan, 10);
                }
                return (
                  <View
                    key={`td${++x}`}
                    style={{
                      width: 200 * rowspan,
                      borderColor: colors.grey6,
                      borderRightWidth: 1,
                      borderLeftWidth: passProps.nodeIndex === 0 ? 1 : 0,
                      padding: 6
                    }}
                  >
                    <Text>{children}</Text>
                  </View>
                );
              }
            }}

Looking into borders, is there a good way to tell if my \

@tclarke-scottlogic the way I managed to prevent double borders is to add top and left borders for <th> & <td> elements and then bottom and right borders to <tr> elements.

@webdevbyjoss Surely the top of the \

I ended up using "passProps.nodeIndex === 0" to support, but your way might be cleaner code...

@webdevbyjoss Any suggestion for how to resolve a "rowspan" tag? Crossing the DOM boundary seems very unlikely to be stable.

yeah, rowspan is unlikely to be doable universally using flexbox styling.
All we can do so far is to upvote this feature request
https://react-native.canny.io/feature-requests/p/css-grid-layout-supporting

Thanks, at least I've updated my snippet above to support colspan and have single px borders

This is a way to use onParsed help with handling row spans. Basically, it detects rowspans and injects additional cells that match the spanning row.

function rowspanParser(dom, elements) {
  const tables = elements.filter(n => n.tagName === "table");
  if (!tables.length) return elements;
  tables.forEach(table => {
    const tbody = table.children.find(n => n.tagName === "tbody");
    if (!tbody) return;

    const rowspanColumns = {};
    for (let i = 0; i < tbody.children.length; ++i) {
      const tr = tbody.children[i];

      const rowspanners = tr.children.filter(n =>
        Object.keys(n.attribs).includes("rowspan")
      );

      const rowspanKeys = Object.keys(rowspanColumns);

      for (let k = 0; k < rowspanKeys.length; ++k) {
        const key = rowspanKeys[k];
        rowspanColumns[key].count = rowspanColumns[key].count - 1;
        if (rowspanColumns[key].count > 0) {
          const column = rowspanColumns[key].column;
          const index = rowspanColumns[key].index;
          tr.children.splice(index, 0, { ...column });
        } else {
          delete rowspanColumns[key];
        }
      }

      if (rowspanners.length > 0) {
        rowspanners.forEach(n => {
          rowspanColumns["val" + n.nodeIndex] = {
            column: n,
            index: n.nodeIndex,
            count: parseInt(n.attribs.rowspan, 10)
          };
        });
      }
    }
  });

  return elements;
}

Remove all tables tags from IGNORED_TAGS in HTMLUtils.js and
add this generateDefaultBlockStyles in HTMLDefaultStyles.js

table:{
          flex:1,
          flexDirection:'row',
          borderLeftWidth:1,
          borderTopWidth:1,
          alignItems:'center',
          justifyContent:'center',
          borderColor:'#ccc',
          borderRightWidth:0.5,
        },
        tbody:{
        },
        tr:{
          flex:1,
          borderBottomWidth:1,
          flexDirection:'row',
          borderBottomColor:'#ccc'
        },
        td:{
          justifyContent:'flex-start',
          width:'50%',
          paddingHorizontal:12,
          paddingVertical:5,
          borderRightWidth:0.5,
          borderRightColor:'#ccc',
          alignItems:'flex-start'
        },

only for 2 colums tables.

@kartavyaparekh96 the example listed few messages earlier is more general, and works in all cases, including the 2 columns. With flexbox style rule flex: 1 you don't need to set width:'50%', all cells will be evenly distributed, and it will automatically set 50% width for tables with 2 columns.

Hi all.
I managed to create a bridge with react-native-webview It's not optimal, since inner nodes are not natives. An other limitation is that its styles are pure CSS, there is no easy bridge with RN styling.
But at least it's fully compliant with HTML table layout algorithm. Video extract.

EDIT: released to npm react-native-render-html-table-bridge

@jsamr Can you provide an example of that solution being used? I'm trying to implement it myself and having issues with implementing it in my <HTML /> .

@nvonbenken I'll work on an example repo next week and share it here.

@jsamr

Any update on this? I'd really like to see how you accomplished this in an example.

I was able to use @jsamr stuff:

!!!! Download his files (link above) !!!!

Imports:

import HTML from 'react-native-render-html';
import { renderers } from './renderers';
import { alterNode } from './alter-node';
import { IGNORED_TAGS } from 'react-native-render-html/src/HTMLUtils';

Constants

const tags = _.without(IGNORED_TAGS, 'table', 'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr');

render()

<HTML
  html={htmlText}
  ignoredTags={tags}
  alterNode={alterNode}
  renderers={renderers}
/>

I don麓t know why but everything I put AFTER (BELLOW) the HTML tag just don麓t render... Any ideas?

@zehgreven I embedded the table in a modal so I didn't see this bug comming. I guess you're experiencing react-native-community/react-native-webview#101!

@nvonbenken OK I'll work on something before the end of the week!

@zehgreven I embedded the table in a modal so I didn't see this bug comming. I guess you're experiencing react-native-community/react-native-webview#101!

I actly found that my <ScrollView> is not growing as much as it should :X

If i find a work arround I let you guys know.

@zehgreven I embedded the table in a modal so I didn't see this bug comming. I guess you're experiencing react-native-community/react-native-webview#101!

Yep! You麓re totally correct... That麓s what happened... But once I fixed it this "glitch" the table just don麓t show anymore, I think that I still need to find a way to grow up the screen height so it麓ll have extra space to show up the table...

I let you know if I fix it.

@jsamr @zehgreven

Is this working with Expo for you? In setting it up as above I'm getting this:

Invariant Violation: requireNativeComponent: "RNCWebView" was not found in the UIManager

I actly wasn麓t able to make it work properly, still deciding if i麓ll try another soluction...

@zehgreven Writing the bridge-library right now ; to circumvent the issue and avoid native dependency, I'll make WebView component an argument to the main function.

:tada: Good news everyone ; I've been working on this all the day through. @native-html/table-plugin released to npm, with support for onLinkPress and autoheight! :tada:

EDIT: The plugin has been renamed from react-native-render-hmlt-table-bridge to @native-html/table-plugin

@nvonbenken How is the released library working for you with Expo ?
@zehgreven would be happy if you could test
@Exilz I think you can close the issue

@jsamr Same issue as above, Invariant Violation: requireNativeComponent: "RNCWebView" was not found in the UIManager

@nvonbenken Did you follow the steps in the readme ? If you are using Expo SDK ≤ 32, you must provide WebView component from react-native.

import { WebView } from 'react-native'

EDIT Feel free to write me a personal message (email address in profile) and I'll be happy to help.

@jsamr Ahh good call, missed that piece. Had referenced WebView from react-native-webview by mistake. Looks good now!

Thanks a lot for making this!

@nvonbenken I'll try to make the readme more Expo-friendly!

@zehgreven would be happy if you could test

Tested and looks like it works.

I麓ll try some more weird test cases as soon as I can.

Here is my code working just fine using your bridge.

/* eslint-disable no-undef */
import React, { PureComponent } from 'react';
import { Linking, Dimensions, View } from 'react-native';
import HTML from 'react-native-render-html';
import WebView from 'react-native-webview';
import { IGNORED_TAGS, alterNode, makeTableRenderer } from 'react-native-render-html-table-bridge';
import MJax from './MJax';
import { Fonts, Colors } from '../../Themes';

export default class HtmlView extends PureComponent {
  render() {
    const { html, baseFontStyle = {}, containerStyle = {} } = this.props;

    const config = {
      WebViewComponent: WebView,
      tableStyleSpecs: {
        trOddBackground: '#FFFFFF',
        trEvenBackground: '#FFFFFF',
      },
    };

    const renderers = {
      table: makeTableRenderer(config),
      mathjax: (attribs, _children, _css, { key }) => (<MJax key={key} rawHtml={attribs.rawHtml} />),
    };

    const htmlConfig = {
      alterNode,
      renderers,
      ignoredTags: IGNORED_TAGS,
    };

    return (
      <View style={{ flex: 1, opacity: 0.99 }}>
        <HTML
          html={html}
          allowFontScaling
          baseFontStyle={{
            ...Fonts.style.small,
            ...Fonts.helpers.textLeft,
            ...Colors.from(Colors.black),
            ...baseFontStyle,
          }}
          containerStyle={[
            containerStyle,
          ]}
          imagesMaxWidth={Dimensions.get('window').width}
          onLinkPress={(event, href) => {
            Linking.openURL(href);
          }}
          {...htmlConfig}
        />
      </View>
    );
  }
}

Did any one get any way to support

tag

@kartavyaparekh96
Remove all tables tags from IGNORED_TAGS in HTMLUtils.js and
add this generateDefaultBlockStyles in HTMLDefaultStyles.js

table:{
          flex:1,
          flexDirection:'row',
          borderLeftWidth:1,
          borderTopWidth:1,
          alignItems:'center',
          justifyContent:'center',
          borderColor:'#ccc',
          borderRightWidth:0.5,
        },
        tbody:{
        },
        tr:{
          flex:1,
          borderBottomWidth:1,
          flexDirection:'row',
          borderBottomColor:'#ccc'
        },
        td:{
          justifyContent:'flex-start',
          width:'50%',
          paddingHorizontal:12,
          paddingVertical:5,
          borderRightWidth:0.5,
          borderRightColor:'#ccc',
          alignItems:'flex-start'
        },

only for 2 colums tables.

I used the solution of styles of @kartavyaparekh96, but I didn't modify the dependency files.
I used the properties tagsStyles and ignoredTags, to pass constants defined with the settings for the component properties.

const IGNORED_TAGS = ['head', 'scripts', 'audio', 'video', 'track', 'embed', 'object', 'param', 'source', 'canvas', 'noscript',
    'caption', 'col', 'colgroup', 'button', 'datalist', 'fieldset', 'form', 'input',  'label',  'legend',  'meter', 'optgroup',
    'option', 'output', 'progress', 'select', 'textarea', 'details', 'diaglog', 'menu', 'menuitem', 'summary', 'tfoot', 'th', 'thead', ];

const TAG_STYLE = {
table:{
  flex:1,
  flexDirection:'row',
  borderLeftWidth:1,
  borderTopWidth:1,
  alignItems:'center',
  justifyContent:'center',
  borderColor:'#ccc',
  borderRightWidth:0.5,
},
tbody:{
},
tr:{
  flex:1,
  borderBottomWidth:1,
  flexDirection:'row',
  borderBottomColor:'#ccc'
},
td:{
   justifyContent:'flex-start',
   width:'50%',
   paddingHorizontal:12,
   paddingVertical:5,
   borderRightWidth:0.5,
   borderRightColor:'#ccc',
   alignItems:'flex-start'
},
}```
```html
<HTML                
html={table}
tagsStyles={TAG_STYLE}
ignoredTags={IGNORED_TAGS}
imagesMaxWidth={Dimensions.get('window').width}
/>

Again these solution works for two column tables as mentioned

@charpeni this regex is not working for me. :(
/(<table(?:.|\n)*?<\/table>)/g

I had to change the \n to \r\n to get it work.

I am trying to figure out the tablestyle part that he added now. Anyone know what goes here?

I ended up just using the amazing code from https://github.com/archriss/react-native-render-html/issues/43#issuecomment-487731497. Thank you!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

duansiyu picture duansiyu  路  4Comments

diamanthaxhimusa picture diamanthaxhimusa  路  7Comments

f-roland picture f-roland  路  3Comments

sayem314 picture sayem314  路  6Comments

xipgroc picture xipgroc  路  6Comments