I have a model that contains a collection, such as this:
class MyModel
{
public List<MySubModel> SubModels { get; set; }
}
In the view, I want to dynamically add/remove from this list using Javascript before submitting. Right now I have this:
$("#new-submodel").click(function () {
var i = $("#submodels").children().size();
var html = '<div>\
<label for="SubModels[' + i + '].SomeProperty">SomeProperty</label>\
<input name="SubModels[' + i + '].SomeProperty" type="textbox" />\
</div>'
$("#submodels").append(html);
});
This works, but it's ugly. And, if I want to show those labels/textboxes for the existing items, there's no clean way to do that either (without duplicating).
I feel like I should be able to use Razor helpers or something to do this. Any ideas? Help me stay DRY.
You approach may lead to unexpected errors if you when you are removing or adding the divs. For example you have 4 items, you remove the first item, then $('#submodels').children().size() will return 3, but your last inserted div has the name attribute value set SubModels[3].SomeProperty which results in a conflict. And if your posted values contain SubModels[1] but not SubModels[0] the default model binder will fail to bind the list (it will bind it as null). I had to learn this the hard way...
To eliminate the aforementioned problem (and your's) I suggest you do something like this:
$("#addBtn").click(function() {
var html = '<div class="submodel">\
<label>SomeProperty</label>\
<input type="textbox" />\
</div>'; // you can convert this to a html helper!
$("#submodels").append(html);
refreshNames(); // trigger after html is inserted
});
$(refreshNames); // trigger on document ready, so the submodels generated by the server get inserted!
function refreshNames() {
$("#submodels").find(".submodel").each(function(i) {
$(this).find("label").attr('for', 'SubModels[' + i + '].SomeProperty');
$(this).find("label").attr('input', 'SubModels[' + i + '].SomeProperty');
});
}
Then your view (or even better an EditorTemplate for the SubModel type) can also generate code like:
<div class="submodel">
#Html.LabelFor(x => x.SomeProperty);
#Html.EditorFor(x => x.SomeProperty);
</div>
It would also be possible to convert the code generation to a html helper class, and use it in the EditorTemplate and in the JavaScript code
I would recommend you going through the following blog post.
Related
I'm creating a website with ASP.NET MVC5 and I'm using MaterializeCSS for the first time, which looks like a very exciting framework.
However, the checkboxes generated by CheckBoxFor helper become hidden !
When I write :
#Html.CheckBoxFor(m => m.IsAgreeTerms)
The generated HTML is :
<input name="IsAgreeTerms" type="hidden" value="false">
Why does Materialize change my type=checkbox into type=hidden ?
I tried to add type="checkbox" in the CheckboxFor helper, but it doesnt change anything. The only way is to modify in in my browser's console.
The only solution I found is this SO thread.
However, the accepted answer doesn't change anything for me.
The other answer works, but I think it's ugly to add some JS script to modify what Materialize modifies without my consent.
Is there any way to say "Hey, I ask for a type=checkbox, so just let my type=checkbox in the generated HTML" ?
Thank you
UPDATE :
My full ASP.NET MVC code is :
#Html.CheckBoxFor(m => m.IsAgreeTerms, new { #type = "checkbox" })
#Html.LabelFor(m => m.IsAgreeTerms, new { #class = "login-label" })
The full generated HTML is
<input data-val="true" data-val-required="Le champ IsAgreeTerms est requis." id="IsAgreeTerms" name="IsAgreeTerms" type="checkbox" value="true"
<input name="IsAgreeTerms" type="hidden" value="false">
<label class="login-label" for="IsAgreeTerms">IsAgreeTerms</label>
Here's a solution in the form of a html helper. It constructs a checkbox and label in the correct order:
public static IHtmlString CheckBoxWithLabelFor<TModel>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, bool>> expression,
string labelText,
object htmlAttributes = null
)
{
if (expression == null)
{
throw new ArgumentNullException(nameof(expression));
}
var checkBoxWithHidden = htmlHelper.CheckBoxFor(expression, htmlAttributes).ToHtmlString().Trim();
var pureCheckBox = checkBoxWithHidden.Substring(0, checkBoxWithHidden.IndexOf("<input", 1, StringComparison.Ordinal));
var labelHtml = htmlHelper.LabelFor(expression, labelText).ToHtmlString().Trim();
var result = pureCheckBox + Environment.NewLine + labelHtml + Environment.NewLine + $"<input type=\"hidden\" name=\"{ExpressionHelper.GetExpressionText(expression)}\" value=\"false\" />";
return new MvcHtmlString(result);
}
Is there other html generated by materialize.css? I think this happens because it is not possible apply a custom CSS to element input of type checkbox.
So, the checkbox becomes hidden and other html component represents visually the checkbox. Many components work like that.
UPDATE:
Why is Html checkbox generating an additional hidden input
OP here. Problem looks like more complex.
Actually, when using #Html.CheckBoxFor, MVC5 generates 3 fields, in that order :
Your input, with type="checkbox", binded to your model property
An hidden field (see the Claudio's link for an explaination)
Your label, generated by #Html.LabelFor
Problem is Materialize expects that in another order to work.
In your browser's console, just move the <label> element between the input and the hidden field, and everything works fine !
I found this very useful link, where, basically, it is said that the order of the generated fields by #Html.checkBoxFor will change ... In MVC6 !
As I'm working with MVC5, I use this very ugly solution in my _Layout:
$(":checkbox").each(function () {
$(this).nextAll("label").before($(this))
})
If anyone has a better idea, please feel free to post an elegant solution.
I need way to display a "List" property with the option to add new elements to the list.
So Basically
Value 1
Value 2
Button: Add new
I created an editfor template for it, where I display all the values with a foreach loop. However, each item get's an index, so when I add a new input field with javascript, the index is wrong.
Any suggestions how to achieve this.
PS: the adding of new elemens mustbe done on the client, since it is a simple form
var abccounter = 1;
$("#abcbutton").click(function () {
$('#itemlist').append('<p><input class="text-box single-line" id="listofstringname_' + abccounter + '_" name="listofstringname[' + abccounter + ']" type="text" value=""></p>');
abccounter++;
});
<p>#Html.EditorFor(model => model.listofstringname)</p>
that is what I did and it worked. the only problem I'm having (and it may be solved eventually) is I want to wrap each element with a tag but I'm not sure how. this JS just adds a new "text box element" assuming 1 as the start as my model loads 1 example by default.
I have a simple view which renders set of images depending on given items array (simplified code), using this as I need to collect some other data to 'build' required class name(s):
App.MyView = Ember.View.extend({
buildTemplate: function () {
var itemz = this.get('items');
var classname = 'classNameDependingOnSomeCalculations...';
var out = '<div>';
$.each(itemz, function (index, obj) {
out += '<img {{action myActionHere}} src="' + obj.href + '" alt="" class="'+classname+'"/>';
});
out += '</div>';
return out;
}.property('view'),
defaultTemplate: Ember.Handlebars.compile(
"<div>{{{view.buildTemplate}}}</div>"
)
});
And in template I'm using it as
{{#each myObj in myCollection}}
{{view App.MyView itemsBinding="myObj.items" otherBinding="otherProps" }}
{{/view}}
Unfortunately this way Ember instead of binding the action puts {{action myActionHere}} directly into code...
How can I bind an action instead while building dynamic template?
I'm using Ember 1.1.2
P.S. Or maybe I should use quite other approach for building this view?
There is a workaround to make this work with the view as you've laid it out here... But this is really not the ember way of doing it. If for some reason you need this kind of an approach, I'll append an answer for that, but I'm going to aim to fix the underlying issue.
Instead of doing this as shown here, you should instead have code that looks like the following directly in your JSP:
{{#each myObj in myCollection}}
<div>
{{#each item in myObj.items}}
<img {{action myActionHere}} src={{item.href}} alt='' class={{classNameFunction}}/>
{{/each}}
</div>
{{/each}}
If your reason for wanting to do this as a view is so that you can reuse this functionality without rewriting the code, take a look at partials which are specifically designed for that purpose.
I'm making a test page for a project I'm working on and I've made desired progress so far but I'm trying to create TextBoxes from a model of List being passed to the view, however, the it seems to just ignore anything I have tried.
<form id="form1" runat="server">
<input id="btnsubmit" type="submit" name="Submit" onclick="Submit" />
<div id="divControls">
<% foreach (TextBox control in (this.Model as List<TextBox>))
{
Html.Label("lblLabel", control.Text);
Html.TextBox(control.ID, control.Text, new { id = control.ID, style = "width:50", name = "txt" + control.ID });
} %>
</div>
</form>
The List isn't null in the Controller on return. I don't have a clue at what the problem could be. If I throw a Something in the for loop it executes the appropriate number of times so why isn't it creating the labels or textboxes?
At first I thought it was that I'm adding them inside a form but I removed the form tags and it still didn't work so I really have no Idea, any help would be much appreciated. I'm relatively new to MVC.
[HttpPost]
public ActionResult Index(FormCollection form)
{
List<TextBox> controls = new List<TextBox>();
foreach (String Key in form.Keys)
{
if (Key.Contains("txt"))
{
TextBox textBox = new TextBox();
textBox.ID = Key;
textBox.Text = form.GetValues(Key)[0];
controls.Add(textBox);
}
}
return View("Index", controls);
}
Here's my Action encase it's helps.
Also encase I wasn't clear enough, I am adding controls to a form at runtime using JQuery and then that Action will be part of the submit so it must send the textboxes back to the view so they are not deleted.
Like I said I'm new to the whole MVC and Asynchronous thing so If there's a better way to do this, advice would be much appreciated.
Your not printing the html
<% foreach (TextBox control in (this.Model as List<TextBox>))
{%>
<%=Html.Label("lblLabel", control.Text)%>
<%=Html.TextBox(control.ID, control.Text, new { id = control.ID, style = "width:50", name = "txt" + control.ID })%>
<% } %>
Your code is looping through the controls and the Html.whaterever is returning a string but your not doing anything with it, just discarding it.
you also don't need to return a whole TextBox object. This is probably inefficient. Just return an struct or a class containing your data
Html.Label returns a string containing a <label> tag.
You're discarding that string.
You need to write it to the page by writing <%= Html.Whatever() %>.
I'm just now starting to learn ASP.NET MVC. How would I go about creating a reusable tri-state checbox? In WebForms this would be a control, but I don't know the MVC equivalent.
Add a TriStateCheckBox (or TriStateCheckBoxFor if you use the strongly typed overloads) extension method to HtmlHelper and add the namespace of that extension method class to the namespaces section of your web.config.
As for the implementation, I'd recommend having at look at the InputExtensions source on codeplex and using that to create your own.
Limitations:
View Rendering - When rendering HTML content, there is no attribute you can possibly place on an <input type="checkbox" /> that will give it the property indeterminate.
At some point, you'll have to use JavaScript to grab the element and set the indeterminate property:
// vanilla js
document.getElementById("myChk").indeterminate = true;
// jQuery
$("#myCheck).prop("indeterminate", true);
Form Data - model binding will always be limited to what values are actually sent in the request, either from the url or the data payload (on a POST).
In this simplified example, both unchecked and indeterminate checkboxes are treated identically:
And you can confirm that for yourself in this Stack Snippet:
label {
display: block;
margin-bottom: 3px;
}
<form action="#" method="post">
<label >
<input type="checkbox" name="chkEmpty">
Checkbox
</label>
<label >
<input type="checkbox" name="chkChecked" checked>
Checkbox with Checked
</label>
<label >
<input type="checkbox" name="chkIndeterminate" id="chkIndeterminate">
<script> document.getElementById("chkIndeterminate").indeterminate = true; </script>
Checkbox with Indeterminate
</label>
<label >
<input name="RegularBool" type="checkbox" value="true">
<input name="RegularBool" type="hidden" value="false">
RegularBool
</label>
<input type="submit" value="submit"/>
</form>
Model Binding - Further, model binding will only occur on properties that are actually sent. This actually poses a problem even for regular checkboxes, since they won't post a value when unchecked. Value types do always have a default value, however, if that's the only property in your model, MVC won't new up an entire class if it doesn't see any properties.
ASP.NET solves this problem by emitting two inputs per checkbox:
Note: The hidden input guarantees that a 'false' value will be sent even when the checkbox is not checked. When the checkbox is checked, HTTP is allowed to submit multiple values with the same name, but ASP.NET MVC will only take the first instance, so it will return true like we'd expect.
Render Only Solution
We can render a checkbox for a nullable boolean, however this really only works to guarantee a bool by converting null → false when rendering. It is still difficult to share the indeterminate state across server and client. If you don't need to ever post back indeterminate, this is probably the cleanest / easiest implementation.
Roundtrip Solution
As there are serious limitations to using a HTML checkbox to capture and post all 3 visible states, let's separate out the view of the control (checkbox) with the tri-state values that we want to persist, and then keep them synchronized via JavsScript. Since we already need JS anyway, this isn't really increasing our dependency chain.
Start with an Enum that will hold our value:
/// <summary> Specifies the state of a control, such as a check box, that can be checked, unchecked, or set to an indeterminate state.</summary>
/// <remarks> Adapted from System.Windows.Forms.CheckState, but duplicated to remove dependency on Forms.dll</remarks>
public enum CheckState
{
Checked,
Indeterminate,
Unchecked
}
Then add the following property to your Model instead of a boolean:
public CheckState OpenTasks { get; set; }
Then create an EditorTemplate for the property that will render the actual property we want to persist inside of a hidden input PLUS a checkbox control that we'll use to update that property
Views/Shared/EditorTemplates/CheckState.cshtml:
#model CheckState
#Html.HiddenFor(model => model, new { #class = "tri-state-hidden" })
#Html.CheckBox(name: "",
isChecked: (Model == CheckState.Checked),
htmlAttributes: new { #class = "tri-state-box" })
Note: We're using the same hack as ASP.NET MVC to submit two fields with the same name, and placing the HiddenFor value that we want to persist first so it wins. This just makes it easy to traverse the DOM and find the corresponding value, but you could use different names to prevent any possible overlap.
Then, in your view, you can render both the property + checkbox using the editor template the same way you would have used a checkbox, since it renders both. So just add this to your view:
#Html.EditorFor(model => model.OpenTasks)
The finally piece is to keep them synchronized via JavaScript on load and whenever the checkbox changes like this:
// on load, set indeterminate
$(".tri-state-hidden").each(function() {
var isIndeterminate = this.value === "#CheckState.Indeterminate";
if (isIndeterminate) {
var $box = $(".tri-state-box[name='" + this.name + "'][type='checkbox']");
$box.prop("indeterminate", true);
}
});
// on change, keep synchronized
$(".tri-state-box").change(function () {
var newValue = this.indeterminate ? "#CheckState.Indeterminate"
: this.checked ? "#CheckState.Checked"
: "#CheckState.Unchecked";
var $hidden = $(".tri-state-hidden[name='" + this.name + "'][type='hidden']");
$hidden.val(newValue);
});
Then you can use however you'd like in your business model. For example, if you wanted to map to a nullable boolean, you could use the CheckState property as a backing value and expose/modify via getters/setters in a bool? like this:
public bool? OpenTasksBool
{
get
{
if (OpenTasks == CheckState.Indeterminate) return null;
return OpenTasks == CheckState.Checked;
}
set
{
switch (value)
{
case null: OpenTasks = CheckState.Indeterminate; break;
case true: OpenTasks = CheckState.Checked; break;
case false: OpenTasks = CheckState.Unchecked; break;
}
}
}
Alternative Solution
Also, depending on your domain model, you could just use Yes, No, ⁿ/ₐ radio buttons
ASP.NET MVC certainly doesn't provide such component, actually it simply relies on the standard elements available in HTML but you may want to check out this solution.