Add Elements to Form Dynamically with JS - javascript

I have created 15 fields in my MySQL Table and would like to give the end user the option of using a form to add up to that many items. However, to keep the interface clean, I would like to only present them with maybe 2-3 Textboxes and give them a button that would allow them to add more should they need it.
I don't believe adding the textboxes to the form using Javascript would be an issue, but I am confused as to how to process it exactly once I have submitted the POST Data to the form handler. Can anyone shed some light on the best way to go about this?

If you have to use a normal POST variable containing all the form values, you should be able to do something like this:
When generating the textboxes with the server language and/or javascript, the way they are sent to the server is with their name attribute. If you provide a consistent way of naming the elements, you can "combine" things with numbers. For example, if you provide 2 textboxes every time the user clicks "Add" (one for "foo" and one for "bar"), then you can increment the number at the end to make sure they match.
<input type="text" name="foo1" /><input type="text" name="bar1" />
<input type="text" name="foo2" /><input type="text" name="bar2" />
and so on
Then on the server, you need to find every item in the POST variable that starts with "foo" and "bar"
for (item in POST) {
if (item startswith "foo") {
// Extract the number at the end, and find the related "bar"
}
}

Assuming that you are using ASP.NET MVC for web application, along with jQuery for client side framework.
Let's more assume that you have a model like this:
public class Gift
{
public string Name { get; set; }
public double Price { get; set; }
}
Your initial action and data could be like this:
public ActionResult Index()
{
var initialData = new[] {
new Gift { Name = "Tall Hat", Price = 39.95 },
new Gift { Name = "Long Cloak", Price = 120.00 },
};
return View(initialData);
}
Whereas, your view could be this:
<h2>Gift List</h2>
What do you want for your birthday?
<% using(Html.BeginForm()) { %>
<div id="editorRows">
<% foreach (var item in Model)
Html.RenderPartial("GiftEditorRow", item);
%>
</div>
<input type="submit" value="Finished" />
<% } %>
And partial view for gift editor could be this:
<div class="editorRow">
<% using(Html.BeginCollectionItem("gifts")) { %>
Item: <%= Html.TextBoxFor(x => x.Name) %>
Value: $<%= Html.TextBoxFor(x => x.Price, new { size = 4 }) %>
<% } %>
</div>
The key is "BeginCollectionItem" helper method, which is not standard in ASP.NET MVC. It will generate some keys for variable length models. I will add a link to files later.
Your Handler would be like this:
[HttpPost]
public ActionResult Index(IEnumerable<gift> gifts)
{
// To do: do whatever you want with the data
}
You get a list of gifts with this approach, filled with values in textboxes.
To add one more item, you need to send an ajax request to this view:
Hope it helps
Source: http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/
Download: http://blog.codeville.net/blogfiles/2010/January/ListEditorDemo.zip

Related

How can I pass multiple radio button value as a Parameter?

I have the following code to display IList<string> array in radio button. How can I pass prior month/s?
In My view i have the following code, currently I only display the selected radio button value
foreach (var record in Model.Months.Select(x=>$"{x.Substring(4,2)}/{x.Substring(0,4)}").OrderByDescending(x=>x))
{
<div class="radio col-12">
<div class="row">
<div class="col-5" style="">
#Html.RadioButton("MonthsAvailable", record, true, new { id = record, #class = "m-r" })<label>#record</label>
</div>
</div>
</div>
}
My Model looks like as follows
public class MonthsAvailable
{
public List<string> Months{ get; set; }
}
My action receives
public async Task<IActionResult> MonthsAvailable(List<string> monthsAvailable)
{
...
}
My radio button looks like as follows
When I select 062020 pass only 062020 to the controller,
When I select 072020 pass 062020 and 072020,
when I select 082020 pass 062020, 072020 and 082020
when I select 092020 pass 062020, 072020, 082020 and 092020 and etc
Have you tried using a LinkedList<> instead of List<> ?
Linked list will give you access to elements prior to or after the one selected.
Check the Microsoft documents on the usage:
https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.linkedlist-1?view=net-5.0
I don't think that you need to pass the whole array. Enough to pass selected data. Inside of the action you can calculate what fields do you need.
public async Task<IActionResult> MonthsAvailable(string monthAvailable)
{
....
}

Dynamic binding of controls in ASP.Net MVC [duplicate]

I have added a button in my view. When this button is clicked partial view is added. In my form I can add as much partial view as I can. When Submitting this form data I am unable to send all the partial view data to controller.
I have made a different model having all the attributes and I have made a list of that model to my main model. Can anyone please give me some trick so that I can send all the partial view content to my controller?
In My View
<div id="CSQGroup">
</div>
<div>
<input type="button" value="Add Field" id="addField" onclick="addFieldss()" />
</div>
function addFieldss()
{
$.ajax({
url: '#Url.Content("~/AdminProduct/GetColorSizeQty")',
type: 'GET',
success:function(result) {
var newDiv = $(document.createElement("div")).attr("id", 'CSQ' + myCounter);
newDiv.html(result);
newDiv.appendTo("#CSQGroup");
myCounter++;
},
error: function(result) {
alert("Failure");
}
});
}
In My controller
public ActionResult GetColorSizeQty()
{
var data = new AdminProductDetailModel();
data.colorList = commonCore.getallTypeofList("color");
data.sizeList = commonCore.getallTypeofList("size");
return PartialView(data);
}
[HttpPost]
public ActionResult AddDetail(AdminProductDetailModel model)
{
....
}
In my Partial View
#model IKLE.Model.ProductModel.AdminProductDetailModel
<div class="editor-field">
#Html.LabelFor(model => model.fkConfigChoiceCategorySizeId)
#Html.DropDownListFor(model => model.fkConfigChoiceCategorySizeId, Model.sizeList, "--Select Size--")
#Html.ValidationMessageFor(model => model.fkConfigChoiceCategorySizeId)
</div>
<div class="editor-field">
#Html.LabelFor(model => model.fkConfigChoiceCategoryColorId)
#Html.DropDownListFor(model => model.fkConfigChoiceCategoryColorId, Model.colorList, "--Select Color--")
#Html.ValidationMessageFor(model => model.fkConfigChoiceCategoryColorId)
</div>
<div class="editor-field">
#Html.LabelFor(model => model.productTotalQuantity)
#Html.TextBoxFor(model => model.productTotalQuantity)
#Html.ValidationMessageFor(model => model.productTotalQuantity)
</div>
Your problem is that the partial renders html based on a single AdminProductDetailModel object, yet you are trying to post back a collection. When you dynamically add a new object you continue to add duplicate controls that look like <input name="productTotalQuantity" ..> (this is also creating invalid html because of the duplicate id attributes) where as they need to be <input name="[0].productTotalQuantity" ..>, <input name="[1].productTotalQuantity" ..> etc. in order to bind to a collection on post back.
The DefaultModelBinder required that the indexer for collection items start at zero and be consecutive, or that the form values include a Index=someValue where the indexer is someValue (for example <input name="[ABC].productTotalQuantity" ..><input name="Index" value="ABC">. This is explained in detail in Phil Haack's article Model Binding To A List. Using the Index approach is generally better because it also allows you to delete items from the list (otherwise it would be necessary to rename all existing controls so the indexer is consecutive).
Two possible approaches to your issue.
Option 1
Use the BeginItemCollection helper for your partial view. This helper will render a hidden input for the Index value based on a GUID. You need this in both the partial view and the loop where you render existing items. Your partial would look something like
#model IKLE.Model.ProductModel.AdminProductDetailModel
#using(Html.BeginCollectionItem())
{
<div class="editor-field">
#Html.LabelFor(model => model.fkConfigChoiceCategorySizeId)
#Html.DropDownListFor(model => model.fkConfigChoiceCategorySizeId, Model.sizeList, "--Select Size--")
#Html.ValidationMessageFor(model => model.fkConfigChoiceCategorySizeId)
</div>
....
}
Option 2
Manually create the html elements representing a new object with a 'fake' indexer, place them in a hidden container, then in the Add button event, clone the html, update the indexers and Index value and append the cloned elements to the DOM. To make sure the html is correct, create one default object in a for loop and inspect the html it generates. An example of this approach is shown in this answer
<div id="newItem" style="display:none">
<div class="editor-field">
<label for="_#__productTotalQuantity">Quantity</label>
<input type="text" id="_#__productTotalQuantity" name="[#].productTotalQuantity" value />
....
</div>
// more properties of your model
</div>
Note the use of a 'fake' indexer to prevent this one being bound on post back ('#' and '%' wont match up so they are ignored by the DefaultModelBinder)
$('#addField').click(function() {
var index = (new Date()).getTime();
var clone = $('#NewItem').clone();
// Update the indexer and Index value of the clone
clone.html($(clone).html().replace(/\[#\]/g, '[' + index + ']'));
clone.html($(clone).html().replace(/"%"/g, '"' + index + '"'));
$('#yourContainer').append(clone.html());
}
The advantage of option 1 is that you are strongly typing the view to your model, but it means making a call to the server each time you add a new item. The advantage of option 2 is that its all done client side, but if you make any changes to you model (e.g. add a validation attribute to a property) then you also need to manually update the html, making maintenance a bit harder.
Finally, if you are using client side validation (jquery-validate-unobtrusive.js), then you need re-parse the validator each time you add new elements to the DOM as explained in this answer.
$('form').data('validator', null);
$.validator.unobtrusive.parse($('form'));
And of course you need to change you POST method to accept a collection
[HttpPost]
public ActionResult AddDetail(IEnumerable<AdminProductDetailModel> model)
{
....
}

Is there any way to call JavaScript in an MVC4 ActionLink for one of the RouteValue parameters?

I have a drop down list (DropDownListFor) and an ActionLink on my page. Basically, the problem I'm having is I'm trying to capture the selected value from my drop down list and passing that into my ActionLink as an ID. Here's my code:
#Html.DropDownListFor(x => x.Capsules, new SelectList(Model.Capsules, "pk", "name", "pk"))
<br />
#Html.ActionLink("Submit", "Create",
new { controller = "Process", id = /*JavaScript here to get the selected ID for the DropDownList above*/ },
new { data_role = "button" })
For what I'm trying to accomplish, is there a way to embed JavaScript into my Html.ActionLink call? If there's not a way, or if it's not recommended, could you please advise of another solution to solve this problem? Thanks in advance!
You can do this via intercepting the link using javascript Darin has posted an example of this.
However, it looks like you're trying to submit some values using an ActionLink, and you're probably better off creating a viewmodel which holds all the values you want, and then posting everything using a submit button. This allows you to post more data than just the ID, prevents you from being dependent on Javascript, and keeps all of the code server side instead of mixing and matching.
Judging by the small code you've posted - you already have a model, probably some strongly typed entity, and it has a property called Capsules.
In your controller, create the view model which holds the view's data:
public class YourViewModel
{
YourModel YourModel { get; set; }
public int CapsuleId { get; set; }
}
Then your view:
#using( #Html.BeginForm( "Create", "Process" ) )
{
#Html.DropDownListFor(m=> m.CapsuleId, new SelectList(Model.YourModel.Capsules, "pk", "name", "pk"))
<input type="submit">
}
Then your controller action to handle this:
[HttpPost]
public ActionResult Create( YourViewModel model )
{
var id = model.CapsuleId;
// do what you're going to do with the id
return View();
}
You can put dummy value for the id parameter like this :
#Html.ActionLink("Submit", "Create",
new { controller = "Process", id = "dummy" },
new { data_role = "button" })
Then replace that value when the link is clicked.
// Assuming your link's id is `submit`, and the dropdown's id is `capsules`
$('#submit').click(function() {
var id = $('capsules').val();
$(this).href = $(this).href.replace('dummy', id);
});

HTML form submit a single item from a collection

I have a partial view with a view model that has a collection of sellers. I loop over all of the sellers to render the list. Here is the view model:
public class SellersPartialViewModel
{
public IList<OrderViewModel> Sellers { get; set; }
}
In the partial view I'm using Html.BeginCollectionItem("Sellers") when I loop through the collection and here is my code for the partial (FYI I've stripped away a lot of useless code that doesn't need to be seen):
<div id="sellers-list">
#{
var i = 0;
while (i < Model.Sellers.Count) {
var seller = Model.Sellers[i];
using (Ajax.BeginForm(MVC.Video.PurchaseShares(), purchaseSharesAjaxOptions, new { #class = "seller-form", id = "seller-form-" + i })) {
#using(Html.BeginCollectionItem("Sellers")) {
#Html.TextBoxFor(m => seller.Qty, new { #class = "buyer-qty" })
#Html.ValidationMessageFor(m => seller.Qty)
<input class="buyer-qty-submit" name="Qty" type="hidden" value="" />
<button type="submit">Buy</button>
}
}
}
i++;
}
}
</div>
This works fine for rendering the partial and getting the client-side validation working
however I want each seller to have the inputs named qty and orderId for a controller action called PurchaseShares(int orderId, int qty).
The only problem is the form is being submitted with the odd GUID like Sellers[5b5fd3f2-12e0-4e72-b289-50a69aa06158].seller.Qty which I understand is correct for submitting collections but I don't need to do that.
Right now I have some Javascript that is updating the class="buyer-qty" with whatever they select and it works fine but there has got to be a better way of doing this, no?
Thanks
Why are you using the Html.BeginCollectionItem helper if you don't want to submit collections?
You could have a partial representing your Order collection item (_Order.cshtml):
#model OrderViewModel
#Html.TextBoxFor(m => m.Qty, new { #class = "buyer-qty" })
#Html.ValidationMessageFor(m => m.Qty)
And in your main view simply loop through your collection property and render the partial for each element:
#model SellersPartialViewModel
<div id="sellers-list">
#foreach (var seller in Model.Sellers)
{
using (Ajax.BeginForm(MVC.Video.PurchaseShares(), purchaseSharesAjaxOptions, new { #class = "seller-form" }))
{
#Html.Partial("_Order", seller)
<button type="submit">Buy</button>
}
}
</div>
Now your controller action you are submitting to could directly work with the corresponding view model:
[HttpPost]
public ActionResult PurchaseShares(OrderViewModel order)
{
...
}
because:
[HttpPost]
public ActionResult PurchaseShares(int orderId, int qty)
{
...
}
kinda looks uglier to me but it would also work if you prefer it.
Also please notice that I have deliberately removed the Qty hidden field shown in your code as it would conflict with the input element with the same name. Also don't forget to include an input field for the orderId argument that your controller action is expecting or when you submit it could bomb. Also you could send it as part of the routeValues argument of the Ajax.BeginForm helper if you don't want to include it as an input field.

Client-server searching with jQuery and MVC

I have a view with two drop downlist which is used to search the description. The list of results are displayed in another view for now. I wish to generate the results in the same search view. I assume some AJAX or Jquery can be used to sort this out but don't know how. So, in this case how can the search result be displayed in the same view page?
Moreover, i have some doubt in Search controller. I want at least one drop down list to be selected (Both drop down list shouldn't be allowed null). How can i validate that part?
View
#using (Html.BeginForm("Search","Work",FormMethod.Get))
{
<fieldset>
<legend>Search</legend>
<div class="editor-label">
#Html.LabelFor(model => model.JobTypeID, "Job Type")
</div>
<div class="editor-field">
#Html.DropDownList("JobTypeID", "Select Job Type")
</div>
<div class="editor-label">
#Html.LabelFor(model => model.JobPriorityID, "Job Priority")
</div>
<div class="editor-field">
#Html.DropDownList("JobPriorityID", "Select Job Priority")
</div>
<p>
<input type="submit" value="Search" />
</p>
</fieldset>
}
Controller:
[HttpGet]
public ActionResult Search(int? jobtypeid, int? jobpriorityid)
{
var vJobDescriptions = new List<JobDescription>();
if (jobtypeid != null && jobpriorityid != null )
{
vJobDescriptions = (from description in db.JobDescriptions
where (description.JobTypeID == jobtypeid && description.JobPriorityID == jobpriorityid)
select description).ToList();
}
else if (jobtypeid == null && jobpriorityid != null)
{
vJobDescriptions = (from description in db.JobDescriptions
where (description.JobPriorityID == jobpriorityid)
select description).ToList();
}
else if (jobtypeid != null && jobpriorityid == null)
{
vJobDescriptions = (from description in db.JobDescriptions
where (description.JobTypeID == jobtypeid)
select description).ToList();
}
else
{
vJobDescriptions = (from description in db.JobDescriptions
select description).ToList();
}
return View(vJobDescriptions);
}
One possibility is to use an Ajax.BeginForm instead of a normal form (don't forget to include jquery.js and jquery.unobtrusive-ajax.js scripts to your page):
#using (Ajax.BeginForm("Search", "Work", new AjaxOptions { UpdateTargetId = "results" }))
then you could have a placeholder for the results that we specified in the UpdateTargetId:
<div id="results"></div>
Now all that's left is to have your Search controller action return a PartialView and pass it the model containing the results of the search:
public ActionResult Search(int? jobtypeid, int? jobpriorityid)
{
var model = ...
return PartialView("_Result", model);
}
and of course the corresponding _Result.cshtml partial:
#model IEnumerable<MyViewModel>
...
Moreover, i have some doubt in Search controller. I want at least one
drop down list to be selected (Both drop down list shouldn't be
allowed null). How can i validate that part?
I would recommend you FluentValidation.NET but if you don't want to use third party libraries you could write a custom validation attribute that will perform this validation and then decorate one of the 2 view model properties that are bound to your dropdown lists with it.
Unfortunately if you decide to go the AJAX route, you will have to be able to display validation errors coming from the server in case there was something wrong. So it is the entire form that has to be put inside the partial.
Another approach that you could use is to simply reload the entire page using a standard form without AJAX. The results will be part of your initial view model as a collection property which will initially be null and after performing the search you will populate it with the results. Then inside the view you will test if the property is not null and if it isn't include the Partial that will take care of rendering the results:
#using (Html.BeginForm("Search", "Work", FormMethod.Get))
{
...
}
<div id="results">
#if (Model.Results != null)
{
#Html.Partial("_Results", Model.Results)
}
</div>
A basic approach to this would be to place the markup for your search results into a partial view, and return that from your Search ActionMethod. This would require you to change the last line of your search method to
return Partial(vJobDescriptions)
In your client-side script, you would do something along the lines of this:
var data = $("form").serialize();
$.get("/Search", data)
.complete(function(results) {
$("form").replace(results) };
With regards to the validation aspect you're looking for, I would consider separating your read model from the search command parameters.
public ActionResult Search(SearchModel search)
{
if (!ModelState.IsValid) // return view w/ invalid model
}
where your search params model would be along these lines:
[CustomValidation(typeof(SearchModel),
"OneNotNullValidator",
"One option must be selected"]
public class SearchModel
{
public int? JobTypeID { get; set;}
public int? JobPriorityID { get; set;}
public bool OneNotNullValidator()
{
return JobTypeID.HasValue || JobPriorityID.HasValue;
}
}
The CustomValidation attribute I've applied to the class may not be 100% correct on the specific syntax and name(s), but I hope the gist of it comes across.

Categories