Wednesday 21 November 2012

Load Queue Override GUI Extension

How many times have you clicked impatiently on "Show Tasks" while the publishing queue was still loading??? I am not sure about you developers, but Tridion users... thousands! And I understand it! The world around us moves at a high pace... and so does our clicking finger! :)

Every hit on the button is making a call to the database; in a system with many publishing transactions and many concurrent users, this can have an impact on the system performance and stability.

How to solve this neatly? A GUI Extension!!!!

This post will show you the "what", "how" and "why" around the solution.

Functionality provided

While the publishing queue is being loaded, the following actions are executed:
  1. Disable the Show Tasks button 
  2. Disable the Reload button icon
  3. Show a progress message



When the publishing queue finishes loading, the following actions are executed:
  1. Enable the Show Tasks button
  2. Enable the Reload button icon
  3. Archive the progress message



Installing the GUI Extension

  1. Download the zip here.
  2. On your Content Manager server machine, copy the Load Queue Override folder in the shipped file to PATH_TO_EDITOR folder. The PATH_TO_EDITOR can be anywhere in the Content Manager; however the default location for this path when SDL Tridion is installed is TRIDION_INSTALL_PATH\web\WebUI\Editors.
    After this folder is copied, the PATH_TO_EDITOR/Load Queue Override will be created and be the root of your new editor.
  3. Access IIS Manager and navigate to the Content Manager Explorer Website.
  4. Add a new virtual directory inside the WebUI\Editors subfolder, and give it a meaningful alias, for example, LoadQueueOverride
    o    Set the physical location of the virtual directory to the PATH_TO_EDITOR/Load Queue Override folder result of step 1.
  5. To add the extension to the CMS open the file TRIDION_INSTALL_PATH\web\WebUI\WebRoot\Configuration\System.config.
    In the <editors> section, add a new editor element, as follows:
     <editor name="LoadQueueOverride">
        <installpath>PATH_TO_EDITOR\Load Queue Override\</installpath>
        <configuration>Configuration\LoadQueueOverride.config</configuration>
        <vdir>LoadQueueOverride</vdir>
     </editor>
    where vdir is the alias name for your Virtual Directory chosen in step 3.
  6. Restart IIS and clear browser cache
    Please see general information on LiveContent documentation here.

Uninstalling the extension

To revert the changes applied by the extension:
  1. Comment out the editor added to the file System.config in step 4 above
  2. Restart IIS
  3. Clear browser cache

Internal functional description

The Load Queue Override GUI extension consists of two files, a configuration file and a JavaScript file.

Configuration file

The configuration file can hold a lot of information. In our extension, it is quite simple as we are not creating anything new, we are extending existing items.

An important part of our configuration file is related to making the javascript available only when the Publishing Queue popup is opened. This way the impact of the extension is narrowed down to the scope where it is needed.
See below the key part where this is configured:
<cfg:extension target="Tridion.Web.UI.Editors.CME.Views.Popups.PublishQueue">
  <cfg:insertafter>LoadQueueOverrideResources</cfg:insertafter>
</cfg:extension>

Javascript file

We ensure our code is executed after all the system Javascript is loaded and after the view has been initialized.
$evt.addEventHandler($display, "start", onPublishingQueuePopupOpen);
The first steps within the main function will be to select the current view and the Show Task Button, already available at this point of the execution.
var view = $display.getView();
var showTasksBtn = $controls.getControl($("#BtnShowTasks"), "Tridion.Controls.Button");
The extension extends the methods available for the Publishing Queue view when loading the list of transactions.

_populateListPublishTransactions

Before the method is extended, it is saved into a variable that will be used later within the method extension.
var default_populateListPublishTransactions = view._populateListPublishTransactions;
This method is called when the queue starts to load. We extend this method to:
  1. Select the reload button by id.
    Please notice that this button is located in a frame. Because of that it is only available when the list starts loading, therefore we cannot access it at the same time we access the Show Tasks button.
    var frame = window.frames[0];
    var reloadBtn = frame.$controls.getControl(frame.$("#ReloadBtn"),
                                          "Tridion.Controls.Button");
  2. Create the Progress message and assign it to a variable for later retrieval
    window.progressMessage = $messages.registerProgress("Loading list");
    If you want to generate a different type of messages, you can check the available variants by using the browser console and typing “$messages.”. Additionaly, you can browse our GUI extensions API, more specifically “N.Tridion.MessageCenter.html#functions”
  3. Disable the buttons
    showTasksBtn.disable();
    reloadBtn.disable();
  4. Call the original _populateListPublishTransactions previously saved in a variable
    default_populateListPublishTransactions.apply(this);

_onListPublishTransactionsLoaded


Before the method is extended, it is saved into a variable that will be used later within the method extension
This method is called when the list has finished loading. We extend this method to:
  1.  Select the reload button by id.
    Please notice that this button is located in a frame. Because of that it is only available when the list starts loading, therefore we cannot access it at the same time we access the Show Tasks button.
    var frame = window.frames[0];
    var reloadBtn = frame.$controls.getControl(frame.$("#ReloadBtn"),
                                          "Tridion.Controls.Button");
  2. Archive the progress message created above, so that the user does not see it anymore. Here we need its id.
    $messages.executeAction("archive", window.progressMessage.getId())
  3. Enable the buttons
    showTasksBtn.enable();
    reloadBtn.enable();
  4. Call the original _onListPublishTransactionsLoaded previously saved in a variable
    default_onListPublishTransactionsLoaded.apply(this);

Supported/unsupported changes

When extending the GUI, it is also key to know which changes are supported or unsupported.

There are a lot of supported actions, for instance, adding new buttons, new tabs, extending controls or doing things like adding listener to the events... and a lot more.

It is of course ideal to accomplish our goal using only supported changes, but unfortunately, this is not always possible. The final solution for this extension goes through an unsupported path. I will add some comments here:
  • The methods extended are private, so this is obviously unsupported.
  • Generally speaking, we can say extending public methods is supported as long as the original method is called.
  • Extending views will likely get you into an unsupported zone. The major extension points are not placed on views but on controls.
  • Accessing the reload button could not be done following a standard as it is placed in an frame.
As a last note on this topic, despite the unsupported nature of the changes applied by this extension, it has been designed to minimize the impact of future changes by calling the original methods. Furthermore, if maintenance was needed in the future, the scope of changes is only a simple javascript file.

 

 Documentation

Don't forget to check the GUI Extensions API that comes with the product documentation. And remember to download the latest version! (SP1 that is). The file contents.html will give you an overview of the documented items.

Lessons learnt

Helping our thinking

We can learn a lot by looking at the sample code and use it as a reference for a future new requirement, but...  when it comes to designing the extension, what to extend exactly... how to approach that next time?

Something very important for you, extension creator, is to focus on the GUI object (or view) that needs to be intercepted. In the Load Queue Override case, the user actions happen on the "Show Tasks" button and on the "Reload" button, but... what are those triggering? That is the track to be followed In my case, the queue load!

 

Testing on the browser console vs testing from your javascript file

The initial solution was different from the final one. I started by accessing the transactions list object  and adding event handlers to the beginning and end of the loading process. Here is a small sample:
var list = $display.getView()._getListPublishTransactions();
function onLoad() {
 // do stuff
$messages.registerGoal("List reloaded");
}
$evt.addEventHandler(list, "load", onLoad)

The problem I had is that the load event never happened when I wrote this on my javascript, even though it did work on the console.

It seems the list object  I had access to in my code was not the same anymore after the window load process. Furthermore,  the list object seemed to be different every time the filter was updated. So all in all, I couldn’t work on the list object itself, but work on the view and its methods.


Big thanks to Boris and Sergei who helped a lot along the process :)

2 comments:

  1. Nice post.I really like your blog and its content.so keep it..

    ReplyDelete
  2. Thanks Nick! I really appreciate it! Good to know people read it *and* like it :)

    ReplyDelete