Question

I'm writing a GUI extension that will essentially wrap selected text in a specific DIV. I have the text being encapsulated with the DIV but have hit a bit of a block...

When the user hasn't selected a specific piece of text I am assuming the intention is to wrap the nearest HTML element

htmlSelectedNode = target.editor.getSelectedHTMLElement()

This all seems to work fine and I pass the HTML element into my extension. After some manipulation I then apply the HTML but it is injecting the new HTML inside the previous element - instead of replacing it...

Is it possible to 'select' the HTML element - I can see a .focus() and a .setCapture() but these don't seem to do the trick - I guess the ideal would be to use the same functionality as applied when selecting an element from the "Current Element" dropdown on the ribbon bar but I've not been successful in locating the onclick/select method associated with this dropdown.

Just to clarify... with an example...

if the text was

    <div class="notrelevant"><p>test1</p><p>this is an example</p></div>

and the user inserted the cursor inbetween the i and s in 'is' I want the GUI to pre-select the nearest HTML Element - in this case the end result would be...

    <div class="notrelevant"><p>test1</p><div class="INJECTED_CORRECTLY"><p>this is an example</p></div></div>

EDIT: For completeness I've included the submit event that I use to (re)inject the HTML into the RTF (although I don't think it would make sense to manipulate the selection at this point and would prefer to have 'selected' the text above using a more 'standard', existing Tridion functionality...)

   $evt.addEventHandler(popup, "submit",
        function DateHighlighter$execute$onPopupSubmitted(event) {

            var el = event.data.html;
            if (el) {
                if (htmlSelectedNode != null) {
                    var lDocument = htmlSelectedNode.ownerDocument;
                    var lTempContainer = lDocument.createElement("span");

                    try {
                        lTempContainer.innerHTML = el || "";
                    }
                    catch (err) {
                        //assigning a value to innerHTML can be sensitive to browser but the error is fine to be ignored
                    }

                    var parentNode = htmlSelectedNode.parentNode;
                    parentNode.replaceChild(lTempContainer, htmlSelectedNode);

                    //Move to the new element
                    var lTextRange = $dom.createTextRange(lTempContainer);
                    $dom.moveRangeToElement(lTextRange, lTempContainer);
                    //Select and remove the temporary element
                    $dom.selectRange(lTextRange, $dom.getSelection(lDocument));
                    $dom.removeNode(lTempContainer, false);

                }
                else {
                    // Insert new created element (DIV)
                    target.editor.applyHTML(el);
                }

            }
            else {
                //TODO: test this - it's likely not required
                if (htmlSelectedNode.attributes["class"] != null)
                    htmlSelectedNode.removeAttribute("class");

                //TODO: test this - it's likely not required
                if (htmlSelectedNode.attributes["ondblclick"] != null)
                    htmlSelectedNode.removeAttribute("ondblclick");

                //TODO: the node isn't removed - leaves the empty <div>... 
                // - delete the node and then apply the node2.outerHTML? - or follow parentnode pattern above!
                var htmlSelectedNode2 = htmlSelectedNode.innerHTML;
                htmlSelectedNode = htmlSelectedNode2;
            }

            //Refreshes the Design view
            target.editor.setCurrentView("Source");
            target.editor.setCurrentView("RichText");
            target.item.closeActivePopup();
        });
    $evt.addEventHandler(popup, "unload", DateHighlighter$execute$onPopupCanceled);
Was it helpful?

Solution

So I located the CurrentElement selection and saw the glaringly obvious setSelectedHTMLElement. Not sure how I missed it but alas here is is below... as the comments note - ensure the method is available (if the user has a string of text selected that doesn't equate directly to a HTMLElement the setSelectedHTMLElement method will fail and remove us the user selection (thus meaning the GUI Extension .aspx can't (re)inject the new HTML...

//if there's no tagname then the setSelected will fail and remove the (non-element based) select the user has done
if (htmlSelectedNode.nodeName && htmlSelectedNode.nodeName != '#text') { 
    //unlikely to get #text here as it's the htmlSelectedNode.commonAncestorContainer.nodeName
    $log.message(logt + 'selecting the htmlSelectedNode.nodeName:' + htmlSelectedNode.nodeName);
    target.editor.setSelectedHTMLElement(htmlSelectedNode);
}
else {
    $log.message(logt + 'no htmlSelectedNode.nodeName - user probably selected incomplete element:');
}

OTHER TIPS

This is a tricky one. You are going to need to check all the possible scenarios. Back a year ago we had to implement a custom "Hyperlink" functionality as a GUI Extension. The problem was the same: "How can I know what is selected?", "What if the user select a text and half of an html node?" "What if the selection is just text?" Basically the same problems you are facing.

If I understood correctly, you want to wrap the selected text with a given (custom) div, right? Something you need to consider as well (if you haven't yet) is: "What happens if the user selects a text which is already wrapped with your custom div?", "What if he selects a text wrapped with your custom div plus a chunk of text afterwards or before the div?"

Well, as I mentioned before is not easy, or at least, not straight forward. Here are some hints on what we did, In our case we had to wrap the text with a Hyperlink tag instead of a div, but from a "get/set selected html" point of view, it should help:

1st: Initialize an element node and get the selected html:

var element = null;
var htmlSelectedNode = target.editor.getSelectedHTMLElement() || {};

2nd: Check what the hell is there:

    if (htmlSelectedNode != null && htmlSelectedNode.tagName != null && htmlSelectedNode.tagName == "A") { //In your case you might wanna check if it is a div, instead
    ...
    ...

}

Note: In our case we had to check also for images and span tags, since the anchor element will be a bit different because we needed to get some information from those.

3rd: Set the element var if the selected text was an element (No extra text selected)

element = htmlSelectedNode;


var selectedText = "";
    // Chrome & Firefox
            if (htmlSelectedNode != null && htmlSelectedNode.startContainer != null && htmlSelectedNode.startOffset != null) {

                selectedText = htmlSelectedNode.toString();

            }
            // IE Range
            else if (htmlSelectedNode != null && htmlSelectedNode.htmlText != null && htmlSelectedNode.text != null) {
                selectedText = htmlSelectedNode.text;
            }
            // IE DIV
            else if (htmlSelectedNode != null && (htmlSelectedNode.tagName == "DIV" || htmlSelectedNode.tagName == "P") && htmlSelectedNode.innerText != null) {
                selectedText = htmlSelectedNode.innerText;
            }

4rd: If the htmlSelectedNode contained a node plus raw text either before or after, we used ranges (read a bit further), taking into account the different browser behaviors:

Well, the examples here don't really solve your problem but I hope they give you some hints on how to check stuff with the xhtml content. Basically you'll need to take care of making the selection a valid xhtml element.

Here more examples on how we dealt with the xhtml when the element already contain a link tag:

// If there is a link, delete it
if (htmlSelectedNode != null) {
    var lDocument = htmlSelectedNode.ownerDocument;
    var lTempContainer = lDocument.createElement("span");

    try {
        lTempContainer.innerHTML = el || "";
    }
    catch (err) {
        //assigning a value to innerHTML might fail -> ignore it then
    }

    var parentNode = htmlSelectedNode.parentNode;
    parentNode.replaceChild(lTempContainer, htmlSelectedNode);

    //move to new element
    var lTextRange = $dom.createTextRange(lTempContainer);
    $dom.moveRangeToElement(lTextRange, lTempContainer);
    //select and remove temp element
    $dom.selectRange(lTextRange, $dom.getSelection(lDocument));
    $dom.removeNode(lTempContainer, false);

}

How to set back the HTML into the RTF and refresh the Source/Design tabs:

// Insert new created link
target.editor.applyHTML(el); //el == element


//Refreshes the Design view
target.editor.setCurrentView("Source");
target.editor.setCurrentView("RichText");

Again, I know this is not a solution for your problem, but I think you can use some of the examples to try new approaches. If you need further assistance, please share the entire JS file, or at least the _execute() method (where I assume you have the code, right?)

Regards,

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top