Using an embedded WYSIWYG HTML editor with a textarea object

Questions related to using nuBuilder Forte.

Using an embedded WYSIWYG HTML editor with a textarea object

Postby nac » Fri Mar 23, 2018 10:20 pm

In the application I am developing there are a number of textarea objects for which I would like to use an embedded WYSIWYG HTML editor. Many lightweight JavaScript plugins are available and I have tried a few – all unsuccessfully and all failing in much the same way. (Maybe I have just picked the wrong ones to try!)

It is no problem to link in the plugin files (using the custom Setup>Header code) and I can create a custom JavaScript function that adds the editor to a textarea object. Using one of the plugins I tried as an example the code is, in theory, very simple:

Code: Select all
Var htmlObjName = new nicEditor({fullPanel : true}).panelInstance(textAreaObjName);

(See http://nicedit.com/demos.php?demo=3 for how it works on a simple page without tabs - no endorsement implied BTW)

The HTML editor is created but it does not observe the tab and co-ordinate properties of the textarea to which it is linked. For example, the graphic ribbon with the formatting icons remains visible across all the tabs, rather than being only on the tab which has the linked textarea object. This behaviour happened with all the plugins I tried. The example below shows this, together with the formatting bar to the left of where it should be.

HTML editor problem.PNG
HTML editor problem.PNG (37.15 KiB) Viewed 2735 times

Has anyone successfully implemented a WYSIWYG textarea editor in Forte or indeed previous versions? Any ideas would be much appreciated.

Neil
nac
 
Posts: 51
Joined: Wed Dec 13, 2017 7:58 am
Location: Aberdeen, UK

Re: Using an embedded WYSIWYG HTML editor with a textarea ob

Postby toms » Fri Mar 23, 2018 11:07 pm

Neil,

Good question. I just implemented a WYSIWYG-editor 2 days ago and I was faced with the same problem.
As there is no "tabchanged" or similar event, to detect when another tab becomes active, I'm using a little trick. I monitor if another nuBuilder field, that is on the same tab, changes its visibility and then hide/show the editor accordingly. This technique might work for other WYSIWYG editors too.

Code: Select all
if (nuFormType() == 'edit') {
   hideShowEditor(editor1, 'myreffield');
}

function hideShowEditor(editor, f) {
    if($('#' + f).css('display') == 'none') {$(editor.container).css('display', 'none'); }
    $('#' + f).hideShow().on('visibilityChanged', function(event, visibility) {
        $(editor.container).css('display', (visibility == 'shown') ? '' : 'none');
    })
}


Rich text editor: https://github.com/tovic/rich-text-editor

Tiny plugin jQuery-HideShow: This plugin is used to get event when an element gets hidden or visible in DOM.
https://github.com/pratik916/jQuery-Hid ... how.min.js
toms
 

Re: Using an embedded WYSIWYG HTML editor with a textarea ob

Postby nac » Fri Mar 23, 2018 11:33 pm

Thanks toms (as always),

Plenty of food for thought there. I will have a go and report back.

Neil
nac
 
Posts: 51
Joined: Wed Dec 13, 2017 7:58 am
Location: Aberdeen, UK

Re: Using an embedded WYSIWYG HTML editor with a textarea ob

Postby nac » Mon Mar 26, 2018 2:35 am

toms,

As yet, I have not had a go at following your suggestion. However, I did come across something that looks very close to what was looking for. It can be found at https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Editable_content with a zip file containing a simple, working example at https://developer.mozilla.org/@api/deki/files/6243/=rich-text-editor.zip

In this example, a textarea element can be made into a basic WYSIWYG editor with one additional attribute in the tag. Thus
<textarea id="mytextarea" ></textarea> displays the basic vanilla textarea, whilst
<textarea id="mytextarea" class="rich-text-editor"></textarea> displays the textarea with the WYSIWYG editor.

I have tried editing the HTML in this example file and it works. What I do not know how to do is to 'convert' an element after the page has been generated by nuBuilder without this attribute . I have tried using a function to add the class attribute:
Code: Select all
function convertToRichText(id) {
  $('#' + id).toggleClass('rich-text-editor');
}

and indeed, this shows class="rich-text-editor" as an attribute to the element when I inspect it. (see below)
Code: Select all
<textarea id="datasources" data-nu-tab="3" data-nu-form="" onchange="nuChange(event)" data-nu-field="datasources"
     data-nu-object-id="5a36a1c6aafdac4" data-nu-format="" data-nu-prefix="" data-nu-type="textarea"
    data-nu-subform-sort="1" data-nu-label="Data sources (DS)" data-nu-data=""
    style="top: 40px; left: 200px; width: 1000px; height: 400px; text-align: left; position: absolute;" class="rich-text-editor"></textarea>   

But it makes no difference at all the the appearance or behaviour of the textarea element in the nuBuilder application. Clearly I am missing something but it is beyond my current knowledge of HTML5 etc. I am hoping that, as the element retains all the nuBuilder assigned attributes, it will behave properly wrt to tabs etc. once the 'rich-text-editor' is active. The whole rich text editor from the Mozilla site is only about 15k and does everything that I am looking for. It would be good to get it working.

Any ideas on how to do this?

Neil
nac
 
Posts: 51
Joined: Wed Dec 13, 2017 7:58 am
Location: Aberdeen, UK

Re: Using an embedded WYSIWYG HTML editor with a textarea ob

Postby toms » Mon Mar 26, 2018 11:26 am

I have experimented a little with it but without result.

What I did is add the stylesheet and js to the header (Home ► Setup)
Code: Select all
<link rel="stylesheet" type="text/css" href="libs/rte/rich-text-editor.css"/>
<script type="text/javascript" src="libs/rte/rich-text-editor.js"></script>


And created a html object
Code: Select all
<textarea name="comment" id="newcomment" class="rich-text-editor"></textarea><br />


But the texterea is not turning into an editor.
However, the standalone rich-text-editor_example.html works all fine.
toms
 

Re: Using an embedded WYSIWYG HTML editor with a textarea ob

Postby nac » Mon Mar 26, 2018 4:56 pm

toms,

You describe exactly what I found when I did the same. It works in the simple html file but I cannot get it to work in nuBuilder. It seems to me that the class="rich-text-editor" needs to be in the tag when the page is loaded. But then again, I may be completely wrong.

Neil
nac
 
Posts: 51
Joined: Wed Dec 13, 2017 7:58 am
Location: Aberdeen, UK

Re: Using an embedded WYSIWYG HTML editor with a textarea ob

Postby toms » Mon Mar 26, 2018 5:31 pm

I'm guessing it might be a timing issue. It could be that the rte editor is initialized before the textarea has been instantiated.
toms
 

Re: Using an embedded WYSIWYG HTML editor with a textarea ob

Postby nac » Mon Mar 26, 2018 7:22 pm

toms,

You may well be right; I am still a novice in this area. My approach in this case is to strip everything down to the minimum, get it working and then incorporate the result into the nuBuilder application. We know that in the static html having class="rich-text-editor" in the tag will render the page with the embedded editor. So I tried the following in a much simplified version of rich-text-editor_example.html, to try to add it after the page has loaded. (First, unchanged part of header not shown.)

Code: Select all
<script>
function convertToRichText(taName) {
  var ta = document.getElementById(taName);
  ta.classList.add('rich-text-editor');
}
</script>
</head>
<body>
<h3>Rich Text Editor Example</h3>
   <textarea id="mytextarea" ></textarea><br />
   <button onclick="convertToRichText('mytextarea')">Convert to Rich Text</button>
</body>
</html>

when I click the button the inspector confirms that the class attribute is added to the mytextarea element but nothing else happens. I don't think this would be a timing issue. Any further thoughts..?

Neil
nac
 
Posts: 51
Joined: Wed Dec 13, 2017 7:58 am
Location: Aberdeen, UK

Re: Using an embedded WYSIWYG HTML editor with a textarea ob

Postby toms » Mon Mar 26, 2018 10:47 pm

Neil,

I pasted the whole code, with slight modifications, into the form's "Custom Code".
I replaced window.addEventListener(...) with documentReady(); Now the WYSIWYG editor appears. Therefore I still believe that it is a timing issue.
The goal would be of course, not to have the entire js-code under " Custom Code" but linked in an external library.

Code: Select all
function convertToRichText(taName) {
    var ta = document.getElementById(taName);
    ta.classList.add('rich-text-editor');
}

if (nuFormType() == 'edit') {
    convertToRichText("your_text_area");  // <--- CHANGE !!!!
}


Code: Select all
function formatDoc(oDoc, sCmd, sValue) {
    if (!validateMode(oDoc)) { return; }
    document.execCommand(sCmd, false, sValue);
    oDoc.focus();
}

function validateMode(oDoc) {
    if (!document.getElementById("rte-mode-" + rId.exec(oDoc.id)[0]).checked) { return true; }
    alert("Uncheck \u00AB" + sModeLabel + "\u00BB.");
    oDoc.focus();
    return false;
}

function extractText(oDoc) {
    if (oDoc.innerText) { return oDoc.innerText; }
    var oContent = document.createRange();
    oContent.selectNodeContents(oDoc.firstChild);
    return oContent.toString();
}

function setDocMode(oDoc, bToSource) {
    if (bToSource) {
        var oContent = document.createTextNode(oDoc.innerHTML)
            , oPre = document.createElement("pre");
        oDoc.innerHTML = "";
        oDoc.contentEditable = false;
        oPre.className = "rte-sourcetext";
        oPre.id = "rte-source-" + oDoc.id;
        oPre.onblur = oDoc.onblur;
        oPre.contentEditable = true;
        oPre.appendChild(oContent);
        oDoc.appendChild(oPre);
    } else {
        oDoc.innerHTML = extractText(oDoc);
        oDoc.contentEditable = true;
    }
    oDoc.focus();
}

function menuSelect() {
    if (this.selectedIndex < 1) { return; }
    var sMenuGroup = rId.exec(this.id)[0]
        , sCmd = this.id.slice(0, -sMenuGroup.length);
    formatDoc(aEditors[sMenuGroup], sCmd, this[this.selectedIndex].value);
    this.selectedIndex = 0;
}

function buttonClick() {
    var sBtnGroup = rId.exec(this.id)[0]
        , sCmd = this.id.slice(0, -sBtnGroup.length);
    customCommands.hasOwnProperty(sCmd) ? customCommands[sCmd](aEditors[sBtnGroup]) : formatDoc(aEditors[sBtnGroup], sCmd, this.alt || false);
}

function changeMode() {
    setDocMode(aEditors[rId.exec(this.id)[0]], this.checked);
}

function updateField() {
    var sFieldNum = rId.exec(this.id)[0];
    document.getElementById("rte-field-" + sFieldNum).value = document.getElementById("rte-mode-" + sFieldNum).checked ? extractText(this) : this.innerHTML;
}

function createMenuItem(sValue, sLabel) {
    var oNewOpt = document.createElement("option");
    oNewOpt.value = sValue;
    oNewOpt.innerHTML = sLabel || sValue;
    return oNewOpt;
}

function createEditor(oTxtArea) {




    var nEditorId = aEditors.length
        , oParent = document.createElement("div")
        , oMenuBar = document.createElement("div")
        , oToolsBar = document.createElement("div")
        , oEditBox = document.createElement("div")
        , oModeBox = document.createElement("div")
        , oModeChB = document.createElement("input")
        , oModeLbl = document.createElement("label");

    oParent.className = "rich-text-editor";
    oParent.id = oTxtArea.id || "rich-text-" + nEditorId;
    oMenuBar.className = "rte-menus";
    oToolsBar.className = "rte-tools";
    oEditBox.className = "rte-editbox";
    oEditBox.id = "rte-editbox-" + nEditorId;
    oEditBox.contentEditable = true;
    oEditBox.innerHTML = oTxtArea.value;
    aEditors.push(oEditBox);

    if (oTxtArea.form) {
        var oHiddField = document.createElement("input");
        oHiddField.type = "hidden";
        oHiddField.name = oTxtArea.name;
        oHiddField.value = oEditBox.innerHTML;
        oHiddField.id = "rte-field-" + nEditorId;
        oTxtArea.form.appendChild(oHiddField);
        oEditBox.onblur = updateField;
    }

    for (var oMenu, oMenuOpts, vOpt, nMenu = 0; nMenu < oTools.menus.length; nMenu++) {
        oMenu = document.createElement("select");
        oMenu.id = oTools.menus[nMenu].command + nEditorId;
        oMenu.onchange = menuSelect;
        oMenu.appendChild(createMenuItem(oTools.menus[nMenu].header));
        oMenuOpts = oTools.menus[nMenu].values;
        if (oMenuOpts.constructor === Array) {
            for (vOpt = 0; vOpt < oMenuOpts.length; oMenu.appendChild(createMenuItem(oMenuOpts[vOpt++])));
        } else {
            for (vOpt in oMenuOpts) { oMenu.appendChild(createMenuItem(vOpt, oMenuOpts[vOpt])); }
        }
        oMenu.selectedIndex = 0;
        oMenuBar.appendChild(document.createTextNode(" "));
        oMenuBar.appendChild(oMenu);
    }

    for (var oBtnDef, oButton, nBtn = 0; nBtn < oTools.buttons.length; nBtn++) {
        oBtnDef = oTools.buttons[nBtn];
        oButton = document.createElement("img");
        oButton.className = "rte-button";
        oButton.id = oBtnDef.command + nEditorId;
        oButton.src = oBtnDef.image;
        if (oBtnDef.hasOwnProperty("value")) { oButton.alt = oBtnDef.value; }
        oButton.title = oBtnDef.text;
        oButton.onclick = buttonClick;
        oToolsBar.appendChild(oButton);
    }

    oModeBox.className = "rte-switchmode";
    oModeChB.type = "checkbox";
    oModeChB.id = "rte-mode-" + nEditorId;
    oModeChB.onchange = changeMode;
    oModeLbl.setAttribute("for", oModeChB.id);
    oModeLbl.innerHTML = sModeLabel;
    oModeBox.appendChild(oModeChB);
    oModeBox.appendChild(document.createTextNode(" "));
    oModeBox.appendChild(oModeLbl);
    oParent.appendChild(oMenuBar);
    oParent.appendChild(oToolsBar);
    oParent.appendChild(oEditBox);
    oParent.appendChild(oModeBox);
    oTxtArea.parentNode.replaceChild(oParent, oTxtArea);
}

function replaceFields(nFlag) {
    nReady |= nFlag;
    if (nReady !== 3) { return; }
    for (
        var oField, nItem = 0, aTextareas = Array.prototype.slice.call(document.getElementsByTagName("textarea"), 0);

        nItem < aTextareas.length; oField = aTextareas[nItem++], oField.className !== "rich-text-editor" || createEditor(oField)

    );
}

function toolsReady() {
    oTools = JSON.parse(this.responseText);

    replaceFields(2);
}

function documentReady() { replaceFields(1); }

if (nuFormType() == 'edit') {
    var
        oTools, nReady = 0
        , sModeLabel = "Show HTML"
        , aEditors = []
        , rId = /\d+$/
        , oToolsReq = new XMLHttpRequest()
        , customCommands = {
            "printDoc": function (oDoc) {
                if (!validateMode(oDoc)) { return; }
                var oPrntWin = window.open("", "_blank", "width=450,height=470,left=400,top=100,menubar=yes,toolbar=no,location=no,scrollbars=yes");
                oPrntWin.document.open();
                oPrntWin.document.write("<!doctype html><html><head><title>Print<\/title><\/head><body onload=\"print();\">" + oDoc.innerHTML + "<\/body><\/html>");
                oPrntWin.document.close();
            }
            , "cleanDoc": function (oDoc) {
                if (validateMode(oDoc) && confirm("Are you sure?")) { oDoc.innerHTML = ""; };
            }
            , "createLink": function (oDoc) {
                var sLnk = prompt("Write the URL here", "http:\/\/");
                if (sLnk && sLnk !== "http://") { formatDoc(oDoc, "createlink", sLnk); }
            }
        };

    oToolsReq.onload = toolsReady;
    oToolsReq.open("GET", "libs/rte/rich-text-tools.json", true); < -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --CHANGE PATH!!
        oToolsReq.send(null);
    documentReady();
}
toms
 

Re: Using an embedded WYSIWYG HTML editor with a textarea ob

Postby nac » Mon Mar 26, 2018 11:51 pm

Thank you toms,

That is very helpful. I will have a go at it later. Just as a matter of interest, did you find that it behaved correctly with tabs visibility, position etc. once the WYSIWYG editor was visible?

Neil
nac
 
Posts: 51
Joined: Wed Dec 13, 2017 7:58 am
Location: Aberdeen, UK

Next

Return to General