Stopping By Abou Shousha's On A Snowy Evening

(or) Why categories are so much more than nestable tags


"I LOVE tags," Jaffer managed despite a mouthful of spiced lamb ouzie.
 
Sergei was finding the younger guest a bit annoying. The kid had ambled in from the snow to get a bite. Sergei himself had come in to escape the noise that seasoned other cafeterias. He fancied Abou Shousha's Mediterranean Oasis for its laid back ambience, not to mention the unending supply of Maghrebi mint tea.
 
And now, this kid. 
 
"They're okay," he mumbled before returning to crafting an XPATH expression for his ADT.
 
Jaffer's jaw dropped open. 
 
"OKAY?!? You got to be kidding me. Tags are PHENOMENAL. I mean, what else do you need to organize and query all that content?"
 
Sergei ignored him, but it wasn't easy.
 
"Huh? Right? Am I right?" Jaffer persisted as shreds of ouzie drizzled out of his mouth onto the platter below.
 
Sergei broke away from his laptop and turned to face the young programmer. 
 
"Have you heard of categories?"
 
Jaffer nodded vigorously. "Yeah, but you don't need them. Tags and categories all get concatenated into a list of comma-separated values and assigned to the page's keywords meta tag. Okay, sure, categories can have child categories..."
 
The boy wonder picked up crumbs of fallen ouzie and tossed it into his mouth as he went on.
 
"...and maybe a hierarchy of categories is useful to some businesses, but hey, you don't need categories. They just make your site harder to maintain. Am I right?"
 
Sergei pursed his lips and raised his eyebrows just as the grocer began making his rounds dispensing complimentary tea. The veteran developer picked up a cup of the steaming liquid and nodded his thanks. The aroma rose into his nostrils and he inhaled, even as he entertained the thought of how best to get this kid out of his hair, or what was left of it. He took a long sip and set the cup aside.
 
"Take a look," he invited the younger man to look at his screen as he clicked a few times. Jaffer leaned over and stared at into the display.
 
"Here, that's a tag for you. Let's edit it."
 
Another click. 
 
 
"See, all you can do is change it. There's nothing else it gives you."
 
Jaffer threw his hands up in exaggerated frustration.
 
"That's my point, Uncle."
 
"Uncle?" Sergei wrinkled his brow.
 
Jaffer went on. "A tag is a tag and that's all it is - a tag. A simple powerful way to... to.... to TAG your content. Nothing else. That is its power."
 
Sergei nodded, "That is true. I'm not going to argue it. Let's take a look at a category now, shall we?"
 
A couple more clicks. 
 
 
"Here's a category. Look! An Edit button AND a Permissions button as well." 
 
Jaffer bounced back from the display and shook his head dismissively. "Why? Why would a category need permissions?"
 
Sergei leaned back, looking down at his keyboard as he spoke. 
 
"Let's say you create a bunch of categories and sub-categories in your vocabulary, and then you realize your administrative team, you know content contributors and such, they need to look at the content through a different lens, let's say department-wise, or something along those lines. So now you create a bunch of internal, as in private, categories in the same vocabulary or in a whole new vocabulary if you like, but... and this is key..."
 
Sergei looked up and was a bit glad to see he had Jaffer's undivided attention.
 
"... but then you grant the View permission on those internal categories to the Site Member role only. So guests don't see the internal categories when the content comes back, say, on a search. Your admin folks can now configure their Asset Publisher instances to query by any combination of public and internal categories to get the view they want. Make sense?" 
 
Jaffer nodded. "Yeah. Actually, I think I could use that to address a certain requirement. In fact, I..."
 
Sergei cut him off. "Hold on. There's more. Let's click the Edit button."
 
 
"See that Properties section down there?" he pointed with his hand as Jaffer leaned in again.
 
"That, my young friend, is where the power lies."
 
Sergei took a few more sips of the mint tea. Jaffer's eyes were wide with anticipation. He wore the look of a child about to become privy to a long-guarded secret.
 
"Go on," he said softly.
 
Sergei typed frantically. "Let me just show you."
 
 
"There, I created one arbitrary property: BackgroundColor. Remember this is just an arbitrary textual key-value pair."
 
Lots more clicking and typing. 
 
"Here's an ADT I was working on for use by an Asset Publisher. Let's write a snippet to read in the category matching a content item, and then check to see if it has a "BackgroundColor" property. If it does, we'll read its value, and we'll use that value to set the background color of the DIV that holds the result corresponding to that content item."
 
#set ($vocabularyLocalService = $serviceLocator.findService("com.liferay.portlet.asset.service.AssetVocabularyLocalService"))
#set ($vocabularies = $vocabularyLocalService.getGroupVocabularies($getterUtil.getLong($groupId)))
 
### we are interested in 2 specific vocabularies - Schools and StudyLevels
#foreach($voc in $vocabularies)
    #if ($voc.name == "poems")
        #set ($poemsVocabularyId = $voc.vocabularyId)
    #end
#end
...
...
#if (!$entries.isEmpty())
    <div>
    #foreach ($curEntry in $entries)
        #set( $journalArticle = $renderer.getArticle() )
        #set ($articlePrimKey = $journalArticle.getResourcePrimKey())
        #set ($catLocalService = $serviceLocator.findService("com.liferay.portlet.asset.service.AssetCategoryLocalService"))
        #set ($catPropertyLocalService = $serviceLocator.findService("com.liferay.portlet.asset.service.AssetCategoryPropertyLocalService"))
        #set ($articleCats = $catLocalService.getCategories("com.liferay.portlet.journal.model.JournalArticle", $articlePrimKey))
        #set($viewURL = $assetPublisherHelper.getAssetViewURL($renderRequest, $renderResponse, $curEntry))
        #set( $document = $saxReaderUtil.read($journalArticle.getContent()) )
        #set( $rootElement = $document.getRootElement() )
        
        #set( $pictureSelector = $saxReaderUtil.createXPath("dynamic-element[@name='imgPicture']") )
        #set( $picture = $pictureSelector.selectSingleNode($rootElement).getStringValue() )
 
        #set ($backgroundColorCode = "")
 
         ###### get the poem categories #######
         #foreach($cat in $articleCats)
             #if($cat.vocabularyId == $poemsVocabularyId)
                 #set ($backgroundColorCode = $catPropertyLocalService.getCategoryProperty($cat.categoryId, "BackgroundColor"))
             #end
         #end
 
######
###### Display the poem in a card #######
######
 
        <div class="span4" style="background-color: #$backgroundColorCode.value; color:white; padding:10px; margin:10px; height:200px; border-radius:10px;">
...
...
 
Jaffer's eyes took in everything as Sergei typed in lines and lines of velocity code from experience, and then proceeded to assign a few content items the category he was testing out. When Jaffer saw the resulting list returned by the asset publisher instance with the one content item having a distinct background color, he let out a low whistle. 
 
Sergei drained the remainder of his tea. 
 
"Think about the possibilities," Jaffer mumbled to himself. His eyes turned to rows of canned hummus and olive jars but his mind was elsewhere.
 
"We could store relative URLs to a document or an image as the value of that property field, or even... even store little clever flags that can dictate all sorts of complex work that the templates could do for a given category."
 
"Exactly!" Sergei couldn't contain his own pleasure at having reached out and touched a soul that needed more. 
 
"And wait, can we add more than one property to a category?" Jaffer asked as he poked his head towards Sergei's display.
 
"Oh yeah," replied Sergei. And he showed him how.
 
 
Jaffer slapped his thigh in delight.
 
"Uncle, you just helped me address a huge chunk of requirements that crept into my plate recently. How can I ever repay you?"
 
Sergei allowed a little smile. "How about not calling me Uncle?"
 
Jaffer grinned, then returned his plate to the counter and flew out of Abou Shousha's like a tornado that was late for an appointment in another town.
 
Sergei took a deep breath, glad for the return of silence in his little haven. 
 
"More tea, my friend?" the smiling grocer asked as he eyed Sergei's empty cup.
 
Sergei held it up in gratitude.
 
"Yes, my friend. Shukron."
Blogs
Hello Javeed,
I liked it. I think for people new in Liferay, it is a good opportunity to learn those concepts.
It is written a bit like "The Goal" of Golratt. https://en.wikipedia.org/wiki/The_Goal_%28novel%29
You know it ?
Hi Pierre. Thanks for the comment. I'm not familiar with that book, but it is on my reading list now :-).

I also recall this being done effectively for memcached - the original post I read over two years ago has moved, I was able to find it here.
https://github.com/memcached/memcached/wiki/TutorialCachingStory