I am trying to include content in a search that is rendered through the uComponents: Multi Node Tree Picker but it isnt happening.
In the Macro Parameters I have the source set to -1 and have included all the document properties including the Picker document property, it just doesnt seem to pick up the content.
Although I'm struggling to create the XPath in order to output the properties within the picker. You mention you use <xsl:with-param name="items" select="$currentPage/ancestor-or-self::*[@level = 1]/descendant::NewsArticle[@isDoc]"/> in order to search against news articles. What would I use to search against and output the properties within the picker? (below the picker alias is "libraryDocumentsPicker" and the property aliases within it are "fileDocDescription" and "fileDocName" in the xsl below)
Did you ever got it working to search in MNTP nodes? I've got the same problem. I have a tree with products that are outside of the website. The products are selected with MNTP on nodes which are inside the website, but XSLT search can't find them because it only searches through nodes in the website and only the id's are there.
Now the problem is that the products which are found don't have a template. They are just nodes with info that is displayed on other pages if they are selected with MNTP. So in my search results I don't want to display the products, but the nodes which have the products selected with MNTP. So I need an xpath which returns the nodes from my website that contain any of the ids from my searchResults2 variable. I have no idea how to do that ;-).
For excluding the nodes that do not have a template set, you'll need to modify XSLTsearch. Find the variable for "possibleNodes" and add the following:
<!-- reduce the number of nodes for applying all the functions in the next step -->
<xsl:variable name="possibleNodes" select="$items/descendant-or-self::*[
@isDoc
and string(umbracoNaviHide) != '1'
and count(attribute::id)=1
and @template != '0'
and (umbraco.library:IsProtected(@id, @path) = false()
or umbraco.library:HasAccess(@id, @path) = true())
]"/>
to exclude the template-less nodes ("@template != '0'").
As for the MNTP problem - it is possible, but doubt it would be very pretty! The key is in the XPath expression for the "items" param. You would need to pipe/join nodesets, e.g.
So I already have the product nodes, but now I'm at the point where I need to get the nodes which have a reference to the product nodes (which are already filtered by the search) with MNTP. So I'm stuck at this xpath:
It should be possible to the extraNodes using a key - something like this (roughly):
<xsl:key name="nodes-by-id" match="DocumentTypeAlias1 | DocumentTypeAlias2" use="@id" />
<xsl:variable name="siteRoot" select="$currentPage/ancestor-or-self::*[@level = 1]" />
<xsl:template match="/">
<!-- Make sure we have the right context -->
<xsl:for-each select="$siteRoot">
<!-- Grab all nodes that have been selected through an MNTP -->
<xsl:variable name="extraNodes" select="key('nodes-by-id', descendant::DocumentTypeAlias/*/MultiNodePicker/nodeId)" />
<!-- Do stuff with $extraNodes here -->
</xsl:for-each>
</xsl:template>
Take note of the highlighted parts:
DocumentTypeAlias1 + 2 are document types that should be found (i.e., the ones that are selectable in the MNTP)
Adjust the level attribute in the siteRoot variable to land on your website node (the one you're searching within to find nodes that have an MNTP property)
Finally, adjust the DocumentTypeAlias in the extraNodes variable to match the doctype that has the MNTP property.
That's not all, but hopefully it should give you an idea about how it should be done... (?)
Ok this is the code I have now which first looks for the nodes which have the products and then shows the nodes which point to that node with MNTP:
<!--Product search-->
<xsl:variable name="possibleNodesProduct" select="umbraco.library:GetXmlAll()//SharedProduct/descendant-or-self::*[
@isDoc
and string(umbracoNaviHide) != '1'
and count(attribute::id)=1
and (umbraco.library:IsProtected(@id, @path) = false()
or umbraco.library:HasAccess(@id, @path) = true())
]"/>
<!-- generate a string of a semicolon-delimited list of all @id's of the matching nodes -->
<xsl:variable name="matchedNodesIdListProduct">
<xsl:call-template name="booleanAndMatchedNodes">
<xsl:with-param name="yetPossibleNodes" select="$possibleNodesProduct"/>
<xsl:with-param name="searchTermList" select="concat($searchUpper, ' ')"/>
</xsl:call-template>
</xsl:variable>
<!-- get the actual matching nodes as a nodeset -->
<xsl:variable name="matchedNodesProduct" select="$possibleNodesProduct[contains($matchedNodesIdListProduct, concat(';', concat(@id, ';')))]" />
<!--Get the nodes which have MNTP point to the matched nodes.-->
<xsl:variable name="relatedNodesProduct">
<xsl:for-each select="$siteRoot/descendant::ExpertProductDetail/*/MultiNodePicker/nodeId">
<xsl:variable name="id" select="." />
<xsl:variable name="node" select="./parent::*/parent::*/parent::*" />
<xsl:for-each select="$matchedNodesProduct">
<xsl:if test="$id = @id">
<xsl:copy-of select="$node"/>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="productResults" select="msxml:node-set($relatedNodesProduct)"/>
The problem now is that I have a list of nodes with MNTP on it, but the product description is still on the node which MNTP points to. So I would like to add more info to the node that I'm storing in the relatedNodesProduct variable:
<!-- Get the root element -->
<xsl:variable name="absoluteRoot" select="$currentPage/ancestor::root" />
<!-- Select all ExpertProductDetail nodes that have an MNTP pointing to one of the matched nodes -->
<xsl:variable name="relatedNodesProduct"
select="$absoluteRoot//ExpertProductDetail[*/MultiNodePicker/nodeId = $matchedNodesProduct/@id]" />
- this even has the added bonus of eliminating duplicates, which you'd probably run into with the other approach.
If you need some extra data, you can then create a new nodeset variable that references the nodes you've found:
- and then you can do stuff with the $fakeNodes variable...
But you might as well create a <xsl:template match="ExpertProductDetail"> and just use <xsl:apply-templates select="$relatedNodesProduct" /> if you just need it for output.
Thanks for this great piece of code! I already tried something like you did to get the relatedNodesProduct with a single XPath, but I didn't know how :-).
About adding extra data. In your example you rebuild the nodeset instead of extending it. In C# you can do something like this:
//Get the current node as an XElement.
XElement xmlNode = XDocument.Parse(((library.GetXmlNodeCurrent()).Current).OuterXml).Root;
//Add the "fake" property.
xmlNode.Add(new XElement("distance", 1000));
//Convert the XElement to a DynamicNode.
dynamic currentNode = new DynamicNode(new Node(ToXmlElement(xmlNode)));
//Now you can use the "fake" property as any normal Umbraco property on a node.
<p>The distance is @currentNode.distance</p>
Here I don't rebuild the node. I just convert it to an XElement and add the extra element to it. That's something I also want to do in XSLT. If I need to rebuild the complete node with all attributes and elements than I'd rather do it in C# with an XSLT extension. Thanks for your help!
Rebuilding the xml also would be fine, but I'm not sure how I can do that. I want all the attributes and elements of the original xml + a custom element. For now I have this which only tries to add the elements, but it's not working and also doesn't look like the original xml node:
I don't understand why you need to rebuild the entire XML - it's much more efficient to just create "fake" references that point to the actual nodes (via their IDs) and then use <xsl:apply-templates/> to handle the actual output from the real nodes.
I understand that in C# you'd probably need to do it to be efficient but in XSLT you have access to everything in the tree right away with XPath, and using variables to store specific filtered "sets" it's actually easy to grab the data you want. Using match templates to handle rendering prevents endless choose/when/otherwise constructs while rendering.
So here's a scenario that's probably akin to what you're doing — but done the way I normally tackle situations like this. Note that I'm using both values from the original Umbraco nodes, but also a computed value for ease of rendering. Note also how simple the actual "view" (i.e. the template) is - not hard for any frontender to manipulate to his/her needs:
<xsl:variable name="root" select="$currentPage/ancestor::root" />
<!-- Grab all Product nodes with more than 3 items in stock -->
<xsl:variable name="availableProducts" select="$root//Product[stock > 3]" />
<!-- Sort by availability (stock) descending and take the ones below a price of 100 -->
<xsl:variable name="discountedProductsProxy">
<xsl:for-each select="$availableProducts">
<xsl:sort select="stock" data-type="number" order="descending" />
<xsl:if test="price < 100">
<productRef id="{@id}">
<!-- Copy the productName and price properties -->
<xsl:copy-of select="productName" />
<xsl:copy-of select="price" />
<!-- Store the discounted percentage value -->
<discountPercent><xsl:value-of select="100 * price div originalPrice" /></discountPercent>
</productRef>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="discountedProducts" select="make:node-set($discountedProductsProxy)/productRef" />
<xsl:template match="/">
<xsl:apply-templates select="$discountedProducts" />
</xsl:template>
<xsl:template match="productRef">
<!-- Grab the Umbraco node (same as GetXmlNodeById(@id) but we already have it, so...) -->
<xsl:variable name="originalNode" select="$availableProducts[@id = current()/@id]" />
<div class="product">
<h1><xsl:value-of select="productName" /></h1>
<p class="price">
<xsl:value-of select="concat('USD ', price, ' (—', discountPercent, '%)')" />
</p>
<!-- If we really need something from the original, we just grab it -->
<xsl:if test="price < 50">
<xsl:value-of select="$originalNode/specialValueDescription" />
</xsl:if>
</div>
</xsl:template>
I know I'm not actively adressing your problem, but I hope you can see what I mean anyways :-)
Thanks for this. I'll have a look and see if I can improve my code. I need the complete XML because I'm using XSLT search and I'm calling a template that is expecting real Umbraco nodes with all the attributes and elements on it. Passing a new generated node which is missing some of these values could cause problems. This is still a wrapper around XSLT search like Lee described here: http://leekelleher.com/2012/02/21/manipulate-xsltsearch/
OK - I guess that makes sense, because you need to pass them into the $items parameter, right?
So I still think you should take advantage of templates and do what Lee suggested - grab all the "Product" nodes; grab all the nodes referenced by MNTP from those and send the combined set into the $items variable...
But is the problem then that you need to add some "secret sauce" property to some of them "before" the search is performed?
'Coz that's possible - and you were on the right track a couple of comments above :-)
No it's something which needs to be done after the search is performed. I modified the XSLT search part and I do the searching myself and let XSLT search do the rendering (which needs the real Umbraco nodes). So I could modify the rendering part so it also works with custom build nodes that have the "secret sauce", but I hoped I didn't have to do that.
I using a content picker to load content from another page, but am new to XSLT. Could someone provide the full XSLT showing how I would include my content picker pages in the search? It will need to search each editor just as it does the page that has the content.
Then the next step for me would be to occasionally exclude content if the visitor is in a certain area of the site (show only pages within this section). Thanks.
Including content picker in search results
Hi,
I am trying to include content in a search that is rendered through the uComponents: Multi Node Tree Picker but it isnt happening.
In the Macro Parameters I have the source set to -1 and have included all the document properties including the Picker document property, it just doesnt seem to pick up the content.
Any ideas or help hugely appreciated!
Hi Oliver,
I wrote a blog post a while back about how to manipulate XSLTsearch - this included being able to pass-through a custom node-set as the 'source'.
Take a read, see if it makes sense. Let us know if you have any questions, etc.
Cheers, Lee.
Hi Lee,
Thanks a lot for that it does make sense.
Although I'm struggling to create the XPath in order to output the properties within the picker. You mention you use <xsl:with-param name="items" select="$currentPage/ancestor-or-self::*[@level = 1]/descendant::NewsArticle[@isDoc]"/> in order to search against news articles. What would I use to search against and output the properties within the picker? (below the picker alias is "libraryDocumentsPicker" and the property aliases within it are "fileDocDescription" and "fileDocName" in the xsl below)
<xsl:template match="/">
<xsl:variable name="docsIDs" select="$currentPage/libraryDocumentsPicker/MultiNodePicker/nodeId" />
<xsl:if test="count($docsIDs)>0">
<xsl:value-of select="data [@alias = '$currentPage/libraryDocumentsPicker/MultiNodePicker/nodeId/fileDocCategory']"/>
<ul class="document-list">
<xsl:for-each select="$docsIDs">
<xsl:variable name="docsNode" select="umbraco.library:GetXmlNodeById(.)" />
<xsl:if test="count($docsNode//error)=0">
<xsl:call-template name="renderDocs">
<xsl:with-param name="node" select="$docsNode"/>
</xsl:call-template>
</xsl:if>
</xsl:for-each>
</ul>
</xsl:if>
</xsl:template>
<xsl:template name="renderDocs">
<xsl:param name="node" />
<li>
<xsl:attribute name="class">
<xsl:value-of select="$node/fileDocType" disable-output-escaping="yes" />
</xsl:attribute>
<xsl:variable name="mediaId" select="number($node/uploadDocFile)" />
<xsl:if test="$mediaId > 0">
<xsl:variable name="mediaNode" select="umbraco.library:GetMedia($mediaId, 0)" />
<xsl:if test="$mediaNode/umbracoFile">
<h5><a href="{$mediaNode/umbracoFile}" target="_blank"><xsl:value-of select="$node/fileDocName" disable-output-escaping="yes" /><span> (<xsl:value-of select="$node/fileDocSize" disable-output-escaping="yes" />)</span></a></h5>
</xsl:if>
</xsl:if>
<p class="file-description"><xsl:value-of select="$node/fileDocDescription" disable-output-escaping="yes" /></p>
</li>
</xsl:template>
Hello,
Did you ever got it working to search in MNTP nodes? I've got the same problem. I have a tree with products that are outside of the website. The products are selected with MNTP on nodes which are inside the website, but XSLT search can't find them because it only searches through nodes in the website and only the id's are there.
Jeroen
I'm using the SearchResults.xslt file from Lee Kelleher and this is what I've got so far:
Now the problem is that the products which are found don't have a template. They are just nodes with info that is displayed on other pages if they are selected with MNTP. So in my search results I don't want to display the products, but the nodes which have the products selected with MNTP. So I need an xpath which returns the nodes from my website that contain any of the ids from my searchResults2 variable. I have no idea how to do that ;-).
Jeroen
Hi Jeroen,
For excluding the nodes that do not have a template set, you'll need to modify XSLTsearch. Find the variable for "possibleNodes" and add the following:
to exclude the template-less nodes ("@template != '0'").
As for the MNTP problem - it is possible, but doubt it would be very pretty! The key is in the XPath expression for the "items" param. You would need to pipe/join nodesets, e.g.
Of course the XPath for getting the product nodes is going to be complex ... :-$
Good luck!
Cheers, Lee.
There might be some confusion here. I use this code to get the product nodes (the real ones without template):
So I already have the product nodes, but now I'm at the point where I need to get the nodes which have a reference to the product nodes (which are already filtered by the search) with MNTP. So I'm stuck at this xpath:
I know it's probably complex. That's why I'm asking for help :-).
Jeroen
Hi Jeroen/Lee,
It should be possible to the extraNodes using a key - something like this (roughly):
That's not all, but hopefully it should give you an idea about how it should be done... (?)
/Chriztian
Ok this is the code I have now which first looks for the nodes which have the products and then shows the nodes which point to that node with MNTP:
The problem now is that I have a list of nodes with MNTP on it, but the product description is still on the node which MNTP points to. So I would like to add more info to the node that I'm storing in the relatedNodesProduct variable:
So for Example I want to change this:
<ExpertProductDetail id="2734" parentID="2534" level="5" isDoc="">
<properties></properties>
</ExpertProductDetail>
Into this:
<ExpertProductDetail id="2734" parentID="2534" level="5" isDoc="">
<properties></properties>
<extraProperty></extraProperty>
</ExpertProductDetail>
Is that somehow possible in XSLT?
The above code probably could be better, but I'm not really good at XSLT anymore.
Jeroen
If this isn't possible with XSLT I'll create an XSLT extension to do this, but I hope I don't have to ;-).
Jeroen
Hi Jeroen,
Here's a perfect example of the difference between the "C# way" and the "XSLT way" - all of this:
- can actually be done like this:
/Chriztian
Thanks for this great piece of code! I already tried something like you did to get the relatedNodesProduct with a single XPath, but I didn't know how :-).
About adding extra data. In your example you rebuild the nodeset instead of extending it. In C# you can do something like this:
Here I don't rebuild the node. I just convert it to an XElement and add the extra element to it. That's something I also want to do in XSLT. If I need to rebuild the complete node with all attributes and elements than I'd rather do it in C# with an XSLT extension. Thanks for your help!
Jeroen
Rebuilding the xml also would be fine, but I'm not sure how I can do that. I want all the attributes and elements of the original xml + a custom element. For now I have this which only tries to add the elements, but it's not working and also doesn't look like the original xml node:
Sorry for asking for help again :-).
Jeroen
Hi Jeroen,
No problem at all :-)
I don't understand why you need to rebuild the entire XML - it's much more efficient to just create "fake" references that point to the actual nodes (via their IDs) and then use <xsl:apply-templates/> to handle the actual output from the real nodes.
I understand that in C# you'd probably need to do it to be efficient but in XSLT you have access to everything in the tree right away with XPath, and using variables to store specific filtered "sets" it's actually easy to grab the data you want. Using match templates to handle rendering prevents endless choose/when/otherwise constructs while rendering.
So here's a scenario that's probably akin to what you're doing — but done the way I normally tackle situations like this. Note that I'm using both values from the original Umbraco nodes, but also a computed value for ease of rendering. Note also how simple the actual "view" (i.e. the template) is - not hard for any frontender to manipulate to his/her needs:
I know I'm not actively adressing your problem, but I hope you can see what I mean anyways :-)
/Chriztian
Oh man, that editor was killing me - make:node-set() should of course be msxml:node-set() in your setup :-)
Thanks for this. I'll have a look and see if I can improve my code. I need the complete XML because I'm using XSLT search and I'm calling a template that is expecting real Umbraco nodes with all the attributes and elements on it. Passing a new generated node which is missing some of these values could cause problems. This is still a wrapper around XSLT search like Lee described here: http://leekelleher.com/2012/02/21/manipulate-xsltsearch/
Jeroen
Hi Jeroen,
OK - I guess that makes sense, because you need to pass them into the $items parameter, right?
So I still think you should take advantage of templates and do what Lee suggested - grab all the "Product" nodes; grab all the nodes referenced by MNTP from those and send the combined set into the $items variable...
But is the problem then that you need to add some "secret sauce" property to some of them "before" the search is performed?
'Coz that's possible - and you were on the right track a couple of comments above :-)
/Chriztian
No it's something which needs to be done after the search is performed. I modified the XSLT search part and I do the searching myself and let XSLT search do the rendering (which needs the real Umbraco nodes). So I could modify the rendering part so it also works with custom build nodes that have the "secret sauce", but I hoped I didn't have to do that.
Jeroen
I reverted some things and still ended up with an XSLT extension. This is now working:
This code isn't the clean XPath you suggested, but because I'm looping through the nodes I already have the node with I need to manipulate available.
This is the XSLT Extension:
So it's working, but it's more the C# way than XSLT ;-).
Jeroen
I using a content picker to load content from another page, but am new to XSLT. Could someone provide the full XSLT showing how I would include my content picker pages in the search? It will need to search each editor just as it does the page that has the content.
Then the next step for me would be to occasionally exclude content if the visitor is in a certain area of the site (show only pages within this section). Thanks.
is working on a reply...