Wątek przeniesiony 2023-08-04 10:06 z JavaScript przez Riddle.

Jak wywołać funkcję z tagów script?

0

Hej,

Zastanawia mnie jedna kwestia.
Po załadowaniu się całej strony, za pomocą zewnętrznej usługi wstrzykuję do kodu strony kawałek personalizowanego contentu.
Kod do istniejącego htmla dodawany jest za pomocą na przykład appendChild().

Przykładowo, cała wstrzykiwana struktura wygląda tak:

<div>costam</div>
<style> div {background-color: red;}</style>
<script> function attachBehaviour() {console.log('test')} attachBehaviour()</script>

Część html i css działa bez zarzutu, jednakże część jsowa nie wywołuje się. Funkcja nie jest zdefiniowana w oknie, widze <script> i zawartość w strukturze html ale nie mogę się do niego odnieść, próbując wywołać funkcje dostaje info, że jest nie zdefiniowana.
Nie ważne czy wywołuje function expression(iife), dodaje funkcje do onload obrazka czy w ogóle nie definiuje funkcji i chce zobaczyć chociaż console.log()a, skrypt się nie wywołuje.

Dajcie proszę znać jak to ogarnąć i czego nie wiem.

1

Po załadowaniu się całej strony, za pomocą zewnętrznej usługi wstrzykuję do kodu strony kawałek personalizowanego contentu.

Pytanie, w jaki sposób następuje to wstrzykiwanie? Często takie rzeczy są wstrzykiwane przez iframe właśnie po to, żeby zachować izolację różnych zewnętrznych wtyczek od głównej strony.

Da się to zrobić inaczej, ale pytanie, co robisz, z czego dokładnie korzystasz. Jeśli korzystasz z zewnętrznej usługi, to powinna być jakaś konkretna dokumentacja odnośnie prawidłowej integracji ze stroną.

Chociaż istnieje też możliwość, że masz gdzieś jakiś błąd po prostu.

0

Pytanie, w jaki sposób następuje to wstrzykiwanie? Często takie rzeczy są wstrzykiwane przez iframe właśnie po to, żeby zachować izolację różnych zewnętrznych wtyczek od głównej strony.

W tym wypadku jest to raczej proste, na froncie łapiemy element po klasie i używamy na nim metody appendChild(), jako argument podajemy to co pisałem wyżej.
Kod odpalany jest np na kliknięcie w przycisk na stronie, po jego kliknięciu pojawia się na stronie content. Nie jest to iframe, dodaje do rodzica dziecko.
Problem mam z tym, że nie wiem w jaki sposób wywołać kod, który osadzam na stronie.

Tak wygląda przykładowo struktura html bazowej strony:

<html>
<body>
etc etc

<div class="x-custom-elem"></div>
</body>
</html>

A tak po osadzeniu kodu dodatkowego:

<html>
<body>
etc etc

<div class="x-custom-elem">
  <div>costam</div>
  <style> div {background-color: red;}</style>
  <script> function attachBehaviour() {console.log('test')} attachBehaviour()</script>
</div>
</body>
</html>

Dodatkowo, w samym kodzie błędu nie ma jako tako bo testując wszystko na localhoscie cały komponent działa bez zarzutu.
Ten kod działa też jeśli wrzuce cały w np onload elementu img, na przykład:

<img src="https://someurl.com" onload="dataLayer.push({'event':'test','event_category':'test','event_label':'test','event_action':'dosmth'});"/>

Nie chce tu jednak używać onloada bo sam kod funkcji jest tak rozbudowany ze wole uniknąć escapowania wszystkiego używając iife bezpośrednio w onloadzie.

Mam jeszcze pomysł taki, żeby za pomocą appendChild() zdefiniować funkcje a wywołać ją tylko w onload, ale tak jak pisałem tutaj dostaje info, że funkcja jest niezdefiniowana. Sprawdzałem czy działają w tym samym scopie, onload daje błąd nawet jak go wypale po wielu sekundach od appendChild().

Ja w strukturze html strony widzę to co wrzucam tylko nie rozumiem dlaczego nie mam do tego dostępu. Nawet w konsoli wywołując window.attachBehaviour() nie znajduje mi tej funkcji.

EDIT
Badając jak to działa, w zewnętrznej usłudze nie idzie po prostu appendChild() ale raczej coś na wzrór replaceChildren(), dowiem się pewnie dopiero po weekendzie, sprawdzając jak zachowują się te metody to nie jest tak prosto rozwiązane, zerknę do dokumentacji jak to ugryźć.

0

No to pytanie na ten moment klaruje mi się takie, jak wywołać funkcje z kodu poniżej?
Sam html się dodaje poprawnie.

const newElem = ` 
  <div class="example">costam</div>
  <style> .example {background-color: red;} </style>
  <script> function attachBehaviour() {console.log('test')} attachBehaviour()</script>
`
document.querySelector('div[costam]').innerHTML = newElem
0
const newElem = ` 
  <div class="example">costam</div>
  <style> .example {background-color: red;} </style>
  
`;
document.body.innerHTML = newElem;
var z =document.createElement("script");
z.innerHTML="alert();";
document.body.appendChild(z);

// albo

const newElem = ` 
  <div class="example">costam</div>
  <style> .example {background-color: red;} </style>
  <input style="z-index:9999;" value="alert('test');"/>
  <button style="z-index:9999;"onClick=eval(this.previousSibling.value);>Exec</button>
`;
document.body.innerHTML = newElem;

voila

0

No jasne, jsem mogę bez problemu, pytanie było o HTML bo żeby wywołać skrypt muszę go osadzić w htmlu ale i tak dzięki za poświęcony czas ;)

Problem rozwiązałem wrzucając funkcje w onload elementu IMG tylko tak jak pisałem musiałem escapowac dużo znaczników ale tak czy inaczej dziala.

Wydaje mi się że to co ja chciałem zrobić jest po prostu w htmlu nie możliwe, z grubsza?

2

To jest celowe zachowanie opisane w standardzie
https://www.w3.org/TR/2008/WD-html5-20080610/dom.html#innerhtml0

Note: script elements inserted using innerHTML do not execute when they are inserted.

Powodów jest sporo - od bezpieczeństwa, przez performance (skrypty odpalają się synchronicznie) po zapobieganie odpalaniu tego samego skryptu wielokrotnie.

Przykładowo tagi <script> mają ukrytą flagę "already executed" tak że skrypt się wykonuje tylko raz, dzięki temu przemieszczenie elementu script w inne miejsce DOM nie wywoła ponownie skryptu. Przy użyciu "innerHTML" nie da się stwierdzić czy skrypt już był wykonany czy nie bo flaga nie jest przypisana do tekstu. Taki skrypt mógłby na przykład zawierać document.write który normalnie działa ale gdybyś próbował przemieścić element w inne miejsce za pomocą innerHTML z takim skryptem w środku to wywołanie tego skryptu spowodowałoby podmianę całej strony na ten tekst (ze względu na brak aktywnego insertion point po zamknięciu dokumentu, document.write stworzyłby najpierw nowy dokument).
Tak więc rozwiązano w ten sposób sporo problemów o które się dzięki temu nie musisz martwić.

Mimo że tagi <script> nie są wywoływane to nadal nie oznacza jednak że treści wstawianej można zaufać, eventy nadal są odpalane więc można na przykład wstawić błędny obrazek i wywołać kod w obsłudze onerror:

const newElem = ` 
  <img src="http://blednyurl" onerror="alert('dziala')" />
`;
document.body.innerHTML = newElem;
0
obscurity napisał(a):

To jest celowe zachowanie opisane w standardzie
https://www.w3.org/TR/2008/WD-html5-20080610/dom.html#innerhtml0

Rewelacyjna odpowiedź, dziękuję!

1

A zamiast onload bardziej elegancko możesz wyłuskać skrypty z elementu utworzonego na podstawie innerHTML, potem treść tych skryptów możesz ustawić do nowo stworzonych tagów typu script (które nie będą miały ustawionej flagi already executed) i dodać je do drzewka:

function executeScriptsFrom(element) {
	const scripts = element.getElementsByTagName('script');
	for (const script of scripts) {
		// nie możemy użyć `cloneNode` bo to kopiuje również flagę uruchomienia skryptu
		const notExecutedScript = document.createElement('script');
		notExecutedScript.type = script.type;
		notExecutedScript.innerText = script.innerText;
		// podmieniamy uruchomiony skrypt na nieuruchomiony co spowoduje jego ponowne uruchomienie
		script.parentNode.replaceChild(notExecutedScript, script);
    }
}

const newElem = document.createElement('div')
newElem.innerHTML = ` 
  <div><h1>Hello world</h1></div>
  <script>
     alert('test');
  </script>
`;

document.body.appendChild(newElem);
executeScriptsFrom(newElem);

choć nazwa funkcji executeScriptsFrom jest myląca - ta funkcja tak naprawdę nie wykonuje żadnych skryptów ale oznacza je jako nieodpalone. I pozostaną nieodpalone dopóki ich nie dodamy do drzewka dokumentu.
Jeśli zamienimy miejscami dwie ostatnie linijki to skrypt się odpali dopiero po ostatniej linijce. Możemy nawet trzymać sobie taki "nieodpalony" node i w dowolnej chwili dodać go do dokumentu na przykład w setTimeout

0

Dzięki za odpowiedzi, wygląda na to że poszukiwałem odpowiedzi jak dokładniej działają tagi script.

Tutaj odniosę się zbiorczo, że w tym przypadku chodziło o nie korzystanie z jsa bezpośrednio bo nie mam możliwość wykonania go więc akurat w tym przypadku to byloby omijanie onload przy użyciu kolejnego onloada :) bardzo ciekawy temat podałeś obscurity także dzięki jeszcze raz.

Taki case związany z konkretnym narzędziem więc muszę poruszać się czasem wokół określonych ograniczeń.

1 użytkowników online, w tym zalogowanych: 0, gości: 1