Ractive.js and vert.x integration over the event bus

Recently I ran into Ractive.js which seemed like something doing exactly what I’d expect from a client side (browser that is) framework – fetch its own data and create nice web pages with that without getting me from the beginning too deep into JavaScript programming. Nota bene: I’m not a JS programmer and Angular or Ember just scare the s* out of me, plus I have strong moderate opinions against massive JavaScript framework programming so Ractive seemed like a convenient compromise. But would I be able to use it? In a vert.x project? Maybe I should try just that…

The plan is to integrate a Ractive.js sample project (I chose “comments”) with the vert.x Gradle template project thus having a JS Ractive-based application requesting the data from the backend directly via the vert.x bus. I started as described in the Vert.x documentation by cloning the vert.x Gradle template to generate a test project with it. Just for fun I ran a gradlew test and voila – half of the tests will fail with cryptic errors… ugh. Anyway I started to delete a lot of default classes and directories because I’m not planning to develop in all languages ever supported by vert.x. I also removed the .git directories by hand to “start from scratch” – which made Eclipse (STS actually) somehow lose the project’s gradle nature (why?!?) so I re-added it to the Eclipse .project file org.springsource.ide.eclipse.gradle.core.nature (followed by a Gradle refresh all – just in case). I guess I could have just regenerated the Eclipse project with “gradlew eclipse”, but too late for that now.

There will be as always some weird issues – like when “gradlew test” cannot find the Java compiler but “gradlew run” can. A still open issue is that I still have to find out where the default logging goes in this setup. The default logging is documented for a standalone installation of vert.x, while Gradle will fetch its own vert.x in build\deps where there’s no conf directory – and I couldn’t find any trace of a vertx.log on my system. The recommended logging.properties from platform_lib as documented doesn’t seem to do anything either to this respect, so a proper Gradle+vert.x logging is still a riddle for me, I’ll guess some startup values in gradle.properties will do it but I left the analyse for later. After all I can still run the finished module directly with vert.x where this works as documented. Or like I ended doing for this test project – using System.out and console.info respectively.
The first verticle is a basic one. I’ll load the MongoDB persistor for my data and the web server module to… serve everything.

public void start() {
  JsonObject config = container.config();
  container.deployModule("io.vertx~mod-web-server~2.0.0-final", config.getObject("web-server"));                
  container.deployModule("io.vertx~mod-mongo-persistor~2.1.0", config.getObject("mongo-persistor"));
  System.out.println("started");
};

One convenient thing is config.getObject("something"). If you create in the project directory a conf.json file and mention it in gradle.properties like this:

runModArgs=-conf conf.json

Gradle will provide it automatically to the vertx command line. Good. My conf.json looks like this:

{
    "mongo-persistor": 
    {
        "address": "vertx.mongopersistor",
        "host": "localhost",
        "port": 27017,
        "pool_size": 10,
        "db_name": "testdata"
    },
    "web-server": 
    {
        "web_root": "webroot",
        "port": 8080,
        "bridge": true,
        "inbound_permitted": 
        [
            {
                "address": "test.address"
            },
            {
                "address": "vertx.mongopersistor",
                "match": 
                {
                    "action": "find",
                    "collection": "comments"
                }
            },
            {
                "address": "vertx.mongopersistor",
                "match": 
                {
                    "action": "save",
                    "collection": "comments"
                }
            }
        ]
    }
}

To read more on the specific modules configuration check the respective project webpages, right now let’s just say that the section which will be loaded as configuration for the mongodb-persistor module is a pretty default one. The configuration for the web server module part allows it function as both static file server (serving from wwwroot) and as a bus bridge – programmed to accept from the client a test message (for the tiny handler added in my test verticle) and two types of messages routed to the mongodb module. Didn’t I say before I will expect the client to do some real work? So, there is the channel for the client to fetch data: via the vert.x event bus (which will work over a SockJS connection, in case you’re wondering about the innards)

How’s the vert.x client side then going to be? Pretty basic, according to the light documentation and the slightly juicier web app example (based on AngularJS, ouch): you’ll have the basis index.html like this:

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" type="text/css" href="css/app.css">
<title>My index</title>
</head>
<body class="example"> 
</body>
<script src='js/sockjs-min-0.3.4.js'></script>
<script src='js/vertxbus.js'></script>
<script src='js/app.js'></script>
</html>

I know the page won’t show anything, but bear with me to see why – in the app.js file:

var eb = new vertx.EventBus(window.location.protocol + '//' + window.location.hostname + ':' + window.location.port
    + '/eventbus');
var pa = 'vertx.mongopersistor';
eb.onopen = function() {
    eb.send('vertx.mongopersistor', {
    action : 'find',
    collection : 'comments'
    }, function(reply) {
    if (reply.status === 'ok') {
        console.info('Got so many comments: ' + reply.results.length);
    } else {
        console.error('Failed to retrieve comments: ' + reply.message);
    }
    });
}

You can even run this with gradlew run then point your browser to http://localhost:8080 and you’ll see the messages being written nicely the browser console. Even nicer would be to actually HAVE some data to fetch, so let’s preload some in our MongoDB database. For this I’ll add a handler to be executed when the MongoDB vert.x module finished deployment:

container.deployModule("io.vertx~mod-mongo-persistor~2.1.0", config.getObject("mongo-persistor"), deployHandler);

respectively:

private AsyncResultHandler<String> deployHandler = new AsyncResultHandler<String>() {
    public void handle(AsyncResult<String> asyncResult) {
        if (asyncResult.succeeded()) {
            loadSampleData();
        } else {
            asyncResult.cause().printStackTrace();
        }
    }
};
private void loadSampleData() {
    final EventBus eb = vertx.eventBus();
    JsonObject cmd = new JsonObject();
    cmd.putString("action", "count");
    cmd.putString("collection", "comments");
    cmd.putObject("matcher", new JsonObject());

    eb.send("vertx.mongopersistor", cmd,
        new Handler<Message<JsonObject>>() {
            public void handle(Message<JsonObject> message) {
                if (0 != "ok".compareTo(message.body().getString("status"))
                    || 0 == message.body().getInteger("count")) {

                    JsonObject cmd = new JsonObject();
                    cmd.putString("action", "save");
                    cmd.putString("collection", "comments");

                    Buffer jsonBuf = vertx.fileSystem().readFileSync("static_data.json");
                    JsonObject staticData = new JsonObject(jsonBuf.toString());
                    for (int i = 0; i < staticData.getArray("comments").size(); i++) {
                        cmd.putValue("document", staticData.getArray("comments").get(i));
                        eb.send("vertx.mongopersistor", cmd,
                            new Handler<Message<JsonObject>>() {
                                public void handle(Message<JsonObject> message) {
                                    System.out.println("saved test comment: " + message.body().getString("status", "wot?"));
                                }
                            });
                    }
                }
            }
        });
}

There’s nothing special above – just plain ole’ regular usage of the mongodb-persistor vert.x module according to its documentation page. If the database has no “comments” the code will load some from a special file in the project root, called static_data.json and looking like this (contents based on the Ractive.js “comments” example)

{
    "comments": 
    [
        {
            "id": 1,
            "author": "Rich",
            "text": "FIRST!!!"
        },
        {
            "id": 2,
            "author": "anonymous",
            "text": "I disagree with the previous commenter"
        }
    ] 
}

I added an “id” field which I’ll use later for sorting, one should use probably rather a timestamp or whatever, but the issue doesn’t really matter now given the restricted scope of a simple technology test.

So, how’s the client side, the JavaScript Ractive.js side going to work then? After a bit of fiddling around – fixing the example code according to the “60 seconds setup” guide, finding the Ractive.js links and adding the Showdown.js requirement and the Ractive.js plugin named “slide” (with corresponding broken weblinks)… then creating one single CSS file from the example inline CSS, debugging with Firebug some CSS things which were looking odd in Firefox (content completely missing or just with wrong sizes) but not in Internet Explorer… this took me some time but it was good spent time after all, reading through Ractive documentation I ran into one or another little gem of understanding. The major grip I have with it is the required wrapping of Mustache-based templating HTML in script tags. Although I understand the reason (Mustache code is not always proper HTML so a browser would likely mess it before Ractive.js has a chance to use the template) I still like it when my Eclipse does nice formatting and syntax highlighting… anyway, here are the important parts:

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" type="text/css" href="css/app.css">
<title>My index</title>
</head>
<body class="example">

</body>
<script src='js/showdown.js'></script>
<script src='js/ractive.js'></script>
<script src='js/ractive-transitions-slide.js'></script>
<script src='js/sockjs-min-0.3.4.js'></script>
<script src='js/vertxbus.js'></script>
<script src='js/app.js'></script>
</html>

The basic index.html has no important changes, just the adapted example CSS file I saved under webroot/css/app.css and the JS requirements. My application code went into app.js by the way. As I had simple markup in this sample I could get away with putting it like correct HTML (the code is basically taken from the Ractive example):

<div id='template' class='comment-box'>
    <h3>Comments</h3>
    <div class='comment-list'>
        {{#comments}}
        <div class='comment-block' intro='slide'>
            <h4 class='comment-author'>{{author}}</h4>
            <div class='comment-text'>{{{ renderMarkdown( text ) }}}</div>
        </div>
        {{/comments}}
    </div>
    <form name='comment-form' class='comment-form' on-submit='post'>
        <input class='author-input' value='{{author}}'
            placeholder='Your name' required>
        <textarea value='{{text}}' placeholder='Say something...' required></textarea>
        <input type='submit' value='Submit comment'>
    </form>
</div>

but otherwise the above DIV would have been wrapped in a SCRIPT element like Ractive actually recommends:

<script id='template' type='text/ractive'>
    <div class='comment-box'>
        <h3>Comments</h3>

Besides the straightforward Ractive sample code I had to add the binding to the vert.x event bus, done this way in app.js. Notice I did some quick sorting by id on the loaded comments (to show the latest first) but this is just a test project right.

var eb = new vertx.EventBus(window.location.protocol + '//' + window.location.hostname + ':' + window.location.port
    + '/eventbus');
var pa = 'vertx.mongopersistor';

eb.onopen = function() {
    eb.send('vertx.mongopersistor', {
    action : 'find',
    collection : 'comments'
    }, function(reply) {
    if (reply.status === 'ok') {
        var comments = [];
        for (var i = 0; i < reply.results.length; i++) {
        comments.push(reply.results[i]);
        }
        comments.sort(function(a, b) {
        return b.id - a.id
        });
        ractive.set('comments', comments);
    } else {
        console.error('Failed to retrieve comments: ' + reply.message);
    }
    });
}

Ractive initialization code is again the one from the sample site. I just left some sample comments to be loaded at Ractive initialization time – that is, before the real ones come through the bus.

var sampleComments = [ {
    author : 'Didi',
    text : 'Sample...'
} ];
ractive = new Ractive({
    el : 'example',
    template : '#template',
    noIntro : true, // disable transitions during initial render
    data : {
    comments : sampleComments,
    renderMarkdown : function(md) {
        return converter.makeHtml(md);
    }
    }
});

The Ractive “post” event handler stays basically the same because it just handled the client side update. I only liked showing the comments in reverse order (newest on top) so adding the fresh comment to the list had to be done with something like:

this.get('comments').unshift(comment);

I had obviously to implement the handler of the “new comment” event as recommended by the sample, so the fresh comment gets also added to the comments database. In case of failing to save it, the fresh comment will be removed from the shown comments list, as a kind of error handling. The ID of the new comment is set to the presumably next free number (size of the list) – a very primitive and highly not recommended way for any real life scenarios.

ractive.on('new comment', function(comment) {
    comment.id =  this.get('comments').length;
    eb.send('vertx.mongopersistor', {
	action : 'save',
	collection : 'comments',
	document: comment
    }, function(reply) {
	if (reply.status === 'ok') {
	    // whatever
	} else {
	    console.error('Failed to save comment: ' + reply.message);
	    var posn = this.get('comments').indexOf(comment);
	    if(-1 !== posn) {
		this.get('comments').splice(posn,1);
	    }
	}
    });
});

The whole project code can be found on GitHub (make sure you look at this release) in case you need more than above (hopefully not). Before you ask, it has no tests because there was nothing I thought really needed testing, frameworks just work. Of course, in order to know how frameworks work everybody is first perusing the documentation, right?

Advertisements

One thought on “Ractive.js and vert.x integration over the event bus

  1. Pingback: How simple is simple? (from MongoDB to Elasticsearch) | Trying things

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s