Sunday, March 13, 2011

Umbraco Razor Feature Walkthrough - Part 5

Part 5 of the feature walkthrough continues with features available in umbraco 4.7.
You should be using the 4.7 RC or higher for these examples.
Today, I'm covering the changes between the 4.7 beta and RC1
Sorry this post is a little late, the RC has been out for a while now.

Fixed Bugs
We've fixed a lot of little bugs reported on codeplex and the forums.
The most notable was that you couldn't call OrderBy with a single descending column.
There's better support for your expressions in .Where too, but more on that below…

DateTime property support
The first releases of 4.7 didn't gracefully handle DateTime properties, they'd still return as string. Now, you can do the following:

@Model.Children.Where("updateDate < DateTime.Now.AddDays(-2)") 

This didn't work previously in 4.7beta because updateDate was still boxed as an object and object < DateTime can't be implicitly converted. The underlying change here is that now the left hand side of your expression will be unboxed to the type of the right hand side, if it's a node. This means that because DateTime.Now.AddDays(-2) returns a date time and updateDate returns a Func<DynamicNode,object> the object gets unboxed to be a DateTime (which it was anyway)

IsProtected & HasAccess

We've added helper methods (these have changed to properties in the 4.7 final) on DynamicNode to check to see if a given node is protected or if the currently logged in (or not) user has access to that node.
Here's an example:

@foreach(var item in Model.Children) { if(item.HasAccess()) { @item.Name <br/> } } 

Warning: HasAccess and IsProtected are methods in 4.7 RC, but in the 4.7 Final, they're Properties. This means you will need to drop the () off the calls if you use this syntax.

Better Tree Navigation

The 4.7 beta release contained a method - AncestorOrSelf, which allowed you to get the most top-level node in your content tree.
In 4.7 RC, we've added some extra methods which give you more flexible support over navigating the tree.

Each of these methods have 3 overloads.
1) An empty call .Method() - returns the default condition, e.g. all nodes that match the given call
2) A level filtered call .Method(int) - returns nodes which are <= or >= (depending on if you're using Ancestors or Descendants) the desired number
3) A node type alias filtered call .Method(string) - returns nodes which have the matching node type alias (or the first in the case of AncestorOrSelf)

The AncestorOrSelf method itself has been upgraded to support the same overloads too.

Get all the ancestors (parents) walking up the tree and return them as a DynamicNodeList

@foreach(var item in Model.AncestorsOrSelf()) { @item <br/> } 

Get all the children [deep] of the current node, by collecting the children.
.Children will only return the immediate children, whereas Descendants will return all of them (children of children..)

The same as Descendants, but will not return the current node as the first item in the list

The same as .AncestorsOrSelf but will not return the current node as the first item in the list

DynamicNodeWalker - Our secret weapon in the fight against the Rebel XSLT alliance

I don't know about you, but I always found navigating around nodes in XSLT to be difficult. If you had a node, and you wanted to go to the first child, or the next sibling.. well, good luck.
It's possible, but it's not easy.
In the 4.7 RC, we added DynamicNodeWalker - this is mostly just an internal [as in you never see this in your Razor files] name, but I like the walker reference (and, no, i'm not a huge SW fan)

DynamicNodeWalker gives us some more methods on a DynamicNode.
The methods are PrototypeJs inspired, a javascript library I've used extensively but has largely fallen to the JQuery empire.

These methods are: Up/Down/Next/Previous.
Each method will take no parameters, which is equivalent to one "step" or an integer which is equivalent to a number of steps.

For the following examples, I'm using a content tree which looks like this:

Company Division 1 Department Team 1 Employee 1 Employee 2 Team 2 Department 2 Team 3 Employee 3 Employee 4 Team 4 Department 3 Department 4 Division 2 

Assuming you're currently sitting on Company…

Model.Down().Next() //Division 2 Model.Down(1).Next().Down(1) //Employee 3 

Or, if you're on Employee 3..

Model.Up(1).Previous().Down().Next() // Team 2 Model.Next() // Employee 4 

Each of these methods will only ever return a single node, so in the case of Down, it's the first child, for Next, it's the following sibling.
If a node doesn't match the requested node, you'll get null - so look out for null references. If you get "Sequence contains no elements" or "Cannot perform runtime binding on a null reference" then you should check to make sure that the steps (int parameter) and directions you're using match your tree.

.Contains (methods on properties)
In the 4.7 beta, we had pretty good support for .Where - you could check node values, but one thing that didn't work was calling methods on those values.
For example:

@foreach(var item in Model.Children.Where("bodyText.Contains(\"cat\")")) { @item.Name <br/> } 

Wouldn't work.
This is now fixed in 4.7 RC.
Warning: A bug was pointed out on the forum with this which has been fixed in 4.7 Final - which is that if you're performing multiple checks, this won't work:

@foreach(var item in Model.Children.Where("bodyText.Contains(\"cat\") || !(bodyText.Contains(\"dog\"))")) { @item.Name <br/> } 

.ContainsAny (and extension method support)

Taking an example case - you're using the Contains example above, and you want to check for multiple keywords.
In 4.7 Beta, you could do this:

var items = @Model.Children.Where("Name.Contains(\"cat\") || Name.Contains(\"dog\") || Name.Contains("\fish\")") 

Not very nice is it? What if you had more than 3 keywords to check?
In 4.7 RC, you can do this:

var values = new Dictionary<string,object>(); var keywords = new List<string>(); keywords.Add("cat"); keywords.Add("dog"); keywords.Add("fish"); values.Add("keywords",keywords); var items = @Model.Children.Where("Name.ContainsAny(keywords)", values); 

Certainly a lot more readable, though verbose.
The keywords List<string> can come from somewhere else though.

ContainsAny is an extension method, defined within the Razor implementation - however because of the way the method is loaded and invoked, you can write your own and drop them in the bin folder like the example in part 3

Here's the actual implementation for ContainsAny:

public static bool ContainsAny(this string haystack, List<string> needles) { if (!string.IsNullOrEmpty(haystack) || needles.Count > 0) { foreach (string value in needles) { if (haystack.Contains(value)) return true; } } return false; } 

MediaById and NodeById new overloads

We've added another couple of overloads to MediaById and NodeById that take List<object> and params object[]
These let you return multiple nodes as a list.
The intention for these methods is to give better support when working with the uComponents multi node tree picker.
MediaById returns a new type, DynamicMediaList

Warning: DynamicMediaList is just a list wrapper, it doesn't support OrderBy or Where like DynamicNodeList does.

Here's an example of the new overloads being used:
I have a property on my document type which uses a multi node tree picker, and it's set to CSV type.
Warning: I tested the below sample and unfortunately the decimal handling code in DynamicNode caught the CSV string and turned it into a decimal. I've fixed this for the final.

@{ var nodes = @Model.NodeById(Model.multiChildPicker); foreach(var node in nodes) { @node.Name <br/> } } 

This will work in the RC (assuming those node ids are valid)

@{ var nodes = @Model.NodeById(1024,2048,4096); foreach(var node in nodes) { @node.Name <br/> } } 

In the beta, when a property or type wasn't found, we just returned null.
This broke handling, particularly with True/False types which didn't have a value (null instead of 0)

The 4.7 RC will return a new type, DynamicNull which you can test for.
This was added so that a .Where against a property which existed on some nodes in the set but not on others, wouldn't crash.
.Where explicitly checks for this type and returns a default value (false)
An example of this is @Model.Children.Where("umbracoNaviHide != true") and not all of the nodes in your children have that property

You can explicitly check for it like this:

@using umbraco.MacroEngines;
@if(@Model.propertyNameThatDoesntExist.GetType() == typeof(DynamicNull))


Finally, in 4.7 Beta, if you define a property on your document type using the RTE editor, you have to use @Html.Raw in the beta to decode the HTML.
In 4.7 RC, we auto-detect this type and return it as IHtmlString which makes the view engine treat it as HTML.

You should only need to use @Html.Raw if you're not using the RTE editor.


This post ended up being way longer than I expected!
A few more changes have been made for the 4.7 final, if you're code inclined, feel free to check out the commit messages, but otherwise, stay tuned for Part 6 after the 4.7 final release

I'm Gareth Evans, Follow me at @agrath on twitter, and here's a few links:
The new Razor forum on our.umbraco
Github for any feature requests or bugs

Read more from the Umbraco Razor walkthrough series

Umbraco Razor Feature Walkthrough - Part 1
Umbraco Razor Feature Walkthrough - Part 2
Umbraco Razor Feature Walkthrough - Part 3
Umbraco Razor Feature Walkthrough - Part 4
Umbraco Razor Feature Walkthrough - Part 5
Umbraco Razor Feature Walkthrough - Part 6
Umbraco Razor Feature Walkthrough - Part 7
Umbraco Razor Feature Walkthrough - Part 8

Want to be updated on everything Umbraco?

Sign up for the Umbraco newsletter and get the latest news and special offers send directly to your inbox