|
ImplementationModule structureIn 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:
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:
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:
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.
It's not recommended to define multiple components in a single Roxen module. Defining the component pluginWhen subclassing AbstractComponentPlugin your plugin class should at least implement the following two methods:
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 instanceYour 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.
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:
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 typesIn 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:
There are also some optional keys:
The predefined types are:
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 editorPresenting 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.
Continuing the previous example it would look like this:
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:
Field lockingIn 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 managementEditor 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:
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:
|
|||||||||||||||||||