GrapesJS / mjml

Newsletter Builder with MJML components in GrapesJS
http://grapesjs.com/demo-mjml.html
BSD 3-Clause "New" or "Revised" License
636 stars 227 forks source link

Adding tables? #8

Closed ErnestsMillers closed 7 years ago

ErnestsMillers commented 7 years ago

This is not really an issue. I am trying to do it myself, but is it possible to get mjml-table support anywhere soon? It's quite confusing because of td and tr. There really is no documentation about creating blocks for grapesjs-mjml and I am quite stuck when it comes to getting to understand all the conversion between html and mjml.

Please, that would be great help. If I make this work myself I will make pull request. :)

artf commented 7 years ago

Hi @ErnestsMillers honestly I don't think I'm gonna support tables soon. The best I can do for now is to tell you to check https://github.com/artf/grapesjs-mjml/blob/master/src/components.js this is where mjml components are created. Try to see how other components are implemented, but it's not too much different from defining a new Component, you just have to extend coreMjmlModel and coreMjmlView

ErnestsMillers commented 7 years ago

@artf I have been trying to add tables functionality for days but without any success. First of all I don't understand why tr's and td's just gets deleted in the view. The result is <table>Test</table>. Adding blocks is easy but when it comes to components.js I can't figure out how to properly render things. I am posting my code below and if I can get any additional guidelines it would be a lifesaver.

I want tables to be added within mjml-column. All the elements in the mjml-column view are added as tr element. If I want a table would it mean doing something like this? tr>td>table(the actual table starts here?)>tr's>td's.

If anyone can help me with this I would be extremely thankful.

:)

blocks.js

bm.add('mj-table', {
    label: 'Table',
    content: `<mj-table><tr><td>Test</td></tr></mj-table>`,
    attributes: {
      'data-type': 'mj-table',
    },
  });

  bm.add('tr', {
    label: 'Tr',
    content: `<tr><td>Test</td></tr>`,
    attributes: {
      'data-type': 'tr',
    },
  });

   bm.add('td', {
    label: 'Td',
    content: `<td>Test</td>`,
    attributes: {
      'data-type': 'td',
    },
  });

components.js

// mj-table
  domc.addType('Table', {
    model: defaultModel.extend(Object.assign({}, coreMjmlModel, {
      defaults: Object.assign({}, defaultModel.prototype.defaults, {
        'custom-name': 'Table',
        draggable: '[data-type=mj-column]',
        highlightable: true,
        stylable: [
          // Dimension
         'width'
          // Typography
          // Decorations
        ],
        style: {
          'width': '100%'
        },
      }),
    }), {
      isComponent(el) {
        if (el.tagName == 'MJ-TABLE') {
          return {
            type: 'Table',
          };
        }
      },
    }),
    view: defaultView.extend(Object.assign({}, coreMjmlView, {
      tagName: 'table',

      attributes: {
        style: 'pointer-events: all; display: table; width: 100%',
      },

    })),
  });

  // tr
  domc.addType('Tr', {
    model: defaultModel.extend(Object.assign({}, coreMjmlModel, {
      defaults: Object.assign({}, defaultModel.prototype.defaults, {
        'custom-name': 'Tr',
        draggable: '[data-type=mj-table]',
        highlightable: true,
        stylable: [
          // Dimension
          // Typography
          // Decorations
        ],
        style: {
        },
      }),
    }), {
      isComponent(el) {
        if (el.tagName == 'tr') {
          return { 
            type: 'tr',
          };
        }
      },
    }),
    view: defaultView,
  });

   // td
  domc.addType('Td', {
    model: defaultModel.extend(Object.assign({}, coreMjmlModel, {
      defaults: Object.assign({}, defaultModel.prototype.defaults, {
        'custom-name': 'Tr',
        draggable: '[data-type=tr]',
        highlightable: true,
        stylable: [
          // Dimension
          // Typography
          // Decorations
        ],
        style: {
        },
      }),
    }), {
      isComponent(el) {
        if (el.tagName == 'td') {
          return {
            type: 'td',
          };
        }
      },
    }),
    view: defaultView,
  });
artf commented 7 years ago

@ErnestsMillers just by quick looking I've seen this:

domc.addType('Td', { // <-- declaring `Td`
...
     return {
            type: 'td', // returning `td`... so you just don't get this type
          };
// same for tr
ErnestsMillers commented 7 years ago

Hi, @artf. Thank you, I noticed it some time ago and already fixed it. The main issue I am having, I don't understand where the tr, td disappears from markup. Mjml-table works together with standard tr's and td's. But somehow they disappear both in view code and when I inspect editor. You can see in blocks.js The way I add block to the editor.

In the following pictures table block is added with following markup:

<mj-table><tr style="border-bottom:1px solid #ecedee;text-align:left;padding:15px 0;">
              <th style="padding: 0 15px 0 0;">Year</th>
              <th style="padding: 0 15px;">Language</th>
              <th style="padding: 0 0 0 15px;">Inspired from</th>
            </tr>
            <tr>
              <td style="padding: 0 15px 0 0;">1995</td>
              <td style="padding: 0 15px;">PHP</td>
              <td style="padding: 0 0 0 15px;">C, Shell Unix</td>
            </tr>
            <tr>
              <td style="padding: 0 15px 0 0;">1995</td>
              <td style="padding: 0 15px;">JavaScript</td>
              <td style="padding: 0 0 0 15px;">Scheme, Self</td>
            </tr> </mj-table>

Imgur Imgur

I believe the best approach is to place the table element in tr as any other element, then making a td and then table which will contain the actual tr's td's for table.

I really love GrapesJS but I think I am missing something/can't figure out your code. I hope you can help me out with this one, if I figure out the way components work, I surely will add others for you to pull as my project will involve a lot of work with grapesjs-mjml. :)

blocks.js is the same. components.js looks like this:

// mj-table
  domc.addType('mj-table', {
    model: defaultModel.extend(Object.assign({}, coreMjmlModel, {
      defaults: Object.assign({}, defaultModel.prototype.defaults, {
        'custom-name': 'Table',
        draggable: '[data-type=mj-column]',
        highlightable: true,
        stylable: [
        ' height', 'font-style', 'font-size', 'font-weight', 'font-family', 'color',
          // Dimension
         'width'
          // Typography
          // Decorations
        ],
        style: {
          'width': '100%',
          'font-size': '14px'
        },
      }),
    }), {
      isComponent(el) {
        if (el.tagName == 'MJ-TABLE') {
          return {
            type: 'mj-table',
          };
        }
      },
    }),
    view: defaultView.extend(Object.assign({}, coreMjmlView, {
      tagName: 'tr',

      attributes: {
        style: 'pointer-events: all; display: table; width: 100%; user-select: none;',
        'data-type': 'mj-table',
      },

      getChildrenSelector() {
        return 'td > table';
      },

      getMjmlTemplate() {
        return {
          start: `<mjml><mj-body><mj-column>`,
          end: `</mj-column></mj-body></mjml>`,
        };
      },
    })),
  });

  // tr
  domc.addType('tr', {
    model: defaultModel.extend(Object.assign({}, coreMjmlModel, {
      defaults: Object.assign({}, defaultModel.prototype.defaults, {
        'custom-name': 'Tr',
        draggable: '[data-type=mj-table]',
        highlightable: true,
        stylable: [
          // Dimension
          // Typography
          // Decorations
        ],
        style: {
          'width': '100%'
        },
      }),
    }), {
      isComponent(el) {
        if (el.tagName == 'TR') {
          return { 
            type: 'tr',
          };
        }
      },
    }),
    view: defaultView.extend(Object.assign({}, coreMjmlView, {
      tagName: 'tr',
      attributes: {
        'data-type': 'tr',
      },
    })),
  });

   // td
  domc.addType('td', {
    model: defaultModel.extend(Object.assign({}, coreMjmlModel, {
      defaults: Object.assign({}, defaultModel.prototype.defaults, {
        'custom-name': 'Td',
        draggable: '[data-type=tr]',
        highlightable: true,
        stylable: [
          // Dimension
          // Typography
          // Decorations
        ],
        style: {
          'width': '100%'
        },
      }),
    }), {
      isComponent(el) {
        if (el.tagName == 'TD') {
          return {
            type: 'td',
          };
        }
      },
    }),
    view: defaultView.extend(Object.assign({}, coreMjmlView, {
      tagName: 'td',

      attributes: {
        'data-type': 'td',
      },
    })),
  });
artf commented 7 years ago

I'll try to add it by myself and will let you know

NorthstarTech commented 6 years ago

Was a fix found for this issue? Since it is closed.

sachinbhise76 commented 5 years ago

What is changes in @ErnestsMillers mentioned code. Please post code

Sibbir7350 commented 5 years ago

@artf is it fixed ??

jmadureira commented 5 years ago

@ErnestsMillers I don't know if you are still interested in this issue but I noticed that the reason that <tr> and <td> aren't appearing is because they aren't being parsed at all. If you noticed the calls to the isComponent those tags are never checked. My guess is that the issue may be related with grapesjs itself.

jcolla-holla commented 3 years ago

Were you ever to get this to work successfully @ErnestsMillers ?

mykolakecha commented 3 years ago

Are there any updates regarding the issue? I've spend 2 days trying to get this working, but no luck for now...

emilsedgh commented 3 years ago

The reason this doesn't work is this:

Grapesjs uses browsers internal html parser in order to parse an html to a tree.

However, for browsers, mj-table is not a valid element. Thus, they wont allow tr to be inside an mj-table.

The root cause is similar to #149. And the solution for both would be Grapesjs either using a different dom parser (eg DOMParser api) which would properly parse non-html code like mjml, or at least making it possible to use a different parser so plugins like grapesjs-mjml can do that on their own.

artf commented 3 years ago

@emilsedgh yeah it's correct but, you are actually already able to change the HTML parser type on init (here the default config).

grapesjs.init({
 // ...
 parser: { htmlType: 'text/xml' } // text/xml won't change the HTML 
})

One thing worth noting, we have to update isComponentType in mjml components, with XML parser, tagName is not uppercased.