Extending OpenCms: Developing a Custom Widget

0
59
8 min read

Structured Content Types

Support for structured content is a key feature of OpenCms. Structured content types allow different templates to be used to re-skin a site, or to share content with other sites that have a different look. Structured content types are defined by creating XSD schemas and placing them into modules. Once a new content type has been defined, the Workplace Explorer provides a user interface to create new instances of the content and allows it to be edited. There are some sample content types and templates that come with the Template One group of modules. These content types are very flexible and allow a site to be built using them right away. However, they may not fit our site requirements. In general, site requirements and features will determine the design of the structured content types and templates that need to be developed.

BlogEntry Content Type

For designing a blog website it is required that the content type contains blog entries. The schema file for the BlogEntry content type looks like the following :

<!-- ======================================================== 
Content definition schema for the BlogEntry type
========================================================== -->
<!-- 1. Root Element -->
<xsd:schema elementFormDefault="qualified">
<!-- 2. Define the location of the schema location -->
<xsd:include schemaLocation="opencms://opencms-xmlcontent.xsd"/>
<!-- 3. Root element name and type of our XML type -->
<xsd:element name="BlogEntrys" type="OpenCmsBlogEntrys"/>
<!-- 4. Definition of the type described above -->
<xsd:complexType name="OpenCmsBlogEntrys">
<xsd:sequence>
<xsd:element name="BlogEntry" type="OpenCmsBlogEntry" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<!-- 5. Data field definitions -->
<xsd:complexType name="OpenCmsBlogEntry">
<xsd:sequence>
<xsd:element name="Title" type="OpenCmsString" minOccurs="1" maxOccurs="1" />
<xsd:element name="Date" type="OpenCmsDateTime" minOccurs="1" maxOccurs="1" />
<xsd:element name="Image" type="OpenCmsVfsFile" minOccurs="0" maxOccurs="1" />
<xsd:element name="Alignment" type="OpenCmsString" minOccurs="1" maxOccurs="1" />
<xsd:element name="BlogText" type="OpenCmsHtml" minOccurs="1" maxOccurs="1" />
<xsd:element name="Category" type="OpenCmsString" minOccurs="0" maxOccurs="10" />
</xsd:sequence>
<!-- 6. locale attribute is required -->
<xsd:attribute name="language" type="OpenCmsLocale" use="required"/>
</xsd:complexType>
<!—optional code section -->
<xsd:annotation>
<xsd:appinfo>
<!-- Mappings allow data fields to be mapped to content properties -->
<mappings>
<mapping element="Title" mapto="property:Title" />
<mapping element="Date" mapto="attribute:datereleased" />
</mappings><!-- Validation rules for fields -->
<validationrules>
<rule element="BlogText" regex="!.*[Bl]og.*" type= "warning" message="${key.editor.warning.BlogEntry. dontallowblog|${validation.path}}"/>
</validationrules>
<!-- Default values for fields -->
<defaults>
<default element="Date" value="${currenttime}"/>
<default element="Alignment" value="left"/>
</defaults>
<!-- user interface widgets for data fields -->
<layouts>
<layout element="Image" widget="ImageGalleryWidget"/>
<layout element="Alignment" widget="SelectorWidget" configuration="left|right|center" />
<layout element="Category" widget="SelectorWidget"
configuration="silly|prudent|hopeful|fearful| worrisome|awesome" />
<layout element="BlogText" widget="HtmlWidget"/>
</layouts>
<!-- UI Localization -->
<resourcebundle name="com.deepthoughts.templates.workplace"/>
<!-- Relationship checking -->
<relations>
<relation element="Image" type="strong" invalidate="node" />
</relations>
<!-- Previewing URI -->
<preview uri="${previewtempfile}" />
<!-- Model Folder for content models -->
<modelfolder uri="/system/modules/com.deepthoughts.templates /defaults/" />
</xsd:appinfo>
</xsd:annotation>
</xsd:schema>

The BlogEntry content type file is named as blogentry.xsd and it placed in the folder named schemas in modules.

Designing a Custom Widget

Referring to the highlighted code in BlogEntry content type schema file we can see that the category field is populated from a drop-down list provided by SelectorWidget. The SelectorWidget obtains its values from the static configuration string defined within the blog schema file. This design is problematic as we would like category values to be easily changed or added. Ideally, the list of category values should be able to be updated by site content editors. Fortunately, we can create our own custom widget to handle this requirement.

An OpenCms widget is a Java class that implements the I_CmsWidget interface, located in the org.opencms.widgets package. The interface contains a number of methods that must be implemented. First there are some methods dealing with instantiation and configuration of the widget:

  • newInstance: This returns a new instance of the widget.
  • setConfiguration: This method is called after the widget has been initialized to configure it. The configuration information is passed as a string value coming from the declaration of the widget within the schema file of the content type using it.
  • getConfiguration: This is called to retrieve the configuration information for the widget.

Next, there are some methods used to handle the rendering. These methods are called by widget enabled dialogs that might be used in a structured content editor or an administration screen. The methods provide any Javascript, CSS, or HTML needed by the widget dialogs:

  • getDialogIncludes: This method is called to retrieve any Javascript or CSS includes that may be used by the widget.
  • getDialogInitCall: This method may return Javascript code that performs initialization or makes calls to other Javascript initialization methods needed by the widget.
  • getDialogInitMethod: This method may return Javascript code containing any functions needed by the widget.
  • getDialogHtmlEnd: This method is called at the end of the dialog and may be used to return an HTML or Javascript needed by the widget.
  • getDialogWidget: This method returns the actual HTML and Javascript used to render the widget along with its values.
  • getHelpBubble: This method returns the HTML for displaying the help icon relating to this widget.
  • getHelpText: This method returns the HTML for displaying the help text relating to this widget.

Lastly, there are some methods used to get and set the widget value:

  • getWidgetStringValue: This method returns the value selected from the widget.
  • setEditorValue: This method sets the value into the widget.

All these methods have base implementations in the A_CmsWidget class. In most cases, the base methods do not need to be overridden. As such, we will not cover all the methods in detail. If it is necessary to override the methods, the best way to get an idea of how to implement them is to look at the code using them.

All widgets are used in dialog boxes which have been enabled for widgets, by implementing the I_CmsWidgetDialog interface. There are two general instances of these dialogs, one is used for editing structured XML content, the other is found in any dialog appearing in the Administration View. The two classes implementing this interface are:

org.opencms.workplace.CmsWidgetDialog
org.opencms.workplace.editors.CmsXmlContentEditor

The CmsWidgetDialog class is itself a base class, which is used by all dialogs found in the Administrative View.

Before designing a new widget, it is useful to examine the existing widget code. The default OpenCms widgets can be found in the org.opencms.widgets package. All the widgets in this package subclass the A_CmsWidget class mentioned earlier. Often, a new widget design may be subclassed from an existing widget.

Designing the Widget

As mentioned earlier, we would like to have a widget that obtains its option data values dynamically rather than from a fixed configuration string value. Rather than create a widget very specific to our needs, we will use a flexible design where the data source location can be specified in the configuration parameter. The design will allow for other data sources to be plugged into the widget. This way, we can use a single widget to obtain dynamic data from a variety of sources.

To support this design, we will use the configuration parameter to contain the name of a Java class used as a data source. The design will specify a pluggable data source through a Java interface that a data source must implement. Furthermore, a data source can accept parameters via the widget configuration string.

With this design, an example declaration for a widget named CustomSourceSelectWidget would look like this:

<layout element="Category" 
widget="CustomSourceSelectWidget"
configuration="source='com.widgets.sources.MySource'|
option1='some config param'|
option2='another param'"
/>

This declaration would appear in the schema of a content type, using the widget as covered earlier. The configuration parameter consists of name/value pairs, delimited by the vertical bar character. Each name/value pair is separated by the equal to sign and the value is always enclosed in single quotes. The design requires that at least the source parameter be specified. Additional parameters will depend upon the specific data source being used.

The example declaration specifies that the data field named Category will use the CustomSourceSelectWidget widget for its layout. The configuration parameter contains the name of the Java class to be used to obtain the data source. The data source will receive the two parameters named option1 and option2 along with their values. Next, lets move on to the code to see how this all gets implemented.

LEAVE A REPLY

Please enter your comment!
Please enter your name here