The Dockbar provides one-stop shopping for your Portal administration needs; but sometimes you either don’t like the default look of it (if you’re building from _styled) or you don’t feel like styling it from scratch (if you’re building from _unstyled). You can provide links outside the Dockbar; but they’re not all created the same way, and Liferay has moved the goalposts a bit between 6.0 and 6.1. We’re going to build a menu with the following actions:

  • Add Portlet
  • Change Layout Template
  • Toggle Edit Controls
  • Edit User Profile
  • Open Control Panel
  • Sign In/Sign Out

For the purposes of this tutorial, we’re just going to use a barebones unordered list to which you can add whatever styling hooks you want. Since adding some pieces of this functionality is more straightforward than adding others, we’ll tackle the easy ones first and move up in degree of difficulty as we go on.

Sign In/Sign Out

Let’s build a foundation for our menu by adding a little Velocity logic to show the menu to signed-in users and a sign-in link for signed-out users. Liferay helpfully provides some useful shortcuts in init.vm.

<ul>
    #if ($is_signed_in)
        <li>
            <a href="$sign_out_url">Sign Out</a>
        </li>
    #else
        <li>
            <a href="$sign_in_url">Sign In</a>
        </li>
    #end
</ul>

Edit User Profile/Open Control Panel

Velocity aliases help us out here, too – just plug them in. We’ll add in a touch more logic to only show the Control Panel link to administrators.

<li>
    <a href="$my_account_url">Edit My Profile</a>
</li>
#if ($permissionChecker.isOmniadmin())
    <li>
        <a href="$my_account_url">Control Panel</a>
    </li>
#end

Toggle Edit Controls

Showing and hiding edit controls is as simple as a little JavaScript to toggle the classes .controls-hidden and .controls-visible on the <body> element of the page. In the template:

<li>
    <a id="toggle-edit-controls" href="#">Toggle Edit Controls</a>
</li>

…and the JS:

A.one('#toggle-edit-controls').on('click', function(event) {
    event.preventDefault();
    if (A.one('body').hasClass('controls-visible')) {
        A.one('body').removeClass('controls-visible')
            .addClass('controls-hidden');
    } else {
        A.one('body').removeClass('controls-hidden')
            .addClass('controls-visible');
    }
});

Add Portlet

Here’s where things start getting a bit gnarly.  First, add a link to the menu just like we did for ‘Toggle Edit Controls’:

<li>
    <a id="add-application" href="#">Add Application</a>
</li>

We’re going to use AUI to fire the dialog.

AUI().use(
    'aui-dialog',
    'liferay-layout-configuration',
    function(A) {
        // Create the Add Applications dialog
        var addApplicationsDialog = new A.Dialog({
            title: 'Add Application',
            width: 280,
            visible: false
        }).plug(A.Plugin.IO, {
            after: {
                success: function(event, id, obj) {
                    Liferay.LayoutConfiguration._dialogBody = addApplicationsDialog.get('contentBox');
                    Liferay.LayoutConfiguration._loadContent();
                }
            },
            autoLoad: false,
            data: {
                doAsUserId: themeDisplay.getDoAsUserIdEncoded(),
                p_l_id: themeDisplay.getPlid(),
                p_p_id: 87,
                p_p_state: 'exclusive'
            },
            showLoading: false,
            uri: themeDisplay.getPathMain() + '/portal/render_portlet'
        });
    }
);

We can then attach a click handler to fire it…

A.one('#add-application').on('click', function(event) {
    event.preventDefault();
    addApplicationsDialog.render().show().io.start();
});

…but expanding and collapsing the portlet categories will only work every other time you launch the dialog, which is something we’d obviously like to avoid. The trick to getting around that is only using .render() the first time and then using .show() and .hide() to subsequently toggle visibility.

A.one('#add-application').on('click', function(event) {
    event.preventDefault();
    if (addApplicationsDialog.get('visible') == false) {
        if (addApplicationsDialog.get('rendered') == false) {
            addApplicationsDialog.render().show().io.start();
        } else {
            addApplicationsDialog.show();
        }
    } else {
        addApplicationsDialog.hide();
    }
});

(Hat-tip to Patrick Stackpoole for the tip on .render() vs. .show() and .hide().)

The major drawback to this approach is the lack of drag-and-drop support from the ‘Add Applications’ dialog to the page. Using the ‘Add’ Link and dragging the portlet once it’s on the page works perfectly, but this one hiccup is worth mentioning.

Change Layout Template

Liferay 6.0 provided the method Liferay.LayoutConfiguration.showTemplates(); – attach that to a click handler, and you were off to the races. In revamping the Dockbar for 6.1, that method disappeared, leaving developers to try to figure out what happened and attempt to fix it.

The trick is to grab the URL for the Control Panel piece in Velocity and display it in an iframe popup. (Navigating straight to the page will require a back-button-and-refresh to display the newly-chosen layout, and displaying it in a non-iframe popup will have the Control Panel theme bleed over onto the page, causing much ugliness.)

Let’s start in our template by getting the URL and stashing it in a variable and adding our menu item:

#set ($changeLayoutUrl = $themeDisplay.getURLLayoutTemplates().toString())

<li>
    <a id="change-layout" href="#">Change Layout</a>
</li>

…and use AUI for our dialog and iframe pieces:

AUI().use(
    'aui-dialog',
    'aui-dialog-iframe',
    function(A) {

        // Grab 'Change Layout' URL from Velocity
        var changeLayoutUrl = '$changeLayoutUrl';

        // Create dialog
        var changeLayoutDialog = new A.Dialog({
            centered: true,
            modal: true,
            title: 'Change Layout',
            width: 960,
            on: {
                close: function() {
                    // Refresh on close to show new layout
                    document.location.href = Liferay.currentURL
                }
            }
        })
        // Load content in iframe
        .plug(A.Plugin.DialogIframe, {
            iframeCssClass: 'dialog-iframe',
            uri: changeLayoutUrl
        });
    };
)

…and finally attach our click handler.

A.one('#change-layout').on('click', function(event) {
    event.preventDefault();
    changeLayoutDialog.render().show();
});

There is one drawback to this solution: since the Control Panel piece is called in an iframe, the Cancel button and ‘hide this dialog’ link won’t work – the user will have to close the dialog with the regular ‘Close’ button in the upper right-hand corner. (You could write a hook as a workaround; but then it would be broken in the actual Control Panel, which I see as an even worse solution.)

Conclusions

Liferay makes it very easy to duplicate some things from the Dockbar and rather tricky for others. If you do decide to dispense with the Dockbar and roll your own, though, be on the lookout for a couple drawbacks – our custom links work well, but not perfectly; and it involves putting at least some JavaScript in a Velocity template, which is not awesome from a code-tidiness standpoint. The completed source for our little demo is on GitHub – share and enjoy!