Description
Introduction
In our company we have started using Binding.scala in several projects. The library is wonderful, especially when you write code like web designer does. But when we started decomposing our code, we faced the limitations of the component model, especially when processing child elements.
This issue is a meta-task to discuss this limitations
There are two component models in Binding.scala
Let's extract internal div
in this example as a component:
<div><div class="dialog"/></div>
Please, see full source code here
@dom functions (official)
Official way is using a regular function with @dom
annotation:
@dom
def dialog: Binding[Div] = <div class="dialog"/>
Which can be used in other @dom
functions:
<div>{dialog.bind}</div>
User defined tags (unofficial)
This is undocumented, but it is possible to write composable user defined tags. The same component as above is:
implicit final class UserTags(x: TagsAndTags2.type) {
object dialog {
def render: Div = {
val div = x.div.render
div.className = "dialog"
div
}
}
}
Which can be used in @dom
functions:
<div><dialog/></div>
Features of the component model
Using attributes of an underline html element
It's easy to use attributes of an underline html element.
-
We should slightly change the implementation of our @dom component
@dom def dialog(id: String): Binding[Div] = <div id={id} class="dialog"/>
and using:
<div>{dialog("message").bind}</div>
-
Nothing changed in the implementation of user defined tag.
Use it like this:<div><dialog id="message"/></div>
Please, see full source code here
Creating a component attribute
In this chapter we'll create a caption
attribute for our dialog.
And second goal is check ability of using bindable attributes.
-
It's just an additional parameter of @dom component function:
@dom def dialog(id: String, caption: Binding[String]): Binding[Div] = <div id={id} class="dialog" data:dialog-caption={caption.bind}/>
and using:
val caption = Var("Caption") <div>{dialog("message", caption).bind}</div>
-
It's quite hard to implement the same for user defined tag. But easy to use.
<div><dialog id="message" caption={caption.bind}/></div>
Please, see full source code here
Containment
Some components don’t know their children ahead of time. This is especially common for components like Sidebar or Dialog that represent generic “boxes”.
(source)
Children can be absent, can contain single node, list of nodes, option node, text node etc. and their combinations.
-
A common type for all of this is
BindingSeq[Node]
, you can easily add such parameter to @dom component function:@dom def dialog(children: BindingSeq[Node]): Binding[Div] = <div class="dialog">{children}</div>
but it's inconvenient to use:
val warning = Some(<div>warning</div>) <div>{dialog(Constants(<div>Some text</div> +: warning.bind.toSeq:_*)).bind}</div>
converting types to
BindingSeq[Node]
makes code less readable and kills a partial update❗ -
Nothing changed in the implementation of user defined tag. And any kind of children can be used like a charm 😄:
val warning = Some(<div>warning</div>) <div><dialog><div>Some text</div>{warning.bind}</dialog></div>
Please, see full source code here
Multiple “holes” in a component
While this is less common, sometimes you might need multiple “holes” in a component
(the same source)
In both component models you can add BindingSeq[Node]
parameter and have hard way on using it 😞
Painless HTML creation
-
As you can see in previous examples, HTML creation is easy in @dom component (because of
@dom
magic):@dom def dialog(id: String, caption: Binding[String]): Binding[Div] = <div id={id} class="dialog" data:dialog-caption={caption.bind}/>
-
Unfortunately, it isn't possible to use
@dom
for implementing user defined tag. It would be great if we could write something like this:object Dialog { @dom def apply(id: String, caption: Binding[String]): Binding[Div] = <div id={id} class="dialog" data:dialog-caption={caption.bind}/> } implicit final class UserTags(x: TagsAndTags2.type) { val dialog = Dialog }
when using stays the same as before:
<div><dialog id="message" caption={caption.bind}/></div>
Please, see full source code here
Ops, there is some ugly workaround
Conclusion
Current state
@dom | tag | |
---|---|---|
html element attribute | ✔️ | ✔️ |
component attribute | ✔️ | ✔️ |
bindable attribute | ✔️ | ✔️ |
containment | ⛔ | ✔️ |
multiple “holes” | ⛔ | ⛔ |
painless HTML creation | ✔️ | ⛔ |
Possible improvements
- Painless conversion of any types of children to
BindingSeq[Node]
(or to any other more appropriate type) - I think, user defined tags is powerful and elegant concept and it should get an official support 😉
- ... etc.