Skip to content

Instantly share code, notes, and snippets.

@b-studios
Last active December 31, 2015 23:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save b-studios/8057677 to your computer and use it in GitHub Desktop.
Save b-studios/8057677 to your computer and use it in GitHub Desktop.
Example code to illustrate a different approach for Vaadin's scala wrapper classes using the "pimp my library" pattern.
package scalavaadin
import com.vaadin.{ ui => orig }
import scala.language.implicitConversions
import scala.collection.mutable
import scala.collection.JavaConversions
package object ui {
// Adding new Methods
// ------------------
// Let's start with how to add methods to existing Vaadin classes / interfaces.
// The good thing is, that this pattern ("Pimp My Library") also works for interfaces
// and thus removes the need to reconstruct the same inheritance structure on the
// scala-interface site.
//
// It's use is also fully transparent to the user, since all methods always return
// the original base classes instead of the wrapper.
implicit class ComponentWrapper(that: orig.Component) {
def enabled: Boolean = that.isEnabled
def enabled_=(enabled: Boolean) { that.setEnabled(enabled) }
def visible: Boolean = that.isVisible
def visible_=(visible: Boolean) { that.setVisible(visible) }
def caption: Option[String] = Option(that.getCaption)
def caption_=(caption: Option[String]) { that.setCaption(caption.orNull) }
def caption_=(caption: String) { that.setCaption(caption) }
// ...
}
// Creating Instances
// ------------------
// Since we are not fully wrapping classes anymore there is no way to
// add new (more convenient) constructors. Since assembling the GUI
// most of the time consists of plugging (mutable) components together
// to form the GUI graph, adding a curried "initialization function"
// partially solves this problem:
object Button {
private type ClickEvent = orig.Button.ClickEvent
// constructor with default values set to null, since they can be omitted
def apply(caption: String = null, listener: ClickEvent => Unit = null)
(implicit init: orig.Button => Unit): orig.Button = const(new orig.Button) { b =>
Option(caption).map(b.setCaption(_))
Option(listener).map(b.addClickListener(_))
}
}
// adding an noop implicit to be able to avoid the callback all together
implicit def noop[T](x: T): Unit = ()
// companion class to the Button object above, necessary to allow adding lambdas as
// listener
implicit class ButtonWrapper(that: orig.Button) {
def addClickListener(listener: orig.Button.ClickEvent => Unit) {
that.addClickListener(new orig.Button.ClickListener {
def buttonClick(evt: orig.Button.ClickEvent) = listener(evt)
})
}
def +=(listener: orig.Button.ClickEvent => Unit) { that.addClickListener(listener) }
}
// Collection Like Classes
// -----------------------
// Container classes (like layouts) can be used as scala-collections by
// inheriting from the appropriate scala collection class:
private type AbsolutePosition = orig.AbsoluteLayout#ComponentPosition
implicit class AbsoluteLayoutWrapper(that: orig.AbsoluteLayout)
extends mutable.Map[orig.Component, AbsolutePosition] {
def -=(key: orig.Component): this.type = { that.removeComponent(key); this }
def +=(kv: (orig.Component, AbsolutePosition)): this.type = {
that.addComponent(kv._1, kv._2.getCSSString);
this
}
def +=(key: orig.Component): this.type = {
that.addComponent(key);
this
}
def get(key: orig.Component): Option[AbsolutePosition] = Option(that.getPosition(key))
def iterator: Iterator[(orig.Component, AbsolutePosition)] =
JavaConversions.asScalaIterator(that.iterator).map { k =>
(k, that.getPosition(k))
}
override def size = that.getComponentCount
override def empty: AbsoluteLayout = AbsoluteLayout(new orig.AbsoluteLayout)
}
// just a helper combinator
private def const[T](x: T)(f: T => Unit): T = { f(x); x }
}
import com.vaadin.ui._
import scalavaadin.ui._
import Button.ClickEvent
object usageExample {
// example for not providing an init-callback
val b1 = Button()
// example using the callback
val b2 = Button("boo") { b =>
b += { evt: ClickEvent =>
println("clicked")
}
println("name is" + b.caption)
}
b2.caption = "bar"
println(b1)
println(b2)
val layout = AbsoluteLayout { l =>
l += Button()
l += b1
l += b2
l(b1) setCSSString "top: 10px; left: 20%; z-index: 16;"
}
// layout now can be used like a collection
for ((copm, pos) <- layout) {
println(s"$comp at $pos")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment