JSON Function - cascading DropDownList - update the model - asp.net-mvc

Premise: I'm new with MVC, and my english is not perfect, I hope you'll understand my post, otherwise I'm here for explainations.
I'm working on a project created by another developer in ASP.Net MVC and I have this problem:
In a View I have two cascading DropDownList: one with the Distributors and the other with the Vendors of the Distributor selected in the first DropDownList.
This is the code:
In the Body:
<tr>
<td>
<span>Distributor</span>
</td>
<td>
#Html.DropDownList("DistributorID")
#Html.ValidationMessageFor(model => model.DistributorID)
</td>
</tr>
<tr>
<td>
<span>Vendor</span>
</td>
<td class="tdAlignLeft">
#Html.DropDownList("VendorCode")
#Html.ValidationMessageFor(model => model.ClientHeaderInformation.VendorCode)
</td>
</tr>
In the Script:
$("#DistributorID").change(function (e) {
var SelectGroupId =$(this).val();
$.getJSON('#Url.Action("VendorByDistributor", "ClientInfo")', { VendorGroup: DistributorID }, function (param) {
var vendorCodes = $('#VendorCode');
vendorCodes.empty();
$.each(param, function (index, param) {
vendorCodes.append(
$('<option/>')
.attr('value', param.vendorCode)
.text(param.vendorName)
);
});
});
});
In the Controller:
public ActionResult VendorByDistributor(int _DistributorID)
{
var Vendors = db.View_Vendors.Where(n => n.DistributorID.Equals(_DistributorID)).Select(
x => new
{
vendorCode = x.VendorCode,
vendorName = x.VendorName
});
return Json(Vendors, JsonRequestBehavior.AllowGet);
}
'View_Vendors' is a SQL Server view, mapped with Entity Framework, this is the SQL:
SELECT A.DistributorID, A.DistributorName, B.VendorName, B.VendorCode
FROM dbo.Distributors AS A LEFT OUTER JOIN dbo.Vendors AS B ON
A.VendorCode = dbo.Vendor.VendorCode
All seems to work well, but when I save the view sometimes the VendorCode property of the ClientInfo class hasn't the last selected value. This happens if I change The Distributor on the first DropDownList, in this case the second DropDownList (Vendors) gets the right values, but if I select a Vendor and then I save the model, the property VendorCode has still the first value.
I also tried to create a function to test the event 'change' of the Vendors DropDownList ...
$ ("# VendorCode.") Change (function (e) {
SelectId var = $ (this). val ();
$. getJSON ('# Url.Action ("updateVendorCode", "ClientInfo")', {VendorCodePass: SelectId});
});
... and in fact the event fires correctly only if I not change the Distributor and the Vendor items are not reloaded, in that case the event fires only the first time.
Sounds like a refresh problem of the second DropDownList Vendors, but I can't find the solution ...
Pileggi

I wrote a blog post about this here . Here is the relevant code that is similar to your case.
$("#ClientId").change(function () {
var clientId = "";
$("#ClientId option:selected").each(function () {
clientId += $(this)[0].value;
});
var url = '<%:Url.Action("ProjectList", "Client") %>' + "/" + clientId;
$.getJSON(url, null, function (data) {
var selectedValue = '<%:Model.ProjectId %>';
$("#ProjectId").empty();
$.each(data, function (index, optionData) {
if (optionData.OBJID == parseInt(selectedValue))
$("#ProjectId").append("<option value='" + optionData.ObjId+ "' selected='true'>" + optionData.Name + "</option>");
else
$("#ProjectId").append("<option value='" + optionData.ObjId + "'>" + optionData.Name + "</option>");
});
});
}).change();

Ok, sorry, it was a very trivial problem. The code is right, simply a field was wrong:
VendorCode in the place of VendorNumber... :-)
Sometimes you can't see the simplest solution ...
I don't delete the post, because the code is right and it could be useful for others.
Regards!
Pileggi

Related

how to disabled asp-action link after one click

i would like to disabled asp=action link after one click
i have a foreach loop to display my book in table in my view
my asp-action for add book in the cart is in the loop but i want to disabled just the book who already in the cart not all books
<a asp-action="AcheterLivre" asp-Controller="Achat" asp-route-id="#item.Isbn" id="disabled" onclick="return myFunction(this);"> Ajouter
i try something with jquery but its dont work well
i tried this
<script>
function myFunction() {
$("#disabled").one("click", function () {
alert("this book is already in the cart");
});
}
i have this function in my class
its verify if the books is in the cart maybe i should use it ?
public bool IsPresent(string isbn)
{
//rechercher si l'isbn correspond a un livre de la liste
AchatLivreViewModel livre = ListeElements.Find(element => element.Isbn == isbn);
if (livre != null)
{
return true;
}
else
{
return false;
}
}
Why not trying this simple approach:
<tbody>
#foreach (var item in Model)
{
<tr>
<td>
#item.Isbn
</td>
<td>
#item.Titre
</td>
<td>
<label class="btn btn-primary" style="padding:0">Add to Cart</label>
</td>
<td>
<label class="btn btn-danger" style="padding:0">Remove From Cart</label>
</td>
</tr>
}
</tbody>
And in your javascript, if you don't want to use Ajax, you can manage your cart items all on client side using an array of objects, Let's name it CartItems:
var CartItems = [];
$('.ADD2CART').click(function () {
if ($(this).closest('tr').hasClass("ExistingInCart")) {
alert('Already in Cart !!');
}
else {
// add this item to the Cart through Ajax or
// local javascript object: e.g.:
CartItems.push({
ISBN: $(this).closest('tr').find('td:eq(0)').text().trim(),
Title: $(this).closest('tr').find('td:eq(1)').text().trim(),
});
$(this).closest('tr').addClass("ExistingInCart");
}
return false; //to prevent <a> from navigating to another address
});
$('.RemoveFromCART').click(function () {
$(this).closest('tr').removeClass("ExistingInCart");
var isbn = $(this).closest('tr').find('td:eq(0)').text().trim();
CartItems = CartItems.filter(x => x.ISBN !== isbn);
return false;
});
Once you need to submit or post the page, you have all the already selected books in CartItems array.
To add this javascript code to your view, choose one of the options:
Put this block at the bottom of your view and copy the above script inside the <script></script> tag:
#section scripts{
<script>
.... copy it here ...
</script>
}
copy the script code inside a newFile.js and add it to your view
<script src="~/Scripts/jquery-3.3.1.min.js"></script>
<script src="~/Scripts/newFile.js"></script>
You may decide to bundle this newFile.js
try this:
Ajouter
And your Javascript:
function foo(input) {
if ($(input).attr('yetClickable') === '1') {
$(input).attr('yetClickable', '0');
return true;
}
else {
// this false returning will counteract the effect of click event on the anchor tag
return false;
}
}
Once an Item is removed from the cart, again you need javascript to select that Item by its Id and change the yetClickable attribute back to 1 (in order to be clickable).
Note: This idea above (upon your scenario) works until the page is not reloaded. Otherwise, you need to handle ADD/Remove operations on the Cart through Ajax.
Hope this helps.

Track user clicks from list populated of URL's using MVC framework

I have an MVC 5 application that displays a list of 100+ unique URL's. The user can sort, search, and eventually click the URL from the list launching the website in a new window. I want to be able to track every time someone clicks a URL from that list. The URL list is populating as expected. View code below:
<table class="table table-condensed table-hover table-responsive table-striped">
<tr>
<th class="col-md-1">
#Html.ActionLink(" Name", "Index", new { sortOrder = ViewBag.ReportNameSortParam, SelectedReportCategory = ViewBag.SelectedReportCategory, FindReportName = ViewBag.FindReportName}, new { #class = "glyphicon glyphicon-sort", #title = "sort by name" })
</th>
</tr>
#foreach (var item in Model)
{
if (ViewBag.counter == "")
{
ViewBag.rowType = "normalRow";
ViewBag.counter = "1";
}
else {
ViewBag.rowType = "alternateRow";
ViewBag.counter = "";
}
<tr class=#ViewBag.rowType>
<td class="col-md-1" title="#Html.DisplayFor(modelItem => item.Report_Description) : #Html.DisplayFor(modelItem => item.SLA)">
#Html.DisplayFor(modelItem => item.Report_Name)
</td>
</tr>
}
</table>
What would be the best approach to track a user click when they are doing this via the View? I have a column in my table called 'Hits' that I would like to increment by 1 whenever that URL gets clicked. Ideally, after a user clicks a URL, I would like for the list to stay where it is and not have to refresh by hitting another controller and then repopulating. How can I write to the database on via the View?
as mentioned... capture the click event of each link clicked and perform an Ajax post to the relevant controller and action.
$(document).ready(function () {
$(document).on('click', 'a', function () {
var linkID = $(this).attr("id");
$.ajax({
type: "POST",
url: "#Url.Action("YourAction", "YourController")",
data:
{
linkid: linkID
}
});
});
});
hope that helps.
Consider standing up an action method that receives the URL and redirects to the final destination:
#Html.DisplayFor(modelItem => item.Report_Name)
In the controller, add:
public ActionResult Redirect(int id, string url)
{
//Get record using ID, and update Hits column
//Redirect to the final URL
return Redirect(url);
}
Be aware that if your URL's use any special characters, some encoding may occur... that might mean you have to do some encoding on the client end potentially. It's just good to test that out to confirm.

Looking for a cleaner/better way than shown to confirm deletion of a row

I am new to ASP.NET MVC and not that great with Javascript/jQuery.
My end goal is to provide a Delete link to the user to delete a row from a table. I want to do it as a POST operation rather than a GET as this, as I understand, POST is best practice. I wanted to add a confirm dialog before deleting but I didn't want to use the ugly javascript confirm dialog box and I was having problems with the form submitting without waiting for the jQuery dialog box to be answered. So, I turned the submit button into a regular button and added an onclick event which calls a ConfirmDelete javascript method in which I display a custom jQuery confirmation dialog. If answered in the affirmative, I find the form by ID using the keys of the record that the user wants to delete (keys = vendor Id and Effective Date). Once ethe form is found, I use Javascript to submit the form.
-------------------
public static class DateTimeExtensions
{
public static long ToLong(this DateTime value)
{
return long.Parse(value.ToString("yyyyMMddHHmmss"));
}
}
---------------------------------------
#using (Html.BeginForm("Delete", "StateAssessment", new
{
vendorId = Model.VendorId,
effectiveDate = Model.EffectiveDate
},
FormMethod.Post,
new { #id = "Form_" + Model.VendorId + "_" + #Model.EffectiveDate.ToLong() }
))
{
<tr>
<td>
#Html.DisplayFor(modelItem => Model.VendorId)
</td>
<td>
#Html.DisplayFor(modelItem => Model.EffectiveDate)
</td>
<td>
#Html.DisplayFor(modelItem => Model.LastTimeStamp)
</td>
<td>
#Html.ActionLink("Edit", "Edit",
new
{
vendorId = Model.VendorId,
effectiveDate = Model.EffectiveDate
})
<input value="Delete" class="btn btn-link" onclick="#string.Format("ConfirmDelete('{0}','{1}')", Model.VendorId, Model.EffectiveDate.ToLong())" />
</td>
</tr>
}
<script type="text/javascript">
function ConfirmDelete(vendorId, effectiveDate) {
var $myDialog = $('<div></div>')
.html('Are you sure that you want to delete this State Assessment?')
.dialog({
modal: true,
autoOpen: false,
title: 'Delete Confirmation',
buttons: {
"OK": function () {
$(this).dialog("close");
var form = $("#Form_" + vendorId + "_" + effectiveDate);
form.submit();
return true;
},
"Cancel": function ()
{
$(this).dialog("close");
return false;
}
}
});
$myDialog.dialog('open');
}
</script>
As you see, the ID of the form is constructed of the literal "Form_" concatenated with the vendor ID and EffectiveDate keys, where the EffectievDate value was normalized into a value that could be used in an ID.
This all worked, but man, it looks ugly to me and very long winded, especially when I think how easy this could be done in WinForms in a no more than few lines of code. I am looking for a better way. I'm thinking this code has noob written all over it and I'd like to denoob it.

JavaScript in server does not work?

html
<script type="text/javascript">
$(document).ready(function () {
$("#musteri_sno").change(function () {
var strSayacID = "";
strSayacID = $(this)[0].value; // get the selected state id
var url = "/SayacOkumalari/MusteriSayaclariniGetir/" + strSayacID;
// call controller's action
$.getJSON(url, null, function (data) {
// do something once the data is retrieved
$("#sayac_no").empty();
$.each(data, function (index, optionData) {
$("#sayac_no").append("<option value='"
+ optionData.sno
+ "'>" + optionData.sayac_seri_no
+ "</option>");
});
});
})
.change(); // making sure the event runs on initialization for default value
});
</script>
#using (Ajax.BeginForm("SayacSecimiPartial", "SayacOkumalari", new AjaxOptions { UpdateTargetId = "div_grafik" }, new { id="sayac_secimi_form"}))
{
<table>
<tr>
<td>
#Html.DropDownList("musteri_sno", (SelectList)ViewBag.musteri_id, "--Müşteri Seçiniz--", new { id = "musteri_sno" })
</td>
<td>
#Html.DropDownList("sayac_no", Enumerable.Empty<SelectListItem>(), "-- Sayaç Seçiniz --", new { id = "sayac_no" })
</td>
<td>
<input type="submit" value="Uygula" />
</td>
</tr>
</table>
}
This script works on localhost but it does not work on server. There are a lot of script in my project and all of them are working too. Only this script does not work. I cant find, Why?
Thanks.
I suspect the problem line is this one here:
var url = "/SayacOkumalari/MusteriSayaclariniGetir/" + strSayacID;
try changing this to:
var url = '#Url.Action("MusteriSayaclariniGetir", "SayacOkumalari", new {Id = strSayacID })';
As you don't show the controller action, I'm 'assuming' that MusteriSayaclariniGetir has a parameter called Id. If not, then simply change the new {Id = strSayacID }) section to match the parameter name that's required.
var url = "/SayacOkumalari/MusteriSayaclariniGetir/" + strSayacID;
Instead of this try using the following as I have a doubt on the folder structure in your solution.
var url = "../SayacOkumalari/MusteriSayaclariniGetir/" + strSayacID;
Let me know if it doesn't helps, I'll give a try for another thing.

Dynamically adding dropdowns to a form and validating them in ASP.NET MVC

I have a form with various inputs. I have a bunch of optional parameters that have some number of choices. I'd like to allow the user to select these optional parameters in the following way:
First, the user clicks the Add Component button at the bottom of the form and two new dropdowns appear above the button. The first dropdown has a list of Types that can be selected and the second one will be disabled. When the user selects a valid choice in the first dropdown, I want to populate the second dropdown with some Values that are specific to the specified Type. The user should be able to continue adding new Components (the pair of dropdowns) until all the desired optional Components are added. Ideally the form wouldn't be posted until all the fields have been filled out and the desired Components added.
My question is this: How do I design this so that when the form is submitted and there are errors, that the dynamically added fields (the Components) will remain on the page and display the correct values?
I was planning on having the Add Component button be an Ajax.ActionLink that retrieves a partialview:
<div id="divComponentHolder"></div>
<%= Ajax.ActionLink("Add a Component", "GetComponentSelector", new AjaxOptions { UpdateTargetId = "divComponentHolder", InsertionMode = InsertionMode.InsertAfter}) %>
This partial view would look something like this:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MVCAndWebFormsTest.Models.ComponentSelectorModel>" %>
<%= Html.Encode("Type:")%>
<%= Html.DropDownList("ComponentType", Model.ComponentTypes, "", new {onchange = "updateCompValues(this);"}) %>
<%= Html.Encode("File/Folder:")%>
<div id="selectdiv">
<% Html.RenderPartial("ComponentValueSelector", Model.ComponentValues); %>
</div>
<br/>
<script type="text/javascript" language="javascript">
function updateCompValues(obj) {
$.ajax({
url: <% Url.Action("GetCompValues") %>,
async: true,
type: 'POST',
data: { type: obj.value },
dataType: 'text',
success: function(data) { $("#selectdiv").html(data); },
error: function() {
console.log('Erreur');
}
});
}
</script>
And the ComponentValueSelector partial would be pretty simple:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MVCAndWebFormsTest.Controllers.ViewModels.ComponentValueModel>" %>
<%= Html.DropDownList("CompValue", Model.SelectList) %>
Take a look at submitting list in MVC, here are a few useful sites:
http://blogs.teamb.com/craigstuntz/2009/02/11/38013/
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx
This is useful for submitting your dynamic DOM you are building up.
Another way instead of making an ajax call to render a partial view you could always directly add elements to the DOM with jquery. For example use the jquery clone ( $('element').clone(); ) method that would copy your list boxes then do some regex to change the id's of the input boxes so they have unique id/names.
As you are passing through a List of these 'choices' to your controller, you would then have to set them back in your Model and have your View iterate through them to display the correct amount of choices added.
Here is a bare bones example. This may not be the best implementation for yourself or someone else may have better ideas.
View
<% for (int i = 0; i < in Model.Results.Count; i++) { %>
//render better HTML but you should get the point!
<%= Html.Hidden("choices[" + i + "].ID", i) %>
<%= Html.DropDownList("choices[" + i + "].Choice1", ...) %>
<%= Html.DropDownList("choices[" + i + "].Choice2", ...) %>
<% } %>
- add button
jQuery
$('#addButton').click(function()
{
//say if your choice drop downs were in a table then take the last
//row and clone it
var row = $('table tr:last').clone(true);
var newId = //work out the new id from how many rows in the table
//make sure to update the id and name parameters of inputs
//of the cloned row
row.find(':input')
.attr('id', function()
{
return $(this).attr('id').replace(/\[[\d+]\]/g, '[' + newlId + ']');
//this replaces the cloned [id] with a new id
})
.attr('name', function()
{
return $(this).attr('name').replace(/\[[\d+]\]/g, '[' + newId + ']');
});
row.find(':hidden').val(newId); //update the value of the hidden input
//alert(row.html()); //debug to check your cloned html is correct!
//TODO: setup ajax call for 1st drop down list to render 2nd drop down
$('table tr:last').after(row);//add the row
return false;
});
Controller
public ActionResult YourMethod(IList<YourObject> choices, any other parameters)
{
YourViewModel model = new YourViewModel();
model.Results = choices; //where Results is IList<YourObject>
return View(model);
}
Based on advice from David Liddle, I found a different design that was a bit more elegant. It uses more jQuery and fewer partial views and Ajax requests.
Instead of adding a bunch of DropDownLists, I decided to go with a table, a pair of dropdowns and an "Add" button. When the user selects a Type option in the first dropdown, ajax is still used to retrieve the partial view for populating the second Value dropdown. Once a Value option has been selected, the user then clicks the Add button.
Using jQuery, two hidden inputs are added to the page. The naming convention in the links from David are used to name these elements (comps[0].Type and comps[0].Value). Also, a new row is added to the table with the same Type and Value for visual feedback to the user showing what has been added.
I also defined a Component class that just has Type and Value properties and added a List to the Model. In the view, I iterate over this list and add all components in the Model to the table and as hidden inputs.
IndexView
<table id="componentTable">
<tr>
<th>Type</th>
<th>Deploy From</th>
</tr>
<% foreach (Component comp in Model.comps) { %>
<tr>
<td><%= Html.Encode(comp.Type) %></td>
<td><%= Html.Encode(comp.Value) %></td>
</tr>
<% } %>
</table>
<div id="hiddenComponentFields">
<% var index = 0;%>
<% foreach (Component comp in Model.comps) { %>
<input type="hidden" name="comps[<%= Html.Encode(index) %>].Type" value="<%= Html.Encode(comp.Type) %>" />
<input type="hidden" name="comps[<%= Html.Encode(index) %>].Value" value="<%= Html.Encode(comp.value) %>" />
<% index++; %>
<% } %>
</div>
<%= Html.DropDownList("ComponentTypeDropDown", Model.ComponentTypes, "", new { onchange = "updateCompValues();"}) %>
<span id="CompValueContainer">
<% Html.RenderPartial("ComponentValueSelector", new ComponentValueModel()); %>
</span>
<span class="button" id="addComponentButton" onclick="AddComponentButtonClicked()">Add the File</span>
<span id="componentStatus"></span>
ComponentValueSelector PartialView
<%# Control Language="C#" Inherits="ViewUserControl<ComponentValueModel>" %>
<% if(Model.SelectList == null) { %>
<select id="CompValue" name="CompValue" disabled="true">
<option></option>
</select>
<% } else { %>
<%= Html.DropDownList("CompValue", Model.SelectList, "") %>
<% } %>
jQuery
function updateCompValues() {
$.ajax({
url: '<%= Url.Action("GetComponentValues") %>',
async: true,
type: 'POST',
data: { type: $("#CompValue").value },
dataType: 'text',
success: function(data) { $("#CompValueContainer").html(data); enable($("#CompValue")) },
error: function() {
console.log('Erreur');
}
});
}
function AddComponentButtonClicked() {
UpdateCompStatus("info", "Updating...");
var type = $("#ComponentTypeDropDown").val();
var value = $("#CompValue").val();
if (type == "" || value == "") { // No values selected
UpdateCompStatus("warning", "* Please select both a type and a value");
return; // Don't add the component
}
AddComponent(type, value);
}
function AddComponent(type, setting_1) {
// Add hidden fields
var newIndex = GetLastCompsIndex() + 1;
var toAdd = '<input type="hidden" name="comps[' + newIndex + '].Type" value="' + type + '" />' +
'<input type="hidden" name="comps[' + newIndex + '].Setting_1" value="' + setting_1 + '" />';
$("#hiddenComponentFields").append(toAdd);
// Add to page
// Note: there will always be one row of headers so the selector should always work.
$('#componentTable tr:last').after('<tr><td>'+type+'</td><td>'+setting_1+'</td>remove</tr>');
}
function GetLastCompsIndex() {
// TODO
alert("GetLastCompsIndex unimplemented!");
// haven't figured this out yet but something like
// $("#hiddenComponentFields input:hidden" :last).useRegExToExtractIndexFromName(); :)
}
function UpdateCompStatus(level, message) {
var statusSpan = $("#componentStatus");
// TODO Change the class to reflect level (warning, info, error?, success?)
// statusSpan.addClassName(...)
statusSpan.html(message);
}
Controller
public ActionResult Index() {
SelectList compTypes = repos.GetAllComponentTypesAsSelectList();
return View(new IndexViewModel(compTypes));
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(Component[] comps, other params...) {
foreach(Component comp in comps) {
// Do something with comp.Type and comp.Value
}
return RedirectToAction(...);
}
public ActionResult GetComponentValues(string type) {
ComponentValueModel valueModel = new ComponentValueModel();
valueModel.SelectList = repos.GetAllComponentValuesForTypeAsSelectList(type);
return PartialView("ComponentValueSelector", valueModel);
}
IndexViewModel
public class IndexViewModel {
public List<Component> comps { get; set; }
public SelectList ComponentTypes { get; set; }
public IndexViewModel(SelectList types) {
comps = new List<Component>();
ComponentTypes = types;
}
}

Resources