Dans les applications web, il est fréquent de devoir mettre à jour dynamiquement une liste déroulante en fonction de la sélection d'une autre. Cet article détaille la création d'un plugin jQuery personnalisé pour gérer ces menus en cascade, conçu pour s'intégrer parfaitement avec ASP.NET via des requêtes AJAX renvoyant du JSON.
Structure HTML et ASP.NET
<asp:DropDownList ID="drpRegion" runat="server"></asp:DropDownList>
<asp:DropDownList ID="drpProvince" runat="server"></asp:DropDownList>
<asp:DropDownList ID="drpCity" runat="server"></asp:DropDownList>
Intégration des scripts
<script src="Scripts/jquery-3.6.0.min.js"></script>
<script src="Scripts/jquery.linkedSelects.js"></script>
Initialisation du plugin
Lorsqu'une sélection est faite dans la liste parente, la liste enfant est automatiquement peuplée :
$("#drpProvince").linkedSelects({
parentSelector: "#drpRegion",
endpoint: '/Handlers/LocationHandler.ashx',
labelKey: 'provName',
valueKey: 'provId'
});
$("#drpCity").linkedSelects({
parentSelector: "#drpProvince",
endpoint: '/Handlers/LocationHandler.ashx',
labelKey: 'cityName',
valueKey: 'cityId'
});
Format de la réponse JSON
{
"status": "success",
"payload": [
{ "provId": "14", "provName": "Jiangxi" },
{ "provId": "16", "provName": "Henan" },
{ "provId": "17", "provName": "Hubei" }
]
}
Paramètres de configuration
- parentSelector : Sélecteur jQuery du contrôle parent qui déclenche l'événement
change. - endpoint : URL du point de terminaison pour la requête POST.
- labelKey : Nom de la propriété JSON utilisée pour le texte affiché.
- valueKey : Nom de la propriété JSON utilisée pour la valeur de l'option.
- placeholderText : Texte de l'option par défaut (par défaut : "Sélectionnez une option").
- loadingText : Texte affiché pendant le chargement (par défaut : "Chargement...").
- errorText : Texte affiché en cas d'échec (par défaut : "Erreur de chargement").
- additionalData : Fonction retournant un objet de données supplémentaires à envoyer.
- onStart : Callback exécuté avant l'envoi de la requête.
- onComplete : Callback exécuté après le chargement réussi des données.
Traitement côté serveur (C#)
string parentId = context.Request.Params["drpProvince"];
if (!string.IsNullOrEmpty(parentId))
{
string query = $"SELECT Id, Name FROM Cities WHERE ProvinceId = @parentId";
// Exécution de la requête et sérialisation en JSON
}
string regionId = context.Request.Params["drpRegion"];
if (!string.IsNullOrEmpty(regionId))
{
string query = $"SELECT Id, Name FROM Provinces WHERE RegionId = @regionId";
// Exécution de la requête et sérialisation en JSON
}
Code source du plugin jQuery
(function($) {
$.fn.linkedSelects = function(options) {
var defaults = {
parentSelector: null,
endpoint: null,
labelKey: 'text',
valueKey: 'value',
placeholderText: '-- Choisissez --',
loadingText: 'Chargement...',
errorText: 'Erreur',
additionalData: null,
onStart: null,
onComplete: null
};
var settings = $.extend({}, defaults, options);
return this.each(function() {
var $target = $(this);
var $parent = $(settings.parentSelector);
if (!$parent.length) {
console.error("Sélecteur parent introuvable.");
return;
}
function clearOptions() {
$target.empty().prop('disabled', true);
}
function setPlaceholder(text) {
clearOptions();
$target.append($('<option>', { value: '', text: text }));
}
function resetList() {
setPlaceholder(settings.placeholderText);
$target.trigger('change');
}
function showLoading() {
setPlaceholder(settings.loadingText);
}
function showError() {
setPlaceholder(settings.errorText);
}
function enableList() {
$target.prop('disabled', false);
$target.trigger('change');
}
function fetchData() {
showLoading();
if (typeof settings.onStart === 'function') settings.onStart.call($target);
var requestData = $parent.serialize();
if (typeof settings.additionalData === 'function') {
requestData += '&' + $.param(settings.additionalData());
}
$.ajax({
url: settings.endpoint,
type: 'POST',
dataType: 'json',
data: requestData,
success: function(response) {
resetList();
if (response.payload && response.payload.length > 0) {
$.each(response.payload, function(index, item) {
$target.append($('<option>', {
value: item[settings.valueKey],
text: item[settings.labelKey]
}));
});
}
enableList();
if (typeof settings.onComplete === 'function') settings.onComplete.call($target);
},
error: function() {
showError();
}
});
}
$parent.on('change', function() {
if ($(this).val() !== '') {
fetchData();
} else {
resetList();
}
});
if ($target.children().length === 0) {
resetList();
}
});
};
})(jQuery);