Table Zebra Style (even/odd)

Hi there, First of all, thank you very much for your work. I’m very happy to be using Prosemirror, it’s helping me a lot with my Editor. I am currently working on editing tables within the Editor. I managed to add some new cellAttributes in order to apply certain styles in table_cell.toDOM function. That works like a charm.

Now, I would need to apply a zebra style to my tr but I do not find the place where to go through the table nodes and apply this style (css, classes,…).

I was wondering if, instead of applying the style within the toDom() method, I could include in the document some external css styles scripts affecting the whole html. My goal is to be able to copy the table in my editor and paste it somewhere else keeping the style.

I hope the issue is clear enough.

Thanks a lot in advance

Have a nice day

tr:nth-child(even) {
    background: #f8fafc;
}

tr:nth-child(odd) {
    background: #fff;
}

toDOM is context-less, and would not be a place where you can do this. What about the CSS you posted is not working?

Hey, thanks marijn for the quick response. The css works within the editor (editor.component.scss), but if I copy the table from the editor and paste it in Outlook, Microsoft Word, etc. The style is lost. The only way I manage to “export” styles is applying them (inline css) in the toDom() methods Something like:

table.toDOM = () => {

    const styles: Partial<CSSStyleDeclaration> = {
      borderSpacing: '0',
      width: '100%'
    };

    const style = toStyleString(styles) || null;
    return ['table', { style }, ['tbody', 0]];
  }

I did not find a way to get the styles from my the css files.

I appreciate your help

Just in case it’s handy

export let basicTableNodes = tableNodes({
  tableGroup: 'block',
  cellContent: 'block+',
  cellAttributes: {
    cellStyle: {
      default: null,
      getFromDOM(dom: any) {
        return dom.cellStyle;
      },
      setDOMAttr(value, attrs) {
        if (value)
          attrs.cellStyle = (attrs.cellStyle || '') + value;
      }
    },
    className: {
      default: '',
      getFromDOM(dom: any) {
        return dom.className;
      },
      setDOMAttr(value, attrs) {
        if (value)
          attrs.className = (attrs.className || '') + value;
      }
    },
  }
});
 basicTableNodes.table_cell.toDOM = (node: ProseMirrorNode) => {

    const { colspan, colwidth, rowspan, cellStyle, className } = node.attrs;

    const styles: Partial<CSSStyleDeclaration> = {
      borderBottom: '1px solid rgba(0, 0, 0, 0.1)',
      padding: '8px',
      font: css.td.font
    };

    if (className.indexOf('footer') > -1) {
      styles.backgroundColor = '#FFFFFF';
      styles.borderBottom = 'none';
    } else if (className === 'highlight') {
      styles.backgroundColor = '#F1F4F8';
      styles.borderBottom = '1px solid #96A5B1';
      styles.fontWeight = '600';

      /** @todo: paragraph must be bold */

    }

    /** @todo: odd/even - zebra style */

    const style = toStyleString(styles) || null;

    return ['td', { style, colspan, 'data-colwidth': colwidth, rowspan, cellStyle, class: className }, 0];
  }

The toDOM function does not know anything about the document position of the node (and nodes won’t be re-rendered when siblings are added or removed above them), so that won’t work. The only thing you could do is to maintain node decorations for the rows that have to be styled.

Great, It sounds good. I will need to get familiar with node decorations, never used them.

Thank you very much for your time. I’ll start digging a bit to find the way.

:slight_smile:

Hi again, I have created a plugin to handle the table zebra style

const zebraDecoration = (): Plugin => {
  return new Plugin({
    props: {
      decorations(state) {

        const { doc } = state;
        const decorations = [];

        /** In order to calculate the zebra elements */
        let index = 0;

        /** Goes through all the nodes */
        state.doc.nodesBetween(0, doc.nodeSize - 2, (node, position) => {

          /** For each table, we initialize the index */
          if (node.type.name === 'table') {
            index = 0;
          }

          /** We increase the index every row which contains simple cells */
          else if (node.type.name === 'table_row' && node.firstChild.type.name === 'table_cell'
            && !node.firstChild.attrs.className) {
            index++;
            const backgroundColor = index % 2 === 0 ? '#f8fafc' : '#ffffff';
            node.attrs.background = backgroundColor;
            decorations.push(Decoration.node(position, position + node.nodeSize, { style: 'background:' + backgroundColor }));
          }
        });

        return DecorationSet.create(state.doc, decorations);
      }
    }
  })
}

I also had to modify my schema in order to keep the background while mapping to dom (copy to clipboard in my case)

basicTableNodes.table_row.attrs = {
    background: {
      default: null
    }
  };

  basicTableNodes.table_row.parseDOM = [
    {
      tag: 'tr',
      getAttrs(dom: HTMLElement): Record<string, any> {
        const background = dom.getAttribute('background');
        return {
          background
        };
      }
    }
  ];

  basicTableNodes.table_row.toDOM = (trNode: ProseMirrorNode) => {

    const { background } = trNode.attrs;
    const styles: Partial<CSSStyleDeclaration> = {
      background
    };

    const style = toStyleString(styles) || null;


    return ['tr', { style, background }, 0];
  }

It works as expected :slight_smile:

Thank you very much again for your tips. Best

1 Like

Hi :slight_smile:

I might be a bit late to the party, but can’t you just style every nth row in a different color? (maybe only the td selector was missing in your first example?)

tr:nth-child(2n+1) td {
        background-color: red;
}

(the +1 in case you have a header row - but that could also be restricted to tbody)

Kindly

Frederik

Hi @frederik , Thanks a lot for your remarks.

Indeed the original css works fine within the editor. Yours is even a bit better as it does not consider the headers :wink:

But the issue came while copy / pasting to another tool: Gmail, Outlook… the style is lost as it’s not part of the dom.

I was wondering if it’s possible to add some css style block code to the html, something like:

<style type="text/css">
  body {background-color: powderblue;}
   h1   {color: blue;}
   p    {color: red;}
...
   tr:nth-child(2n+1) td {
        background-color: red;
   }

</style>

I did not find anything about this.

Have a great one!

1 Like