Use a treeview item picker in your GUI extensions

Recently, I was asked to build a Tridion GUI extension that would let users insert a key of a Keyword in a rich text field. Users would have to click a button in the ribbon toolbar, get a list of keywords, pick one, and the GUI extension would work it’s magic.

There are some nice examples of this to be found here, so i managed to wrap up a solution pretty quick, and life was good.

After some testing, it turned out that the amount of items in the dropdown would grow to large – if you have only 10 or 20 items to pick from, that’s fine, but what if there are hundreds…

So, a new requirement was added : “can we not have a dropdown of keywords, but a tree view, so users would be able to drill-down to find the keyword they want”. 

Hmm, a tree-view, let’s see. A dropdown is pretty easy, it’s a standard html element, it has not so much functionality, just show the items, pick one and you are done. However, a treeview shows a hierarchy of items, items can be collapsed, expanded, so this might add some complexity.

So, I started expanding my custom popup:  remove the dropdown, and then what. Hmm, let’s delve in the Tridion javascript code to see what they are doing …

Now hold on, as Tridion already has such a functionality, why should we mimic it. Can’t we simply reuse what they have ?

Turns out, we can, and the solution is fairly easy, once you know what to look for.
In the Tridion CME, there exists a popup window “/Views/Popups/TreeItemSelect/TreeItemSelect.aspx”. There also exists a function to open this popup.

In your gui extensions Execute function, all you need to do is call this function

var popup = $popupManager.createExternalContentPopup($cme.Popups.TREE_ITEM_SELECT.URL, $cme.Popups.TREE_ITEM_SELECT.FEATURES,
{
rootId: rootID,
itemTypes: [$const.ItemType.KEYWORD],
allowRootSelection: false
});

So, basically, you call the popup manager to create a new popup of type “tree_item_select, which takes some parameters

– rootId : Tcm uri of the root item to show. In my case, this was a tcm id of a category
– itemTypes : which kind of items do you wish to show (in this case, we show keywords, but you could as well show components or pages etc.)
– allowRootSelection : allows you to actually select the root item or not

The result of this popup looks like this

treeview_popup

Looks pretty neat, and all you had to do is call one javascript function – so no hassle with creating custom popups, custom javascript coding, …

The return value of this popup is a tcm id, so when you click the “select” button, your code can consume it like this :

 $evt.addEventHandler(popup, "select", function TagSelectorBtn$execute$onPopupSubmitted(event) {

        var selectedKeyword = event.data.itemId;
        if (selectedKeyword) {
            var selectedKeyword = $models.getItem(selectedKeyword);
            var selectedKey = selectedKeyword.getKey();
            target.editor.applyHTML(selectedKey);

        }
        else {
            //a problem occurred - we didn't receive anything from the popup
        }

        // close the active pop up window
        target.item.closeActivePopup();
    });

Small breakdown of this code

var selectedKeyword = event.data.itemId 
gives you the tcm uri of the selected object

$models.getItem(selectedKeyword);
loads the keyword object from the Content Manager DB

target.editor.applyHTML(selectedKey);
Inserts something in the rich text field

So, we were quickly able to implement the desired functionality, simply by reusing standard out of the box Tridion functions. The hardest part is finding where they are, and how to use them. That’s where both the “Find in files” of your favourite IDE (to search for any useful functions that exist in the tridion GUI code – which can be fouind in your %TRIDION_HOME%\web\WebUI\Editors\CME folder) and the the Chrome or Firefox’s javascript console (to find out what these handy functions can do, and which options they have) comes in handy.

An additional benefit from this solution is that we rely on tested code.

If you have any questions or additions, feel free to leave a comment.

For reference, here is the full block of code of my gui extension

/*
* Inserts Selectable Dropdown values from keywords RTF Editor
* Command implementation
*/
Type.registerNamespace("ContentBloomGuiExtensions.Commands");
/*
* Constructor
*/
ContentBloomGuiExtensions.Commands.TagSelector = function (name) {
    Type.enableInterface(this, "ContentBloomGuiExtensions.Commands.TagSelector");
    this.addInterface("Tridion.Cme.Command", [name || "TagSelectorBtn"]);
};

/*
*Checks whether to display the extension or not based on, in which control the current selection is.
*Returns true is current selection is in RTF Format Area else returns false
*/
ContentBloomGuiExtensions.Commands.TagSelector.prototype.isAvailable = function (target) {

    if (!Tridion.OO.implementsInterface(target.editor, "Tridion.FormatArea")
        || target.editor.getDisposed()
        || (target.editor.getCurrentView() != Tridion.FormatArea.Views.RichText)) {
        return false;
    }
    return true;
};

/*
*Checks whether to enable the extension or not based on, in which control the current selection is.
*Also verifies whether the extension is listed under 'Allowed Actions'. if not returns false else returns true
*/
ContentBloomGuiExtensions.Commands.TagSelector.prototype.isEnabled = function (target) {
    if (!Tridion.OO.implementsInterface(target.editor, "Tridion.FormatArea")
        || target.editor.getDisposed()
        || (target.editor.getCurrentView() != Tridion.FormatArea.Views.RichText)) {
        return false;
    }
    
    var faDisallowedActions = target.editor.getDisallowedActions();
    if (!String.isNullOrEmpty(faDisallowedActions)) {
        var daArr = faDisallowedActions.split(",");
        if (daArr && daArr.length > 0) {
            for (var i = 0, cnt = daArr.length; i < cnt; i++) {
                if (daArr[i] == "TagSelectorBtn") {
                    return false;
                }
            }
        }
    }

    return true;
};

/*
* Opens a popup for the author to select a keyword from a drop down select list
*/
ContentBloomGuiExtensions.Commands.TagSelector.prototype._execute = function (target) {

    target.editor.setFocus();

    var pubId = $display.getItemPublicationUri();
    pubId = pubId.replace("tcm:0-", "");
    pubId = pubId.replace("-1", "");
    var categoryUrl = $extUtils.getConfigurationItem("TagSelectorCategory", $OUTConst.RTFEDITOR);
    if (categoryUrl === "") {
        $messages.registerWarning('ID for tag category is not defined in configuration', 'Please set valid ID for xml tag TagSelectorCategory in editor.config', true);
        return;
    }
    var rootID = "tcm:" + pubId + "-" + categoryUrl + "-512";
    var popup = $popupManager.createExternalContentPopup($cme.Popups.TREE_ITEM_SELECT.URL, $cme.Popups.TREE_ITEM_SELECT.FEATURES,
	{
	    rootId: rootID,
	    itemTypes: [$const.ItemType.KEYWORD],
	    allowRootSelection: false
	});
    if (target.item.isActivePopupOpened(popup)) {
        return;
    }

    $evt.addEventHandler(popup, "select", function TagSelectorBtn$execute$onPopupSubmitted(event) {

        var selectedKeyword = event.data.itemId;
        if (selectedKeyword) {
            var selectedKy = $models.getItem(selectedKeyword);
            var selectedDescription = selectedKy.getKey();
            target.editor.applyHTML(selectedDescription);

        }
        else {
            alert("event data = " + event.data);
        }

        // close the active pop up window
        target.item.closeActivePopup();
    });
    //Handle the CTA styles popup cancel event
    function TagSelectorBtn$execute$onPopupCanceled(event) {
        target.item.closeActivePopup();
    };

    $evt.addEventHandler(popup, "unload", TagSelectorBtn$execute$onPopupCanceled);
    target.item.setActivePopup(popup);
    popup.open();


};


5 thoughts on “Use a treeview item picker in your GUI extensions

  1. Hi Harald – nice post, thanks. Quick question on the root… can this be a {abstract} keyword rather than the higher level category?

  2. Nice post Harald, in my current project we have a use-case that looks similar to this.

    Just one note; there is some unreachable code in ContentBloomGuiExtensions.Commands.TagSelector.prototype.isEnabled = function (target), in the middle there is a return true;

  3. Pingback: Using a GUI extension for RTF fields with regular Text fields | SDL Tridion Developer

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>