Graphity: Prototypes Element.repace

Guten morgen Forum

Ich habe das wohl ein sehr spezielles Problem mit der Methode Element.replace in der Prototype JS Lib.
Kurz gesagt, ich habe ein DIV, welches als Inhalt eine SELECT-Liste enthält. Dieses ersetze ich nach einem Ajax-Aufruf mit wieder einem DIV-Element welches ein IMG-Tag und ein SCRIPT-Tag enthält (unschön, ja, ich weiß). In diesem SCRIPT-Tag weise ich einer JS-Variable erst null zu (lösche also den vorherigen Inhalt) und weise dann eine Funktion zu (quasi "alert('test');"). Direkt danach rufe ich dann diese Funktion auf.
Also:

  
addAllEvents = null;  
Element.replace("events_list", "<div id='events_list'>  
<img src="test.png" alt="Testbild" />  
<script type="text/javascript">  
//<![CDATA[  
addAllEvents = function() {alert('test');};  
 //]]>  
</javascript>  
</div>");  
addAllEvents();  

Gut, zugegeben, die Funktion ist bei mir natürlich etwas größer, außerdem baut Prototype noch ein try-catch außen rum. Prinzipiell geht es mir aber um folgendes Problem:
Beim Aufruf von addAllEvents(); in der letzten Zeile kommt die Fehlermeldung, dass diese Funktion nicht definiert ist. Gebe ich die danach in die FireBug-Javascript-Konsole ein, funktionierts. Baue ich um diesen Aufruf ein setTimeout() herum, funktionierts aus.
Nun meine Frage: Gibt es etwas "besseres" als das warten? Weil das ist ja nicht wirklich sicher, es könnte ja mal länger, mal kürzer dauern. Und wieso wird eigentlich der Aufruf ausgeführt bevor die Zeile darüber fertig ist?

Liebe Grüße
Euer
    Graphity

  1. Hallo,

    wieso wird eigentlich der Aufruf ausgeführt bevor die Zeile darüber fertig ist?

    Weil Prototype die Scripte im HTML-Code nicht sofort, sondern jeweils mit einem Timeout ausführt. Da JavaScript nur mit einem Thread arbeitet, läuft erst dein Teilscript durch. Wenn das zur Ruhe kommt, werden die Scripte ausgeführt.

    Keine Ahnung, warum sich die Entwickler dazu entschlossen haben, jedenfalls hast du direkt nach der Ausführung von Element.replace keinen Zugriff auf die Scripte.

    Es ist nur konsequent, dass du addAllEvents ebenfalls nach einem setTimeout startest. Der kann auch 1ms sein, er wird in jedem Fall nach den Funktionen ausgeführt, die Prototype per setTimeout in die Ausführungs-Queue eingefügt hat.

    Mathias

    1. Das erklärt natürlich alles! :)
      Herzlichen Dank!

    2. Hi,

      ich glaube, dass ist nicht richtig, was Du geschrieben hast.
      Prototype führt sowas nicht mit einem Timeout aus.

      1. Ist der Code von Graphity ziemlich falsch z.B. öffnender script-Tag und schließender javascript-Tag?!
        und
      2. Kann man kein Javascript dynamisch in das DOM "injizieren". Das ist so, als würde man einDiv.innerHTML = "<script>aaa=function (alert(1))</script>"; machen wollen. Das interessiert den Browser überhaupt nicht.

      Malzeit.

      1. Hi nochmal,

        ich muss mein letztes Posting korrigieren.
        Natürlich hat Molily Recht.
        Die Evaluierung der Scripte wir "defered" ausgeführt.

        Malzeit

      2. Hallo,

        ich glaube, dass ist nicht richtig, was Du geschrieben hast.
        Prototype führt sowas nicht mit einem Timeout aus.

        Du glaubst? Ich kann dir gerne den Prototype-Quelltext vorlesen.

        Element.replace:

        Element.Methods = {  
        ...  
          replace: function(element, content) {  
            element = $(element);  
            if (content && content.toElement) content = content.toElement();  
            else if (!Object.isElement(content)) {  
              content = Object.toHTML(content);  
              var range = element.ownerDocument.createRange();  
              range.selectNode(element);  
              content.evalScripts.bind(content).defer();  
              content = range.createContextualFragment(content.stripScripts());  
            }  
            element.parentNode.replaceChild(content, element);  
            return element;  
          },  
        ...
        

        Man beachte hier die Zeile
        content.evalScripts.bind(content).defer();

        bind erzeugt nur eine Wrapper-Funktion, die evalScripts im Kontext von content ausführt. (Wobei dieser Aufruf an dieser Stelle überflüssig ist, denke ich.)
        content ist die String-Variable mit HTML-Code und darin script-Elemente.

        evalScripts:

        Object.extend(String.prototype, {  
        ...  
          evalScripts: function() {  
            return this.extractScripts().map(function(script) { return eval(script) });  
          },  
        ...
        

        Was extractScripts und map machen, ist ja offensichtlich, also ist noch defer interessant:

        Function.prototype.defer = Function.prototype.delay.curry(0.01);

        curry(0.01) gibt eine Funktion zurück, bei der defer mit dem Parameter 0.01 aufgerufen wird (nennt sich Currying).

        Schließlich zu delay:

        Object.extend(Function.prototype, {  
        ...  
          delay: function() {  
            var __method = this, args = $A(arguments), timeout = args.shift() * 1000;  
            return window.setTimeout(function() {  
              return __method.apply(__method, args);  
            }, timeout);  
          },  
        ...
        

        Hier wird also die Funktion mit setTimeout aufgerufen. Und wenn delay über defer aufgerufen wird, dann ist der zweite Parameter von setTimout 1 für eine Millisekunde.

        Das ist alles ziemlich konsequente funktionale Programmierung auf Basis von prototypischer Erweiterung, aber letztlich ist der Zweck dieser Konstrukte, dass evalScripts zeitverzögert ausgeführt wird. Wenn ich mich da irre, zeige es mir bitte auf.

        Den Sinn von so etwas wie defer erfasst man, wenn man weiß, How JavaScript Timers Work.

        1. Kann man kein Javascript dynamisch in das DOM "injizieren". Das ist so, als würde man einDiv.innerHTML = "<script>aaa=function (alert(1))</script>"; machen wollen. Das interessiert den Browser überhaupt nicht.

        Es geht hier um Element.replace und das ist etwas ganz anderes als eine Zuweisung an innerHTML. Genauer gesagt wird innerHTML überhaupt nicht verwendet, wie man an obigem Code sieht. Und zu den Scripten siehe Dokumentation:
        "If it [i.e. der Parameter html] contains any <script> tags, these will be evaluated after element has been replaced (Element.replace() internally calls String#evalScripts)."
        Aber auch das kann man im Quelltext sehen.

        Mathias

        1. Tag nochmal,

          Hallo,

          ich glaube, dass ist nicht richtig, was Du geschrieben hast.
          Prototype führt sowas nicht mit einem Timeout aus.

          Du glaubst? Ich kann dir gerne den Prototype-Quelltext vorlesen.

          sorry, wenn ich Dir auf den Schlips getreten bin, aber wie Du aus meinem Folgeposting sehen kannst, habe ich selber auch erkannt, dass das Script das "defered" ausgeführt wird. Ich war mir nur über den Umfang von Element.replace nicht bewusst.

          Hier wird also die Funktion mit setTimeout aufgerufen. Und wenn delay über defer aufgerufen wird, dann ist der zweite Parameter von setTimout 1 für eine Millisekunde.

          Das ist alles ziemlich konsequente funktionale Programmierung auf Basis von prototypischer Erweiterung, aber letztlich ist der Zweck dieser Konstrukte, dass evalScripts zeitverzögert ausgeführt wird. Wenn ich mich da irre, zeige es mir bitte auf.

          Mmh?
          Ich würde sagen 10 ms, oder?

          Bis dann und wann.

          1. Hallo,

            sorry, wenn ich Dir auf den Schlips getreten bin

            Nö, kein Problem.

            Und wenn delay über defer aufgerufen wird, dann ist der zweite Parameter von setTimout 1 für eine Millisekunde.

            Ich würde sagen 10 ms, oder?

            Stimmt, nach Adam Riese und Eva Zwerg.

            Mathias