From facf7991ed52cb21852dd6175a3817e94598c2dc Mon Sep 17 00:00:00 2001 From: Robert Jaros Date: Wed, 31 Jan 2018 00:10:58 +0100 Subject: TodoMVC example --- .../todomvc/src/main/kotlin/com/example/Main.kt | 40 ++++ .../todomvc/src/main/kotlin/com/example/Todomvc.kt | 242 +++++++++++++++++++++ 2 files changed, 282 insertions(+) create mode 100644 examples/todomvc/src/main/kotlin/com/example/Main.kt create mode 100644 examples/todomvc/src/main/kotlin/com/example/Todomvc.kt (limited to 'examples/todomvc/src/main/kotlin') diff --git a/examples/todomvc/src/main/kotlin/com/example/Main.kt b/examples/todomvc/src/main/kotlin/com/example/Main.kt new file mode 100644 index 00000000..d8894a4c --- /dev/null +++ b/examples/todomvc/src/main/kotlin/com/example/Main.kt @@ -0,0 +1,40 @@ +package com.example + +import pl.treksoft.kvision.ApplicationBase +import pl.treksoft.kvision.core.KVManager +import pl.treksoft.kvision.module +import kotlin.browser.document + +fun main(args: Array) { + var application: ApplicationBase? = null + + val state: dynamic = module.hot?.let { hot -> + hot.accept() + + hot.dispose { data -> + data.appState = application?.dispose() + KVManager.shutdown() + application = null + } + + hot.data + } + + if (document.body != null) { + KVManager.start() + application = start(state) + } else { + KVManager.init() + application = null + document.addEventListener("DOMContentLoaded", { application = start(state) }) + } +} + +fun start(state: dynamic): ApplicationBase? { + if (document.getElementById("todomvc") == null) return null + val application = Todomvc() + @Suppress("UnsafeCastFromDynamic") + application.start(state?.appState ?: emptyMap()) + return application +} + diff --git a/examples/todomvc/src/main/kotlin/com/example/Todomvc.kt b/examples/todomvc/src/main/kotlin/com/example/Todomvc.kt new file mode 100644 index 00000000..76d90716 --- /dev/null +++ b/examples/todomvc/src/main/kotlin/com/example/Todomvc.kt @@ -0,0 +1,242 @@ +package com.example + +import com.lightningkite.kotlin.observable.list.observableListOf +import org.w3c.dom.get +import org.w3c.dom.set +import pl.treksoft.kvision.ApplicationBase +import pl.treksoft.kvision.core.Root +import pl.treksoft.kvision.data.DataComponent +import pl.treksoft.kvision.data.DataContainer +import pl.treksoft.kvision.form.FieldLabel +import pl.treksoft.kvision.form.check.CHECKINPUTTYPE +import pl.treksoft.kvision.form.check.CheckInput +import pl.treksoft.kvision.form.text.TextInput +import pl.treksoft.kvision.html.Button +import pl.treksoft.kvision.html.LIST +import pl.treksoft.kvision.html.Link +import pl.treksoft.kvision.html.ListTag +import pl.treksoft.kvision.html.TAG +import pl.treksoft.kvision.html.Tag +import pl.treksoft.kvision.routing.routing +import kotlin.browser.localStorage + +const val ENTER_KEY = 13 +const val ESCAPE_KEY = 27 + +class Todo(completed: Boolean, title: String, hidden: Boolean) : DataComponent() { + var completed: Boolean by obs(completed) + var title: String by obs(title) + var hidden: Boolean by obs(hidden) +} + +enum class LISTMODE { + ALL, + ACTIVE, + COMPLETED +} + +class Todomvc : ApplicationBase() { + + private val model = observableListOf() + + private val checkAllInput = CheckInput(classes = setOf("toggle-all")).apply { + id = "toggle-all" + onClick { + val value = this.value + model.forEach { it.completed = value } + } + } + private val allLink = Link("All", "#!/", classes = setOf("selected")) + private val activeLink = Link("Active", "#!/active") + private val completedLink = Link("Completed", "#!/completed") + private val clearCompletedButton = Button("Clear completed", classes = setOf("clear-completed")).onClick { + model.filter { it.completed }.forEach { model.remove(it) } + } + + private val countTag = Tag(TAG.STRONG, "0") + private val itemsLeftTag = Tag(TAG.SPAN, " items left", classes = setOf("todo-count")).apply { + add(countTag) + } + private var mode: LISTMODE = LISTMODE.ALL + + private val header = genHeader() + private val main = genMain() + private val footer = genFooter() + + override fun start(state: Map) { + val root = Root("todomvc") + val section = Tag(TAG.SECTION, classes = setOf("todoapp")) + section.add(this.header) + section.add(this.main) + section.add(this.footer) + root.add(section) + loadModel() + checkModel() + routing.on("/", { _ -> all() }) + .on("/active", { _ -> active() }) + .on("/completed", { _ -> completed() }) + .resolve() + } + + private fun loadModel() { + localStorage.get("todos-kvision")?.let { + JSON.parse>(it).map { Todo(it.completed, it.title, false) }.forEach { + model.add(it) + } + } + } + + private fun saveModel() { + val jsonString = model.map { + val array = listOf("title" to it.title, "completed" to it.completed).toTypedArray() + JSON.stringify(kotlin.js.json(*array)) + }.toString() + localStorage.set("todos-kvision", jsonString) + } + + private fun checkModel() { + val countActive = model.filter { !it.completed }.size + val countCompleted = model.filter { it.completed }.size + this.main.visible = model.isNotEmpty() + this.footer.visible = model.isNotEmpty() + this.countTag.text = countActive.toString() + this.itemsLeftTag.text = when (countActive) { + 1 -> " item left" + else -> " items left" + } + this.checkAllInput.value = (countActive == 0) + this.clearCompletedButton.visible = countCompleted > 0 + saveModel() + } + + private fun all() { + this.mode = LISTMODE.ALL + this.allLink.addCssClass("selected") + this.activeLink.removeCssClass("selected") + this.completedLink.removeCssClass("selected") + this.model.forEach { it.hidden = false } + } + + private fun active() { + this.mode = LISTMODE.ACTIVE + this.allLink.removeCssClass("selected") + this.activeLink.addCssClass("selected") + this.completedLink.removeCssClass("selected") + this.model.forEach { it.hidden = it.completed } + } + + private fun completed() { + this.mode = LISTMODE.COMPLETED + this.allLink.removeCssClass("selected") + this.activeLink.removeCssClass("selected") + this.completedLink.addCssClass("selected") + this.model.forEach { it.hidden = !it.completed } + } + + private fun genHeader(): Tag { + return Tag(TAG.HEADER, classes = setOf("header")).apply { + add(Tag(TAG.H1, "todos")) + add(TextInput(classes = setOf("new-todo")).apply { + placeholder = "What needs to be done?" + autofocus = true + setEventListener { + keydown = { e -> + if (e.keyCode == ENTER_KEY) { + addTodo(self.value) + self.value = null + } + } + } + }) + } + } + + private fun addTodo(value: String?) { + val v = value?.trim() ?: "" + if (v.isNotEmpty()) { + model.add(Todo(false, v, mode == LISTMODE.COMPLETED)) + } + } + + private fun editTodo(index: Int, value: String?) { + val v = value?.trim() ?: "" + if (v.isNotEmpty()) { + model[index].title = v + } else { + model.removeAt(index) + } + } + + private fun genMain(): Tag { + return Tag(TAG.SECTION, classes = setOf("main")).apply { + add(checkAllInput) + add(FieldLabel("toggle-all", "Mark all as complete")) + add(DataContainer(model, { index -> + val li = Tag(TAG.LI) + li.apply { + if (model[index].completed) addCssClass("completed") + if (model[index].hidden) addCssClass("hidden") + val edit = TextInput(classes = setOf("edit")) + val view = Tag(TAG.DIV, classes = setOf("view")).apply { + add(CheckInput( + CHECKINPUTTYPE.CHECKBOX, model[index].completed, classes = setOf("toggle") + ).onClick { + model[index].completed = this.value + model[index].hidden = + mode == LISTMODE.ACTIVE && this.value || mode == LISTMODE.COMPLETED && !this.value + }) + add(Tag(TAG.LABEL, model[index].title).apply { + setEventListener { + dblclick = { + li.getElementJQuery()?.addClass("editing") + edit.value = model[index].title + edit.getElementJQuery()?.focus() + } + } + }) + add(Button("", classes = setOf("destroy")).onClick { + model.removeAt(index) + }) + } + edit.setEventListener { + blur = { + if (li.getElementJQuery()?.hasClass("editing") == true) { + li.getElementJQuery()?.removeClass("editing") + editTodo(index, self.value) + } + } + keydown = { e -> + if (e.keyCode == ENTER_KEY) { + li.getElementJQuery()?.removeClass("editing") + editTodo(index, self.value) + } + if (e.keyCode == ESCAPE_KEY) { + li.getElementJQuery()?.removeClass("editing") + } + } + } + add(view) + add(edit) + } + }, Tag(TAG.UL, classes = setOf("todo-list"))).onUpdate { + checkModel() + }) + } + } + + private fun genFooter(): Tag { + return Tag(TAG.FOOTER, classes = setOf("footer")).apply { + add(itemsLeftTag) + add(ListTag(LIST.UL, classes = setOf("filters")).apply { + add(allLink) + add(activeLink) + add(completedLink) + }) + add(clearCompletedButton) + } + } + + override fun dispose(): Map { + return mapOf() + } +} -- cgit