Aufrufen einer JavaScript-Funktion, die von einer Ajax-Antwort zurückgegeben wurde

68

Ich habe ein System, in dem ich einen Ajax-Befehl sende, der einen Skriptblock mit einer Funktion darin zurückgibt. Nachdem diese Daten korrekt in den DIV eingefügt wurden, möchte ich diese Funktion aufrufen können, um die erforderlichen Aktionen auszuführen.

Ist das möglich?

williamtroup
quelle

Antworten:

73

Ich denke, um Ihre Frage unter diesem Formular richtig zu interpretieren: "OK, ich bin bereits mit all dem Ajax-Zeug fertig; ich möchte nur wissen, ob die JavaScript-Funktion, die mein Ajax-Rückruf in den DIV eingefügt hat, von diesem Moment an jederzeit aufrufbar ist , das heißt, ich möchte es nicht kontextuell zur Rückrufrückgabe aufrufen ".

OK, wenn Sie so etwas meinen, lautet die Antwort "Ja". Sie können Ihren neuen Code jederzeit während der Seitenpersistenz im Browser unter den folgenden Bedingungen aufrufen:

1) Ihr vom Ajax-Rückruf zurückgegebener JavaScript-Code muss syntaktisch in Ordnung sein.
2) Selbst wenn Ihre Funktionsdeklaration in einen <script>Block innerhalb eines vorhandenen <div>Elements eingefügt wird, weiß der Browser nicht, dass die neue Funktion vorhanden ist, da der Deklarationscode nie ausgeführt wurde. Sie müssen also eval()Ihren Deklarationscode vom Ajax-Rückruf zurückgeben, um Ihre neue Funktion effektiv deklarieren zu können und sie während der gesamten Lebensdauer der Seite verfügbar zu haben.

Auch wenn dieser Code ziemlich dumm ist, erklärt er die Idee:

<html>
    <body>
        <div id="div1">
        </div>
        <div id="div2">
            <input type="button" value="Go!" onclick="go()" />
        </div>
        <script type="text/javascript">
            var newsc = '<script id="sc1" type="text/javascript">function go() { alert("GO!") }<\/script>';
            var e = document.getElementById('div1');
            e.innerHTML = newsc;
            eval(document.getElementById('sc1').innerHTML);
        </script>
    </body>
</html>

Ich habe Ajax nicht verwendet, aber das Konzept ist das gleiche (auch wenn das Beispiel, das ich ausgewählt habe, sicher nicht sehr klug ist :-)

Im Allgemeinen stelle ich Ihr Lösungsdesign nicht in Frage, dh ob es mehr oder weniger angemessen ist, die Funktion in einer separaten .js-Datei und dergleichen zu externalisieren + zu verallgemeinern. Beachten Sie jedoch, dass eine solche Lösung weitere Probleme aufwerfen kann, insbesondere wenn Ihre Ajax-Aufrufe sollten wiederholt werden, dh wenn sich der Kontext derselben Funktion ändern sollte oder wenn die Persistenz der deklarierten Funktion betroffen sein sollte. Daher sollten Sie möglicherweise ernsthaft in Betracht ziehen, Ihr Design in eines der vorgeschlagenen Beispiele in diesem Thread zu ändern.

Wenn ich Ihre Frage falsch verstanden habe und Sie über den kontextbezogenen Aufruf der Funktion sprechen, wenn Ihr Ajax-Rückruf zurückkehrt, möchte ich den von krosenvold beschriebenen Prototyp-Ansatz vorschlagen , da er browserübergreifend, getestet und voll funktionsfähig ist. Dies kann Ihnen eine bessere Roadmap für zukünftige Implementierungen geben.

Federico Zancan
quelle
1
Dies funktioniert im Beispiel, da sich alles auf derselben Seite befindet und das Timing nicht ins Spiel kommt. Es funktioniert nicht mit irgendetwas, das tatsächlich AJAX-Methoden verwendet, um externe Dateien zur Verarbeitung aufzurufen, da die Funktion eval () ausgeführt wird, bevor der externe Prozess Zeit hat, mit einer Antwort zurückzukehren.
Typel
Hervorragende Antwort spart mir wirklich Zeit. Gott schütze dich.
Manish
@Typel Gibt es Sicherheitsrisiken bei der Verwendung von eval () hier?
Anupam
1
@Anupam Ich denke, der allgemeine Konsens ist, dass eval () immer mit Sicherheitsrisiken verbunden ist - ich wäre mir bewusst, wie und ob es verwendet wird. Persönlich würde ich es nicht in Verbindung mit AJAX verwenden, um das Öffnen von XSS-Schwachstellen zu vermeiden. In den meisten Fällen, wenn ich eine Funktion zum Ausführen von einer AJAX-Rückgabe benötige, gebe ich stattdessen einfach die Argumente zurück und lasse das Fleisch der Funktion in der aufrufenden Seite liegen und stattdessen von der aufrufenden Seite ausführen (oder etwas Ähnliches).
Typel
41

Hinweis : eval () kann leicht missbraucht werden. Nehmen wir an, die Anforderung wird von einem Dritten abgefangen und sendet Ihnen nicht vertrauenswürdigen Code. Dann würden Sie mit eval () diesen nicht vertrauenswürdigen Code ausführen. Hier finden Sie die Gefahren von eval () .


In der zurückgegebenen HTML / Ajax / JavaScript-Datei befindet sich ein JavaScript-Tag. Geben Sie ihm eine ID wie Runscript . Es ist ungewöhnlich, diesen Tags eine ID hinzuzufügen, diese muss jedoch speziell referenziert werden.

<script type="text/javascript" id="runscript">
    alert("running from main");
</script>

Rufen Sie dann im Hauptfenster die Funktion eval auf, indem Sie nur diesen NEUEN Block JavaScript-Code auswerten (in diesem Fall Runscript ):

eval(document.getElementById("runscript").innerHTML);

Und es funktioniert zumindest in Internet Explorer 9 und Google Chrome.

Dandymon
quelle
Erstaunlich, habe versucht, dies für eine Weile
herauszufinden
Ich habe stundenlang versucht, dies zu lösen und bin gerade auf diese Antwort gestoßen! Das ist großartig und sollte wahrscheinlich akzeptiert werden! Vielen Dank, +1 :)
Bryan Zwicker
4
Gibt es eine Sicherheitslücke bei der Implementierung dieser Lösung?
Savas Vedova
Noch aktuell im Jahr 2018: Das klingt gefährlich nach "Sicherheit durch Dunkelheit". Wenn ich Ihre Site angreifen und diese eval () - Funktion bemerken würde, würde ich das ID-Tag dort sicherlich bemerken und meinen Angriff so ändern, dass nur der Malicios-Code in Ihr <script id="my-secure-id">Tag eingefügt wird . Ich sehe keine zusätzliche Sicherheit bei Verwendung dieser Lösung. In der Tat gibt es Ihnen nur das falsche Gefühl der Sicherheit, was noch gefährlicher ist. Aber ungefähr 6 Jahre später ist HTTPS jetzt sehr viel häufiger. Ich glaube, dass ein Angriff auf eval () über sicheres HTTPS nicht möglich sein sollte. Bin ich falsch anzunehmen?
Jan Bradáč
Natürlich ist eval in einigen Kontexten gefährlich, aber wenn jemand Ihren Code abfängt, vorausgesetzt, Sie laufen nicht über ssl und fügen Code in eval ein, gibt es einen legitimen Grund, warum er nicht dasselbe tun könnte, wenn Ihre Antwort HTML zurückgibt, das eingefügt wird in das DOM (sie können einfach ein Skript-Tag hinzufügen). Es scheint mir, dass das Ausführen von eval für eine Antwort vom Server dieselben Sicherheitsauswirkungen hat wie der Server, der Ihnen HTML-Code zur Verfügung stellt, den Sie in das DOM einfügen. Vielleicht gibt es aber einen Unterschied.
Joel M
9

Es ist durchaus möglich, und es gibt sogar einige ziemlich legitime Anwendungsfälle dafür. Mit dem Prototype- Framework wird wie folgt vorgegangen.

new Ajax.Updater('items', '/items.url', {
    parameters: { evalJS: true}
});

Siehe Dokumentation des Ajax-Updaters. Die Optionen befinden sich im allgemeinen Optionssatz . Wie üblich gibt es einige Einschränkungen, wo "dies" darauf hinweist. Lesen Sie daher das Kleingedruckte.

Der JavaScript-Code wird beim Laden ausgewertet. Wenn der Inhalt eine Funktion enthält, kann myFunc(), man das wirklich einfach myFunc()später sagen . Vielleicht wie folgt.

if (window["myFunc"])
   myFunc()

Dies prüft, ob die Funktion vorhanden ist. Vielleicht hat jemand eine bessere browserübergreifende Methode, um das zu tun, was in Internet Explorer 6 funktioniert.

Krosenvold
quelle
Das ist großartig! Vielen Dank! Ich habe immer die "onSuccess" -Funktion im Updater verwendet, um das Javascript manuell auszuwerten. Dies ist jedoch besser lesbar und funktioniert in allen Browsern.
Matthias Kleine
6

Das scheint ein ziemlich seltsames Design für Ihren Code zu sein - es ist im Allgemeinen sinnvoller, Ihre Funktionen direkt aus einer .js-Datei aufrufen zu lassen und dann nur Daten mit dem Ajax-Aufruf abzurufen.

Ich glaube jedoch, dass es funktionieren sollte, indem eval () für die Antwort aufgerufen wird - vorausgesetzt, es handelt sich um syntaktisch korrekten JavaScript-Code.

DanSingerman
quelle
3

Denken Sie daran, wenn Sie eine Funktion wie folgt durch Ajax erstellen ...

function foo()
{
    console.log('foo');
}

... und führen Sie es über eval aus, Sie werden wahrscheinlich ein Kontextproblem bekommen. Nehmen Sie dies als Ihre Rückruffunktion:

function callback(result)
{
    responseDiv = document.getElementById('responseDiv');
    responseDiv.innerHTML = result;
    scripts = responseDiv.getElementsByTagName('script');
    eval(scripts[0]);
}

Sie deklarieren eine Funktion innerhalb einer Funktion, sodass auf diese neue Funktion nur in diesem Bereich zugegriffen werden kann.

Wenn Sie in diesem Szenario eine globale Funktion erstellen möchten, können Sie diese folgendermaßen deklarieren:

window.foo = function ()
{
    console.log('foo');
};

Aber ich denke auch, dass du das nicht tun solltest ...

Entschuldigung für jeden Fehler hier ...

Daniel Hartmann
quelle
3

Ich möchte hinzufügen, dass es in jQuery eine Auswertungsfunktion gibt, mit der Sie den Code global auswerten können, um alle Kontextprobleme zu beseitigen. Die Funktion heißt globalEval () und hat für meine Zwecke hervorragend funktioniert . Die Dokumentation finden Sie hier .

Dies ist der Beispielcode, der in der jQuery-API-Dokumentation bereitgestellt wird:

function test()
{
  jQuery.globalEval("var newVar = true;")
}

test();
// newVar === true

Diese Funktion ist äußerst nützlich, wenn Sie externe Skripte dynamisch laden möchten, was Sie anscheinend versucht haben.

Beta
quelle
2

Eine Checkliste für so etwas:

  1. Die zurückgegebene Ajax-Antwort ist eval (ed).
  2. Die Funktionen werden in Form deklariert func_name = function() {...}

Besser noch, verwenden Sie Frameworks, die wie in Prototype damit umgehen . Du hast Ajax.updater.

Nasaralla
quelle
2

PHP-Seitencode Name der Datei class.sendCode.php

<?php
class  sendCode{ 

function __construct($dateini,$datefin) {

            echo $this->printCode($dateini,$datefin);
        }

    function printCode($dateini,$datefin){

        $code =" alert ('code Coming from AJAX {$this->dateini} and {$this->datefin}');";
//Insert all the code you want to execute, 
//only javascript or Jquery code , dont incluce <script> tags
            return $code ;
    }
}
new sendCode($_POST['dateini'],$_POST['datefin']);

Jetzt müssen Sie von Ihrer HTML-Seite aus die Ajax-Funktion auslösen, um die Daten zu senden.

....  <script src="http://code.jquery.com/jquery-1.9.1.js"></script> ....
Date begin: <input type="text" id="startdate"><br>
Date end : <input type="text" id="enddate"><br>
<input type="button" value="validate'" onclick="triggerAjax()"/>

Jetzt definieren wir in unserer lokalen script.js den Ajax

function triggerAjax() {
    $.ajax({
            type: "POST",
            url: 'class.sendCode.php',
            dataType: "HTML",
            data : {

                dateini : $('#startdate').val(),
                datefin : $('#enddate').val()},

                  success: function(data){
                      $.globalEval(data);
// here is where the magic is made by executing the data that comes from
// the php class.  That is our javascript code to be executed
                  }


        });
}
Sultanos
quelle
1

Das klingt nicht nach einer guten Idee.

Sie sollten die Funktion, die in den Rest Ihres JavaScript-Codes aufgenommen werden soll, aus den von Ajax-Methoden zurückgegebenen Daten abstrahieren.

Für das, was es wert ist (und ich verstehe nicht, warum Sie einen Skriptblock in ein div einfügen?), Sind auch Inline-Skriptmethoden, die in einen Skriptblock geschrieben wurden, verfügbar.

Annakata
quelle
1

Dieser Code funktioniert auch, stattdessen wird das HTML ausgewertet, an das ich das Skript an den Kopf anhängen werde

function RunJS(objID) {
//alert(http_request.responseText);
var c="";
var ob = document.getElementById(objID).getElementsByTagName("script");
for (var i=0; i < ob.length - 1; i++) {
    if (ob[i + 1].text != null) 
       c+=ob[i + 1].text;
}
var s = document.createElement("script");
s.type = "text/javascript";
s.text = c;
document.getElementsByTagName("head")[0].appendChild(s);
}
user2054758
quelle
1

Meine übliche Ajax-Aufruffunktion:

function xhr_new(targetId, url, busyMsg, finishCB)
{
    var xhr;

    if(busyMsg !== undefined)
        document.getElementById(targetId).innerHTML = busyMsg;

    try { xhr = new ActiveXObject('Msxml2.XMLHTTP'); }
    catch(e)
    {
        try { xhr = new ActiveXObject('Microsoft.XMLHTTP'); }
        catch(e2)
        {
            try { xhr = new XMLHttpRequest(); }
            catch(e3) { xhr = false; }
        }
    }

    xhr.onreadystatechange = function()
    {
        if(xhr.readyState == 4)
        {
            if(xhr.status == 200)
            {
                var target = document.getElementById(targetId)
                target.innerHTML = xhr.responseText;
                var scriptElements = target.getElementsByTagName("script");
                var i;
                for(i = 0; i < scriptElements.length; i++)
                    eval(scriptElements[i].innerHTML);
                if(finishCB !== undefined)
                    finishCB();
            }
            else
                document.getElementById(targetId).innerHTML = 'Error code: ' + xhr.status;
        }
    };

    xhr.open('GET', url, true);
    xhr.send(null);
    // return xhr;
}

Einige Erklärungen:
targetIdist eine (normalerweise div) Element-ID, in die der Ergebnistext des Ajax-Aufrufs eingefügt wird.
urlist die Ajax-Anruf-URL.
busyMsgwird der temporäre Text im Zielelement sein.
finishCBwird aufgerufen, wenn die Ajax-Transaktion erfolgreich abgeschlossen wurde.
Wie Sie in der Abbildung sehen, werden xhr.onreadystatechange = function() {...}alle <script>Elemente aus der Ajax-Antwort erfasst und einzeln ausgeführt. Es scheint sehr gut für mich zu funktionieren. Die beiden letzten Parameter sind optional.

Strahl
quelle
0

Ich habe das getestet und es funktioniert. Was ist das Problem? Fügen Sie einfach die neue Funktion in Ihr Javascript-Element ein und rufen Sie sie auf. Es wird klappen.


quelle
0

Ich habe alle hier angebotenen Techniken ausprobiert, aber schließlich funktionierte die JavaScript-Funktion einfach in die Seite / Datei, in der sie stattfinden soll, und rief sie aus dem Antwortteil des Ajax einfach als Funktion auf:

...
}, function(data) {
    afterOrder();
}

Dies funktionierte beim ersten Versuch, also entschied ich mich zu teilen.

Sagive SEO
quelle
0

Ich habe dies heute gelöst, indem ich mein JavaScript am Ende des Antwort-HTML-Codes platziert habe.

Ich hatte eine AJAX-Anfrage, die eine Menge HTML zurückgab, das in einem Overlay angezeigt wurde. Ich musste ein Klickereignis an eine Schaltfläche im zurückgegebenen Antwort-HTML / Overlay anhängen. Auf einer normalen Seite würde ich mein JavaScript in ein "window.onload" oder "$ (document) .ready" einschließen, damit der Ereignishandler an das DOM-Objekt angehängt wird, nachdem das DOM für das neue Overlay gerendert wurde Da dies eine AJAX-Antwort und kein Laden einer neuen Seite war, ist dieses Ereignis nie aufgetreten, der Browser hat mein JavaScript nie ausgeführt, mein Ereignishandler wurde nie an das DOM-Element angehängt und meine neue Funktionalität hat nicht funktioniert. Wieder löste ich mein "Ausführen von JavaScript in einem AJAX-Antwortproblem", indem ich nicht "$ (document) .ready" im Kopf des Dokuments verwendete.

Andrew Koper
quelle
0

Wenn die Ausführung Ihres AJAX-Skripts länger als ein paar Millisekunden dauert, wird eval () immer ausgeführt und bewertet das leere Antwortelement, bevor AJAX es mit dem Skript füllt, das Sie ausführen möchten.

Anstatt mit Timing und eval () herumzuspielen, ist hier eine ziemlich einfache Problemumgehung, die in den meisten Situationen funktionieren sollte und wahrscheinlich etwas sicherer ist. Die Verwendung von eval () ist im Allgemeinen verpönt, da die als Code ausgewerteten Zeichen leicht clientseitig bearbeitet werden können.

Konzept

  1. Fügen Sie Ihre Javascript-Funktion in die Hauptseite ein. Schreiben Sie es so, dass alle dynamischen Elemente als Argumente akzeptiert werden können.
  2. Rufen Sie in Ihrer AJAX-Datei die Funktion mithilfe eines offiziellen DOM-Ereignisses auf (onclick, onfocus, onblur, onload usw.). Je nachdem, welche anderen Elemente in Ihrer Antwort enthalten sind, können Sie sich ziemlich geschickt fühlen, damit sie sich nahtlos anfühlt. Übergeben Sie Ihre dynamischen Elemente als Argumente.
  3. Wenn Ihr Antwortelement ausgefüllt wird und das Ereignis stattfindet, wird die Funktion ausgeführt.

Beispiel

In diesem Beispiel möchte ich eine dynamische Autovervollständigungsliste aus der jquery-ui-Bibliothek an ein AJAX-Element anhängen, nachdem das Element zur Seite hinzugefügt wurde. Einfach richtig?

start.php

<!DOCTYPE html>
<html>
<head>
<title>Demo</title>
<!-- these libraries are for the autocomplete() function -->
<link rel="stylesheet" type="text/css" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/ui-lightness/jquery-ui.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>
<script type="text/javascript">
<!--
// this is the ajax call
function editDemoText(ElementID,initialValue) {
    try { ajaxRequest = new XMLHttpRequest();
    } catch (e) {
    try { ajaxRequest = new ActiveXObject("Msxml2.XMLHTTP");
    } catch (e) {
    try { ajaxRequest = new ActiveXObject("Microsoft.XMLHTTP");
    } catch (e) {
    return false;
    }}}
    ajaxRequest.onreadystatechange = function() {
        if ( ajaxRequest.readyState == 4 ) {
            var ajaxDisplay = document.getElementById('responseDiv');
            ajaxDisplay.innerHTML = ajaxRequest.responseText;
            }
        }
    var queryString = "?ElementID="+ElementID+"&initialValue="+initialValue;
    ajaxRequest.open("GET", "ajaxRequest.php"+queryString, true);
    ajaxRequest.send(null);
    }

// this is the function we wanted to call in AJAX, 
// but we put it here instead with an argument (ElementID)
function AttachAutocomplete(ElementID) {
    // this list is static, but can easily be pulled in from 
    // a database using PHP. That would look something like this:
    /*
     * $list = "";
     * $r = mysqli_query($mysqli_link, "SELECT element FROM table");
     * while ( $row = mysqli_fetch_array($r) ) {
     *    $list .= "\".str_replace('"','\"',$row['element'])."\",";
     *    }
     * $list = rtrim($list,",");
     */
    var availableIDs = ["Demo1","Demo2","Demo3","Demo4"];
    $("#"+ElementID).autocomplete({ source: availableIDs });
    }
//-->
</script>
</head>
<body>
<!-- this is where the AJAX response sneaks in after DOM is loaded -->
<!-- we're using an onclick event to trigger the initial AJAX call -->
<div id="responseDiv"><a href="javascript:void(0);" onclick="editDemoText('EditableText','I am editable!');">I am editable!</a></div>
</body>
</html>

ajaxRequest.php

<?php
// for this application, onfocus works well because we wouldn't really 
// need the autocomplete populated until the user begins typing
echo "<input type=\"text\" id=\"".$_GET['ElementID']."\" onfocus=\"AttachAutocomplete('".$_GET['ElementID']."');\" value=\"".$_GET['initialValue']."\" />\n";
?>
Typel
quelle
-1

Federico Zancans Antwort ist richtig, aber Sie müssen Ihrem Skript keine ID geben und Ihr gesamtes Skript . Bewerten Sie einfach Ihren Funktionsnamen und er kann aufgerufen werden.

Um dies in unserem Projekt zu erreichen, haben wir eine Proxy-Funktion geschrieben, um die in der Ajax-Antwort zurückgegebene Funktion aufzurufen.

function FunctionProxy(functionName){
    var func = eval(functionName);
    func();
}
Hüseyin Yağlı
quelle