├── mod.json ├── .gitignore ├── Procfile ├── VertxLeaderboard.gif ├── app.json ├── data.js ├── eb_client.js ├── server.js ├── README.md ├── app.js ├── index.html └── event-bus.js /mod.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: vertx run server.js -------------------------------------------------------------------------------- /VertxLeaderboard.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siddii/vertx-leaderboard/master/VertxLeaderboard.gif -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "JavaScript Leaderboard", 3 | "description": "Sample application demonstrating Vert.x Event Bus with AngularJS data binding", 4 | "logo": "http://vertx.io/logo-white-big.png", 5 | "keywords": [ 6 | "Vert.x", 7 | "Angular", 8 | "Event Bus" 9 | ], 10 | "env": { 11 | "BUILDPACK_URL": "https://github.com/mthenw/heroku-buildpack-vertx.git" 12 | } 13 | } -------------------------------------------------------------------------------- /data.js: -------------------------------------------------------------------------------- 1 | //Fill in some dummy data 2 | var data = [ 3 | { 4 | "name": "jQuery", 5 | "upVote": 0, 6 | "downVote": 0 7 | }, 8 | { 9 | "name": "AngularJS", 10 | "upVote": 0, 11 | "downVote": 0 12 | }, 13 | { 14 | "name": "CanJS", 15 | "upVote": 0, 16 | "downVote": 0 17 | }, 18 | { 19 | "name": "ExtJS", 20 | "upVote": 0, 21 | "downVote": 0 22 | }, 23 | { 24 | "name": "Meteor", 25 | "upVote": 0, 26 | "downVote": 0 27 | } 28 | ]; 29 | -------------------------------------------------------------------------------- /eb_client.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var eventBus = null; 4 | var address = "js-frameworks"; 5 | 6 | function publish(message) { 7 | initConn(); 8 | eventBus.publish(address, {text: message}); 9 | } 10 | 11 | function subscribe(cb) { 12 | initConn(); 13 | eventBus.registerHandler(address, function (response) { 14 | cb(response); 15 | }); 16 | } 17 | 18 | function initConn(cb) { 19 | if (!eventBus) { 20 | eventBus = new vertx.EventBus("/leaderboard"); 21 | 22 | eventBus.onopen = function () { 23 | console.log('Eventbus Connected!'); 24 | subscribe(cb); 25 | }; 26 | 27 | eventBus.onclose = function () { 28 | console.log('Eventbus Closed'); 29 | eventBus = null; 30 | }; 31 | } 32 | } -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var vertx = require('vertx'); 4 | 5 | var server = vertx.createHttpServer(); 6 | var container = require('vertx/container'); 7 | var eventBus = require("vertx/event_bus"); 8 | 9 | //load some dummy data 10 | load('data.js'); 11 | 12 | // Serve the static resources 13 | server.requestHandler(function (req) { 14 | var uri = req.uri(); 15 | if (uri == "/") req.response.sendFile("index.html"); 16 | 17 | if (uri == '/data') { 18 | req.response.end(JSON.stringify(data)); 19 | } 20 | 21 | if (uri && (uri.indexOf('js') !== -1 || uri.indexOf('.css') !== -1)) { 22 | req.response.sendFile(uri.substring(1)); 23 | } 24 | }); 25 | 26 | var sockJSServer = vertx.createSockJSServer(server); 27 | sockJSServer.bridge({prefix: "/leaderboard"}, [{}], [{}]); 28 | 29 | sockJSServer.on('socket-created', function (msg) { 30 | return true; 31 | }); 32 | 33 | 34 | eventBus.registerHandler("js-frameworks", function(message) { 35 | data = message.text; 36 | }); 37 | 38 | server.listen(parseInt(container.env.get('PORT') ? container.env.get('PORT') : '9090')); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | vertx-leaderboard [![Deploy](https://www.herokucdn.com/deploy/button.png)](https://heroku.com/deploy) 2 | ================= 3 | Sample app demonstrating the power of [Vert.x's](http://vertx.io/) *distributed event bus* & [AngularJS's](http://angularjs.org) *data binding* 4 | 5 | ## Introduction 6 | This sample app demonstrates the power of Vert.x distributed event bus along with AngularJS data binding. 7 | Launch the app in multiple tabs or browser windows, play with it & see what happens. Cool eh? 8 | 9 | ### Demo 10 | ![Vertx Leaderboard Demo](VertxLeaderboard.gif "Demo") 11 | 12 | ## Running the app on cloud 13 | The easiest way to test this app is running it on Heroku. 14 | * Get a Free developer account with Heroku (if you don't have one) 15 | * Fork this repo and/or Click on the "Deploy to Heroku" button at the top 16 | * Navigate to the deployed application url 17 | 18 | ## Running the app locally (if you have vertx installed) 19 | ```bash 20 | vertx run server.js 21 | ``` 22 | * Navigate to [http://localhost:9090](http://localhost:9090) 23 | 24 | 25 | ### Build pack 26 | Thanks to the build pack - https://github.com/mthenw/heroku-buildpack-vertx.git for easier heroku deployment :) -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('Leaderboard', []).controller("LeaderboardController", ['$scope', '$http', function ($scope, $http) { 4 | 5 | $http.get('/data').success(function (data) { 6 | $scope.frameworks = data; 7 | }).error(function (error) { 8 | console.error('Error = ', error); 9 | }); 10 | 11 | $scope.addFramework = function () { 12 | var frameworks = angular.copy($scope.frameworks); 13 | frameworks.push({ 14 | name: $scope.framework, 15 | upVote: 1, 16 | downVote: 0 17 | }); 18 | publish(frameworks); 19 | }; 20 | 21 | function addVote(name, vote) { 22 | var frameworks = angular.copy($scope.frameworks); 23 | for (var i = 0; i < frameworks.length; i++) { 24 | if (frameworks[i].name === name) { 25 | frameworks[i][vote]++; 26 | break; 27 | } 28 | } 29 | publish(frameworks); 30 | } 31 | 32 | $scope.upVote = function (framework) { 33 | addVote(framework.name, 'upVote'); 34 | }; 35 | 36 | $scope.downVote = function (framework) { 37 | addVote(framework.name, 'downVote'); 38 | }; 39 | 40 | initConn(function (response) { 41 | $scope.frameworks = response.text; 42 | $scope.$apply(); 43 | }); 44 | }]); -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JavaScript Leaderboard powered by Vert.x & Angular 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 31 | 32 | 33 | 34 |
35 |
36 | 37 |
38 | 41 | 42 |
43 |
44 |

What's your favorite framework?

45 |
46 |
47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 60 | 63 | 64 |
NameUp VoteDown Vote
{{framework.name}}{{framework.upVote}}     58 | 59 | {{framework.downVote}}     61 | 62 |
65 |
66 |
67 | 68 |
69 |
70 |
71 | 73 | 74 |
Add
75 |
76 |
77 |
78 |
79 | 80 | -------------------------------------------------------------------------------- /event-bus.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011-2012 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | var vertx = vertx || {}; 18 | 19 | !function(factory) { 20 | if (typeof define === "function" && define.amd) { 21 | // Expose as an AMD module with SockJS dependency. 22 | // "vertxbus" and "sockjs" names are used because 23 | // AMD module names are derived from file names. 24 | define("vertxbus", ["sockjs"], factory); 25 | } else { 26 | // No AMD-compliant loader 27 | factory(SockJS); 28 | } 29 | }(function(SockJS) { 30 | 31 | vertx.EventBus = function(url, options) { 32 | 33 | var that = this; 34 | var sockJSConn = new SockJS(url, undefined, options); 35 | var handlerMap = {}; 36 | var replyHandlers = {}; 37 | var state = vertx.EventBus.CONNECTING; 38 | var sessionID = null; 39 | var pingTimerID = null; 40 | 41 | that.onopen = null; 42 | that.onclose = null; 43 | 44 | that.login = function(username, password, replyHandler) { 45 | sendOrPub("send", 'vertx.basicauthmanager.login', {username: username, password: password}, function(reply) { 46 | if (reply.status === 'ok') { 47 | that.sessionID = reply.sessionID; 48 | } 49 | if (replyHandler) { 50 | delete reply.sessionID; 51 | replyHandler(reply) 52 | } 53 | }); 54 | } 55 | 56 | that.send = function(address, message, replyHandler) { 57 | sendOrPub("send", address, message, replyHandler) 58 | } 59 | 60 | that.publish = function(address, message, replyHandler) { 61 | sendOrPub("publish", address, message, replyHandler) 62 | } 63 | 64 | that.registerHandler = function(address, handler) { 65 | checkSpecified("address", 'string', address); 66 | checkSpecified("handler", 'function', handler); 67 | checkOpen(); 68 | var handlers = handlerMap[address]; 69 | if (!handlers) { 70 | handlers = [handler]; 71 | handlerMap[address] = handlers; 72 | // First handler for this address so we should register the connection 73 | var msg = { type : "register", 74 | address: address }; 75 | sockJSConn.send(JSON.stringify(msg)); 76 | } else { 77 | handlers[handlers.length] = handler; 78 | } 79 | } 80 | 81 | that.unregisterHandler = function(address, handler) { 82 | checkSpecified("address", 'string', address); 83 | checkSpecified("handler", 'function', handler); 84 | checkOpen(); 85 | var handlers = handlerMap[address]; 86 | if (handlers) { 87 | var idx = handlers.indexOf(handler); 88 | if (idx != -1) handlers.splice(idx, 1); 89 | if (handlers.length == 0) { 90 | // No more local handlers so we should unregister the connection 91 | 92 | var msg = { type : "unregister", 93 | address: address}; 94 | sockJSConn.send(JSON.stringify(msg)); 95 | delete handlerMap[address]; 96 | } 97 | } 98 | } 99 | 100 | that.close = function() { 101 | checkOpen(); 102 | if (pingTimerID) clearInterval(pingTimerID); 103 | state = vertx.EventBus.CLOSING; 104 | sockJSConn.close(); 105 | } 106 | 107 | that.readyState = function() { 108 | return state; 109 | } 110 | 111 | sockJSConn.onopen = function() { 112 | // Send the first ping then send a ping every 5 seconds 113 | sendPing(); 114 | pingTimerID = setInterval(sendPing, 5000); 115 | state = vertx.EventBus.OPEN; 116 | if (that.onopen) { 117 | that.onopen(); 118 | } 119 | }; 120 | 121 | sockJSConn.onclose = function() { 122 | state = vertx.EventBus.CLOSED; 123 | if (that.onclose) { 124 | that.onclose(); 125 | } 126 | }; 127 | 128 | sockJSConn.onmessage = function(e) { 129 | var msg = e.data; 130 | var json = JSON.parse(msg); 131 | var body = json.body; 132 | var replyAddress = json.replyAddress; 133 | var address = json.address; 134 | var replyHandler; 135 | if (replyAddress) { 136 | replyHandler = function(reply, replyHandler) { 137 | // Send back reply 138 | that.send(replyAddress, reply, replyHandler); 139 | }; 140 | } 141 | var handlers = handlerMap[address]; 142 | if (handlers) { 143 | // We make a copy since the handler might get unregistered from within the 144 | // handler itself, which would screw up our iteration 145 | var copy = handlers.slice(0); 146 | for (var i = 0; i < copy.length; i++) { 147 | copy[i](body, replyHandler); 148 | } 149 | } else { 150 | // Might be a reply message 151 | var handler = replyHandlers[address]; 152 | if (handler) { 153 | delete replyHandlers[address]; 154 | handler(body, replyHandler); 155 | } 156 | } 157 | } 158 | 159 | function sendPing() { 160 | var msg = { 161 | type: "ping" 162 | } 163 | sockJSConn.send(JSON.stringify(msg)); 164 | } 165 | 166 | function sendOrPub(sendOrPub, address, message, replyHandler) { 167 | checkSpecified("address", 'string', address); 168 | checkSpecified("replyHandler", 'function', replyHandler, true); 169 | checkOpen(); 170 | var envelope = { type : sendOrPub, 171 | address: address, 172 | body: message }; 173 | if (that.sessionID) { 174 | envelope.sessionID = that.sessionID; 175 | } 176 | if (replyHandler) { 177 | var replyAddress = makeUUID(); 178 | envelope.replyAddress = replyAddress; 179 | replyHandlers[replyAddress] = replyHandler; 180 | } 181 | var str = JSON.stringify(envelope); 182 | sockJSConn.send(str); 183 | } 184 | 185 | function checkOpen() { 186 | if (state != vertx.EventBus.OPEN) { 187 | throw new Error('INVALID_STATE_ERR'); 188 | } 189 | } 190 | 191 | function checkSpecified(paramName, paramType, param, optional) { 192 | if (!optional && !param) { 193 | throw new Error("Parameter " + paramName + " must be specified"); 194 | } 195 | if (param && typeof param != paramType) { 196 | throw new Error("Parameter " + paramName + " must be of type " + paramType); 197 | } 198 | } 199 | 200 | function isFunction(obj) { 201 | return !!(obj && obj.constructor && obj.call && obj.apply); 202 | } 203 | 204 | function makeUUID(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx" 205 | .replace(/[xy]/g,function(a,b){return b=Math.random()*16,(a=="y"?b&3|8:b|0).toString(16)})} 206 | 207 | } 208 | 209 | vertx.EventBus.CONNECTING = 0; 210 | vertx.EventBus.OPEN = 1; 211 | vertx.EventBus.CLOSING = 2; 212 | vertx.EventBus.CLOSED = 3; 213 | 214 | return vertx.EventBus; 215 | 216 | }); 217 | --------------------------------------------------------------------------------