Mittwoch, 26. Februar 2014

App Start - Bringing JavaFX Applications to the End User

How to bring JavaFX applications to the end user's client?

Two possibilities:

  1. Install Java (quite up to date version...) on the client. Then use web start or applet.
  2. Package your JavaFX application so that the JRE is included (e.g. using bundling) and run it as autarc application on client side.
Let's take a deeper look into both approaches:
  1. WebStart/Applet: hmmm... maybe we ourselves are too close to industry/business scenarios, but the installation of a client side Java VM often is some hot discussion. If the discussion is valid or not - who cares at the end, if you are the poor guy to try to make your JavaFX application run at the client side.

    And even if the customer agrees to install some up to date Java VM, he/she typically is not the one who is willing to constantly upgrade the VM on all the clients later on... So it's you having to live with the fact that your WebStart/Applet-stared JavaFX application will have to run on many different Java versions...
  2. Autarc client application: yes, this often is a nice alternative, because now people are not talking about this client wide Java installation - they more or less treat your application as a native one. Who cares, if there is some bundled JRE which no one outside the application knows about. - And now you are the master of which Java version you use, there is no company policy or whatever you have to fight with.

    But...: now you are missing all the nice features that are part of WebStart/Applet: the re-loading of your client application if it changes on server side - the automated roll out of your client software, both for the initial and for the continuous installation.
So, this was the situation we were/are in: for many scenarios we want to go the "autarc application" way, but at same point of time needed something to control the roll out of the client software.

Consequence: we wrote a simple and thin framework which we called "App Start". (We did not check if this name is somehow blocked, so this is an internal project name at the moment!).


It's a client side launcher of a JavaFX application that is kept on server side. The launcher is a small Java programm that is configured by some xml-file, so that it knows what to download from where and after downloading how to start the JavaFX application.
The launcher knows the version stamp of the JavaFX application and with each start checks if the version still is in sync with the server side.

So "App Start" is some mixture out of the "autarc application" mode and the "Web Start" mode. Main difference: "App Start" is only working in the context of one JavaFX client application

As I said: App Start is a quite thin framework. And currently it's only available for Windows scenarios (sorry... but this is the 9x% of our users...), but taking over to other OSs is "just effort". We are open to share everything here - sources, docu, demos etc. are available in the links below. If this is interesting for you: please contact us (info@CaptainCasa.com), maybe this is something we can join forces.

Now the links:

Some slides (PDF)
Tutorial
Test / Demo (currently Windows only)

App Start page: http://www.CaptainCasa.com => Community => App Start Framework - please find all resources (including source code)

...as I said: the framework was born out of our concrete own requirements, but is strictly decoupled from our "normal" JavaFX development. So, it's normal water which we used for cooking, there is not too much magic inside.

One final comment: the picture above only is a simplified view on "App Start" - emphasizing the loading of the JavaFX application. On a long run a client side applications also will require a Java version update (only in the context of this autarc application!) and also an update of the starter program itself. So, in reality, everyhing is versioned and everyhing is synced if required...:


Montag, 10. Februar 2014

Business Application Workplace with JavaFX

We did some screen recording in order to show how a JavaFX business application workplace might look like.

The video is available here:
http://youtu.be/F77UcB1hNXg

Please note: the video was taken "from the labs"... The JavaFX demo application that is shown in the video is available via http://www.CaptainCasa.com, so you might view and download from there.

Björn

Montag, 13. Januar 2014

JavaFX and OSM (OpenStreetMap)

In our Swing based client implementation we used a certain library "swingx-ws.jar" around the class "JXMapKit" to render OSM content. The library was coming from the Swinglabs and rendered the maps directly into the Swing-Graphics.

We now wanted to do the same in JavaFX - of course finding out that there is no "native" implementation of an FX-based OSM renderer.

But: there is a nice web browser integration in FX, that I believe everyone in the meantime knows about. And this is also the base for integrating OSM in a really simple way:


In the screenshot you see an FX screen with an embedded WebView-instance showing an OSM map. In the map there are two way points, each one containing certain text information.

When changing the text in the FX grid, then also the text in the OSM map is updated:


When clicking onto a way point's text then the corresponding grid row is selected:

There are not a lot of things at all that are required in order to set up contact between FX and the OSM page:

  1. In FX define a WebView instance with a corresponding WebEngine
  2. In the WebEngine load an HTML page that uses the map-JavaScript-interface. The following implementation is based on something we found in http://wiki.openstreetmap.org/wiki/DE:Karte_in_Webseite_einbinden. So we did not a lot more than copying and pasting from there...:

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="de" lang="de-de" style="height:100%;width:100%;margin:0;padding:0">

    <!--
    Based on http://wiki.openstreetmap.org/wiki/DE:Karte_in_Webseite_einbinden
    -->

    <head>
      <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
      <meta http-equiv="content-script-type" content="text/javascript" />
      <meta http-equiv="content-style-type" content="text/css" />
      <meta http-equiv="content-language" content="de" />
      <script type="text/javascript" src="http://www.openlayers.org/api/OpenLayers.js"></script>
      <script type="text/javascript" src="http://www.openstreetmap.org/openlayers/OpenStreetMap.js"></script>

    <script type="text/javascript">
    function jumpTo(lon, lat, zoom)
    {
        var x = Lon2Merc(lon);
        var y = Lat2Merc(lat);
        map.setCenter(new OpenLayers.LonLat(x, y), zoom);
        return false;
    }
    function Lon2Merc(lon)
    {
        return 20037508.34 * lon / 180;
    }
    function Lat2Merc(lat)
    {
        var PI = 3.14159265358979323846;
        lat = Math.log(Math.tan( (90 + lat) * PI / 360)) / (PI / 180);
        return 20037508.34 * lat / 180;
    }
    function ccCallback(pId)
    {
        ccApp.callback(pId);
    }
    function addMarker(pId,lon, lat, popupContentHTML)
    {
        if (popupContentHTML != null)
        {
            popupContentHTML = "<span onclick='ccCallback(\""+pId+"\")'>" + popupContentHTML + "</a>";
        }
        var layer = layer_markers;
        var ll = new OpenLayers.LonLat(Lon2Merc(lon), Lat2Merc(lat));
        var feature = new OpenLayers.Feature(layer, ll);
        feature.closeBox = true;
        feature.popupClass = OpenLayers.Class(OpenLayers.Popup.FramedCloud, {minSize: new OpenLayers.Size(150, 50) } );
        feature.data.popupContentHTML = popupContentHTML;
        feature.data.overflow = "hidden";
        var marker = new OpenLayers.Marker(ll);
        marker.feature = feature;
        if (popupContentHTML != null)
        {
            var markerClick = function(evt)
            {
                if (this.popup == null)
                {
                    // this.popup = this.createPopup(this.closeBox);
                    // map.addPopup(this.popup);
                    // this.popup.show();
                }
                else
                {
                    this.popup.toggle();
                }
                OpenLayers.Event.stop(evt);
            };
        }
        marker.events.register("mousedown", feature, markerClick);
        layer.addMarker(marker);
        m_markers[pId]  = marker;
        if (popupContentHTML != null)
        {
            var vPopup = feature.createPopup(feature.closeBox);
            map.addPopup(vPopup);
            m_popups[pId] = vPopup;
        }
    }
    function removeMarker(pId)
    {
        var marker = m_markers[pId];
        if (marker != null)
        {
             layer_markers.removeMarker(marker);
        }
        var vPopup = m_popups[pId];
        if (vPopup != null)
        {
            map.removePopup(vPopup);
        }
    }
    function getCycleTileURL(bounds)
    {
       var res = this.map.getResolution();
       var x = Math.round((bounds.left - this.maxExtent.left) / (res * this.tileSize.w));
       var y = Math.round((this.maxExtent.top - bounds.top) / (res * this.tileSize.h));
       var z = this.map.getZoom();
       var limit = Math.pow(2, z);
       if (y < 0 || y >= limit)
       {
         return null;
       }
       else
       {
         x = ((x % limit) + limit) % limit;
         return this.url + z + "/" + x + "/" + y + "." + this.type;
       }
    }

    var map;
    var layer_mapnik;
    var layer_tah;
    var layer_markers;
    var m_markers = new Array();
    var m_popups = new Array();

    function drawmap()
    {
        OpenLayers.Lang.setCode('de');
        var lon = 0;
        var lat = 0;
        var zoom = 10;
        map = new OpenLayers.Map('map',
        {
            projection: new OpenLayers.Projection("EPSG:900913"),
            displayProjection: new OpenLayers.Projection("EPSG:4326"),
            controls: [
                new OpenLayers.Control.Navigation(),
                new OpenLayers.Control.LayerSwitcher(),
                new OpenLayers.Control.PanZoomBar()],
            maxExtent:
                new OpenLayers.Bounds(-20037508.34,-20037508.34,
                                        20037508.34, 20037508.34),
            numZoomLevels: 18,
            maxResolution: 156543,
            units: 'meters'
        });
        layer_mapnik = new OpenLayers.Layer.OSM.Mapnik("Mapnik");
        layer_markers = new OpenLayers.Layer.Markers("Address", { projection: new OpenLayers.Projection("EPSG:4326"),
                                                      visibility: true, displayInLayerSwitcher: false });
        map.addLayers([layer_mapnik, layer_markers]);
        // position
        // jumpTo(lon, lat, zoom);
        // Position des Markers
        // addMarker(layer_markers, 6.641389, 49.756667,"SERVUS 1");
        // addMarker(layer_markers, 6.741389, 49.856667,"SERVUS 2");
    }
    </script>

    </head>
    <body onload="drawmap()" topmargin="0" leftmargin="0" rightmargin="0" bottommargin="0" style="height:100%;width:100%;margin:0;padding:0">
      <div id="map" style="height:100%;width:100%">
      </div>
    </body>
    </html>

    You see that there are two functions "jumpTo" and "addMarker" which are designed to be called from outside.
  3. These functions can be called from JavaFX by the WebEngine's capability to execute JavaScript statements in the loaded page, so the Java code might look like:

    getEngine().executeScript("jumpTo("+m_longitude+","+m_latitude+","+m_osmzoom+")");
  4. The callback from HTML to JavaFX is done by a call-back-object which is added to the page by the JavaFX program:

    Java(FX) code:

        public class CCApp
        {
            public void callback(String id)
            {       
                ...
                // reaction on callback
                ...
            }
        }
    ...
    ...
        JSObject window = (JSObject)m_browser.getNode().getEngine().executeScript("window");
                        window.setMember("ccApp", new CCApp());



    JavaScript code:

    function ccCallback(pId)
    {
        ccApp.callback(pId);
    }

  5. If there's any difficulty at all, then it is just the timing of operations...: in the FX WebView component you first load the page, then you establish the callback object ("ccApp") and then you call the JavaScript e.g. "jumpTo(...)" in order to show a certain position. To keep this order you have to wait for the page to be completely loaded, and then execute the follow on functions.

    This is done by using the WebEngine's listener mechanism:


        getEngine().getLoadWorker().stateProperty().addListener(new MyStateListener());

        class MyStateListener implements ChangeListener<State>
        {
            @Override
            public void changed(ObservableValue<? extends State> paramObservableValue, State from, State to)
            {
                if (to == State.SUCCEEDED)
                {
                    // ...
                    // now the page is loaded and you can "work"
                    // with the page!
                    // ...        
                }       
            }
        }
That's it! Some mini-Javascript processing together with some simple integration between FX and the WebView/WebEngine.


Please note:

Sonntag, 15. Dezember 2013

Screenshots from our Demo Workplace

Find below some screen shots from our JavaFX based rich client solution, CaptainCasa Enterprise Client 5.0.
Regards, Björn






















Donnerstag, 12. Dezember 2013

Photos from yesterday's Community Meeting

Thanks to all having participated in yesteday's Community Meeting!!!
Please find some photos below (thanks, Antje, for taking them!).
Regards, Björn













Mittwoch, 20. November 2013

JavaFX based Rich Client Applications - IX. CaptainCasa Community Meeting

Begin of October 2013 we released version 5.0 of our rich client framework "CaptainCasa Enterprise Client". Version 5.0 now includes a full JavaFX frontend, running as rich client in front of server side business applications.

All tooling is available in JavaFX as well, so the framework is 100% JavaFX!

This year's CaptainCasa Community Meeting on 11th of December 2013 is a good opportunity to get to know the framework, the people around it - and to see a lot of JavaFX being used in the context of (big) business applications...! The community meeting is held in Heidelberg, Germany. More information is available here: http://www.captaincasa.com/pdf/eclnt_CommunityMeeting_IX.pdf

In case of interest please contact info@CaptainCasa.com.



CaptainCasa Demo Workplace, screenshot:
http://www.CaptainCasa.com



Montag, 21. Oktober 2013

IX. CaptainCasa Community Meeting

The IX.CaptainCasa Community meeting will be held on 11th of December close to Heidelberg, Germany.

Please find some more info her:
http://www.CaptainCasa.com/pdf/eclnt_CommunityMeeting_IX.pdf  


This year of course the JavaFX client will play an important role. During last year's community meeting we decided to go the JavaFX way, based on some first implementation proof of concepty. In the meantime the JavaFX client is part of our delivery since June 2013 - and was quite silently released with our 5.0 release from 1st of October 2013.

Everyone using CaptainCasa Enterprise Client and/or being interested in CaptainCasa is invited. Please register by mail (info@CaptainCasa.com).

Regards, Björn