JavaFX WebView: snippets
Today I present a few snippets around JavaFX WebView that I use in The Console:
- load style sheet
- set style or class of element
- scroll page to the bottom
- check whether the page is scrolled down to the bottom
- handle adding DOM elements while web engine loads
- remove some elements from document
- style scrollbar in the document
Load style sheet
webView.engine.userStyleSheetLocation = getClass().getResource("ConsoleOutput_WebView.css").toString
Set element style or class
entryNode.setAttribute("style", "color: #" + hexColor)
entryNode.setAttribute("class", className)
Scroll to the bottom
Since HTML page is not a domain of JavaFX but our WebView, simply scrolling down to the bottom of page is a matter of internal WebView scrollbar rather than any JavaFX control. That’s why we have to handle this situation with JavaScript.
def void scrollToBottom() {
val script = 'window.scrollTo(0, document.body.scrollHeight)'
engine.executeScript(script)<br>}
}
Is it scrolled down?
In The Console I scroll down the page when new text line appears. But sometimes it’s a bit frustrating when you want to read certain lines and now ones keep being added. That’s why I want to check whether page is scrolled down to the bottom or not. If the page wasn’t scrolled down before adding a text entry then I don’t scroll it down automatically after adding the text entry. JavaScript comes again.
def boolean isScrolledToBottom() {
val script = '(window.innerHeight + window.scrollY) >= document.body.offsetHeight'
return engine.executeScript(script) as Boolean
}
Handle adding data to HTML during loading
WebView consists of WebKit engine which is loaded asynchronously. In The Console I write initialization logs to console too, so I need it as quick as possible. Instead of waiting for initialization I create tasks and push them to the queue. When succeeds loading then I launch all tasks.
// this could be in class
val BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>
// this in some initialization method
webView.engine.loadWorker.stateProperty.addListener([ observableValue, state, newState |
if (newState.equals(Worker.State.SUCCEEDED)) {
while (!queue.empty) {
queue.poll.run()
}
}
])
engine.loadContent("<html><head></head><body></body></html>")
And here’s a method I used for appending text entries to the console, even if it’s still not loaded - by queuing them as tasks.
def private createTextEntry(String text, String styleClass, BiConsumer<Element, Text> nodeModifier) {
val entry = createEntry()
val Runnable task = [
val wasAlreadyScrolled = isScrolledToBottom()
val doc = engine.document
val body = doc.getElementsByTagName("body").item(0)
val entryNode = doc.createElement("pre")
val textNode = doc.createTextNode(text)
entryNode.appendChild(textNode)
body.appendChild(entryNode)
entry.entryNode = entryNode
entry.textNode = textNode
var className = 'entry-text'
if (styleClass != null) {
className += ' ' + styleClass
}
entryNode.setAttribute("class", className)
if (nodeModifier != null) {
nodeModifier.accept(entryNode, textNode)
}
if (wasAlreadyScrolled) {
scrollToBottom()
}
]
if (!isWebLoaded) {
queue.put(task)
}
else {
task.run()
}
return entry
}
Remove some elements
Here’s another job that needs dealing with a document. But this time, instead of injecting JavaScript code, we’re going to use the power of org.w3c.dom.
override clear() {
val Runnable task = [
val doc = engine.document
val body = doc.getElementsByTagName("body").item(0)
for (entry : entries) {
body.removeChild(entry.entryNode)
entry.dispose()
}
]
if (!isWebLoaded) {
queue.put(task)
}
else {
task.run()
}
}
entries is a custom collection that contains entryNode s.
Style WebView scrollbar
Styling again but this time it’s a little harder. Styling JavaFX scrollbars is easy but it’s a little harder to find out how to style scrollbars inside WebView’s document. Here’s what I used for The Console.
::-webkit-scrollbar {
width: 16px;
}
::-webkit-scrollbar-track {
background-color: black;
}
::-webkit-scrollbar-thumb {
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 6px;
box-shadow: inset 0 0 6px rgba(0,0,0,0.5);
background: black;
}
::-webkit-scrollbar-thumb:vertical {
min-height: 100px;
}
::-webkit-scrollbar-thumb:hover, ::-webkit-scrollbar-thumb:active {
background: rgba(255, 255, 255, 0.1);
}