I recently came across the fact that the imports of bootstrap were not accessible in my code. Namely, the plugin alert for jQuery was not available within my code (jQuery.fn.alert was undefined). I had in the header:
<script src="#routes.Assets.at("lib/jquery/jquery.min.js")"
type="text/javascript"></script>
<script src="#routes.Assets.at("lib/bootstrap/js/bootstrap.min.js")"
type="text/javascript"></script>
and in the body:
<body>
#playscalajs.html.scripts("client")
</body>
In the client script, when it called $("...").alert(), I receive the error: alert is not a function, although I verified bootstrap did define it.
How to make bootstrap and playscalajs work nicely together?
The problem came from #playscalajs.html.scripts("client") which is unfold to these:
<script src="/assets/client-jsdeps.js" type="text/javascript"></script>
<script src="/assets/client-fastopt.js" type="text/javascript"></script>
<script src="/assets/client-launcher.js" type="text/javascript"></script>
jQuery is imported again in client-jsdeps.js since it is a scala-js dependency. Since I could not interleave bootstrap imports before the client-launcher.js script and after the client-jsdeps.js scripts, I delayed the execution of the main object using the following code:
// New object calling the previous Main object
#JSExport
object MainDelayed extends js.JSApp {
#JSExport def main(): Unit = $(document).ready(Main.main _)
}
// The original object
object Main {
def main() = { ... }
}
Related
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.
I was just benchmarking a new play/scala application.
When performing a simple text output in my action, I get 60K requests per second.
If I render a view (see below), it drops to 13K per second.
Since views are just functions in scala, I would have expected that the extra overhead of calling a function wouldn't drop the requests per second down so dramatically.
I only ran the benchmark for 10-30 seconds, would it take longer for the jvm to optimize maybe or this is just expected behavour?
def index() = Action { implicit request: Request[AnyContent] =>
Ok("hello")
}
If I actually render a view, the requests per second drops to about 13K.
def index() = Action { implicit request: Request[AnyContent] =>
Ok(views.html.index())
}
/app/views/index.scala.html
#()
#main("Welcome to Play") {
<h1>Welcome to Play!</h1>
}
/app/views/main.scala.html
#*
* This template is called from the `index` template. This template
* handles the rendering of the page header and body tags. It takes
* two arguments, a `String` for the title of the page and an `Html`
* object to insert into the body of the page.
*#
#(title: String)(content: Html)
<!DOCTYPE html>
<html lang="en">
<head>
#* Here's where we render the page title `String`. *#
<title>#title</title>
<link rel="stylesheet" media="screen" href="#routes.Assets.versioned("stylesheets/main.css")">
<link rel="shortcut icon" type="image/png" href="#routes.Assets.versioned("images/favicon.png")">
</head>
<body>
#* And here's where we render the `Html` object containing
* the page content. *#
#content
<script src="#routes.Assets.versioned("javascripts/main.js")" type="text/javascript"></script>
</body>
</html>
As I already said in my comment, the view is not a trivial function. It performs string concatenation and it also calls routes.Assets.versioned three times. Profiling session shows, that the view basically only waits on this function:
Drilling down, we learn, that the versioned function always re-reads the file from classpath:
Maybe you can open an issue and ask Play framework creators, whether serving of assets could be optimized better ?
Edit: I profiled two setups. First I modified HomeControllerSpec test:
"render the index page from a new instance of controller" in {
val controller = new HomeController(stubControllerComponents())
val indexAction = controller.index()
val fakeRequest = FakeRequest(GET, "/")
var i = 100000
while (i > 0) {
indexAction.apply(fakeRequest)
i -= 1
}
But this does not rule out, that some components can behave differently in production mode. So I ran sbt stage and started generated application, attached profiler to running JVM and executed 10000 requests to the profiled JVM. Result was identical though.
I want to inject some CSS and JavaScript files via a preprocessor.
In my preprocessor I inject the html template to the body element.
I printed the result out via console.log(document.body) - you can see the result at the bottom. It looks good, but the script is not evaluated.
If I run console.log(window.foobar) in my test, it's undefined.
Actually I don't want to to inject simple scripts, I want to load some files via
<script src="build/app.js"></script>
I need it in every test, so I don't want to refactor every single test for the same code injection, that's the reason why I tried to put it into the html generated by karma.
<body><script> window.foobar = 'miau!';</script>
<!-- The scripts need to be at the end of body, so that some test running frameworks
(Angular Scenario, for example) need the body to be loaded so that it can insert its magic
into it. If it is before body, then it fails to find the body and crashes and burns in an epic
manner. -->
<script type="text/javascript">
// sets window.__karma__ and overrides console and error handling
// Use window.opener if this was opened by someone else - in a new window
if (window.opener) {
window.opener.karma.setupContext(window);
} else {
window.parent.karma.setupContext(window);
}
// All served files with the latest timestamps
window.__karma__.files = {
'/base/node_modules/mocha/mocha.js': '253e2fdce43a4b2eed46eb25139b784adbb5c47f',
'/base/node_modules/karma-mocha/lib/adapter.js': '3664759c75e6f4e496fef20ad115ce8233a0f7b5',
'/base/test/custom-test.js': 'abf5b0b3f4dbb62653c816b264a251c7fc264fb9',
'/base/test/build/build.css': 'df7e943e50164a1fc4b66e0a0c46fc86efdef656',
'/base/test/build/build.js': '9f0a39709e073846c73481453cdee8d37e528856',
'/base/test/build/test.js': '0ccd4711b9c887458f81cf1dedc04c6ed59abe43'
};
</script>
<!-- Dynamically replaced with <script> tags -->
<script type="text/javascript" src="/base/node_modules/mocha/mocha.js?253e2fdce43a4b2eed46eb25139b784adbb5c47f"></script>
<script type="text/javascript" src="/base/node_modules/karma-mocha/lib/adapter.js?3664759c75e6f4e496fef20ad115ce8233a0f7b5"></script>
<script type="text/javascript" src="/base/test/custom-test.js?abf5b0b3f4dbb62653c816b264a251c7fc264fb9"></script></body>
Karma introduces the page scripts/html just like ajax, so it wont execute once the append has finished.
You will need to append the files for each spec. I have a helper for this job:
function appendCSS(path){
var link = document.createElement('link');
link.rel = 'stylesheet';
link.href='base/' + path;
document.body.appendChild(link)
}
function appendScript(path){
var link = document.createElement('script');
link.type = 'javascript';
link.src='base/' + path;
document.body.appendChild(link)
}
function loadAssets(page){
document.body.innerHTML = __html__['_site/' + (page || 'index') + '.html'];
appendCSS('_site/styles/demo.css');
appendCSS('_site/styles/' + page + '.css');
appendScript('_site/scripts/vendor.js');
appendScript('_site/scripts/' + page + '.js');
}
module.exports = {
loadAssets: loadAssets
};
In my spec i then simply call the helper, passing the name of the html page to be tested.
require('../helper').loadAssets('tested-page-name');
As you can see, i use the borwserify plugin, but i hope this helps.
I have been trying to integrate zero clipboard library with gwt code as follows.
test.html
<script type="text/javascript" language="javascript" src="test/test.nocache.js"></script>
<script type="text/javascript" src="ZeroClipboard.js"></script>
<script language="text/javascript">
function initZeroClipboard() {
ZeroClipboard.setMoviePath('ZeroClipboard.swf');
}
</script>
.....................................
//Intial body load instantiating 'Moviepath'
<body onload="initZeroClipboard();">
TestWidget.java
Anchor copy = new Anchor("Copy");
......................
//setting id to refer the movie
copy.getElement().setId("copyId");
glueCopy("Hello World");
...........
//Native method
public static native void glueCopy(String text) /*-{
var clip = new $wnd.ZeroClipboard.Client();
clip.setText(text);
clip.glue('copyId');
}-*/;
But on intial load itself i am getting the following error in IE and FF.
Jetty Server IE error
Jetty server FF error
I have donloaded 'ZeroClipboard.swf' and 'ZeroClipboard.js' files from the following
https://github.com/jonrohan/ZeroClipboard
Can anyone come across this issue, if so suggest me how to get rid of this.
I am trying to embed Google-Plus into my GWT Application. I would like it to be embedded into a HorizontalPanel. I did read +1button developers google. I didn't find any post about this particular problem in stackoverflow. My problem might be that I don't understand how to include the js into a GUI component. I would appreciate an Example of how to add the Google+ code into a Panel.
Here is how to do it:
Documentation:
<!-- Place this tag in your head or just before your close body tag -->
<script type="text/javascript" src="https://apis.google.com/js/plusone.js"></script>
<!-- Place this tag where you want the +1 button to render -->
<g:plusone></g:plusone>
in GWT:
private void drawPlusOne() {
String s = "<g:plusone href=\"http://urltoplusone.com\"></g:plusone>";
HTML h = new HTML(s);
somePanel.add(h);
// You can insert a script tag this way or via your .gwt.xml
Document doc = Document.get();
ScriptElement script = doc.createScriptElement();
script.setSrc("https://apis.google.com/js/plusone.js");
script.setType("text/javascript");
script.setLang("javascript");
doc.getBody().appendChild(script);
}
I've personally never embedded the +1 button in GWT, but the linked article seems pretty self explanatory.
In the section "A Simple Button", it indicates that the simplest way of implementing GooglePlus integration is to add this:
<script src="https://apis.google.com/js/plusone.js" />
<g:plusone></g:plusone>
First, the <script> tag should be included in your .gwt.xml file.
Then I'd implement the <g:plusone></g:plusone> like this:
public class GPlusOne extends SimplePanel {
public GPlusOne () {
super((Element)Document.get().createElement("g:plusone").cast());
}
}
(Note that this code is untested, but it's based on the simple concept that a SimplePanel can be extended to compile as any HTML element.)
Then you'd use the new GPlusOne element wherever you'd want the button to show.
I found a better way to do it:
Follow this example to have the button work on invocation on a normal html page (you can try one here http://jsfiddle.net/JQAdc/)
<!doctype html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<script src="https://apis.google.com/js/plusone.js">
{"parsetags": "explicit"}
</script>
<script type="text/javascript">
function gPlusBtn(id, params) {
/* window.alert("searching for "+ id +" with params: "+ params) */
paramsObj = eval( '('+params+')' );
gapi.plusone.render(id, paramsObj );
}
// params is here just for a reference to simulate what will come from gwt
params = '{href:"http://1vu.fr", size:"tall"}';
</script>
</head>
<body>
taken from http://jsfiddle.net/JQAdc/
<div id="gplus" />
<button onclick="gPlusBtn('gplus', params)">show!</button>
</body>
</html>
Then you can call a native method to trigger the button display on Activity start (if you're using MVP).
protected native void plusOneButton(String id, String params) /*-{
$wnd.gPlusBtn(id, params);
}-*/;
You can have multiple buttons with different urls, that's why id is left as a parameter.
NOTE: for me the raw HTML works on localhost, but the GWT version. I have to deploy to the server to be able to see the results