MouseEvent.target returns an EventTarget instead of a HTMLElement when clicked inside an iframe, in ScalaJs - scala.js

MouseEvent.target returns an EventTarget instead of a HTMLElement when clicked inside an iframe, in ScalaJs.
src/main/scala/tutorial/webapp/TutorialApp.scala:
package tutorial.webapp
import org.scalajs.dom._
import org.scalajs.dom.raw._
import scala.scalajs.js
object TutorialApp {
def main(args: Array[String]): Unit = {
window.document.body.innerHTML = "<p><b>main window</b></p>"
val iframe = document.createElement("iframe")
document.body.appendChild(iframe)
val iframeWindow = iframe.asInstanceOf[HTMLIFrameElement].contentWindow
iframeWindow.document.body.innerHTML = "<p><b>iframe</b></p>"
window.document.addEventListener("click", clicked)
// this works as expected:
// clicking on the 'main window' text, produces this console log:
// - clicked an HTMLElement B
// - parent is an HTMLParagraphElement P
iframeWindow.document.addEventListener("click", clicked) // this doesn't
// this does not work as expected:
// clicking on the 'iframe' text, produces this console log:
// - clicked an EventTarget B
// - parent is an HTMLElement P
}
def clicked(mouseEvent: MouseEvent) {
mouseEvent.target match {
case e: HTMLElement => console.log("clicked an HTMLElement", e.asInstanceOf[HTMLElement].tagName)
case e: EventTarget => console.log("clicked an EventTarget", e.asInstanceOf[HTMLElement].tagName)
}
val parent = mouseEvent.target.asInstanceOf[HTMLParagraphElement].parentElement
parent match {
case e: HTMLParagraphElement => console.log("parent is an HTMLParagraphElement", e.asInstanceOf[HTMLElement].tagName)
case e: HTMLElement => console.log("parent is an HTMLElement", e.asInstanceOf[HTMLElement].tagName)
}
}
}
index.html
<html>
<body>
<!-- Include Scala.js compiled code -->
<script type="text/javascript" src="./target/scala-2.12/scala-js-tutorial-fastopt.js"></script>
</body>
</html>
When I click inside the iframe on the <h1>iframe</h1>, I get an EventTarget instead of an HTMLElement. Casting it to HTMLElement works, but e.parentElement is an HTMLElement instead of HTMLParagraphElement.
Why and how to solve it?

Hoping someone will provide a more precise answer, but in the absence of that:
Iframes are typically loaded from other URLs, often from another origin, and their security model reflects that primary use case – the document inside the iframe is quite isolated from the parent document. I'm not quite sure which exact restriction you're hitting though as manually creating iframes like in your example is not very common.
Usually, the Javascript code running inside the iframe needs to be willing to communicate with Javascript code running in the parent window. Currently in your example you only have code running in the parent window, as the iframe itself does not load any scripts.
Depending on your exact use case, there are several ways to achieve this. For example, you could post custom events to the parent window as described in How to communicate between iframe and the parent site?
Iframes can be really annoying if you don't want the isolation that they come with.

Related

JustPy Redirect won't redirect

I feel like I'm doing something rediculously stupid here, but I've been banging my head against the wall all day, and don't seem to be making any progress! I have this function:
def init_create_reports(self, msg):
if not common.create_report_in_progress and len(common.tracks_selected_for_report) > 0:
common.create_report_in_progress = True
return jp.redirect('/yeeha')
That is called when the user clicks a button. Stepping through it, everything works fine until the redirect function, which seems to fire from within justPy, but then I get nothing. It never redirects to the '/yeeha' route. It seems simple enough according to the godawful justPy documentation, but agaghhhhh!
I've also tried passing the redirect the current webpage: wp.redirect('/yeeha') with the same outcome. Honestly all I need is to set that variable and navigate to another page, and at this point I'm prepared to just do it from a tag.
It seems that there is currently a problem in Justpy with the handling of the event_result of an event callback function. I opened an issue for this problem. See https://github.com/justpy-org/justpy/issues/657
Alternatively to jp.redirect() you can also set the redirect attribute of the webpage.
In your example it would be:
def init_create_reports(self, msg):
if not common.create_report_in_progress and len(common.tracks_selected_for_report) > 0:
common.create_report_in_progress = True
msg.page.redirect = '/yeeha'
Here is a complete example showing a redirect from one justpy webpage to another route:
import justpy as jp
def hello_function() -> jp.WebPage:
wp = jp.WebPage()
jp.P(a=wp, text='Hello there!', classes='text-5xl m-2')
jp.Button(
a=wp,
text="redirect",
on_click=handle_click,
classes="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-full"
)
return wp
def handle_click(self, msg):
"""
handle redirect button click
Args:
self: justpy component that triggered the event
msg: event message
"""
wp = msg.page
wp.redirect = "/bye"
def bye_function() -> jp.WebPage:
wp = jp.WebPage()
wp.add(jp.P(text='Goodbye!', classes='text-5xl m-2'))
return wp
jp.Route('/bye', bye_function)
jp.justpy(hello_function)
See also Login Example

How can I dynamically create HTML components in Marko?

I want to create new Marko components every time the user clicks a button — by calling something like the JavaScript DOM method document.createElement("tag"). How can I do this in Marko, not just with ordinary HTML tags, but with custom Marko tags?
What I tried: document.createElement("custom-marko-component")
Expected behavior: Marko engine compiles a new instance of the custom component.
Actual behavior: The browser makes a useless new <custom-marko-component></custom-marko-component>.
Use Marko's rendering functions (documentation: https://markojs.com/docs/rendering/):
Example:
// Create the custom component, like document.createElement() but asynchronous.
// Import `./custom-marko-component.marko`
var customComponent = require("./custom-marko-component");
var resultPromise = customComponent.render({});
// Insert the custom component into the webpage.
resultPromise.then(result => {
result.appendTo(document.body);
});

Can't locate element by id in scala-js-dom

I'm trying to write text to an element in the DOM:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>The Scala.js Tutorial</title>
<!-- Include Scala.js compiled code -->
<script type="text/javascript" src="./target/scala-2.13/hello-world-fastopt/main.js"></script>
</head>
<body>
<div id="output"></div>
</body>
</html>
However, the element is null:
package hello
import org.scalajs.dom
object TutorialApp {
def main(args: Array[String]): Unit = {
println(dom.document.getElementById("output")) // null
dom.document.onload = (e) => {
println(dom.document.getElementById("output")) // null
}
}
}
What's needed to do to get to the element with id "output"?
Edit: Answers to questions:
def main(args: Array[String]): Unit = {
// Uncaught TypeError: Cannot read property 'innerHTML' of null
println("innerHTML: " + dom.document.body.innerHTML)
dom.document.onload = (e) => {
println("onload") // Doesn't get called
}
}
Locally, compiling with sbt fastOptJS. The end result line is:
[success] Total time: 2 s, completed Dec 13, 2020 4:30:36 PM
The HTML is as is rendered in the HTML page (hello-world-template/index-dev.html) (by viewing source), verbatim. It's printing "Initial text" inside the <DIV>.
Printing the body's innerHTML in main() results in TypeError (see code comment). The onload() in the code seems to not get called at all, as a println() call inside of it doesn't print.
Yes, it printed null in the browser console.
In your code:
def main(args: Array[String]): Unit = {
println(dom.document.getElementById("output")) // null
dom.document.onload = (e) => {
println(dom.document.getElementById("output")) // null
}
}
The first println prints null because at this point the browser hasn't finished parsing HTML / instantiating the DOM (simplifying a bit...). The browser reads the HTML top to bottom and builds up the DOM tree as it goes, and when it sees your script tag, it "blocks", i.e. downloads and executes the script first before reading the rest of the DOM. That's when your main runs, and why the output div could not be found at this stage – the browser didn't get to it yet.
To solve this, you could move your script tag to be below the <body> tag in your HTML, so that the script would be downloaded and executed after the document is all parsed and the DOM all initialized.
But a nicer solution is to delay DOM access in the script until the browser fires an event indicating it's safe to do that. That's what you're trying to achieve by assigning dom.document.onload, but this is kinda "old style" in JS world, and is not universally supported by all browsers (specifically dom.document.onload I mean, I think assigning dom.window.onload might work just fine).
Ultimately it's best to use modern syntax for this:
dom.window.addEventListener("load", ev => {
println(dom.document.getElementById("output"))
})
You can also use the "DOMContentLoaded" event instead of "load", they serve the same purpose, but have slightly different timing related to waiting for resources like images. Does not matter in your case though. Check MDN for details if curious.

Scalatags tag / container that prints nothing

I have a website structure where I use functions to maximize code reuse. Sometimes I need a certain page to add more scripts or stylesheets to the head of the html page. The code below does what I want, except I'm forced to use a div or some other tag to contain the rest of the scalatags. I don't really want a random div tag in my document's head part. Is there some generic/empty tag I could use for this purpose? (tag("") makes a compilation error btw.)
import scalatags.Text.all._
class Temp {
def document = html(
head(
script("some javascript"),
extraScripts
),
body(
h1("A website")
)
)
def extraScripts = div( // <-- This div is the part I want to change
script("More scripts"),
script("and what not")
)
}

Writing script src dynamically via wicket

I want my page to load javascript dynamically to my body:
<script type= "text/javascript" src="this path should be decided from wicket dynamically"/>
I am using wicket version 1.4 therefore JavaScriptResourceReference does not exist in my version (for my inspection it wasn't ' )
how can I solve this ?
thanks in advance :).
I specify my comment into an answer.
You can use this code snippet:
WebMarkupContainer scriptContainer = new WebMarkupContainer("scriptContainer ");
scriptContainer .add(new AttributeAppender("type", Model.of("text/javascript")));
scriptContainer .add(
new AttributeAppender("src", urlFor(
new JavaScriptResourceReference(
YourClass.class, "JavaScriptFile.js"), null).toString()));
add(scriptContainer );
and the corresponding html:
<script wicket:id="scriptContainer "></script>
Just change the string JavaScriptFile.js to load any other Javascript file.
JavascriptPackageResource.getHeaderContributor() does exactly what you need.
You need nothing in your markup, just add the HeaderContributor it returns to your page.
Update: For Wicket 1.5 see the migration guide, but it goes like this:
public class MyPage extends WebPage {
public MyPage() {
}
public void renderHead(IHeaderResponse response) {
response.renderJavaScriptReference(new PackageResourceReference(YuiLib.class,
"yahoo-dom-event/yahoo-dom-event.js"));
response.renderCSSReference(new PackageResourceReference(AbstractCalendar.class,
"assets/skins/sam/calendar.css"));
}
}
If you want to put your <script> element in the body, you can simply declare it as a WebMarkupContainer and add an AttributeModifier to set the src attribute. Although in that case wicket won't generate the relative URLs for you, you have to do it yourself.
I'm not sure I understood completely.
If you are trying to create and append a script to the body after the page is loaded you should do it this way:
<script type="text/javascript">
function load_js() {
var element = document.createElement("script");
element.src = "scripts/YOUR_SCRIPT_SRC.js"; // <---- HERE <-----
document.body.appendChild(element);
}
// Wait for the page to be loaded
if(window.addEventListener)
window.addEventListener("load",load_js,false);
else if(window.attachEvent)
window.attachEvent("onload",load_js);
else
window.onload = load_js;
</script>
What I did here is create a new script element, and then apply to it its source.
That way you can control dynamicaly the src. After that I append it to the body.
The last part is there so the new element is applied only after the page is loaded.