Return to Jive Software

Jivespace Community Blog

2 Posts tagged with the customize tag

One very important feature of the Supportal that can sometimes be overlooked is the ability to make a case public.  A public case is just a case that shows as a discussion thread in a community that can be viewed by anyone.

 

The Supportal makes it easy to change a case from just being a normal case in your secure account space into a public case that can benefit the whole Clearspace user base.  Let's take a look at how this is done:

A private case

Let's suppose that you've already created a case in your secure account space.  As a customer, when you view the case, the case fields will look like this:

blogpost_privatecase.png

Notice the "public" check box is not checked.  Also, notice that the breadcrumbs at the top show that the case thread is in the secure account space as expected.

 

A public case

Being a good citizen of the broader Clearspace user community, you see that this question or issue that you had in this case is something that others may encounter as well.  Making this case public would help others out because if they are searching for an answer to the same question, this case thread will then show in their search results and they can find that answer quickly.  Who knows, maybe they will then return the favor by making a case they have public as well which could benefit you later on down the road .

 

Ok, so you look at the content of the case thread and ensure that there is nothing there that is sensitive information, and you decide to make the case public.  How is this done?  With just two easy steps:

  1. Click the "public" check box to indicate you want the case publicly viewable.
  2. After clicking the check box, you'll see a combo box showing the possible communities that you can put the public case in.  Choose whatever community makes most sense for the content of the case thread.

 

Now, click the "Update Case" button.  Looks like nothing changed right?

blogpost_publiccase.png

 

Take a look at the breadcrumbs.  Notice that this discussion thread is now listed as being in the public community that you selected above.

blogpost_publicbreadcrumb.png

Also, notice that the case now has a box that says "This is a public case which is tied to your support account.  Your community is located here:"  The "Account Community" link takes you back to the homepage for your secure account space.

 

But wait.  You still want this to show as a case in your list of cases in your secure account space so you can easily reference it for yourself later right?  No problem.

blogpost_community.png

blogpost_casestab.png

The case appears as expected in the list of cases in your secure account space, but it actually resides in a public community now.  You can manage the case as you would any other private case, but the content of that case is now benefiting the whole community!

 

The code

Making the case a publicly viewable case was easy enough right?  Surely, the code behind the scenes must be complicated!

 

Not at all!

 

When the "Public" check box is checked, some Javascript on the page fires to show the communities that the case thread can be moved to:

function changeVisibility()
{
    if (document.getElementById('publicCase').checked==true) {
        document.getElementById('publicCommunityTag1').style.display="table-row";
    }
    else {
        document.getElementById('publicCommunityTag1').style.display="none";
    }
}

The list of community options that are show in the community drop down is populated in the action class with this code:

public List<Community> getPublicCommunityOptions() {
    List<Community> availCommunities = new ArrayList<Community>();
    Community root = communityManager.getRootCommunity();
    for (Community c : communityManager.getRecursiveCommunities(root)) {
        if (!accountManager.isAccountContainer(c)) {
            availCommunities.add(c);
        }
    }
    return availCommunities;
}

The call to accountManager.isAccountContainer(c) calls a custom class in the Supportal that basically just checks to see if the community has an extended property set that marks it as a secure account space or not.  If the community is not an account space, it is eligible for putting public cases into.

 

Here is the code from the method responsible for doing the move:

private void moveCaseThread(ForumThread caseThread, long targetContainerID) {
     try {
          Community publicCommunity = communityManager.getCommunity(targetContainerID);
          forumManager.moveThread(caseThread, publicCommunity);
      }

The first line gets the Community object for the community that the discussion thread is going to be moved to (the one that was selected in our example above).  The second line is what causes the move of the discussion thread to occur.

 

What about the code responsible for having the public case still show in the open cases widget?

 

We store the account that the case was created in originally as an extended property on the ForumThread object.  So, to get all the threads associated with a private account community, we use this SQL:

SELECT threadID FROM jiveThreadProp WHERE name='caseAccountID' AND propValue=<PRIVATE_ACCOUNT_ID> ORDER BY threadID DESC

This gives us a list of the thread ids for all threads that are associated with <PRIVATE_ACCOUNT_ID> no matter what community that thread actually lives in.

 

That's really pretty much all there is to it.

 

 

So the next time you open a new case with Jive Software Support, ask yourself, "Would the broader Clearspace user community benefit from the question and answer I just had?"  If the answer is "yes," (and really, when is the answer not "yes?" ), make it public!

3,310 Views 1 Comments Permalink Tags: customize, supportal, public_case

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

2,310 Views 4 Comments Permalink Tags: widgets, customize, supportal