|
|
BROWSE
All Titles WordPress Web Services SOA BPEL Web Graphics & Video Web Development RAW Portugues, Espanol, Italiano PHP/MySQL Oracle Open Source Networking & Telephony Moodle Microsoft & .NET Linux Servers Joomla! JBoss Java e-Commerce Drupal CRM Content Management Beginner Guides Architecture and Analysis AJAX Future Titles Recently Published Titles Want to know more about Packt's Article Network? Interested in contributing your article ideas? Please visit our FAQ for more information. See More |
Extending OpenCms: Developing a Custom Widget
Structured Content TypesSupport 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 TypeFor 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 : <!-- ======================================================== The BlogEntry content type file is named as blogentry.xsd and it placed in the folder named schemas in modules. Designing a Custom WidgetReferring 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:
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:
Lastly, there are some methods used to get and set the widget value:
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 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 WidgetAs 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" 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. The Widget CodeBy examining the existing OpenCms widget code base, we see that the widget can be based on the SELECT widget, which is implemented by the CmsSelectWidget class. Recall that the SELECT widget obtains its option values statically from the configuration parameter. We will subclass the widget to modify its behavior, so that it will get the data using the Java data source instead. This turns out to be quite straightforward, requiring only a few method overrides: public class CmsCustomSourceSelectWidget extends CmsSelectWidget { The class starts with the default constructor and some member variables which we'll discuss later on. The newInstance method is overridden to return an instance of the widget. To manage the configuration information, the setConfiguration method is overridden. This method gets called right after a widget has been constructed to allow it to initialize. The method uses the data source to read the option data: public void setConfiguration(String configuration) { OpenCms 7 Development
After calling the base class, an instance of the CustomSourceConfiguration class is created with the configuration data. The class is used to encapsulate the parsing and formatting of configuration options. It also provides an easy way to retrieve the configuration values: public class CustomSourceConfiguration { Back in the setConfiguration method, the source parameter is obtained from the configuration data. This value is then used to construct an instance of the data source using Java reflection. In case of an error, it is logged to the console and a fallback source is used. The fallback source returns the error message in the option value to indicate that there was a configuration problem. After the data source has been instantiated, the configuration information is passed to it. Note that the data source must adhere to the I_WidgetSelectSource interface. We will describe that interface later on. The next method in the class is the getDialogWidget method. This method returns the actual HTML for the widget: public String getDialogWidget(CmsObject cms, I_CmsWidgetDialog widgetDialog, I_CmsWidgetParameter param) { The code was copied from the base CmsSelectWidget implementation as it is very similar. After building the SELECT tag, the option values are read from the data source. This is encapsulated in the getSelectOptionData method which places the data list into a member variable. The option tags are then built from the data list. The getSelectedValue method is used to retrieve the value of any previously selected option value. This method is implemented by the base class, and does not need to be changed. While building the Option tags, the code looks for a current value and sets the SELECTED attribute, if a match is found. This ensures the state of any previously selected value. At the end of the class is the method used to obtain the option values from the data source: protected List getSelectOptionData(CmsObject cms) { The method first passes the configuration information to the data source to give it a chance to initialize itself. It then reads the data from the data source. By default, the data is read each time the method is called. But in some cases, this may be time consuming. To speed this up, the method supports caching the result if the cacheData option parameter is set. That's it for the widget class. Next we'll go over the data source interface, which is very straightforward. Custom Source Interface and ImplementationsThe interface designed for data sources is quite simple, consisting of only two methods: public interface I_WidgetSelectSource { As seen in the widget code, the setConfiguration method is first called to give the data source a chance to initialize and capture any configuration data. The getValues method is then called to retrieve the option data. The option data must be returned in a List of SelectOptionValue objects, which is a simple bean class containing name/value pairs used for the option tag. Let's have a look at couple of data sources that implement this interface. First, there is the DefaultDS class. This data source is used as a fallback in case the configured one can not be instantiated: public class DefaultDS implements I_WidgetSelectSource { The getValues method constructs an ArrayList and adds a single value to it. The value is an error string indicating that the configuration has failed. The string is retrieved using the OpenCms Messages class, which is discussed later on. The data source doesn't use the configuration data. So, the setConfiguration method is empty. Now let's look at a more interesting data source. Recall that we would like the select widget to retrieve values from some list that content editors can edit. A structured content type is a perfect candidate for such a list. The valuelist content type will allow many instances of data lists to be created: <!-- ============================================================ The content type has a single field named Value, and has an arbitrary upper limit of 1000 items. To install a content type the resourcetype and explorertype entries need to be added to the opencms-modules.xml configuration file. First is the resource type entry: <resourcetypes> And then the explorer type entry: <explorertype name="valuelist" key="fileicon.valuelist" icon="xmlcontent.gif" reference="xmlcontent"> After adding the new content type, a new list can be created within the Workplace Explorer. The list will maintain the Category values assigned to Blog entries and will be placed somewhere that the content editors can access it. The last thing needed is a data source that gets its option values from an instance of this content type. Again, we will use a design that is more general in purpose rather than specific. The data source class will be able to read values from any content type that has repeating fields. This way it will not be limited to reading list values from the ValueList content type. This will be done by using the configuration parameter to instruct the data source how to get the values. The configuration string will need to have these options:
OpenCms 7 Development
Now lets look at the code which implements this. The data source class is named ContentFieldListDS: public class ContentFieldListDS implements I_WidgetSelectSource { The member variables will contain the configuration data. They are initialized in the constructor, and then set in the setConfiguration method: public void setConfiguration(CustomSourceConfiguration config) A utility method is used to check the configuration parameters. Last is the getValues method, where the content values are actually read from the data source: public List getValues(CmsObject cms) { The method begins by allocating a list to contain the returned values. If the configuration values cannot be set properly, then an error return message is returned in the list. Otherwise, the routine reads the configured CmsResource. Another validation check is done to insure that the content type of the resource matches the configured one. Again, an error message is returned if this is not the case. After the validation check, the resource is read and unmarshalled. This converts its XML representation into a CmsXmlContent instance. After this is done, the configured content fields are read into an array and then converted into SelectOptionValue instances. For safety, the entire effort is surrounded with a catch block which returns any caught errors. The error messages have not been externalized as done in the earlier class. This exercise will be left to the reader. But first we will cover the Messages class used for doing this. Using OpenCms Message Strings for LocalizationOpenCms provides full support for localization. The messages.properties file to contains user interface strings. The property values can also be used to localize message strings in Java classes. The I_CmsMessageBundle interface provides the support for this. The interface can be a bit confusing at first, but fortunately, most of the methods are already implemented in the A_CmsMessageBundle class. To support localized strings in our own classes, we just need to provide a subclass of the A_CmsMessageBundle class. Each Java package can have its own messages.properties file with a corresponding Java class. The convention is to name the class Messages. Let's take a look at the one used by the widgets: public final class Messages extends A_CmsMessageBundle { A static declaration containing the name of the bundle appears first. The name will always match the package that the Java class is in. Next, is the static declaration and instantiation of the class. There needs to be only one instance of the class, as messages are a read-only resource. To further ensure that only one instance of the class is created, the default constructor is declared private. The next two methods return the class instance and the name of the bundle respectively. Last in the class is the list of localized messages. The convention used to format the messages is to declare the message name as a string constant and to have the message value match the constant name. A localized message can then be retrieved through its message constant value, for example: String msg = Messages.get().getBundle(). key(Messages.DEFAULT_DS_ERROR_MSG_0) Looking at the variations of the key method on the I_CmsMessageBundle interface, we see that it supports the ability to parameterize the message string with up to three values. The convention used to format message keys is to append a numeral at the end, indicating the number of parameters in the message. This was done in the error handling of the widget's setConfiguration method to return the name of the class and the exception message: Messages.get().getBundle(). Relating message constant values to the message strings in the messages.properties file is easy. Each entry in the properties file is named according to the string value of the message constant: # messages.properties for widget Parameterized messages are formatted using numbered macro placeholders, as shown previously. Registering the Widget with OpenCmsThe last step in creating the widget is to register it with OpenCms. Widgets are registered in the XML file located in the configuration directory: <OPENCMS_INSTALL>WEB-INFconfigopencms-vfs.xml To add the widget, locate the <WIDGETS> section and add the widget definition: <!-- Custom Widget --> The class parameter contains the fully qualified class name of the widget. The alias is the name that will be used in the layout declaration of a schema file using the widget. Any changes made to the file will require a restart of OpenCms to take effect. After compiling the code, deploying the class files and registering the widget it can finally be used. Here is an example of what a schema file using the widget will look like: <layout element="Category" widget="CustomSourceSelectWidget" configuration= "source='com.deepthoughts.widgets.sources.ContentListDS'| The layout goes into the blogentry.xsd file which was mentioned in the beginning of this article. The declaration specifies that the Category field uses the CustomSourceSelectWidget widget. The ContentListDS class is specified as the data source and it uses a content type of ValueList. The resource instance of the ValueList content type is located at /Blogs/BlogCategories and the field within the content type containing the values is named Category. After making these changes to the schema file, the content editor will have a pull-down that obtains the values dynamically. SummaryIn this article, we talked about how to create a custom widget. We first covered the interfaces necessary to implement a widget and then went through the design of a widget for the blog content. After that, we went over the widget code and how to register a widget with OpenCms. Although the widget we developed is used to retrieve category values from a list, we've designed it to have broader use. It's an easy matter to configure the widget to read from a different resource containing different values or a different resource type altogether. The widget design allows for other data sources to be configured. OpenCms 7 Development
About the AuthorAs eFoundry's Founder and Chief Technology Officer, Dan Liliedahl's early vision for the company has culminated in a professional services firm that provides its Fortune 1000 clients with focused expertise in the development and deployment of sophisticated Web infrastructures and content management applications. eFoundry is a system integrator that customizes and deploys OpenCms, which is quite mature with a large developer community. Dan serves as the firm's primary architect for the enterprise content management solutions that enable the delivery of e-business initiatives such as portals, web services, and online catalogs. Prior to his position at eFoundry, Dan served as principal engineer and architect for content management software pioneer FutureTense, Inc. While there, he designed and deployed state-of-the-art J2EE-based enterprise content management solutions for leading international companies in the financial services and publishing industries. Before FutureTense, Dan was principal software engineer for SBL scripting language firm, Mystic River Software. He has also managed the architecture, design and implementation of mission-critical software products for Massachusetts-based firms Adra Systems, Inc., Simplex Time Recorder, Intellution, and Butler Automatic. Dan has a Bachelors of Science in electrical engineering from the University of New Hampshire's College of Engineering and Physical Sciences. Books from Packt |
|
| ||||||||