Supporting Tags in a Content Type

Version 1

    You can add support that allows people to add tags to instances of your content type. People can also browse or search for your content type by tag. The content in this topic assumes you've implemented a manager class to locate content type instances based on their ID in the system.

     

    Note: The code in this topic is from the Memo sample content type. You'll find the sample in the Jive public sample Subversion repository.

     

    Note: The content type API is still a new feature that might change as developers provide feedback about it.

     

    Here are the high-level steps. You'll find more about each in the sections below.

    • Have your content type class give information about how tags to instances of your content type should be handled.
    • Have your content object class give information about tags on a particular instance.
    • Signal that your content type supports tags.
    • Add support for user interface to display tags.

    Provide Information About Tagging Support

    Implement the TaggableTypeInfoProvider interface to describe your content type's tag handling strategy.

     

    • Create an info provider class that implements TaggableTypeInfoProvider.
    • Add setters that Spring will use to inject instances your code will need. Here, this includes MemoManager and ObjectTypeManager instances that the class will need for the interface methods it's implementing.
      /**
       * An implementation of <code>TaggableTypeInfoProvider</code> for the memo content type. This
       * class provides information describing the content type's support for being tagged.
       */
      public class MemoTaggableTypeInfoProvider implements TaggableTypeInfoProvider {
      
          // Fields to hold instances injected by Spring.
          private MemoManager memoManager;
          private ObjectTypeManager objectTypeManager;
      
          /**
           * Called by Spring to inject the MemoManager instance.
           *
           * @param memoManager The injected instance.
           */
          public void setMemoManager(MemoManager memoManager) {
              this.memoManager = memoManager;
          }
      
          /**
           * Called by Spring to inject the ObjectTypeManager, an application object for
           * getting Jive object types (including content types) by name, ID, and
           * other properties.
           *
           * @param objectTypeManager The injected instance.
           */
          public void setObjectTypeManager(ObjectTypeManager objectTypeManager) {
              this.objectTypeManager = objectTypeManager;
          }
      
          // Code omitted.
      }
    • Implement TaggableTypeInfoProvider methods. The first two methods are pretty self-explanatory. In this implementation, isAllowedToTag grants tagging permission to based on whether the user is allowed to create an instance of the type; it uses a permissions helper class that's part of this content type. The getTypeCount implementation uses this content type's manager class to find out how many memo instances have a tag from the set applied. For getContainableType, you should return the ContainableType implementation that must contain your content type, if any. For example, the blog post content type must be contained by the blog type. In many cases, as here, you can simply return an instance of your content type's object type class, which implements ContainableType.
      /**
       * Called by the application to discover whether the specified memo instance
       * can be tagged by the specified user.
       *
       * @param object The memo instance to check permission about.
       * @param user The user to check permission for.
       * @return true if the user has permission; otherwise false.
       */
      public boolean isAllowedToTag(JiveObject object, User user) {
          return MemoPermHelper.canWriteMemo((Memo) object);
      }
      
      /**
       * Called by the application to get the content type's containable type. In this case,
       * this is the content type itself (as it is known to the application, via its
       * ID).
       *
       * @return A MemoObjectType instance.
       */
      public ContainableType getContainableType() {
          return (ContainableType) objectTypeManager.getObjectType(MemoObjectType.MEMO_TYPE_ID);
      }
      
    • To support dependency injection for the instances needed here, add a stanza like the following to your Spring configuration XML (spring.xml) file.
      <!-- Configure Spring to inject MemoManager and ObjectTypeManager instances into the info provider class. -->
      <bean id="memoTaggableTypeInfoProvider" class="com.jivesoftware.clearspace.plugin.test_dynamic.MemoTaggableTypeInfoProvider">
          <property name="memoManager" ref="memoManager"/>
          <property name="objectTypeManager" ref="objectTypeManager"/>
      </bean>
      

    Signal Support for Tags

    To tell the application that your content type should be taggable, your  implement the TaggableType interface.

     

    • Implement TaggableType on the same Jive object type class where you implement other subsystem support interfaces. The implementation's one method should return your TaggableTypeInfoProvider instance, which provides information the application needs to understand how your content type supports tags. Here, the info provider instance returned by the method will be injected by Spring.
      public class MemoObjectType implements TaggableType
      {
          // A field to hold the instance injected by Spring.
          private TaggableTypeInfoProvider taggableTypeInfoProvider;
      
          /**
           * Called by Spring to set the info provider implementation needed to support tagging.
           *
           * @param taggableTypeInfoProvider The provider defined in the Spring configuration file.
           */
          public void setTaggableTypeInfoProvider(TaggableTypeInfoProvider taggableTypeInfoProvider) {
              this.taggableTypeInfoProvider = taggableTypeInfoProvider;
          }
      
          /**
           * Called by Spring to get the info provider needed to support tagging.
           *
           * @return The info provider defined in the Spring configuration file.
           */
          public TaggableTypeInfoProvider getTaggableTypeInfoProvider() {
              return taggableTypeInfoProvider;
          }
      }
      
    • Configure Spring to inject the info provider object that your implementation returns.
      <bean id="memoObjectType" class="com.jivesoftware.clearspace.plugin.test_dynamic.MemoObjectType">
          <property name="taggableTypeInfoProvider" ref="memoTaggableTypeInfoProvider"/>
      </bean>
      

    Provide User Interface for Applying and Viewing Tags

    Once you've connected your content type to the tagging subsystem through your object type and info provider classes, you can create user interface and logic for applying and viewing tags. Here are the pieces you'll probably need:

    • An action class that can set or retrieve the tags for a content type instance (along with Struts configuration, of course).
    • FreeMarker templates that provide a way to receive tags typed by the user and to display them for viewing.

    Supporting Applying and Editing Tags

    To help make your content type consistent with other types in the application, you should have your type make tags available for adding and editing when the user is creating or editing the instance. To do this, you can implement the methods you need in the action class you use to handle instance creation.

     

    For example, assume you have created an action class to create new instances of the content type. That class might include a way to capture a new instance's subject and body if it has them. For a taggable type, it will also include a way to capture and validate tags the user wants to save as part of the content, as well as a way to get popular tags from the community so that users can select from tags already in use.

     

    To provide all of this, your action should implement the CreateEditTaggableTypeAction interface. It has methods for getting and setting tags, as well as for getting the list of popular tags. In the following example, code that's specific to things other than tags has been omitted to keep the example simpler. (Note that this assumes you're configuring Struts to be aware of your action. For more on that, see the topic on how to support content type instance creation.)

    public class CreateMemoAction extends JiveActionSupport implements CreateEditTaggableTypeAction {
    
        private List<String> popularTags;
        private String tags;
        private Set<String> validatedTags = new HashSet<String>();
        private TagActionUtil tagActionUtil;
        private MemoManager memoManager;
    
        /**
         * Called by Spring to inject a MemoManager instance.
         *
         * @param memoManager The MemoManager.
         */
        public final void setMemoManager(MemoManager memoManager) {
            this.memoManager = memoManager;
        }
    
        /**
         * Called by Spring to inject the TagActionUtil instance.
         *
         * @param tagActionUtil A TagActionUtil instance.
         */
        public final void setTagActionUtil(TagActionUtil tagActionUtil) {
            this.tagActionUtil = tagActionUtil;
        }
    
        /**
         * Validates content data to ensure that information needed for creating the memo is present.
         * For example, if the memo's subject or body aren't present, then the UI will display an
         * error message so that the user knows what to do before creating or saving.
         *
         * This method is called automatically by Struts before the execute method is called.
         */
        public void validate() {
    
        // Code for subject and body omitted.
    
            // If there are tags, validate them before saving. If invalid tags are found, catch the
            // resulting exception and display and error for the user.
            if (!StringUtils.isBlank(tags)) {
                try {
                    validatedTags = tagActionUtil.getValidTags(tags);
                }
                catch (InvalidPropertyException ipe) {
                    switch(ipe.getError()) {
                        case length:
                            addFieldError("tags",
                                    getText("memo.create.error.tag.length",
                                        new String[] {ipe.getProperty().toString()}));
                            break;
                        default:
                            addFieldError("tags",
                                    getText("memo.create.error.tag.unknown",
                                        new String[] {ipe.getProperty().toString()}));
                            break;
                    }
                }
            }
        }
    
        /**
         * Called by Struts to execute the action, creating the memo from content entered by the user.
         *
         * @return The Struts response.
         */
        @Override
        public String execute() {
    
            if ( !StringUtils.isEmpty( cancel ) ) {
                refererURL = JiveResourceResolver.getJiveObjectURL( container, false );
                return "cancel";
            }
    
            Memo memo;
    
            try {
                // Create a memo instance from valid data entered by the user.
                memo = memoManager.createMemo(container, subject, body);
            }
            catch (RejectedException e) {
                log.error("Memo " + subject + " rejected for create", e);
                return UNAUTHORIZED;
            }
    
            // Save the memo's validated tags.
            tagActionUtil.saveTags(memo, validatedTags);      
    
            // The URL that will be used to redirect to a view of this memo after the action
            // executes.
            refererURL = "/view-memo.jspa?memoID=" + memo.getID();
          
            return SUCCESS;
        }
    
        /**
         * Sets tags entered by the user.
         *
         * @param The space-separated list of tags.
         */
        public void setTags(String tags) {
            this.tags = tags;
        }
      
        /**
         * Gets tags saved for the memo instance.
         *
         * @return The space-separated list of tags.
         */
        public String getTags() {
            return tags;
        }
    
        /**
         * Get the popular tags for the community from the application.
         *
         * @return The list of popular tags.
         */
        public Iterable<String> getPopularTags() {
            if (popularTags == null) {
                popularTags = tagActionUtil.getPopularTags(getContainer(),
                    JiveGlobals.getJiveIntProperty("memos.tags.populartags", 25));
            }
            return popularTags;
        }
    }
    

     

    The user interface is remarkably simple to create. Again, to be consistent with other content types in the UI, you can import that tag-related UI that's used by the rest of the application. You do that by including tag-macros.ftl, which has the code for displaying the tag entry form and for displaying the list of tags associated with the document when viewing it. The FreeMarker code in the included file will use values from your action class to display the tag-related UI.

     

    For example, for UI to edit tags, include tag-macros.ftl with the include statement in the following example.

     

    <!-- Insert this line near the top of your FTL file. -->
    <#include "/template/global/include/tag-macros.ftl" />
    
    <!-- Insert this code where the tag entry form and list of popular tags should be displayed. -->
    <@editTags/>

    In a similar way, you can add UI for displaying tags when your content is begin viewed by including the tag-macros.ftl file in your  FTL file for viewing UI. You'd use a different macro, of course, as shown in the following snippet.

    <!-- Insert this line near the top of the FTL file. -->
    <#include "/template/global/include/tag-macros.ftl" />
    
    <!-- Insert this code where the list of tags should be displayed. -->
    <@viewTags contentObject=memo />