Copied to clipboard

Flag this post as spam?

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


  • Tom Cowling 144 posts 342 karma points
    Jul 16, 2015 @ 16:56
    Tom Cowling
    0

    Testing querystrings

    Hey guys,

    I'm building a page that has some filter boxes on (check boxes). There are four sets of filters.

    On my page I have a macro that reads in the values of query strings sent to the page and then displays the results accordingly. I have got this to work just fine using a set of and statements in an apply-template.

    The problem I now have though is that it is too precise. To get a result back, I have to to check everything and then uncheck the one box I don't want to filter on (because of the and filters). It also means that when the page loads, I get no results and I would like to load everything by default.

    My ideal situation would be to test to see if the query string exists, if it does include it in the filter. If it doesn't exist, don't filter on it. I could probably do this with 16 seperate if statements where I pop a different apply-tempate in each one, but that sounds ridiculous to me.

    My original code is below (I have only included two filters to keep the code to a minimum on here). What I have tried since is to store each of the filters in a variable as plain text, with a simple check for the query string being there, but it doesn't seem to work. Any help would be much appreciated.

    Thanks,

    Tom

    <xsl:variable name="CountriesSplit" select="umb:Split(umb:RequestQueryString('country'), ',')/value" />
    <xsl:variable name="YearsSplit" select="umb:Split(umb:RequestQueryString('year'), ',')/value" />
    
    
    <xsl:template match="/">  
        <!-- Pop some results in! -->
        <xsl:apply-templates select="$resourceRoot//Resources[umb:Split(countries, ',')/value = $CountriesSplit and ./year = $YearsSplit]">
        <xsl:sort select="year" order="descending" />
        </xsl:apply-templates>
    </xsl:template>
    
    
    
    <!-- Template for a resource -->
    <xsl:template match="Resources">
        <p style="border-bottom: 1px dashed #ccc; padding-bottom: 10px;">
              <a href="{umb:NiceUrl(@id)}"><xsl:value-of select="title"/></a><br />
            <strong>Resource Type: </strong><xsl:value-of select="./../title" /><br />
            <em><xsl:value-of select="year" /></em>
        </p>
    </xsl:template>
    

    To pass values as a parameter, I have tried doing the following:

        <xsl:variable name="YearsAlive">
        <xsl:choose>
            <xsl:when test="umb:RequestQueryString('year')">./year = $YearsSplit</xsl:when>
        </xsl:choose>
    </xsl:variable>
    
  • Chriztian Steinmeier 2798 posts 8788 karma points MVP 7x admin c-trib
    Jul 16, 2015 @ 17:48
    Chriztian Steinmeier
    0

    Hi Tom,

    Ideally, you'd perform all the filtering in a custom extension - XSLT was really not made for these things; however - here's how I usually handle this (common problem):

    <!-- Grab all resources -->
    <xsl:variable name="allResources" select="$resourcesRoot//Resources" />
    
    <xsl:variable name="resourcesFilteredByCountries" select="$allResources[umb:Split(countries, ',')/value = $CountriesSplit]" />
    <xsl:variable name="resourcesFilteredByYear" select="$allResources[year = $YearsSplit]" />
    
    <!-- We want to show only filtered resources (but all of them if no filter has been selected) -->
    <xsl:variable name="resourcesToShow" select="$resourcesFilteredByYear | $resourcesFilteredByCountries | $allResources[not(normalize-space($CountriesSplit) and normalize-space($YearsSplit))]" />
    
    <xsl:template match="/">
        <!-- Pop some results in! -->
        <xsl:apply-templates select="$resourcesToShow">
            <xsl:sort select="year" order="descending" />
        </xsl:apply-templates>
    </xsl:template>
    

    Hope that helps - shoot away with questions, if you have any :)

    /Chriztian

  • Tom Cowling 144 posts 342 karma points
    Jul 16, 2015 @ 18:04
    Tom Cowling
    0

    That looks great as a solution, but my only question would be is is scalable?

    I have four filters so would I need to factor in a load of extra ors in there too? If there are 16 combinations rather than 3 (as in your example) I can see my brain turning to a thick sludge fairly quickly!

    If this is going to be the simplest way, then many thanks for your help and I'll crack on with that!

    <xsl:variable name="resourcesFilteredByCountries" select="$allResources[umb:Split(countries, ',')/value = $CountriesSplit]" />
    <xsl:variable name="resourcesFilteredByYear" select="$allResources[year = $YearsSplit]" />
    <xsl:variable name="resourcesFilteredByThemes" select="umb:Split(umb:RequestQueryString('theme'), ',')/value" />
    <xsl:variable name="resourcesFilteredByType" select="umb:Split(umb:RequestQueryString('resourcetype'), ',')/value" />
    

    Just out of interest, what would be a more suitable tool for doing things like this? Razor?

    T

  • Chriztian Steinmeier 2798 posts 8788 karma points MVP 7x admin c-trib
    Jul 16, 2015 @ 18:23
    Chriztian Steinmeier
    0

    Hi Tom,

    That's why I mentioned a custom extension - even if you did the whole thing in Razor, you should handle the filtering in some custom code file that returned the results, so you don't do all the logic in the view. That said, I know that there are of course always reasons for not going that way :-)

    Using all 4 of your filters, the result would look something like this:

    <!-- Grab filter values -->
    <xsl:variable name="CountriesSplit" select="umb:Split(umb:RequestQueryString('country'), ',')/value" />
    <xsl:variable name="YearsSplit" select="umb:Split(umb:RequestQueryString('year'), ',')/value" />
    <xsl:variable name="ThemesSplit" select="umb:Split(umb:RequestQueryString('theme'), ',')/value" />
    <xsl:variable name="TypeSplit" select="umb:Split(umb:RequestQueryString('resourcetype'), ',')/value" />
    
    <!-- Grab all resources -->
    <xsl:variable name="allResources" select="$resourcesRoot//Resources" />
    
    <!-- Create some filtered sets -->
    <xsl:variable name="resourcesFilteredByCountries" select="$allResources[umb:Split(countries, ',')/value = $CountriesSplit]" />
    <xsl:variable name="resourcesFilteredByYear" select="$allResources[year = $YearsSplit]" />
    <xsl:variable name="resourcesFilteredByThemes" select="$allResources[theme = $ThemesSplit]" />
    <xsl:variable name="resourcesFilteredByType" select="$allResources[resourcetype = $TypeSplit]" />
    
    <!-- We want to show only filtered resources (but all of them if no filter has been selected) -->
    <xsl:variable name="resourcesToShow" select="
        $resourcesFilteredByYear |
        $resourcesFilteredByCountries |
        $resourcesFilteredByThemes |
        $resourcesFilteredByType |
        $allResources[not(
            normalize-space($CountriesSplit) and
            normalize-space($YearsSplit) and
            normalize-space($ThemesSplit) and
            normalize-space($TypeSplit)
        )]"
    />
    
    <xsl:template match="/">
        <!-- Pop some results in! -->
        <xsl:apply-templates select="$resourcesToShow">
            <xsl:sort select="year" order="descending" />
        </xsl:apply-templates>
    </xsl:template>
    

    Ideally, you just want this:

    <xsl:variable name="resourcesToShow" select="tom:GetFilteredResources()" />
    
    <xsl:template match="/">
        <!-- Pop some results in! -->
        <xsl:apply-templates select="$resourcesToShow/Resources">
            <xsl:sort select="year" order="descending" />
        </xsl:apply-templates>
    </xsl:template>
    

    - and not have your view be concerned about how to get those resources, right? :-)

    /Chriztian

  • Tom Cowling 144 posts 342 karma points
    Jul 16, 2015 @ 19:03
    Tom Cowling
    0

    That looks suspiciously simple.. o_O I'll give it a whirl tomorrow.

    So from a logic point of view what happens if I pick a couple of items from Themes a couple from Countries and then nothing from Years and Type?

    The second solution looks great too. Have you got some further reading on setting up a custom extension? I'm always looking to find a neater way to do things

    Thanks a lot again for your help with this,

    Tom

  • Chriztian Steinmeier 2798 posts 8788 karma points MVP 7x admin c-trib
    Jul 16, 2015 @ 19:17
    Chriztian Steinmeier
    0

    So this is the beauty of working with sets...

    All of the variables that selects nodes are holding sets of nodes.

    We start by selecting all applicable resources (the $allResources variable). Then we create additional subsets of those by using the various filters. A set can contain a truckload of nodes or none - so when we combine all the variables (using the pipe character) we get a new set of all the filtered nodes. Here's the great part: A node can only exist once in a set - so even if the same node gets selected multiple times (e.g. both by year and by country), it'll only be present once in the combined set.

    To handle the "what if no filters are used" scenario we do one more thing - we add the $allResources set to the filtered nodes - but only if none of the filters were used.

    RE: extensions: I've learned most by looking at the source for some of the existing ones in umbraco.library - e.g., the Split() method.

    /Chriztian

  • Tom Cowling 144 posts 342 karma points
    Jul 17, 2015 @ 09:23
    Tom Cowling
    0

    I think the pipes are making it too vague now. :(

    I have a resource in the system and I know it is the only one where these four values exist. If I tick all the boxes for each of these values and hit search, I somehow get everything back..?

    I probably didn't explain enough at the start. As they are tick boxes, I want use these to refine the results that come back, rather than expand them. So an example query run against the allResources variable should be:

    [Country 1 | Country 2] and [2012 | 2013] and [Theme1] and [Any Type]

    or with a different selection

    [Any Country] and [2012 | 2013 | 2014] and [Any Theme] and [Type 3]

    or when the page loads

    [Any Country] and [Any Year] and [Any Theme] and [Any Type]

    The problem that I was having was that the and parts were too rigid and if a value didn't exist in the query string, I wanted to exclude it completely from the query.

  • Chriztian Steinmeier 2798 posts 8788 karma points MVP 7x admin c-trib
    Jul 17, 2015 @ 16:23
    Chriztian Steinmeier
    100

    Hi again,

    Hehe - now you're really getting far down the rabbit hole :)

    You can do that by adding the fallback to each of the filtered sets, and then picking all the nodes that exist in all sets, like this:

    <!-- Create some filtered sets (with fallback to 'all') -->
    <xsl:variable name="resourcesFilteredByCountries" select="$allResources[umb:Split(countries, ',')/value = $CountriesSplit] | $allResources[not(normalize-space($CountriesSplit))]" />
    <xsl:variable name="resourcesFilteredByYear" select="$allResources[year = $YearsSplit] | $allResources[not(normalize-space($YearsSplit))]" />
    <xsl:variable name="resourcesFilteredByThemes" select="$allResources[theme = $ThemesSplit] | $allResources[not(normalize-space($ThemesSplit))]" />
    <xsl:variable name="resourcesFilteredByType" select="$allResources[resourcetype = $TypeSplit] | $allResources[not(normalize-space($TypeSplit))]" />
    
    <!-- We want to show only the nodes that exist in all of the filtered sets -->
    <xsl:variable name="resourcesToShow" select="
        $resourcesFilteredByYear
        [@id = $resourcesFilteredByCountries/@id]
        [@id = $resourcesFilteredByThemes/@id]
        [@id = $resourcesFilteredByType/@id]"
    />
    

    But again: Doing this with an extension would be much better... (I know, repeating myself :-)

    /Chriztian

  • Tom Cowling 144 posts 342 karma points
    Jul 20, 2015 @ 08:49
    Tom Cowling
    1

    Perfect as always. I can't believe how simple that is as a code snippet.

    Definitely reusing that one again!

Please Sign in or register to post replies

Write your reply to:

Draft