Καλώς ορίσατε στο dotNETZone.gr - Σύνδεση | Εγγραφή | Βοήθεια
σε

 

Αρχική σελίδα Ιστολόγια Συζητήσεις Εκθέσεις Φωτογραφιών Αρχειοθήκες

AJAX TabContainer & PostBacks

Îåêßíçóå áðü ôï ìÝëïò rousso. Τελευταία δημοσίευση από το μέλος rousso στις 31-01-2007, 16:52. Υπάρχουν 4 απαντήσεις.
Ταξινόμηση Δημοσιεύσεων: Προηγούμενο Επόμενο
  •  30-01-2007, 17:57 24287

    AJAX TabContainer & PostBacks

    Παρατήρησα ότι το ActiveTabChanged του TabContainer δεν τρέχει όπως αναμένεται.

    Θα περίμενε κανείς ότι μετά από κάθε PostBack και εφόσον εν τω μεταξύ έχει αλλάξει το ActiveTab στον client, θα τρέξει το ActiveTabChanged στον Server. Δυστυχώς αυτό δεν συμβαίνει και μάλιστα όταν συμβαίνει, γίνεται με τρόπο περίεργο.

    Π.χ. αν έχεις στην σελίδα σου ένα TabContainer (με όλα τα παρελκόμενα TabPanels κλπ), ένα Button και ένα LinkButton, τότε ο TabContainer θα "ακούσει" μόνο το events που προέρχονται από το Button ενώ θα αγνοήσει τα PostBacks από το LinkButton. Δεν θα συμβεί όμως το ίδιο αν προσθέσεις και ένα ImageButton (οι PostBack event handlers του TabContainer, θα τρέξουν κανονικά σε κάθε PostBack που θα κάνει το ImageButton). Δοκίμασα το ίδιο και με ένα DropDownList με AutoPostBack=true. Τα ίδια με το LinkButton! Μόνο τα PostBacks των Button και ImageButton controls "φτάνουν" ως τον TabContainer. Όμως ένα PostBack είναι ένα PostBack! Δεν θα έπρεπε να συμπεριφέρεται διαφορετικά ανα περίπτωση ο TabContainer!!! Υπάρχουν και άλλοι πολλοί τρόποι να κάνει κανείς PostBack, τους οποίους προς το παρόν δεν έχω δοκιμάσει με το TabContainer. (Δεν ξέρω καν αν έχει και νόημα να μπω σε αυτή τη διαδικασία).

    Όλα αυτά βέβαια συμβαίνουν ανεξάρτητα από το αν τα controls που προκαλούν το PostBack είναι μέσα σε κάποιο TabPanel ή όχι. Η συμπεριφορά αυτή (όπως εξάλου θα περίμενε κανείς) είναι επίσης ίδια, ανεξάρτητα από τον browser που χρησιμοποιεί κανείς. Το δοκίμασα με IE 7 και FireFox 2.

    Τι τρέχει;

    Τσέκαρα να δω αν ο TabContainer έχει AutoPostBack property για να ξεπεράσω προσωρινά το πρόβλημα με πλάγιο τρόπο. Δεν έχει. (Δεν θα είχε και πολύ νόημα εδώ που τα λέμε).

    Στο CodePlex δεν βρήκα κάτι σχετικό που να δίνει μια λύση ή να εξηγεί το πρόβλημα. Το μόνο σχετικό link εκεί είναι το http://www.codeplex.com/AtlasControlToolkit/WorkItem/View.aspx?WorkItemId=7739.

    Κοιτάζοντας το source στο TabContainer.cs παρατηρεί κανείς ότι το ActiveTabChanged καλείται στην RaisePostDataChangedEvent την οποία κάνει explicitly implement η base class ScriptControlBase από το interface IPostBackDataHandler (και override η TabContainer με την σειρά της). Κάνω copy/paste τα σχετικά code snippets από το TabContainer.cs.

    protected override void RaisePostDataChangedEvent()
    {
       OnActiveTabChanged(EventArgs.Empty);
    }

    protected virtual void OnActiveTabChanged(EventArgs e)
    {
       EventHandler eh = Events[EventActiveTabChanged] as EventHandler;
       if (eh != null)
       {
          eh(this, e);
       }
    }

    private static readonly object EventActiveTabChanged = new object();

    [Category("Behavior")]
    public event EventHandler ActiveTabChanged
    {
       add { Events.AddHandler(EventActiveTabChanged, value); }
       remove { Events.RemoveHandler(EventActiveTabChanged, value); }
    }

    H RaisePostDataChangedEvent θα έπρεπε να τρέχει εφόσον η LoadPostData επέστρεφε true. Η LoadPostData από το TabContainer.cs φαίνεται παρακάτω:

    protected override bool LoadPostData(string postDataKey, NameValueCollection postCollection)
    {
       int tabIndex = ActiveTabIndex;
       bool result = base.LoadPostData(postDataKey, postCollection);
       if (tabIndex != ActiveTabIndex)
       {
          return true;
       }
       return result;
    }

    Και όπως είναι προφανές από το παραπάνω η LoadPostData προσπαθεί να βεβαιωθεί ότι όντως θα τρέξει ο event handler αν αλλάξει το ActiveTab στον client μεταξύ των PostBacks.

    Όμως τελικά η LoadPostData καλώντας το base implementation (που σας παραθέτω παρακάτω) δεν βλέπει καμία αλλαγή στο ActiveTabIndex, παρά μόνο αν το PostBack έχει γίνει από ένα <input type=submit /> ή ένα <input type=image /> control, δηλαδή από ένα Button (με UseSubmitBehaviour=true) ή ένα ImageButton.

    Κάνω copy/paste και το base implementation της LoadPostData από το ScriptControlBase.cs παρακάτω:

    protected virtual bool LoadPostData(string postDataKey, NameValueCollection postCollection)
    {
       if (SupportsClientState)
       {
          string clientState = postCollection[ClientStateFieldID];
          if (!string.IsNullOrEmpty(clientState))
          {
             LoadClientState(clientState);
          }
       }
       return false;
    }

    Η SupportsClientState επιστρέφει true για το TabContainer. H LoadClientState για το TabContainer έχει ως εξής:

    protected override void LoadClientState(string clientState)
    {
       Dictionary<string, object> state = (Dictionary<string, object>)new JavaScriptSerializer().DeserializeObject(clientState);
       if (state != null)
       {
          ActiveTabIndex = (int)state["ActiveTabIndex"];
          object[] tabState = (object[])state["TabState"];
          for (int i = 0; i < tabState.Length; i++)
          {
             Tabs[ i ].Enabled = (bool)tabState[ i ];
          }
       }
    }

    Τι ακριβώς τρέχει δεν έχω καταλάβει ακόμα. Πρέπει να είναι μπροστά μου και να μην το βλέπω! Ίσως κάτι δεν πάει καλά με τα client-side scripts στο Tabs.js (αν και δεν νομίζω ότί είναι εκεί το πρόβλημα). Τι διαφορετικό κάνει ένα Button από ένα LinkButton στο  κατά το submit; Το LinkButton καλεί explicitly την __doPostBack. Η διαφορά είναι μια: τα Button και ImageButton controls κάνουν submit την φόρμα by design (ως HTML input controls), χωρίς να χρειάζεται να κληθεί η __doPostBack, σε αντίθεση με όλα τα άλλα controls που πρέπει να καλέσουν την __doPostBack (η οποία με την σειρά της καλεί την form.submit()). Κανονικά δεν θα έπρεπε άρα να υπάρχει καμία διαφορά άρα ανάμεσα στα δύο PostBacks (εκτός εάν η form.submit() δεν κάνει την ίδια δουλειά που κάνει ο browser όταν πατήσεις ένα submit input control!).

    Που έχω καταλήξει; Το πρόβλημα εμφανίζεται στην LoadPostData. Εκεί το Framerwork περνάει μια παράμετρο τύπου NameValueCollection με όλα τα data της φόρμας. Εκεί υπάρχει και ένα ζευγάρι με το ClientState του TabContainer. To ClientState γινεται deserilize με έναν JavaScriptSerializer και διαβάζεται το περιεχομενό του από όπου προκύπτει η τρέχουσα τιμή για το ActiveTabIndex. Αυτό δεν είναι ενημερωμένο αν το PostBack γίνει με κλήση στην __doPostBack!!! Γιατί;;; Δεν έχω την παραμικρή ιδέα προς το παρόν!!!

    Help me out here!!!!

     


    rousso
    Δημοσίευση στην κατηγορία: , , ,
  •  31-01-2007, 15:38 24340 σε απάντηση της 24287

    Απ:AJAX TabContainer & PostBacks

    Σήμερα αφιέρωσα λίγο ακόμα χρόνο για να το ψάξω περισσότερο.

    Το θέμα εντοπίζεται στο γεγονός ότι η _onsubmit function που ορίζεται στο BaseScripts.js (και παρατίθεται παρακάτω) δεν καλείται παρά μόνο αν το PostBack γίνει μέσω ενός Button ή ImageButton.

    _onsubmit : function() {
       if (this._clientStateField) {
          this._clientStateField.value = this.saveClientState();
       }
       return true;
    }

     H _onsubmit όπως φαίνεται και από τον κώδικα αναλαμβάνει να σώσει το ClientState (μέσα στο οποίο θα πρέπει να καταγράψει το ActiveTabIndex).

    H _onsubmit ορίζεται ως delegate στον Constructor του AjaxControlToolkit.ControlBase και προστίθεται στον πίνακα με τους submit event handlers του Sys.WebForms.PageRequestManager κατά την εκτέλεση της ControlBase.initialize όπως φαίνεται παρακάτω:

    initialize : function() {
       AjaxControlToolkit.ControlBase.callBaseMethod(this, "initialize");
       // load the client state if possible
       if (this._clientStateField) {
          this.loadClientState(this._clientStateField.value);
       }

       // attach an event to save the client state before a postback or updatepanel partial postback
       if (typeof(Sys.WebForms)!=="undefined" && typeof(Sys.WebForms.PageRequestManager)!=="undefined") {
          Array.add(Sys.WebForms.PageRequestManager.getInstance()._onSubmitStatements, this._onsubmit$delegate);
       } else {
          $addHandler(document.forms[0], "submit", this._onsubmit$delegate);
       }
    }

    Άρα: Τι τρέχει; Γιατί εφόσον προστίθεται στον πίνακα _onSubmitStatements του PageRequestManager, τελικά ποτέ δεν εκτελείται όταν κληθεί η form.onsubmit();

    Για να ξεκαθαρίσω αν φταίει ο PageRequestManager πείραξα λίγο την initalize που φαίνεται παραπάνω ώστε να τρέξει το else κομμάτι και να βάλει τον _onsubmit$delegate στην στο submit event της document.form[0]. Τα ίδια! Δεν τρέχει η _onsubmit παρά μόνο ανα κάνεις post με ένα Button ή ImageButton.

    Παραμένω αβοήθητος και συνεχίζω την έρευνα...


    rousso
    Δημοσίευση στην κατηγορία: , , ,
  •  31-01-2007, 16:29 24345 σε απάντηση της 24340

    Απ:AJAX TabContainer & PostBacks

    Ok! Got it!

    Ευκολο τελικά, αλλά εύκολο και να ξεφύγει!!!

    Το onSubmit event δεν καλείται όταν καλέσεις την form.onsubmit()!!! Αυτό είναι by design και μια και δεν πέφτει κανείς πάνω του συχνά είναι εύκολο να σου διαφύγει. Δείτε σχετικά εδώ κι εδώ.

    Δεν έχω δει αν μπορεί να ξεπεραστεί το θέμα βάζοντας ένα κατάλληλο DOCTYPE στην σελίδα. Θα το ψάξω. Πάντως σε DOM Level 2 θα έπρεπε να τρέχει το onSubmit event της φόρμας ακόμα και αν το submit γίνει με κλήση στην form.submit(). Το DOCTYPE που είχα στην σελίδα με την οποία έκανα τα tests ήταν:

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

    Πάντως ένας τρόπος να το ξεπεράσει κανείς είναι να αντικαταστήσει την default __doPostBack. Εγώ έκανα το εξής και δούλεψε:

    protected void Page_Load(object sender, EventArgs e)
    {
       ClientScript.RegisterClientScriptBlock(this.GetType(), "Replace__doPostBack",
          "<SCRIPT LANGUAGE=\"JAVASCRIPT\">\n" +
          "   __doPostBack = __myPostBack;\n" +
          "   function __myPostBack(eventTarget, eventArgument)\n"
    +
          "   {\n" +
          "      if (!theForm.onsubmit || (theForm.onsubmit() != false)) {\n" +
          "         theForm.__EVENTTARGET.value = eventTarget;\n" +
          "          theForm.__EVENTARGUMENT.value = eventArgument;\n" +
          "\n" +
          "          // \n" +
          "          // \n" +
          "          // κάνω fire το event μόνος μου!!! \n" +
          "          theForm.fireEvent('onsubmit'); \n" +
          "\n" +
          "          theForm.submit();\n" +
          "       }\n" +
          "   }\n" +
          "</SCRIPT>\n");
    }

    Αντικατέστησα δηλαδή την __doPostBack με μια δικιά μου έκδοση στην οποία απλά έκανα fire το onSubmit event μόνος μου πριν καλέσω την form.submit(). Δεν είναι λύση βέβαια αυτή γιατί ανάλογα με τον browser μπορεί να φτάσεις να τρέχεις δύο φορές το event. Αποτελεί όμως απόδειξη για την αιτία του προβλήματος...

    Αν έχει κανείς έτοιμη την απάντηση στο θέμα DOCTYPE ή browser DOM Level 2 support ας μου την προσφέρει παρακαλώ. Εν τω μεταξύ θα συνεχίσω να το κοιτάζω αν και φοβάμαι ότι δεν θα βγάλω εύκολα άκρη με αυτό.

    Πάνως ειδικά σε θέματα AJAX (όπου το onSubmit event είναι κρίσιμης σημασίας) υπάρχει σοβαρό πρόβλημα με το συγκεκριμένο θέμα και θα περίμενα να βρω κάποια απλή και straight forward λύση...

     


    rousso
    Δημοσίευση στην κατηγορία: , , , ,
  •  31-01-2007, 16:37 24346 σε απάντηση της 24345

    Απ: AJAX TabContainer & PostBacks

    Μην ξεχάσεις να θέσεις το "Κατάσταση Ενότητας" στην ερώτηση σου σε "Έχει επιλυθεί" και να μαρκάρεις τις ερωτήσεις που σε βοήθησαν πατώντας το κουμπάκι "Σημείωση ως Απάντησης"

    Big Smile


    Vir prudens non contra ventum mingit
  •  31-01-2007, 16:52 24348 σε απάντηση της 24346

    Απ: AJAX TabContainer & PostBacks

    Χαχα! Big Smile Καλό!

    Δεν θεωρώ ότι το έχω λύσει ακόμα δυστυχώς... Μάλλον πρέπει να κάνω 2-3 posts ακόμα για να γίνει "φλέγον θέμα" και να προξενήσει λίγο περισσότερο ενδιαφέρον!

    Τα δυσκολότερα θέματα μου φαίνεται ότι τελικά δεν είναι τα bugs, αλλά τα features!!! Wink


    rousso
Προβολή Τροφοδοσίας RSS με μορφή XML
Με χρήση του Community Server (Commercial Edition), από την Telligent Systems