GRAILS: send parameter to renderEditor.template - grails

I need to send a parameter to the file renderEditor.template (After of installing of "grails install-templates"), but I have no idea how. Can anyone help me? Thanks.

The binding variables available to renderEditor.template are fixed in DefaultGrailsTemplateGenerator
def binding = [pluginManager: pluginManager,
property: property,
domainClass: domainClass,
cp: cp,
domainInstance:getPropertyName(domainClass)]
domainClass being the GrailsDomainClass, property being the GrailsDomainClassProperty that was passed to the renderEditor(p) call in the scaffolding template, and cp being the corresponding ConstrainedProperty. You may be able to extract what you need from one of those.

For other people who got here via Google with the same problem (as I did)
I've managed to do so in similar way as Ian Roberts suggested, but you don't need to create new implementation for Template Generator. All you need is to create a proxy implementation for org.codehaus.groovy.grails.commons.GrailsDomainClassProperty
inside of _form.gsp I did just created new property with added data:
p = new HgfGrailsDomainClassProperty(p, prefix, domainClass)
if (display) { %>
<hgf:ifAllowedOnField action="show" field="${p.name}">
<hgf:ifReasonToShow reasonElement="${p.name}" bean="\${${propertyName}}">
<div class="fieldcontain \${hasErrors(bean: ${propertyName}, field: '${prefix}${p.name}', 'error')} ${required ? 'required' : ''} ${(cp?.metaConstraints?.hiddenField)?'hiddenProperty':''}">
<label for="${prefix}${p.name}">
<g:message code="${domainClass.propertyName}.${prefix}${p.name}.label" default="${p.naturalName}" />
<% if (required) { %><span class="required-indicator">*</span><% } %>
</label>
${renderEditor(p)}
...
Implementation can look something like this:
package sk.hoppo.hgf;
import org.codehaus.groovy.grails.commons.GrailsDomainClass
import org.codehaus.groovy.grails.commons.GrailsDomainClassProperty
public class HgfGrailsDomainClassProperty implements GrailsDomainClassProperty {
private final String domainSuffix = "Instance";
private GrailsDomainClassProperty property;
String prefix;
String domainInstance;
public HgfGrailsDomainClassProperty(GrailsDomainClassProperty property, String prefix, GrailsDomainClass parentDomainClass) {
super();
this.property = property;
this.prefix = prefix;
domainInstance = getPropertyName(parentDomainClass);
}
private String getPropertyName(GrailsDomainClass domainClass) {
return "${domainClass.propertyName}${domainSuffix}";
}
#Override
public int getFetchMode() {
return property.getFetchMode();
}
#Override
public String getName() {
return property.getName();
}
...
Inside the renderEditor.template you just access the send data:
sb << '<hgf:securedField'
sb << ' name="' << property.prefix << property.name << '"'
sb << ' field="' << property.prefix << property.name << '"'
BTW. with this I've made composition (embedding) to work with my custom renderEditor correctly

Related

Render MVC form using reflection and Razor Tag Helper .Net 3.1

I want to create complicate form which will create structure of wizard, partial steps form, validation and submit. This structure have to use model attributes annotations to create one structure object over the model. So after reflection I have model and one other class with structure description. All properties within are strings with Fields which I have to pass on 'asp-for' tag helper. So part of the code is:
#foreach(var field in #group.Fields) {
<div class="col-12 col-md-6 col-lg-4">
<div class="form-group md-form md-outline">
<label asp-for="#field.Name" class="control-label"></label>
<input asp-for="#field.Name" class="form-control" />
<span asp-validation-for="#field.Name" class="text-danger"></span>
</div>
</div>
}
This is nor working because tag helper expect expression and generate wrong values which are not expected from me. The value in #field.Name is 'PostAddress.Street1'. If I replace all of "#field.Name" with "PostAddress.Street1" everything work properly how I expected.
It looks small issue but I'm trying many things and reading some theads in forums but didn't find the answer. What I tried:
Experiment 1
Tried to inherit InputTagHelper class from dotnet library and override property For but without success. It changed ModelExpression but no changes in interface. May be base class have some logic to skip this changed object or is not correct generated:
[HtmlAttributeName("asp-for")]
public new ModelExpression For
{
get
{
return base.For;
}
set
{
ModelExpression me = value;
if (value.Model != null)
{
var viewData = this.ViewContext.ViewData as ViewDataDictionary<AbnServiceModel>;
me = ModelExpressionProvider.CreateModelExpression<AbnServiceModel, string>(viewData, model => model.PostAddress.Street1);
}
base.For = me;
}
}
=================================================
2. Experiment 2
Try to get original implementation from .NET Core code and made some modification in code to fix the issue. But the code and dependencies with internal libraries were very complicated and I reject this idea.
Expiriment 3
Using HTML helpers
#Html.Label(#field.Name, "", new{ #class="control-label" })
#Html.Editor(#field.Name, new { htmlAttributes = new{ #class="form-control" } })
#Html.ValidationMessage(#field.Name,"",new { htmlAttributes = new{ #class="text-danger" } })
It render components correct into the browser but client side validation using jquery.validate.unobtrusive.js is not working. Not sure why.
Expiriment 4
Using HTML helpers:
#Html.LabelFor(m=>m.PostAddress.Street1, new{ #class="control-label" })
#Html.EditorFor(m=>m.PostAddress.Street1, new { htmlAttributes = new{ #class="form-control" } })
#Html.ValidationMessageFor(m=>m.PostAddress.Street1,"",new { htmlAttributes = new{ #class="text-danger" } })
The validation is working but class weren't applied well, may be my mistake. But other problem here is that I'm not using expression which is string which can get from model object. Also It doesn't catch all logic which is included in asp-for tag helper.
Experiment 5
Tried to create my own tag helper and using generator to create the content html. But this means that I have to implement all logic like helper in dotnet core to have all functionality which is same like Expiriment 2
So I didn't find good solution of this "simple" problem and lost some days to investigate and doing some code to resolve it. I'm surprised that no way to pass string variable with property name and it wouldn't work.
Can someone help me to fix this problem with real example? I didn't find the answer in all posts. I want to have all logic from asp-for tag helper but use variable to pass the expression. It cab be and tricky, just want to have some resolution to continue with my project.
Thank you
I resolved my issue.
Created one helper method:
public static class CommonHelperMethods
{
public static ModelExplorer GetModelExplorer(this ModelExplorer container, string field, IModelMetadataProvider modelMetadataProvider = null)
{
ModelExplorer result = container;
var fields = field.Split(".").ToList();
var match = Regex.Match(fields[0], #"(.+)\[(\d)+\]");
if (!match.Success)
{
fields.ForEach(x =>
{
result = result?.GetExplorerForProperty(x) ?? result;
});
}
else
{ //List have to create own Property browser
string proName = match.Groups[1].Value;
int idx = Convert.ToInt32(match.Groups[2].Value);
var model = ((IList)result?.GetExplorerForProperty(proName).Model)[idx];
var targetProperty = model.GetType().GetProperty(fields[1]);
var targetValueModel = targetProperty.GetValue(model);
var elementMetadata = modelMetadataProvider.GetMetadataForProperty(model.GetType(), fields[1]);
return new ModelExplorer(modelMetadataProvider, container, elementMetadata, targetValueModel);
}
return result;
}
}
And just override the tag helper class with this:
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.TagHelpers;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace GetTaxSolutions.Web.Infrastructure.TagHelpers
{
[HtmlTargetElement("input", Attributes = ForAttributeName, TagStructure = TagStructure.WithoutEndTag)]
public class InputTextGtTaxHelper : InputTagHelper
{
private const string ForAttributeName = "asp-for";
[HtmlAttributeName("not-exp")]
public bool NotExpression { get; set; } = false;
[HtmlAttributeName(ForAttributeName)]
public new ModelExpression For
{
get
{
return base.For;
}
set
{
ModelExpression me = value;
if (NotExpression)
{
var modelExplorertmp = value.ModelExplorer.Container.GetModelExplorer(value.Model.ToString(), ModelMetadataProvider);
var modelExplorer = new ModelExplorer(ModelMetadataProvider, value.ModelExplorer.Container, modelExplorertmp.Metadata, modelExplorertmp.Model);
me = new ModelExpression(value.Model.ToString(), modelExplorer);
}
base.For = me;
}
}
public IModelExpressionProvider ModelExpressionProvider { get; }
public IModelMetadataProvider ModelMetadataProvider { get; }
public IActionContextAccessor Accessor { get; }
public InputTextGtTaxHelper(
IHtmlGenerator generator,
IModelExpressionProvider modelExpressionProvider,
IModelMetadataProvider modelMetaDataProvider) : base(generator)
{
ModelExpressionProvider = modelExpressionProvider;
ModelMetadataProvider = modelMetaDataProvider;
}
}
}
Also should skip original class in tag helper registration:
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
#removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.InputTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
#removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.LabelTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
#removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.ValidationMessageTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
#removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.SelectTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
#addTagHelper GetTaxSolutions.Web.Infrastructure.TagHelpers.*, GetTaxSolutions.Web
And when use model in expression just have to pass attribute 'no-exp' on input elements. Otherwise will work like original tag helper.
<input not-exp="true" asp-for="#field.Name" class="form-control" />
Also you have to do same with label, select and other used tag helpers which you want to support this way of model passing.

FreeMarker: how to get value of private attribute, not of public "get" method

I am trying to get the value of private field (attribute) without using "get" method even when this method exists.
Is it possible?
I created several examples following Freemarker get-method without "get":
Example 1: Successfully use "get" method for "private" field
Class:
public class MyClass {
private String myField = "TestA";
public String getMyField() { return "from method " + myField; }
}
Template: Test:${myObject.myField}
BeansWrapper config:
bw.setExposeFields(true);
// bw.setExposureLevel(BeansWrapper.EXPOSE_NOTHING);
Output: "Test:from method TestA"
Example 2: Successfully get value of "public" field
Class:
public class MyClass {
public String myField = "TestA";
public String getMyField() { return "from method " + myField; }
}
Template: Test:${myObject.myField}
BeansWrapper config:
bw.setExposeFields(true);
bw.setExposureLevel(BeansWrapper.EXPOSE_NOTHING);
Output: "Test:TestA"
Example 3: Can't get value of "private" field
Class:
public class MyClass {
private String myField = "TestA";
public String getMyField() { return "from method " + myField; }
}
Template: Test:${myObject.myField}
BeansWrapper config:
bw.setExposeFields(true);
bw.setExposureLevel(BeansWrapper.EXPOSE_NOTHING);
Output: Exception "The following has evaluated to null or missing: ==> myClass.myField"
None of the out-of-the-box ObjectWrapper-s (like BeansWrapper) allows reading private members. OTOH you can implement your own ObjectWrapper or extend an existing one (like BeansWrapper).

RazorEngine and parsing physical view files always causes exception

I have the following RazorEngine call:
public class RazorEngineRender
{
public static string RenderPartialViewToString(string templatePath, string viewName, object model)
{
string text = System.IO.File.ReadAllText(Path.Combine(templatePath, viewName));
string renderedText = Razor.Parse(text, model);
return renderedText;
}
}
This is called from:
_emailService.Render(TemplatePath, "Email.cshtml", new { ActivationLink = activationLink });
I also have this view file (email.cshtml):
<div>
<div>
Link: #Model.ActivationLink
</div>
</div>
When the call to Razor.Parse() occurs, I always get a:
Unable to compile template. Check the Errors list for details.
The error list is:
error CS1061: 'object' does not contain a definition for 'ActivationLink' and no extension method 'ActivationLink' accepting a first argument of type 'object' could be found
I've tried everything under the sun, including trying a concrete type as opposed to anonymous type, declaring the #Model line at the top of the view file but no luck. I'm wondering if the library is at fault or definately me?
By the way, the razorengine I am referring to is available here at codeplex:
RazorEngine
If you make the call like so:
Razor.Parse(System.IO.File.ReadAllText(YourPath),
new { ActivationLink = activationLink });
That should give you the correct output. But after I see your method posted above I'll be able to make a determination where the problem lies.
Update
Change your method to the following:
public class RazorEngineRender {
public static string RenderPartialViewToString<T>(string templatePath, string viewName, T model) {
string text = System.IO.File.ReadAllText(Path.Combine(templatePath, viewName));
string renderedText = Razor.Parse(text, model);
return renderedText;
}
}
and you can call it like you do above.
The reason it doesn't work is because you're telling the Parser that the model is of type object rather than passing in what type it really is. In this case an anonymous type.
The accepted answer was perfect in 2011 (I believe pre-v3 of RazorEngine) but this code is now marked as obsolete in latest version (in time of typing it is 3.7.3).
For newer version your method can be typed like this:
public static string RenderPartialViewToString<T>(string templatePath, string templateName, string viewName, T model)
{
string template = File.ReadAllText(Path.Combine(templatePath, viewName));
string renderedText = Engine.Razor.RunCompile(template, templateName, typeof(T), model);
return renderedText;
}
and in order for it to work you need to add
using RazorEngine.Templating;
Here are a few hints you might try:
Make your razor view strongly typed to a model:
#model Foo
<div>
<div>
Link:
<a href="#Model.ActivationLink" style="color:#666" target="_blank">
#Model.ActivationLink
</a>
</div>
</div>
When rendering it pass a Foo model:
_emailService.Render(
TemplatePath,
"Email.cshtml",
new Foo { ActivationLink = activationLink }
)
If you are trying to send emails from your Views make sure you checkout Postal before reinventing something.

ASP. Net MVC and RegisterClientScriptBlock alternative

I currently have a web form aspx page that calls RegisterClientScriptBlock. This sends down message text that I use for client side validation e.g.
<script type="text/javascript">
//<![CDATA[
var c_errorMessages = {
RequiredField : "* Mandatory field"
};
//]]>
</script>
The values are generated on the server side based on culture and resource files.
I believe you cannot use RegisterClientScriptBlock with MVC.
Any ideas on how I can achieve this with MVC?
The validation that comes with ASP.NET MVC 2 has built-in support for localized validation messages when using client-side validation. This will take care of registering the client scripts.
Having said that, the most common way to do this in ASP.NET MVC would probably be to simply have the controller feed the view with the data required to render the client script. An example of this would be
Action method in controller:
ViewData["MessagesToClient"] = new[] { "Abandon", "All", "Hope" };
return View("MyView");
Some code in MyView.aspx:
<%= RenderPartial("MyPartialView") %>
Some code in MyPartialView.ascx:
<script>
<% foreach (var message in (IEnumerable<string>)ViewData["MessagesToClient"]) { %>
alert("<%= message %>");
<% } %>
</script>
This way, MyView.aspx doesn't necessarily need to know about the data MyPartialView.ascx needs.
Create an extension method for HtmlHelper which takes your model and renders the javascript.
If you are doing validation:
Have a look at Castle Validation attributes. http://castleproject.org.
You can add attributes to your model (class) ...
[ValidateNonEmpty]
public string Name { get; set; }
and your extension method can determine the presence of these and write the appropriate javascript / jquery.
public static string JSValidation<T>(this HtmlHelper<T> html, T model) where T : class {}
And in your view use the helper:
<%= Html.JSValidation(Model) %>
ASP.NET MVC in Action has a brilliant example of this in Chapter 13.
Thanks for answer bzlm. My solution is pretty much the same as yours except I am not using a user control
In my controller I add to viewdata(this would be generated from method):
ViewData["javascriptBlockRequired"] =
"\r\n<script type='text/javascript'>\r\n//<![CDATA[\r\nvar c_merchant = { \r\n\tSomeField: false};\r\nvar c_errorMessages = {\r\n\tRequiredField : '* Required' \r\n};\r\n//]]>\r\n</script>\r\n";
In my view I output the script at the start of the body:
<body>
<%= ViewData["javascriptBlockRequired"] %>
....
<body/>
1- Add Extentions methods class
public static class Extentions
{
public static void AddRegisterClientsSript(this HtmlHelper helper, string key, string script)
{
Dictionary<string, string> scripts;
if (helper.ViewBag.RegisterClientsSript != null)
{
scripts = (Dictionary<string, string>)helper.ViewBag.RegisterClientsSript;
}
else
{
scripts = new Dictionary<string, string>();
helper.ViewBag.RegisterClientsSript = scripts;
}
if (!scripts.ContainsKey(key))
{
scripts.Add(key, script);
}
}
public static MvcHtmlString RegisterClientsSript(this HtmlHelper helper)
{
var outScripts = new StringBuilder();
if (helper.ViewBag.RegisterClientsSript != null)
{
var scripts = (Dictionary<string, string>)helper.ViewBag.RegisterClientsSript;
foreach (string script in scripts.Values)
{
outScripts.AppendLine(script);
}
}
return MvcHtmlString.Create(outScripts.ToString());
}
public static MvcHtmlString Paging(this HtmlHelper helper, string uniqId)
{
helper.AddRegisterClientsSript("jquerypaginatecss", #"<link href=""/Content/Paginate/jquery.paginate.css"" rel=""stylesheet"" />");
helper.AddRegisterClientsSript("jquerypaginatejs", #"<script src=""/Content/Paginate/jquery.paginate.js""></script>");
}
}
2- Add the namespace in page and use it
#using Mirak.Ui.Components
#section scripts{
#Html.RegisterClientsSript()
}

Conditionally disable Html.DropDownList

How can I change this DropDownList declaration so that the disabled attribute is enable/disabled conditionally?
<%= Html.DropDownList("Quantity", new SelectList(...), new{#disabled="disabled"} %>
non-working example:
<%= Html.DropDownList("Quantity", new SelectList(...), new{#disabled=Model.CanEdit?"false":"disabled"} %>
p.s. adding an if condition around the entire statement is not a desired approach :)
EDIT: based on this extension method from another question I came up with the following extension:
public static IDictionary<string, object> Disabled (this object obj, bool disabled)
{
return disabled ? obj.AddProperty ("disabled", "disabled") : obj.ToDictionary ();
}
which can then be used as
<%= Html.DropDownList("Quantity", new SelectList(...), new{id="quantity"}.Disabled(Model.CanEdit) %>
There is no need to add helper methods, you can just use
<%= Html.DropDownList("Quantity", new SelectList(...), IsEditable == true ? new { #disabled = "disabled" } as object : new {} as object %>
If you were to remove the as object entries this wouldn't work because by default new {} is a dynamic object compiled at runtime, therefore the two possible objects must have the same properties. But the Html attributes parameter is actually just an object, so these dynamics can be cast as objects to get around this.
This solution even allows you to use multiple HTML attributes where one is optional and another is not, i.e class='whatever' is not optional but disabled is so you put class='whatever' in both the objects, but the optional one only in the first. Dimitrov's answer does not support any custom attributes other than disabled.
Please don't write spaghetti code. Html helpers are there for this purpose:
public static MvcHtmlString DropDownList(this HtmlHelper html, string name, SelectList values, bool canEdit)
{
if (canEdit)
{
return html.DropDownList(name, values);
}
return html.DropDownList(name, values, new { disabled = "disabled" });
}
And then:
<%= Html.DropDownList("Quantity", new SelectList(...), Model.CanEdit) %>
Or maybe you could come up with something even better (if the model contains the options):
<%= Html.DropDownList("Quantity", Model) %>
You will also get the bonus of having more unit testable code.
One option is creating a custom version of Html.DropDownList that takes an extra parameter and does what you want... but then you would have to make a new one for every type of helper - TextBoxFor, TextAreaFor, CheckBoxFor, etc... and you still have to figure out how to make the guts of it work.
I opted, instead, to create an Html Helper to replace the normal anonymous HtmlAttributes object since then it would be compatible with all of the Helpers that use HtmlAttributes without any special work. This solution also lets you pass through additional Attributes like Class, Name, or whatever you want. It doesn't lock you down to only disabled.
I created the following Helper - it takes a boolean and an anonymous object. If disabled is true, it adds the disabled attribute to the anonymous object (which is actually a Dictionary) with the value "disabled", otherwise it doesn't add the property at all.
public static RouteValueDictionary ConditionalDisable(
bool disabled,
object htmlAttributes = null)
{
var dictionary = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
if (disabled)
dictionary.Add("disabled", "disabled");
return dictionary;
}
An example of it in action:
#Html.TextBoxFor(m => m.SomeProperty,
HtmlHelpers.ConditionalDisable(true, new { #class = "someClass"))
One huge advantage to this approach for me was that it works with virtually all of the MVC HtmlHelpers since they all have Overloads that accept a RouteValueDictionary instead of an anonymous object.
Caveats:
HtmlHelper.AnonymousObjectToHtmlAttributes() uses some fancy code ninja work to get things done. I'm not entirely sure how performant it is... but it's been sufficient for what I use it for. Your mileage may vary.
I don't especially like the name of it - but I couldn't come up with anything better. Renaming is easy.
I also don't love the usage syntax - but again I couldn't come up with anything better. It shouldn't be difficult to change. An extension method on object is one idea... you'd end up with new { #class = "someClass" }.ConditionalDisable(true) but then if you only want the disable attribute and don't have anything additional to add you end up with something gross like new {}.ConditionalDisable(true); and you also end up with an extension method that shows up for all objects... which is probably not desirable.
#bool IsEditable=true;
#if (IsEditable)
{
Html.DropDownListFor(m => m, selectList);
}
else
{
Html.DropDownListFor(m => m, selectList, new { disabled = "disabled" })
}
Strongly typed verison:
public static MvcHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> html,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> selectList,
string optionText, bool canEdit)
{
if (canEdit)
{
return html.DropDownListFor(expression, selectList, optionText);
}
return html.DropDownListFor(expression, selectList, optionText, new { disabled = "disabled" });
}
For completeness here is one that preservers all parameters and it would post select value to the server:
public static MvcHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList, object htmlAttributes, bool enabled)
{
if (enabled)
{
return SelectExtensions.DropDownListFor<TModel, TProperty>(html, expression, selectList, htmlAttributes);
}
var htmlAttributesAsDict = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
htmlAttributesAsDict.Add("disabled", "disabled");
string selectClientId = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(ExpressionHelper.GetExpressionText(expression));
htmlAttributesAsDict.Add("id", selectClientId + "_disabled");
var hiddenFieldMarkup = html.HiddenFor<TModel, TProperty>(expression);
var selectMarkup = SelectExtensions.DropDownListFor<TModel, TProperty>(html, expression, selectList, htmlAttributesAsDict);
return MvcHtmlString.Create(selectMarkup.ToString() + Environment.NewLine + hiddenFieldMarkup.ToString());
}
Usage example, disable drop down if there is only one item in list, that one value is still posted to server with correct client id:
#Html.DropDownListFor(m => m.SomeValue, Model.SomeList, new { #class = "some-class" }, Model.SomeList > 1)
You can do:
var dropDownEditDisable = new { disabled = "disabled" };
var dropDownEditEnable = new { };
object enableOrDisable = Model.CanEdit ?
(object)dropDownEditEnable : (object)dropDownEditDisable;
#Html.DropDownList("Quantity", new SelectList(...), enableOrDisable)
Html.DropDownListFor() can be long, so doing that, there is no need to repeat it.
I don't know if ASP.NET offers a more succinct special-case approach, but presumably you could do:
<%= Html.DropDownList("Quantity", new SelectList(...), Model.CanEdit? new{#class="quantity"} : new{#class="quantity", #disabled:"disabled"}) %>

Resources