3

This post will be dual purpose: first, to announce the release of the dotCMS IRC Client plugin, and second to provide a tutorial explaining how to launch a Velocity portlet plugin. Velocity portlets are a relatively simple way of building tools that can be built into the backend of dotCMS and be used as tabs that users can access. In this case, this plugin builds a portlet that includes the Freenode web based IRC client and an extra collaboration tool. We will deconstruct the plugin piece by piece so that you can see the components needed for such a tool.

To start off, you’ll want to set up the files and folders that you’ll need for the plugin. A basic portlet only requires a handful of files. The files listed in italics are required for any plugin – the rest are specific to this one:

  • com.learndotcms.plugins.ircsupport
    • /conf
      • Language-ext.properties
      • plugin.properties
      • portal-ext.xml
    • /static_velocity
      • /portlet
        • irc.vm
    • build.xml
    • MANIFEST.MF

The two files in the root of your plugin are required, and extremely straightforward. The build.xml file is just to name the plugin internally, and the MANIFEST.MF file carries the specific details of the plugin. We don’t need to define a deploy class in the manifest file for this plugin, because our portlet stands on its own and doesn’t need to do anything when it’s installed. Otherwise, you can use the Deploy-Class field to list a Java class provided in the plugin that should be ran when the plugin is deployed. This can be done to do things like build a structure, add content, or do anything else that requires API interaction necessary for the plugin.

build.xml

<project name="dotCMS IRC Channel Client" default="build">
    <import file="../common.xml"/>
</project>

MANIFEST.MF

Manifest-Version: 1.0
Deploy-Class:
Plugin-Name: dotCMS IRC Channel Client
Plugin-Version: 1.110106
Author: Michael Fienen (http://learndotcms.com)

From there, you’ll set up the two folders: /conf for the configuration elements, and /static_velocity/portlet which will store the code for our portlet. There’s very little configuration needed, and if you use the Hello World plugin as an example, it’s easy to modify. The language extension file is used to define the name of the portlet that will be visible in the back end:

Language-ext.properties

javax.portlet.title.EXT_IRC_SUPPORT=dotCMS IRC Client

In our portal extension file, mostly we just set the portlet name and the path to our Velocity file that will be used for the portlet. Take note that the portlet-name node (line 3) corresponds to the portlet title value from Language-ext.properties file. You’ll also set the view-template value on line 8 to correspond to your path inside the /static_velocity folder of your plugin.

portal-ext.xml

<!-- Use this file to add portlets and extend the portlet file -->
<portlet>
    <portlet-name>EXT_IRC_SUPPORT</portlet-name>
    <display-name>dotCMS IRC Client</display-name>
    <portlet-class>com.liferay.portlet.VelocityPortlet</portlet-class>
    <init-param>
        <name>view-template</name>
        <value>/static/plugins/com.learndotcms.plugins.ircsupport/portlet/irc.vm</value>
    </init-param>
    <expiration-cache>0</expiration-cache>
    <supports>
        <mime-type>text/html</mime-type>
    </supports>
    <resource-bundle>com.liferay.portlet.StrutsResourceBundle</resource-bundle>
    <security-role-ref>
        <role-name>CMS User</role-name>
    </security-role-ref>
</portlet>

Finally, the plugin.properties file is required, and can be left simply as:

plugin.properties

## The default for the reload.force is true.  If it is set to false the properties will only load once from
## the file. After that they will be stored and maintained in the database.  If true every time the server
## restart the properties will be cleared and reloaded.
reload.force=true

Now, the fun starts. The heavy lifting for this plugin happens almost entirely in the irc.vm file. As a Velocity portlet, dotCMS effectively takes the file you define above in the portal extension file and wraps it in the back end tab structure, making it look nice, and allowing you to use code you are familiar with. We’ll start the file with some simple Velocity code that takes care of checking some cookie values and setting up option defaults:

irc.vm (part 1/3)

## SEE IF A DEFAULT IRC NICKNAME HAS BEEN SET
#set($dotCMSIRCNick = $cookietool.get('dotCMSIRCNick').value)
#if($dotCMSIRCNick)
    ## SET THE STRING USED IN THE iframe SOURCE URI FOR PRESET NICKNAME
    #set($enableNick = "nick=$!{dotCMSIRCNick}&")
#end
## LOOK FOR DEFAULT HEIGHT AND WIDTH FOR THE POPUP CHAT CLIENT
#set($ircPopupW = $cookietool.get('ircPopupW').value)
#set($ircPopupH = $cookietool.get('ircPopupH').value)
## IF NO HEIGHT OR WIDTH DETECTED, USE DEFAULTS
#if(!$ircPopupW || $ircPopupW.length() == 0)
     #set($ircPopupW = 640)
#end
#if(!$ircPopupH || $ircPopupH.length() == 0)
    #set($ircPopupH = 400)
#end
## ACCOUNT FOR THE INNER HEIGHT OF THE POPUP CHAT CLIENT
#set($ircPopupIframe = $math.sub($ircPopupH,22))

Next, we hit some basic CSS for the layout, and a whole lotta JavaScript. The JavaScript handles the popups (for chat and the Collabedit windows), and setting values from the option popup.

irc.vm (part 2/3)

<style type="text/css">
${esc.h}credit {
    font-size:.8em;
    position:absolute;
    right: 0;
}
${esc.h}ircButtons {
    position:relative;
    text-align:center;
}
${esc.h}ircContainer {
    margin:1em auto;
    width:80%
}
    ${esc.h}ircContainer iframe {
        border:1px solid #ccc;
        height:400px;
        margin:1em 0 2em;
        width:100%;
    }
.subNavCrumbTrail {
    margin:-7px 0px 10px;
}
</style>

<script type="text/javascript">
dojo.require("dojo.cookie");
dojo.require("dijit.form.Button");
dojo.require("dijit.form.TextBox");

/* Set up configurable settings based on cookie default values */
var ircPopupH = '$!{ircPopupH}';
var ircPopupW = '$!{ircPopupW}';
var iframeH = '$!{ircPopupIframe}';
var nick = '$!{dotCMSIRCNick}';

/* Set the user options from the popup form */
setCookie = function() {
    /* Get config field values */
    var isValid = true;
    var nickVal = document.getElementById('ircNick').value;
    var popupW = document.getElementById('popupW').value;
    var popupH = document.getElementById('popupH').value;

    /* Validate the fields */
    if(!isAlpha(nickVal) || !isNumeric(popupW) || !isNumeric(popupH)) {
        isValid = false;
    }

    if(isValid) {
        /* Default IRC Nickname */
        nick = nickVal;
        dojo.cookie("dotCMSIRCNick", nick, {
            expires: 365
        });

        /* Default Chat Window Popup Width */
        ircPopupW = popupW;
        dojo.cookie("ircPopupW", popupW, {
            expires: 365
        });

        /* Default Chat Window Popup Height */
        ircPopupH = popupH;
        iframeH   = ircPopupH - 22;
        dojo.cookie("ircPopupH", popupH, {
            expires: 365
        });
        dijit.byId('optionsDiv').hide();
    } else {
        alert('Please check configuration settings');
    }
};

/* Open IRC chat popup window */
openIRCPopup = function() {
    newwindow = window.open('','irc','height=' + ircPopupH + ',width=' + ircPopupW + ',resizable=0');
    var tmp = newwindow.document;
    tmp.write('<html><head><title>dotCMS IRC Client</title>');
    tmp.write('</head><body style="margin:0;padding:10px;">');
    tmp.write('<iframe style="width:100%;height:' + iframeH + 'px;border:1px solid #ccc;" src="http://webchat.freenode.net?nick=' + nick + '&channels=dotcms&uio=MTE9MjM20f"></iframe>');
    tmp.write('</body></html>');
    tmp.close();
};

/* Open options popup */
showOptions = function() {
    dijit.byId('optionsDiv').show();
};

/* Open a collabedit popup */
collaborate = function() {
    window.open('http://collabedit.com/new','collabedit','top=200,left=200,height=600,width=800,resizable=1,status=0,location=1,menubar=0,directories=0');
}

/* Validator functions */
isNumeric = function(str) {
    return str.match(/^\d+$/);
};
isAlpha = function(str) {
    return str.match(/^[A-Za-z0-9_-]*$/);
};
</script>

Finally, we just need to lay out the HTML for the visible part of the portlet. In this case, we use an iframe embed for the Freenode web IRC client (based on qwebirc). Below that, a few buttons that allow the user to open a popup with the chat client in it (created using the openIRCPopup() function above on line 76), open a popup with a Collabedit panel in it (useful for brainstorming or trying to get help from folks in the IRC channel), and finally the options popup. Be sure to note in the HTML the use of Dojo properties on the buttons and dialog to make the look and feel of everything blend in with the rest of the backend.

irc.vm (part 3/3)

<div class="subNavCrumbTrail">
    <ul>
        <li id="selectHostDiv" style="" onclick="window.location='/admin';"><img src="/html/images/icons/globe-grey.png" width="16" height="16" title="global page" align="absmiddle" hspace="5" />Home</li>
        <li><span>dotCMS IRC Chat</span></li>
    </ul>
    <div class="clear"></div>
</div>  <!-- .subNavCrumbTrail -->
<div class="clear"></div>

<div id="ircContainer">
    <iframe src="http://webchat.freenode.net?$!{enableNick}channels=dotcms&uio=MTE9MjM20f"></iframe>

    <div id="ircButtons">
        <div id="credit">
            Brought to you by <a href="http://learndotcms.com/" target="_blank">LearndotCMS.com</a>
        </div>  <!-- ${esc.h}credit -->

        <button dojoType="dijit.form.Button" iconClass="browseIcon" onClick="openIRCPopup();" tabindex="0">Popout Chat</button>
        <button dojoType="dijit.form.Button" iconClass="publishIcon" onClick="collaborate();" tabindex="1">Collaborate</button>
        <button dojoType="dijit.form.Button" iconClass="editIcon" onClick="showOptions();" tabindex="2">Options</button>
    </div>  <!-- ${esc.h}ircButtons -->
</div>  <!-- ${esc.h}ircContainer -->

<div id="optionsDiv" dojoType="dijit.Dialog" title="IRC Options" style="display:none;">
    <p><label for="ircNick">Default Nickname:</label> <input type="text" name="ircNick" id="ircNick" tabindex="3" value="$!{dotCMSIRCNick}" /></p>
    <p><label for="popupW">Popup Size:</label> <input type="text" name="popupW" id="popupW" tabindex="4" value="$!{ircPopupW}" placeholder="w" size="3" />px by <input type="text" name="popupH" id="popupH" tabindex="5" value="$!{ircPopupH}" placeholder="h" size="3" />px</p>
    <div style="text-align:center;">
        <button dojoType="dijit.form.Button" iconClass="saveIcon" onClick="setCookie();" tabindex="6">Save Options</button>
        <button dojoType="dijit.form.Button" iconClass="cancelIcon" onClick="dijit.byId('optionsDiv').hide();" tabindex="7">Cancel</button>
    </div>
    <div>
        <small><em>Clear your cookies to reset IRC settings to their defaults.</em></small>
    </div>
</div>

Now, save all this stuff out, and dump your folder into the /plugins directory in your dotCMS install. From here on out, it’s all simply configuration. Start but shutting down your server, open a command prompt, navigate to the dotCMS directory and run the command:

  • ant clean-plugins deploy-plugins
Set up a new portlet on a tab

Set up a new portlet on a tab

Then, start dotCMS back up, log in to the back end, and under the CMS Admin tab open up Roles, Tabs, & Tools. How you deploy this particular portlet is up to you. For instance, you could create a new tab and dedicate it to the chat client, or make it a child of an existing tab, such as the Home tab. In the screenshot to the left we have opened up the Home tab and selected the new portlet from the Tools dropdown list. Click the Add button, and you can drag and drop the portlets in the order you want them to appear in the menu (if you create a dedicated tab, you can just put one portlet on it and there won’t be a menu, clicking the tab will just go straight to it.). Refresh the page, and you’re golden. You can repeat this process for whichever roles you want to have access to the client.

The IRC Portlet plugin

The IRC Portlet plugin

Wherever you tell dotCMS to load the portlet, clicking on it will open a page that will read in the source VM file from your plugin. In our case, it shows our embedded chat client, and extra buttons. By researching the setup of other portlet pages in the backend, applying the appropriate markup to the buttons results in a very familiar feel, allowing you to build something that isn’t just crowbarred into the backend (though, nothing’s stopping you from doing it ugly, it’s just really easy to get that extra polish).

Options popup

Options popup

We also added a Dijit powered dialog box to control the portlet options. All it is needs is a hidden div with some markup, and Dojo handles the rest (lines 24-34 in irc.vm part 3). In some cases, you can hardcode plugin options into the plugin.properties file. That’s fine. In this case, I wanted the options to be specific per user, without going through the effort of tying into the user settings. The answer was simply adding in a little JavaScript to set up cookies that will last for a year on the machine. When the page loads, we read in those cookie values and use them to customize the chat client and its popup.

Overall, this is about as simple a process as you could ask for. Being Velocity powered, you can access anything you would normally be able to get into on web pages. That might be usage of viewtools, pulling content, or utilizing macros. Better yet, you don’t need to learn any complex Struts mapping or Java code to try it out.

[ Download the IRC Plugin (v1.110106) ]

Image Gallery

3 Responses to “Building a Velocity Portlet Plugin for IRC”

  1. rajesh says:

    Good article Michael , I tried it on my local macine it worked very well 🙂

    Just need your help if I want it in webpage then what changes I need to do. Actually we want to implement live chat in our website.

    Thanks in advance for your help 🙂

    Cheers,
    Rajesh

  2. fvn says:

    I have done all the steps but I cant find the dotIRC portlet on my dotcms.
    help?

  3. fvn says:

    I cant find the portlet on the website. what did i do wrong?

Leave a Reply