I am developing a page that displays items from a JSR-170 query of a content item in a slot. The items returned by the slot need to be grouped by alphabetical order and displayed under their associated alphabetical letter linked by anchor links at the top of the page to the respective letter.
So far this works great except I need to exclude letters from the top anchor links where no items are returned. Is there a way to make a dummy slot that I can modify to only show a distinct listing of the first letters returned?
I noticed the $sys.index variable, “This variable can be used to modify the rendering of different rows in the Slot, or to add more data to the snipped rendering” - p475 Rhythmyx Implementation Guide.
We did something similar in our A-Z index (which in our case is an auto slot). I am sure that the same idea/similar method will work for you…
Update: clearly you want the auto query to output the results ordered by name (order by displayname asc)…
#set($slotname = "myslotname")
#set($folderpath = "//Sites/mySite/myFolder")
<a name="top"></a>
#set($params = "folderpath=$folderpath&template=ourSnTemplate")
#initslot($slotname $params)
#if($sys.currentslot.relresults.size() > 0)
#set($oldChar = '-1')
## get first characters for list at top and anchor names
<ul class="az_heading">
#foreach($relresult in $sys.currentslot.relresults )
## get the firstcharacter of the display title
#set($currChar = $relresult.getNode().getProperty("rx:displaytitle").String.substring(0,1).toUpperCase() )
#if ($currChar != $oldChar)
#set($oldChar = $currChar)
#set($dq = '"') ## "
#set($anchText = "href=${dq}#${currChar}${dq}")
<li><a $anchText>$currChar</a></li>
#end
#end
</ul>
## next set with actual links
#set($oldChar = '-1')
<ul>
#foreach($relresult in $sys.currentslot.relresults )
#set($currChar = $relresult.getNode().getProperty("rx:displaytitle").String.substring(0,1).toUpperCase() )
#if ($currChar != $oldChar)
#if ($oldChar != '-1')
</li></ul>
#end
#set($oldChar = $currChar)
<li><h2><a name="$currChar">$currChar</a></h2>
<ul><li class="az_gotop"><a href="#top">Return to Top</a></li>
#end
<li>
$rx.doc.extractBody($relresult)
</li>
#end
</ul>
#end
#endslot($slotname)
The previous ‘A - Z Index’ post has been very helpful. I am now running into another issue. I have several slots that accept various content types with different fields in each. I want to sort all the items in the slot alphabetically by 2 or 3 different fields (and these fields don’t all exist in each content type). If I try to specify those fields in the order_by parameter (which is in the Extension Parameters screen of the sys_RelationshipContentFinder option under the ‘Content finder:’ options) I get the items grouped by content type and then alphabetized.
My next option would be to handle the sorting of the slot manually, but I have yet to be able to create an array that I can append items to and then sort correctly.
Any ideas for either of these two options? Or are there others I am missing?
So if i understand you properly, you want to be able to sort a particular slot based on what content types are contained in the slot? Or are you trying to create a generic macro that given any particular slot, will “sort” the items in the slot (i say in quote b/c you are sorting the display, not the actual slot items)?
Something that i’ve done is create a generic auto slot for an auto indexer. In our case a user picks a query and depending on what is selected, i create a query that i pass as a parameter (using bindings http://forum.percussion.com/showthread.php?t=1381&highlight=bindings) to the slot.
What can probably help you a lot if you are building the slot items is:
#foreach($relresult in $sys.currentslot.relresults )##
...
#set($myContentType = $relresult.getNode().getPrimaryNodeType().getType())
...
Then you can build the output depending on the content type found in the slot…or you could order all of a particular content types in a slot based on some field that only that content type has (by searching all the items in the slot for that particular content type)…the possibilities are endless…
No to the first and yes to the second. I do want to ‘sort’ the display of the items, and I don’t want to do it based on content type. I want the display text that comes out of the snippet template to be sorted alphabetically regardless of which content type it is. In my case, these would not be auto slots.
So, for example:
Content Type X has field A (and not B or C)
Content Type Y has field B (and not A or C)
Content Type Z has field C (and not A or B)
I want to sort all items based on fields A, B, and C. This would mean that items of all these content types would be interspersed throughout the list of slot items in the display.
I figured there were two ways to approach it. I could try to get the slot to order the items correctly, but this doesn’t seem to work. If I specify the different fields for the various content types the result without ordering anything at the template level is that the items are grouped by type and then sorted alphabetically.
The second approach I can see is to ‘sort’ the display of the items in the template. If I’m thinking correctly, in order to do that I would have to grab all the items in the slot using a #foreach statement similar to what you mention, but then I would have to append each item to an array and then sort that array somehow. That is where I’m stuck. I can create an array in Velocity, but I don’t know how to append items to it.
You could rename the fields in each content type so they are all called the same thing. If you keep them as local fields, their labels can still be different, so your users wouldn’t know the difference.
Or, if you are too far down the road with the development of these content types, you could add a new hidden field to each of the content types, called the same thing in each, and use a pre-processing call to the sys_CopyParameter extension to copy the values entered in those different fields into your single, common hidden field. Then sort on that in your templates. Obviously, you’d need to do some work in the backend database to fill in the hidden field for your already existing content items.
The reason I am suggesting this kind of workaround (that requires you to jump through hoops, fill your database with junk, and change the way you work in order to implement something that should be trivially easy - the kind that always annoys the hell out of me when someone from Percussion suggests something like it, but at least I am not charging you for this advice) is that I’ve wasted a lot of time trying to sort arrays. There is a sorter Velocity tool (accessed via $tools.sorter.sort) but that requires the names of simple, direct properties (Java properties, not properties of content types which are accessed via methods) to sort on. To sort the complex objects in the java.util.ArrayList returned by $sys.currentslot.relresults you need to use Java’s collection sorting techniques, which would require access to static methods (which we don’t have in Velocity) and the ability to create a Comparator object (which we cannot do in Velocity) or for the PSAssemblyWorkItem objects in the array to have implemented the Comparable interface (which they do not seem to have, but I cannot find documentation to confirm it.)
#initslot($slotname $params)##
## Create an ArrayList to hold a map for each item found in slot
#set($foo = [0])##
## Remove dummy entry in array necessitated by method of creating array
$foo.clear()##
## Loop thru items in slot
#foreach( $relresult in $sys.currentslot.relresults )##
## Create HashMap
#set($bar = $rx.string.stringToMap("qwerty=uiop"))##
## Remove dummy entry in map necessitated by method of creating map
$bar.clear()##
## Store string in map containing Rx field to sort on
#set($bar.title = $relresult.node.getProperty("rx:displaytitle").String)##
## Store assembly work item
#set($bar.assworkitem = $relresult)##
## Add map to array (in IF statement otherwise it'll print "true" in the assmebled page
#if($foo.add($bar)) #end##
#end##
## Sort the array by the field in the map for each item
#foreach( $baz in $tools.sorter.sort($foo, "title"))##
## Assemble items using template specified in $params
#slotItem($baz.assworkitem)##
#end##
#endslot($slotname)##
That works for me. For you, rcbush, you’ll need some conditional code to set $bar.title appropriately according to what content type each item in the slot is.
Not very efficient, but it might come in very handy. Thanks jitendra for the suggestion.
Cool…so nice to see an idea actually get implemented. Excellent job i don’t think i could have done a nicer job… (great idea to store the assembly work item and to use the #slotItem macro)
My only concern would be in
#foreach( $baz in $tools.sorter.sort($foo, "title"))##
Shouldn’t you break out the sort from that foreach loop? To me it seems like it would attempt to sort $foo everytime (even after it should have done sorted it properly on the first run)
In our scenario, users dont want an automated slot, but a regular slot where they can add content items from multiple sites and folders and then want them to be displayed in an A-Z Index page. How can this functionality be acheived using regular slots and not automated slots?
In Jitendra’s code for A-Z index, is it possible to make $folderpath variable dynamic, users selecting the folder rather than hardcoding it in the template?
The source of the items in the slot is completely decoupled from the rendering, so you should have no problems applying this technique to regular (non-automated) slots.
As for your second question, where do the users enter the folder path? If it’s in a field on the item, I guess it’s possible. There’s no easy way to “ask the user” for input during assembly, as this can happen during either preview or publish.
In terms of the second question did you figure it out?
If not, yes that folder path can be dynamic (and for what i have done, it is dynamic. It is only fixed for the example). Just create a field and set the contents of that field to the folderpath variable.
I’m trying to adapt the hashmap sorting code you wrote below, but I can’t get it to sort. I have an array holding the nodes for content items that are coming from two different slots. I loop through the array instead of the items in a single slot to apply your code. I can see that I have created the hashmap, and can get the node and “last” field from it, but it does not sort by the “last” field as desired. Any help much appreciated!
## Create an ArrayList to hold a map for each item found in slot
#set($foo = [0])##
## Remove dummy entry in array necessitated by method of creating array
$foo.clear()##
## Loop thru items in my array of nodes from two slots
#foreach($item in $newarray)##
## Create HashMap
#set($bar = $rx.string.stringToMap("qwerty=uiop"))##
## Remove dummy entry in map necessitated by method of creating map
$bar.clear()##
## Store string in map containing Rx field to sort on
#set($bar.last = $item.getProperty("last_name").String)##
## Store node
#set($bar.node = $item)##
## Add map to array (in IF statement otherwise it'll print "true" in the assmebled page
#if($foo.add($bar)) #end##
#end##
## Sort the array by the field in the map for each item
#foreach($baz in $tools.sorter.sort($foo, "last"))##
## display needed field from node
$baz.node.getProperty("sys_title").String##
#end##
The original collection will not be re-ordered; a new list containing the sorted elements will always be returned.
so, if you’re trying to reorder the $foo list, then use it later, you may want to try:
#set( $foo = $tools.sorter.sort($foo, "last") )
another note… newlines after most velocity directives are ignored. You don’t need to put ## at the end of every line if you don’t want to.
and another one… use #set( $foo = ) and #set( $bar = {} ) if you have velocity 1.5 or higher installed. it’s much easier to read, you don’t have to clear the contents, and it’s more efficient to boot.