Firstly, I'd like to thank all the people who've said nice things about the first three parts - here, on twitter and on various news-groups. Of special note is Mark Harrah, the creator of SBT. Not only did he drop me a nice note, he made a change to SBT so that the issue whereby you couldn't usefully run jetty in batch mode is solved.
Now that's service!I'd also like to apologise to the people who've been reading my blog on the web. I realise that I need to change to a variable width column layout so that you can actually see all of the code. I'll get to it soon... honest.
Last time I promised I'd talk about continuous compilation and deployment. This is pretty complex, so try to keep up.
Normally to compile all the code and run jetty you'd type:
~/code/spike : sbt
> jetty-restart
Or some such. To get continuous compilation and deployment going you need to do this:
~/code/spike : sbt
> ~ jetty-restart
See the tilde on the second line? That's it. That sets up file watchers to watch for changed files/resources and executes the specified action when it detects them
Want to run your tests every time the code changes?
~/code/spike : sbt
> ~ test
I'm sorry if you'd like an XML configuration file or something similar. We're all out of complexity this evening, try again tomorrow.
Back to the Scala & Vaadin.
The next example, from page 40 of the Book of Vaadin is slightly more substantial. I'm hoping that Scala will let us make even bigger improvements to the code because we've more to work with.
The Java code:
public class WindowOpener extends CustomComponent implements Window.CloseListener {
Window mainwindow;
Window mywindow;
Button openbutton;
Button closebutton;
Label explanation;
public WindowOpener(String label, Window main) {
mainwindow = main;
final VerticalLayout layout = new VerticalLayout();
openbutton = new Button("Open Window", this, "openButtonClick");
explanation = new Label("Explanation");
layout.addComponent(openbutton);
layout.addComponent(explanation);
setCompositionRoot(layout);
}
public void openButtonClick(Button.ClickEvent event) {
mywindow = new Window("My Dialog");
mywindow.setPositionX(200);
mywindow.setPositionY(100);
mainwindow.addWindow(mywindow);
mywindow.addListener(this);
mywindow.addComponent(new Label("A text label in the window."));
closebutton = new Button("Close", this, "closeButtonClick");
mywindow.addComponent(closebutton);
openbutton.setEnabled(false);
explanation.setValue("Window opened");
}
public void closeButtonClick(Button.ClickEvent event) {
mainwindow.removeWindow(mywindow);
openbutton.setEnabled(true);
explanation.setValue("Closed with button");
}
public void windowClose(CloseEvent e) {
openbutton.setEnabled(true);
explanation.setValue("Closed with window controls");
}
}
public void init() {
Window main = new Window("The Main Window");
setMainWindow(main);
main.addComponent(new WindowOpener("Window Opener", main));
}
The free floating init() method is supposed to be placed into the Application class.
I've removed the comments and done a little tidying up of the structure: people have suggested that by not tidying the Java code in the earlier examples I was doing Java a disservice. I also removed the comments because I believe that comments are, for the most part, an abomination, a crutch that has removed the need for programmers to support their own weight and write clean code. As with most things, I could be wrong. I doubt it, but it is possible. I'm not egotistical enough to believe I'm never wrong. Just very, very rarely. Honest.
Transliteration:
package spike
import com.vaadin.Application
import com.vaadin.ui._
class WindowOpener(abel: String, main: Window) extends CustomComponent with Window.CloseListener {
var mainwindow: Window = null
var mywindow: Window = null
var openbutton: Button = null
var closebutton: Button = null
var explanation: Label = null
mainwindow = main
val layout = new VerticalLayout()
openbutton = new Button("Open Window", this, "openButtonClick")
explanation = new Label("Explanation")
layout.addComponent(openbutton)
layout.addComponent(explanation)
setCompositionRoot(layout)
def openButtonClick(event: Button#ClickEvent): Unit = {
mywindow = new Window("My Dialog")
mywindow.setPositionX(200)
mywindow.setPositionY(100)
mainwindow.addWindow(mywindow)
mywindow.addListener(this)
mywindow.addComponent(new Label("A text label in the window."))
closebutton = new Button("Close", this, "closeButtonClick")
mywindow.addComponent(closebutton)
openbutton.setEnabled(false)
explanation.setValue("Window opened")
}
def closeButtonClick(event: Button#ClickEvent): Unit = {
mainwindow.removeWindow(mywindow)
openbutton.setEnabled(true)
explanation.setValue("Closed with button")
}
def windowClose(e: Window#CloseEvent): Unit = {
openbutton.setEnabled(true)
explanation.setValue("Closed with window controls")
}
}
class SpikeApplication2 extends Application {
override def init: Unit = {
val main = new Window("The Main Window")
setMainWindow(main)
main.addComponent(new WindowOpener("Window Opener", main))
}
}
I've dropped this into a new file on my system src/main/scala/spike/Application2.scala.
To get it to run, you need to change the web.xml. Change the line:
<param-value>spike.SpikeApplication</param-value>
to:
<param-value>spike.SpikeApplicatio2n</param-value>
Yup, there's a 2 on the end of the class name now. Fire up the app and
have a look - we're going to get pimping!
So, what do I want to change first? Well, if you look where the Buttons are instantiated you'll see something that made my skin crawl. In an earlier episode I talked about the ugly anonymous inner class usage, but mentioned that it was probably the best choice. Best of a bad lot in Java as it were. This is one of the other choices. The button is being instantiated with a target object and a method name so that the button can call the method when clicked. I know why people do this, I've done it myself and in Ruby or Smalltalk it'd be idiomatic, people would be prepared for it, and it wouldn't get messed up. In Java having method names as strings is always a bad idea. Amusingly, as if my sub-consience was trying to prove me right, I misspelled one of the method names not once, but twice. And when I ran the app.. it didn't work.
Using method names as Strings and calling them by reflection isn't a good idea. There are so many things that can go wrong - signature changes, misspellings, changes in visibility level, return type changes, exceptions etc. In Java, the belief that static typing protects you from these sort of problems means that developers just aren't prepared for it. Thankfully, the SButton class we created earlier solves this problem in a nice type-safe, statically defined way. Goodbye reflection invocation.
One of the ugly parts about using reflection is that the methods you're calling back to have to be public so that the reflector can see them. This leads to classes with odd "don't call me I'm not what you think" methods.
This class also implements the WindowListener interface because it needs to respond somehow, and some of the class' state is modified as a result of the callback. Implementing this interface here was the expedient thing to do, but it does mean that the class has a broader interface, implements an interface so that it can comply with the demands of its own internal workings and, if it wanted to listen to multiple child windows would need to jump through hoops to figure out which child window it was that was being close. I'm gonna get my closure tools out again.
package spike
import com.vaadin.Application
import com.vaadin.ui._
class SWindowCloseListener(action: Window#CloseEvent => Unit) extends Window.CloseListener {
def windowClose(event: Window#CloseEvent) = {
action(event)
}
}
class WindowOpener(private val mainWindow: Window) extends CustomComponent {
private val openbutton = new SButton("Open Window", _ => createSubWindow)
private val explanation = new Label("Explanation")
private val layout = new VerticalLayout()
layout.addComponent(openbutton)
layout.addComponent(explanation)
setCompositionRoot(layout)
private var subWindow: Window = null
private def createSubWindow: Unit = {
subWindow = new Window("My Dialog")
subWindow.setPositionX(200)
subWindow.setPositionY(100)
subWindow.addComponent(new Label("A text label in the window."))
subWindow.addComponent(new SButton("Close", _ => closeSubWindow))
subWindow.addListener(new SWindowCloseListener(_ => onSubWindowClose))
mainWindow.addWindow(subWindow)
openbutton.setEnabled(false)
explanation.setValue("Window opened")
}
private def closeSubWindow = {
mainWindow.removeWindow(subWindow)
openbutton.setEnabled(true)
explanation.setValue("Closed with button")
}
private def onSubWindowClose = {
openbutton.setEnabled(true)
explanation.setValue("Closed with window controls")
}
}
class SpikeApplication2 extends Application {
override def init = {
val main = new Window("The Main Window")
main.addComponent(new WindowOpener(main))
setMainWindow(main)
}
}
In the tidy up, we've gone from five instance variables down to four ( three of them immutable values), lost a constructor parameter that didn't do anything, created a nice little reusable listener class, removed risky reflection invocation and removed ALL of the new public methods and properties. The interface of our class is now identical to the interface of our super class.