Scales is a Web Components based UI framework written in Scala.js. It aims to provide a comprehensive and modular web framework which is entirely written in Scala, so that it could be used and extended easily by those developers who prefer the language to plain Javascript.
Currently, the project is in the proof-of-concept stage, so it's not recommended to use it in a production environment. For implemented and planned features, please refer to the next section.
- Javascript wrapper for the Web Components APIs.
- Statically typed custom component definition.
- A MVC framework (planned).
- Two way data binding of component attributes (planned).
- A dependency injection container for components and services (planned).
- Core UI components like widgets and layouts (planned).
- Services like Ajax, i18n, or routing support implemented as components (planned).
Add the following lines to your sbt
build definition:
libraryDependencies += "com.greencatsoft" %%% "scales" % "0.1"
If you want to test the latest snapshot version instead, change the version to
0.2-SNAPSHOT
and add Sonatype snapshot repository to the resolver as follows:
resolvers += Resolver.sonatypeRepo("snapshots")
To define a component, write a class which extends Component[A]
trait and register it
using ComponentRegistry.register
method as below:
@name("my-component")
class MyComponent extends Component[Element]
ComponentRegistry.register[MyComponent]
And in a HTML file:
<my-component />
Alternatively, you can use document.createElement
method or a component constructor
which is returned by the register
method directly to create a new component instance.
ComponentRegistry.register[MyComponent]
val element = document.createElement("my-component")
document.body.appendChild(element)
// Or use a component constructor.
val constructor = ComponentRegistry.register[AnotherComponent]
val component = constructor()
document.body.appendChild(component.element)
When you define a component, it is required to specify a valid component name (which must
include a dash) with the @name
annotation. Aside from this requirement, the component
should be a concrete class with a public constructor without any arguments (it will be
changed when the constructor based DI is implemented in future).
In case you want to extend a specific element prototype, you can change the type
parameter accordingly, and optionally specify @tag
with the name of the native
tag you want to extend, like a below example:
@name("fancy-button")
@tag("button") // Optional. Use this, if you want use the native tag(with 'is' attribute).
class FancyButton extends Component[Button]
In some cases, you might want your component to extend a custom prototype, which is not provided by Scala.js DOM library.
In this case, you can use the @prototype
annotation to specify the name of the prototype
object you want to extend.
@name("fancy-button")
@prototype("CustomButton")
class FancyButton extends Component[Button]
All public properties and methods with @JSExport
annotation will be exported to the
resulting custom element, and will be able to accessed from the Javascript side.
@name("hello-component")
class HelloComponent extends Component[Div] {
@JSExport
var greet: Boolean = false
@JSExport
var lastPerson: String = "(nobody)"
@JSExport
def hello(name: String) {
if (greet) {
console.log(s"Hello, $name!")
lastPerson = name
}
}
}
And in Javascript:
var component = document.getElementById("#greeter")
component.greet = true;
component.hello("Jane");
// Should print out "Jane".
console.log(component.lastPerson);
In Scala, you can also reference other custom components directly, like an example below:
val elem = document.getElementById("#greeter")
val component = elem.component.asInstanceOf[HelloComponent]
component.hello("Jane")
In future, components and services will be treated as dependencies so they can be resolved
automatically in a declarative manner.
By default, Component
extends the LifecycleAware
trait, which provides various
lifecycle callback methods defined by the Web Components specification. Optionally, you
can also mixin AttributeChangeAware
, if you want to be notified when the value of
the component's attribute changes.
@name("hello-component")
class HelloComponent extends Component[Div]
with AttributeChangeAware[Div] {
// Called when the component is created.
override def onCreate(element: Div) {
super.onCreated(element) // Don't forget to call super!
}
// Called when the component is attached to a DOM tree.
override def onAttach(element: Div) {
super.onAttach(element)
}
// Called when the component is detached from a DOM tree.
override def onDetach(element: Div) {
super.onDetach(element)
}
// Called when the value of an attribute is changed.
override def onAttributeChange(
name: String, oldValue: Any, newValue: Any, element: Div) {
super.onAttributeChange(name, oldValue, newValue, element)
console.info(s"Attribute '$name' is changed: '$newValue'")
}
}
By default, Component
extends the NodeProvider
trait, which means
you can use its contentRoot
property to build its content as below:
@name("hello-component")
class HelloComponent extends Component[Div] {
override def onCreate(element: Div) {
super.onCreated(element)
contentRoot.innerHTML = "<h1>Hello World!</h1>"
}
}
Alternatively, you can make your component extend the ContentProvider
trait, like
a following example:
@name("hello-component")
class HelloComponent extends Component[Div]
with ContentProvider[Div] {
override def build(document: Document): Node = {
val elem = document.createElement("h1")
elem.innerHTML = "Hello World!"
elem
}
}
There are other variants of the ContentProvider
like TemplateContentProvider
or ScalaTagsDOMProvider
, which can be mixed in when you want to use an external
template,
or ScalaTags to build the contents, respectively:
@name("templated-component")
class TemplatedComponent extends Component[Div]
with TemplateContentProvider[Div] {
// Selector query for an external template tag.
override def templateSelector = "#template"
}
@name("scalatags-component")
class ScalaTagsComponent extends Component[Div]
with ScalaTagsDOMProvider[Div] {
override def template = h1("Hello World!")
}
- Scala 2.11
- Scala.js 0.6.4+
- A browser which supports Web Components API, or polyfills
- PhantomJS 2.0+ (for testing)
This project is provided under the terms of Apache License, Version 2.0.