Copied to clipboard

Flag this post as spam?

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


  • Lee Kelleher 4026 posts 15836 karma points MVP 13x admin c-trib
    Mar 30, 2011 @ 18:28
    Lee Kelleher
    0

    Recursive fields using Razor macro

    Hello to all you Razor Synth Lords, wondering if you could help an old XSLT'er? ;-)

    I got to a point in my current website where I needed to do a little templating, but felt a separate macro (whatever flavour) would be a tad overkill.  So thought I'd give the new Inline Razor macro a try.

    However I hit a small block... how do you do recursive fields?

    Old school examples... <umbraco:Item>

    <umbraco:Item runat="server" Field="contactTelephone" recursive="true" />

    XSLT

    <xsl:value-of select="$currentPage/ancestor-or-self::*[@isDoc and normalize-space(contactTelephone)][1]/contactTelephone" />

    ... now how to do this using Razor?  Without going crazy complicated in C# syntax!

    At the moment, I'm making use of the Macro Parameters, like so...

    <umbraco:Macro runat="server" language="cshtml" contactTelephone="[$contactTelephone]">
        <h2>Telephone</h2>
        <p>@Parameter.contactTelephone</p>
    </umbraco:Macro>

    Could that be the better way of doing this? Very curious!

    Thanks, Lee.

  • Alex 78 posts 136 karma points
    Mar 30, 2011 @ 18:48
    Alex
    1

    There is probably a much more elegant solution than this, but it should at least do what you want it to do.

    @Model.AncestorsOrSelf().Where("contactTelephone != \"\"").Last().contactTelephone

     

  • Lee Kelleher 4026 posts 15836 karma points MVP 13x admin c-trib
    Mar 30, 2011 @ 18:54
    Lee Kelleher
    0

    Thanks Alex.  I'm still getting my head around Razor syntax... so it's essentially Linq methods?

    Slight tweak of yours, as I didn't like the string escaping... also do you think that using First() would make any difference?

    @Model.AncestorsOrSelf().Where("contactTelephone != string.Empty").First().contactTelephone

    Cheers, Lee.

    PS. I'm probably going to look back on the reply in a few months and laugh at myself.

  • Dirk De Grave 4541 posts 6021 karma points MVP 3x admin c-trib
    Mar 30, 2011 @ 20:16
    Dirk De Grave
    1

    Lee,

    i'm not sure if that would give the desired result... as AncestorsorSelf() returns a list of nodes, and not sure if that is in any particular order (as opposed to xslt which would traverse the tree upwards and find the first match). Might be wrong here, but giving it a shot anyway... but what if you added an extra OrderBy clause (for the Level property) and take the last (or first if using OrderByDescending)?

     

    Let me know what you find out...

     

    Cheers,

    /Dirk

  • Alex 78 posts 136 karma points
    Mar 30, 2011 @ 20:39
    Alex
    2

    @Dirk I think the AncestorOrSelf() method returns a DynamicNodeList in order from the root down.

    @Lee First() would give you the first node which matches the criteria starting with root and working down to the current node. Last() does the opposite so will take the last node (including itself) in the DynamicNodeList which matches the criteria.

  • Lee Kelleher 4026 posts 15836 karma points MVP 13x admin c-trib
    Mar 30, 2011 @ 20:41
    Lee Kelleher
    0

    Thanks Alex, that makes sense.  In my case the "contactTelephone" property is only at the root/homepage node, so didn't matter which one I'd used (First()/Last()).

    All good for my learning curve!

    Cheers, Lee.

  • Alex 78 posts 136 karma points
    Mar 30, 2011 @ 20:58
    Alex
    4

    No problem Lee, still learning myself which is why I keep jumping on to the posts on here trying to absorb as much as possible, "learn by doing" and all that! :)

    By the way just realised you can do this too, notice it's AncestorOrSelf instead of AncestorsOrSelf, so it will only return the first node which matches the criteria navigating up the tree.

    @Model.AncestorOrSelf((Func<dynamic,bool>)(n => n.contactTelephone != string.Empty)).contactTelephone
  • Dirk De Grave 4541 posts 6021 karma points MVP 3x admin c-trib
    Mar 30, 2011 @ 21:01
    Dirk De Grave
    0

    wow, this is really cool Alex, and thanks for sharing

     

    Cheers,

    /Dirk

  • Mads Krohn 211 posts 504 karma points c-trib
    Mar 30, 2011 @ 21:53
    Mads Krohn
    1

    Lee, if all you want is the root node of your site, AncestorOrSelf() without any parameters, will give you just that:

    @Model.AncestorOrSelf().contactTelephone
  • Lee Kelleher 4026 posts 15836 karma points MVP 13x admin c-trib
    Mar 30, 2011 @ 22:09
    Lee Kelleher
    0

    Excellent, thanks Mads, good to know!

  • Chriztian Steinmeier 2800 posts 8791 karma points MVP 8x admin c-trib
    Mar 30, 2011 @ 22:38
    Chriztian Steinmeier
    2

    I don't get this - why should AncestorOrSelf() with no arguments give you the root node? That doesn't make sense to me (but I'm heavily XPath-biased so...)

    Is that just a convention, or how am I supposed to get that from the name?

    /Chriztian

  • Mads Krohn 211 posts 504 karma points c-trib
    Mar 30, 2011 @ 22:47
    Mads Krohn
    1

    Well, AncestorOrSelf, with no filtering parameters, would ultimately have to traverse all the way to the root, I guess? As there is no matching expression to stop the traversing. In that light, I guess it makes kind of sence?

    Looking in reflector, we can make sure though, that we actually get the root node.

    public DynamicNode AncestorOrSelf()
    {
        return this.AncestorOrSelf((Func<DynamicNode, bool>) (node => (node.Level == 1)));
    }

    If this release of Umbraco supported extension methods on DynamicNode, you could wrap the AncestorOrSelf() method inside Root() or something, to make the method name make more sence :)

  • Rich Green 2246 posts 4008 karma points
    Mar 30, 2011 @ 23:17
    Rich Green
    0

    So much to learn.. :)

  • Yannick Smits 321 posts 718 karma points
    Mar 31, 2011 @ 16:19
    Yannick Smits
    0

    He Alex, just tested your expression:

     

    @Model.AncestorOrSelf((Func<dynamic,bool>)(n => n.contactTelephone != string.Empty)).contactTelephone

    but it gives me a:

    Operator '!=' cannot be applied to operands of type 'umbraco.MacroEngines.DynamicNull' and 'string'

    error when the property is empty. This one works though:

     

    @Model.AncestorsOrSelf().Where("contactTelephone != \"\"").Last().contactTelephone

    Looks like our recursive queries are going to get a little easier in future versions:

    Change Set a117c8d644bb
    Committed By: agrath
    Committed On: Wed Mar 23 2011 at 10:01 AM
    Comment: 4.7.1 Added GetProperty(string alias, bool recursive) overload to DynamicNode
    Added GetPropertyValue(string alias) and GetPropertyValue(string alias, bool recursive) to DynamicNode

     

     

  • James Drever 118 posts 149 karma points
    May 06, 2011 @ 18:39
    James Drever
    0

    Five weeks on, and I'm wondering: did the tantalising new methods in the change set above actually get implemented?

    Cos when I try and use them, e.g

    var courses=Model.GetProperty("availabilityChecker", true); 

    I get:

    Error loading Razor Script FSCListofRecommendedCourses.cshtml
    Cannot invoke a non-delegate type

    If I'm just being dumb, please someone do let me know.  Cos the methods above for getting recursive properties would be really really useful.


  • Mads Krohn 211 posts 504 karma points c-trib
    May 07, 2011 @ 01:25
    Mads Krohn
    0

    There haven't been a new Umbraco release since the above change set, so, they are quite surely implemented, just not officially released :)

     

  • Bjørn Arild Mæland 2 posts 22 karma points
    May 31, 2011 @ 16:56
    Bjørn Arild Mæland
    0

    The following snippet works fine as long as the property type is set to e.g. textstring, but it doesn't return anything at all if the type is changed to Richtext editor. Am I missing something obvious here or is this a bug?

    Code:

    @Model.AncestorsOrSelf().Where("sidebarLeft != string.Empty").Last().sidebarLeft
    

  • Dan Diplo 1554 posts 6205 karma points MVP 6x c-trib
    Jun 27, 2011 @ 13:41
    Dan Diplo
    1

    Just came across this thread and thought I'd supply a belated guess as to why Bjørn's query (above) doesn't work.

    The Rich Text Editor's underlying type is of System.Web.HtmlString therefore you need to call ToString() on it to get the actual content to check if it is not empty. So you're query would be something like:

    Model.AncestorsOrSelf().Where("sidebarLeft.ToString() != string.Empty").Last().sidebarLeft
  • Tom 713 posts 954 karma points
    Nov 13, 2011 @ 08:40
    Tom
    0

    GetPropertyValue isn't working for me at all... 

  • Robert Foster 459 posts 1820 karma points MVP 3x admin c-trib
    Nov 17, 2011 @ 12:55
    Robert Foster
    0

    Hi Tom,

    What version of Umbraco are you using?  and what are you trying to get? 

     

  • Sebastiaan Janssen 5060 posts 15522 karma points MVP admin hq
    Nov 17, 2011 @ 19:02
    Sebastiaan Janssen
    0

    @Tom this was introduced in 4.7.1, I suspect you're using 4.7.0 then. You can still do: Model.GetProperty("yourPropertyAlias").Value (it has the same effect).

  • Dan Diplo 1554 posts 6205 karma points MVP 6x c-trib
    Nov 23, 2011 @ 15:37
    Dan Diplo
    0

    Just noticed what appears to be a weird bug in Razor recursive properties:

    Basically I have a property on every page called headerImage. This is populated via a media picker and stores the id of the selected image. Not all pages have an image selected, so for some it will be empty. The root (home) node definitely has a value.

    I then wrote some razor code to recursively find the last ancestor node that had a headerImage with a value:

    dynamic node = Model.AncestorsOrSelf().Where("headerImage != \"\"").LastOrDefault();

    However, the above always ended up returning NULL and wouldn't work (even though I'd used similar code before).

    However, a standard recursive page field did work fine and returned the correct id:

    <umbraco:Item runat="server" Field="headerImage" recursive="true" />

    In the end I found I had to do the following in Razor which did work:

    dynamic node = CurrentModel.AncestorsOrSelf().Items.
    Where(x => x.HasValue("headerImage")).LastOrDefault();

    I've no idea why. I suspect it might be because the media picker stores the value as an int rather than a string?

    Anyone any clues?

  • Michael Latouche 504 posts 819 karma points MVP 4x c-trib
    Nov 23, 2011 @ 16:00
    Michael Latouche
    1

    Hi Dan,

    Did you try with .Where("headerImage != null")?

    Cheers,

    Michael.

  • Dan Diplo 1554 posts 6205 karma points MVP 6x c-trib
    Nov 23, 2011 @ 16:05
    Dan Diplo
    0

    Michael - I thought I must have, but when I checked I actually hadn't since it worked fine. Feel a bit stupid now :)

    Thanks for the tip - it works great now!

  • Michael Latouche 504 posts 819 karma points MVP 4x c-trib
    Nov 23, 2011 @ 16:09
    Michael Latouche
    0

    Great!

    Glad I could help. Sometimes we are so deep in our own stuff that we don't see/think of the obvious ;-)

    Cheers,

    Michael.

  • Dave Woestenborghs 3504 posts 12135 karma points MVP 9x admin c-trib
    Nov 23, 2011 @ 16:14
    Dave Woestenborghs
    1

    I'm having a problem with getting a recursive value with .GetPropertyValue.

    I created a codeplex item for it. Please vote : http://umbraco.codeplex.com/workitem/30607

  • Funka! 398 posts 661 karma points
    Jan 20, 2012 @ 02:16
    Funka!
    6

    Here is a very convenient way to do what you are asking, using special "leading underscore" on the property name. So, to answer the original question on page 1 of this thread (asked almost a year ago), we have :

    Old school examples... <umbraco:Item>

    <umbraco:Item runat="server" Field="contactTelephone" recursive="true"/>

    XSLT

    <xsl:value-of select="$currentPage/ancestor-or-self::*[@isDoc and normalize-space(contactTelephone)][1]/contactTelephone"/>

    ... And in Razor, at least in version 4.7.1 (not sure if also works in older versions), you can simply do this:

    @Model._contactTelephone

    The leading underscore is the shortcut for recursive. I know this is an old topic but, for posterity, thought I would add this in case it helps someone else...

  • Tom 713 posts 954 karma points
    Jan 20, 2012 @ 02:23
    Tom
    0

    Love it! that is fantastic! Thanks Funka! wish it was documented! 

  • Jeroen Breuer 4908 posts 12265 karma points MVP 5x admin c-trib
    Jan 20, 2012 @ 09:03
  • Carlos 338 posts 472 karma points
    May 18, 2012 @ 20:24
    Carlos
    0

    How far up the tree does the _recursive go. Right now I see it only going up 1 level.

  • Jeroen Breuer 4908 posts 12265 karma points MVP 5x admin c-trib
    May 18, 2012 @ 20:32
    Jeroen Breuer
    0

    Think it should go up all the way until it's found somewhere.

    Jeroen

  • Carlos 338 posts 472 karma points
    May 18, 2012 @ 20:49
    Carlos
    0

    If I write an If/Else and the a statement is true will is search all the way up the tree until is sees the true statement?

    Something like this

     if (@Model._backgroundRepeat)

         {

            <text>repeat</text>

         }

         else if (@Model._backgroundRepeatVertical)

         {

            <text>repeat-y</text>

         }

         else if (@Model._backgroundRepeatHorizontal)

         {

            <text>repeat-x</text>

         }

         else

         {

            <text>no-repeat</text>

         }

     

    Will it look up the tree until it finds the true statement if not then hit the else? Because right now I have a sub page that is 3 levels deep. The page has the same property that the parent nodes have the same property. I want it set up so each page can have its own property option and if the option is not true on the current page, I want it to crawl up the tree until it finds the true one. Will that work?

  • Carlos 338 posts 472 karma points
    May 18, 2012 @ 20:54
    Carlos
    0

    I am on 4.7.1

  • Carlos 338 posts 472 karma points
    May 18, 2012 @ 22:44
    Carlos
    0

    Nevermind. Got recursive to work. in my case I has to use AncestorOrSelf.

  • Tony Kiernan 278 posts 341 karma points
    Jun 21, 2012 @ 17:40
    Tony Kiernan
    0

    Don't suppose anyone's worked out how to do this for a datatype like a multinode picker or embedded content where there is xml recorded, so always a value?

    Model.AncestorsOr Self().Where("things.Count() > 0").Last().Id;

    Or something

  • Heather Floyd 610 posts 1032 karma points MVP 6x c-trib
    Nov 21, 2012 @ 18:46
    Heather Floyd
    0

    Even with all these great responses, I am struggling with Razor recursion...

    Here is a simplified snippet of my macro:

    var RelatedLinksProperty = @Parameter.RelatedLinksProperty; //string 
    MatchingNodeId = @Model.AncestorsOrSelf((Func<dynamic,bool>)(x => x.HasValue(RelatedLinksProperty))).Last().Id;
    CountTest = @Model.AncestorsOrSelf((Func<dynamic, bool>)(x => x.HasValue(RelatedLinksProperty))).Count();
    DynamicNode ContentNode = @Library.NodeById(MatchingNodeId);

    XDocument AllLinks = XDocument.Parse(ContentNode.GetProperty(RelatedLinksProperty).Value.ToString());
    <ul>
    @foreach (XElement item in AllLinks.Root.Elements("link") )
    {
    //...Code to output <li>s for each link...
    }
    </ul>

    Assuming I have a content structure like this:

    HOME => (doctype 'Website') has RL property named "MainNavLinks"

    - Products => (doctype Textpage) does not have RL property named "MainNavLinks"

    And I have a template which is used on both pages which includes this:

    <nav  class="horz-list"><umbraco:Macro RelatedLinksProperty="MainNavLinks" Alias="Nav_RelatedLinksList_RZ" runat="server"></umbraco:Macro></nav>

    This works propery on the HOME page = the list of nav links is output.

    But it fails on the Products page = "MatchingNodeId" is equal to the current page's Id (Products) and "CountTest" is equal to 2.

    I have tried these variations with the same result:

    MatchingNodeId = @Model.AncestorsOrSelf((Func<dynamic,bool>)(x => x.HasProperty(RelatedLinksProperty))).Last().Id;

    and

    string Query = RelatedLinksProperty + ".ToString() != string.Empty";
    MatchingNodeId = @Model.AncestorsOrSelf().Where(Query).Last().Id;

    What am I missing? Why do these expressions return "true" for the current page which doesn't even have the property?

    Thanks!

  • AlexRamsey 4 posts 24 karma points
    Dec 11, 2012 @ 04:23
    AlexRamsey
    0

    Hi,

    I also found that the "Related Links" and the "uComponents Multi-URL Picker" data types were problematic to implement recursivley.

    As "Tony Kiernan" posted, "...there is xml recorded, so always a value" even when nothing has been selected.

    As a result I was able to implement recursion for the "Related Links" data type the following way:

    @inherits umbraco.MacroEngines.DynamicNodeContext             
    @using umbraco.MacroEngines;

    @{
    var RelatedLinksProperty = @Parameter.RelatedLinksProperty;
    @traverse(@Model, RelatedLinksProperty)
    }

    @helper traverse(dynamic node, string relatedLinksProperty)
    {
    if (node.HasProperty(relatedLinksProperty))
    {
    var relatedLinks = node.AncestorsOrSelf((Func)(x => x.HasProperty(relatedLinksProperty))).Last();
    var relatedLinksXML = Library.ToDynamicXml(relatedLinks.GetProperty(relatedLinksProperty).Value);

    if (relatedLinksXML.BaseElement.IsEmpty)
    {
    @traverse(node.Parent, relatedLinksProperty)
    }
    else
    {
    foreach (var item in relatedLinksXML.link)
    {
    .
    .
    .
    }
    }
    }
    }

    Can anyone suggest a better alternative?

     

     

     

Please Sign in or register to post replies

Write your reply to:

Draft