Issue #481Opened November 3, 2017by mathieuk17 reactions

Question: Dragging blocks into a Text block?

Question

This is not a bug but an implementation question. If this is not the right place to ask these questions, please let me know.

We're working on an implementation where we want to use GrapesJS to allow users to create an e-mail template. As part of this implementation we are working to create mail-merge functionality: we've introduced the concept op 'merge-fields' or 'placeholders' which we will replace with the proper values on the server side. This means we send over the components JSON structure and turn it into HTML server-side, replacing values as we go.

So, as an example, one of our users might enter the text: Hello <<username>> and we'll replace that merge-field <<username>> with the proper field.

But, we haven't been able to implement this this way quite yet as we're not able to drag these blocks into a Text block. We can only drag it around it. So, for now we're extending the RTE with a merge-field 'inline block' ( <input type=text readonly class=mergeField data-isMergefield=1 />) and creating a merge-field block with the same HTML in the block manager. Implementing a DomComponent type to recognize it offers a method to configure it. But it feels suboptimal, we'd really like to be able to drag that mergefield block onto the right place in the textfield.

To allow this, I imagine GrapesJS would have to be able to grab the textNode and split it in to (atleast) two textnodes and a tag for the merge-field but I'm not sure where to start with this. Could you advise as to how we might implement this?

Answers (3)

artfNovember 6, 201710 reactions

Have you checked API-Rich-Text-Editor? You can add a custom action like this

editor.RichTextEditor.add('custom-vars', {
  icon: `<select class="gjs-field">
		<option value="">- Select -</option>
        <option value="[[firstname]]">FirstName</option>
        <option value="[[lastname]]">LastName</option>
        <option value="[[age]]">Age</option>
      </select>`,
    // Bind the 'result' on 'change' listener
  event: 'change',
  result: (rte, action) => rte.insertHTML(action.btn.firstChild.value),
  // Reset the select on change
  update: (rte, action) => { action.btn.firstChild.value = "";}
})

rte-action

artfApril 10, 20194 reactions

Probably in the next release, this feature will be available. textable

So textable will be just another property, this will allow any component to be dropped inside Text components. Here is the code of the component from the example above:

// Define a component with `textable` property
editor.DomComponents.addType('var-placeholder', {
      model: {
        defaults: {
          textable: 1,
          placeholder: 'VARIABLE-1',
        },
        toHTML() {
          return `{{ ${this.get('placeholder')} }}`;
        },
      },
	  // The view below it's just an example of creating a different UX
      view: {
        tagName: 'span',
        events: {
          'change': 'updatePlh',
        },
        // Update the model once the select is changed
        updatePlh(ev) {
          this.model.set({ placeholder: ev.target.value });
          this.updateProps();
        },
        // When we blur from a TextComponent, all its children components are
        // flattened via innerHTML and parsed by the editor. So to keep the state
        // of our props in sync with the model so we need to expose props in the HTML
        updateProps() {
          const { el, model } = this;
          el.setAttribute('data-gjs-placeholder',  model.get('placeholder'));
        },
        onRender() {
          const { model, el } = this;
          const currentPlh = model.get('placeholder');
          const select = document.createElement('select');
          const options = [ 'VARIABLE-1', 'VARIABLE-2', 'VARIABLE-3' ];
          select.innerHTML = options.map(item => `<option value="${item}" ${item === currentPlh ? 'selected' : ''}>
			${item}
		  </option>`).join('');
          while (el.firstChild) el.removeChild(el.firstChild);
          el.appendChild(select);
          select.setAttribute('style', 'padding: 5px; border-radius: 3px; border: none; -webkit-appearance: none;');
          this.updateProps();
        },
      }
    });
	
	// Use the component in blocks
    editor.BlockManager.add('simple-block', {
      label: 'Textable block',
      content: { type: 'var-placeholder' },
    });
mathieukNovember 8, 20173 reactions

Yes, I've used that and I've pretty much got that working. The difference is that I am not using a text placeholder like you are. I'm actually inserting a block (with a corresponding 'type') so that I can further configure these placeholders (for instance, a field might be a Datetime field and my user may want to configure the exact output format for that datetime). I used HTML5 drag'n'drop to implement this and that works pretty nice, bút...

It feels like departing from the expected user interface. I feel I should be able to drag a mergefield from the 'blocks' and onto the right position. I've been toying around with the Sorter to allow this and I'm up to this:

image

I now have the problem that the Sorter very much wants actual blocks to align to so I have some more tweaking to do. For now implementing this has required changed in the ComponentTextView (mostly: dont clear out the toolbar for 'mergefields') and the Sorter (if I'm hovering a mergefield over a textblock, insert HTML into the activeRTE instead of appending a block).

I'm currently working on making the dragging work more reliably (for some reason, whenever I drag right the field gets appended to the textnode instead of at the cursor position, works fine when dragging left :) ), making the sorter ignore the idea of 'blocks' when within a textfield, showing the proper placeholder in that case (I'd want to see the actual cursor) and cleaning things up.

Not sure on how to approach the Sorter issue at this point, short of specialcasing textblocks so if you have any ideas in that area I'd love to hear them. You can see some of the hacky code I've made so far over at https://github.com/mathieuk/grapesjs/commit/d58c5ee5306c358cd19509f6b8affe9bb60493ed .

Related Questions and Answers

Continue research with similar issue discussions.

Paid Plugins That Match This Issue

Curated by issue keywords and label relevance to help you ship faster.

View all plugins

Loading paid plugin recommendations...

Browse Plugin Categories

Jump directly to plugin category pages on the marketplace.