Intro
JavaFX is a quite new platform for developing Rich Internet Applications (RIAs).
The claim is that it:
- reaches every Internet consumer - on desktops, mobile, and new devices too.
- delivers high performance - and the ability to engage creative professionals in the design process.
- leverages existing skills and enterprise infrastructure.
- is totally free, and open source.
- provides content owners with control and ownership of their own data.
At first glance it seems, that it's true, however, if you start digging into it, you may be disappointed. Firstly, there is not so much good examples or showcases on the Internet, that show good sides of it. For instance, there is a "video of a really cool demo", that was done at Java One conference ..... even though the demo crashed a couple of times. Another official showcase can be found here. Basically, both showcases show how cool things can be animated, how cool it is to get application out of browser and put it into desktop.
However, for me it doesn't seem to be really the point, why I would choose JavaFX to write RIA! Hence I continued searching.... Something more suitable, what I would like to see I found here. Yeah, that's already something...but! Just take a look at the code of this application (it was written by one of JavaFX developers, not some "startup programmer"!)
- http://jfxtras.googlecode.com/svn/jfxtras.samples/trunk/SpeedReaderFX/
- http://jfxtras.googlecode.com/svn/jfxtras.samples/trunk/SpeedReaderFX/src/org/jfxtras/speedreader/ui/SpeedReaderCriteriaNode.fx
- http://jfxtras.googlecode.com/svn/jfxtras.samples/trunk/SpeedReaderFX/src/org/jfxtras/speedreader/model/SpeedReaderModel.fx
I am not sure if someone would like to work with constructions like this: "}}]}}]}}]}]}}]}}".
JavaFX & MVC
Other thing I wanted to check is how hard it is to write some kind of MVC using JavaFX. Basically, it is possible, but it will not be "plain MVC" pattern. The designers of JavaFX did not separate code and mark-up, as it is done in Flex or Silverlite. However, it does support communication with plain Java. So, you can find a way to implement your controller in Java and connect it to your view, that is written in JavaFX. More information about writing UI controller can be found here.
What else.... Maybe testing?
If you will write your application so that UI is totally separated from business logic, then the latter can be tested with usual JUnit or some other framework. However, it is also possible to write some kind of tests for JavaFX code. They have JavaFX Unit Test base class for creating declarative, fluent, behavior-driven tests. It is also possible to use the usual way to write tests - read more here.
OK...that concerned business logic, but how to test UI?
Traditional Selenium-like tests wont help here, due to JavaFX application just embeds into your web-page, as usual Java applet. It's not integrated into DOM, so you cannot operate with its elements, to find something on page. One solution I found could be using FEST-Swing library (honestly, the "SWING" word scares me a lot:S). Basically, it is a "Java library, released under Apache 2.0 license, that provides a fluent interface for functional Swing GUI testing. This library provides an easy-to-use API that makes creation and maintenance of GUI tests easy.". In other words - the same Selenium-like framework, but for SWING applications. There you can operate with elements in the frame. Seems, that everything is OK here....but no! For some reason this library can't find JavaFX elements and components, even if they are JavaFX swing components. So basically it is not possible to test JavaFX with FEST library without additional effort. Some workarounds to this are described here:
- http://www.jroller.com/alexRuiz/entry/using_fest_swing_to_discover
- http://www.jroller.com/alexRuiz/entry/testing_javafx_uis_part_1
- http://www.jroller.com/alexRuiz/entry/testing_javafx_uis_part_2
- http://www.jroller.com/alexRuiz/entry/testing_javafx_uis_part_3
- here http://www.jroller.com/alexRuiz/entry/testing_javafx_uis_part_4
Performance
To my mind this is the most critical issue that JavaFX has at the moment. There are a lot of blogs on the Internet you can find about it. I just want to mark some thoughts I found that might be interesting:
- it does not support all modern browsers! For instance, simple JavaFX applets don't always work for IE and Opera. It absolutely does not work in Safary. All that after JavaFX creators said that there should not be compatibility issues with browsers, like you are facing with JavaScript.
- Check out this blog:
- JavaFX — 14 fps
- Firefox + Silverlight (JavaScript) — 56 fps
- Firefox + Flex — 62 fps
- Adobe AIR — 62 fps
- Firefox + Silverlight (CLR) — 99 202 fps (update: 202 fps after fixing main timer’s latency)
- CPU Utilization: http://java.dzone.com/articles/node-count-and-javafx
There is more...
- Bad plugin for Eclipse: no content assist(even for plain java), no formatting, no imports resolve possibility, no autocomplition, poor component base. The NetBeans plugin is much better - it has everything that Eclipse doesn't. About 90% of developers use NetBeans to write JavaFX applications according to this
- Why JavaFX sucks http://pacoup.com/2008/12/08/why-javafx-sucks/
- Unnecessary new syntax. Well, it is created mostly for designers, but why <> instead of != if it is Java based technology?
- ...
Conclusion
JavaFX has a long way to go...
This is a post from my colleague Alexander Gavrilov |
By default GORM injects whole set of dynamic finders into domain entities. However what should one do if he needs to implement more complex queries that require use of Criteria Builder or HQL?
Simplest option is just to do this in the layer on top of domain model. So our controllers or services would call something like Customer.executeQuery or Customer.createCriteria. Although this is the simplest option it is also the worst. First, this will expose queries to client code and second we cannot reuse these queries anywhere else.
Second option is to implement these queries as methods in entities. The problem here is that now we are actually not just mixing business and persistence APIs (look my previous post about this) but also the implementation.
Third option is to use the same injection employed by Grails framework also in case of custom queries. For example if we need to implement complex query to find all customers who should to be notified about completed orders we can implement following finder:
class CustomerRepositoryPlugin {
def findAllCustomersWhoNeedToBeNotifiedAboutCompletedOrder() {
...
def result = Customer.executeQuery(...)
...
}
def install() {
Customer.metaClass.static.findAllCustomersWhoNeedToBeNotifiedAboutCompletedOrder = {
findAllCustomersWhoNeedToBeNotifiedAboutCompletedOrder()
}
}
}
Of course this could be made more generic and compact so that all suitable repository plugins are found and injected automatically.
It's not possible to talk about technology and not to try writing something on it. So, because I have something to compare to (Aqris AMS, which was written using GWT http://ams.aqris.com) I wanted to make a similar calendar, but on javafx.
Firstly, it was really hard to get used to new syntax. Secondly, components base is a little poor and I'll show you why.
My first thought was to do calendar view using simple table, that has 96 rows and 7 columns. For that purpose I took swing component, that is called JList and after some time playing with layouts and other stuff I finally draw my first calendar. By default it has single cell selection. "Nice.." was my first reaction, when without additional effort I got cell selection. I thought, that if there is a single cell selection, then there should be also multiselection. Ctrl + space for searching appropriate argument in this class did not give anything interesting, neither java-doc for JList. I've started researc in internet and found, that it does not support multiple selection in [JavaFX]...."weird isn't it"((c) Homer Simpson).
OK, but one guy showed in some blog how it's possible to add this kind of support. Basically, all you need to do is create your own swing component and put there JList as a field. Then add mouse dragging event and for every new cell, mouse comes in, add it into your list selection:
onMouseDragged: function( e: MouseEvent ): Void { swingList.list.addSelectionInteraval(e.x, e.y);//instead of e.x & e.y thare were other variables, that were calculated based on e.x and e.y } public class SwingList extends SwingComponent { public var list: JList; ........ public function addSelectionInterval(anchor : Integer, lead : Integer) : Void { list.addSelectionInterval(anchor, lead); } ...... }
However, I faced other problems here, concerning selection and after that I stopped using JList.
Another way to draw list was to draw 7 * 96 cells, which are basically rectangles. Why i did not choose drawing borders as lines (it would be 8 * 97 lines) is because of showing selected area. As far as I see it, it is not possible to fill some part of background (for instance two cells containing part 11 * 40 px) with other color, you still need to have some component (shape) to be filled on this part.
It isn't rocket science for javafx to draw a table (basically, some layout info can be placed in CSS file. However to write CSS for javafx, you have to follow some additional rules http://forums.sun.com/thread.jspa?threadID=5357325):
var cells: CalendarCell[] = { for (i in [0..6]) { for (j in [0..95]) { CalendarCell { x: bind (cellWidth * i) + TIME_COLUMN_WIDHT as Integer y: bind cellHeight * j as Integer width: bind cellWidth as Integer height: bind cellHeight as Integer day: i timeOfADay: j stroke: Color.web("#DDDDDD"); fill: {if (isCurrentDay(i)) Color.web("#FFFFCC") else Color.WHITE} cellWidth: bind cellWidth cellHeight: bind cellHeight isCurrentDayCell: isCurrentDay(i) } } } }
Calendar cell itself is a simple rectangle with additional info:
public class CalendarCell extends Rectangle { public var day: Integer = 0; public var timeOfADay: Integer = 0; public var cellHeight: Integer; public var cellWidth: Integer; public var isCurrentDayCell: Boolean; }
Now, I have calendar view, that is fitted into user's window, depending on window size. That means, if user will re-size the window, then the calendar will be also re-sized (works only if it is desktop application, because in case of embedding it into browser it will have fixed height and width parameters). This is done by using really powerful javafx feature binding. In two words: it means that if value of bound variable will change, then the value of variable to which it is bound to will be changed automatically.
Basically, for GWT it goes also quite easily:
private void drawGrid() { drawRows(); drawColumns(); } private void drawRows() { for (int row = 1; row < model.getRowCount(); row++) { addRowSeparator(new RowSeparator(row, model.getCellHeight(), model .getTimeGranularityModel().getCellDuration())); } } private void drawColumns() { for (int col = 0; col < model.getColumnCount(); col++) { final ColumnSeparator separator = new ColumnSeparator(col, model .getCellWidthPerc()); addColumnSeparator(separator); } }
However, here you have to play with CSS (you know what it means? yep! IE..) to draw cell borders ([ColumnSeparator] and [RowSeparator]).
public class ColumnSeparator extends Widget { public ColumnSeparator(final int column, final float cellWidthInPercentage) { setElement(DOM.createDiv()); setStyleName("line vertical"); DOM.setStyleAttribute(getElement(), "left", column * cellWidthInPercentage + "%"); } }
Unfortunately, here the feeling that javafx has lack of components comes again - there is no support for scrolling, yet (despite the fact, there is a [ScrollBar] component). To create content, which can be scrolled in GWT you would probably do something like this:
scrollPanel = new ScrollPanel(mainContentGrid); //mainContentGrid contains created above calendar
But for javafx you need to be creative again (google, google, google....). Nice blogpost about how to create scrollable content: http://blog.alutam.com/2009/08/30/implementing-a-scroll-view-in-javafx/. All I had to do is just change some parameters to fit into my layout. In the end you will get something like this:
ScrollView {
width: bind sceneWidth - 120 as Integer
height: bind sceneHeight - 120
translateX: 50
translateY: 50
node:
Group {
content: bind [ //content that can be scrolled
timeColumn,
cells,
sceneActivities = Group {
content: bind activities
}
]
}
}
OK, works nicely...lets try to add some cells selection. In javafx every component (or almoust every?) has support for mouse events like onMousePressed, onMouseReleased, onMouseDragged, onMouseWheelMoved etc. So, all you need to do is just implement appropriate method, and voilà:
ScrollView {
....
onMouseDragged: function(e: MouseEvent): Void {
var selectedTime = e.y / cellHeight as Integer;
var selectedCell = getSelectedCell(selectedDayCol, selectedTime);
if (e.y > prevEY) {//mouse moves down
if (selectedTime < firstSelectedCell) { //mouse moves down but time is smaller than strat time
if (selectedCell.isCurrentDayCell) {
selectedCell.fill = Color.web("#FFFFCC")
} else {
selectedCell.fill = Color.WHITE;
}
} else {
selectedCell.fill = Color.web("#CCCCE0");
}
} else {
if (selectedTime > firstSelectedCell) { //mouse moves up but time is grater than strat time
if (selectedCell.isCurrentDayCell) {
selectedCell.fill = Color.web("#FFFFCC")
} else {
selectedCell.fill = Color.WHITE;
}
} else {
selectedCell.fill = Color.web("#CCCCE0");
}
}
prevEY = e.y;
}
...
}
In case of GWT there is no rectangles or something that represents cells on the page and it much more painful to achieve the same effect.
Now it would be great to have popup window to create activity.
Damn, but there is no support for popup windows in javafx
For sure, it is not hard to create one: just draw another rectangle with some fields inside it:
public class PopupWindow extends CustomNode { ... override public function create(): Node { return Group { content: [ Rectangle { x: bind popupPosX, y: bind popupPosY width: 280, height: 180 fill: Color.web("#E8EEF7") blocksMouse: true }, SwingTextField { layoutX: bind popupPosX + 10 layoutY: bind popupPosY + 10 columns: 10 text: bind activityText with inverse editable: true blocksMouse: true }, fromHoursComboBox, fromMinutesComboBox, toHoursComboBox, toMinutesComboBox, Button { text: "Add" layoutX: bind popupPosX + 10 layoutY: bind popupPosY + 100 action: function() { this.visible = false; def activity: Activity = Activity { activityId: sizeof activities visible: true; activityStartPosX: activityStartPosX; activityStartPosY: (fromHoursComboBox.selectedIndex * 4 + fromMinutesComboBox.selectedIndex) * cellHeight activityWidth: bind activityWidth activityHeight: ((toHoursComboBox.selectedIndex * 4 + toMinutesComboBox.selectedIndex) - (fromHoursComboBox.selectedIndex * 4 + fromMinutesComboBox.selectedIndex) ) * cellHeight; fromTime: fromHoursComboBox.selectedIndex * 4 + fromMinutesComboBox.selectedIndex toTime: toHoursComboBox.selectedIndex * 4 + toMinutesComboBox.selectedIndex activityDay: selectedDayCol cellHeight: bind cellHeight } insert activity into activities; clearSelection(); } }, CloseButton { blocksMouse: true windowToClose: this translateX: bind popupPosX + 258 translateY: bind popupPosY + 2 layoutInfo: LayoutInfo { hpos: HPos.RIGHT, vpos: VPos.TOP } selectedCellsIndxs: bind selectedCellsIndxs cells: cells } ] } }
Nothing complicated here, except that it's needed to implement close button for the popup (basically, it can be a simple rectangle or two cross-lines which react to mousePress and release events like that:
onMouseReleased: function(me:MouseEvent):Void { windowToClose.visible = false; windowToClose.clearSelection(); }
However, concerning popup window and it's communication with activities and with calendar, here starts the hardest part. First of all it has to appear on position, that depends on mouse. That means that position has to be changed all the time. So, that means we have to bind parameters, that can change, so that they will change automatically:
popup = PopupWindow {
visible: bind showPopup with inverse
popupPosX: bind popupPosX
popupPosY: bind popupPosY
activities: bind activities with inverse
activityStartPosX: bind activityStartPosX;
activityStartPosY: bind activityStartPosY;
activityDuration: bind cellHeight * numOfCellsSelected
activityWidth: bind cellWidth
fromTime: bind firstSelectedCell
toTime: bind lastSelectedCell + 1
selectedCellsIndxs: bind selectedCellsIndxs
cells: cells
selectedDayCol: bind selectedDayCol
cellHeight: bind cellHeight
}
But! The position of activity (activityStartPosX and activityStartPosY) is also bound, but I wanted to do it so that it would be possible to change activity start time and end time by using comboboxes (it means, that in calendar you can select one interval, but through comboboxes you will be able to change it). This means, that activity position is also depending on values, that user select in appropriate comboboxes. What is the solution here? Binding! I've bound selected values from comboboxes to activity positions, done some calculations and as a result got exception: "Exception in thread "main" com.sun.javafx.runtime.[BindingException]: Both components of bijective bind must be mutable" o_O wtf? Solution for this problem would be using so called "Bridge pattern", described here: http://blogs.sun.com/clarkeman/entry/bounding_bridge. Done! I have implemented bridge variables, compile project, run...."com.sun.javafx.runtime.[AssignToBoundException]" is what I see instead of calendar. So, javafx cannot bind variable if it is already bound. As a solution you should use temporary variable http://forums.sun.com/thread.jspa?messageID=10747205#10747205. After all these manipulations I have got piece of quite nice code:
public var fromTime: Integer on replace { fromTimeMinutes = fromTime mod 4; fromTimeHours = Math.floor(fromTime / 4) as Integer; newFromTime = fromTimeHours + fromTimeMinutes; }; //bridge variable var fromTimeMinutes: Integer on replace { newFromTime = fromTimeMinutes + fromTimeHours * 4; //recalculate time }; //bridge variable var fromTimeHours: Integer on replace { newFromTime = fromTimeMinutes + fromTimeHours * 4; //recalculate time }; //temp variable to elimineate AssignToBoundException var newFromTime: Integer on replace { tempActivityStartPosY = newFromTime * cellHeight; //recalculate start position for drawing //newToTime = newStartTime + activityDuration / cellHeight as Integer; }; //to time bridge public var toTime: Integer on replace { toTimeMinutes = toTime mod 4; toTimeHours = Math.floor(toTime / 4) as Integer; newToTime = toTimeHours + toTimeMinutes; }; //bridge variable var toTimeMinutes: Integer on replace { newToTime = toTimeMinutes + toTimeHours * 4; //recalculate time }; //bridge variable var toTimeHours: Integer on replace { newToTime = toTimeMinutes + toTimeHours * 4; //recalculate time }; //temp variable to elimineate AssignToBoundException var newToTime: Integer on replace { newActivityDuration = (newToTime - newFromTime) * cellHeight; }; var tempActivityStartPosY: Number; var newActivityDuration: Number;
Fortunately, a piece of jelly in my head told me, that it is better to take values, directly from comboboxes. In this case there is no need to overcome this exceptions.
At least! the final part is to draw activity: nothing unusual here, again rectangles and some other shapes do the stuff. And also, to add activity drag effect and re-sizing effect, you need to implement appropriate methods in the appropriate way. For instance, activity dragging effect would look like this:
Rectangle {//activity header
x: bind activityStartPosX
y: bind activityStartPosY
width: bind activityWidth
height: 10
fill: Color.web("#1E4420")
blocksMouse: true
cursor: Cursor.HAND
onMouseDragged: function(e: MouseEvent): Void {
//don't allow to drag outside scrollable area
if (e.x >= 60 and e.x < scene.width - 135 and e.y > 0 //scroll width = 120 + slider width 15
and e.y + activityHeight < 2 * scene.height - 40) { //-40 due to bottom margin
var selectedDay = (e.x - 60) / (activityWidth) as Integer;//60 = TIME_COLUMN_WIDHT
activityStartPosX = (selectedDay * activityWidth) + 60;//60 = TIME_COLUMN_WIDHT
var selectedTime = e.y / cellHeight as Integer;
activityStartPosY = (selectedTime * cellHeight);//e.y;
fromTime = selectedTime;
toTime = fromTime + (activityHeight / cellHeight) as Integer;
}
}
}
That's it!
| Author note This is post by: Alexander Gavrilov |
Labels: springsource, spring, aspectj, grails, groovy
Spring Roo is essentially RAD tool that simplifies project setup and some other common tasks like creating simple persistence queries, stubbing controllers/views, adding JMS/email support.
Internal architecture is more described here http://blog.springsource.com/2009/06/18/roo-part-3/ but essentially it relies heavily on AspectJ inter-type declarations (ITD - "mixin" for Java) to separate the generated code and code written by developer.
It has both command line tool and special IDE (SpringSource Tool Suite).
Roo is very similar to Grails:
- they follow domain model centric design where all validation rules and database schema generation is based on the domain model
- they provide fast project setup
- they provide set of scripts for generating stubbed controllers and views
- both have extension mechanism for addons (like security, email support) that can be used to easily add some common technical features into the system
Interestingly it seems that the testing story is somewhat better in Roo. Tests are faster and can be executed from IDE. Also some simple tests for entities are generated for you. However there are some features that don't work so well in Roo. For example if you want to use dynamic finders you have to execute special command that generates the code for this finder and injects it into your entity. In Grails such finders are available automatically.
What I like most in Roo is the ability to set up new Spring web app project very fast. I'm don't like so much the way how finders are implemented . It seems that whenever you generate new finder, additional finder will also be added to the list of finders in @RooEntity annotation. I can imagine that this list can easily get quite long.
So the question that many ask - why is SpringSource developing two tools that are so similar? Some people from SpringSource say that these tools are not really competing because one is for Java-only environments. According to this post http://old.nabble.com/Re%3A-Spring-Roo-vs-plus-Grails---discuss-p23287081.html the goal of Roo is to lead the path to Grails for those who are more conservative. So essentially SpringSource doesn't want to invest everything into Grails and is therefor trying to push both tools at least for now.
In general if SpringSource will make production release (currently RC2) and add some documentation then it could be worth trying. Many features however feel much more natural in Grails where metaprogramming is already part of the underlying language.
The thing that I seriously missed since I started to use Android Dev Phone 1 was Estonian Mobiil-ID. So in order to visit internet bank I had to use my ID-card again. And as I haven't bothered to set ID-card software up to my Gentoo box, I could only use bank services in Windows.
Android cupcake roadmap says it includes SIM Application Toolkit (STK) 1.0 as a new feature. Mobiil-ID is a SIM application too. This should mean that after upgrading the dev phone to newer SDK with image files provided by HTC I should be able to use my Mobiil-ID again! However, STK application package was not included in the images. Sigh.
BTW, when I look at it, it seems Android consumer phones do not have SIM Application Toolkit either.
So after some googling for solutions and grieving for a while, I decided to do it the hard way: download Android sources and compile STK application package myself.
Before compiling the code blindly, I checked the STK code, just for fun. For some reason, STK's AndroidManifest.xml contains a reference to an activity StkSettings which does not exist anymore (this class did exist before, so it must be a bug in the manifest file). So I simply commented this activity out from the manifest:
sven@galdor ~/mydroid/packages/apps/Stk $ git diff
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index b3eae21..ace3fcb 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -61,6 +61,7 @@
</activity>
<!-- SIM Toolkit settings activity -->
+<!--
<activity android:name="StkSettings"
android:label="@string/app_name">
<intent-filter>
@@ -70,6 +71,7 @@
android:name="android.intent.category.DEVELOPMENT_PREFERENCE" />
</intent-filter>
</activity>
+-->
<receiver android:name="com.android.stk.StkCmdReceiver">
<intent-filter>
Alright, typed make and waited for some time. Then finally the compilation finished and a list of shiny application packages are in out/target/product/generic/system/app/. Er... but not Stk.apk that I expected the most. So apparently it is specified somewhere what individual application packages are built by default. It didn't take long to find that the file target/product/generic.mk contained a list of application names that the default run of make produced. Added Stk to the list of application packages in PRODUCT_PACKAGES:
sven@galdor ~/mydroid/build $ git diff diff --git a/target/product/generic.mk b/target/product/generic.mk index b9bc070..8aac809 100644 --- a/target/product/generic.mk +++ b/target/product/generic.mk @@ -16,7 +16,8 @@ PRODUCT_PACKAGES := \ Updater \ CalendarProvider \ SubscribedFeedsProvider \ - SyncProvider + SyncProvider \ + Stk $(call inherit-product, $(SRC_TARGET_DIR)/product/core.mk)
and then typed make again.
After biting my nails for a short while, the compilation finished and what do I see... Stk.apk in out/target/product/generic/system/app/!
Installed the package with adb install out/target/product/generic/system/app/Stk.apk.
According to AndroidManifest.xml, STK listens to android.intent.action.BOOT_COMPLETED intent in order to start its service. Rebooted the phone to make sure it has started the service and is able to process the incoming messages from my mobile operator.
Let's test how it works. Opened my internet bank web site and chose Mobiil-ID as the login method. The phone was unlocked and home screen displayed. Unfortunately the phone did not react on the Mobiil-ID message, and the web site got a timeout finally. Not good. For the next test, I opened SIM Toolkit application and left its front page open:
![]()
Trying to log in to internet bank again. In a few moments I could see the PIN1 entry dialog!
![]()
Keyed in my PIN1 and in a few more moments I was logged in to internet bank! ![]()
So far I haven't made operations in the bank that require the entry of PIN2, yet. Will do this test probably in a few weeks time. Don't see why this should fail though.