Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Oliver 5 posts 25 karma points
    May 30, 2012 @ 10:20
    Oliver
    0

    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!

  • Lee Kelleher 4020 posts 15802 karma points MVP 13x admin c-trib
    May 30, 2012 @ 10:34
    Lee Kelleher
    0

    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.

  • Oliver 5 posts 25 karma points
    Jun 01, 2012 @ 11:01
    Oliver
    0

    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>

  • Jeroen Breuer 4908 posts 12265 karma points MVP 4x admin c-trib
    Feb 27, 2013 @ 10:47
    Jeroen Breuer
    0

    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

  • Jeroen Breuer 4908 posts 12265 karma points MVP 4x admin c-trib
    Feb 27, 2013 @ 12:11
    Jeroen Breuer
    0

    I'm using the SearchResults.xslt file from Lee Kelleher and this is what I've got so far:

    <xsl:template match="/" priority="2">
    
      <!--Normal search-->
    
      <xsl:variable name="searchResults">
            <xsl:call-template name="search">
                <xsl:with-param name="items" select="$currentPage/ancestor-or-self::*[@level = 2]"/>
            </xsl:call-template>
        </xsl:variable>
    
      <xsl:variable name="results1" select="msxml:node-set($searchResults)"/>
    
      <!--Product search-->
    
      <xsl:variable name="searchResults2">
        <xsl:call-template name="search">
          <xsl:with-param name="items" select="umbraco.library:GetXmlAll()//SharedProduct"/>
        </xsl:call-template>
      </xsl:variable>
    
      <xsl:variable name="results2" select="msxml:node-set($searchResults2)"/>
    
      <!--Total search-->
    
      <xsl:variable name="total">
        <xsl:for-each select="$results1">
          <xsl:copy-of select="."/>
        </xsl:for-each>
        <xsl:for-each select="$results2">
          <xsl:copy-of select="."/>
        </xsl:for-each>
      </xsl:variable>
    
      <xsl:variable name="results" select="msxml:node-set($total)"/>
    
        <xsl:apply-templates select="$results/div" />
    
    </xsl:template>

    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

  • Lee Kelleher 4020 posts 15802 karma points MVP 13x admin c-trib
    Feb 27, 2013 @ 12:29
    Lee Kelleher
    1

    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:

        <!-- 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.

    <xsl:variable name="extraNodes" select="/do/whatever/to/get/the/MNTP/nodeId/and/cross/reference/with/the/Products" />
    <xsl:call-template name="search">
        <xsl:with-param name="items" select="$currentPage/ancestor-or-self::*[@level = 2] | $extraNodes"/>
    </xsl:call-template>

    Of course the XPath for getting the product nodes is going to be complex ... :-$

    Good luck!

    Cheers, Lee.

  • Jeroen Breuer 4908 posts 12265 karma points MVP 4x admin c-trib
    Feb 27, 2013 @ 12:46
    Jeroen Breuer
    0

    There might be some confusion here. I use this code to get the product nodes (the real ones without template):

    <!--Product search-->
    
      <xsl:variable name="searchResults2">
        <xsl:call-template name="search">
          <xsl:with-param name="items" select="umbraco.library:GetXmlAll()//SharedProduct"/>
        </xsl:call-template>
      </xsl:variable>
    
      <xsl:variable name="results2" select="msxml:node-set($searchResults2)"/>

    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:

    <xsl:variable name="extraNodes" select="/do/whatever/to/get/the/MNTP/nodeId/and/cross/reference/with/the/Products" />

    I know it's probably complex. That's why I'm asking for help :-).

    Jeroen

  • Chriztian Steinmeier 2798 posts 8788 karma points MVP 7x admin c-trib
    Feb 27, 2013 @ 12:54
    Chriztian Steinmeier
    2

    Hi Jeroen/Lee,

    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... (?)

    /Chriztian

  • Jeroen Breuer 4908 posts 12265 karma points MVP 4x admin c-trib
    Feb 27, 2013 @ 17:45
    Jeroen Breuer
    0

    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:

    <xsl:if test="$id = @id">
      <xsl:copy-of select="$node"/>
    </xsl:if>

    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

  • Jeroen Breuer 4908 posts 12265 karma points MVP 4x admin c-trib
    Feb 27, 2013 @ 18:23
    Jeroen Breuer
    0

    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

  • Chriztian Steinmeier 2798 posts 8788 karma points MVP 7x admin c-trib
    Feb 27, 2013 @ 21:47
    Chriztian Steinmeier
    1

    Hi Jeroen,

    Here's a perfect example of the difference between the "C# way" and the "XSLT way" - all of this:

    <xsl:variable name="relatedNodesProduct">
        <xsl:for-each select="umbraco.library:GetXmlAll()/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>
    

    - can actually be done like this:

    <!-- 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:
    <xsl:variable name="nodesProxy">
        <xsl:for-each select="$relatedNodesProduct">
            <ExpertProductDetailFake id="{@id}">
                <computedProperty>
                    <xsl:value-of select="concat(property1, property2)" />
                </computedProperty>
            </ExpertProductDetailFake>
        </xsl:for-each>
    </xsl:variable>
    <xsl:variable name="fakeNodes" select="msxml:node-set($nodesProxy)" />
    
    - 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. 
    Let me know if it makes sense (or not :-)

    /Chriztian

  • Jeroen Breuer 4908 posts 12265 karma points MVP 4x admin c-trib
    Feb 28, 2013 @ 09:19
    Jeroen Breuer
    0

    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!

    Jeroen

  • Jeroen Breuer 4908 posts 12265 karma points MVP 4x admin c-trib
    Feb 28, 2013 @ 09:52
    Jeroen Breuer
    0

    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:

    <xsl:variable name="nodesProxy">
      <xsl:for-each select="$relatedNodesProduct">
        <xsl:element name="{local-name()}">
          <xsl:for-each select="/*">
            <xsl:element name="{local-name()}">
              <xsl:value-of select="."/>
            </xsl:element>
          </xsl:for-each>
        </xsl:element>
      </xsl:for-each>
    </xsl:variable>
    <xsl:variable name="fakeNodes" select="msxml:node-set($nodesProxy)" />

    Sorry for asking for help again :-).

    Jeroen

  • Chriztian Steinmeier 2798 posts 8788 karma points MVP 7x admin c-trib
    Feb 28, 2013 @ 11:16
    Chriztian Steinmeier
    1

     

    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:

    <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 &gt; 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 &lt; 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 &lt; 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 :-)

    /Chriztian

     

  • Chriztian Steinmeier 2798 posts 8788 karma points MVP 7x admin c-trib
    Feb 28, 2013 @ 11:18
    Chriztian Steinmeier
    0

    Oh man, that editor was killing me - make:node-set() should of course be msxml:node-set() in your setup :-)

  • Jeroen Breuer 4908 posts 12265 karma points MVP 4x admin c-trib
    Feb 28, 2013 @ 11:23
    Jeroen Breuer
    0

    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

  • Chriztian Steinmeier 2798 posts 8788 karma points MVP 7x admin c-trib
    Feb 28, 2013 @ 11:37
    Chriztian Steinmeier
    0

    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

  • Jeroen Breuer 4908 posts 12265 karma points MVP 4x admin c-trib
    Feb 28, 2013 @ 11:41
    Jeroen Breuer
    0

    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

  • Jeroen Breuer 4908 posts 12265 karma points MVP 4x admin c-trib
    Feb 28, 2013 @ 14:09
    Jeroen Breuer
    0

    I reverted some things and still ended up with an XSLT extension. This is now working:

    <!--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">
    
            <!--Add the product description to the found node.-->
            <xsl:variable name="extendedNode" select="XsltExtensions:AddElement($node, concat('prodDescription_',$siteRoot/@nodeName), ./*[local-name() = concat('prodDescription_',$siteRoot/@nodeName)])" />
            <xsl:copy-of select="$extendedNode/*"/>
    
          </xsl:if>
    
        </xsl:for-each>    
    
      </xsl:for-each>
    </xsl:variable>
    
    <xsl:variable name="productResults" select="msxml:node-set($relatedNodesProduct)"/>

    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:

    public static XPathNodeIterator AddElement(XPathNodeIterator node, string name, string value)
    {
        node.MoveNext();
        XElement xmlNode = XDocument.Parse((node.Current).OuterXml).Root;
        xmlNode.Add(new XElement(name, value));
        return xmlNode.CreateNavigator().Select("/");
    }

    So it's working, but it's more the C# way than XSLT ;-).

    Jeroen

  • Eric Siebeneck 3 posts 73 karma points notactivated
    Sep 24, 2016 @ 11:12
    Eric Siebeneck
    0

    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.

Please Sign in or register to post replies

Write your reply to:

Draft