mvc3 model bind to multiple lists of the same type - asp.net-mvc-3

suppose...
in the razor view we have a model of Object A
#Model Object A
Object A is an instance of class A, which has multiple instances of Class B, which have a list of type C
Class A
public B Object1
public B Object2
Class B
public List<C> List1
There is an EditorView for Object B. There is a larger view for Object A in which each object B use an object B EditorView.
#Model Object B
...do things with list in object B
So, when the view renders, there are multiple Editor Views of class B present. What code can I use in the editorview to add additional C to the list List1 in B Object1 without affecting the list List1 in B Object2.
#Model Object B
... would like to add to this list and have it post back, and only effect the b it in. I don't know how to reference this object's master to add to this list exculsively
I looked at http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx a few times. Its like having multiple...
<input type="text" name="[0].Title" value="Curious George" />
<input type="text" name="[0].Author" value="H.A. Rey" />
<input type="text" name="[0].DatePublished" value="2/23/1973" />
<input type="text" name="[0].Title" value="Curious George" />
<input type="text" name="[0].Author" value="H.A. Rey" />
<input type="text" name="[0].DatePublished" value="2/23/1973" />
on the same page and wanting them to post back to their correct masters on the postback. Thats what I want to do. I want to dynamically add
<input type="text" name="...." value=""/>
but I'm running into problems with what do I put in the name spot to make it happen. Doing the #Html.EditorFor, and #Html.HiddenFor seem to work in that they modelbind to the correct parent, but they do not allow me to add additional input to the list.
My intuition direction - do something with reflection (or not) to get the name of the object of the master, use this to create a proper name?
Ideas or Code samples please. Thanks.

Using Html.EditorFor should give you the format to use. They need to be sequential and you'll need to either count the existing items (-1) to get your new [x] index to use in the name or parse the last elements name using a regex or string parse and extract its index, assuming you are allowing gaps in the sequence, which means you'll also need a custom binder if you want gaps.

okay, solution may be to use the editorfor a different variable to get the general name of the parent variable, and use jquery/js to parse this name and then dynamically insert the input type=file with a name based on this.

Related

Alpine.js - sync computed field back to model

I have a form with some dynamic inputs, and I need to define a readonly computed field, which will have a value count from other fields. This I am doing using x-bind:value='myFormulaFunction()' and it shows the value on the field correctly, and it even updates live if the variables change.
The problem is how to sync this count value back to the model. Even when I define x-model on the field, it doesn't update with the value. And apparently no functions like #change and #input are triggered on the value update either, neither does x-effect.
I made a quick fiddle here: https://jsfiddle.net/janzikmund/cm3zqeyj/12/
I know I should probably make a get function to achieve this, but I am doing this within x-for of dynamically generated array and having the logic on the field itself would just be much more convenient. Anything I am missing?
For that purpose you can use the $watch magic method that actually supports watching for multiple variables. So when one of the variables changes, it updates the value of count.sum.
Note: $watch checks for each child attribute, so don't watch the whole count variable because it will cause an infinite loop.
<script defer src="https://unpkg.com/alpinejs#3.x.x/dist/cdn.min.js"></script>
<div x-data="{ count: { f1: 0, f2: 0, sum: 0} }"
x-init="$watch('count.f1,count.f2', () => {count.sum = !isNaN(count.f1) && !isNaN(count.f2) ? parseInt(count.f1) + parseInt(count.f2) : 0})">
<input type="number" x-model="count.f1">
<input type="number" x-model="count.f2">
<input type="text" readonly x-model="count.sum">
<h2 x-text="count.sum"></h2>
</div>

ASP Classic : Put a "variable" in a "Request.Form()"

I have a form that will send an array of data to an ASP page.
Let's say this array is called "matrix".
Usually, on the ASP receiving the form, I will write this out to retrieve the form inputs from the array "matrix".
Request.Form("matrix[]")(i) where i = 1, 2, 3 which are the elements in the array.
Let's say I want to do make a variable like this
a="matrix"
and I want to use this variable a and put it into the request form, instead of writing "matrix", so that it would look something like this
Request.Form(a[])(i)
How can it be done? For now, all my attempts are showing blank. e.g. when I try to make them appear on the page with response.write, nothing shows up.
Please help me or let me know if it cannot be done, I've been spending hours on this.
Request.Form("matrix[]") is taking a string value of "matrix[]" not an array of strings called "matrix".
So you need to do either
a = "matrix[]"
Request.Form(a)(i)
or
a = "matrix"
Request.Form(a & "[]")(i)
Unlike PHP which requires adding square brackets, in classic ASP you just have to give the same name to the elements you want to be combined into an array.
The HTML should be:
<input type="text" name="matrix" />
<input type="text" name="matrix" />
<input type="text" name="matrix" />
Then you can iterate over the submitted values like this:
For x=1 To Request.Form("matrix").Count
Response.Write("Value of matrix #" & CStr(x) & "is: " & Request.Form("matrix").Item(x))
Next
Note that all elements are included, even if user left them empty.

How can I wrap a div around existing elements?

I'm trying to parse an existing document and modify it by wrapping a div around some existing form elements.
HTML form looks a bit like this:
<form>
<label for="username">Username:</label>
<input name="username" type="text" />
<label for="password">Password:</label>
<input name="password" type="password" />
</form>
I can parse the document OK with Nokogiri and i'm aware of the wrap method but i'm struggling to grasp how to select both the label and input tags in one go and then wrap a div around these. So the result I am looking for is:
<form>
<div class="form-group">
<label for="username">Username:</label>
<input name="username" type="text" />
</div>
<div class="form-group">
<label for="password">Password:</label>
<input name="password" type="password" />
</div>
</form>
I have tried various XPaths / CSS selectors and can create a nodeset of just labels/inputs or all of the elements of the whole form. Is there any way to achieve this modification?
A single XPath expression can only return a single collection of nodes, so in order to achieve what you want you will need to make several queries, one for each label – input pair.
You can select an individual pair with something like this, assuming the markup is well behaved (i.e each input has a label before it):
//label[1] | //label[1]/following-sibling::input[1]
This will select the first label and the following input. However you want to select all such pairs. One way would be to first select all the label nodes, and then for each label select it and the following input.
labels = doc.xpath("//form/label")
labels.each do |l|
nodes = l.xpath(". | ./following-sibling::input[1]")
# nodes now contains a label-input pair...
end
I don’t think the wrap method will work to add a div element as an ancestor to each pair, as it will add the element to each member of the nodeset. You will probably have to move them manually, something like
labels = doc.xpath("//form/label")
labels.each do |l|
# Select this node and its neighbour.
nodes = l.xpath(". | ./following-sibling::input[1]")
# Create the new element, and add it before the label.
div = Nokogiri::XML::Node.new('div', l.document)
l.before(div)
# Move each of the pair onto this new element.
nodes.each do |n|
div.add_child(n)
end
end
Note that this method doesn’t move any text nodes, so you may find the whitespace of your document changes a bit.

How do I select child elements of any depth using XPath?

Suppose I have this (simplified):
<form id="myform">
<!-- some input fields -->
<input type="submit" value="proceed"/>
</form>
Then I can select the submit button by XPath //form[#id='myform']/input[#type='submit']. Great.
However, my templates might change and I want to be flexible in the depth in which the submit button is located. It might be put in a table, like this:
<form id="myform">
<!-- some input fields -->
<table><tr><td>
<input type="submit" value="proceed"/>
</td></tr></table>
</form>
I know I can select elements which are grandchildren, but I can't select grand-grand-grand-...-childeren of any depth. E.g.:
//form[#id='myform']/*/input[#type='submit'] only selects grand-children, no further depths.
//form[#id='myform']/*/*/input[#type='submit'] only selects grand-grand-children, no further or less depths.
//form[#id='myform']/**/input[#type='submit'] is not valid.
So, how do I select this submit button reliably without using element IDs?
You're almost there. Simply use:
//form[#id='myform']//input[#type='submit']
The // shortcut can also be used inside an expression.
If you are using the XmlDocument and XmlNode.
Say:
XmlNode f = root.SelectSingleNode("//form[#id='myform']");
Use:
XmlNode s = f.SelectSingleNode(".//input[#type='submit']");
It depends on the tool that you use. But .// will select any child, any depth from a reference node.
//form/descendant::input[#type='submit']

EditorFor with Collections and Name attribute

I am trying to create a form that allows the editing of multiple rows of data. I have no problem looping through and getting input boxes to render...I just cannot get the name attributes to output correctly.
I know that in order to submit a collection you need to post back an indexed name where the index is sequential starting at 0.
<input name="Books[0].Title" />
<input name="Books[1].Title" />
and so on...
Now, I can get the EditorFor function to output my proper name with given the following loop code
#For n = 0 To (Model.Books.Count - 1)
#Html.EditorFor(Function(m) Model.Books.Item(n).Title)
Next
giving me
<input name="Books[0].Title" />
<input name="Books[1].Title" />
and so on...
My problem is VS shows the following warning
Using the iteration variable in a lambda expression may have unexpected results. Instead, create a local variable within the loop and assign it the value of the iteration variable.
Yet when I change the loop to
#For n = 0 To (Model.Books.Count - 1)
Dim item = Mode.Books.Item(n)
#Html.EditorFor(Function(m) item.Title)
Next
I get
<input name="$VB$Local_item.Title" />
<input name="$VB$Local_item.Title" />
and so on...
Any thoughts? Should I just ignore the warning?
Thanks.
Jason
MVC works by actually breaking apart the lambda expression, and seeing what it's made of. It doesn't just execute the lambda and get the result. So you need to actually use the model parameter in the lambda for it to work. This should do it for you:
#For n = 0 To (Model.Books.Count - 1)
Dim index = n
#Html.EditorFor(Function(m) m(index).Title)
Next

Resources