ASP.Net MVC Razor - Javascript function routing issue - javascript

I'm experiencing some issues with a cascade dropdownlist (ASP.Net MVC Razor), i know there are many questions answered on this topic but mine is quite different and i couldn't find an answer after hours of search.
I have my PessoaController (PersonController) which is inside an area named requerente.
I have two dropdownlists one for the provinces (Província) and other for the suburbs (Município).
Belown is the code i have on my view:
<div class="editor-label">
#Html.LabelFor(model => model.ProvinciaId, "Província")
</div>
<div class="editor-field">
#Html.DropDownListFor(Model => Model.ProvinciaId, new SelectList(ViewBag.ProvinciaId as System.Collections.IEnumerable, "id", "Valor"),
"Seleccione", new { id = "ddlProvincia" })
#Html.ValidationMessageFor(model => model.ProvinciaId)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.MunicipioId, "Município")
</div>
<div class="editor-field">
#Html.DropDownListFor(Model => Model.MunicipioId, new SelectList(ViewBag.MunicipioId as System.Collections.IEnumerable, "Id", "Valor"),
"Seleccione", new { id = "ddlMunicipio" })
#Html.ValidationMessageFor(model => model.MunicipioId)
</div>
and this is the javascript code that calls the function on my controller
<script type="text/javascript">
$(document).ready(function () {
$("#ddlProvincia").change(function () {
var selectedProvinceId = $(this).val();
$.getJSON("../pessoa/LoadMunicipiosByProvinceId", { provinciaId: selectedProvinceId },
function (municipioData) {
var select = $("#ddlMunicipio");
select.empty();
select.append($('<option/>', {
value: 0,
text: "Escolha o Municipio"
}));
$.each(municipioData, function (index, itemData) {
select.append($('<option/>', {
value: itemData.Value,
text: itemData.Text
}));
});
});
});
});
</script>.
This code works perfectly (on the create view), when i want to add a new person i select the province and it loads the suburbs according to the selected province, so far so good.
To avoid having to duplicate the javascript function by copying the code to the edit view, i decided to move my javascript function to a partial view and include it in both the create and edit view.
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
#Html.Partial("LoadMunicipiosScript");
}
The code still runs perfectly on the create view, but on the edit view, whenever i select a different province i get a 404 error because the function cannot be found.
From firebug i can see what the problem is, my create view uses the right path to invoke the function in the controller.
localhost:57934/requerente/pessoa/LoadMunicipiosByProvinceId?provinciaId=200003
How ever on my edit view somehow the name of my controller is added twice to the path, and thus results in a 404.
localhost:57934/requerente/pessoa/pessoa/LoadMunicipiosByProvinceId?provinciaId=200003
I know that duplicating the code and tweaking the url would get me up and running but does anyone has any idea why the inconsistent behavior, having into count that both views (create and edit) are on the same folder and are executing the same shared function.

The problem looks like you have the same relative URL ../pessoa/LoadMunicipiosByProvinceId loading from two different pages which ends up referring to a different place in each. This would happen if, for example, your two URLs are (note the different base folder):
localhost:57394/requerente/foo
localhost:57394/requerente/pessao/bar
Instead, you can use Url.Action to dynamically generate the target URL. I do not know your routing scheme, but for example:
$.getJSON("#Url.Action("LoadMunicipiosByProvinceId", "pessoa")",
{ provinciaId: selectedProvinceId },
function(data) {/*...*/});

Related

How add new values in drop-down list using plugin "selectory" jquery

I need some help. How can I add new values in code to the list if I use a plugin from jquery. I wrote this code, but the list is empty, although the values are passed to the view. This is probably due to the fact that I am referring to the id of the div tag, but the plugin did not work differently. Help please
<html>
<main>
<form action="#">
<div class="form-group col-xs-12 col-sm-4" id="example-2"> </div>
</form>
</main>
<script>
$('#example-2').selectivity({
items: ['Amsterdam', 'Antwerp'],
multiple: true,
placeholder: 'Type to search a city'
});
function addOption() {
var ul = document.getElementById("#example-2");
for (var item in #ViewBag.List)
{
var value = item;
}
var newOption = new Option(value, value);
ul.options[ul.options.length] = newOption;
}
</script>
</html>
result of code from answer 1
The documentation of the selectivity library covers how to add new options to the dropdown.
The main issue you have is that the output from #ViewBag.List won't be in a format that JS can understand. I would suggest formatting it as JSON before outputting it to the page, then the JS can access this as a standard object, though which you can loop.
// initialisation
$('#example-2').selectivity({
items: ['Amsterdam', 'Antwerp'],
multiple: true,
placeholder: 'Type to search a city'
});
// add options, somewhere else in your codebase...
const $list = $('#example-2')
const options = #Html.Raw(Json.Encode(ViewBag.List));
options.forEach((option, i) => {
$list.selectivity('add', { id: i, text: option })
});
Note that for this to work the JS code which reads from the ViewBag needs to be placed somewhere the C# code will be executed, ie. in a .cshtml file, not in a .js file.

MVC5 #Html.EditorFor: Second Attribute Problem

I am having a minor frustration with the #html.EditorFor in MVC5, in a "Create View"
Basically, I have a drop down that the user selects information from. On Change, the value of the drop down is passed (via javascript) to the relative #Html.EditorFor, to be saved in the table upon submission of the view.
This is my view code for the DropDown (The dropdown itself is populated by the index controller, and works perfectly)
#Html.DropDownList("testList", null, "Select Delivery Unit", new { htmlAttributes = new { #class = "form-control" } })
This is my view code for the EditorFor:
#Html.EditorFor(model => model.DeliveryUnitID, null, "myunit", new { htmlAttributes = new { #class = "form-control" } })
Although the JavaScript is working properly, I will include that code as well, just in case it's needed:
<script type="text/javascript">
$(function () {
$("[name='testList']").change(function () {
$("#myunit").val($(this).val());
});
});
</script>
The user selects an option from the "testlist" dropdown, and that value is passed to "myunit" with the javascript provided. That all works really well. But, when I save the data. . . that field is always empty. It's not capturing the value.
I believe the issue is with the second attribute (null).
What do I need to change to make this work properly?
Update: Here is the Create View Controller Code
public ActionResult Create()
{
List<SelectListItem> testList = db.ICS_Units.Select(x => new SelectListItem { Value = x.DeliveryUnitID.ToString(), Text = x.DeliveryUnit, Selected = false }).DistinctBy(p => p.Text).ToList();
ViewBag.testList = new SelectList(testList, "Value", "Text");
return View();
}
// POST: InternalOrders/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "TransID,SuppliesID,OriginalDate,TransType,LastUpdatedBy,Contact,OpenClosed,CurrentStatus,CurrentStatusDate,RequsitionNumber,PONumber,DeliveryMonth,DeliveryYear,UnitsOrdered,Emergency,Comments,DeliveryUnitID")] ICS_Transactions iCS_Transactions)
{
if (ModelState.IsValid)
{
db.ICS_Transactions.Add(iCS_Transactions);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(iCS_Transactions);
}
The fact that you are forcing a HtmlFieldName to your field editor changes the default markup and your posted data. Leave the field name alone and instead update your jquery to match the model field name:
$('#DeliveryUnitID').val($(this).val());

DropDownItem doesn't load items after update(Javascript) partial view

I have the following functionality: A user registers a series of data in a form, after sending the form and saving it in the database, it is redirected to a second form, where it must load the rest of the information, it is possible that in this second form, contains a lot of values of the first form, but this is not always the case, that is why in this second form there is a button that allows "copy" the data of the first form, to reduce data entry.
As I did?
I created a main view, where the form is loaded as a partial view with the empty model fields, so that the user can enter the information if it is not the same as the first form
Main View:
<div id="partialView">
#Html.Partial("_CreatePartialResponsable", Model)
</div>
In the form that contains the partial view, there is a dropdowlist that is filled through a web service. This dropdow is filled correctly when loading the blank form, the first time.
DropDownList in the partial view
<div class="form-group col-md-6">
#Html.LabelFor(model => model.SeguroNombre, htmlAttributes: new { #class = "titulocampo" })
<select id="SeguroNombre" name="SeguroNombre" class="form-control"><option value=""></option></select>
#Html.ValidationMessageFor(model => model.SeguroNombre, "", new { #class = "text-danger" })
</div>
With this script, the dropdowlist is loaded, I call it from the main view in the document.ready event (it's works perfectly)
function llenaDropDownSeguros(url, valor) {
var data = { nombre: valor };
$.post(url, data).done(function (data) {
if (data.isError) {
muestraError(data.Titulo, data.Mensaje);
} else {
for (var i = 0; i < data.length; i++) {
$('#SeguroNombre').append('<option value=' + data[i].codigoSeguro + '>' + data[i].descripcion + '</option > ');
}
}
});
};
If the user wants to "copy" the information of the previous form, press a button that executes the following function js to go to the server, search the data and return the same partial view, but with the corresponding fields loaded. Re render?
Script to "copy" data
function loadPartial(url, valor) {
$("#esperar").show();
var data = { cedula: valor };
$.post(url, data).done(function (data) {
if (!data.isError) {
$("#esperar").hide();
$("#partialView").html(data);
} else {
$("#esperar").hide();
muestraError(data.Titulo, data.Mensaje);
}
}).fail(manejarErrorAjax);
};
What is the problem?
After the copy button is pressed and the partial view is reloaded with the "copied" data, the dropdownlist items are no longer there. I understand that they are lost, because of course, when doing a partial view reloaded, that dropdown comes empty, that I understood. What I do not understand is because, after reloading that partial view, there is no way I can fill the dropdowlist again. By test I made a change and in the click event of the ddl, I called the loadPartial function to see if it worked, but even though in the browser console, it is seen that the function is executed and the data object takes 94 values, these they are not loaded to the dll.
Can you help me with this problem?

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)
{
....
}

How to invoke my post method when I'm changing dropdown list in ASP.NET MVC

I'm very new to MVC and Javascript so please be patient with me, I'm working on small application and I came to part when I need to select something from dropdown list and based on that selection I need to redirect user to another View, I also need to determine somehow where I should redirect user, so that is reason why I tried to pass parameter also ( database ID to my post method) but unfortunatelly this is not working, in section below I will post my code:
Method which is sending data to my DropDownList :
public ActionResult ShowArticleGroup()
{
List<ArticleGroup> articlesGroups = GroupsController.GetAllGroups();
ViewBag.articlesGroups = articlesGroups;
return View(articlesGroups);
}
[HttpPost]
public ActionResult ShowArticleGroup(string id)
{
//Here I wanted to take ID of selected Group and because there will be allways 3 Groups I can do if else and Redirect by ID
if(id =="00000000-0000-0000-0000-000000000002")
{
return RedirectToAction("Create","Article");
}
return RedirectToAction("Create", "Article");
}
And my VIEW - there is only one control on the view : just one dropdown, and based on selection I should be redirected to another view, and I wanted here to take ID of selected group and by that I wanted to redirect user to appropiate view:
#model IEnumerable<Model.ArticleGroup>
#{
ViewBag.Title = "Add new article";
}
<h3 style="text-align:center">Choose article group</h3>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
#Html.ValidationSummary(true)
<div class="form-group" style="text-align:center">
#Html.DropDownList("Group", new SelectList(ViewBag.articlesGroups, "GroupID", "GroupTitle.Name"), null, new { onchange = "document.location.href = '/Articles/ShowArticleGroup/' + this.options[this.selectedIndex].value;" })
</div>
</div>
}
First of all, usage of location.href on DropDownList seems wrong here:
#Html.DropDownList("Group", new SelectList(ViewBag.articlesGroups, "GroupID", "GroupTitle.Name"), null,
new { onchange = "document.location.href = '/Articles/ShowArticleGroup/' + this.options[this.selectedIndex].value;" })
AFAIK, location.href used for redirect to another page using HTTP GET, hence it will try to call first ShowArticleGroup action method without parameter, and the URL parameter simply ignored since given URL parameter only exist in POST.
To submit the form with DropDownList, you need to handle change event triggering POST into controller action method:
jQuery
<script type="text/javascript">
$(document).ready(function() {
$("#Group").change(function() {
var groupId = $("#Group").val();
$.post('#Url.Action("ShowArticleGroup", "ControllerName")', { id: groupId }, function (response, status) {
// response handling (optional)
});
});
});
</script>
DropDownList
#Html.DropDownList("Group", new SelectList(ViewBag.articlesGroups, "GroupID", "GroupTitle.Name"), null)
I recommend you using strongly-typed DropDownListFor with binding to a viewmodel approach if you want to pass viewmodel contents during form submit.
NB: $.post is shorthand version of $.ajax which uses POST submit method as default.
Related issues:
Autopost back in mvc drop down list
MVC 4 postback on Dropdownlist change

Categories