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.
.AncestorsOrSelf
Get all the ancestors (parents) walking up the tree and
return them as a DynamicNodeList
@foreach(var item in Model.AncestorsOrSelf())
{
@item <br/>
}
.Descendants
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..)
.DescendantsOrSelf
The same as Descendants, but will not return the current
node as the first item in the list
.Ancestors
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/>
}
}
DynamicNull
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))
{
}
IHtmlString
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.
Conclusion
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: http://our.umbraco.org/forum/developers/razor
Codeplex for any feature requests or bugs: http://umbraco.codeplex.com/
Read more from the Umbraco Razor walkthrough series
Part 1
Part 2
Part 3
Part 4
Part 5
Part 6
Part 7
Part 8