Supporting Content Type Display On Userbar Menus

Version 1

    Having your content type displayed on userbar menus is an important part of providing a user user experience that's consistent with other content in the application. The userbar is (almost) omnipresent list of navigation menus located (by default) at the top of the application's user interface.

     

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

     

    The userbar has the following menus:

     

    • New, where users can go to create new instances of content types (and some containers, including social groups and projects).
    • History, which lists recently viewed content and containers. This menu also provides a link to a tabbed page that displays the user's history divided into content type categories.
    • Your Stuff, which provides navigation to items specifically related to the current users. From here, they can get a list of content type instances they've created (including documents, discussions, and so on), as well as containers and other personal items.
    • Browse, which lists content types, containers, and people from across the community.

     

    In the examples in this topic, a key piece of the support for these menus is provided through an implementation of the UserBarProvider interface. Implementing this interface gives you a way to declare that your content type should show up there, and to provide the URL that should be used when the item you're adding gets clicked. If you've written plugins before, this might be a departure from the way you've adding things to the userbar. In the past, you added new menu items through a <component> stanza in your plugin.xml file (the <component> element is from the ui-components schema). Implementing UserBarProvider is an alternative.

    Supporting Display on the New Menu

    The New menu is one of the places where people can go to create new instances of your content type. By default, each of the commands on the New menu displays a small dialog designed to help the user get their new content started quickly, usually in part by choosing where the content's going to go. For documents and discussions, this means choosing a container, for tasks it means entering brief task information and selecting a containing project, and so on.

     

    You can implement your own behavior, including your own dialog for display when the user clicks your content type in the list. The simple example in this section instead takes the easy way out, extending a base class (that you can use also) that implements UserBarProvider and provides support for a default container chooser dialog. Here are the high-level steps:

     

    • Extend the AbstractUserBarProvider class to describe your content type's support for the New menu.
    • Implement the ContentObjectTypeInfoProvider interface to describe an instance's support for being created.
    • Implement ContentObjectType in your Jive object type class to signal that you're creatable.

    Provide Information About New Menu Support

    Implement the UserBarProvider interface, such as by extending AbstractUserBarProvider. Extending the base class gives you access to a default way to let users choose the community container in which to create the new instance.

     

    If you want to create your own container chooser, you can do it with a Struts action that presents the UI. Once you have the action, you can either implement UserBarProvider directly or override the following three base class methods to return information about your action:

    • getUserBarChooseContainerAction
    • getUserBarChooseContainerActionParams
    • getUserBarChooseContainerActionJSHelper

     

    Here's a simple implementation example that keeps the base class methods in order to lean on the default behavior for choosing a container.

     

    • Extend AbstractUserBarProvider. If you want to limit the containers where your content type appears on the New menu, you could do that here. In that case, your code would use the currentContainer parameter to find out what you needed to know.
    public class MemoUserBarProvider extends AbstractUserBarProvider {
    
        /**
         * Determines whether the memo content type should appear on the New
         * menu in the specified container.
         */
        public boolean isVisibleOnUserBarNewDropDown(JiveContainer currentContainer) {
          // The memo type is creatable everywhere.
            return true;
        }
    }
    

    Providing Information About How Instances Can Be Created

    You'll need code that executes when someone clicks your content type in the New menu. To do that, you implement the ContentObjectTypeInfoProvider interface. Through its methods, you tell the application whether the current user can create an instance and whether your content type supports having binary files uploaded with instances. You also implement the method that returns the URL for the action through which a user creates a new instance.

    • Implement ContentObjectTypeInfoProvider. The URL built in the getCreateNewFormRelativeURL method is defined in the struts.xml file. For more on that action, and on supporting instance creation, take a look at The specified item was not found..
    /**
     * An object type info provider that describes how the memo
     * content type supports being created.
     */
    public class MemoContentObjectTypeInfoProvider implements ContentObjectTypeInfoProvider {
    
        /**
         * Determines whether the current user can create a memo instance
         * in the specified container.
         *
         * @return true if they can; false if they can't.
         */
        public boolean userHasCreatePermsFor(JiveContainer container) {
            // Use the permissions helper to find out if they can.
            return MemoPermHelper.canWriteMemo(container);
        }
    
        /**
         * Determines whether memos support having binary files uploaded as
         * their body content.
         *
         * @return true if they do; false if they don't.
         */
        public boolean isBinaryBodyUploadCapable() {
             // They don't.
            return false;
        }
    
        /**
         * Gets the relative URL for the action through which users create
         * a new memo instance. This method is called by the application
         * after the user has continued past container chooser dialog; the
         * parameter values reflect the user's choices.
         *
         * The URL should be relative to the community's base URL (in other
         * words, it shouldn't include the base URL).
         *
         * @param targetContainer The container in which the new instance is
         * to be created.
         * @param isUpload true if the user chose to to upload a binary file;
         * otherwise, false.
         * @param tempObjectId The object ID to use while the instance is
         * being created.
         * @param tags Tags the user added when requesting to create the
         * instance.
         * @param subject The subject the user added.
         * @return An action URL relative to the community's base URL.
         */
        public String getCreateNewFormRelativeURL(JiveContainer targetContainer, boolean isUpload,
                  String tempObjectId, String tags, String subject)
        {
            StringBuilder url = new StringBuilder();
            url.append( "memo-create!input.jspa?" );
            url.append( "container=" );
            url.append( targetContainer.getID() );
            url.append( "&containerType=" );
            url.append( targetContainer.getObjectType() );
            return url.toString();
        }
     
        // Code omitted.
    }
    
    • Implement the ContentObjectType interface in your Jive object type class. This will signal support for instances to be created from the New menu and provide a way for the application to get your ContentObjectTypeInfoProvider implementation.
    public class MemoObjectType implements ContentObjectType
    {
        // Field and accessors to support Spring dependency injection and implementation
        // of the interface.
        private ContentObjectTypeInfoProvider contentObjectTypeInfoProvider;
        public void setContentObjectTypeInfoProvider(ContentObjectTypeInfoProvider contentObjectTypeInfoProvider) {
            this.contentObjectTypeInfoProvider = contentObjectTypeInfoProvider;
        }
        public ContentObjectTypeInfoProvider getContentObjectTypeInfoProvider() {
            return contentObjectTypeInfoProvider;
        }
    
        // Code omitted. 
    }
    

    Supporting Display on the History Menu

    The History menu is where people can go to get back to something they've looked at recently. There are two parts to supporting this menu: getting your content type instances to show up in the menu itself and adding a tab for your content type to the Recent History page that's also available from the menu.

     

    Here are the high-level steps:

     

    • Extend the AbstractUserBarProvider class to describe your content type's support for History.
    • Implement the RecentHistoryProvider interface to capture events which will end up being displayed in the userbar history dropdown.
    • Implement the RecentHistoryEnabledType interface in your content object type to signal support for displaying your content type's instances in history.
    • Implement an action through which you can display a page of recent history related to instances of your content type.
    • Write an FTL file with user interface for listing recent instance history.
    • Add a <component> stanza to your plugin.xml file so that you can display a tab for your content type's recent history.

    Support Logging Instances in History

    Implement the UserBarProvider interface, such as by extending AbstractUserBarProvider.

    • Extend AbstractUserBarProvider. If you want to limit the containers where your content type appears on the History menu, you could do that here. In that case, your code would use the currentContainer parameter to find out what you needed to know.
    public class MemoUserBarProvider extends AbstractUserBarProvider {
    
        public boolean isVisibleOnUserBarHistoryDropDown(JiveContainer currentContainer) {
            return true;
        }
     
        // Code omitted.
    }
    
    • Implement the RecentHistoryProvider interface to capture events which will end up being displayed in the userbar history dropdown. As noted in the comments in this example, the application uses your content type view action to register an instance as having been viewed.
    public class MemoRecentHistoryProvider implements RecentHistoryProvider {
    
        /**
         * Gets an instance of objectType from the context represented by viewAction. The
         * application calls this method when registering the content type instance as
         * having been viewed. The viewAction parameter is the action instance that was
         * executed to display the object to the user. Because actions have no predictable
         * way to retrieve an instance, the application can't simply get the instance
         * from it. Instead it passes the action that was used to content type code, which
         * knows how to get the instance from the action.
         *
         * @param objectType The Jive content object that was viewed.
         * @param viewAction The action instance that was invoked to view the object.
         * @return The object that was viewed.
         */
        public JiveObject getJiveObjectFromViewAction(String objectType, ActionSupport viewAction) {
            if ("memo".equals(objectType)) {
                return ((ViewMemoAction) viewAction).getMemo();
            }
            return null;
        }
    }
    
    • Implement the RecentHistoryEnabledType interface in your content object type to signal support for displaying your content type's instances in history.
    public class MemoObjectType implements RecentHistoryEnabledType
    {
        // Field and accessors to support Spring dependency injection and
        // interface implementation.
        RecentHistoryProvider recentHistoryProvider;
        public RecentHistoryProvider getRecentHistoryProvider() {
            return recentHistoryProvider;
        }
        public void setRecentHistoryProvider(RecentHistoryProvider recentHistoryProvider) {
            this.recentHistoryProvider = recentHistoryProvider;
        }
    
        // Code omitted.
    }
    

    Displaying Instances on the Recent History Page

    • Create an action class through which you can display a page of recent history related to instances of your content type. Include a field and setter for Spring dependency injection.
    /**
     * An action to display the recent history for a user.
     */
    public class MemoRecentHistoryAction extends JiveActionSupport {
    
        // A variable to hold an injected manager instance.
        protected MemoManager memoManager;
    
        /**
         * Called by Spring to inject a memo manager instance.
         *
         * @param memoManager The injected instance.
         */
        public void setMemoManager(MemoManager memoManager) {
            this.memoManager = memoManager;
        }
        // Code follows.
    }
    
    • Add logic to retrieve and filter the history from the user's session, as well as display the history in your history UI.
    public static final String VIEW_MEMOS = "memos";
    private String view = VIEW_MEMOS;
    
    // The history for display in the UI.
    private List<Memo> memoHistory;
    
    public String execute() {
    
        // Get the history from the session.
        Map<Integer, List<Long>> history =
            RecentHistoryManager.getInstance().getRecentHistory(getRequest());
    
        // Using session history, get those items that are memos.
        if (history != null) {
            if (VIEW_MEMOS.equals(view)) {
                List<Long> memoList = history.get(MemoObjectType.MEMO_TYPE_ID);
                if (memoList != null && memoList.size() > 0) {
                    memoHistory = new ArrayList<Memo>(memoList.size());
                    synchronized (memoList) {
                        // Get a Memo instance for each of the memos in the
                        // history. Add each to an arraylist for display in
                        // this action's UI.
                        for (long memoId : memoList) {
                            Memo memo = memoManager.getMemo(memoId);
                            if (memo != null) {
                                memoHistory.add(memo);
                            }
                        }
                    }
                }
            }
        }
        return SUCCESS;
    }
    
    /**
     * Called by Struts to set the value from the view parameter in the
     * action invocation URL.
     *
     * @param view The parameter value
     */
    public void setView(String view) {
        this.view = view;
    }
    
    /**
     * Gets the view parameter value.
     *
     * @return The parameter value.
     */
    public String getView() {
        return view;
    }
    
    /**
     * Called by Struts to get the history list of memos for display
     * in the UI via the FreeMarker template
     *
     * @return The list of memos from the history.
     */
    public List<Memo> getMemoHistory() {
        if (memoHistory != null) {
            return memoHistory;
        }
        else {
            return Collections.EMPTY_LIST;
        }
    }
    
    • Write an FTL file with user interface for listing recent instance history. As with other FreeMarker files that incorporate content type UI into the application, this one wraps the content type-specific markup with markup that helps the page look like others in the application.
    <html>
    <head>
        <head>
            <#include '/template/global/include/recent-history-head.ftl'/>
        </head>
    </head>
    <body>
    
    <#include '/template/global/include/recent-history-header.ftl' />
    
    <!-- BEGIN main body -->
    <div id="jive-body-main">
    
        <!-- BEGIN main body column -->
        <div id="jive-body-maincol-container">
            <div id="jive-body-maincol">
    
                <!-- BEGIN content list -->
                <div class="jive-content-block-container">
                    <#--
                        Check the property from the action to make sure it's for memos.
                    -->
                    <#if view == statics['com.jivesoftware.clearspace.plugin.test_dynamic.action.MemoRecentHistoryAction'].VIEW_MEMOS>
                    <h3 class="jive-content-block-header"><@s.text name="recentHistory.rctVwdMemo.tooltip"/></h3>
                    </#if>
    
                    <div class="jive-content-block">
    
                        <!-- BEGIN content -->
                        <div id="jive-history-content">
    
                            <#if view == statics['com.jivesoftware.clearspace.plugin.test_dynamic.action.MemoRecentHistoryAction'].VIEW_MEMOS>
    
                                <!-- BEGIN discussion list content -->
                                <div id="jive-memo-content">
    
                                    <!-- BEGIN jive-table -->
                                    <div class="jive-table">
                                        <#--
                                            If there are memo instances in the list, then iterate through them,
                                            displaying a link to each.
                                        -->
                                        <#assign memos = memoHistory.iterator()>
                                        <#if (!memos?exists || !memos.hasNext())>
                                            <div class="jive-recentcontent-none">
                                                <p><@s.text name="recentHistory.memos.empty"/></p>
                                            </div>
                                        <#else>
                                            <#list memos as memo>
                                                <ul>
                                                <a href="<@s.url value='${JiveResourceResolver.getJiveObjectURL(memo)}'/>">Memo ID: ${memo.ID}</a>
                                                </ul>
                                            </#list>
                                        </#if>
    
                                    </div>
                                    <!-- END jive-table -->
    
                                </div>
                                <!-- END discussion list content -->
                            </#if>
                        </div>
                        <!-- END content -->
                    </div>
                </div>
                <!-- END content list -->
            </div>
        </div>
        <!-- END main body column -->
    </div>
    <!-- BEGIN main body -->
    </body>
    </html>
    
    • Configure the Struts action to connect the logic with the UI.
    <action name="memo-recent-history" class="com.jivesoftware.clearspace.plugin.test_dynamic.action.MemoRecentHistoryAction">
        <result name="input">/plugins/memo-type-example/resources/memo-recent-history.ftl</result>
        <result name="success">/plugins/memo-type-example/resources/memo-recent-history.ftl</result>
    </action>
    

     

    • Add a <component> stanza to your plugin.xml file so that you can display a tab for your content type's recent history. This markup gives the tab a name and description, as well as providing a URL to execute your recent history action.
    <!-- recent history -->
    <component id="recent-history-tabs">
        <tab id="memos" cssClass="jive-icon-sml jive-icon-memo-sml">
            <name><![CDATA[<@s.text name="recentHistory.memo.link" />]]></name>
            <description><![CDATA[<@s.text name="recentHistory.rctVwdMemo.tooltip" />]]></description>
            <url><![CDATA[<@s.url action='memo-recent-history'/>?view=${statics['com.jivesoftware.clearspace.plugin.test_dynamic.action.MemoRecentHistoryAction'].VIEW_MEMOS}]]></url>
        </tab>
    </component>
    

    Supporting Display on the Your Stuff Menu

    The Your Stuff menu is where people get a list of the things they've either helped create or have expressed an interest in. People who created instances of your content type will be able to get back to those instances easily by clicking your content type's name in their Your Stuff menu. This means you'll need to give them a place to go to view that list, with the Your Stuff menu as a means to get there.

     

    You'll need to implement an action that displays content on a user's profile. You'll also implement UserProfileInfoProvider to return the name of your action (as it's known in your struts.xml).

     

    • Extend the AbstractUserBarProvider class to describe your content type's support for Your Stuff. Return a URL of the form shown in the example: people/<username>?view=<content_type_code>. The application's URL mapping handles the rest. It will use the value of the view parameter -- your content type's code -- to retrieve your implementation of UserProfileInfoProvider. From this it will build a URL to your profile action, passing as parameters you content type's code and the user's name.
    public class MemoUserBarProvider extends AbstractUserBarProvider {
    
        /**
         * Determines whether the memo content type should appear on the Your Stuff
         * menu in the specified container.
         */
        public boolean isVisibleOnUserBarYourStuffDropDown(JiveContainer currentContainer) {
             // Memos can always be in your stuff.
            return true;
        }
    
        public String getUserBarYourStuffURL() {
            User user = JiveApplication.getEffectiveContext().getAuthenticationProvider().getJiveUser();
            return "people/"  + user.getUsername() + "?view=memo";
        }
    
        // Code omitted.
    }
    
    • Implement an action for displaying a tab for your content type on the user's profile.

    Supporting Display on the Browse Menu

     

    • Extend AbstractUserBarProvider
    public class MemoUserBarProvider extends AbstractUserBarProvider {
    
        /**
         * Determines whether the memo content type should appear on the Browse
         * menu in the specified container.
         */
        public boolean isVisibleOnUserBarBrowseDropDown(JiveContainer currentContainer) {
            // You can always browse for memos.
            return true;
        }
    
        /**
         * Gets the URL used to execute the action that displays a view
         * of memos in the system.
         */
        public String getUserBarBrowseURL() {
            return "browse-memos.jspa";
        }
    
        // Code omitted.
    }