kweb-core icon indicating copy to clipboard operation
kweb-core copied to clipboard

Create a DSL for CSS

Open sanity opened this issue 8 years ago • 10 comments

Just as we've created a DSL for HTML (or started to) we're going to need a DSL for CSS. This should be usable both for adding to the document

, but also for individual 'style' attributes on elements (considering that the latter can't contain newlines).

Would also be nice if the JQuery plugin could reuse elements of this DSL for it's implementation of .css().

sanity avatar Jan 10 '17 07:01 sanity

We should investigate whether we can use this: https://github.com/edvin/tornadofx/wiki/Type-Safe-CSS.

If we do we will probably have to copy the code over from tornadofx because we don't want to pull in the entire tornadofx dependency just for this component.

At a minimum though it may serve as inspiration.

sanity avatar Jan 11 '17 15:01 sanity

@sanity are you aware of this project? https://github.com/orangy/kotlinx.css

jmfayard avatar Feb 06 '17 08:02 jmfayard

@jmfayard I was not, although I am aware of one or two others. This is potentially very interesting, although the documentation is a bit sparse, it looks experimental (just like kweb!). I'll poke around with it.

sanity avatar Feb 06 '17 18:02 sanity

Kotlinx is a great HTML and CSS DSL. I used it on two projects and would always use it again!

stangls avatar Mar 11 '17 19:03 stangls

Some other candidates for this:

  • https://github.com/olegcherr/Aza-Kotlin-CSS
  • https://github.com/JetBrains/kotlin-wrappers/tree/master/kotlin-css

Need to think about how to ensure this plays nice with KVar mechanism.

sanity avatar Jul 21 '18 17:07 sanity

If it helps, I had some success binding kotlinx html/css to kweb with this little bridge code. It seemed to work, except that the elements jumped around a bit when the button was clicked.

fun ElementCreator<*>.dom(cb: FlowContent.()->Unit){
    cb(KwebKotlinxTagBridge(KwebKotlinxConsumerBridge(this)))
}

class KwebKotlinxTagBridge(private val cons : TagConsumer<Element>) : FlowContent {
    override val attributes = mutableMapOf<String,String>()
    override val attributesEntries = attributes.entries
    override val consumer = cons
    override val emptyTag: Boolean = false
    override val inlineTag: Boolean = false
    override val namespace: String? = null
    override val tagName: String = ""
}

class KwebKotlinxConsumerBridge(private val elementCreator : ElementCreator<*>) : TagConsumer<Element> {
    private val path = arrayListOf<Element>()
    private var lastLeft : Element? = null

    override fun onTagStart(tag: Tag) {
        val creator = if (path.isNotEmpty()) {
            path.last().new()
        } else {
            elementCreator
        }

        val element = creator.element(tag.tagName, attributes = tag.attributes)

        path.add(element)
    }

    override fun onTagAttributeChange(tag: Tag, attribute: String, value: String?) {
        if (path.isEmpty()) {
            throw IllegalStateException("No current tag")
        }

        path.last().let { node ->
            if (value == null) {
                node.removeAttribute(attribute)
            } else {
                node.setAttribute(attribute, value)
            }
        }
    }

    override fun onTagEvent(tag: Tag, event: String, value: (Event) -> Unit) {
        throw UnsupportedOperationException("You can't assign lambda event handler on JVM")
    }

    override fun onTagEnd(tag: Tag) {
        if (path.isEmpty() || path.last().tag?.toLowerCase() != tag.tagName.toLowerCase()) {
            throw IllegalStateException("We haven't entered tag ${tag.tagName} but trying to leave")
        }

        val element = path.removeAt(path.lastIndex)
        lastLeft = element
    }

    override fun onTagContent(content: CharSequence) {
        if (path.isEmpty()) {
            throw IllegalStateException("No current DOM node")
        }

        path.last().addText(content.toString())
    }

    override fun onTagComment(content: CharSequence) {
        throw IllegalStateException("Comments not supported")
    }

    override fun onTagContentEntity(entity: Entities) {
        if (path.isEmpty()) {
            throw IllegalStateException("No current DOM node")
        }

        path.last().addText(entity.text)
    }

    override fun finalize() = lastLeft ?: throw IllegalStateException("No tags were emitted")

    override fun onTagContentUnsafe(block: Unsafe.() -> Unit) {
        UnsafeImpl.block()
    }

    private val UnsafeImpl = object : Unsafe {
        override operator fun String.unaryPlus() {
            path.last().innerHTML(this)
        }
    }
}

fun example(){
        path("/example"){ params ->
            val user = params.getValue("id").value

            val counter = KVar(0)
            dom {
                div {
                    +"Hello, ${user}"
                }
                render(counter){
                    dom {
                        p {
                            style = CSSBuilder().apply { color = Color.red }.toString()
                            +("The value is: $it")
                        }
                    }
                }
            }

            button().text("Clicky").apply {
                on.click {
                    counter.value += 1
                }
            }
        }
    }

ScottPeterJohnson avatar Jul 02 '19 12:07 ScottPeterJohnson

Very interesting @ScottPeterJohnson, thank you. I'll poke around to understand your code.

sanity avatar Jul 02 '19 15:07 sanity

Hmm, just looking at the example, it seems to be using alternate HTML builders, dom, p, div. The issue here is that these builders aren't "KVar aware", which means that they won't automatically re-render, for example if the value of the id KVar were to change. This could be the reason the elements are jumping around.

Does that make sense?

sanity avatar Jul 02 '19 15:07 sanity

Yea, those are the kotlinx builders. The outer div { +"Hello, ${user}" } doesn't re-render which makes sense, but the inner counter rerenders fine since it's in the render block. It just changes its relative position with the button after the first update. (I wrote this because I have a lot of Kotlinx HTML/CSS already. It's a very thorough and excellent DSL; I've never found anything missing from it)

ScottPeterJohnson avatar Jul 03 '19 00:07 ScottPeterJohnson

Understood.

I think the HTML part of the library would be duplicative of Kweb's existing HTML DSL, which is designed to work with Kweb's observer paradigm.

However the CSS side could be interesting if there was a clean way to integrate it.

sanity avatar Jul 03 '19 01:07 sanity

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.

github-actions[bot] avatar Nov 09 '22 03:11 github-actions[bot]