Anguilla Framework: Adding Messages With MessageCenter and $messages

The Anguilla Framework comes with a built in message center that allows you to display different types of messages and notifications to the user. You’ve probably seen these messages already – they appear at the top of the window when you do almost any action in Tridion. They can even appear as a modal, display a question that requests a user response, or display a progress message that lets the user know when a certain action is done. When building your custom GUI Extensions, instead of rolling your own status or update notification system, you can easily take advantage of the one that’s already in place (and make your extension feel like its an actual part of Tridion). Messages added with MessageCenter are even archived, so your users can go back and view them if needed (by clicking the flag like symbol in the upper left of the screen). In this post, we’ll cover how to add notifications, warnings, errors, questions, goals and progresses.

Notifications

Notifications get displayed with the “i” icon (as with the image at the top of this post). You can use these for simple updates or alerts. Notifications will disappear from the upper window after around ten seconds or so.

$messages.registerNotification(title, description, local, modal)

title – The title of the notification. This is the message that gets displayed when shown at the top of the screen.
description – (optional) A better description of the notification. Can be seen when user double clicks the message to get further details.
local – (optional) You’ve probably noticed that most of the messages get displayed across all of your open Tridion windows. Setting ‘local’ to true will only display your message at the top of the window you are working with. Note that it still goes to the MessageCenter, so you can still view it from the other windows via the Message Center icon.
modal – (optional) True or false, if true your notification will get displayed as a modal popup.

Goals


Goals are like notifications, except they are displayed with a check mark icon (and a different colored background!).

$messages.registerGoal(title, description, local, modal)

Warnings


Warnings are also similar to notifications as well and are displayed with a warning icon. Unlike Notifications and Goals, the warning message will not disappear on its own (you’ll have to click the little ‘x’).

$messages.registerWarning(title, description, local, modal)

Errors


Errors, like Warnings, do not disappear on their own, and they are displayed with an error icon. They allow for an extra area for specifics of your error.
Error message with Details.

$messages.registerError(title, description, details, local, modal)

details – Details are for displaying the details of your error. For example, this area would be a lovely place to display the stack trace of an error.

Questions

Questions allow you to supply a simple “yes/no” type of question to the user. You can add events to your message to perform an action upon confirming or canceling. Messages are great to use with the modal feature.

Question with modal option turned on

$messages.registerQuestion(title, description, local, modal, buttonLabels)

buttonLabels – (optional) Additional settings to control the labels of the button, which by default is “Yes” and “No”. { cancel: "Cancel Label", confirm: "Confirm Label" }

Question Code Samples:

var question = $messages.registerQuestion("Do you love GUI Extensions?", null, true, true, { cancel: "Hate Them!", confirm: "Love Them!" });
question.addEventListener("cancel", function (event) {
    $messages.registerNotification("Haters want to hate!");
});
question.addEventListener("confirm", function (event) {
    $messages.registerNotification("TridionLove++");
});

Progress


Progress messages let you display the progress of an action, for example, “Saving…”. Like Questions, they are a bit more interactive than just displaying a message to the user. You can set messages that will get displayed upon success of your action using setOnSuccessMessage(title, description). You can even have a cancel button appear and allow the user to cancel your action, and set a message to display upon cancellation using setOnCancelMessage(title, description). Note that the Success and Cancel messages will be displayed as Goals.

$messages.registerProgress(title, description, canCancel, local, modal)

canCancel – (Optional) True or false, when set to true will display a cancel button

Progress Code Samples:

var progress = $messages.registerProgress("Waiting...", null, true);

progress.setOnCancelMessage("You've canceled!");
progress.setOnSuccessMessage("Done waiting!!!");

progress.addEventListener("cancel", function (event) { 
    console.log("Do stuff when canceled..."); 
});

// ... somewhere later in your code, in a callback or some form of dark arts... 
progress.finish({ success: true }); // Will remove progress message and display your onSuccess message.
progress.finish(); // Will remove progress message, but not display your onSuccess message.

// You can even cancel programatically by... clicking the cancel button or calling this method directly will display the cancel message.
progress.cancel();

You can also set it to automatically call the .finish() method upon the event of another item via the .addFinishEvent(itemID, event, isSuccessEvent) method. The follow example will show a loading status, and will automatically fire our success message upon completion of our item being loaded.

var item = $tcm.getItem("tcm:1-234"),
    progress = $messages.registerProgress("Loading our item...");

progress.setOnSuccessMessage("Item Finished Loading!");
progress.addFinishEvent("tcm:1-234", "load", true);

item.load();

Playing around in a console…

If you want a quick way to experiment with MessageCenter, feel free to try the $messages api inside of the console window!

Update 08/01/2014

I’ve posted a follow up on this article on retrieving, archiving, and disposing of messages. You can find it here!

Anguilla Framework: Getting Items From Tridion With $tcm.getItem() And Load Events

For anyone wondering if you can get items from Tridion using the Anguilla Framework in your GUI Extensions, the answer is yes, yes you can.

var yourPage = $tcm.getItem("tcm:10-1234-64");

Simple, no? Unfortunately your work is not done here. The getItem() method loads data from a cached state. That means, if the properties that you are looking for hasn’t been loaded already, they will return undefined.

var templateID = yourPage.getPageTemplateId();
// templateID is "undefined".

To load the data, you can call the load() method. You’ll also want to create an event handler for when the event has been triggered.

var yourPage = $tcm.getItem("tcm:10-1234-64");
$evt.addEventHandler(yourPage, "load", function (event) {
    doSomethingWithYourPage(yourPage); // event.source also contains the page instance.
});
yourPage.load();

Remember to take advantage of how Tridion handles the caching. The above script is inefficient, because you’ll always load the item regardless of whether or not it is in the cache. To check if the items has already been loaded, you could do the following:

var yourPage = $tcm.getItem("tcm:10-1234-64");
if (yourPage.isLoaded()) {
    doSomethingWithYourPage(yourPage);
} else {
    $evt.addEventHandler(yourPage, "load", function (event) {
        doSomethingWithYourPage(yourPage); // event.source also contains the page instance.
    });
    yourPage.load();
}

Another possible gotcha is that .load() doesn’t load everything. For example, the WebDav URL. After the .load() call, the following will still not return anything.

var webDavUrl = yourPage.getWebDavUrl();

To get the data you need in the above example, you would have to do something along the lines of:

var yourPage = $tcm.getItem("tcm:10-1234-64"),
    webDavUrl = yourPage.getWebDavUrl();

if (!webDavUrl) {
    $evt.addEventHandler(yourPage, "loadwebdavurl", function (event) {
        webDavUrl = yourPage.getWebDavUrl(); // also could do event.source.getWebDavUrl()
    });
    yourPage.loadWebDavUrl();
}

There are other load functions that load different types of data, each with their own events. Here is a list of them with their associated load and fail event names.

Function Name Load Event Load Fail Event
load() load loadfailed
loadBlueprintInfo() loadblueprintinfo loadblueprintinfofailed
loadItemCompareInfo() compareinfoload compareinfoloadfailed
loadRollBackComment() rollbackcommentload rollbackcommentloadfailed
loadWebDavUrl() loadwebdavurl loadwebdavurlfailed
staticLoad() staticload staticloadfailed

GUI Extensions: Experimenting with Extended Areas

A little back I posted Event System code on Inheriting the Page Process setting on Structure Groups. I mentioned the missing piece: an “Inherit Page Process” like check box to have full control over the inheriting process. I decided to investigate completing this little extension and started looking at Site Edit 2012′s code where they added check boxes for “Enable Inline Editing”. This brought me to experiment with the <ext:extendedareas /> section of the GUI Extension configuration, and I thought I’d share what I learned in case anyone was wondering what these areas were for or how to use them.  NOTE that this is just my experimentation, and if you see anything stated incorrectly (or done in an inefficient way) please do comment!

The Good News

Extended Areas allows you to add your custom controls and code to existing areas in Views. Since I was looking for a way to add a “Inherit Page Process” check box right below the “Associated Page Process” dropdown in the Workflow Tab of the StructureGroup view, I thought that this would be the perfect way of adding my little extension.

How It Works

The extendedarea section contains another section where you get to target the view and the ExtendableArea control. The following comes from the configuration in the SiteEdit 2012 GUI Extension.

<ext:extension assignid="EnableSEForPublicationTarget" name="{Resources: Tridion.Web.UI.Editors.SiteEdit.Strings, SE}">
    <ext:control>~/Extensions/PublicationTarget/EnableSiteEdit.ascx</ext:control>
    <ext:pagetype></ext:pagetype>
    <ext:renderinblock>false</ext:renderinblock>
    <ext:apply>
        <ext:view name="PublicationTargetView">
            <ext:control id="PublicationTargetTab_ExtendableArea" />
        </ext:view>
    </ext:apply>
</ext:extension>

The above example looks for the Control <c:ExtendableArea id=”PublicationTargetTab_ExtendableArea” runat=”server” RenderInBlock=”false” /> on the PublicationTarget View (PublicationTarget.aspx). It will inject a custom control you define in the <ext:control>~/YourControl.ascx</ext:control> into the ExtendableArea control. Sounds good, right?

The Bad News

Unfortunately, not every view and area contains an ExtendableArea control (hopefully this changes in the future!). And unfortunately for me, the WorkflowTab on StructureGroups is one of those unlucky areas that does NOT contain an ExtendableArea control. :(

With the real extension I plan on posting once its finished up, I’ll probably be doing a lil JavaScript injection to get my customization showing up. But for the sake of science and still wanting to complete my experiment using extended areas, I committed an abomination and a no no for this little experiment…

Adding the ExtendedableArea Control Manually

To see my check box get added using an extended area, I added my own ExtendableArea Control to Tridion’s WorkflowStructureGroup.ascx (\WebUI\Editors\CME\Tabs\Workflow\).

<%@ Import Namespace="Tridion.Web.UI"%>
<%@ Control Language="C#" AutoEventWireup="true" Inherits="Tridion.Web.UI.Editors.CME.Tabs.WorkflowStructureGroup" ClassName="WorkflowStructureGroup" %>

<div id="ItemWorkflow">
    <div id="Workflow_StructureGroup">
        <fieldset>
            <div class="field">
                <label for="PageProcess">
                    <asp:Literal runat="server" Text="<%$ Resources: Tridion.Web.UI.Strings, TextPageProcess %>" />
                </label>
                <c:Dropdown id="PageProcess" runat="server" NullText="<%$ Resources: Tridion.Web.UI.Strings, None %>" disabled="true"/>
            </div>
        </fieldset>
        <c:ExtendableArea id="WorkflowStructureGroup_ExtendableArea" runat="server" RenderInBlock="false" />
    </div>
</div>

InheritFromParent.ascx

The following is just the markup for my custom checkbox.

<%@ Control Language="C#" AutoEventWireup="true" %>
<div class="form stack-elem fieldgroup">
    <div class="field">
        <label for="InheritPageProcess">Inherit Page Process</label>
        <div>
            <input type="checkbox" id="InheritPageProcess" />
        </div>
    </div>
</div>
<div class="stack-elem hr"></div>

InheritFromParent.ascx.js

Again, much thanks to the Tridion UI 2012 code for the PublicationTarget extension for me to look at as a refererence. The following JavaScript was used just for some quick testing.

Type.registerNamespace("Tahzoo.Extensions.StructureGroup");

Tahzoo.Extensions.StructureGroup.InheritFromParent = function Tahzoo$Extensions$InheritFromParent() {
    Tridion.OO.enableInterface(this, "Tahzoo.Extensions.StructureGroup.InheritFromParent");
    this.addInterface("Tridion.DisposableObject");

    this.properties.controls = {};
};

Tahzoo.Extensions.StructureGroup.InheritFromParent.prototype.initialize = function Tahzoo$Extensions$InheritFromParent$initialize() {
    var c = this.properties.controls;
    console.log($display.getItem().getTitle() + " Initialized!");
    c.inheritPageProcess = $("#InheritPageProcess");
    c.inheritPageProcess.disabled = true;

    $evt.addEventHandler(c.inheritPageProcess, "click", this.getDelegate(this._onInheritPageProcessClicked));

    var item = $display.getItem();
    if (item) {
        $evt.addEventHandler(item, "load", this.getDelegate(this._onItemLoaded));
        $evt.addEventHandler(item, "change", this.getDelegate(this._onItemChanged));
        $evt.addEventHandler(item, "undocheckoutfailed", this.getDelegate(this._onItemChanged));
        $evt.addEventHandler(item, "checkinfailed", this.getDelegate(this._onItemChanged));

        if (item.isLoaded()) {
            this._onItemLoaded();
        }
    }

};

Tahzoo.Extensions.StructureGroup.InheritFromParent.prototype.disposeInterface = Tridion.OO.nonInheritable(function Tahzoo$Extensions$InheritFromParent$disposeInterface() {
    var item = $display.getItem();
    if (item) {
        $evt.removeEventHandler(item, "load", this.getDelegate(this._onItemLoaded));
        $evt.removeEventHandler(item, "change", this.getDelegate(this._onItemChanged));
        $evt.removeEventHandler(item, "undocheckoutfailed", this.getDelegate(this._onItemChanged));
        $evt.removeEventHandler(item, "checkinfailed", this.getDelegate(this._onItemChanged));
    }
});

Tahzoo.Extensions.StructureGroup.InheritFromParent.prototype._onItemLoaded = function Tahzoo$Extensions$InheritFromParent$_onItemLoaded() {
    console.log("Item Loaded");
    var item = $display.getItem();

    this._updateCheckbox(item);
};

Tahzoo.Extensions.StructureGroup.InheritFromParent.prototype._onItemChanged = function Tahzoo$Extensions$InheritFromParent$_onItemChanged(event) {
    console.log("Item Changed");
    this._updateCheckbox($display.getItem());
};

Tahzoo.Extensions.StructureGroup.InheritFromParent.prototype._onInheritPageProcessClicked = function Tahzoo$Extensions$InheritFromParent$_onInheritPageProcessClicked(event) {
    var c = this.properties.controls;
    console.log("Clicked To: " + c.inheritPageProcess.checked.toString());
};

Tahzoo.Extensions.StructureGroup.InheritFromParent.prototype._updateCheckbox = function Tahzoo$Extensions$InheritFromParent$_updateCheckbox(item) {
    this.properties.controls.inheritPageProcess.disabled = item.isReadOnly() || item.isLoading();
};

Tridion.Controls.Deck.registerInitializeExtender("WorkflowTab", Tahzoo.Extensions.StructureGroup.InheritFromParent);

The Extension Configuration (InheritedWFSG.config)

And finally we have our extension’s actual configuration file which pieces everything together.

<?xml version="1.0"?>
<Configuration xmlns="http://www.sdltridion.com/2009/GUI/Configuration/Merge" xmlns:cfg="http://www.sdltridion.com/2009/GUI/Configuration" xmlns:ext="http://www.sdltridion.com/2009/GUI/extensions" xmlns:cmenu="http://www.sdltridion.com/2009/GUI/extensions/ContextMenu">
    <resources cache="true">
        <cfg:filters/>
        <cfg:groups>
            <cfg:group name="Tridion.Extensions.GUI.InheritedWFSG.StructureGroup">
                <cfg:fileset>
                    <cfg:file type="script">/InheritFromParent.ascx.js</cfg:file>
                </cfg:fileset>
            </cfg:group>
        </cfg:groups>
    </resources>
    <definitionfiles/>
    <extensions>
        <ext:editorextensions>
        <ext:editorextension target="CME">
        <ext:editurls />
        <ext:listdefinitions />
        <ext:itemicons />
        <ext:taskbars />
        <ext:commands />
        <ext:commandextensions />
        <ext:contextmenus />
        <ext:lists />
        <ext:tabpages />
        <ext:toolbars />
        <ext:ribbontoolbars />
        <ext:extendedareas>
            <ext:add>
                <ext:extension assignid="InheritPageProcessFromParent" name="InheritPageProcessFromParent">
                    <ext:control>~/InheritFromParent.ascx</ext:control>
                    <ext:pagetype></ext:pagetype>
                    <ext:renderinblock>false</ext:renderinblock>
                    <ext:dependencies>
                        <cfg:dependency>Tridion.Extensions.GUI.InheritedWFSG.StructureGroup</cfg:dependency>
                    </ext:dependencies>
                    <ext:apply>
                        <ext:view name="StructureGroupView">
                            <ext:control id="WorkflowStructureGroup_ExtendableArea" />
                        </ext:view>
                    </ext:apply>

                </ext:extension>
            </ext:add>
        </ext:extendedareas>
        </ext:editorextension>
        </ext:editorextensions>
        <ext:dataextenders />
    </extensions>
    <commands/>
    <contextmenus/>
    <localization/>
    <settings>
        <defaultpage />
        <navigatorurl />
        <editurls/>
        <listdefinitions/>
        <itemicons/>
        <theme>
            <path/>
        </theme>
        <customconfiguration/>
    </settings>
</Configuration>

And there you have it! If you need help understanding how to deploy the above, check out this tutorial here.