RoxenCMS 5.4System Developer Manual PikeRoxen CMS-Specific Module APIsEditor Components

   

Overview
Implementation
API Reference

Implementation

Module structure

In order to simplify the development process, code performing the basic module registration and loading tasks needed in a Roxen module in general has been moved into a separate library. Your module should inherit this library with this statement:

inherit "roxen-module://shared-component-code";

The roxen-module:// prefix makes sure the library identifier is found regardless of where in the directory structure your component module is placed.

Aside from the library inherit there are two initialization statements that are needed:

#include <module.h> import Sitebuilder.Editor;

The first brings in necessary constants and the second imports the page editor framework. Next, you need to register the name of the Roxen module and optionally provide a documentation string to be shown in the server administration interface:

constant module_name = "Cartoon Component"; constant module_doc = "This module manages cartoons.";

By convention module_name can be written as "X: Y" where X is a group name used by all custom components and Y is the name of this particular component. Roxen CMS will then group components by prefix in the administration interface. No part of module_name is visible to content editors, though.

Roxen CMS provides two abstract base classes for editor components that your custom component need to inherit from. The first class, AbstractComponentPlugin, is instantiated once for each component module and handles the global properties of a component. This includes the name of the component, the XML tag it registers and so on.

The second class, AbstractComponentInstance, represents a single instance of a component in a given page. These instances are created and deleted as the user brings pages into the editor. Multiple instances may exist in parallel if the same component is used more than once in a page. Instances normally have no connection but they can always find their global plugin object and set up a communication channel that way.

Setting up your custom classes with the two inherit statements will look like as illustrated below. It's a requirement that your classes have the suffixes ComponentPlugin and ComponantInstance, respectively, and that the first part of the name is identical for both classes. If you fail to fulfill these requirements the editor framework will not find and instantiate your classes correctly.

class CartoonComponentPlugin { inherit AbstractComponentPlugin; ... }; class CartoonComponentInstance { inherit AbstractComponentInstance; ... };

It's not recommended to define multiple components in a single Roxen module.

Defining the component plugin

When subclassing AbstractComponentPlugin your plugin class should at least implement the following two methods:

string get_component_name(); string get_component_tag();

In get_component_name() you should return the human-presentable name of your component, for example "Cartoon Component". Be aware of space constraints in the editing interface and use as short a name as possible. Your get_component_tag() method should return the name of the XML tag which corresponds to your component. For the given example "cartoon-component" would be suitable.

The remaining methods are presented in the reference section later in this chapter. At this time we will introduce the most important ones and describe in which way they are used.

If your component wants to define fields the plugin class should implement the get_component_fields() method which is asked to return all field names. As mentioned earlier, fields are by default managed as plain text strings with proper quoting. This means fields containing markup will not work unless you inform the editor framework about these exceptions. In such cases you must add the method get_unquoted_fields() to your plugin class.

In case your component supports layout variants you will need to return a mapping describing the connection between variant numbers and the names presented to the user in the variant drop-down menu. This is the purpose of the get_component_variants() method. A related method is get_component_display_order() which controls the order in which the variant names appear.

The component instance

Your subclass of AbstractComponentInstance is, as mentioned above, instantiated for each component of your type in the page the user currently edits. Again, a reference of available method overrides follows after a short introduction of the most common methods.

In render_editor() you generate the editing form for this particular component instance. Even though you can return any type of HTML you should be aware that the editing framework may e.g. wrap the output inside a table or other type of layout element. It is therefore recommended that you in turn call the inherited method render_field() for each field in turn and concatenate the result into a string which then becomes your response. Additional non-layout code such as JavaScript definitions, hidden variables etc are also acceptable. By adhering to these guidelines you improve the chance of your component working unmodified in future versions of Roxen CMS even though the page editing environment may change in significant ways.

The render_editor() method is called with two parameters: a variable prefix string (discussed earlier), and a Roxen CMS request object. The latter contains a large number of request-specific data which you may find useful in a number of situations. The functionality provided in this object is documented elsewhere and will not be explained in this chapter.

Given the CartoonComponentInstance class introduced earlier, this is a simplified example of what render_editor() may look like.

string render_editor(string var_prefix, RequestID id) { return render_field("cartoon", ([ ... ]), id) + render_field("rating", ([ ... ]), id) + render_field("review", ([ ... ]), id); }

By passing the field name to render_field() the editor will internally get the current field value and insert it into the form. You can always access this value using get_field() if necessary. Also note that the <variant> is handled automatically so you don't need to include it in your form.

Now, the parts marked ... still remain to explain. Each of those mappings contain a combination of required and optional keys. Among the standard keys we have title, type and name. Depending on type there are also other keys that can be used, such as size, value, rows, cols and so on. All possible keys will be detailed in next section.

Once you have a render_editor() method in place generating your form you should define the save_variables() method as well. It is responsible for parsing the form data and converting it into field values. Basically it should look into the posted variables enclosed in the RequestID object and call set_field() for each one. For example:

void save_variables(string var_prefix, RequestID id) { set_field("cartoon", id->variables[var_prefix + "cartoon"]); set_field("rating", id->variables[var_prefix + "rating"]); // <review> needs special treatment -- see below }

If you have additional form widgets that need handling you should put that code into render_editor() and not save_variables() since the former is invoked every time the editor page needs to be redrawn.

As the user accepts the modified page all page component instances are requested to produce the XML output for saving into the repository. You do not have to provide any custom code for this to work, but you may define a callback called notify_close() that gets a chance to modify the file properties during the saving process. This feature is primarily intended for updating metadata values and you will need to be familiar with the SBFileData and SBObject APIs to make use of it.

In situations where your component has internal state to preserve you should preferably use member variables in your component instance. Placing data in hidden form variables is also a possibility but has disadvantages such as requiring string conversion and adds unnecessary burden in page size and network communication overhead.

When your component uses shared resources (databases, files etc) it can be convenient to move this handling into the plugin object. This avoids repeated initalization and cleanup as the user moves in and out of the page editor. Your component instance can access the plugin object via the plugin member variable.

Field types

In previous sections we have introduced the concept of different field types for the component editing form. Roxen CMS gives access to a variety of predefined types with accompanying editing widgets for your component to use when calling the render_field() method.

All of the field types (with the exception of the type none) have the required keys title, type and name:

  • title

    User-presentable name of the input field.

  • type

    Field data type (string, color etc).

  • name

    Variable name for this form element. It is important that this name includes the prefix string mentioned earlier.

There are also some optional keys:

  • value

    Override field value. Normally the value is taken from the current component settings but this can be replaced as needed.

  • lock

    Override field locking. More on this in following section.

The predefined types are:

html
Description

Creates a rich-text editor input widget.

Required keys
  • title
  • type
  • name

Optional keys
  • options

    A mapping with data that can control the behavior of the rich-text editor. In particular:

    • height

      A key containing an integer indicating the pixel height of the editor object. Default is 600 pixels.

    • toolbar

      Name of toolbar. Currently two predefined toolbars exist, but you can also create your own and reference it here.

    • no_para_init

      See the Rich-Text Editor section below for an explanation.

string
Description

Creates a one-line text input field.

Required keys
  • title
  • type
  • name

Optional keys
  • size

    Initial input field width in characters. Default is 60.

text
Description

Creates a multi-line text input field.

Required keys
  • title
  • type
  • name

Optional keys
  • rows

    Width of text field in characters. Default is 40.

  • cols

    Height of text field in rows. Default is 6.

color
Description

Creates a color selector. The input as well as the output is a color in #RRGGBB hexadecimal format.

Required keys
  • title
  • type
  • name

Optional keys

none

font
Description

Creates a font selection menu based on the fonts installed in the Roxen CMS system.

Required keys
  • title
  • type
  • name

Optional keys

none

select
Description

Creates a drop-down menu.

Required keys
  • title
  • type
  • name
  • options

    A mapping or a string of menu choices. If a mapping is provided the indices should hold the variable ids and the values should be the user-presentable titles. The default order is ids sorted alphabetically.

    When supplied as a string it should be formatted as id1:Title1,id2:Title2,id3:Title3 and so on. Note that literal comma or colon in titles need to be escaped with backslash (\).

Optional keys
  • sort

    Overrides the alphabetical sorting order for variable ids when the options key is a mapping. This key may then contain an array of variable ids that control the order.

href
Description

Creates a URL field with a button that opens a link browser window. By using a second string field and providing the name in the optional description-field key the link browser can copy the page title of the selected page into the appointed field.

Required keys
  • title
  • type
  • name

Optional keys
  • size

    Initial input field width in characters. Default is 60.

  • description-field

    Name of field where the page title is placed.

picture, file
Description

Creates a path field with button that opens a file selector window. The type of files presented is either pictures (with thumbnails) or a generic file listing.

Required keys
  • title
  • type
  • name

Optional keys
  • chroot

    Force a root directory for the file selector window. The user cannot navigate outside this root, and the returned path will exclude the root prefix.

  • abspath

    A flag which forces all file paths to be returned is absolute form. When not set relative paths may be used.

  • show-title

    Displays file titles in the file selection dialog.

  • show-access

    Enable padlock icons in file selection dialog where editors can modify access permissions.

user
Description

Creates a widget for selecting a user identity from the AC database.

Required keys
  • title
  • type
  • name

Optional keys

none

none
Description

Displays custom HTML code.

Required keys
  • type
  • value

    The unescaped HTML fragment.

Optional keys
  • title

    Only displayed if a non-empty string is provided.

Notably missing from this list are radio and checkbox controls. You can create these manually by inserting the corresponding <input> tag directly in a call to render_field() of type none.

Rich-text editor

Presenting the rich-text editor requires no extra work aside from using the html field type presented above. However, in the save_variables() method you must make a library call to get_html_applet_result() in order to get the current rich-text data as an unescaped HTML string.

string get_html_applet_result(string var_prefix, string field, RequestID id, void|mapping options);

Continuing the previous example it would look like this:

void save_variables(string var_prefix, RequestID id) { set_field("cartoon", id->variables[var_prefix + "cartoon"]); set_field("rating", id->variables[var_prefix + "rating"]); set_field("review", get_html_applet_result(var_prefix, "review", id)); }

The options mapping allows some customization on how the HTML is treated; normally it is filtered according to a built-in filter and later cleaned so that a result consisting of a single empty paragraph is avoided. These mapping keys can be used to override this behavior:

  • filter

    Name of HTML filter to apply to the HTML data. Default is fck_editor. (Note that for compatibility reasons the FCK Editor name remains despite the switch to CKEditor starting in Roxen CMS 5.4.)

  • no_para_init

    When set, don't undo the <p>&nbsp;</p> prefix that the editor inserts into an empty editing session. This is needed for some browsers to force <p>-based editing compared to <br> mode. When set to non-zero it is recommended you also set the corresponding no_para_init flag in the html field options. Default is 0 meaning the filtering is active.

Field locking

In certain pages it is valuable to be able to lock editing of fields or components. For example, a web developer may request a given component to not be possible to delete, or that a particular field is locked. This is signalled by including special attributes in the XML markup of a page, and Roxen CMS will honor them in the page editor interface. Details on how to accomplish this is available in the section Managing templates in a Basic site (look for the Controlling the component editor heading) in the Web Developer Manual.

If the component instance renders custom editing widgets it is sometimes necessary to manually implement locking. To check whether a given field is locked, call the get_field_lock() method and pass the field name as a parameter.

Link management

Editor components running in Roxen CMS version 5.0 or later may need special attention to enable Link Management functionality. The two new API calls are:

  • resolve_permlink()
  • get_permlink_for_path()

For normal usage you don't have to update existing component code since the conversions from/to UUID form is happening internally. The decision on whether a field requires conversion is based on the field type you pass in the render_field() call. Later, after the save_variables() callback the editor will again process fields that it knows contain links.

The exceptions to the automatic handling are primarily if you want to call get_field() directly, or if you call set_field() prior to calling render_field(). In those cases the editor has no knowledge of the field type and doesn't know it needs to apply decoding or encoding. Likewise, any field data not based on the built-in editor controls (i.e. file picker, link browser etc) must be taken care of explicitly. It is not harmful to call either function repeatedly on the same data.

Although the bundled Link component falls in the category of components that are handled transparently, this example based on its source code illustrates how you can make direct conversion calls:

string render_editor(...) { [...] render_field("link", ([ "title" : ..., "type" : "href", "value" : resolve_permlink(get_field("link"), id), [...] ]), id) + [...] } void save_variables(...) { string permlink = get_permlink_for_path(id->variables[var_prefix + "link"], id); set_field("link", permlink); [...] }