I try to create a WYSIWYG web component and I have problem defining a reusable function inside the web component class. The contents of my index.html file is the following:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<cm-richtexteditor text=""></cm-richtexteditor>
<br />
<cm-richtexteditor text="<p>Aris</p>"></cm-richtexteditor>
<script type="text/javascript">
class CMRichTextEditor extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
get text() {
let value = this.getAttribute('text');
if (!value || value == "")
value = "<p><br></p>";
return value;
}
set text(val) { this.setAttribute('text', val); }
static get observedAttributes() {
return ['text'];
}
connectedCallback() {
this.render();
}
render() {
this.shadowRoot.innerHTML = `<style type="text/css">
#componentContainer {
border: 1px solid #cccccc;
}
#toolbar {
padding: 4px;
}
#toolbar button {
font-size: 12px;
font-family: inherit;
border: 1px solid #777777;
background-color: white;
padding: 5px;
margin: 0 10px 0 0;
}
.activeIcon {
background-color: #777777 !important;
color: white !important;
}
#editorContainer {
height: 300px;
border-top: 1px solid #cccccc;
outline: none;
padding: 5px;
overflow: auto;
}
#editorContainer img {
max-width: 250px;
}
#editorContainer p {
margin: 0;
}
</style>
<div id="componentContainer">
<div id="toolbar">
<button id="boldButton" title="Bold">B</button>
<button id="underlineButton" title="Underline">U</button>
<button id="unorderedListButton" title="Bullet List">UL</button>
<button id="numberedListButton" title="Numbered List">OL</button>
<button id="imageButton" title="Picture">Picture</button>
</div>
<div id="editorContainer" contenteditable="true" spellcheck="false">${this.text}</div>
</div>`;
document.execCommand('defaultParagraphSeparator', false, 'p');
// Bold menu
this.shadowRoot.querySelector('#boldButton').addEventListener('click', function () {
document.execCommand('bold');
});
// Underline menu
this.shadowRoot.querySelector('#underlineButton').addEventListener('click', function () {
document.execCommand('underline');
});
// List menu
this.shadowRoot.querySelector('#unorderedListButton').addEventListener('click', function () {
document.execCommand('insertUnorderedList');
});
// List menu
this.shadowRoot.querySelector('#numberedListButton').addEventListener('click', function () {
document.execCommand('insertOrderedList');
});
// Picture menu
this.shadowRoot.querySelector('#imageButton').addEventListener('click', function () {
document.execCommand('insertImage', false, 'http://usefulangle.com/img/posts/17-1px.jpg');
});
// Check menu options to be highlighted on keyup and click event
this.shadowRoot.querySelector('#editorContainer').addEventListener('keyup', this.UpdateActiveStatus.bind(this));
this.shadowRoot.querySelector('#editorContainer').addEventListener('click', this.UpdateActiveStatus.bind(this));
this.shadowRoot.querySelector('#editorContainer').addEventListener('keyup', this.ContentsChanged.bind(this));
}
ContentsChanged() {
// editor container
var editorContainer = this.shadowRoot.querySelector('#editorContainer');
// make sure that we have at least one paragraph tag
var contents = editorContainer.innerHTML;
if (contents == "") {
contents = "<p><br></p>";
editorContainer.innerHTML = contents;
}
// set the text property of the web component to contain the html that the user typed
this.setAttribute('text', editorContainer.innerHTML);
}
UpdateActiveStatus() {
// editor container
var editorContainer = this.shadowRoot.querySelector('#editorContainer');
// will hold format codes of all ranges
var rangesFormats = [];
var end_element, cur_element;
// for all ranges
for (var i = 0; i < this.shadowRoot.getSelection().rangeCount; i++) {
// Start container of range
var start_element = this.shadowRoot.getSelection().getRangeAt(i).startContainer;
// End container of range
end_element = this.shadowRoot.getSelection().getRangeAt(i).endContainer;
// Will hold parent tags of a range
var range_parent_tags = [];
// If starting node and final node are the same
if (start_element.isEqualNode(end_element)) {
// If the current element lies inside the editor container then don't consider the range
// This happens when editor container is clicked
if (editorContainer.isEqualNode(start_element)) {
rangesFormats.push([]);
continue;
}
cur_element = start_element.parentNode;
// Get all parent tags till editor container
while (!editorContainer.isEqualNode(cur_element)) {
range_parent_tags.push(cur_element.nodeName);
cur_element = cur_element.parentNode;
}
}
// Push tags of current range
rangesFormats.push(range_parent_tags);
}
// Find common formats for all ranges
rangesFormats = rangesFormats.filter((item, index) => rangesFormats.indexOf(item) === index)[0];
console.log(rangesFormats);
// Activate or deactivate the toolbar icons
if (rangesFormats.indexOf('B') != -1)
this.shadowRoot.querySelector("#boldButton").classList.add("activeIcon");
else
this.shadowRoot.querySelector("#boldButton").classList.remove("activeIcon");
if (rangesFormats.indexOf('U') != -1)
this.shadowRoot.querySelector("#underlineButton").classList.add("activeIcon");
else
this.shadowRoot.querySelector("#underlineButton").classList.remove("activeIcon");
if (rangesFormats.indexOf('UL') != -1)
this.shadowRoot.querySelector("#unorderedListButton").classList.add("activeIcon");
else
this.shadowRoot.querySelector("#unorderedListButton").classList.remove("activeIcon");
if (rangesFormats.indexOf('OL') != -1)
this.shadowRoot.querySelector("#numberedListButton").classList.add("activeIcon");
else
this.shadowRoot.querySelector("#numberedListButton").classList.remove("activeIcon");
}
}
customElements.define('cm-richtexteditor', CMRichTextEditor);
</script>
</body>
</html>
This code has two problems. The first problem is that when I click one of the buttons it does not update the active state of the button. For example when I click the Bold button I have to call the UpdateActiveStatus function.
// Bold menu
this.shadowRoot.querySelector('#boldButton').addEventListener('click', function () {
document.execCommand('bold');
this.UpdateActiveStatus();
});
The problem is that I get the following error:
Uncaught TypeError: this.UpdateActiveStatus is not a function at HTMLButtonElement.<anonymous>
Does anyone knows what is the right way to call this function?
My second problem is that if I select some text in the first editor and click the bold button of the second editor this will have as a result to change the format of the first editor because this is the location of the page selected text.
Any possible solution would be appreciated.
I found how to solve my problem and I will write the solution just in case someone else has the same problem. Inside a web component we can call a function using the getRootNode method of the object so I changed the following line:
this.UpdateActiveStatus();
using the following code:
this.getRootNode().host.UpdateActiveStatus();
For my second problem I created the following function and I called it before I modify the contents of the editor.
SelfFocus() {
this.shadowRoot.querySelector('#editorContainer').focus();
}