-
Notifications
You must be signed in to change notification settings - Fork 86
Spark Bindings
A binding is a small rule that matches an element in a spark file and provides a code fragment that takes its place in the generated class.
By default bindings are loaded from a ~/Views/Bindings.xml
file if it is present in your project. There are two main forms of bindings,
<bindings>
<element name="TheElementName">TheReplacementCode</element>
</bindings>
<bindings>
<element name="TheElementName">
<start>StartElementReplacementCode</start>
<end>EndElementReplacementCode</end>
</element>
</bindings>
It is loaded through the IViewFolder
abstraction so it does not need to be a physical file if that path is provided by another source.
You may also replace the default IBindingProvider
service on SparkViewEngine
instance if you want to entirely replace how binding definitions are located and parsed.
By default a binding will match an element by name, and the replacement code will be used as an output expression.
<bindings>
<element name="Greetings">"Hello, " + Model.CurrentUser.Name + "!"</element>
</bindings>
<!-- using the binding -->
<p><Greetings/></p>
<!-- is the same as -->
<p>${"Hello, " + Model.CurrentUser.Name + "!"}</p>
The replacement code may contain small terms that are designed to resemble xpath
selectors. If they are present it has two effects: the rule will only match elements in the spark template which have those attributes, and the value of the attribute will replace the term in the generated code.
<bindings>
<element name="Greetings">"Hello, " + @username + "!"</element>
</bindings>
<!-- using the binding -->
<p><Greetings username="Product.Name" /></p>
<!-- is the same as -->
<p>${"Hello, " + Product.Name + "!"}</p>
Possible matches for bound elements are evaluated in order of appearance from the binding provider. The first one that satisfies all requirements is used, so to overload an element put the most specific bindings first.
<bindings>
<element name="Greetings">"Hello, " + @username + "!"</element>
<element name="Greetings">"Hello, " + Model.CurrentUser.Name + "!"</element>
</bindings>
<!-- using the binding -->
<p><Greetings/></p>
<p><Greetings username="Product.Name" /></p>
<!-- is the same as -->
<p>${"Hello, " + Model.CurrentUser.Name + "!"}</p>
<p>${"Hello, " + Product.Name + "!"}</p>
There are times when an attribute containing code is given a string value, and the nested quotes seems counter-intuitive. In the following example you wouldn't assume the quotes around the action and text are needed.
<bindings>
<element name="ActionLink">Html.ActionLink(@text, @action)</element>
</bindings>
<p>Please <ActionLink action="'Register'" text="'register'"/> to continue.</p>
To make the quotes implied, wrap the attribute in the reference with single- or double-quotes. The attribute values in the spark templates will be treated as text, and may contain ${expr}
to mix anything dynamic into the generated code.
<bindings>
<element name="ActionLink">Html.ActionLink('@text', '@action')</element>
</bindings>
<p>Please <ActionLink action="Register" text="register before ${DateTime.Now.AddSeconds(3)}"/> to continue.</p>
If an element name has bindings, but it does not match any of the rules because of missing attributes, then it will be output as if it was plain markup like any other element in the temples. That can be useful to give special meaning to existing elements, like <a>
, if it contains some meaningful attributes you define, like action="..."
.
<bindings>
<element name="a">Html.ActionLink('@text', '@action')</element>
</bindings>
<p>Please <a action="Register" text="register before ${DateTime.Now.AddSeconds(3)}"/> to continue.</p>
If you want use a binding element around some child content, and that child content must be passed to a macro or html helper as an argument, you can refer to it with a 'child::*'
term.
<bindings>
<element name="a">Html.ActionLink('child::*', '@action')</element>
</bindings>
<!-- normal usage of a link, followed by an action="" attribute to use the html helper -->
<p>
Either <a href="~/">return to the home page</a>
or <a action="Register">register before ${DateTime.Now.AddSeconds(3)}</a>
to continue.
</p>
Html helpers often take a dictionary of name/value pairs to do things like add route values, or additional html attributes, to the output they produce. An attribute wildcard syntax {{'@*'}}
may be used to expand any unused attributes into a new dictionary.
<bindings>
<element name="a">Html.ActionLink('child::*', '@action', '@controller',
new Dictionary<string,object>{{'@*'}})</element>
</bindings>
<p>Please <a action="Register" controller="Account" title="This is the hover text">register</a> to continue.</p>
Because @action
and @controller
are in the replacement code they will not appear in the dictionary. The attribute value may also contain code in ${expr}
syntax.
There are cases where a helper may take several dictionaries. One example would be route values in addition to html attributes. In that case a prefix may be used in the wildcard attribute reference.
<bindings>
<element name="a">Html.ActionLink('child::*', '@action',
new RouteValueDictionary{{'@route-*'}},
new Dictionary<string,object>{{"@*"}})
</element>
</bindings>
<ul>
<li each="var product in Products">
Id ${product.Id}:
<a action="show" route-id="${product.Id}" title="${product.Details}">${product.Name}</a>
</li>
</ul>
If a wildcard element is not surrounded with a double-curley brace, then the code generated will be suitable for object initialization.
<bindings>
<!-- element Foo will call function Foo() -->
<element name="Foo">Foo(new Thing{'@*'})</element>
</bindings>
<!-- macro puts Foo() in scope. assumes class Thing exists. -->
<macro name="Foo" theThing="Thing">
<span>Thing ${theThing.Id}: #{theThing.Name}</span>
</macro>
<p>
<Foo Id="${34}" Name="Frank"/>
<!-- is the same as -->
${Foo(new Thing {Id = 34, Name="Frank" })}
</p>
</bindings>
Some helpers that write directly to the response and have a void return value. If you put a hash at the front of a binding it will simply generate the code as a statement and omit the output.
<bindings>
<element name="Partial">#Html.RenderPartial("@name", new ViewDataDictionary{{"@*"}});</element>
</bindings>
<Partial name="ShowThis" Foo="My Caption" Bar="${Model.Product}" />
Another way to use a binding as a element with child contents is by providing two replacement code fragments. In this case the child content is output normally - it is not marshalled into a parameter - the start and end code is simply inserted at the point where the start and end element appeared.
<bindings>
<element name="Form">
<start># using (Html.BeginForm("@action", "@controller", new RouteValueDictionary{{"@route-*"}}, FormMethod.@method, new Dictionary<string,object>{{"@*"}})) {</start>
<end># }</end>
</element>
</bindings>
<div>
<Form action="Create" controller="Product">
<!-- input elements and whatnot -->
</Form>
</div>