7.4. Content Types Tools

7.4.1. Content Type Assets

Assets are helper CMS classes and object types which encapsulate some functionality for commonly-needed Content Item attributes or components that are more complex than simple textfields.

Commonly-used Asset types include the following:

Using an asset as a component of your content type is very similar to using standard Java classes. However, since assets are persistent DomainObjects in their own right, keep in mind that the object returned from the get() method call will be a DataObject which may need to be converted to the appropriate DomainObject subclass.

For example, if we want to extend the BirdWatch Content Type to include an uploaded image of the bird, we would add the following to BirdWatch.pdl:

component ImageAsset[0..1] imageAsset = join ct_birdwatch.image_id 
                                        to cms_images.image_id;

For the domain layer, we will add the following to BirdWatch.java:

protected static final String IMAGE = "imageAsset";

/** Accessor. Get the image associated with this item. */
public ImageAsset getImage() {
    DataObject dobj = (DataObject) get(IMAGE)
    if ( dobj == null ) {
        return null;
    }
    return (ImageAsset) DomainObjectFactory.newInstance(dobj);
}

/** Mutator. Set the image associated with this item. */
public void setImage(ImageAsset image) {
    setAssociation(IMAGE, image);
}

7.4.2. Other Content Type Helper Classes

There are several other helper classes which may be used to add functionality to your Content Types. Some of these, like assets, must be explicitly added to your content types in order to make use of them. There are other helper classes which apply generically to all content types, you only need to load the packages.

7.4.2.1. Links and Related Links

The Link component allows you to include links to other content within your Content Items. A Link can link to either a ContentItem within the CMS or to an external URL. There are two main ways to use links in your content types. One method is to embed a Link component directly in your content type, defining the component in your PDL:

component Link[0..n] links = join ct_birdwatch.link_id to cms_links.link_id;

The domain class for your content type will need appropriate access methods, and your authoring kit will need to populate the link object's attributes.

If your main purpose in using links is simply to maintain a list of sortable, related links to internal or external content, the second method of using links will be much simpler. RelatedLink is a subtype of Link with a generic association to ContentItem and an associated authoring step already created. Install the ccm-cms-assets-relatedlink package, and the related link authoring step will be automatically added to all of your content types. This step will allow authors to add an arbitrary number of links to both internal and external content, sorting them as appropriate.

7.4.2.2. Content Groups

The ContentGroup component allows a developer to associate multiple content items in a sortable order. This functionality is in some ways similar to links, except associated items must be CMS items (not internal/external links).

To illustrate, a developer could create an index page with a left column group of items, a center column group, and a right column group. The columns would consist of content items which could come from these multiple sources. Supporting authoring-component base classes are included along with CMS Bebop widgets.

As an example, we will create a BirdWatcher Content Type which will contain a list of birds watched. The PDL will be defined as follows:

model com.arsdigita.package1;

import com.arsdigita.cms.contenttypes.*;
import com.arsdigita.cms.*;

object type BirdWatcher extends ContentPage {
    component ContentGroup[0..1] birds = join ct_bird_watchers\
.content_group_id
                                         to ct_content_groups.group_id;

    reference key (ct_bird_watchers.item_id);
}

The Domain code for BirdWatcher is as follows. Constructors are left out for clarity.

public class BirdWatcher extends ContentPage 
    implements ContentGroupContainer {

...

    public static final String BIRDS = "birds";

...

    public ContentGroup getContentGroup(String attributeName) {
        Assert.assertTrue(BIRDS.equals(attributeName));
        DataObject object = (DataObject)get(attributeName);
        if (object == null) {
            return null;
        } else {
            return (ContentGroup) DomainObjectFactory.newInstance(object);
        }
    }
    
    public void setContentGroup(String attrName, ContentGroup group) {
        Assert.assertTrue(BIRDS.equals(attrName));
        setAssociation(attrName, group);
    }

    public ContentGroup getBirds() {
        ContentGroup items = getContentGroup(BIRDS);
        return items;
    }

    public void addBirdWatch(BirdWatch item) {
        getItems().addContentItem(item);
    }

    public void removeBirdWatch(BirdWatch item) {
        getItems().removeContentItem(item);
    }

}

Once we've defined the BirdWatcher content type, we must also create an authoring kit, as defined in Section 7.2 Writing an Authoring Kit. In addition to the basic properties step, we will need a separate step for maintaining the list of BirdWatch items. The bulk of the necessary functionality has already been defined in the helper authoring step superclasses. So at this point we just need a fairly simple authoring step subclass:

package com.arsdigita.package1;

import com.arsdigita.cms.ContentItem;
import com.arsdigita.cms.ItemSelectionModel;
import com.arsdigita.cms.contenttypes.ui.ContentGroupPropertiesStep;
import com.arsdigita.cms.contenttypes.ui.ContentGroupPropertyForm;
import com.arsdigita.cms.ui.authoring.AuthoringKitWizard;
import com.arsdigita.package1.BirdWatcher;

public class BirdWatcherBirdsStep extends ContentGroupPropertiesStep {
    public BirdWatcherBirdsStep(ItemSelectionModel itemModel,
                                     AuthoringKitWizard parent) {
        super(itemModel, parent);
    }

    public String getMainAttributeName() {
        return BirdWatcher.BIRDS;
    }

    protected ContentGroupPropertyForm getPropertyForm(ItemSelectionModel \
itemModel) {
        return new BirdWatcherBirdsForm( itemModel, 
                                         getMainAttributeName() );
    }

    private class BirdWatcherBirdsForm extends ContentGroupPropertyForm {

        // constructors omitted for brevity

        protected String getSearchContentType() {
            return BirdWatch.BASE_DATA_OBJECT_TYPE;
        }

    }

}

There are only two methods of interest in our authoring step. getMainAttributeName() specifies the actual PDL association name for the ContentGroup. If your content type defines several ContentGroup components, you will need a separate authoring step for each one -- this method will let each component know which specific ContentGroup to operate on. Since we will be further limiting the ContentGroup to only include links to BirdWatch items, the getPropertyForm definition is needed so that the authoring step will use our customized authoring form which will filter the search popup on a particular content type. For a generic ContentGroup which allows links to all content types, this latter step is not required.

7.4.2.3. File Attachments

Like Related Links, the File Attachments package (ccm-cms-assets-relatedlink) requires no additional development effort to use it. Once the package is installed, all content types will have an authoring step for adding file attachments. This step allows authors to upload and sort binary files. The default styling for these will display a list of download links for the files, sorted as specified in the authoring step.

7.4.3. Indexing for Search

Which content to search is specified in much the same way as which content to include in the XML output for styling. For classes defined within CMS (such as ContentItem, TextPage, etc), a default set of properties to include for search is listed in cms-item-adapters.xml. By default, attributes are included and associations excluded.

If these defaults work for your requirements for your custom content types, no further work is needed. If you do require that some custom attributes be hidden or custom associations be included, you'll need to define an adapter file for your content type. If we want to include BirdWatch information in our indexed search content for BirdWatcher, we might define the following adapter file:

package com.arsdigita.package1;
<?xml version="1.0" encoding="utf-8"?>
<xrd:adapters xmlns:xrd="http://xmlns.redhat.com/schemas/waf/\
xml-renderer-rules" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" \
xsi:schemaLocation="http://rhea.redhat.com/schemas/waf/xml-renderer-rules \
xml-renderer-rules.xsd">
  <xrd:context name="com.arsdigita.cms.search.ContentPageMetadataProvider">
    <xrd:adapter objectType="com.arsdigita.package1.BirdWatcher" extends=\
"com.arsdigita.cms.ContentPage" traversalClass="com.arsdigita.cms.contenttypes\
.ContentItemTraversalAdapter">
      <xrd:associations rule="include">
        <xrd:property name="/object/birds"/>
      </xrd:associations>
    </xrd:adapter>
  </xrd:context>

</xrd:adapters>

We would also add the following to BirdWatcherInitializer to register our adapter:

public String getTraversalXML() {
    return "/WEB-INF/traversal-adapters/com/arsdigita/package1/\
BirdWatcher.xml";
}