Namek Dev
a developer's log
NamekDev

JavaFX WebView: snippets

March 14, 2016

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);
}
Daj Się Poznać, java, xtend, the-console
comments powered by Disqus