Last episode I explained how we create the secure spaces and setup permissions (see How do the account spaces get created anyway? Is it private?). This post deals with the default widgets that come with a newly created community, and how we had to customize this functionality to meet our requirements.

 

defaultLayout.png

 

I. How it's done out of the box

 

To do this, let's first take a look at how it would be done out of the box:

 

  1. Create a new community
  2. Then get re-directed to the newly created community

          Here, community.ftl will call CommunityAction's getWidgetLayout via:

 

 

<#if widgetLayout?exists>
    <div id="jive-body-main">
        <div id="jive-widget-content">
            <div id="thread.watch.notify" class="jive-info-box" style="display:none">
            </div>

            <#include "/template/global/include/form-message.ftl" />

            <#include "${widgetLayout.freemarkerTemplate}" />

        </div>
    </div>
</#if>

And within getWidgetLayout() you'll see that if we haven't customized a community and a layout doesn't exist yet, we'll just copy the default:

 

public WidgetLayout getWidgetLayout() {
        try {
            if (!isHasCustomized()) {
                // if no draft widget frames exist, then no default layout exists
                if (widgetManager.getWidgetFrames(getCommunity()).size() == 0) {
                    copyDefaultLayout();
                }
                .....

 

 

And to copy the default, we do this:

 

 

/**
* Copy the default layout (large left, small right) to the current community.
*/
     protected void copyDefaultLayout() {
          WidgetManager widgetManagerUnproxied = (widgetManager instanceof WidgetManagerProxy) ? ((WidgetManagerProxy)widgetManager).getUnproxiedObject() : widgetManager;

          try {
               WidgetLayoutDescriptor wld = widgetManagerUnproxied.getWidgetLayoutByName(TwoColumnLargeLeftColumnLayout.class.getName());
               WidgetLayout wl = widgetManagerUnproxied.getWidgetLayout(wld);
               widgetManagerUnproxied.addWidgetLayout(getCommunity(), wl);
          .....             

 

 

 

And at that point, you'll have the default layout with no widgets, and so further down the line you'll see a call to getWidgetFrames, and similarly to above it will copy the default:

 

 

public Map<Integer, List<WidgetFrame>> getWidgetFrames() {
        if (!isHasCustomized()) {
            if (widgetManager.getWidgetFrames(getCommunity()).size() == 0) {
                copyDefaultFrames();
            }
          .....

 

 

which will add the default widgets:

 

/**
     * Copy the default widget frames to the current community:
     *  <ul>
     *      <li>Left: Sub-Spaces</li>
     *      <li>Left: Projects</li>
     *      <li>Left: Recent Activity</li>
     *      <li>Right: Actions</li>
     *      <li>Right: Top Members</li>
     *      <li>Right: Tag Cloud</li>
     *  </ul>
     */
    protected void copyDefaultFrames() {
        try {
            WidgetManager widgetManagerUnproxied = (widgetManager instanceof WidgetManagerProxy) ? ((WidgetManagerProxy)widgetManager).getUnproxiedObject() : widgetManager;

            // column 1
            int row = 0;
            if (widgetManagerUnproxied.getWidgetByName(SubCommunitiesWidget.class.getName()) != null) {
                widgetManagerUnproxied.addWidgetFrame(WidgetUtils.createWidgetFrameBean(getCommunity(), SubCommunitiesWidget.class.getName(), 1, row++));
            }
            if (isProjectsEnabled()) {
                if (widgetManagerUnproxied.getWidgetByName(CommunityProjectsWidget.class.getName()) != null) {
                    widgetManagerUnproxied.addWidgetFrame(WidgetUtils.createWidgetFrameBean(getCommunity(), CommunityProjectsWidget.class.getName(), 1, row++));
                }
            }
            if (widgetManagerUnproxied.getWidgetByName(RecentContentWidget.class.getName()) != null) {
                widgetManagerUnproxied.addWidgetFrame(WidgetUtils.createWidgetFrameBean(getCommunity(), RecentContentWidget.class.getName(), 1, row));
            }

            // column 2, start row back at 0
            row = 0;
            if (widgetManagerUnproxied.getWidgetByName(CommunityActionsWidget.class.getName()) != null) {
                widgetManagerUnproxied.addWidgetFrame(WidgetUtils.createWidgetFrameBean(getCommunity(), CommunityActionsWidget.class.getName(), 2, row++));
            }
            if (widgetManagerUnproxied.getWidgetByName(TopMembersWidget.class.getName()) != null) {
                widgetManagerUnproxied.addWidgetFrame(WidgetUtils.createWidgetFrameBean(getCommunity(), TopMembersWidget.class.getName(), 2, row++));
            }
            if (widgetManagerUnproxied.getWidgetByName(TagCloudWidget.class.getName()) != null) {
                widgetManagerUnproxied.addWidgetFrame(WidgetUtils.createWidgetFrameBean(getCommunity(), TagCloudWidget.class.getName(), 2, row));
            }
        }
        ......

 

 

II. How do the objects interact, and how are they displayed?

Sometimes these widget layouts, widget layout descriptors, widgets, widget frames, etc can get kind of confusing. I hope this illustration and ftl explanation can help paint a better picture, and deepen the understanding.

 

As an overview, you have the layout (red - WidgetLayout), the layout descriptor (blue - WidgetLayoutDescriptor) which tells how to display the widgets (green - WidgetFrames, contain widgets). A bit more technical tidbits from the database side of things, the container in jiveWidgetFrame is essentially the column, and the frameIndex is essentially the row. See more here:http://www.jivesoftware.com/builds/docs/clearspace/latest/DatabaseSchemaGuide.html#jiveWidgetFrame

defaultLayout_explain_b.png

 

We saw above how it will grab the widget layout, but I didn't show it returning from that method. This can be seen as the red box in the picture above (the layout)

 

<#if widgetLayout?exists>
    <div id="jive-body-main">
        <div id="jive-widget-content">
            <div id="thread.watch.notify" class="jive-info-box" style="display:none">
            </div>

            <#include "/template/global/include/form-message.ftl" />

            <#include "${widgetLayout.freemarkerTemplate}" />

        </div>
    </div>
</#if>

 

 

public WidgetLayout getWidgetLayout() {
        try {
            if (!isHasCustomized()) {
                // if no draft widget frames exist, then no default layout exists
                if (widgetManager.getWidgetFrames(getCommunity()).size() == 0) {
                    copyDefaultLayout();
                }
                return widgetManager.getWidgetLayout(getCommunity());

 

 

After returning the layout, we look to the layout FTL (which for this particular layout is ls.ftl) and CommunityAction for what happens next (this is the blue box above, the layout descriptor). Each different layout FTL asks for the widget frames associated with it, and will have them available via the CommunityAction. For our example, it looks for the widget frames in the left container, and then the right container.

 

This loads the widgets in the left container (green A)...

 

<div id="jive-body-layout-ls">
    <div class="jive-body-layout-l">
        <div id="jive-widget-container_1" class="jive-widget-container jive-widget-container-large">
         <#if widgetFrames?exists && widgetFrames.containsKey(1?int)>
             <#list widgetFrames.get(1?int) as widgetFrame>
                 <#if widgets?exists>
                     <@jive.editWidgetFrame widgetFrame=widgetFrame />
                 <#else>
                     <@jive.displayWidgetFrame widgetFrame=widgetFrame size=enums["com.jivesoftware.community.widget.Widget$ContainerSize"].LARGE />
                 </#if>
             </#list>
         </#if>
        </div>
    </div>

 

 

....and the rest of the file loads the widgets in the right container (green B)

    <div class="jive-body-layout-s">
         <div id="jive-widget-container_2" class="jive-widget-container jive-widget-container-small">
         <#if widgetFrames?exists && widgetFrames.containsKey(2?int)>
             <#list widgetFrames.get(2?int) as widgetFrame>
                 <#if widgets?exists>
                     <@jive.editWidgetFrame widgetFrame=widgetFrame />
                 <#else>
                     <@jive.displayWidgetFrame widgetFrame=widgetFrame size=enums["com.jivesoftware.community.widget.Widget$ContainerSize"].SMALL />
                 </#if>
             </#list>
         </#if>
         </div>
    </div>
</div>

 

 

They widgetFrames are available from this call to the widgetManager in CommunityAction.getWidgetFrames():

return widgetManager.getPublishedWidgetFrames(getCommunity());

 

And that's it! The widgets are displayed.

 

 

III. How we customized this

As you've seen, it's all initiated by NOT having anything setup the first time you are directed to the community.ftl file. So, we needed to ensure that there was a widget layout, and within that widget layout there are widget frames. So, during our secure space creation we need to hook into this ourselves. Have a look at how we're doing it:

 

1. Get a list of all the widgets in the system

 

Map<WidgetDescriptor, Widget> widgets = widgetManager.getAvailableWidgets();

2. Identify the two widgets we want to add initially, and loop through all the available widgets and hold onto them

 

Widget actionWidget = null;
Widget openCasesWidget = null;
for(Iterator<WidgetDescriptor> it = widgets.keySet().iterator(); it.hasNext(); ) {
     WidgetDescriptor descriptor = it.next();
     Widget widget = widgets.get(descriptor);

     if(descriptor.getClassName().equalsIgnoreCase("com.jivesoftware.community.widget.impl.CommunityActionsWidget")) {
          actionWidget = widget;
     } else if(descriptor.getClassName().equalsIgnoreCase("com.jivesoftware.community.portal.widgets.OpenCasesWidget")) {
          openCasesWidget = widget;
     }
}

 

3. Then, as we saw above, we need to get the widget layout desired and add it to the newly created secure community

try {
  WidgetLayoutDescriptor wld = getWidgetLayoutByName(TwoColumnLargeLeftColumnLayout.class.getName());
  WidgetLayout wl = widgetManager.getWidgetLayout(wld);
  widgetManager.addWidgetLayout(community, wl);
  widgetManager.publishWidgetLayout(community);
}

 

4. Finally, setup our OpenCasesWidget (custom built by us) and the Actions widget. They are mapped to the correct lay out by the matching up of parentObjectType and ParentObjectID.

 

//load the widgets
WidgetFrameBean widgetBean = new WidgetFrameBean();
widgetBean.setWidgetID(openCasesWidget.getID());
widgetBean.setContainerID(1);
widgetBean.setParentObjectID(community.getID());
widgetBean.setParentObjectType(JiveConstants.COMMUNITY);
widgetBean.setPublished(true);
widgetManager.addWidgetFrame(widgetBean);

WidgetFrameBean actionWidgetBean = new WidgetFrameBean();
actionWidgetBean.setWidgetID(actionWidget.getID());
actionWidgetBean.setContainerID(2);
actionWidgetBean.setParentObjectID(community.getID());
actionWidgetBean.setParentObjectType(JiveConstants.COMMUNITY);
actionWidgetBean.setPublished(true);
widgetManager.addWidgetFrame(actionWidgetBean);

 

5. All done!

 

That is how we are creating the communities and customizing their overview tabs. Hope this has been helpful!

 

Let me know if there are any questions on the above functionality