I have a value coming in from the Model to determine whether I am in create mode or edit mode... If I'm in Edit mode, I want to set the value of a dropdownlist to the value last saved. If I am in create mode, I want to default my dropdownlist to a default value....
*.ASCX
<% if(Model.isCreate == true)
{
Html.DropDownList("myOptionListName",
new SelectList(ViewBag.MyOptions, "Id", "Name"),
Resources.Global.DefaultMenuItem,
new { style = "width:200px" });
}
else
{
Html.DropDownList("myOptionListName",
new SelectList(ViewBag.MyOptions, "Id", "Name",
ViewBag.LastSavedOption),
new { style = "width:200px" });
}%>
If I only use one or the other without the if, the one menu works in only one scenario... I need to use "myOptionListName" as the name because I have other javascript that references this. As it stands now, the code above doesn't render any drop down.
Q: How can I get the menu to appear based on the if check but have the same name?
You can have your select list created based on your mode of the form and then assign the appropriate select list to your control.
<% var selectList = new SelectList(ViewBag.MyOptions, "Id", "Name");
if(!Model.isCreate)
{
selectList = new SelectList(ViewBag.MyOptions, "Id", "Name",
ViewBag.LastSavedOption);
}
Html.DropDownList("myOptionListName", selectList ,
new { style = "width:200px" });
%>
Related
I'm dynamically adding fields to my form. In case of a standard text field it works fine with Rails + StimulusJS with an <template> element.
I'm using MaterializeCSS for styling and if I try to do the same thing with a select menu, it breaks and it seems the innerHTML I get back in my JS code doesn't match the code inside the template-tag. So I decided to use a div instead which I duplicate.
The view code (the relevant part):
<div data-nested-form-target="template">
<%= f.fields_for :stations, department.stations.build, child_index: "NEW_RECORD" do |station| %>
<%= render "admin/departments/stations/station_fields", f: station %>
<% end %>
This is the template I duplicte in my Stimulus controller.
This is what the first (and working) select menu looks like in HTML:
So the next step was to change the ID'S of the <ul> and the two <li> elements + the data-target element to target the right select menu.
What I ended up was this JS-Code, which is indeed adding a second select menu with the right styles, but it is not clickable and doesn't show options, despite they exist in the HTML markup and the ID's differ from the first one:
add_association(event) {
event.preventDefault()
let random_id = this.generate_random_id()
let content = this.templateTarget.cloneNode(true)
content.getElementsByTagName("ul").item(0).setAttribute("id", random_id)
content.getElementsByClassName("dropdown-trigger").item(0).setAttribute("data-target", random_id)
let list_elements = Array.from(content.getElementsByTagName("ul").item(0).querySelectorAll("li"))
list_elements.forEach((item) => {
let rnd = this.generate_random_id()
item.setAttribute("id", rnd)
})
let html = content.innerHTML.replace(/NEW_RECORD/g, new Date().getTime())
this.linksTarget.insertAdjacentHTML("beforebegin", html)
console.log(html)
let collection = this.basicTarget.getElementsByClassName("nested-fields")
let element = collection[collection.length - 1].getElementsByClassName("animate__animated")[0]
element.classList.add(this.fadeInClass)
}
Now it looks like this and I can't figure out how to make this thing working:
We're using the same stack (Rails, Stimulus.js, Materialize) with no problem. One thing you have to do after updating the select is re-initialize the Materialize select. For example, this is a general purpose nested select controller in which different sub-selects become active based on the parent select:
import { Controller } from 'stimulus';
import Materialize from 'materialize-css';
export default class extends Controller {
static targets = [
'childSelect',
]
parentSelectChanged (e) {
const selectedParentMenu = e.target.value;
this.childSelectTargets.forEach(selectElement => {
if (selectElement.dataset.parentMenuName === selectedParentMenu) {
selectElement.disabled = false;
selectElement.value = '';
} else {
selectElement.disabled = true;
}
Materialize.FormSelect.init(selectElement);
});
}
}
And here is the corresponding Haml:
.input-field.col
= select_tag :category,
options_for_select(grouped_conditions.keys.sort, selected_parent_menu_name),
include_blank: 'Select One...',
data: { action: 'nested-select#parentSelectChanged' }
.input-field.col.hide-disabled
- grouped_conditions.each do |parent_menu_name, child_conditions|
= f.select :condition_id,
options_for_select(child_conditions.sort_by(&:menu_name).map{ |tc| [tc.menu_name, tc.id] }, selected_condition_id),
{ include_blank: 'Select One...' },
disabled: selected_parent_menu_name != parent_menu_name,
data: { 'nested-select-target' => 'childSelect', 'parent-menu-name' => parent_menu_name }
= f.label :condition_id
I'm trying to create a form that maps to an entity of the type "Participant". A participant is in a one-to-one relationship with a 'person'. Adding a participant, I want to first give the option to choose a person already in the database and if the right one doesn't exist, create that person with the participant form.
This works if I do it with two pages/forms. The first one trying to choose an existing person, otherwise open a new page with the different form.
First page:
$form->add('person', AjaxEntityType, [ // EntityType but with select2 ajax
'class' => Person::class,
'remote_route' => 'person_ajax_list'
]);
Second page:
$participant->setPerson(new Person());
$form->add('person', PersonType::class);
// adds PersonType fields to the Participant form
Well, that works, but it's terribly slow and unecessary. What I'd rather want is having BOTH of those shown, where the PersonType form fields (first name, last name, title, company, address, etc.) are automatically populated with the persons data, if one is selected. Otherwise, if no Person is selected and the form is submitted with data entered, a new Person should be created and persisted in the database.
It's sadly not possible to render the 'person' twice, once as a dropdown and once as a PersonType form. So how would I go about achieving what I want, without surreal amounts of JavaScript?
My current solution would be to manually create all the required fields with JavaScript and populate them with the person data I'd get with another Ajax request on a onchange event on the person dropdown, then in the PRE_SUBMIT event of the form, remove the 'person' field and add it again as a PersonType field, check if the entered data corresponds to an existing person or a new one and then act accordingly. There has to be a better solution, right?
Form events have sadly otherwise proven majorly pointless, as it's not possible to attach an event listener to a 'change' event on one of the fields.
Thanks.
Ended up solving it with an unmapped person choice field and javascript to automatically update the data (using ajax).
participant/add.twig:
{% block javascripts %}
<script type="text/javascript">
$(document).ready(function () {
function onTrainerChange() {
let trainerId = $('#participant_person_choose').val();
$.get(Routing.generate('person_data_ajax', { id: trainerId }), function (data) {
$('#participant_person_gender').val(data.gender);
$('#participant_person_title').val(data.title);
$('#participant_person_firstName').val(data.firstName);
$('#participant_person_lastName').val(data.lastName);
$('#participant_person_email').val(data.email);
$('#participant_person_telephone').val(data.telephone);
if (data.company) {
let company = $('#participant_person_company');
company.empty();
company.append(new Option(data.company.text, data.company.id));
company.val(data.company.id);
company.trigger('change');
// manipulate dom directly because of .select('data') bug with select2 >=4.0
}
});
};
let trainer = $('#participant_person_choose');
trainer.change(onTrainerChange);
});
</script>
{% endblock %}
ParticipantController add:
$participant = new Participant($seminar);
$person = $participant->getPerson() ?? new Person();
$participant->setPerson($person);
$form = $this->createParticipantForm($participant)
->add('person_choose', AjaxEntityType::class, [
'mapped' => false,
'class' => Person::class,
'remote_route' => 'person_select_ajax',
'placeholder' => 'form.personCreate',
'label' => 'form.person'
])
->add('person', PersonType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
if ($form->get('reservation')->getData()) {
$participant->setInterested();
}
$personEntered = $form->get('person')->getData();
$personChosen = $form->get('person_choose')->getData();
if ($personChosen) {
$person = $personChosen;
$person->setGender($personEntered->getGender());
$person->setTitle($personEntered->getTitle());
$person->setFirstName($personEntered->getFirstName());
$person->setFirstName($personEntered->getLastName());
$person->setCompany($personEntered->getCompany());
$person->setEmail($personEntered->getEmail());
$person->setTelephone($personEntered->getTelephone());
$participant->setPerson($person);
}
$this->getDoctrine()->getManager()->persist($person);
$this->getDoctrine()->getManager()->persist($participant);
}
PersonController Ajax:
/**
* #Route("/{id}/data", name="person_data_ajax", methods={"GET"}, options={"expose": true})
*/
public function dataAjax(Person $person, PhoneNumberHelper $phonenumberHelper)
{
$arr = [
'id' => $person->id,
'gender' => $person->getGender(),
'title' => $person->getTitle(),
'firstName' => $person->getFirstName(),
'lastName' => $person->getLastName(),
'email' => $person->getEMail(),
'telephone' => $person->getTelephone() ? $phonenumberHelper->format($person->getTelephone(), PhoneNumberFormat::NATIONAL) : null,
'company' => $person->getCompany() ? [
'id' => $person->getCompany()->id,
'text' => $person->getCompany()->__toString()
] : null
];
return new JsonResponse($arr);
}
Hope this can help someone else. Really disappointed with how limited Symfonys Forms are.
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)
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()" })
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.