loading...
Cover image for Kendo Inline Editor for TributeJS

Kendo Inline Editor for TributeJS

unitehenry profile image Henry Unite Updated on ・3 min read

Introduction

So for one of my striven internship projects, I was tasked with working on supporting mentions (@unitehenry ) with the pre-existing WYSIWYG editor being that we currently use. I was then introduced to the proprietary editor created by Telerik called Kendo UI.

After some research, I decided to use tributejs to implement the new mention support. But I then found myself running into problems when implementing the package with our current editor…

The problem

The Kendo UI editor uses iframe for its WYSIWYG widget and TributeJS does not support iframe based editors, as mentioned in their documentation.

We also see a couple of areas for improving compatibility with different rendering situations, such as in iframes inside of rich text editors.

Inline Editor

Well that’s fine… Kendo UI Editor has inline support, so surely a contenteditable div will suffice.

As you can see, the Kendo UI Inline Editor works as a contenteditable div for tribute to utilize, but it comes at the cost of a floating toolbar with WYSIWYG options.

Fixing the Toolbar

The following code shows how the editors toolbar can be permanently shown, undraggable, and positioned relative to the elements on the body.

//jQuery (kendo ships with jQuery)
const $ = kendo.jQuery;

// Show the toolbar
this.$refs.editor.kendoWidget().toolbar.show();
// Add Active Class to Editor

this.$refs.editor.kendoWidget().body.classList.add('k-state-active');

// Disable Hide Toolbar Event
$(this.$refs.editor.kendoWidget().body).off("focusout.kendoEditor");

// Disabe Drag Handle
$('.k-editortoolbar-dragHandle').hide();

// Relative Position Toolbar
$('div.k-editor-widget').css('position', 'inherit')

// Attach the element to a relative div
const toolbar = $('div.k-editor-widget').detach()
$('.toolbar').append(toolbar);
.k-window.k-editor-widget{
  box-shadow: none!important;
  -webkit-box-shadow:none!important;
  border:1px solid #ddd;
}
<div class="editor">
     <div class="toolbar" /> <!-- Toolbar placeholder div -->
     <div class="kendo-editor" /> <!-- Kendo Editor Widget -->
</div>

Attaching Tribute

Great! We have out inline editor and now we can bind tribute to the element, right?

Tribute Implementation

// TributeJS Options
tributeOptions: {
  // Open Menu on Trigger Character
  trigger: '@',
  // Initial Values
  values: [],
  // Replacement Template
  selectTemplate: function (item) {
    const { value } = item.original;
    return `<span class="mention" data-mention="${value}"">${value}</span>`;
  },
  // Menu Item Template
  menuItemTemplate: function (item) {
    const { avatar, value } = item.original;
    return `<span class="menu-item">${ avatar ? `<img src="${avatar}" />` : '' }<p>${value}</p></span>`
  },
  // Filtering
  lookup: 'value',
  fillAttr: 'value',
  allowSpaces: true
}

// Create Tribute Instance
this.tribute = new Tribute(this.tributeOptions);

// Attach Tribute Instance to the Editor
this.tribute.attach(editor.body);

Editor Error

So we have tribute attached, whenever we are adding a mention to the editor, the range looses track of which offset contains which node. We need to add an event listener for whenever a "tribute" is selected.

We need to provide a unique way for this event to replace the innerHTML of the editor with. I used a mention's unique value and replaced any spaces while converting the string to all lowercase.

// Change this in your tributeOptions
selectTemplate: function (item) {
  const { value } = item.original;
  return `@${value.replace(' ', '').toLowerCase()}`;
}

Now we need an event listener to give a set of instructions for the editor to construct an element and replace that unique key with.

editor.body.addEventListener('tribute-replaced', function(e) {
  // Get Mention Data
  const { value } = e.detail.item.original

  // Copy HTML content for mutation
  let content = editor.body.innerHTML;

  // Construct Mention
  const mention = `<span class="mention new-mention" contenteditable="false" data-mention="${value}">${value}</span>`;

  // Replace tribute replacement with mention HTML
  content = content.replace('@'+value.replace(' ', '').toLowerCase(), mention);

  // Set content with mutated HTML
  editor.body.innerHTML = content;

  // Get the newly added mention
  const newMention = $('.new-mention')[0];
  const range = editor.getRange();

  // Set the editors range to select the new node (for editing)
  range.selectNode(newMention);

  // Set Cursor to End
  range.collapse();

  // Remove the newly created mention indentifying class
  newMention.classList.remove('new-mention');
})

The Result

Final Product

You now have an inline editor that doesn't have a floating / disappearing toolbar and supports mentions through TributeJS with a contenteditable div.

Posted on by:

unitehenry profile

Henry Unite

@unitehenry

software engineer at striven

Discussion

markdown guide