Skip to content
This repository has been archived by the owner on Aug 10, 2024. It is now read-only.

Commit

Permalink
major API change, bump version to 0.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
sanity committed Mar 29, 2017
1 parent fd0932b commit 51ddf4b
Show file tree
Hide file tree
Showing 21 changed files with 160 additions and 132 deletions.
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ dependencies {
}

compile 'io.github.microutils:kotlin-logging:1.4.2'
// compile group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.25'


testCompile 'io.kotlintest:kotlintest:1.3.4'
testCompile 'com.moodysalem:phantomjs-wrapper:3.0.0'
Expand Down
24 changes: 0 additions & 24 deletions docs/_posts/2017-03-11-more-examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,11 @@ date: 2017-03-11 07:46:49
1. TOC
{:toc}

#### Material Design Lite plugin

[Material Design Lite](https://getmdl.io/) is a JavaScript framework by Google let's you add a "material design"
look to your websites.

Kweb includes a fairly comprehensive plugin for MDL, supporting most MDL components. The plugin API is designed
to mirror the library API.

See [here](https://github.com/sanity/kweb/blob/master/src/main/kotlin/com/github/sanity/kweb/demos/materialDesignLite/materialDesignLite.kt)
for a usage example.

The MDL plugin's implementation can be seen [here](https://github.com/sanity/kweb/tree/master/src/main/kotlin/com/github/sanity/kweb/plugins/materialdesignlite).

#### Asynchronous usage

[This demo](https://github.com/sanity/kweb/blob/master/src/main/kotlin/com/github/sanity/kweb/demos/async/async.kt)
illustrates Kweb's powerful asynchronous features and how Kotlin's `future {}` construct can be used efficiently.

#### Select 2 plugin

[select2](https://select2.github.io/) is a JavaScript library that gives you a customizable select box with support for
searching, tagging, remote data sets, infinite scrolling, and many other highly used options.

See [this demo](https://github.com/sanity/kweb/blob/master/src/main/kotlin/com/github/sanity/kweb/demos/plugins/select2/select2demo.kt)
for a usage example.

See [here](https://github.com/sanity/kweb/tree/master/src/main/kotlin/com/github/sanity/kweb/plugins/select2) for the
implementation of the select2 plugin.

#### Others

See other demos of Kweb's capabilities [here](https://github.com/sanity/kweb/tree/master/src/main/kotlin/com/github/sanity/kweb/demos).
42 changes: 10 additions & 32 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,30 +32,9 @@ val day = doc.body.evaluate("$('#day').text()")
database.update(BOOK).set(BOOK.DAY, day).execute()
```

But Kweb's real power is what's built on top of this low-level framework. Through its plugin mechanism
Kotlin lets you use powerful JavaScript libraries like [Material Design Lite](https://getmdl.io/) through
a Kotlin DSL that mirrors the library's API, but with the benefits of Kotlin's DSL-friendly syntax and
its type safety:

```kotlin
header().create().apply {
row().apply {
title().text("Title")
spacer()
navigation().apply {
navLink().text("Delete").on.click {
database.delete().where(ID.eq(oId)).execute()
}
navLink().text("Create")
navLink().text("Modify")
}
}
}
```

Aside from Material, Kotlin has plugins for JavaScript libraries like [JQuery](https://jquery.com/),
[select2](https://select2.github.io/), and [others](https://github.com/sanity/kweb/tree/master/src/main/kotlin/com/github/sanity/kweb/plugins).
It's also surprisingly easy to build your own plugins for other JavaScript libraries, or extend those Kweb already
Kotlin has plugins for JavaScript libraries like [JQuery](https://jquery.com/) and
[others](https://github.com/sanity/kweb/tree/master/src/main/kotlin/com/github/sanity/kweb/plugins). It's also
surprisingly easy to build your own plugins for other JavaScript libraries, or extend those Kweb already
supports.

#### Features
Expand All @@ -80,37 +59,36 @@ Here we element a simple "todo" list app:
```kotlin
fun main(args: Array<String>) {
Kweb(8091, debug = true) {
doc.body.apply {
doc.body.new {
h1().addText("Simple Kweb demo - a to-do list")
p().addText("Edit the text box below and click the button to add the item. Click an item to remove it.")

val ul = ul().apply {
val ul = ul().new {
for (text in listOf("one", "two", "three")) {
newListItem(text)
}
}

val inputElement = input(type = InputType.text, size = 20)

val button = button()
button.addText("Add Item")
button.on.click {
future {
val newItemText = inputElement.getValue().await()
ul.newListItem(newItemText)
ul.new().newListItem(newItemText)
inputElement.setValue("")
}
}
}
}
Thread.sleep(10000)
}

fun ULElement.newListItem(text: String) {
fun ElementCreator<ULElement>.newListItem(text: String) {
li().apply {
addText(text)
on.click { event ->
delete() }
on.click {
delete()
}
}
}
```
Expand Down
13 changes: 8 additions & 5 deletions src/main/kotlin/com/github/sanity/kweb/Kweb.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import org.jetbrains.ktor.request.uri
import org.jetbrains.ktor.response.contentType
import org.jetbrains.ktor.routing.Routing
import org.jetbrains.ktor.routing.get
import org.jetbrains.ktor.util.ValuesMap
import org.jetbrains.ktor.websocket.Frame
import org.jetbrains.ktor.websocket.WebSocket
import org.jetbrains.ktor.websocket.readText
Expand Down Expand Up @@ -47,8 +48,6 @@ typealias JavaScriptError = String
* warning is logged
* @property buildPage A lambda which will build the webpage to be served to the user, this is where your code should
* go
*
* @sample com.github.sanity.kweb.samples.main
*/
class Kweb(val port: Int,
val debug: Boolean = true,
Expand All @@ -57,7 +56,7 @@ class Kweb(val port: Int,
val appServerConfigurator: (Routing) -> Unit = {},
val onError : ((List<StackTraceElement>, JavaScriptError) -> LogError) = { _, _ -> true},
val maxPageBuildTimeMS : Long = 200,
val buildPage: RootReceiver.() -> Unit
val buildPage: WebBrowser.() -> Unit
) {
companion object: KLogging()

Expand Down Expand Up @@ -111,11 +110,11 @@ class Kweb(val port: Int,
val logStatement = logStatementBuilder.toString()
logger.warn { logStatement }
}) {
buildPage(RootReceiver(newClientId, httpRequestInfo,this@Kweb))
buildPage(WebBrowser(newClientId, httpRequestInfo,this@Kweb))
logger.debug { "Outbound message queue size after buildPage is ${outboundBuffer.queueSize()}"}
}
} else {
buildPage(RootReceiver(newClientId, httpRequestInfo, this@Kweb))
buildPage(WebBrowser(newClientId, httpRequestInfo, this@Kweb))
logger.debug { "Outbound message queue size after buildPage is ${outboundBuffer.queueSize()}"}
}
for (plugin in plugins) {
Expand Down Expand Up @@ -253,6 +252,10 @@ private data class WSClientData(val id: String, @Volatile var outboundChannel: O
}
}

data class HttpRequestInfo(val visitedUrl : String, val headers : ValuesMap) {
val referer : String? get() = headers["Referer"]
}

data class DebugInfo(val js: String, val action : String, val throwable: Throwable)

data class S2CWebsocketMessage(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ package com.github.sanity.kweb

import com.github.sanity.kweb.dom.Document
import com.github.sanity.kweb.plugins.KWebPlugin
import org.jetbrains.ktor.util.ValuesMap
import java.util.concurrent.CompletableFuture
import kotlin.reflect.KClass
import kotlin.reflect.jvm.jvmName

class RootReceiver(private val clientId: String, val httpRequestInfo: HttpRequestInfo, internal val cc: Kweb, val response: String? = null) {
/**
* A conduit for communicating with a remote web browser, can be used to execute JavaScript and evaluate JavaScript
* expressions and retrieve the result.
*/
class WebBrowser(private val clientId: String, val httpRequestInfo: HttpRequestInfo, internal val cc: Kweb, val response: String? = null) {
private val plugins: Map<KClass<out KWebPlugin>, KWebPlugin> by lazy {
cc.appliedPlugins.map { it::class to it }.toMap()
}
Expand Down Expand Up @@ -45,15 +48,9 @@ class RootReceiver(private val clientId: String, val httpRequestInfo: HttpReques
}


fun evaluateWithCallback(js: String, rh: RootReceiver.() -> Boolean) {
cc.evaluate(clientId, js, { rh.invoke(RootReceiver(clientId, httpRequestInfo, cc, it)) })
fun evaluateWithCallback(js: String, rh: WebBrowser.() -> Boolean) {
cc.evaluate(clientId, js, { rh.invoke(WebBrowser(clientId, httpRequestInfo, cc, it)) })
}

val doc = Document(this)
}

// TODO: Not sure if this should be a separate property of RootReceiver, or passed in
// TODO: some other way.
data class HttpRequestInfo(val visitedUrl : String, val headers : ValuesMap) {
val referer : String? get() = headers["Referer"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.github.sanity.kweb.demos.errorDetection.jsError

import com.github.sanity.kweb.Kweb

/**
* Created by ian on 3/28/17.
*/

fun main(args: Array<String>) {
Kweb(port = 4124, debug = true) {
doc.body.execute("deliberate error;")
}
}
32 changes: 29 additions & 3 deletions src/main/kotlin/com/github/sanity/kweb/dom/Document.kt
Original file line number Diff line number Diff line change
@@ -1,15 +1,41 @@
package com.github.sanity.kweb.dom

import com.github.sanity.kweb.RootReceiver
import com.github.sanity.kweb.Kweb
import com.github.sanity.kweb.WebBrowser
import com.github.sanity.kweb.dom.cookies.CookieReceiver
import com.github.sanity.kweb.dom.element.Element
import com.github.sanity.kweb.dom.element.creation.tags.h1
import com.github.sanity.kweb.dom.element.modification.text
import com.github.sanity.kweb.dom.element.new

class Document(private val receiver: RootReceiver) {
/**
* Represents the in-browser Document Object Model, corresponding to the JavaScript
* [document](https://www.w3schools.com/jsref/dom_obj_document.asp) object.
*
* Passed in as `doc` to the `buildPage` lambda of the [Kweb] constructor.
*
* @sample document_sample
*/
class Document(val receiver: WebBrowser) {
fun getElementById(id: String) = Element(receiver, "document.getElementById(\"$id\")")

val cookie = CookieReceiver(receiver)

val body = BodyElement(receiver)
}

class BodyElement(rootReceiver: RootReceiver, id: String? = null) : Element(rootReceiver, "document.body", "body", id)
/**
* Represents the `body` element of the in-browser Document Object Model, corresponding to
* the JavaScript [body](https://www.w3schools.com/tags/tag_body.asp) tag.
*
* @sample document_sample
*/
class BodyElement(webBrowser: WebBrowser, id: String? = null) : Element(webBrowser, "document.body", "body", id)

private fun document_sample() {
Kweb(port = 1234) {
doc.body.new {
h1().text("Hello World!")
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.github.sanity.kweb.dom.cookies

import com.github.salomonbrys.kotson.fromJson
import com.github.sanity.kweb.RootReceiver
import com.github.sanity.kweb.WebBrowser
import com.github.sanity.kweb.dom.element.KWebDSL
import com.github.sanity.kweb.gson
import com.github.sanity.kweb.toJson
Expand All @@ -10,7 +10,7 @@ import java.util.*
import java.util.concurrent.CompletableFuture

@KWebDSL
class CookieReceiver(val receiver: RootReceiver) {
class CookieReceiver(val receiver: WebBrowser) {
fun set(name: String, value: Any, expires: Duration? = null, path: String? = null, domain: String? = null) {
setString(name, value.toJson(), expires, path, domain)
}
Expand Down
47 changes: 40 additions & 7 deletions src/main/kotlin/com/github/sanity/kweb/dom/element/Element.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package com.github.sanity.kweb.dom.element

import com.github.sanity.kweb.RootReceiver
import com.github.sanity.kweb.Kweb
import com.github.sanity.kweb.WebBrowser
import com.github.sanity.kweb.dom.element.creation.ElementCreator
import com.github.sanity.kweb.dom.element.creation.tags.h1
import com.github.sanity.kweb.dom.element.modification.setAttribute
import com.github.sanity.kweb.dom.element.modification.text
import com.github.sanity.kweb.plugins.KWebPlugin
import java.util.concurrent.CompletableFuture
import kotlin.reflect.KClass
Expand All @@ -12,8 +15,8 @@ annotation class KWebDSL


@KWebDSL
open class Element (open val rootReceiver: RootReceiver, open var jsExpression: String, val tag : String? = null, val id: String? = null) {
constructor(element: Element) : this(element.rootReceiver, jsExpression = element.jsExpression, tag = element.tag, id = element.id)
open class Element (open val webBrowser: WebBrowser, open var jsExpression: String, val tag : String? = null, val id: String? = null) {
constructor(element: Element) : this(element.webBrowser, jsExpression = element.jsExpression, tag = element.tag, id = element.id)
/*********
********* Low level methods
*********/
Expand All @@ -24,7 +27,7 @@ open class Element (open val rootReceiver: RootReceiver, open var jsExpression:
* are based.
*/
fun execute(js: String) {
rootReceiver.execute(js)
webBrowser.execute(js)
}

/**
Expand All @@ -33,7 +36,7 @@ open class Element (open val rootReceiver: RootReceiver, open var jsExpression:
* are based.
*/
fun <O> evaluate(js: String, outputMapper: (String) -> O): CompletableFuture<O>? {
return rootReceiver.evaluate(js).thenApply(outputMapper)
return webBrowser.evaluate(js).thenApply(outputMapper)
}

/*********
Expand All @@ -44,12 +47,27 @@ open class Element (open val rootReceiver: RootReceiver, open var jsExpression:
********* will typically be just the tag of the element like "div" or "input".
*********/

fun require(vararg plugins: KClass<out KWebPlugin>) = rootReceiver.require(*plugins)
fun require(vararg plugins: KClass<out KWebPlugin>) = webBrowser.require(*plugins)

fun <P : KWebPlugin> plugin(plugin: KClass<P>) = rootReceiver.plugin(plugin)
fun <P : KWebPlugin> plugin(plugin: KClass<P>) = webBrowser.plugin(plugin)
}

/**
* Returns an [ElementCreator] which can be used to create new elements and add them
* as children of the receiver element.
*
* @receiver This will be the parent element of any elements created with the returned
* [ElementCreator]
*
* @sample new_sample_1
*/
fun <T : Element> T.new(position : Int? = null) = ElementCreator(this, position)

/**
* A convenience wrapper around [new] which allows a nested DSL-style syntax
*
* @sample new_sample_2
*/
fun <T : Element> T.new(receiver: ElementCreator<T>.() -> Unit) : T {
receiver(new())
return this
Expand All @@ -58,3 +76,18 @@ fun <T : Element> T.new(receiver: ElementCreator<T>.() -> Unit) : T {
// Element Attribute modifiers

fun Element.spellcheck(spellcheck : Boolean = true) = setAttribute("spellcheck", spellcheck)

private fun new_sample_1() {
Kweb(port = 1234) {
doc.body.new().h1().text("Hello World!")
}
}

private fun new_sample_2() {
Kweb(port = 1234) {
doc.body.new {
h1().text("Hello World!")
}
}
}

Loading

0 comments on commit 51ddf4b

Please sign in to comment.