how to write some variable(name and value) in dust template using helper - dust.js

I have client side dust template and a corresponding helper.
Helper :
function(chunk, context, bodies, params) {
};
Now I want to write some key value pair(from helper) which I can read in dust template.
e.g.If i write (k,v) in helper, in dust template
{k}
should output 'v'
Thanks,
Shantanu

A helper in Dust receives the current chunk and context. You can push new values onto the context stack, and then call chunk.render().
{
"helper": function(chunk, context, bodies, params) {
var obj = { "k": "value" };
return chunk.render(bodies.block, context.push(obj));
}
}
Here, I am rendering the default body (bodies.block), using the existing context as well as my extra obj, which is pushed onto the context stack.
{#helper}{k}{/helper} {! k is only accessible inside the context of my helper !}

Related

Can I use a LambdaExpression type for MVC's HTML helpers?

I am trying to use reflection to auto-generate a view. Html.DisplayFor and some of the other helpers take Expression<Func<,>> which derives from LambdaExpression. Seemed like I'd be able to manually generate my own lambda and then pass that in, but it's throwing this error:
The type arguments for method 'DisplayExtensions.DisplayFor<TModel, TValue>(HtmlHelper<TModel>, Expression<Func<TModel, TValue>>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.`
Here's my markup:
<tr>
#foreach (var pi in Model.GetType().GetProperties())
{
<td>
#Html.DisplayFor(ExpressionHelpers.GetPropertyGetterLambda(pi))
</td>
}
</tr>
I am pretty sure what's happening is that .DisplayFor requires generic type arguments to infer the types for Func<TModel, TValue>, but I am using LambdaExpression which is hiding the types.
It seems like the only way to do what I want is to build/compile an expression that actually calls .DisplayFor using type-safe arguments, but that seems overly complicated.
Is there another way to achieve my goal or would I be better off just outputting the results directly to the HTML rather than calling the helpers?
Edit: Per request, here's the code for GetPropertyGetterLambda:
public static LambdaExpression GetPropertyGetterLambda(PropertyInfo pi, BindingTypeSafety TypeSafety)
{
if (pi.CanRead)
{
ParameterExpression entityParameter = Expression.Parameter(TypeSafety.HasFlag(BindingTypeSafety.TypeSafeEntity) ?
pi.ReflectedType : typeof(object));
LambdaExpression lambda = Expression.Lambda(GetPropertyReadExpression(entityParameter, pi, TypeSafety), entityParameter);
return lambda;
}
else
{
return null;
}
}
There is another solution for this that I recommend first. Change your for loop to an DisplayForModel call:
#Html.DisplayForModel()
That will render out all the properties, using DisplayFor internally. You can modify this by modifying the Object.cshtml in the DisplayTemplates folder (either in the Views folder your are working in, or in the Shared folder to apply globally).
If that is insufficient, or you really really want to use LambdaExpression, here is an alternative. It will require you adding a DisplayFor<TModel>(LambdaExpression expression) extension method. It would be something like (note that I haven't actually tested this, but this is close to what is needed):
public static IHtmlString DisplayFor<TModel>(this HtmlHelper<TModel> helper, LambdaExpression expression)
{
var wrapperClass = typeof(DisplayExtensions);
///find matching DisplayFor<TModel, TProperty> method
var matchingMethod = wrapperClass.GetMethods()
.Single( c => c.IsStatic
&& c.Name == "DisplayFor"
&& c.GetGenericArguments( ).Count() == 2
&& c.GetParameters( ).Count() == 2 //overloads don't have same # of parameters. This should be sufficient.
).MakeGenericMethod( typeof(TModel), expression.ReturnType ); //Make generic type from the TModel and the Return Type
//invoke the method. The result is a IHtmlString already, so just cast.
return (IHtmlString) matchingMethod.Invoke( null, new Object[] { helper, expression } );
}

Multiple tag helpers targetting the same element

I have just noticed that if I have 2 tag helpers targeting the same element, both can be executed. The order in which they are executed depends on the order in which they are registered in _ViewImports.cshtml.
For example, I can create another tag helper for the anchor element:
[HtmlTargetElement("a", Attributes = "foo")]
public class FooTagHelper : TagHelper
{
public override void Process(TagHelperContext context, TagHelperOutput output)
{
//Get the url from href attribute generated in the default AnchorTagHelper
var url = output.Attributes["href"].Value.ToString();
...
}
}
Use it as follows (notice I am also adding attributes of the default anchor helper like asp-controller):
<a class="menu" asp-controller="Home" asp-action="Index" foo>Foo</a>
If this helper is registered in _ViewImports.cshtml after the default ASP ones:
Whenever Process is called, the TagHelperOutput already contains the href generated by the default AnchorTagHelper. I could also update the anchor generated by the default tag helper in any way I like.
Is there any degree of control over this behavior?
You might want to decide whether or not to execute further helpers targeting the same element (As if sealing your output). You might also want to allow other helpers, but make sure some attribute wasn't modified.
Override the readonly property order, like this:
[HtmlTargetElement("a", Attributes = "foo")]
public class FooTagHelper : TagHelper
{
// This should be the last tag helper on any tag to run
public override int Order => int.MaxValue;
public override async Task ProcessAsync(TagHelperContext context,
TagHelperOutput output)
{
//...
}
}
Reading the source code of the TagHelperRunner class, I realized that the same TagHelperContext and TagHelperOutput will be shared for all the tag helpers found for the same element, which will be processed orderd by the ITagHelper.Order property.
So you can control the order in which they are executed by assigning appropriated values to the Order property. As a reference, this is the TagHaelperRunner.RunAsync method:
public async Task<TagHelperOutput> RunAsync([NotNull] TagHelperExecutionContext executionContext)
{
var tagHelperContext = new TagHelperContext(
executionContext.AllAttributes,
executionContext.Items,
executionContext.UniqueId,
executionContext.GetChildContentAsync);
var tagHelperOutput = new TagHelperOutput(
executionContext.TagName,
executionContext.HTMLAttributes)
{
SelfClosing = executionContext.SelfClosing,
};
var orderedTagHelpers = executionContext.TagHelpers.OrderBy(tagHelper => tagHelper.Order);
foreach (var tagHelper in orderedTagHelpers)
{
await tagHelper.ProcessAsync(tagHelperContext, tagHelperOutput);
}
return tagHelperOutput;
}
The default Order if you extend the class TagHelper is 0.
The MVC tag helpers like AnchorTagHelper or InputTagHelper seem to have the order defined as -1000.
So far, I have also found that you can query some of the properties in TagHelperOutput to check if a previous tag helper has modified the output. Although you cannot know if a tag helper with higher order (executed after yours) modifies the output:
TagHelperOutput.IsContentModified will return true only when the Content is modified (Not when the attributes or the PreElement, PreContent, PostElement, PostContent are modified)
TagHelperOutput.PreElement.IsModified and similar for PreContent, PostElement and PostContent will return true when those have been modified.
Content set by a previous tag helper could be removed by calling TagHelperOutput.Content.Clear() and similar for Pre/Post Element/Context properties.
Content can be completely suppressed by calling TagHelperOutput.SuppressOutput() which calls clear on every of those properties and set TagName as null. If you want the tag helper to render something you will then need to assign them again.
Finally, if you had to share some data between multiple tag helpers for the same element, you can use the TagHelperContext.Items dictionary.

DustJS: Escape input from context

Doing some investigation for using dust.js, and I was wondering is there a way from preventing bad data to be rendered.
Template
Hello {name}! You have {count} new messages
Context
{
"name": "Mick",
"count": Math.PI
}
Yields, this result:
Hello Mick! You have 3.141592653589793 new messages
In this example, is there a way to escape the Math.PI, so that we can bail out and not print 3.14..
You, as the developer, have to decide what is 'bad data' and what is an acceptable alternative.
Then you must either transform it in code (eg. the node.js building the page) before it reaches dust.js, or write a helper to render whatever you want with appropriate fallback. For instance, if you want to render integers, and display some custom fallback text otherwise, you might use a helper something like this:
Create an integerOrElse function, and save it in a file, eg.
local-dust-helpers.js:
// this extends dustjs-helpers (which must therefore be in package.json)
var dust = require('dustjs-helpers');
dust.helpers.integerOrElse = function (chunk, ctx, bodies, params) {
// tap function resolves variables in params
var value = dust.helpers.tap(params.value, chunk, ctx),
fallback = dust.helpers.tap(params.fallback, chunk, ctx) || '';
// define a fallback for the fallback :) ----------------^^^^^
// for more brevity, you could do this in one line with a ternary operator
if (!isNaN(value) && parseInt(value) == value) {
return chunk.write(value);
} else {
return chunk.write(fallback);
}
}
Then require() it in your app, replacing where you would have called the vanilla dust.js:
app.js
...
var dust = require('./local-dust-helpers');
...
You can then use it just like a native dust.js directive:
template.dust
Hello {name}!
You have {#integerOrElse value='{count}' fallback='some' /} new messages

HTMLHelper, generating parameter of type "Expression<Func<TModel, TValue>> expression" out of property

I'm writing an HTML Helper for an editor. The idea is to get properties from the Model with attributes AutoGenerateField and build a table, each line of which contains a name of a field (also from the attribute) and a TextBox or a CheckBox containing actual value of the field.
I have a problem with HTMLHelper. Since I send the whole model to the helper and not one value, I cannot use methods such as TextBoxFor, as they need parameter, such as
"Expression<Func<TModel, TValue>> expression".
I'm using reflexion and I tried to send the property instead, still VisualStudio considers this as incorrect usage.
Below is simplified method for my HtmlHelper:
public static MvcHtmlString GenerateEditor<TModel>(this HtmlHelper<TModel> htmlHelper)
{
var model = htmlHelper.ViewData.Model;
var result = String.Empty;
//generating container, etc ...
foreach (var property in model.GetType().GetProperties())
{
var attr = property.GetCustomAttributes(typeof (DisplayAttribute), true).FirstOrDefault();
if (attr == null) continue;
var autoGenerate = ((DisplayAttribute)attr).AutoGenerateField;
if(autoGenerate)
{
//here I'm building the html string
//My problem is in the line below:
var r = htmlHelper.TextBoxFor(property);
}
}
return MvcHtmlString.Create(result);
}
Any ideas?
How about just using the non-lambda overloads. : InputExtensions.TextBox()
if(autoGenerate)
{
//here I'm building the html string
//My problem is in the line below:
var r = htmlHelper.TextBox(property.Name);
}
//not sure what you do with r from here...
If I'm not mistaken the name attribute of the form element is set to the property name even when you use the lambda version of the function so this should do the same thing.
I will try and verify what the lambda function does, you might be able to do the same since you have the TModel with you.
Update
From a quick look of things inside the source code of InputExtensions.cs, TextBoxFor calls eventually calls InputHelper() which eventually calls ExpressionHelper.GetExpressionText(LambdaExpression expression) inside ExpressionHelper.cs which from the cursory looks of things gets the member.Name for the name html attribute on the input element.
I can't quite verify it right now because I'm not on windows but I think the non-lambda function should suit your need. Do tell me how it goes?

ASP.NET MVC 3 client-side validation with parameters

Following on from this post Perform client side validation for custom attribute
I am trying to get my head around how to do this, passing additional parameters to the client-side script
As I understand it so far to implement custom validation with MVC 3 the following is required
Create a custom validation attribute
Based on ValidationAttribute and implementing IClientValidatable. I have also see some examples deriving from ModelValidator, which seems to implement the functionality of both ValidationAttribute and IClientValidatable. So this is my first point of confusion as to what the diffirences are or whether ModelValidator was used in MVC 2 but is now deprecated or what ?
An instance of ModelClientValidationRule must be returned from GetClientValidationRules() to specify details such as the error message, ValidationType (which I understand to be the name of the Javascript function that will perform the client-side validation) and any additional custom parameters that the attribute may have, and that need to be passed to the Javascript validation.
I assume that the runtime (not sure which part of it) then use the ModelClientValidationRule to generate html attribute in the tag elements as follows:
data-val="true" (to indicate that the element requires validation)
data-val-[ValidationType]=[ErrorMessage]
data-val-[ValidationType].[ValidationParameters(n).Key]=[ValidationParameters(n).Value]
Implement the client-side validation logic
A Javascript function must be created and added to jQuery.validators with jQuery.validators.addmethod() so that JQuery is aware of it when it need to be executed. Something like:
jQuery.validator.addMethod(
'greaterThan',
function (value, element, params) {
/.../
return /* true or false */ ;
},
''
);
My question here is whether the signature 'function (value, element, params)' is standard for methods that will handle validation and I assume it will be called by some jQuery functionality at the appropriate time such as before a form is submitted or when an element looses fuces or on keyUp events. I just don't undertand how you can controll this i.e. choose which event is appropriete for yout custom validation.
Implement an unobtrusive adapter
This translates unobtrusive attributes to; something I am not very clear on, but assume it to be a jQuery Rule, but I am not clear on how those work. Something like
jQuery.validator.unobtrusive.adapters.add(
'futuredate',
{ },
function (options) {
options.rules['greaterThan'] = true;
options.messages['greaterThan'] = options.message;
}
);
My question here is about 'function (options)'. Is this the function that will be called before 'function (value, element, params)' and is responsible for extracting the unobtrusive tags into a data structure that can be understood by jQuery.Validation. From the code example it seems to me that options is an object that contains both, the attribute values from the tag (such as options.message) and the jQuery relevant properties it must map to (such as options.messages['ClientSideValidationFunctionName']. If so how are custom parameters retrieved and mapped.
I hope I have not added any additional confusion.
You could use the ValidationParameters property to add custom parameters to the rule:
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = this.ErrorMessage,
ValidationType = "futuredate",
};
rule.ValidationParameters.Add("param1", "value1");
rule.ValidationParameters.Add("param2", "value2");
yield return rule;
}
which could be used in the adapter:
jQuery.validator.unobtrusive.adapters.add(
'futuredate',
[ 'param1', 'param2' ],
function (options) {
var param1 = options.params.param1; // shall equal 'value1'
var param2 = options.params.param2; // shall equal 'value2'
// TODO: use those custom parameters to define the client rules
}
);
UPDATE:
As requested in the comments section here's how you could pass those parameters to the custom validator rule function:
jQuery.validator.unobtrusive.adapters.add(
'futuredate',
[ 'param1', 'param2' ],
function (options) {
// simply pass the options.params here
options.rules['greaterThan'] = options.params;
options.messages['greaterThan'] = options.message;
}
);
jQuery.validator.addMethod('greaterThan', function (value, element, params) {
// params here will equal { param1: 'value1', param2: 'value2' }
return ...
}, '');

Resources