recursively query all "Tags" below a specific node?
I'm haivng trouble finding documentation about this, can anyone tell me how i can retrieve all tags below a specific node? I'm using languages and have a blog installed for each language, ofcourse i want to seperate the tags for different languages...
/nl/blog/...
-> dutch tag cloud for all tags below /nl/
fr/blog/...
-> french tag cloud for all tags below /fr
The default xslt that goes through each of the tags throughout the entire site:
using the tagsLib is a bit of trial and error, so there may be a better way to do this using tagsLib, but this will eventually go through all tags from all ndoes
As I said above, this is just a guess and I have not tried it, but it might be something to have a look into if you don't get an answer from someone else.
Doubt it as well, think you'll need to create a small xslt extension, similar to the existing ones, and return only those tags that are relevant to your node tree structure. Example code can be found in the umbraco source (editorControls project, there's a tag datatype folder that has a library class)
Chris: different tag groups would be a better option probably, but this would require even more changes (to the blog package) and i'm afraid i'm a little short on time for that... As for the choose statement, it's also a good option yes, but at this point there's little functional difference.
Dirk: this would defintatly increase it's resuability!
Be aware that Dan's solution will work great, but may affect performance as you'll hit the db for each getTagsFromNode() call (especially when having lots of childnodes/levels of child nodes
Dirk: that's definatly true, but i don't see any better ways, the tagslib isn't documented that much and the only alternative that might work would be using rag groups right?
If using Dan's solution, no there isn't, unless you write your own extension and build the xml tag fragment yourself. it will require some sql coding to get the right tags from the right nodes/childnodes, but it'll require a single db hit.
In addition, you could optimize performance by adding the output (xml fragment) to the http context cache (altho that would require you to invalidate the cache when a new document is being published - not sure if umbraco will invalidate the complete http context cache)
As I said above, this is just a guess and I have not tried it, but it might be something to have a look into if you don't get an answer from someone else.
To my knowledge getalltagsingroup doesn't support this way of working :(
yeh, using the "solution" you'll just get a big ol list of tags from all nodes.
When recently using tags for a document library, we decided not to use the built-in tag datatype as it seemed a little...flaky, esp. the autocomplete. We just used simple comma separated values in a textfield, then wrote our own function to take a "master list" of comma separated tags and spit out unique values with counts of the amount of tags.
The xslt is below - it's currently using the media library, but if you go down the route of just using a textfield and comma separated tags it should do what you need with a little tweaking. I've added some comments for clarity...
<!-- grab filter querystring param to use to filter tag list --> <xsl:variable name="filter"> <xsl:choose> <xsl:when test="string-length(umbraco.library:Request('tag')) > 0"> <xsl:value-of select="umbraco.library:Request('tag')"/> </xsl:when> <xsl:otherwise> <xsl:text>all</xsl:text> </xsl:otherwise> </xsl:choose> </xsl:variable>
<!-- if a media folder is chosen --> <xsl:if test="string($currentPage/data [@alias='documentMediaFolder']) != ''">
<!-- set the media folder node as a variable --> <xsl:variable name="mediaFolder" select="umbraco.library:GetMedia($currentPage/data [@alias='documentMediaFolder'], 'true')" />
<!-- Build up a comma separated list of all tags from all nodes (tags are themselves simply comma separated lists) --> <xsl:variable name="mediaFolderTags"> <xsl:for-each select="$mediaFolder/node [string(data[@alias='fileTags']) != '']"><xsl:value-of select="current()/data[@alias='fileTags']" />,</xsl:for-each> </xsl:variable>
<!-- pass the comma separated list of all tags to our function to get a unique list plus counts --> <xsl:variable name="resultSet" select="gecko:DistinctValues($mediaFolderTags)" />
<h4 class="document_tags"><span>Filter by tags</span></h4> <ul class="all_tags"> <!-- allow user to choose all tags --> <li> <xsl:if test="$filter= 'all'"> <xsl:attribute name="class"> <xsl:text>current</xsl:text> </xsl:attribute> </xsl:if> <a> <xsl:attribute name="href"> <xsl:value-of select="umbraco.library:NiceUrl($currentPage/@id)" /> </xsl:attribute> All </a> </li> <!-- for each tag in our result set --> <xsl:for-each select="$resultSet//value"> <!-- first sort by amount of tags, then alphabetically --> <xsl:sort select="@count" order="descending" /> <xsl:sort select="text()" order="ascending" /> <li> <xsl:if test="current()/text() = $filter"> <xsl:attribute name="class"> <xsl:text>current</xsl:text> </xsl:attribute> </xsl:if> <a> <xsl:attribute name="href"> <xsl:value-of select="umbraco.library:NiceUrl($currentPage/@id)" />?tag=<xsl:value-of select="current()" /> </xsl:attribute> <xsl:value-of select="current()" /> <span> (<xsl:value-of select="current()/@count" />) </span> </a> </li>
public XmlDocument DistinctValues(String commaList) {
string[] arr = commaList.Split(','); var distinctCount = new ArrayList(arr.Length); var distinctNames = new ArrayList(arr.Length);
foreach(string str in arr) { if(string.IsNullOrEmpty(str)) { continue; } var index = distinctNames.IndexOf(str); if (index >= 0) { var currentCount = (int)distinctCount[index]; distinctCount[index] = currentCount + 1; } else { distinctNames.Add(str); distinctCount.Add(1); } }
var outPut = ""; for (var i = 0; i < distinctNames.Count; i++) { var name = distinctNames[i]; var count = distinctCount[i]; outPut += "<value count='" + count + "'>" + name + "</value>"; }
var xml = new XmlDocument(); xml.LoadXml("<values>" + outPut + "</values>"); return xml; } ]]>
public XmlDocument DistinctValues(String commaList) {
string[] arr = commaList.Split(','); var distinctCount = new ArrayList(arr.Length); var distinctNames = new ArrayList(arr.Length);
foreach(string str in arr) { if(string.IsNullOrEmpty(str)) { continue; } var index = distinctNames.IndexOf(str); if (index >= 0) { var currentCount = (int)distinctCount[index]; distinctCount[index] = currentCount + 1; } else { distinctNames.Add(str); distinctCount.Add(1); } }
var outPut = ""; for (var i = 0; i < distinctNames.Count; i++) { var name = distinctNames[i]; var count = distinctCount[i]; outPut += "<value count='" + count + "'>" + name + "</value>"; }
var xml = new XmlDocument(); xml.LoadXml("<values>" + outPut + "</values>"); return xml; } ]]>
Actually, looking at the database, there are two tables associated with Tags. One if called "cmsTags" and another is called "cmsTagRelationship". This are pretty simple tables: one record in "Tags" for each unique tag and one record per associated tag in the relationship table. I should be able to create a user control pretty easily that will list all of tag counts for a given document type. That's all I want, not sure about you. I'll spit out of a UL that can be styled and will pass a parameter to a destination page to filter to a given tag (which is apparently an easier task than getting the tag counts in XSLT).
XSLT just isn't suitable for some tasks. What's funny is how simple this will be in a user control...
SELECT TOP (100) PERCENT COUNT(dbo.cmsDocument.nodeId) AS Count, dbo.cmsTags.tag, dbo.cmsTags.[group] FROM dbo.cmsTagRelationship INNER JOIN dbo.cmsTags ON dbo.cmsTagRelationship.tagId = dbo.cmsTags.id INNER JOIN dbo.cmsDocument ON dbo.cmsTagRelationship.nodeId = dbo.cmsDocument.nodeId WHERE (dbo.cmsDocument.published = 1) AND (dbo.cmsTags.[group] = 'default') GROUP BY dbo.cmsTags.tag, dbo.cmsTags.[group] ORDER BY dbo.cmsTags.tag
My user control will have parameters for "group" and the destination page to view a given category.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using umbraco.DataLayer;
public partial class usercontrols_getTagCount : System.Web.UI.UserControl
{
public string tagGroup { get; set; }
public string viewPageUrl { get; set; }
protected void Page_Load(object sender, EventArgs e)
{
ISqlHelper SqlHelper = DataLayerHelper.CreateSqlHelper(umbraco.GlobalSettings.DbDSN);
string strSQL =
@"SELECT TOP (100) PERCENT COUNT(dbo.cmsDocument.nodeId) AS Count, dbo.cmsTags.tag, dbo.cmsTags.[group]
FROM dbo.cmsTagRelationship INNER JOIN
dbo.cmsTags ON dbo.cmsTagRelationship.tagId = dbo.cmsTags.id INNER JOIN
dbo.cmsDocument ON dbo.cmsTagRelationship.nodeId = dbo.cmsDocument.nodeId
WHERE (dbo.cmsDocument.published = 1) AND (dbo.cmsTags.[group] = @group) AND (dbo.cmsTags.tag <> '')
GROUP BY dbo.cmsTags.tag, dbo.cmsTags.[group]
ORDER BY dbo.cmsTags.tag";
IRecordsReader rr = SqlHelper.ExecuteReader(strSQL, SqlHelper.CreateParameter("@group", tagGroup));
rptTags.DataSource= rr;
rptTags.DataBind();
}
}
recursively query all "Tags" below a specific node?
I'm haivng trouble finding documentation about this, can anyone tell me how i can retrieve all tags below a specific node? I'm using languages and have a blog installed for each language, ofcourse i want to seperate the tags for different languages...
/nl/blog/...
-> dutch tag cloud for all tags below /nl/
fr/blog/...
-> french tag cloud for all tags below /fr
The default xslt that goes through each of the tags throughout the entire site:
What i'd like (this code does not work, the bold was added just to give a sample:
All help is very much appreciated
You could probably use
<xsl:for-each select="tagsLib:getTagsFromNode(@id)/tags/tag">
Inside a for-each loop where you select the nodes you want
Dan
i doubt this walks through all the nodes below the one specified?
No, but this might,
using the tagsLib is a bit of trial and error, so there may be a better way to do this using tagsLib, but this will eventually go through all tags from all ndoes
Dan
Hi Rik,
I have not used the tag package so cannot comment directly on this but I might be able to help :)
I would suggest you use a CHOOSE statement instead of IF. e.g.:
My first thought on this was can you have different tag clouds by defining a different tag group for each language? ( i.e. instead of default )
My second thought was does this function return only the tags or can you access the parent nodes? If so, you could do something like:
getAllTagsInGroup('default')/../../node [@nodeName = 'nl']/blog/tags/tag
As I said above, this is just a guess and I have not tried it, but it might be something to have a look into if you don't get an answer from someone else.
Cheers,
Chris
Looks like I just posted at the same time as Dan, so you might already be on the right tracks :)
Cheers,
Chris
Doubt it as well, think you'll need to create a small xslt extension, similar to the existing ones, and return only those tags that are relevant to your node tree structure. Example code can be found in the umbraco source (editorControls project, there's a tag datatype folder that has a library class)
Hope this helps.
Regards,
/Dirk
Thanks a lot! this worked like a charm!
ps. thank you all for your replies,
Chris: different tag groups would be a better option probably, but this would require even more changes (to the blog package) and i'm afraid i'm a little short on time for that... As for the choose statement, it's also a good option yes, but at this point there's little functional difference.
Dirk: this would defintatly increase it's resuability!
Rik,
Be aware that Dan's solution will work great, but may affect performance as you'll hit the db for each getTagsFromNode() call (especially when having lots of childnodes/levels of child nodes
Cheers,
/Dirk
Dirk: that's definatly true, but i don't see any better ways, the tagslib isn't documented that much and the only alternative that might work would be using rag groups right?
If using Dan's solution, no there isn't, unless you write your own extension and build the xml tag fragment yourself. it will require some sql coding to get the right tags from the right nodes/childnodes, but it'll require a single db hit.
In addition, you could optimize performance by adding the output (xml fragment) to the http context cache (altho that would require you to invalidate the cache when a new document is being published - not sure if umbraco will invalidate the complete http context cache)
Cheers,
/Dirk
It seems the issue wasn't resolved...
The current code doesn't return an aggegrated list of tags with a counter, but simply each tag it's encounters is returned seperatly...
returns:
The search continues (all help is appreciated)
Does ayone know how to get something like this to work?
getAllTagsInGroup('default')/../../node [@nodeName = 'nl']/blog/tags/tag
To my knowledge getalltagsingroup doesn't support this way of working :(
yeh, using the "solution" you'll just get a big ol list of tags from all nodes.
When recently using tags for a document library, we decided not to use the built-in tag datatype as it seemed a little...flaky, esp. the autocomplete. We just used simple comma separated values in a textfield, then wrote our own function to take a "master list" of comma separated tags and spit out unique values with counts of the amount of tags.
The xslt is below - it's currently using the media library, but if you go down the route of just using a textfield and comma separated tags it should do what you need with a little tweaking. I've added some comments for clarity...
This outputs a list like
Dan
Thanks a LOT dandrayne, i just implemented your approach and it's working fine.
My resulting code:
This is one of those situations where I have to wonder... It should not require so much code to simply get a list of tag counts and filter by tag.
Amen. Coincidentally, I just came up against this today!
Dan,
Actually, looking at the database, there are two tables associated with Tags. One if called "cmsTags" and another is called "cmsTagRelationship". This are pretty simple tables: one record in "Tags" for each unique tag and one record per associated tag in the relationship table. I should be able to create a user control pretty easily that will list all of tag counts for a given document type. That's all I want, not sure about you. I'll spit out of a UL that can be styled and will pass a parameter to a destination page to filter to a given tag (which is apparently an easier task than getting the tag counts in XSLT).
XSLT just isn't suitable for some tasks. What's funny is how simple this will be in a user control...
Robert
Here's an example query:
SELECT TOP (100) PERCENT COUNT(dbo.cmsDocument.nodeId) AS Count, dbo.cmsTags.tag, dbo.cmsTags.[group]
FROM dbo.cmsTagRelationship INNER JOIN
dbo.cmsTags ON dbo.cmsTagRelationship.tagId = dbo.cmsTags.id INNER JOIN
dbo.cmsDocument ON dbo.cmsTagRelationship.nodeId = dbo.cmsDocument.nodeId
WHERE (dbo.cmsDocument.published = 1) AND (dbo.cmsTags.[group] = 'default')
GROUP BY dbo.cmsTags.tag, dbo.cmsTags.[group]
ORDER BY dbo.cmsTags.tag
My user control will have parameters for "group" and the destination page to view a given category.
Ok, here it is.
getTagCount.ascx:
getTagCount.ascx.cs:
Hmz, couldn't this be done by only using xslt? I needed something like this for a client and the following is what i came up with...
<xsl:for-each select="tagsLib:getAllTagsInGroup('default')/tags/tag">
<xsl:sort data-type="text" order="descending" select="@nodesTagged"/>
<xsl:if test="position() <= $numberOfItems">
<xsl:variable name="currentTag" select="."/>
<xsl:if test="count($currentPage/ancestor-or-self::node[@nodeTypeAlias='Homepage']/descendant::node[data[@alias='tags'] != '' and contains(data[@alias='tags'],$currentTag)]) > 0">
<li>
<a>
<xsl:attribute name="href">
<xsl:value-of select="umbraco.library:NiceUrl($currentPage/ancestor-or-self::node[@nodeTypeAlias='Homepage']/node[@nodeTypeAlias='tags']/@id)"/>?tag=<xsl:value-of select="."/>
</xsl:attribute>
<xsl:value-of select="."/>
(<xsl:value-of select="count($currentPage/ancestor-or-self::node[@nodeTypeAlias='Homepage']/descendant::node[data[@alias='tags'] != '' and contains(data[@alias='tags'],$currentTag)])"/>)
</a>
</li>
</xsl:if>
</xsl:if>
</xsl:for-each>
Thank God for a great XSLT solution to this!!!
Cheers Gerty - you're a life-saver!
Thank you Gerty for a great solution to this problem, much appreciated!
FYI,
I modified Gerty's code to work with the new 4.5.2 XSLT schema:
is working on a reply...