Event handling for chips in rich text editor

Hi!
Trying to implement some chips functionality in text editor. But have 2 problems:
1) I insert chip programmatically via button click on that place where cursor was. For that purpose I use .getRange() with (blur) method and .selectRange() before insertion. The problem is: when I insert 2 o more chips in a row, they become nested (chip which contain next chip which contain one more chip..). Code:

onSaveSelection(): void {
this.curPos = this.editor.getRange();
}

addValue(value, type): void {
this.uuidValue = 'dynamic_' + UUID();
let chip;

switch (type) {
case 'addDefinedField':
chip =
'<span id=' + '\'' + this.uuidValue + '\'' +
' class="e-chip-list" style="display:inline" contenteditable="false"> <span class="e-chip"> <span class="e-chip-text red"> ' +
value + '</span><span class="e-chip-delete e-dlt-btn"> </span></span> </span>';
break;

case 'addTemplate':
chip =
'<span id=' + '\'' + this.uuidValue + '\'' +
' class="e-chip-list" style="display:inline" contenteditable="false"> <span class="e-chip"> <span class="e-chip-text green"> ' +
value + '</span><span class="e-chip-delete e-dlt-btn"> </span></span> </span>';
break;
}
if (this.curPos) {
this.editor.selectRange(this.curPos);
}
this.editor.executeCommand('insertHTML', chip);
const deleteBtn = document.querySelector(`#${this.uuidValue} .e-chip-delete`);
deleteBtn.addEventListener('click', () => {
deleteBtn.closest('.e-chip-list').remove();
});
}

2) The second problem is: when I've added a chip - it has Event for deleting, but after adding another chip, the previous one lost its event and click doesn't work.




11 Replies

IS Indrajith Srinivasan Syncfusion Team November 17, 2021 01:01 PM UTC

Hi Yan, 
 
Greetings from Syncfusion support, 
 
We have validated your reported queries, 
 
Query 1: “The problem is: when I insert 2 o more chips in a row, they become nested  
 
We suspect the range is not saved properly hence the chips are nested. In the below example, we have added the chip content in the editor when drag and drop the text content from the list view. In which the chips are rendered properly as expected when 2 or more chips added. Check the below sample for reference. 
 
 
Query 2: “when I've added a chip - it has Event for deleting, but after adding another chip, the previous one lost its event and click doesn't work. 
 
We are able to reproduce the reported issue from our end. You can resolve the reported issue by bounding the click event for the Rich Text Editor content area, instead for the close button and remove the chips based in the target element returned with the click action. Check the below shared code blocks and above shared sample for reference. 
 
 
this.rteObj.getContent().addEventListener('click'this.remove.bind(this)); 
 
remove(args) { 
    if (args.target.classList.contains('e-chip-delete')) { 
      (args as any).target.closest('.e-chip-list').remove(); 
    } 
  } 
 
 
Please get back to us if you face any difficulties, 
 
Regards, 
Indrajith 



YL Yan Lukouski November 18, 2021 01:52 PM UTC

I found solution with deleting chips, but checked yours and it also works.


But with chip adding still have a problem because in your example you have drop event. I my case there isn't any event. Chip is added by clicking from other component. I suppose it's a common problem to get caret position in contenteditable areas.


Maybe there is any method to save/get caret (cursor) position? So it will be a working example both in Chrome and Safari.



IS Indrajith Srinivasan Syncfusion Team November 19, 2021 10:55 AM UTC

Hi Yan, 
 
Good day to you, 
 
We are glad that your issue with chip delete action is resolved. 
 
We have further validated on your reported query “I suppose it's a common problem to get caret position in contenteditable areas.”. You can get the carretPosition using the carrentRangeFromPoint API, in the change event of the editor once the value is inserted in the editor with the button click action. We have modified the already shared sample. 
 
Code blocks: 
 
 
Onchange(args) { 
    if ( 
      this.textInserted && 
      this.rteObj.contentModule.getDocument() && 
      this.rteObj.contentModule.getDocument().caretRangeFromPoint 
    ) { 
      if (document.getElementById(this.uuidValue)) { 
        this.range = this.rteObj.contentModule 
          .getDocument() 
          .caretRangeFromPoint( 
            document.getElementById(this.uuidValue).getBoundingClientRect().x, 
            document.getElementById(this.uuidValue).getBoundingClientRect().y 
          ); 
        this.saveSelection = this.selection.save(this.rangedocument); 
      } 
    } 
    this.textInserted = false; 
  } 
 
clicked(args) { 
    this.textInserted = true; 
    this.uuidValue = 'dynamic_' + UUID.UUID(); 
    if (this.saveSelection) { 
      this.saveSelection.restore(); 
    } 
    var value = 
      '<span id=' + 
      "'" + 
      this.uuidValue + 
      "'" + 
      ' class="e-chip-list" style="display:inline" contenteditable="false"> <span class="e-chip"> <span class="e-chip-text"> ' + 
      args.target.innerText + 
      '</span><span id=' + 
      "'" + 
      this.uuidValue + 
      '-deleteId' + 
      "'" + 
      ' class="e-chip-delete e-dlt-btn">&nbsp;</span>'; 
 
    this.rteObj.executeCommand('insertHTML'value); 
    var deleteBtn = document.querySelector('#' + this.uuidValue + '-deleteId'); 
  } 
 
 
 
Please let us know if the solution helps, 
 
Regards, 
Indrajith 



YL Yan Lukouski November 22, 2021 12:34 PM UTC

Hi, Indrajith

It doesn't work in Opera, Firefox. It works 50/50 in Chrome and works in Safari.

And as I know .caretRangeFromPointis deprecated and is not stable in different browsers.


I'm looking for cross-browser solution.

Summary:
1) Opera - doesn't work

2) Firefox - doesn't work and if you click 2 times in a raw - inserted chip is going wrong (it inserts chip into chip)

3) Chrome - works 50/50, sometimes it insert chip at the beginning or at the wrong place

4) Safari - works


Any suggestions?





IS Indrajith Srinivasan Syncfusion Team November 22, 2021 12:35 PM UTC

Hi Yan,

We are currently validating your reported query and will get back to you with further details in two business days.

Regards,
Indrajith


IS Indrajith Srinivasan Syncfusion Team November 24, 2021 12:24 PM UTC

Hi Yan, 
 
Good day to you, 
 
We have further validated on the reported query. Instead of using the carrentRangeFromPoint javascript method, to get the cursor location you can manually set the range for the element. If it is current range is the span text element, to prevent the nesting of chips with the button click action. We have also ensured the same with other browsers, check the below shared sample and code blocks for reference. 
 
Code blocks: 
 
 
 Onchange(args) { 
       this.range = this.selection.getRange(this.rteObj.contentModule.getDocument()); 
        if (this.range.startContainer.nodeType === 3 && this.range.startContainer.parentElement && 
          this.range.startContainer.parentElement.classList.contains('e-chip-delete')) { 
          var rangeElement = this.range.startContainer; 
          this.range.setStart(rangeElement.parentElement.closest('p'), 0); 
          this.range.setEnd(rangeElement.parentElement.closest('p'), 0); 
        } 
        this.saveSelection = this.selection.save(this.rangedocument); 
} 
 
 
 
Please let us know if the solution helps, 
 
Regards, 
Indrajith


YL Yan Lukouski November 25, 2021 12:33 PM UTC

Hi!
It does work like a charm almost in all browsers, except Safari. Bug founded:
if you set cursor somewhere in text, then before clicking the button to insert chip - click somewhere else on the page just to loose focus and then inset chip - it will appear at the beginning of editor. 


Other browsers - absolutely ok!



IS Indrajith Srinivasan Syncfusion Team November 26, 2021 07:40 AM UTC

Hi Yan, 
 
Good day to you, 
 
We have further validated on the reported query “if you set cursor somewhere in text, then before clicking the button to insert chip - click somewhere else on the page just to loose focus and then inset chip”. The reported scenario is behavior across all browsers in the Rich Text Editor, since when focusing out the editor will lose focus and default focus will be set at initial. In order to achieve your requirement, we suggest you to save the cursor selection in the blur event of the editor. Check the below shared sample and code blocks for reference. 
 
Code blocks: 
 
 
 <ejs-richtexteditor #RTE id="defaultRTE" (blur)="OnBlur()" > 
 
 
OnBlur() { 
    this.range = this.rteObj.getRange(); 
    this.saveSelection = this.selection.save(this.rangedocument); 
  } 
 
 
 
Please let us know if the solution helps, 
 
Regards, 
Indrajith 



YL Yan Lukouski November 26, 2021 07:50 AM UTC

Hi there!

This trick doesn't work on Safari, I've already tried it yesterday and have checked once again right now. Safari behaves itself some strange, because when focus is lost - it remembers not that place where cursor was, but where it was clicked after blur event.




IS Indrajith Srinivasan Syncfusion Team November 29, 2021 02:06 PM UTC

Hi Yan, 
 
We have ensured the reported query, in the safari browser and able to reproduce the reported issue. Since the issue occurs only with the safari browser, we are currently validating it and will update you with further details by two business days. 
 
Regards, 
Indrajith 



IS Indrajith Srinivasan Syncfusion Team December 15, 2021 11:23 AM UTC

We have further validated the reported query in the safari browser. The issue occurs since the range is not maintained properly, with the blur action call in the safari browser. In order to resolve the reported issue, we suggest you to bind the mousedown event for the page and save the selection if the focusable element is not the editor. Check the below shared code blocks and sample for reference. 
 
Code blocks: 
 
 
onCreate() { 
    this.listviewEle = document.getElementById('listview'); 
    this.editArea = document.querySelector('#defaultRTE .e-content'); 
    this.rteObj.getContent().addEventListener('click'this.remove.bind(this)); 
    document.addEventListener('mousedown'this.onMouseDown.bind(this)); 
  } 
  onMouseDown(args) { 
    if (!args.target.closest('.e-richtexteditor') && !args.target.closest('button')) { 
      this.range = this.rteObj.getRange(); 
      this.saveSelection = this.selection.save(this.rangedocument); 
    } 
  } 
 
 


Loader.
Up arrow icon