For one reason or another your bosses (we all have more than one, don't we?) have told you that the look-n-feel of the common object opener in ALUI just doesn't cut it. Even though it's powerful, scalable and pretty nice-looking and it includes a myriad of options (e.g search, browse, single vs. multi-select, set previously selected, etc.), they just want something different. Perhaps they don't want a pop-up window. Perhaps they don't like how many clicks it takes to get down to an object. Perhaps they're just being difficult.
Regardless, you've been asked to come up with a clean, fast, in-place object selector that still shows a hierarchical view. (For the purposes of this discussion, I'm going to use the example of communities from here on out instead of just talking about "objects.") So naturally, as a portlet developer, you turn to the IDK. Unfortunately, if you want to get portal metadata, you have to use the PRC/SOAP server. There goes fast. So maybe you can write it in native code or using database calls. There goes clean.
Your best bet here -- and really the only good way to accomplish this -- is to develop a custom taglib. Custom taglibs are quickly becoming my favorite new feature in G6. (BTW, if you aren't on G6, upgrade ASAP -- it's worth the effort.) So, for your benefit, I decided to try my hand at writing a taglib to present a nice hierarchy of communities. Here's what I discovered.
First off, let's talk about the HTML I want to display in my custom tag for a moment. Whoever came up with the concept of select boxes and optgroup elements was a complete goofball. Why develop something that's naturally suited for a hierarchy and then limit the hierarchy to a depth of one?
Here's an example:
So I had to throw my initial idea of using nested option
elements out the window simply because you can't nest an optgroup
within an option
. Bummer.
So here's the display I settled on:
There's still a hierarchy here, it's just flattened and there's essentially a "breadcrumb" for each community. In the example I have, bdg is a top level community and services is a subcommunity of bdg. Consulting, development, integration and training are all subcommunities of services.
Alrighty then, so how to you construct this nice select box? And BTW, make it easy, clean and fast. Here you go:
package com.bdgportal.alui.taglibimport com.plumtree.openlog.OpenLogService;
import com.plumtree.openlog.OpenLogger;
import com.plumtree.portaluiinfrastructure.tags.*;
import com.plumtree.portaluiinfrastructure.tags.metadata.*;
import com.plumtree.xpshared.htmlelements.*;
import com.plumtree.server.*;public class CommunitySelector extends ATag
{
private static OpenLogger log = OpenLogService.GetLogger(
OpenLogService.GetComponent("UI_Infrastructure"),
"com.bdgportal.alui.taglib.CommunitySelector");public static final ITagMetaData TAG;
public static final RequiredTagAttribute SELECT_ID;
public static final RequiredTagAttribute SELECT_NAME;
public static final OptionalTagAttribute SELECT_CLASS;
public static final RequiredTagAttribute ROOT_FOLDER_ID;static
{
TAG = new TagMetaData("communityselector",
"Displays a community selector.");SELECT_ID = new RequiredTagAttribute("id",
"The id of the select box.",
AttributeType.STRING);SELECT_NAME = new RequiredTagAttribute("name",
"The name of the select box.",
AttributeType.STRING);ROOT_FOLDER_ID = new RequiredTagAttribute("rootfolderid",
"The root folder. All communities in this folder and below " +
"will be displayed.",
AttributeType.INT);SELECT_CLASS = new OptionalTagAttribute("class",
"The CSS class of the select box.",
AttributeType.STRING, "objectText");
}public HTMLElement DisplayTag()
{
HTMLSelect comms = new HTMLSelect(
GetTagAttributeAsString(SELECT_NAME),
GetTagAttributeAsString(SELECT_ID));comms.SetStyleClass(GetTagAttributeAsString(SELECT_CLASS));
recursiveAddComms(((IPTSession)GetEnvironment().GetUserSession())
.GetCommunities(), ((IPTSession)GetEnvironment()
.GetUserSession()).GetAdminCatalog(), comms,
GetTagAttributeAsInt(ROOT_FOLDER_ID), "");return comms;
}public ATag Create()
{
return new CommunitySelector();
}public TagType GetTagType() {
return TagType.NO_BODY;
}private void recursiveAddComms(IPTObjectManager commObjMgr,
IPTAdminCatalog adminCatalog, HTMLSelect comms,
int folderId, String prefix) {//CAB: add the communities at this level, if any
IPTQueryResult commsToAdd = commObjMgr.SimpleQuery(folderId,
PT_PROPIDS.PT_PROPID_NAME);
for (int i = 0; i < commsToAdd.RowCount(); ++i) {
comms.AddOption(new HTMLOption(
Integer.toString(commsToAdd.ItemAsInt(i, PT_PROPIDS.PT_PROPID_OBJECTID)),
prefix.substring(0, prefix.length() - 3)));
}IPTAdminFolder adminFolder = adminCatalog.OpenAdminFolder(folderId, false);
if (0 == adminFolder.QuerySubfoldersCount()) {
return; //CAB: base case
} else {
IPTQueryResult subFolders = adminFolder.QuerySubfolders(
PT_PROPIDS.PT_PROPID_OBJECTID + PT_PROPIDS.PT_PROPID_NAME
+ PT_PROPIDS.PT_PROPID_FOLDER_FOLDERTYPE,
0,
PT_PROPIDS.PT_PROPID_NAME,
0,
-1,
null);//CAB: recurse into each subfolder
for (int i = 0; i < subFolders.RowCount(); ++i) {
recursiveAddComms(commObjMgr, adminCatalog, comms,
subFolders.ItemAsInt(i, PT_PROPIDS.PT_PROPID_OBJECTID),
prefix + subFolders.ItemAsString(i, PT_PROPIDS.PT_PROPID_NAME)
+ " : ");
}
}
}
}
I think the code pretty much speaks for itself, but if you want further explanation, let me know by posting a comment.
Comments
Comments are listed in date ascending order (oldest first)
- Iiiinteresting. This is very cool, Chris. I might be forced to highjack this and turn it into a pt:data tag :) You have any thoughts on how / where you might approach caching with this? Have you seen the EOD sample tag?
- Hmmm . . . caching. It's so fast OOTB that I didn't think about caching it. :-) Plus, I used it on a project where we have fewer than 100 communities, so I didn't have any problems. I mean, we're not using the PRC, right? I guess if you wanted to cache it you could doink around with the shared variables in the tag library (session scope), but you've got to worry about clearing the cache after a fixed interval of time.