How to get reference to json key name inside {{#with}} helper? - handlebars.js

I want to draw fields using a Handlebars partial that have the key name and value. My json is like this:
{ "someKnownField" : { "textValue" : "the value I want" } }
I want a label with someKnownField as its text, and an input with the value of textValue in it. I use a partial because I have hundreds of these fields and don't want to have to hard code their names.
Here's my partial code, called textfield
<div class="control-group">
<label class="control-label" for="input{{#key}}">{{#key}}</label>
<div class="controls">
<input type="text" id="input{{#index}}" placeholder="{{#key}}" value="{{textValue}}">
</div>
</div>
Now, I can't use a {{#with}} helper, a-la {{#with someKnownField}}{{> textfield}}{{/with}} since a with doesn't give you a #key. {{#each}} has a #key but its of the context within the each node (textValue); So how do you key the key name OF the each node itself?
This does not work, but demonstrates what I need to grab:
<label>{{../#key}}</label>
since it's expecting an id in the parent path, not a calculated value (which doesn't exist anyway, since it's not an array itself).

You've done well, but made a typo. The correct way:
<label>{{#../key}}</label>

Related

Tag Helper asp-validation-for is always showing

I tried using the Asp.Net Core TagHelper but it doesn't seem to work. However, when using HtmlHelpers it works as expected. My issue is that it always display the error message although the ModelState is valid. Am I doing something wrong or can someone reproduce this error?
<label class="control-label" asp-for="Firstname">Firstname</label>
<input type="text" class="form-control" asp-for="Firstname">
<span class="form-control-feedback" asp-validation-for="Firstname"> This field has an error. </span>
The property Firstname has a Required attribute in the ViewModel.
It works like this:
<label class="control-label" asp-for="Firstname">Firstname</label>
<input type="text" class="form-control" asp-for="Firstname">
#Html.ValidationMessageFor(x => x.Firstname)
Edit:
It seems to work if I don't add the custom error message to the Html element but instead to the ViewModel DataAnnotation, like this:
<label class="control-label" asp-for="Firstname">Firstname</label>
<input type="text" class="form-control" asp-for="Firstname">
<span class="form-control-feedback" asp-validation-for="Firstname"></span>
Model:
[Required(ErrorMessage = "This field has an error.")]
public string Firstname { get; set; }
TL;DR:
Consider putting text inside the tag helpers in scenarios when you really want something
different from the generated value.
Full answer
You practically find the solution on your own, but I think I can still throw in my two cents here.
Most tag helpers work in a manner of generating content on a condition when its content is empty or contain only whitespace characters. For example, the ValidationMessageTagHelper checks it in this way:
var tagHelperContent = await output.GetChildContentAsync();
// We check for whitespace to detect scenarios such as:
// <span validation-for="Name">
// </span>
if (!tagHelperContent.IsEmptyOrWhiteSpace)
{
message = tagHelperContent.GetContent();
}
It gets tag content and then fills up message variable if the content is null, empty or whitespace. The message variable is then used to generate the validation message:
var tagBuilder = Generator.GenerateValidationMessage(
ViewContext,
For.ModelExplorer,
For.Name,
message: message,
tag: null,
htmlAttributes: htmlAttributes);
If the message is null or empty then the generator will provide the model error (see line 858 of DefaultHtmlGenerator);
if (!string.IsNullOrEmpty(message))
{
tagBuilder.InnerHtml.SetContent(message);
}
else if (modelError != null)
{
modelExplorer = modelExplorer ?? ExpressionMetadataProvider.FromStringExpression(
expression,
viewContext.ViewData,
_metadataProvider);
tagBuilder.InnerHtml.SetContent(
ValidationHelpers.GetModelErrorMessageOrDefault(modelError, entry, modelExplorer));
}
The GetModelErrorMessageOrDefault() of ValidationHelpers:
public static string GetModelErrorMessageOrDefault(
ModelError modelError,
ModelStateEntry containingEntry,
ModelExplorer modelExplorer)
{
Debug.Assert(modelError != null);
Debug.Assert(containingEntry != null);
Debug.Assert(modelExplorer != null);
if (!string.IsNullOrEmpty(modelError.ErrorMessage))
{
return modelError.ErrorMessage;
}
// Default in the ValidationMessage case is a fallback error message.
var attemptedValue = containingEntry.AttemptedValue ?? "null";
return modelExplorer.Metadata.ModelBindingMessageProvider.ValueIsInvalidAccessor(attemptedValue);
}
So yes, if you put any text inside the <span> validation tag, the tag helper will choose your text over validation error from model state. Similar behaviour occurs if you put text inside the <label> tag as you did:
<label class="control-label" asp-for="Firstname">Firstname</label>
The tag helper will not overwrite the Firstname value you put inside the tag. It may not seem as bad behaviour, but if you would like to use display name for the Firstname property:
[Display(Name = "Fancy first name")]
public string Firstname { get; set; }
you would not see it work! Because the tag helper would again choose the text you put in-between <label> tags over the display name for Firstname.
What you should do is leave it as simple as i can be:
<label class="control-label" asp-for="Firstname"></label>
Consider putting text inside the tag helpers in scenarios when you really want something
different from the generated value.
At the begging I said that most tag helpers work that way. Most of them do, but not all of them. For example SelectTagHelper allows you to put any custom text inside the tag and if you provide a select list, it will generate the options by appending them to the existing content. It is extremely handy for adding custom <option> tags. For example I can easily add a selected and disabled option, so the dropdown does not have initial value, therefore the user is forced to manually select an option. These lines of code:
<select asp-for="LevelId" asp-items="#Model.Levels" class="custom-select">
<option selected disabled>Select option</option>
</select>
will result in:
<select class="custom-select" data-val="true" data-val-required="'Level Id' must not be empty." id="LevelId" name="LevelId">
<option selected disabled>Select parking level</option>
<option value="9">-2</option>
<option value="8">-1</option>
<option value="7">0</option>
</select>

Thymeleaf: populating checkboxes from an Array

I have a Spring MVC application using Thymeleaf for templating. I am using enums to generate checkboxes dynamically. So if my enum file has 3 values it will generate 3 checkboxes:
My enum file:
public enum Foods {
PIZZA("Pizza"),
PASTA("Pasta"),
MAC_CHEESE("Mac and Cheese"),
ICE_CREAM("Ice Cream"),
BURGER("Burger"),
private String type;
Foods(String type) {
this.type = type;
}
public String getType() {
return this.type;
}
}
This is my checkbox generation:
<label for="decision">What is your favorite food?</label>
<div id="decision" class="row" style="margin-top:1%;">
<div class="col-md-4" th:each="option : ${T(in.app.model.enums.Foods).values()}">
<div class="checkbox checkbox-custom checkbox-circle">
<input name="decision" type="checkbox" th:id="${option.toString()}" th:value="${option}" />
<label th:for="${option.toString()}" th:text="${option.type}"></label>
</div>
</div>
</div>
This code will generate 5 checkboxes for each of the food type. All works till here. The issue I am facing is how to set the checked attribute when reading a saved record.
I am getting back an object via the model view controller. The object has a food property with its value as the array of the chosen food types.
user = {
.
.
food : ["PIZZA", "BURGER", "PASTA"],
.
.
}
Now I want to loop through this array and if the value match then set the checkbox.
I am trying to do something like this:
<label for="decision">What is your favorite food?</label>
<div id="decision" class="row" style="margin-top:1%;">
<div class="col-md-4" th:each="option : ${T(in.app.model.enums.Foods).values()}">
<div class="checkbox checkbox-custom checkbox-circle">
<input
name="decision"
type="checkbox"
th:id="${option.toString()}"
th:value="${option}"
th:each="food : ${user.food}"
th:attr="checked = ${food} == ${option} ? 'checked'"
/>
<label th:for="${option.toString()}" th:text="${option.type}"></label>
</div>
</div>
</div>
I know its wrong (since its not working) but I am unable to figure out how to loop over two arrays to show the checkboxes and to check them.
You might want to try using th:checked instead of th:attr if you can, so:
th:checked="${food == option.type}"
This post might also be helpful when looking into that. If you can't use th:checked, switching to the below statement should also work.
th:attr="checked=${food == option.type} ? 'checked'"
It also seems like you may run into some issues with checking this data due to case sensitivity while comparing, in which case this post might be helpful.
The safe option is to go with
th:attr
and do compare like #Artichoke
th:attr="checked=${food == option.type} ? 'checked'"
The problem with "th:checked" is, its simply do not go well when you need to post the data unless you change the value. You see its value as null if you do not switch it.

Rendering template within context in Handlebars

Is it possible to render a template, or even just a partial, from within the context passed to a top level template? It seems like this might require recursive rendering, but maybe I'm missing something.
The example below demonstrates this using Bootstrap.
Say this is my top level template:
<div class="panel">
<div class="panel-body">
{{{description}}}
</div>
</div>
And my context is:
{
description: "\
Some text before the warning.\
<div class=\"alert alert-warning\" role=\"alert\">\
<span class=\"glyphicon glyphicon-warning-sign\" aria-hidden=\"true\"> </span>\
My warning here.\
</div>\
Some text after the warning."
}
What I'd like to do is separate the alert into a partial for a number of reasons:
Arbitrary placement within surrounding text
Can make partials for types other than warning (danger, info, etc.)
Can add as many as needed interspersed in the context string
For these reasons, it seems like it's not possible to put it into the top level template.
The partial would look something like this:
<script id="partial-warning-template" type="text/x-handlebars-template">
<div class="alert alert-warning" role="alert">
<span class="glyphicon glyphicon-warning-sign" aria-hidden="true"> </span>
{{{warning-message}}}
</div>
</script>
Once this is in place, I would be able to use it like so:
{
description: "\
Some text before the warning.\
{{> partial-warning-template \"My warning here.\"}}\
Some text after the warning.\
{{> partial-warning-template \"Now adding a second warning.\"}}"
}
Maybe I'm missing something fundamental - is there a more idiomatic way of doing this?
You won't be able to include the partial blocks in the description value and expect them to be evaluated as partials by the top level template method; the entire description string will be spat out as a single literal string.
What you would need to do is to have the partials evaluated before you pass the context object with description to the top level template method.
If you have pre-compiled your partial in something like the following manner:
Handlebars.registerPartial('warn', Handlebars.compile(document.getElementById('partial-warning-template').innerHTML));
Then you will be able to call this partial when you construct your description string:
{
description: 'Some text before the warning.' +
Handlebars.partials.warn({ 'warning-message': 'My warning here.' }) +
'Some text after the warning.' +
Handlebars.partials.warn({ 'warning-message': 'Now adding a second warning.' })
}

Ractive: How to access a variable integer index keypath of an array in a Ractive view

I have a 'collection' (extended ractive object) that has a keypath of 'collection' in the data key of the object which is an array of records.
The view loops the collection with {{#each collection:i}} and a href is generated for the name of each record.
When the user clicks a link on a collection item, an event is triggered which sets a 'selectedIndex' property obj.set('selectedIndex',i)
In my view then, how then do I access the record in collection at this index?
<input type="text" name="title" value="{{abc}}" placeholder="Name">
Where 'abc' is something like 'collection[selectedIndex].prop'
Of course its fine if selectedIndex is not variable, I could use collection.1.prop or collection[1].prop ... but I've tried every possible combination and hunted through the docs but I can't see how to do this ... surely it's possible?
You pretty much nailed it:
<input type="text" name="title" value="{{collection[selectedIndex].prop}}" placeholder="Name">
Looks like you need to guard against the non-existent value (or set a default), see http://jsfiddle.net/m199umtv/

Error occurs when jQuery processing a form is inside another form

I am writing a form using jQuery and encounter some difficulties.
My form works fine in static page (html).
However, when I use the form in dynamic page(aspx), the form does not behave correctly.
I cannot append items to the form and call the form.serialize function.
I think the error occurs when a form is inside another form (.aspx code needs to enclosed by a form tag).
What should I do?
Let me give a simplified version of my code:
<form name="Form1" method="post" id="Form1">
some content
<form name="form_inside">
<input name="fname" type="text" />
</form>
</form>
jQuery code:
$("#form_inside").append($("<input type='text' name='lname'>"));
When the user submits,
$("#form_inside").serialize();
// it should return fname=inputfname&lname=inputlname
I want to append element to "form_inside" and serialize the form "form_inside".
The form "Form1" is required by the aspx and I cannot remove it.
Could you just serialize the fields inside Form1?
I don't know anything about ASP, but it seems that you're not doing a straightforward "submit" anyway - so does it really matter if the fields aren't within their own separate form?
You could possibly group the fields you're interested in within a <div> or something, e.g.:
<div id="my-interesting-fields">
...
</div>
then substitute #form-inside with #my-interesting-fields where appropriate - is that helpful at all?
Edit
OK, a quick glance at the jQuery code suggests that serialize() depends on the form's elements member.
I suppose you could hack this in a couple of different ways:
Copy all elements from #my-interesting-fields into a temporary <form> that you dynamically create outside Form1, then call serialize() on that. Something like:
$("#Form1").after("<form id='tmp-form'></form>").
append("#my-interesting-fields input");
$("tmp-form").serialize();
Or, create an elements member on #my-interesting-fields, e.g.
$("#my-interesting-fields").elements = $("#my-interesting-fields input");
$("#my-interesting-fields").serialize();
I haven't tried either of these, but that might give you a couple of ideas. Not that I would necessarily recommend either of them :)
Because you can't have nested <form> tags you'll need to close off the standard dotnet form tag like below:
<script type="text/javascript">
$(document).ready(function() {
$("#form_inside").append($("<input type='text' name='lname'>"));
$("#submitBtn").click(function() {function() {
var obj = $("#form_inside *");
var values = new Array();
obj.each(function(i,obj1) {
if (obj1.name && !obj1.disabled && obj1.value) {
values.push(obj1);
};
});
alert(jQuery.param(values));
}); });
});
</script>
<form id="form1" runat="server">
<div>
<div id="form_inside" name="form_inside"> <input name="fname" type="text" /><input type="button" id="submitBtn" /></div>
</div>
</form>
jQuery.param on a array of form elements will give you the same results as .serialize()
so you get all elements in div $("#form_inside *) then filter for elements then on the result jQuery.param will give you exactly what you need

Resources