Disable enable dropdownlistfor based on model property in mvc - javascript

I am trying to disable or enable a dropdownlistfor in my mvc application based on model property:-
what I am doing is :-
#Html.DropDownListFor(m => m.ParentOrganisationID, new SelectList(Model.ParentOrganisations, "ID", "Name", Model.ParentOrganisationID), new { #id = "ddlParentOrganisations", #class = "form-control css-select", #disabled = Model.IsReadOnly ? "disabled" : "false", #style = "width:40%; height:10%;" })
but even if model property "model.IsReadOnly" is false, then also it is showing the dropdown as disabled.
Please suggest how to handle this, without using any javascript
Thanks in advance

It is not possible to include the condition (if/ternary statement(s)) inside the call to the DropDownListFor helper method because you cannot pass a line of c# code (with your if condition) where it expects an object for html attributes. Also all of the below markups will render a disabled SELECT.
<select disabled></select>
<select disabled="disabled"></select>
<select disabled="false"></select>
<select disabled="no"></select>
<select disabled="usingJqueryEnablePlugin"></select>
<select disabled="enabled"></select>
You can simply check the value of your Model property with an if condition and conditionally render the disabled version.
#if (!Model.IsReadOnly)
{
#Html.DropDownListFor(s => s.ParentOrganisationID,
new SelectList(Model.ParentOrganisations, "ID", "Name"))
}
else
{
#Html.DropDownListFor(s => s.ParentOrganisationID,
new SelectList(Model.ParentOrganisations, "ID", "Name"),new {disabled="disabled"})
}
You may consider creating a custom html helper method which takes care of the if condition checking.
public static class DropDownHelper
{
public static MvcHtmlString MyDropDownListFor<TModel, TProperty>
(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> selectItems,
object htmlAttributes,
bool isDisabled = false)
{
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression,
htmlHelper.ViewData);
IEnumerable<SelectListItem> items =
selectItems.Select(value => new SelectListItem
{
Text = value.Text,
Value = value.Value,
Selected = value.Equals(metadata.Model)
});
var attributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
if (isDisabled && !attributes.ContainsKey("disabled"))
{
attributes.Add("disabled", "disabled");
}
return htmlHelper.DropDownListFor(expression,items, attributes);
}
}
Now in your razor view,call this helper
#Html.MyDropDownListFor(s=>s.ParentOrganisationID,
new SelectList(Model.ParentOrganisations, "ID", "Name")
,new { #class="myClass"},Model.IsReadOnly)

This is an HTML basic rule: from the moment you set the attribute disabled (regardless of its value), the element will be disabled.
To get what you want, you need to create an HTML extension DropDownListFor.
Please see this link.

The accepted answer from Shyju works great. But what if you want to use HTML5 data-* attributes in your custom helper? The standard MVC DropDownListFor provides a workaround by using an underscore (_) in place of the dash (-). And that helper is intelligent enough to convert the underscores to dashes when the markup is rendered.
Here is a custom helper that will provide a parameter to disable a DropDownList and also converts the HTML5 data-* attributes appropriately. It also preserves any other values passed in via the htmlAttributes parameter. The code is a little more concise as well (imo).
public static MvcHtmlString MyDropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> list, string optionLabel, object htmlAttributes, bool disabled)
{
var routeValues = new System.Web.Routing.RouteValueDictionary();
// convert underscores to dashes...
foreach (System.ComponentModel.PropertyDescriptor property in System.ComponentModel.TypeDescriptor.GetProperties(htmlAttributes))
{
routeValues.Add(property.Name.Replace('_', '-'), property.GetValue(htmlAttributes));
}
if(disabled)
{
routeValues.Add("disabled", "disabled");
}
return htmlHelper.DropDownListFor(expression, list, optionLabel, routeValues);
}
And the markup:
#Html.MyDropDownListFor(m => m.MonthId, Model.Months, "Select Month", new { #class = "form-control", data_url = Url.Action("GetMonths") }, Model.IsDisabled)

Related

Why is my routeValues variable changing between my view loading and me clicking on my Html.ActionLink in ASP.NET MVC?

I have an ASP.NET MVC application where I have a textbox with a jquery datePicker and a HTML.ActionLink that will download an Excel document that uses the date string picked from the datePicker. I want the link to the Excel download to use this date string as a parameter in its query string.
In the example code I will provide sample names for my classes and variables.
Here is an excerpt from my main view (View is named "TestView", Model is of class "TestModel"):
#using (Ajax.BeginForm("TestForm", FormMethod.Post,
new AjaxOptions
{
UpdateTargetId = "IdSomething",
OnFailure = "handleFailure(xhr, status, 'IdSomething')"
}))
{
#Html.AntiForgeryToken()
#Html.Action("TestSettings", Model)
<div class="clear margin-bottom-adjusted">
#Html.ActionLink(Markup.Download_As_Microsoft_Excel, "Download",
new { InstantlyUpdatedDate = Model.InstantlyUpdatedDate },
new { #class = "download-excel" });
</div>
}
Here is the relevant excerpt from the "TestSettings" view(it also uses a model of the class "TestModel"). The submit button here and the variable "NotInstantlyUpdatedDate" are used for updating a graph, but this should not be relevant for the Excel download:
<div>
#Html.TextBoxFor(m => m.NotInstantlyUpdatedDate, new { #class =
"datepicker", #onchange = "setExcelDownloadDate(" + #Json.Encode(Model) +
", $(this))" })
<input type="submit" value="#Markup.Update" class="btn" />
</div>
The "setExcelDownloadDate" function is defined in javascript like this:
function setExcelDOwnloadDate(model, item) {
if (item.attr('name') === "NotInstantlyUpdatedDate")
model.InstantlyUpdatedDate = item.val();
$.ajax({
url: '../Test/UpdateTestView',
type: 'post',
data: {
model: model
}
});
}
Relevant excerpt from my Controller:
public TestController
{
//Should only get called once by another view
[HttpPost]
public ActionResult InitTestView(TestModel model)
{
model.NotInstantlyUpdatedDate = "2018, 01, 01";
model.InstantlyUpdatedDate = model.NotInstantlyUpdatedDate;
return PartialView("TestView", model);
}
[HttpPost]
public ActionResult UpdateTestView(TestModel model)
{
return PartialView("TestView", model);
}
[HttpPost]
public Task<ActionResult> Download(DownloadModel model)
{
//download the model here
}
}
Here is my "TestModel" class for this example:
[Serializable]
public class TestModel
{
public string NotInstantlyUpdatedDate { get; set; }
public string InstantlyUpdatedDate { get; set; }
}
And here is my DownloadModel class:
public class DownloadModel
{
public string InstantlyUpdatedDate { get; set; }
}
OK, thank you for bearing with me. Here is what's happening:
First the InitTestView method is called, and it renders a TestView. If I place a breakpoint at the #Html.ActionLink line in the TestView, it will show me that the model.InstantlyUpdatedDate variable is "2018, 01, 01" (this is correct and the expected behaviour).
Since the TestSettings view is embedded in the TestView, it will render the Html.TextBoxFor for my datepicker. If I now inspect the Download button in my browser, a correct download query string will show, with the date "2018, 01, 01" as a parameter.
Now, let's say I pick the date ("2018, 01, 02") from the datepicker (the conversion to a date string is done in jquery, don't worry about this as it's working as expected). This date will now show in the textbox, and the #onchange event will trigger my javascript function setExcelDownloadDate. In this method, I can put breakpoints and see that my model.InstantlyUpdatedDate has indeed been set to "2018, 01, 02". This is the correct behaviour.
From the javascript function the ajax call sends the model object to my TestController. If I break in the function UpdateTestView, I can look at my model variable and also see here that the value has changed to "2018, 01, 02". Working correctly.
Now that this method returns a new instance of my TestView with the updated model, I can still break at the Html.ActionLink line and see that yes indeed, the Model.InstantlyUpdatedDate is "2018, 01, 02", which is correct.
However, here comes the problem. If i inspect the link in my browser, I will see that the url is incorrect. The date is not "2018, 01, 02", but still "2018, 01, 01". If I click the link and put a breakpoint in my Download method, the model's InstantlyUpdatedDate property will also be "2018, 01, 01" instead of "2018, 01, 02".
For some reason the model property InstantlyUpdatedDate seems to change back to it's original value. I do not know why this happens, and therefore I ask if some of you may be able to help me. This is part of a larger codebase, and something I don't know about might of course screw with what's happening. It could also be that this is the expected behaviour for some reason, and that I'm just not familiar enough with how this should work.
Thank you for your time.
I have a bit of trouble following this but I'll give a try. It seems like you aren't doing anything with the result of $.ajax. It will return the partial view with everything filled up but nothing is done with it.
$.ajax({
url: '../Test/UpdateTestView',
type: 'post',
data: {
model: model
}
}).done(function( html ) {
// $( "#results" ).append( html ); // Put the html somewhere
});
Personally, in the onchange, I would just update the link instead of the whole partial view. I usually update the partial view when there's a bit more changes than just a link.
$('#linkId').attr('href', '#Url.Action(Markup.Download_As_Microsoft_Excel, "Download")?InstantlyUpdatedDate=' + item.val());

How to clear textboxes besides using JS

I am building an app using MVC, and this question pertains to the Create page and action.
Lets say my model has 2 decimal properties along with other properties but aren't necessary for this example:
public class TestClass
{
public int ID { get; set; }
public decimal DecimalProperty { get; set; }
public decimal SecondDecimalProperty { get; set; }
// more properties below this, but deemed unnecessary for this question
}
Obviously these properties are non-nullable, so in my Create View they appear as so on page load (ignore the 2nd textbox):
Now my goal is to clear those textboxes out, so they are just blank.. so I used JS to achieve that by doing:
$(".clear-textbox").val("");
I put a class called clear-textbox on those input fields.. works perfectly.. but now in my HttpPost Create Action I have conditional statements checking to see if other fields are valid, and if not return the object.. like so:
if (object.property== 0)
{
ModelState.AddModelError("property", "This field is required!");
return View(object);
}
This results in the Create view to be redisplayed with the values that the user has already entered, along with an error message below the one property that needs to be changed.. and this is where the problem lies. Once the Create view is reloaded.. then so are the scripts for clear-textbox, resulting in DecimalProperty and SecondDecimalProperty to be empty text-boxes.. instead of keeping what the user originally entered for them.
So my question, is there another way to clear out textboxes for decimal properties other than using javascript?
Any help is appreciated.
UPDATE
Here is the cshtml.
<div class="form-group">
#Html.LabelFor(model => model.DecimalProperty, htmlAttributes: new { #class = "control-label col-md-4" })
<div class="col-md-8">
#Html.EditorFor(model => model.DecimalProperty, new { htmlAttributes = new { #class = "form-control clear-textbox" } })
#Html.ValidationMessageFor(model => model.DecimalProperty, "", new { #class = "text-danger" })
</div>
</div>
Either you have to do it via Javascript on load like following
$(".clear-textbox").each(function(){
if($(this).val() <= 0 )
$(this).val("");
});
OR
You can create your own MVC Html Helper which will do things as you need for your special needs. Let me know if you want code for that...
You can also refer this link
You can set the default value as a data- attribute of the textbox and clear it only if they match. Like:
$(".clear-textbox").each(function(){
var $this = $(this);
if( $this.val() == $this.data().defaultvalue ) $this.val('');
});
It's hard to come up with an answer without knowing how the text boxes are being rendered. However, I'm assuming you are using something like
#Html.TextBoxFor
or
#Html.EditorFor
There are two ways to do this.
1. Add a DisplayFormat attribute to the model fields and use EditorFor:
public class TestClass
{
public int ID { get; set; }
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:#.#}")]
public decimal DecimalProperty { get; set; }
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:#.#}")]
public decimal SecondDecimalProperty { get; set; }
}
#Html.EditorFor(model => model.DecimalProperty)
2. Use the inline format attribute:
#Html.TextBoxFor(model => model.DecimalProperty, "{0:#.#}")

Update label depending on selected value on dropdownlist with knockout

In ASP.NET MVC, I have a form. In this form the user selects a country, and then the ID is posted back to the server, using a normal #using(Html.BeginForm) .
However, for some UX reasons, I use Knockout. Therefore I need to get my observable value countryId to have the value of a dropdownlist.
I want my label in the markup, to show the countryId, depending on the selected value on the dropdownlist.
Markup:
#Html.DropDownListFor(model => model.SelectedCountry, Model.Countries, new Dictionary<string, object> { { "class", "form-control sendaletter_countrylist" }, { "data-bind", "value:countryId" }})
<label data-bind="text: countryId"></label>
ViewModel:
public class CreateSingleLetterModel
{
public CreateSingleLetterModel()
{
this.Countries = new List<SelectListItem>();
}
public string SelectedCountry { get; set; } // expected to be the ID of the SelectListItem
public List<SelectListItem> Countries { get; set; }
}
Then my question is:
How do I modify my DropDownListFor, so the countryId is being set automatically? :-)
Thanks a lot! I really enjoy learning Knockout, but this one has taken me a long time!
All you've done looks correct.
Now you need to define the client-side view model (where countryId will be ko.observable) and call ko.applyBindings
var viewModel = {
countryId: ko.observable(0)
};
ko.applyBindings(viewModel);
example: http://jsfiddle.net/tabalinas/xJ7mm/

Change label display name labels, based on the values from db, on dropdown change

Problem Statement: I want to change the display name of labels(#Html.LabelFor) in Razor view of MVC based on the display names which i get from db.
I have added the dropdown list of languages in the _Layout.cshtml
<li>#Html.Action("Index", "LanguageDropdown", new { languageid = Request["languageId"] })</li>
I have created one partial view for drop down:
#model ALCMS.Web.Models.Master_or_Configuration.LanguageDropdownModel
<script type="text/javascript">
function GetLanguage() {
var languageId = $('#LanguageId').val();
var Url = "#Url.Content("~/MasterConfigGeneral/GetLanguage")";
$.ajax({
url: Url,
dataType: 'json',
data: { LanguageId: languageId },
success: function (data) {
}
});
}
</script>
<div style="display:inline-block">
#Html.DropDownListFor(l => l.LanguageID, new SelectList(Model.Languages, "Value", "Text"), "Select Language", new { id = "LanguageId" ,onchange="GetLanguage()" })
</div>
Partial View Controller:
public ActionResult Index(string languageId)
{
//return View();
var languages = dbEntity.LookupLanguages;
var model = new LanguageDropdownModel
{
LanguageID = languageId,
Languages = languages.ToList().Select(l => new SelectListItem
{
Value = Convert.ToString(l.LanguageID),
Text = l.Name
})
};
return PartialView(model);
}
In Controller Json Result method:
public JsonResult GetLanguage(int languageID)
{
JsonResult jsResult = new JsonResult();
objdbGlobalTenant.ddlLanguage = (from lsr in dbEntity.LocaleStringResources
where lsr.LanguageID == languageID
select new SelectListItem()
{
Text = lsr.ResourceValue,
Value = lsr.ResourceName
}).Distinct().ToList<SelectListItem>();
//ViewBag.Language = objdbGlobalTenant.ddlLanguage;
jsResult.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
return jsResult;
}
Now everything is working fine.I'm able to get the selected langaugeID in Json Result method in Controller based on the change event of Language dropdown. Based on this Language ID i'm getting display names(ResourceValue) which i need to apply for the particular view.
Problems:
1>After getting the display names from db how to change display names
of particular view when language change event triggers.?? For
ex:Currently i'm seeing the Create.CSHTML. Now if i change the
language dropdown it should trigger Json Event in controller and
after getting values it should apply the values on the view which it
got from db.
Note: Dropdown is in Layout.cshtml(like master in .aspx)
2>Drop-down which i placed in Layout.cshtml is getting refreshed
every time new view is loaded which inherits(layout.cshtml).How to
make the controller to retain it's state during postback??
3>How to get the selected drop-down item from the layout in multiple
Controllers,to change the display name in each view based on the langaugeid
of dropdown in layout
How to do this??If i'm doing wrong suggest me some other ways...
Below are the suggestions :
Issue 1 :
You may keep one attribute in each label which identifies them uniquely.
Your HTML should render like following
<!-- For English -->
<label label-unique-name="Name">Name</label>
<label label-unique-name="Surname">Surname</label>
<!-- For French -->
<label label-unique-name="Name">nom</label>
<label label-unique-name="Surname">nom de famille</label>
<!-- For Spanish -->
<label label-unique-name="Name">nombre</label>
<label label-unique-name="Surname">apellido</label>
Here label-unique-name is your attribute, which will remain fixed for each language. Now when you change the language from dropdown you will bring the values like below.
<!-- For English -->
<label-unique-name:"Name",label-value:"Name">;<label-unique-name:"Surname",label-value:"Surname">
<!-- For French -->
<label-unique-name:"Name",label-value:"nom">;<label-unique-name:"Surname",label-value:"nom de famille">
<!-- For English -->
<label-unique-name:"Name",label-value:"nombre">;<label-unique-name:"Surname",label-value:"apellido">
Please note : this is for understanding only, it's not a JSON.
Now using jQuery go through each label and replace the label's value. Hope it'll help you.
Issue 2 :
You can save the selected language's value in session, and generate your dropdown accordingly.
#Html.DropDownListFor(l => l.LanguageID, new SelectList(Model.Languages, "Value", "Text"), !string.isNullorEmpty(HttpContext.Current.Sessions["Language"]) ? HttpContext.Current.Sessions["Language"] : "Select Language", new { id = "LanguageId" ,onchange="GetLanguage()" })

jquery autocomplete in variable length list

Trying to figure out how to do this, using Sanderson begincollectionitems method, and would like to use autocomplete with a field in each row.
I think I see how to add a row with an autocomplete, just not sure the approach for existing rows rendered with guid.
Each row has an of field that the user can optionally point to a record in another table. Each autocomplete would need to work on the html element idfield_guid.
I'm imagining using jquery to enumerate the elements and add the autocomplete to each one with the target being the unique of field for that row. Another thought is a regex that maybe let you enumerate the fields and add autocomplete for each in a loop where the unique field id is handled automatically.
Does that sound reasonable or can you suggest the right way? Also is there a reasonable limit to how many autocomplete on a page? Thanks for any suggestions!
Edit, here's what I have after the help. data-jsonurl is apparently not being picked up by jquery as it is doing the html request to the url of the main page.
$(document).ready(function () {
var options = {
source: function(request, response) {
$.getJSON($(this).data("jsonurl"), request, function (return_data) {
response(return_data.itemList);
});
},
minLength: 2
};
$('.ac').autocomplete(options);
});
<%= Html.TextBoxFor(
x => x.AssetId,
new {
#class = "ac",
data_jsonurl = Url.Action("AssetSerialSearch", "WoTran", new { q = Model.AssetId })
})
%>
And the emitted html look okay to me:
<input class="ac" data-jsonurl="/WoTran/AssetSerialSearch?q=2657" id="WoTransViewModel_f32dedbb-c75d-4029-a49b-253845df8541__AssetId" name="WoTransViewModel[f32dedbb-c75d-4029-a49b-253845df8541].AssetId" type="text" value="2657" />
The controller is not a factor yet, in firebug I get a request like this:
http://localhost:58182/WoReceipt/Details/undefined?term=266&_=1312892089948
What seems to be happening is that the $(this) is not returning the html element but instead the jquery autocomplete widget object. If I drill into the properties in firebug under the 'element' I eventually do see the data-jsonurl but it is not a property of $(this). Here is console.log($this):
You could use the jQuery UI Autocomplete plugin. Simply apply some know class to all fields that require an autocomplete functionality as well as an additional HTML5 data-url attribute to indicate the foreign key:
<%= Html.TextBoxFor(
x => x.Name,
new {
#class = "ac",
data_url = Url.Action("autocomplete", new { fk = Model.FK })
})
%>
and then attach the plugin:
var options = {
source: function(request, response) {
$.getJSON($(this).data('url'), request, function(return_data) {
response(return_data.suggestions);
});
},
minLength: 2
});
$('.ac').autocomplete(options);
and finally we could have a controller action taking two arguments (term and fk) which will return a JSON array of suggestions for the given term and foreign key.
public ActionResult AutoComplete(string term, string fk)
{
// TODO: based on the search term and the foreign key generate an array of suggestions
var suggestions = new[]
{
new { label = "suggestion 1", value = "suggestion 1" },
new { label = "suggestion 2", value = "suggestion 2" },
new { label = "suggestion 3", value = "suggestion 3" },
};
return Json(suggestions, JsonRequestBehavior.AllowGet);
}
You should also attach the autocomplete plugin for newly added rows.

Categories

Resources