Addition and Removal of parts of a Dynamic Form Array - c#

I am currently playing around an idea of making views as much typed as possible. What I want to do is make a Partial View that will be capable of adding a new items into the array/list (and ofc the removal as well). I have noticed that I am basically unable to avoid JavaScript, however I wish not to hardcore anything other than URLs. Is it possible to achive this without making basically unreadable reflection for someone that gets the code in their hands after me ?
For a bit more clarification, lets assume a simple Pizza shop with the following models:
PizzaViewModel:
public class PizzaViewModel
{
[Required]
public string Name { get; set; } = "";
[Required]
public string Description { get; set; } = "";
[Required]
public IList<IngredientViewModel> Ingredients { get; set; } = new List<IngredientViewModel>();
}
IngredientViewModel:
public class IngredientViewModel
{
public int Id { get; set; }
public string? Name { get; set; }
[Required]
public int Amount { get; set; }
[Required]
public string? TypeOfAmount { get; set; }
}
What I want to be able to create is something that resembles something like this:
Form
- Name
- Description
- (Ingredient1 Name; Ingredient1 Amount)
- (Ingredient2 Name; Ingredient2 Amount)
...
- (IngredientX Name; IngredientX Amount)
- [Add Ingredient ; Remove Ingredient]
Now, to the core of my question, is it possible to write the HTML/Csharp method using PartialView or even the view itself to generate something like this?
<div id="NewItem" style="display:none">
<input type="text" name="Ingredient[#_PlaceHolder_#].Name"/>
<input type="text" name="Ingredient[#_PlaceHolder_#].Amount"/>
</div>
to be a little bit more specific:
<input type="text" name="Ingredient[#_PlaceHolder_#].Amount"/>
^ ^
I wish not to hardcore these, since they can be changed while refactoring
Additionally, regarding the removal, I am kinda wondering if its possible to do this differently than just re-indexing the whole array.
Thank you all in advance for any help.

if I understand correctly
It is possible to generate a dynamic form array in ASP.NET Core without hardcoding the index. You can use JavaScript to handle the addition and removal of items in the list, but you can also make use of Razor's support for indexed properties to keep track of the index. Here's a simple example that demonstrates how this can be done:
<form asp-action="Create">
<div class="form-group">
<label asp-for="Name"></label>
<input asp-for="Name" class="form-control" />
</div>
<div class="form-group">
<label asp-for="Description"></label>
<input asp-for="Description" class="form-control" />
</div>
<div id="ingredients">
#for (var i = 0; i < Model.Ingredients.Count; i++)
{
<div class="form-group">
<label asp-for="Ingredients[i].Name"></label>
<input asp-for="Ingredients[i].Name" class="form-control" />
<label asp-for="Ingredients[i].Amount"></label>
<input asp-for="Ingredients[i].Amount" class="form-control" />
</div>
}
</div>
<button id="add-ingredient" type="button">Add Ingredient</button>
<input type="submit" value="Create" class="btn btn-primary" />
</form>
<script>
$(document).ready(function () {
var ingredients = $('#ingredients');
var index = #Model.Ingredients.Count;
$('#add-ingredient').click(function () {
ingredients.append(
'<div class="form-group">' +
'<label for="Ingredients[' + index + '].Name"></label>' +
'<input type="text" asp-for="Ingredients[' + index + '].Name" class="form-control" />' +
'<label for="Ingredients[' + index + '].Amount"></label>' +
'<input type="text" asp-for="Ingredients[' + index + '].Amount" class="form-control" />' +
'</div>'
);
index++;
});
});
</script>
In this example, the index is managed in JavaScript and appended to the name attribute of each input field. The Razor code generates the initial form inputs using a loop, and the JavaScript code adds new inputs when the "Add Ingredient" button is clicked. To remove an item, you can add a button next to each ingredient that removes its corresponding inputs.

Related

How do I access values from string array in c#?

Can anyone help me please? I'm really stuck.
I need to be able to access the values of the following in the post method (the name and the qty) so that I can add them to the model. (the inputs will be generated by JavaScript
<div id="ingredientsMainContainer">
<input type="text" name="Ingredient[0][Name]" />
<input type="text" name="Ingredient[0][Qty]" />
<input type="text" name="Ingredient[1][Name]" />
<input type="text" name="Ingredient[1][Qty]" />
</div>
The list can be unlimited, so it could go up to Ingredient[10] or more.
I can access like this:
var test = Request.Form["Ingredient[0][Name]"];
var test2 = Request.Form["Ingredient[1][Name]"];
but I need to loop through all Ingredient[] inputs for the Name and Qty.
Thanks
A for-loop and string concatenation should work:
for (int i = 0; i < 2; i++) {
// $"Ingredient[{i}][Name]" is the same as "Ingredient[" + i + "][Name]"
Request.Form[$"Ingredient[{i}][Name]"];
}

Passing an IEnumerable to a view to generate a form and then passing form data back to controller

I'm working on a project for my office. The end result I'm looking for is that a boilerplate letter is pulled from a database, the sections that need specific input are extracted, a form is generated from those sections, the form then returns user data, and the letter is rebuilt with the user data integrated into the text of the letter.
for example, the string pulled from the database would look like this
Claim #: |string^clmNum^Claim Number: | - Ref#: |string^RefNum^Reference Number: |
and would end up like the following after it was rebuilt with user data:
Claim #: 123456 - Ref#: 789012
This is what I have working so far...
The sections between the | are pulled out, split, and loaded into an IEnumerable
My foo model is:
public class Foo
{
public string InputType {get; set;}
public string InputName {get; set;}
public string InputLabel {get; set;}
}
I pass the IEnumerable to the view with a ViewModel
public class FormBuildViewModel
{
public IEnumerable<Foo> FooProperty {get; set;}
}
I then display the input items dynamically with the following Razor markup on my view.
<form>
#{ var e = Model.FooProperty.ToList();
foreach (var subItem in e)
{
<div class="FormGroup-items">
<label>#subItem.InputLabel</label>
<input name="#subItem.ObjName" type="text" />
</div>
}
}
<..// form button stuff //..>
</form>
Which creates the following HTML:
<form>
<div class="FormGroup-items">
<label>Claim Number: </label>
<input name="clmNum" type="text" />
</div>
<div class="FormGroup-items">
<label>Reference Number: </label>
<input name="RefNum" type="text" />
</div>
<..// button stuff //..>
</form>
I have everything working up to this point. I need to take the data entered on the dynamically created form and get it back to the controller in a manner that I can index to rebuild the string.
I've tried using the #html.beginform similar to this
#using (Html.BeginForm())
{
#for(int i=0; i<Model.Count; i++)
{
#Html.CheckBoxFor(m => m[i].IsActive, new { #value = Model[i].Id })
#Html.DisplayFor(m => m[i].Name)
}
<input type="submit" value="Save" />
}
but to use #Html.BeginForm you need to know the names of the items before runtime, and it doesn't seem to work with a dynamically created form like this.
The only thing I can think of is I need to load the form data into a List< T > and return that to the controller, but I can't think of a way to get C# to allow me to initialize a List< T > and load the values in the view. I know I've got to be missing something, but I'm kinda lost at this point. Any help would be appreciated.
Are you passing your viewmodel back to your page? This seems like you are setting the viewmodel with data atleast from a 5000 foot view:
[HttpGet]
public IActionResult MyCallMethod()
{
FooProperty = getmydatafromsomewhere();
return View();
}
Then your page would have a way to build appropriately
#model My.Name.Space.MyViewModel
#using (Html.BeginForm("Action", "Controller"))
{
#foreach (var item in #Model.FooProperty)
{
<div class="FormGroup-items">
<label asp-for="item.InputType" />
<input asp-for="item.InputType" class="form-control" />
</div>
//other data
}
}
I also assume you have a post setup on the controller.
[HttpPost]
public IActionResult MyCallMethod(MyViewModel viewModel)
{
//do something with the viewmodel here
//same page, somewhere else, do something else, etc.
}
You can use some tag helpers as well for your labels and inputs if you so chose:
#Html.LabelFor(m => item.InputType, new { #class="whateverIwant" })
#Html.TextBoxFor(m => item.InputType, new { #class="form-control" })

How to bind form to Nancy

I’ve written a code snippet creates HTML form via C#. But I want the form’s fields to be bound class’s field after the form is submitted. How can I do that and check the result(if the class’s fields are filled)? Moreover, I don’t know how to test the code via Postman or Fiddle. Could you exemplify? For example, when the form is filled via a browser, I don’t know how to see the result forwarded to sent.
HTML form,
<form action="sent” method="POST"<br>
<label for="firstName">First Name Label:</label>
<input type="text" title="testTitle" name="firstName" placeholder="First Name" ><br>
<label for="lastName">Last Name Label:</label>
<input type="text" name="lastName" placeholder="Last Name" ><br>
<input type="submit" name = "noname">
</form>
Nancy,
Get("/form", parameters =>
{
// codes to construct the above HTML code
return Response.AsText(form.ToString(), "text/html");
}
// Received form information (fields)
Post("/sent”, _ =>
{
testClass receivedData = this.Bind<testClass>();
return new
{
success = true,
message = $"Record recieved First Name = {receivedData.Name}",
message2 = $"Record recieved Last Name = {receivedData.SurName}"
};
});
testClass,
public class testClass
{
public string Name { get; set; }
public string SurName { get; set; }
}
Serving your form should be easy with this working example.
Get("/api/base", _ =>
{
return Response.AsFile("content/Base.html", "text/html");
});
Make sure to add content folder and make files inside copy to output directory when newer.
make sure you close tags properly.
Also your form could just call a mapped api on submit like this
<form action="/api/submit" method="POST">
<br>
<label for="firstName">First Name Label:</label>
<input type="text" title="testTitle" id="firstName" placeholder="First Name"><br>
<label for="lastName">Last Name Label:</label>
<input type="text" id="lastName" placeholder="Last Name" ><br>
<input type="submit" id = "noname">
</form>
https://stackoverflow.com/a/53192255
Post("/api/submit”, args =>
{
//this.Request.Body;
var r = (Response)"";
testClass receivedData = this.Bind<testClass>();
r = (Response)$"Record recieved First Name = {receivedData.Name}"
+ Environment.NewLine +
$"Record recieved Last Name = {receivedData.SurName}";
r.StatusCode = HttpStatusCode.OK;
return r;
});
I Could be wrong, but I think it is because you are using a discard for the input.

asp.net core mvc model binding with jquery repeater

I am using jquery repeater in my form to dynamically add only a part of input group to form. I did try but couldn't get inputs bind to model, I went blind.
Here my model.
public class SuggestionCreateEditViewModel
{
public Guid[] DetectionId { get; set; }
public SuggestionCreateEditRepeatedModel[] Repeated { get; set; }
}
public class SuggestionCreateEditRepeatedModel
{
public Guid To { get; set; }
public string Description { get; set; }
public DateTime Deadline { get; set; }
}
Form, I removed a lot of parts of form for brevity
<div class="col-lg-9 col-md-9 col-sm-12">
<select asp-for="DetectionId" asp-items="ViewBag.AllDetections" class="m-bootstrap-select m_selectpicker toValidate"
multiple data-actions-box="true" data-width="100%"></select>
</div>
<div class="col-lg-9 col-md-9 col-sm-12 input-group date">
<input name = "Repeated.Deadline" type="text" readonly class="form-control toValidate dtDueDate" />
</div>
<div class="col-lg-12 col-md-12 col-sm-12">
<textarea name = "Repeated.Description" class="form-control toValidate txtSuggestion" type="text" >
</textarea>
</div>
after adding a new repeated section to form and before posting it to server, if I form.serailizeArray() it returns collection like as follows (what jquery form repeater dynamically shape names I believe)
{name: "DetectionId", value: "afca1b82-0455-432e-c780-08d6ac38b012"}
{name: "[0][Repeated.To][]", value: "b1176b82-1c25-4d13-9283-df2b16735266"}
{name: "[0][Repeated.Deadline]", value: "04/04/2019"}
{name: "[0][Repeated.Description]", value: "<p>test 1</p>"}
{name: "[1][Repeated.To]", value: "188806d8-202a-4787-98a6-8dc060624d93"}
{name: "[1][Repeated.Deadline]", value: "05/04/2019"}
{name: "[1][Repeated.Description]", value: "<p>test 2</p>"}
and my controller
[HttpPost]
public IActionResult CreateSuggestion(SuggestionCreateEditViewModel model, IFormFile[] documents)
{...
controller couldn't get Repeated binded, only DetectionId is binded. How should I shape my model to get the data?
Here is a working demo for with jquery.repeater.js, pay attention to this line <div data-repeater-list="Repeated"> which will format the field like name="Repeated[0][Description]"
#model TestCore.Models.SuggestionCreateEditViewModel
#{
ViewData["Title"] = "Contact";
}
<form class="repeater" asp-action="CreateSuggestion" method="post">
<!--
The value given to the data-repeater-list attribute will be used as the
base of rewritten name attributes. In this example, the first
data-repeater-item's name attribute would become group-a[0][text-input],
and the second data-repeater-item would become group-a[1][text-input]
-->
<div data-repeater-list="Repeated">
<div data-repeater-item>
<div class="col-lg-9 col-md-9 col-sm-12">
<select asp-for="DetectionId" asp-items="ViewBag.AllDetections" class="m-bootstrap-select m_selectpicker toValidate"
multiple data-actions-box="true" data-width="100%"></select>
</div>
<div class="col-lg-12 col-md-12 col-sm-12">
<textarea name="Description" class="form-control toValidate txtSuggestion" type="text">
</textarea>
</div>
</div>
</div>
<input data-repeater-create type="button" value="Add" />
<input type="submit" value="Submit"/>
</form>
#section Scripts{
<!-- Import repeater js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.repeater/1.2.1/jquery.repeater.js"></script>
<script>
$(document).ready(function () {
$('.repeater').repeater({
// (Optional)
// start with an empty list of repeaters. Set your first (and only)
// "data-repeater-item" with style="display:none;" and pass the
// following configuration flag
initEmpty: true,
// (Optional)
// "show" is called just after an item is added. The item is hidden
// at this point. If a show callback is not given the item will
// have $(this).show() called on it.
show: function () {
$(this).slideDown();
},
// (Optional)
// "hide" is called when a user clicks on a data-repeater-delete
// element. The item is still visible. "hide" is passed a function
// as its first argument which will properly remove the item.
// "hide" allows for a confirmation step, to send a delete request
// to the server, etc. If a hide callback is not given the item
// will be deleted.
hide: function (deleteElement) {
if (confirm('Are you sure you want to delete this element?')) {
$(this).slideUp(deleteElement);
}
},
// (Optional)
// Removes the delete button from the first list item,
// defaults to false.
isFirstItemUndeletable: true
})
});
</script>
}
By the looks of things, the controller cannot bind the repeater properties back to your view model because the naming of the posted content does not match the naming in your view model (as Topher mentioned).
The DetectionId is named correctly though because the name of the property matches and its not an array.
To resolve an array we need to make sure we include the property name in the form as well as an index so that mvc model binding knows where to bind the result to.
With that, can you try changing the format of the name to:
Repeated[0].To
That should match up with your controller and correctly bind.
For more info on binding, please see this.

Pass Selected Dropdown Value to Controller using a Button

In the example below when clicking the button the value selected in the dropdownlist should be passed to the controller, but its not. How can I pass the value?
View:
#model BillingModel
...
<select id="ddl" asp-for="SelectedCompanyID" asp-items="Model.Companies" class="form-control"></select>
<a asp-action="Create" asp-controller="Invoice" asp-route-id="#Model.SelectedCompanyID" class="btn btn-primary btn-sm"></span> Create Invoice</a>
....
Model:
public class BillingModel
{
public int SelectedCompanyID { get; set; }
public SelectList Companies { get; set; }
}
Your link is using razor code to specify the route id value which is server side code. It does not change on the client side just because a option is selected.
Either use a form that makes a GET and submits the option value
<form asp-controller="Invoice" asp-action="Create" method="get">
<select id="ddl" asp-for="SelectedCompanyID" asp-items="Model.Companies" class="form-control"></select>
<input type="submit" value="Create Invoice" /> // you can style this to look like your link if you want
</form>
Note that this will generate the url with a query string value for the id, not a route value (i.e. it will generate ../Invoice/Create?id=1, not ../Invoice/Create/1)
Alternatively, you could use javascript/jquery to make the redirect by building a url based on the selected option
<a id="create" href="#" class="btn btn-primary btn-sm">Create Invoice</a>
$('#create').click(function() {
var baseUrl = '#Url.Action("Create", "Invoice")';
location.href = baseUrl + '/' + $('#SelectedCompanyID').val();
}

Categories