novembre
2008
J’ai récemment eu besoin de remplacer une liste déroulante par une ComboBox dans l’une de mes applis web.
Pour rappel, une ComboBox est une liste déroulante qui offre également la possibilité de saisir le texte de son choix (une liste déroulante ne permet que de sélectionner l’un de ses items).
Ma liste déroulante proposait une série de nombres compris entre 1 et 1000 avec un pas qui augmentait régulièrement (pas de 1 entre 0 et 100, pas de 10 entre 100 et 500 et pas de 25 entre 500 et 1000). Il fallait offrir à l’utilisateur la possibilité de saisir une valeur non existante dans la liste déroulante (108 par exemple).
Après quelques recherches sur internet, je n’avais trouvé que des composants payants ou qui ne fonctionnaient pas vraiment. En regardant du côté de l’AJAX Control Toolkit, je suis tombé sur le DropDownExtender (démo). Je me suis dit que ça pouvait faire l’affaire et je suis parti dans cette direction.
Le principe du DropDownExtender est d’afficher un contrôle lors du clic le contrôle qu’étend le DropDownExtender.
Pour réaliser une ComboBox, on va utiliser une TextBox que l’on va étendre avec le DropDownExtender et on va faire afficher un Panel. Ce panel contiendra les différents items initialement contenus dans la liste déroulante. Afin de remplir la TextBox avec la valeur de l’item sélectionné dans le Panel, chaque item se présentera sous la forme d’un lien et on appellera une fonction javascript sur le onclick pour remplir la TextBox.
Définition de la TextBox, du DropDownExtender et du Panel:
<!– La TextBox –>
<asp:TextBox ID=« tbSpread » runat=« server » Width=« 143px »>0</asp:TextBox>
<!– Le DropDownExtender –>
<act:DropDownExtender
ID= »ddeSpread »
runat= »server »
TargetControlID= »tbSpread »
DropDownControlID= »pnlSpread »
HighlightBackColor= »white »>
</act:DropDownExtender>
<!– Le Panel –>
<asp:Panel
ID= »pnlSpread »
runat= »server »
Height= »200px »
ScrollBars= »Vertical »
CssClass= »ContextMenuPanel »
style= »display: none; visibility: hidden; »>
<asp:Literal ID= »ltSpread » runat= »server » EnableViewState= »false »>
</asp:Literal>
</asp:Panel>
Concernant la TextBox, rien à dire.
Concernant le DropDowExtender:
– TargetControlID indique quel contrôle étendre, c’est-à-dire quel est le contrôle sur lequel un clic entrainera l’affichage d’un autre contrôle.
– DropDownControlID correspond à l’ID du contrôle qui sera affiché
Concernant le panel, il contient un Literal qui contiendra les liens correspondants aux items à afficher.
Remplissage du panel (code behind):
private void FillPanel()
{
var sb = new StringBuilder();
// Pas = 1 entre 0 à 100.
for (var i = 0; i < 101; i++)
{
sb.AppendFormat(GenerateLink(i));
}
// Pas = 10 entre 100 et 500.
for (var i = 110; i < 501; i += 10)
{
sb.AppendFormat(GenerateLink(i));
}
// Pas = 25 entre 500 et 1000.
for (var i = 525; i < 1001; i += 25)
{
sb.AppendFormat(GenerateLink(i));
}
ltSpread.Text = sb.ToString();
}
private string GenerateLink(int i)
{
var link = @"<a href=""javascript:OnItemSelected({0});"" class=""ContextMenuItem"">{0}</a>";
return string.Format(link, i);
}
On génère directement le code html correspondant aux items et on l’injecte dans le literal.
On remarque que le click sur un item appelle la fonction javascript OnItemSelected:
function OnItemSelected(value)
{
$get(‘<%= tbSpread.ClientID %>’).value = value;
}
Cette fonction permet de remplir la TextBox avec la valeur de l’item sélectionné.
A ce niveau là, on devrait avoir une ComboBox fonctionnelle.
Voyons ce que cela donne en affichant la page:
On constate 3 problèmes:
(1) le Panel n’a pas la même longueur que la TextBox
(2) le Panel n’est pas aligné avec la TextBox
(3) le Panel apparaît en-dessous des listes déroulantes existantes
Pour le problème (1), on va utiliser un bout de code javascript qui va récupérer la longueur de la TextBox et l’appliquer au Panel:
function pageLoad()
{
// On adapte la taille du panel à la taille de la textbox
$get(‘<%= pnlSpread.ClientID %>’).style.width = $get(‘<%= tbSpread.ClientID %>’).clientWidth;
}
La fonction pageLoad est une fonction proposée par ASP.NET AJAX et qui est automatiquement appelée à chaque chargement de la page.
Maintenant, on obtient:
Prochaine étape: aligner le Panel avec la TextBox (problème (2).
On va encore utiliser un bout de code javascript:
function pageLoad()
{
// On adapte la taille du panel à la taille de la textbox
$get(‘<%= pnlSpread.ClientID %>’).style.width = $get(‘<%= tbSpread.ClientID %>’).clientWidth;
// On aligne le côté gauche du panel avec le côté gauche de la textbox
var chngPosition = $find(‘<%= ddeSpread.ClientID %>’)._dropPopupPopupBehavior;
chngPosition.set_positioningMode(2);
}
On obtient alors:
Le dernier problème à régler est dû à un bug bien connu de IE (une explication en français ici) . En effet, ce dernier ne gère pas correctement le z-index avec certains éléments HTML et du coup les listes déroulantes s’affichent au-dessus de notre Panel ici.
Une solution souvent évoquée est d’utiliser une IFRAME. Ne trouvant pas ça très « propre », je me suis orienté vers une solution javascript. L’idée est de masquer les listes déroulantes lors de l’affichage du Panel et de les réafficher lors du masquage de ce dernier.
Afficher/masquer un élément HTML en javascript se fait très simplement via la fonction suivante:
function ShowHideControl(control, toShow)
{
if(control == null)
return;
if(toShow)
control.style.visibility = ‘visible';
else
control.style.visibility = ‘hidden';
}
La seule difficulté ici est de détecter l’affichage/masquage du Panel. Pour cela, il suffit de trouver le nom des événements levés par le DropDownExtender et s’y abonner. Dans un précédent billet, j’ai donné une astuce pour obtenir facilement ces événements. Les 2 événements qui nous intéressent ici sont donc « showing » et « hiding ». Il ne nous reste plus qu’à s’y abonner et afficher/masquer les listes déroulantes. Le code javascript est le suivant:
var bhvDde = $find(‘<%= ddeSpread.ClientID %>’);
bhvDde.add_showing(ShowPanelSpread);
bhvDde.add_hiding(HidePanelSpread);
function ShowPanelSpread()
{
ShowHideControl($get(‘<%= ddlPaymentFrequency.ClientID %>’), false);
ShowHideControl($get(‘<%= ddlBasis.ClientID %>’), false);
ShowHideControl($get(‘<%= ddlRoll.ClientID %>’), false);
ShowHideControl($get(‘<%= ddlAdjustment.ClientID %>’), false);
ShowHideControl($get(‘<%= ddlFixingFrequency.ClientID %>’), false);
ShowHideControl($get(‘<%= ddlFixing.ClientID %>’), false);
}
function HidePanelSpread()
{
ShowHideControl($get(‘<%= ddlPaymentFrequency.ClientID %>’), true);
ShowHideControl($get(‘<%= ddlBasis.ClientID %>’), true);
ShowHideControl($get(‘<%= ddlRoll.ClientID %>’), true);
ShowHideControl($get(‘<%= ddlAdjustment.ClientID %>’), true);
ShowHideControl($get(‘<%= ddlFixingFrequency.ClientID %>’), true);
ShowHideControl($get(‘<%= ddlFixing.ClientID %>’), true);
}
Le résultat en image:
Have Fun!!!