Alimenter un composant enfant dans un Datagrid ou un Gridview
Les événements d'un contrôle enfant d'un Gridview ou d'un DataGrid
Lever un événement serveur depuis un script javascript
Une page ASP est composée d'un certain nombre de contrôles qui vont réagir entre eux par l'intermédiaire d'événements. Le fonctionnement de cet ensemble dépend étroitement de notre capacité à savoir les exploiter de façon adéquate.
En tant que développeur, les problèmes les plus typiques à résoudre sont:
Ils répondent tous à un certain nombre de règles dont la connaissance vous fera gagner beaucoup de temps, le temps que vous consommez à vous débattre avec le fonctionnement de votre page.
Commençons par une classe utilitaire Binome utilisée pour toute la suite afin d'alimenter nos contrôles. La classe Binome expose deux propriétés Libelle et Valeur ainsi qu'une méthode static/Shared qui retourne une collection de Binome.
Listing 6-1: Alimentation des combos:
Code C#
using System.Collections.Generic; public class Binome { private string _Libelle; public string Libelle { get { return _Libelle; } set { _Libelle = value; } } private string _Valeur; public string Valeur { get { return _Valeur; } set { _Valeur = value; } } public static List<Binome> ObtenirCollection(string prefix) { List<Binome> Liste = new List<Binome>(); for (int i = 0; i < 20; i++) { Binome Element = new Binome(); Element.Libelle = prefix + i.ToString(); Element.Valeur=i.ToString(); Liste.Add(Element); } return Liste; } }
Code VB
Imports System.Collections.Generic Public Class Binome Private _Libelle As String Public Property Libelle() As String Get Return _Libelle End Get Set(ByVal value As String) _Libelle = value End Set End Property Private _Valeur As String Public Property Valeur() As String Get Return _Valeur End Get Set(ByVal value As String) _Valeur = value End Set End Property Public Shared Function ObtenirCollection(ByVal prefix As String) As List(Of Binome) Dim Liste As List(Of Binome) = New List(Of Binome) For i As Integer = 0 To 19 Dim Element As Binome = New Binome() Element.Libelle = prefix + i.ToString() Element.Valeur = i.ToString() Liste.Add(Element) Next Return Liste End Function End Class
Les règles d'alimentation permettent de résoudre les questions relatives à l'alimentation d'un contrôle. Nous allons prendre l'exemple d'une liste déroulante, mais tout autre contrôle convient aussi bien.
Listing 6-2: Une combo sur une page
<html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Untitled Page</title> </head> <body> <form id="form1" runat="server"> <div> <asp:dropdownlist DataTextField="Libelle" DataValueField="Valeur" ID="DropDownList1" runat="server"> </asp:dropdownlist> <asp:button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click" /></div> </form> </body> </html>
Pour faire fonctionner le formulaire on doit ajouter dans l'événement Load ou Init de la page le code suivant:
Listing 6-3: Code behind du listing précédent
Code C#
protected void Page_Load(object sender, EventArgs e){ if (!this.IsPostBack) { this.DropDownList1.DataSource = Binome.ObtenirCollection("ddl1_"); this.DropDownList1.DataBind(); } }
Code VB
Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) If Not Me.IsPostBack Then Me.DropDownList1.DataSource = Binome.ObtenirCollection("ddl1_") Me.DropDownList1.DataBind End If End Sub
La règle est la suivante:
L'alimentation d'un contrôle se fait lors des événements Load ou Init de la page et uniquement lors du premier chargement de la page.
Lors du premier chargement de la page on alimente la
combo. Puisque l'on est après le Init du
composant, on copie dans le viewstate le contenu de ce
combo.
Ultérieurement on fait une sélection et on poste la
page. celle-ci va donc successivement recharger le combo
depuis le viewstate, puis restaurer la sélection à
l'aide des informations trouvées dans le paquet HTTP POST. Si on alimentait à nouveau le combo dans
l'événement Load, alors on perdrait aussitôt la
sélection puisqu'elle est restaurée avant.
Dans le cas où rechercher les informations du contrôle n'est pas coûteux, il est possible d'optimiser le viewstate en alimentant le contrôle dans son événement Init. Nous avons vu dans l'article sur le viewstate comment faire.
Evidemment l'alimentation doit se faire à chaque renvoi de la page cette fois, d'où la nouvelle règle:
L'alimentation d'un contrôle lors de l'événement Init du contrôle se fait à chaque appel.
Note:
On parle bien de l'événement Init
du contrôle,
pas de la page. Souvenez vous du diagramme tracé dans
l'article sur le viewstate.
Listing 6-4: Règle d'alimentation dans OnInit
Code C#
protected override void OnInit(EventArgs e){ this.DropDownList1.DataSource = Binome.ObtenirCollection("ddl1_"); this.DropDownList1.DataBind(); }
Code VB
Protected OverLoads Sub OnInit(ByVal e As EventArgs) Me.DropDownList1.DataSource = Binome.ObtenirCollection("ddl1_") Me.DropDownList1.DataBind End Sub
Le contenu d'un contrôle peut être lié à une sélection dans un autre composant comme une liste déroulante. Compliquons alors notre exemple avec 2 combos, mais dont le contenu du second dépend de celui du premier.
Listing 6-5: Deux combos
<html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Untitled Page</title> </head> <body> <form id="form1" runat="server"> <div> <asp:dropdownlist AutoPostBack="True OnSelectedIndexChanged="dd1_SelectedIndexChanged" ID="dd1" runat="server"" DataTextField="Libelle" DataValueField="Valeur"> </asp:dropdownlist> <asp:dropdownlist ID="ddl2" runat="server" DataTextField="Libelle" DataValueField="Valeur"> </asp:dropdownlist><br /> <br /> <asp:button ID="Button1" runat="server" Text="Button" /></div> </form> </body> </html>
Code C#
protected void Page_Load(object sender, EventArgs e) { if (!this.IsPostBack) { // on applique la règle d'alimentation des combos this.dd1.DataSource = Binome.ObtenirCollection("ddl1_"); this.dd1.DataBind(); this.ddl2.DataSource = Binome.ObtenirCollection("sel" + dd1.SelectedIndex + "_"); this.ddl2.DataBind(); } } protected void dd1_SelectedIndexChanged(object sender, EventArgs e) { // on applique la règle de synchronisation du combo lié this.ddl2.DataSource = Binome.ObtenirCollection("sel" + dd1.SelectedIndex + "_"); this.ddl2.DataBind(); }
Code VB
Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) If Not Me.IsPostBack Then ' on applique la règle d'alimentation des combos Me.dd1.DataSource = Binome.ObtenirCollection("ddl1_") Me.dd1.DataBind Me.ddl2.DataSource = Binome.ObtenirCollection("sel" + dd1.SelectedIndex + "_") Me.ddl2.DataBind End If End Sub Protected Sub dd1_SelectedIndexChanged(ByVal sender As Object, ByVal e As EventArgs) ' on applique la règle de synchronisation du combo lié Me.ddl2.DataSource = Binome.ObtenirCollection("sel" + dd1.SelectedIndex + "_") Me.ddl2.DataBind End Sub
La règle illustrée par l'exemple précédent est la suivante:
La synchronisation d'un contrôle lié se fait depuis l'événement levé lorsque son état change.
Un problème classique, un contrôle dans un GridView a besoin d'être alimenté avec le contenu d'une autre table. Typiquement se sera le cas d'une combo qui charge des valeurs de référence.
Il y a plusieurs façons de résoudre ce problème comme créer un contrôle personnalisé qui charge lui même son contenu. Cette solution permet de mettre en place une optimisation éventuelle du viewstate en alimentant le contrôle dans son événement Init.
On peut aussi plus directement remplir le contrôle (une combo dans notre exemple) depuis les événements du GridView:
Listing 6-6: Alimenter une combo dans un GridView
<html xmlns="http://www.w3.org/1999/xhtml"> <body> <form id="form1" runat="server"> <div> <asp:gridview OnRowDataBound="GridView1_RowDataBound" ID="GridView1" runat="server" AutoGenerateColumns="False"> <columns> <asp:boundfield DataField="Libelle" /> <asp:templatefield> <edititemtemplate> <asp:textbox ID="TextBox1" runat="server"></asp:textbox> </edititemtemplate> <itemtemplate> <asp:dropdownlist ID="DropDownList1" runat="server" DataTextField="Libelle" DataValueField="Valeur"> </asp:dropdownlist> </itemtemplate> </asp:templatefield> </columns> </asp:gridview> </div> </form> </body> </html>
Code C#
protected void Page_Load(object sender, EventArgs e) { if (!this.IsPostBack) { // règle d'alimentation this.GridView1.DataSource = Binome.ObtenirCollection("grille_"); this.GridView1.DataBind(); } } protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e) { if (e.Row.RowType == DataControlRowType.DataRow) { DropDownList Ddl = e.Row.FindControl("DropDownList1") as DropDownList; Ddl.DataSource = Binome.ObtenirCollection("ddl_"); Ddl.DataBind(); } }
Code VB
Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) If Not Me.IsPostBack Then ' règle d'alimentation Me.GridView1.DataSource = Binome.ObtenirCollection("grille_") Me.GridView1.DataBind End If End Sub Protected Sub GridView1_RowDataBound(ByVal sender As Object, ByVal e As GridViewRowEventArgs) If e.Row.RowType = DataControlRowType.DataRow Then Dim Ddl As DropDownList = CType(ConversionHelpers.AsWorkaround(e.Row.FindControl("DropDownList1"), GetType(DropDownList)), DropDownList) Ddl.DataSource = Binome.ObtenirCollection("ddl_") Ddl.DataBind End If End Sub
Il est toujours plus propre d'éviter de placer du code inline dans une page ASPX. Ce n'est pas sa place et
contrairement à une légende cela n'est en rien plus
rapide à l'exécution.
Le bon endroit est
d'intercepter l'événement RowDataBound pour le
GridView et DataItemBound pour le DataGrid.
Ces événements sont levés au moment où la ligne est liée
aux données. On trouvera en bibliographie une
comparaison entre ces deux composants [35].
Il est important de comprendre que l'événement est levé pour chaque line du tableau, y compris l'entête. D'où le test de début de code.
Dans le cas d'un DataGrid les événements et le code diffèrent légèrement:
Listing 6-7: Cas du DataGrid
Code C#
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem) { // alimentation du composant }
Code VB
If e.Item.ItemType = ListItemType.Item OrElse e.Item.ItemType = ListItemType.AlternatingItem Then ' alimentation du composant End If
Le test de début se justifie parce que DataItemBound distingue les lignes paires des lignes impaires. Si vous n'en tenez pas compte, vous ne remplirez qu'une ligne sur deux.
Une alternative est celle-ci:
Listing 6-8: Alternative aux codes précédents
<asp:dropdownlist OnInit="Initialisation" ID="DropDownList1" runat="server" DataTextField="Libelle" DataValueField="Valeur"> </asp:dropdownlist>
Code C#
protected void Initialisation(object sender,EventArgs e) { DropDownList Ddl = sender as DropDownList; Ddl.DataSource = Binome.ObtenirCollection("ddl_"); Ddl.DataBind(); }
Code VB
Protected Sub Initialisation(ByVal sender As Object, ByVal e As EventArgs) Dim Ddl As DropDownList = CType(ConversionHelpers.AsWorkaround(sender, GetType(DropDownList)), DropDownList) Ddl.DataSource = Binome.ObtenirCollection("ddl_") Ddl.DataBind End Sub
Dans tous les cas on obtient ceci:

Vous devez maintenant savoir ce que cette solution implique...
Cette fois le contrôle enfant lève un événement. Nous souhaitons l'intercepter. Nous allons prendre le cas d'un DropDownList.
J'ai depuis toujours l'habitude d'utiliser les événements tels DataItemBound ou RowItemBound pour alimenter mes grilles. L'avantage que j'y trouve est que l'on peut mieux contrôler ce qui s'affiche dans le contrôle qu'en utilisant du code inline. C'est donc tout naturellement que j'ai essayé d'abonner un gestionnaire d'événement à mon combo depuis un des événements précédents... Il se trouve que cela ne fonctionne pas.
J'ai depuis découvert deux solutions à cette difficulté. La première consiste à effectuer l'abonnement depuis DataItemCreated (DataGrid) ou RowCreated (GridView). Cet événement se produit après ItemDataBound/RowDataBound au moment où la création de la ligne est terminée.
Dans le cas du GridView, mais c'est transposable directement au DataGrid le code est le suivant:
Listing 6-9: Intercepter les événements d'un contrôle enfant
<asp:gridview OnRowCreated="GridView1_RowCreated" OnRowDataBound="GridView1_RowDataBound" ID="GridView1" runat="server" AutoGenerateColumns="False"> <columns> <asp:boundfield DataField="Libelle" /> <asp:templatefield> <edititemtemplate> </edititemtemplate> <itemtemplate> <asp:dropdownlist ID="DropDownList1" AutoPostBack="true" runat="server" DataTextField="Libelle" DataValueField="Valeur"> </asp:dropdownlist> </itemtemplate> </asp:templatefield> </columns> </asp:gridview>
Code C#
protected void GridView1_RowCreated(object sender, GridViewRowEventArgs e) { if (e.Row.RowType == DataControlRowType.DataRow) { DropDownList Ddl = e.Row.FindControl("DropDownList1") as DropDownList; Ddl.SelectedIndexChanged += new EventHandler(Ddl_SelectedIndexChanged); } } void Ddl_SelectedIndexChanged(object sender, EventArgs e) { DropDownList Ddl = sender as DropDownList; Response.Write(string.Format("Sélectionné: {0}", Ddl.SelectedValue)); }
Code VB
Protected Sub GridView1_RowCreated(ByVal sender As Object, ByVal e As GridViewRowEventArgs) If e.Row.RowType = DataControlRowType.DataRow Then Dim Ddl As DropDownList = CType(ConversionHelpers.AsWorkaround(e.Row.FindControl("DropDownList1"), GetType(DropDownList)), DropDownList) Ddl.SelectedIndexChanged += New EventHandler(Ddl_SelectedIndexChanged) End If End Sub Sub Ddl_SelectedIndexChanged(ByVal sender As Object, ByVal e As EventArgs) Dim Ddl As DropDownList = CType(ConversionHelpers.AsWorkaround(sender, GetType(DropDownList)), DropDownList) Response.Write(String.Format("Sélectionné: {0}", Ddl.SelectedValue)) End Sub
Notez l'attribut AutoPostBack qui est à true pour avoir un événement immédiat. Mais ce code marche aussi bien avec un code différé.
Si l'on n'a pas besoin de câbler dynamiquement le gestionnaire d'événement, il existe une autre solution que nous avons en fait déjà vu au chapitre précédent:
Listing 6-10: Autre solution
<asp:gridview OnRowCreated="GridView1_RowCreated" OnRowDataBound="GridView1_RowDataBound" ID="GridView1" runat="server" AutoGenerateColumns="False"> <columns> <asp:boundfield DataField="Libelle" /> <asp:templatefield> <itemtemplate> <asp:dropdownlist OnSelectedIndexChanged="Ddl_SelectedIndexChanged" ID="DropDownList1" runat="server" AutoPostBack="true" DataTextField="Libelle" DataValueField="Valeur"> </asp:dropdownlist> </itemtemplate> </asp:templatefield> </columns> </asp:gridview>
On câble le gestionnaire de façon déclarative dans la page ASPX. Le résultat est exactement le même.
On peut aussi avoir besoin de connaître la ligne sur laquelle l'événement s'est produit. Il existe diverses solutions applicables ou non selon le contrôle qui lève l'événement.
Certains contrôles disposent d'un événement Command. Ce sont ceux qui implémentent IButtonControl comme Button, LinkButton, ImageButton, mais aussi GridView et DataList et d'autres encore.
Listing 6-11: Utilisation de l'événement Command
<asp:gridview OnRowCommand="GridView1_RowCommand" ID="GridView1" runat="server" AutoGenerateColumns="False" OnRowDataBound="GridView1_RowDataBound"> <columns> <asp:boundfield DataField="Libelle" /> <asp:templatefield> <itemtemplate> <asp:button ID="Button2" runat="server" Text="Button" /> </itemtemplate> </asp:templatefield> </columns> </asp:gridview>
Code C#
protected void Page_Load(object sender, EventArgs e) { if (!this.IsPostBack) { // règle d'alimentation this.GridView1.DataKeyNames = new string[] { "Valeur" }; this.GridView1.DataSource = Binome.ObtenirCollection("grille_"); this.GridView1.DataBind(); } } protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e) { if (e.Row.RowType == DataControlRowType.DataRow) { Button Bouton = e.Row.FindControl("Button2") as Button; Bouton.CommandName = "clicBouton"; Bouton.CommandArgument = GridView1.DataKeys[e.Row.RowIndex].Value as string; } } protected void GridView1_RowCommand(object sender, GridViewCommandEventArgs e) { if (e.CommandName == "clicBouton") { Button Bouton = e.CommandSource as Button; Response.Write(string.Format("Id de la ligne: {0}, contrôle source: {1}", e.CommandArgument,Bouton.ClientID)); } }
Code VB
Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) If Not Me.IsPostBack Then Me.GridView1.DataKeyNames = New String() {"Valeur"} Me.GridView1.DataSource = Binome.ObtenirCollection("grille_") Me.GridView1.DataBind End If End Sub Protected Sub GridView1_RowDataBound(ByVal sender As Object, ByVal e As GridViewRowEventArgs) If e.Row.RowType = DataControlRowType.DataRow Then Dim Bouton As Button = CType(ConversionHelpers.AsWorkaround(e.Row.FindControl("Button2"), GetType(Button)), Button) Bouton.CommandName = "clicBouton" Bouton.CommandArgument = CType(ConversionHelpers.AsWorkaround(GridView1.DataKeys(e.Row.RowIndex).Value, GetType(String)), String) End If End Sub Protected Sub GridView1_RowCommand(ByVal sender As Object, ByVal e As GridViewCommandEventArgs) If e.CommandName = "clicBouton" Then Dim Bouton As Button = CType(ConversionHelpers.AsWorkaround(e.CommandSource, GetType(Button)), Button) Response.Write(String.Format("Id de la ligne: {0}, contrôle source: {1}", e.CommandArgument, Bouton.ClientID)) End If End Sub
Tout commence dans Load. On déclare la
propriété Binome.Valeur comme identifiant de
chaque ligne.
Dans RowDataBound on alimente les propriétés
nécessaires à l'événement Command du bouton. A
chaque clic sur le bouton celui-ci sera levé et
intercepté par GridView dans son événement
RowCommand qui se contente d'affiche un message de
passage.

On pourrait également faire l'interception au niveau de la page en surchargeant l'événement OnBubbleEvent. Nous verrons plus en détail cette technique dans la partie consacrée à l'architecture serveur.
Les choses se corsent pour les autres contrôles. Il existe diverses solution, celle présentée à l'avantage d'être toujours possible. Nous prendrons le cas d'un DropDownList:
Listing 6-12: Evénement d'un contrôle enfant:
<html xmlns="http://www.w3.org/1999/xhtml"> <script language="javascript"> function JeVousEcoute(id,controle) { alert(id); alert(controle.selectedIndex); } </script> <body> <form id="form1" runat="server"> <asp:gridview OnRowCommand="GridView1_RowCommand" ID="GridView1" runat="server" AutoGenerateColumns="False" OnRowDataBound="GridView1_RowDataBound"> <columns> <asp:boundfield DataField="Libelle" /> <asp:templatefield> <itemtemplate> <asp:dropdownlist DataTextField="Libelle" DataValueField="Valeur" ID="DropDownList1" runat="server"> </asp:dropdownlist> </itemtemplate> </asp:templatefield> </columns> </asp:gridview> </form> </body> </html>
Code C#
protected void Page_Load(object sender, EventArgs e) { if (!this.IsPostBack) { // règle d'alimentation this.GridView1.DataKeyNames = new string[] { "Valeur" }; this.GridView1.DataSource = Binome.ObtenirCollection("grille_"); this.GridView1.DataBind(); } } protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e) { if (e.Row.RowType == DataControlRowType.DataRow) { DropDownList Ddl = e.Row.FindControl("DropDownList1") as DropDownList; Ddl.DataSource = Binome.ObtenirCollection("ddl_"); Ddl.DataBind(); string Modele = "javascript:JeVousEcoute('{0}',this);"; Ddl.Attributes.Add("onchange",string.Format(Modele,this.GridView1.DataKeys[e.Row.RowIndex].Value)); } } protected void GridView1_RowCommand(object sender, GridViewCommandEventArgs e) { if (e.CommandName == "clicBouton") { Button Bouton = e.CommandSource as Button; Response.Write(string.Format("Id de la ligne: {0}, contrôle source: {1}", e.CommandArgument,Bouton.ClientID)); } }
Code VB
Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) If Not Me.IsPostBack Then Me.GridView1.DataKeyNames = New String() {"Valeur"} Me.GridView1.DataSource = Binome.ObtenirCollection("grille_") Me.GridView1.DataBind End If End Sub Protected Sub GridView1_RowDataBound(ByVal sender As Object, ByVal e As GridViewRowEventArgs) If e.Row.RowType = DataControlRowType.DataRow Then Dim Ddl As DropDownList = CType(ConversionHelpers.AsWorkaround(e.Row.FindControl("DropDownList1"), GetType(DropDownList)), DropDownList) Ddl.DataSource = Binome.ObtenirCollection("ddl_") Ddl.DataBind Dim Modele As String = "javascript:JeVousEcoute('{0}',this);" Ddl.Attributes.Add("onchange", String.Format(Modele, Me.GridView1.DataKeys(e.Row.RowIndex).Value)) End If End Sub Protected Sub GridView1_RowCommand(ByVal sender As Object, ByVal e As GridViewCommandEventArgs) If e.CommandName = "clicBouton" Then Dim Bouton As Button = CType(ConversionHelpers.AsWorkaround(e.CommandSource, GetType(Button)), Button) Response.Write(String.Format("Id de la ligne: {0}, contrôle source: {1}", e.CommandArgument, Bouton.ClientID)) End If End Sub
L'idée est simplement de lancer un script javascript sur l'événement OnChange de la combo (fonction JeVousEcoute). C'est ensuite le rôle de ce script de lever un l'événement serveur comme on verra dans le chapitre qui suit.
Le code est assez explicite, remarquez juste comment on déclare l'appel à OnChange et la façon dont on passe les paramètres voulus: l'Id de la ligne et le contrôle qui lève l'événement.
Nous verrons lors de l'étude de la partie serveur de ce tutoriel une autre méthode pour obtenir les même résultats.
On peut avoir besoin de lever un événement depuis un
javascript. Sous sa forme la plus simple un événement
est un appel à submit(). Mais là où les choses
deviennent intéressantes c'est de pouvoir passer des
paramètres. On utilise traditionnellement des champs
cachés qui sont lus dans le paquet HTTP.
ASP propose en standard une telle architecture basé sur l'appel d'une méthode
__doPostBack():
Listing 6-13: Architecture postback
input type="hidden" name="__EVENTTARGET" id="__EVENTTARGET" value="" />
<input type="hidden" name="__EVENTARGUMENT" id="__EVENTARGUMENT" value="" />
<script type="text/javascript">
<!--
var theForm = document.forms['form1'];
if (!theForm)
{
theForm = document.form1;
}
function __doPostBack(eventTarget, eventArgument)
{
if (!theForm.onsubmit || (theForm.onsubmit() != false))
{
theForm.__EVENTTARGET.value = eventTarget;
theForm.__EVENTARGUMENT.value = eventArgument;
theForm.submit();
}
}
// -->
</script>
La méthode attend deux paramètres:
| eventTarget | ID du contrôle qui est responsable du renvoi de la page |
| eventArgument | Argument complémentaire facultatif utilisé par l'événement |
Ceux-ci sont recopiés dans des champs cachés que le
paquet HTTP
postera et que nous pourrons lire côté serveur.
Examinons un peu la ligne avec le if qui a été
ajoutée depuis ASP 2.0[34].
L'idée est de permettre à une fonction javascript d'être
exécutée avant que le formulaire soit posté vers le
serveur. Normalement on intercepte
l'événement onsubmit du formulaire. Malheureusement cela
ne fonctionne qu'avec les boutons de type submit
et échoue si on lance depuis un script la fonction
submit() comme le démontre l'exemple suivant:
Listing 6 - 14: Une fonction qui s'exécute avant l'envoi de la page... mais ne marche pas
<html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <script language="javascript" type="text/javascript"> <!-- function Button1_onclick() { document.form1.submit(); } function maFonction() { alert("hello"); } // --> </script> </head> <body> <form id="form1" runat="server" onsubmit="javascript:maFonction();"> <input onclick="return Button1_onclick();" id="Button1" type="button" value="button" language="javascript" /> </form> </body> </html>
La nouvelle syntaxe __doPostBack prend plus de précautions et poste la page s'il n'y a pas de méthode onsubmit (premier test) ou bien s'il y a une méthode onsubmit et qu'elle a renvoyée true (deuxième test). Cela évite les bidouilles diverses que l'on faisait auparavant.
Comment utiliser __doPostBack dans un script? Le plus simple est d'examiner un exemple:
Notre exemple démontre deux façons d'utiliser __doPostBack:
Listing 6- 15: Exemple d'utilisation de __doPostBack
<script language="javascript" type="text/javascript"> <!-- function Traitement(cible,argument) { __doPostBack(cible,argument); } // --> </script> <html xmlns="http://www.w3.org/1999/xhtml" > <body> <form id="form1" runat="server"> <input onclick="javascript:Traitement('Button1','Input');" id="Button1" type="button" value="button1" /> <a href="javascript:Traitement('button2','ancre');">Simule un click sur button2</a> <br /> <br /> <asp:button ID="Button2" runat="server" OnClick="Button2_Click" Text="Button2" /> </form> </body> </html>
Code C#
protected override void OnInit(EventArgs e) { Page.ClientScript.GetPostBackEventReference(new System.Web.UI.PostBackOptions(this)); base.OnInit(e); } private void reponseAuRenvoi() { if (! string.IsNullOrEmpty(Request["__EVENTTARGET"])) { string Cible = Request["__EVENTTARGET"]; string Argument = Request["__EVENTARGUMENT"]; Response.Write(string.Format("Cible: {0},Argument: {1}<br/>",Cible,Argument)); } } protected void Page_Load(object sender, EventArgs e) { reponseAuRenvoi(); } protected void Button2_Click(object sender, EventArgs e) { Response.Write("Button2 cliqué<br/>"); } protected override void Render(HtmlTextWriter writer) { // l'argument est sensible à la casse this.Page.ClientScript.RegisterForEventValidation(this.Button2.UniqueID, "ancre"); base.Render(writer); }
Code VB
Protected Overloads Overrides Sub OnInit(ByVal e As EventArgs) Page.ClientScript.GetPostBackEventReference(New System.Web.UI.PostBackOptions(Me)) MyBase.OnInit(e) End Sub Private Sub reponseAuRenvoi() If Not String.IsNullOrEmpty(Request("__EVENTTARGET")) Then Dim Cible As String = Request("__EVENTTARGET") Dim Argument As String = Request("__EVENTARGUMENT") Response.Write(String.Format("Cible: {0},Argument: {1}<br/>", Cible, Argument)) End If End Sub Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) reponseAuRenvoi End Sub Protected Sub Button2_Click(ByVal sender As Object, ByVal e As EventArgs) Response.Write("Button2 cliqué<br/>") End Sub Protected Overloads Overrides Sub Render(ByVal writer As HtmlTextWriter) Me.Page.ClientScript.RegisterForEventValidation(Me.Button2.UniqueID, "ancre") MyBase.Render(writer) End Sub
Il mérite pas mal d'explications! Mais commençons par le faire fonctionner.
Au démarrage on observe:

Un clic sur Button 1 :

Un clic sur le lien:

Un clic sur Button 2:

Button1 est un contrôle HtmlInputButton
qui ne déclenche donc pas nativement un renvoi de
la page. Il faut l'aider à l'aide d'un peu de
javascript, c'est le boulot de Traitement
qui encapsule un appel à __doPostBack.
Un renvoi étant effectué, le code behind lance la
méthode reponseAuRenvoi() depuis l'événement
Load.
La méthode examine la valeur du champ caché
__EVENTTARGET. S'il n'est pas vide, c'est que
__doPostBack a été invoqué, on affiche ensuite
la valeur des deux champs cachés, c'est la deuxième
séquence.
Le lien montre une autre façon d'utiliser
__doPostback en se faisant passer pour Button2.
Le fonctionnement est similaire au cas précédent, sauf
que le nom de la cible est le nom de Button2.
ASP détecte lit la valeur de __EVENTTARGET et
trouve Button2 d'où le premier message. Puisqu'il s'agit d'un contrôle
serveur il appelle alors son gestionnaire d'événement
Button2_Click qui s'exécute, d'où le second
message. L'événement
Load se produisant avant la méthode
reponseAuRenvoi() est également exécutée.
La dernière séquence montre juste que l'on peut faire une distinction entre le cas précédent et un clic direct sur Button2 puisque dans ce cas __EVENTTARGET ne sera pas renseigné.
Reste une question, d'où viennent le script
__doPostback et les champs cachés?
On pourrait les ajouter à la main bien sur, mais il est
plus maintenable de se servir de la méthode standard
GetPostBackEventReference depuis OnInit.
Il existe une variante spécialisée pour le contrôle
HyperLink: GetPostackClientHyperLink.
Notez que cet appel n'est pas nécessaire si la page contient au moins un contrôle visible doté d'une propriété AutoPostback à true car il est alors toujours généré.
Le dernier point à élucider est le code dans la
méthode Render.
Ce code n'était pas nécessaire avec ASP 1. Une des
nouveautés d'ASP 2.0 est le mécanisme de validation des
événements dont nous avons parlé dans la première partie
de ce tutoriel.
Normalement, __EVENTARGUMENT lors d'un clic sur un
Button est vide. C'est ce que le mécanisme de
validation enregistre automatiquement.
Notre cas est différent puisque l'on y passe la valeur
"ancre". On doit donc enregistrer ce cas en lançant un
appel à RegisterForEventValidation.
Note:
__doPostback est en partie remplacé
par une fonction offrant de meilleures perspectives en
terme d'évolution: WebForm_DoPostBackWithOptions .
Cette fonction est également générée comme __doPostBack par GetPostBackEventReference() selon les options choisies. Nous en parlerons plus longuement dans la partie suivante du tutoriel.