In this tutorial you'll build a simple Hello World widget. Widgets are views for displaying content on customizable Clearspace pages.
Currently, people using Clearspace can put widgets on a home page, a community overview page, or a project overview page. They can "design" the layout by dragging widgets onto the design area, setting widget properties, and arranging widgets in a particular way. They can even choose from a variety of column arrangements.
Widgets included with Clearspace provide views of content in Clearspace or on the web, or simply display a message. You can write your own widgets to give views of other content, or even act as enhanced versions of the included widgets.
Here's what the Clearspace home page design space looks like when someone is personalizing it. There's a widget on the left (in preview mode) and one on the right (in edit mode).
Under the covers, a basic widget merely pulls together content and returns HTML for Clearspace to display when the widget is rendered. It can provide properties through which its user can guide what and how content is displayed. Along the way the widget might use an FTL file for presentation, properties files for internationalization, and so on.
This tutorial introduces you to the basics of building widget plugins. The simple widget you'll build with this tutorial displays a Hello World greeting. Other kinds of plugins include macros, actions, filters, and web services.Note: For information on using widgets to design layouts, see Designing Pages with Widgets, included with the Clearspace documentation.
Your widget can be very basic -- such as a single class. But the widget can also include other files to support your design goals.
A basic widget could include:
In this tutorial, you'll create a widget that displays a "Hello World" message. Here are the pieces you'll work with:
This tutorial is about the basics, so you'll want only a few things to build all the pieces it describes:
That's about it. Check out the next section for a few setup steps, then you can get started writing code.
These setup steps are likely pretty common to other extension work you'll do -- whether with macros or plugins or themes.
If you haven't installed Clearspace and used its setup tool, use the following instructions. If you have, you can skip this part. Here are the steps:
It's easier to develop plugins if you have your source and compiled artifacts in certain places. In particular, Clearspace expects your compiled Java classes to live in a classes directory under your plugin's root, your plugin.xml file will need to be at the root, and so on. Jive Software provides an Ant build.xml file you can use to create the project directory hierarchy, compile its sources, and deploy the result to your Clearspace instance for testing. (For the build file and samples, grab the latest developer kit at Jivespace).
This tutorial assumes you're using the Clearspace standalone distribution. Of course, you can use this tutorial with another Clearspace distribution you've set up for development and testing (such as a Clearspace WAR file deployed to your application server). You can also set a jiveHome directory that's external to the distribution (this is the best practice for production). If you do either of these, you'll want to edit a couple of properties in the Ant build file:
<distribution_root>/plugins/plugins/example
ant create.plugin.dirs
Now that you've got a place for your code, you'll actually write some.
Note: At this point, if you don't know where your application log files are, you might want to locate them. The logs can be very helpful for debugging. If you're using the standalone distribution, you'll find the log files at <dist_root>/server/logs.
Every plugin has one. This file tells Clearspace what's in the plugin -- in short, what Clearspace features you're extending -- and where to find code and other supporting files your plugin needs (although not necessarily all of them, as you'll see in the next section).
<plugin xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://www.jivesoftware.com/schemas/clearspace/2_0/plugin.xsd">
<name>Hello World Widget</name>
<description>Simple example of a widget that says hello.</description>
<author>Jive Software</author>
<version>1.0.0</version>
<minServerVersion>2.0.0</minServerVersion>
<!-- Defines a custom widget for this plugin -->
<widget class="com.jivesoftware.clearspace.plugin.example.widget.HelloWorldWidget" />
</plugin>
Pretty simple, really. The <plugin> element's children include information about where the plugin is coming from (you), its version (in case you revise it for upgrade later), and so on. The <minServerVersion> element specifies the minimum Clearspace version that the plugin will run on (Clearspace won't deploy it on earlier versions). There's a corresponding maxServerVersion element, also. The code here tells Clearspace that the plugin includes a widget, and which widget class to load. You'll write that class in a moment.
Here, you'll create a Java class that provides logic for the widget. Your widget class will implement the com.jivesoftware.community.widget.Widget interface, usually by extending the com.jivesoftware.community.widget.BaseWidget class. As you implement the class, you will:
Note: For a basic widget, your classpath should include the main Clearspace JAR file, which is named clearspace-<version>.jar.
When you extend the BaseWidget class, you'll need to implement three methods from the Widget interface that aren't implemented in BaseWidget:
You'll see that each of these methods takes a WidgetContext instance as a parameter. The WidgetContext class includes methods through which you can get information about the context in which the widget instance is executing -- its containing community, its current user, request and response objects, and so on. In some cases, you'll simply be passing the instance to other Clearspace methods.
Each widget exposes properties that the page designer can use to customize how and what the widget displays. For the two default properties — title and description — you need only implement getters to return the values that Clearspace should display, as described above. For properties you add, you do the following:
In the following example, the title ("Hello World Widget") and description ("Displays a 'Hello World' message.") are properties implemented through the required accessors getTitle and getDescription.
Another property, greetUser, is implemented in the widget class through its own annotation and accessors; the radio buttons here are automatically provided because the accessors get and set a boolean value. The greetUser property also provides title and description values through a properties file included in the widget.
See the Java and property file examples in this topic.
If your FTL file uses property variables, you can add values to the FreeMarker context from within your widget class. When Clearspace applies your template, the values you set will be used in generating the output.
Note that Clearspace doesn't add the widget class itself to the FreeMarker context in the way it does with action plugins. This means that a ${x} property in the FreeMarker template won't result automatically in a call to a getX() method in your widget class.
Instead, you should override the BaseWidget.loadProperties(WidgetContext, ContainerSize) method to add in any FreeMarker variables you need. Your implementation should call the superclass's implementation to retrieve the properties Map, then set additional properties by putting them into the map. In other words, you'd add the x variable to the FreeMarker context using your loadProperties method implementation.
See the widget class code below for an example.
BaseWidget provides helper methods you might find useful. Here are a few:
package com.jivesoftware.clearspace.plugin.example.widget;
import com.jivesoftware.community.annotations.PropertyNames;
import com.jivesoftware.community.widget.*;
import java.util.Map;
// The WidgetTypeMarker annotation tells Clearspace which kinds of pages this
// widget can be used on. This widget will show up in the widget list
// on the Clearspace home page (the one seen by everyone) and a community overview
// page. Other supported values include WidgetType.PERSONALIZEDHOMEPAGE and WidgetType.PROJECT.
@WidgetTypeMarker({WidgetType.HOMEPAGE, WidgetType.COMMUNITY})
@PropertyNames("greetUser")
public class HelloWorldWidget extends BaseWidget {
// FreeMarker template for rendering preview and published widget.
private static final String FREEMARKER_FILE = "/plugins/example/resources/hello-world.ftl";
// To hold the value of a custom property.
private boolean greetUser = false;
/**
* Called by Clearspace to get the description that should be displayed for
* the widget when the user hovers over it in the "customize" mode list of
* widgets.
*
* @param widgetContext
* Context in which the widget instance is executing.
*/
public String getDescription(WidgetContext widgetContext) {
return "Displays a 'Hello World' message.";
}
/**
* Called by Clearspace to get the widget's default title. The user will be
* able to change this. If they do, their new title will be set with a call
* to the final method BaseWidget.setCustomTitle.
*
* @param widgetContext
* Context in which the widget instance is executing.
*/
public String getTitle(WidgetContext widgetContext) {
return "Hello World Widget";
}
/**
* Called by Clearspace to get the value for the greetUser property.
*
* @return true if the user should be greeted; false to greet the world.
*/
public boolean getGreetUser() {
return greetUser;
}
/**
* Called by Clearspace to set the value for the greetUser property.
*
* @param greetUser
* true to greet the user; false to greet the world.
*/
public void setGreetUser(boolean greetUser) {
this.greetUser = greetUser;
}
/**
* Called by Clearspace to get the HTML used to display the widget when it's
* previewed or published.
*
* @param widgetContext
* Context in which the widget instance is executing.
* @param containerSize
* An enum constant representing the size of the widget
* instance's current container: LARGE or SMALL.
*/
public String render(WidgetContext widgetContext,
ContainerSize containerSize) {
// Process the included FTL file to render the HTML for display.
return applyFreemarkerTemplate(widgetContext, containerSize,
FREEMARKER_FILE);
}
/**
* Called by Clearspace to get properties for use in your FTL file. These
* will be added to the FreeMarker context.
*
* @param widgetContext
* Context in which the widget instance is executing.
* @param containerSize
* An enum constant representing the size of the widget
* instance's current container: LARGE or SMALL.
* @return A map of the properties and their values.
*/
protected Map<String, Object> loadProperties(WidgetContext widgetContext,
ContainerSize containerSize) {
// First load existing properties.
Map<String, Object> properties = super.loadProperties(widgetContext,
containerSize);
// Get the name of the community this instance is in, then add it as a
// property.
String communityName = ((CommunityWidgetContext)widgetContext).getCommunity().getName();
String userName = widgetContext.getUser().getName();
properties.put("communityName", communityName);
properties.put("userName", userName);
properties.put("greetUser", greetUser);
return properties;
}
}Here's a summary of what's going on in this class:
Next, you'll need to create a .properties file that provides the strings used in the design-time user interface. In the widget JAR, this file will be located at <jar_root>/classes/beans/HelloWorldWidget.properties.
# Resource bundle for Hello World Widget # Author of widget author=Me # Version of the widget version=1.0 # Properties - Display name and description greetUser.displayName=Greet the User greetUser.shortDescription=Greets the user by name; otherwise, greets the World.
Notice that the "greetUser" part of the keys matches the name of the property as given in the @PropertyNames annotation.
You'll use a FreeMarker template (FTL) file to shape the data you're presenting in your published widget. The FTL file must be on the Clearspace classpath; that's easily accomplished by including it in your widget JAR file, then loading it at the relative location using the applyFreemarkerTemplate method (see the widget class code example in this topic).
When someone clicks the "customize" link to begin designing a layout, the design space includes a list of installed widgets and an area for arranging widgets. This area can be divided into multiple columns; the user chooses which configuration they want. In a multi-column layout, when someone drags your widget between large and small columns, Clearspace resizes the widget from a large size to a small size.
Here's a view with the same widget on both sides of a two-column layout:
Each widget is responsible for its display characteristics, including adjusting within its frame when its size changes. In your FTL file, you can handle each of these cases by providing a large rendering and a small one. You do that by testing for the value of the enum com.jivesoftware.community.widget.Widget.ContainerSize; its two values are LARGE and SMALL.
<style type="text/css">
.jive-widget .jive-widget-body p.gobig {
font: 16px verdana, helvetica, sans-serif;
}
.jive-widget .jive-widget-body p.getsmall {
font: 10px verdana, helvetica, sans-serif;
}
</style>
<#-- Use the widget's greetUser property to define the greeting style. -->
<#if greetUser>
<#assign greeting = "Hello " + userName + "!">
<#else>
<#assign greeting = "Hello World!">
</#if>
<#-- Render for display in a small area. -->
<#if containerSize == enums['com.jivesoftware.community.widget.Widget$ContainerSize'].SMALL>
<p class="getsmall">${greeting} Welcome to the ${communityName} community!</p>
<#-- Render for display in a large area. -->
<#else>
<p class="gobig">${greeting} Welcome to the ${communityName} community!</p>
</#if>Use the build.plugins Ant target to compile and package the code into a JAR file, then use the deploy.plugins target to copy the plugin into the <jiveHome>/plugins directory.
ant build.plugins
to build, then
ant deploy.plugins
to deploy to your server. There's no need to restart your server, but you'll want to wait five or ten seconds for Clearspace to deploy the plugin for use.
To learn more about plugins, be sure to check out Jivespace, Jive's developer community site.