Grapesjs: [QUESTION] control table cell (add row, column..)

Created on 5 Dec 2018  路  11Comments  路  Source: artf/grapesjs

Hello. I want to dev some of feature to control the table.

There are many feature for table the other editor like add columns, rows, set color, reset column width for each cell...

I just have a dev plan to implementation on below..

1. add a button on block manager

bm.add('table-block', {
  id: 'table',
  label: 'Table',
  category: 'Basic',
  attributes: { class: 'fa fa-table' },
  content: `
      <table class="table table-striped table-bordered table-resizable">
          <tr><td></td><td></td><td></td></tr>
          <tr><td></td><td></td><td></td></tr>
          <tr><td></td><td></td><td></td></tr>
      </table>
    `,
});

this is a dummy table 3x3. each cell have a text component.

2018-12-05 17-11-35 2018-12-05 17_12_04

2. customize toolbar on cell

{
    defaults: {
      ...Component.prototype.defaults,
      type: 'cell',
      tagName: 'td',
      draggable: ['tr'],
      toolbar: [
        {
          attributes: { class: "fa fa-arrows" },
          command: "tlb-move"
        },
        {
          attributes: { class: "fa fa-flag" },
          command: "table-insert-row-above"
        }
      ]
    },
  },

add a custom command to insert row above.

3. Add a command in order to insert a row

run(editor, sender, opts) {
  var domComponents = editor.DomComponents;

  domComponents.addComponent({
      tagName: 'table',
      removable: true, // Can't remove it
      draggable: true, // Can't move it
      copyable: true, // Disable copy/past
      style: { background: 'red'},
      attributes: { title: 'here' },
      content: `<tr data-gjs-type="row" data-highlightable="1">
         <td data-gjs-type="cell" data-highlightable="1" style="min-width: 100px;"><div data-gjs-type="text" data-highlightable="1"></div></td>
    });
}

2018-12-05 17-14-10 2018-12-05 17_16_29

But the problem is...

  1. Cannot detect the cell's location. above code just add on canvas below. not inside cell.
  2. Cannot add multiple components into the cell. It just added only one depth. but need to add muitiple depth. (tr > td * 3)

Anyone have a nice idea? I cannot find a awesome plugin.

Have a nice day.

Most helpful comment

Hey, @cnaa97 No worries I have added the default commands in the toolbar. I got what I wanted.
Thanks for helping.

Below is the code

        const blockManager = this._editor.BlockManager;
        blockManager.add('table-block', {
          id: 'table',
          label: 'Table',
          category: 'Basic',
          attributes: { class: 'fa fa-table' },
          content: `
              <table class="table  table-bordered table-resizable">
                  <tr><td></td><td></td><td></td></tr>
                  <tr><td></td><td></td><td></td></tr>
                  <tr><td></td><td></td><td></td></tr>
              </table>
            `,
        });
        const TOOLBAR_CELL = [
          {
            attributes: { class: "fa fa-arrows" },
            command: "tlb-move"
          },
          {
            attributes: { class: "fa fa-flag" },
            command: "table-insert-row-above"
          },

          {
            attributes: {class: 'fa fa-clone'},
            command: 'tlb-clone',
          },
          {
            attributes: {class: 'fa fa-trash-o'},
            command: 'tlb-delete',
          }
        ];
        const getCellToolbar = () => TOOLBAR_CELL;


        const components = this._editor.DomComponents;
        const text = components.getType('text');
        components.addType('cell', {
          model: text.model.extend({
            defaults: Object.assign({}, text.model.prototype.defaults, {
              type: 'cell',
              tagName: 'td',
              draggable: ['tr'],

            }),
          },

            {
              isComponent(el) {
                let result;
                const tag = el.tagName;
                if (tag == 'TD' || tag == 'TH') {
                  result = {
                    type: 'cell',
                    tagName: tag.toLowerCase()
                  };
                }
                return result;
              }
            }),
          view: text.view,
        });



        this._editor.on('component:selected', m => {
          const compType = m.get('type');
          switch (compType) {
            case 'cell':
              m.set('toolbar', getCellToolbar()); // set a toolbars
          }
        });



        this._editor.Commands.add('table-insert-row-above', editor => {
          const selected = editor.getSelected();

          if (selected.is('cell')) {
            const rowComponent = selected.parent();
            const rowIndex = rowComponent.collection.indexOf(rowComponent);
            const cells = rowComponent.components().length;
            const rowContainer = rowComponent.parent();

            rowContainer.components().add({
              type: 'row',
              components: [...Array(cells).keys()].map(i => ({
                type: 'cell',
                content: 'New Cell',
              }))
            }, { at: rowIndex });
          }
        });

All 11 comments

  1. The content inside the component definition is for static text (eg. the content is used for editing with Rich Text Editor) so you can't select its element once added, instead you should use components: '<tr data-gjs-type="row" ...'
  2. You can always get the selected Component and use its API, so your table-insert-row-above could be:
editor.Commands.add('table-insert-row-above', editor => {
  const selected = editor.getSelected();

  if (selected.is('cell')) {
      const rowComponent = selected.parent();
      const rowIndex = rowComponent.collection.indexOf(rowComponent);
      const cells = rowComponent.components().length;
      const rowContainer = rowComponent.parent();

      rowContainer.components().add({
        type: 'row',
        components: [ ...Array(cells).keys() ].map(i => ({
          type: 'cell',
          content: 'New Cell',
        }))
      }, { at: rowIndex });
  }
});

Thanks for your kindness answer.

Hey, @cnaa97 @artf please help me with adding columns command or duplicate column because using toolbar removes all default command.

@shubh9415 It seems necessary to update a toolbar after command execution, duplication or adding columns. I handled to add a component:select event listener.

editor.on('component:selected', m => {
  const compType = m.get('type');
  switch (compType) {
    case 'cell':
        m.set('toolbar', getCellToolbar(editor)); // set a toolbars
   }
  ...
}

Hey, @cnaa97 thanks, that was really quick response. I am getting an error with getCellToolbar() as if it not a default function. Please help me with this below is my code .

 const components = this._editor.DomComponents;
        const text = components.getType('text');
        components.addType('cell', {
          model: text.model.extend({
            defaults: Object.assign({}, text.model.prototype.defaults, {
              type: 'cell',
              tagName: 'td',
              draggable: ['tr'],

              toolbar: [
                {
                  attributes: { class: "fa fa-arrows" },
                  command: "tlb-move"
                },
                {
                  attributes: { class: "fa fa-flag" },
                  command: "table-insert-row-above"
                },
                {
                  attributes:{class:"fa fa-clone"},
                  command:"duplicate_column"
                }
              ]
            }),
          },

            {
              isComponent(el) {
                let result;
                const tag = el.tagName;
                if (tag == 'TD' || tag == 'TH') {
                  result = {
                    type: 'cell',
                    tagName: tag.toLowerCase()
                  };
                }
                return result;
              }
            }),
          view: text.view,
        });
        this._editor.Commands.add('table-insert-row-above', editor => {
          const selected = editor.getSelected();

          if (selected.is('cell')) {
            const rowComponent = selected.parent();
            const rowIndex = rowComponent.collection.indexOf(rowComponent);
            const cells = rowComponent.components().length;
            const rowContainer = rowComponent.parent();

            rowContainer.components().add({
              type: 'row',
              components: [...Array(cells).keys()].map(i => ({
                type: 'cell',
                content: 'New Cell',
              }))
            }, { at: rowIndex });
          }
        });

I thought I could add a custom command to add columns also.

@shubh9415 Opps! getCellToolbar is my custom function.
In your code, you can use like this...

const TOOLBAR_CELL = [
                {
                  attributes: { class: "fa fa-arrows" },
                  command: "tlb-move"
                },
                {
                  attributes: { class: "fa fa-flag" },
                  command: "table-insert-row-above"
                },
                {
                  attributes:{class:"fa fa-clone"},
                  command:"duplicate_column"
                }
];

const getCellToolbar = () => TOOLBAR_CELL;

this._editor.on('component:selected', m => {
  const compType = m.get('type');
  switch (compType) {
    case 'cell':
        m.set('toolbar', getCellToolbar()); // set a toolbars
   }
  ...
}

Hey, @cnaa97 thanks, that was my mistake but I wanted to know how we can duplicate column in the table. I was trying with the "duplicate_column" command but doesn't seem to work. Do you know any workaround?

Is there a way we can add additional icons in the toolbar. adding using a function also removes the default commands. If I don't add toolbar there is a duplicate icon in the table cells

Hey, @cnaa97 thanks, that was my mistake but I wanted to know how we can duplicate column in the table. I was trying with the "duplicate_column" command but doesn't seem to work. Do you know any workaround?

@shubh9415 How about this approach?

In duplicate_column command...

  rowContainer
    .components()
    .add(
      { type: 'row', components: [...rowComponent.components().models.map((cell) => {
         return {
           type: cell.get('type'),
          content: cell.get('content'),
         }
      })] },
      { at: rowIndex }

Hey, @cnaa97 thanks, the duplicate functionality is working but it is adding a tr and not td. Also, I know I would be asking for too much but if you anyhow have implemented delete function also? I just wanted to add extra functionality to the existing toolbar but that doesn't seem to be working right now. Please forgive me I just started using it.

Hey, @cnaa97 No worries I have added the default commands in the toolbar. I got what I wanted.
Thanks for helping.

Below is the code

        const blockManager = this._editor.BlockManager;
        blockManager.add('table-block', {
          id: 'table',
          label: 'Table',
          category: 'Basic',
          attributes: { class: 'fa fa-table' },
          content: `
              <table class="table  table-bordered table-resizable">
                  <tr><td></td><td></td><td></td></tr>
                  <tr><td></td><td></td><td></td></tr>
                  <tr><td></td><td></td><td></td></tr>
              </table>
            `,
        });
        const TOOLBAR_CELL = [
          {
            attributes: { class: "fa fa-arrows" },
            command: "tlb-move"
          },
          {
            attributes: { class: "fa fa-flag" },
            command: "table-insert-row-above"
          },

          {
            attributes: {class: 'fa fa-clone'},
            command: 'tlb-clone',
          },
          {
            attributes: {class: 'fa fa-trash-o'},
            command: 'tlb-delete',
          }
        ];
        const getCellToolbar = () => TOOLBAR_CELL;


        const components = this._editor.DomComponents;
        const text = components.getType('text');
        components.addType('cell', {
          model: text.model.extend({
            defaults: Object.assign({}, text.model.prototype.defaults, {
              type: 'cell',
              tagName: 'td',
              draggable: ['tr'],

            }),
          },

            {
              isComponent(el) {
                let result;
                const tag = el.tagName;
                if (tag == 'TD' || tag == 'TH') {
                  result = {
                    type: 'cell',
                    tagName: tag.toLowerCase()
                  };
                }
                return result;
              }
            }),
          view: text.view,
        });



        this._editor.on('component:selected', m => {
          const compType = m.get('type');
          switch (compType) {
            case 'cell':
              m.set('toolbar', getCellToolbar()); // set a toolbars
          }
        });



        this._editor.Commands.add('table-insert-row-above', editor => {
          const selected = editor.getSelected();

          if (selected.is('cell')) {
            const rowComponent = selected.parent();
            const rowIndex = rowComponent.collection.indexOf(rowComponent);
            const cells = rowComponent.components().length;
            const rowContainer = rowComponent.parent();

            rowContainer.components().add({
              type: 'row',
              components: [...Array(cells).keys()].map(i => ({
                type: 'cell',
                content: 'New Cell',
              }))
            }, { at: rowIndex });
          }
        });
Was this page helpful?
0 / 5 - 0 ratings

Related issues

kawika-connell picture kawika-connell  路  3Comments

crazyxhz picture crazyxhz  路  3Comments

desilvaNSP picture desilvaNSP  路  3Comments

ryandeba picture ryandeba  路  3Comments

faizansaiyed picture faizansaiyed  路  3Comments