Click in the grey area to move your ball. Open a new browser window to see your ball move in real-time!
This demo shows how APE can handles massive multi-user moving on a web page in real-time. This demo does not use APE as a middleware. A custom server-side module has been created to record the coordinates of the users as a public user properties. This allow APE to place to know where existing users are currently placed when initializing.
To go further with this demo, you could try enabling sessions. With sessions enable, if you have two browser windows open, both will share the same ball and it will move in real-time in both windows when the grey area is clicked.
Check out the Client JavaScript, HTML and ServerSide JavaScript source code of this demo by reading the following :
<!-- BEGIN DEMO CODE --> <link rel="stylesheet" href="/demos/move/css/move.css"> <script type="text/javaScript" src="/assets/apejsf/src/apeClient.js"></script> <script type="text/javaScript" src="/assets/apejsf/apeConfig.js"></script> <script type="text/javaScript" src="/demos/move/js/move.js"></script> <div id="APE_MoveContainer"> <div id="APE_User" class="APE_User"> <div class="circle"></div> <div class="name">You</div> </div> </div> <script type="text/javascript"> /** * This handle the even when the master Container is clicked. * This will get the new coordinates and send them to APE Server */ $('#APE_MoveContainer').click(function(e) { //Calculate the position. Do the math to make sure the object doesn't get out of the frame var posX = Math.max(0, Math.min(e.pageX - $(this).offset().left - $('#APE_User').width() / 2, $('#APE_MoveContainer').width() - $('#APE_User').width())); var posY = Math.max(0, Math.min(e.pageY - $(this).offset().top - ( $('#APE_User').find('.circle').height() / 2 ), $('#APE_MoveContainer').height() - $('#APE_User').height())); //Send the new coordinate to APE Server using the custom 'SETPOS' command movePipeId.request.send('SETPOS', {'x': posX, 'y': posY}); }); //Instantiate APE Client var client = new APE.Client(); //Some vars to keep track of things var movePipeId; var MyPubId; //Load APE Core and connect to the 'move' channel client.load({ 'identifier': 'movedemo', 'channel': 'move', }); //Intercept 'load' event. This event is fired when the Core is loaded and ready to connect to APE Server client.addEvent('load', function() { //Call core start function to connect to APE Server using a random name client.core.start({"name":rand_chars(5)}); }); //Listen to the ready event to know when your client is connected client.addEvent('ready', function() { //1) Intercept multiPipeCreate event client.addEvent('multiPipeCreate', function(pipe, options) { //We are now connected to a Channel movePipeId = pipe; //Store the channel pipe so we can send command to it later MyPubId = pipe.ape.user.pubid; //Store our user pubid }); //2) Intercept the reception of new message. client.onRaw('positions', function(raw, pipe) { //Move the sender to the specified coordinates $("#"+raw.data.from.pubid).animate({ left: raw.data.x, top: raw.data.y }, 'fast'); }); //3) Intercept the creation of a new user. // N.B.: Our user will be created here with the others client.addEvent('userJoin', function(user, pipe) { //Add the new user to the DOM using the helper function createUser(user.pubid, user.properties.name, user.properties.x, user.properties.y); }); //4) Intercept when a user has left the channel. client.addEvent('userLeft', function(user, pipe) { //We delete the user from the DOM $("#APE_MoveContainer").find("#"+user.pubid).remove(); }); }); /** * createUser function. * * @access public * @param mixed userPubId * @param mixed username * @param mixed posX * @param mixed posY * @return void */ function createUser(userPubId, username, posX, posY) { //Duplicate the user div template and set the id tag var temp = $("#APE_User").clone().attr("id", userPubId); //Set the duplicate color $(temp).find('.circle').css('background-color', userColor(username)); //Set the duplicate name. We show "YOU" is it is the user div if (userPubId == MyPubId) { $(temp).find('.name').text('YOU'); } else { $(temp).find('.name').text(username); } //The div is hidden by default. We show it. $(temp).show(); //Append the duplicate to the master div $('#APE_MoveContainer').append(temp); //Set the duplicate position $("#"+userPubId).animate({ left: posX, top: posY }, 'fast'); } </script> <!-- END DEMO CODE -->
/** * rand_chars function. * This function is used to create a unique * username of a given lenght. * * @access public * @param mixed plength * @return void */ function rand_chars(plength) { var keylist = "abcdefghijklmnopqrstuvwxyz"; var temp = ''; for (i = 0; i < plength; i++) { temp += keylist.charAt(Math.floor(Math.random() * keylist.length)); } return temp; } /** * userColor function. * This function is used to determine * the user color based on the first character * of his nickname. * * @access public * @param mixed nickname * @return void */ function userColor(nickname) { var color = new Array(0, 0, 0); var i = 0; while (i < 3 && i < nickname.length) { color[i] = Math.abs(Math.round(((nickname.charCodeAt(i) - 97) / 26) * 200 + 10)); i++; } return 'rgb('+color.join(', ')+')'; }
Ape.registerCmd('setpos', true, function(params, infos) { if ((!$defined(params.x) || !$defined(params.y)) && (!isFinite(params.x) || !isFinite(params.y))) return 0; //Set the user properties for the new coordinates infos.user.setProperty('x', params.x); infos.user.setProperty('y', params.y); //Get the channel where we need to send the callback var chan = Ape.getChannelByPubid(params.pipe); //Test if channel exist if (chan) { //Send a raw to the channel pipe. chan.pipe.sendRaw('positions', {'x': params.x, 'y': params.y}, {'from': infos.user.pipe}); //The raw won't be sent to our user since the 'from' parameter is used. But we still want our //user to get the raw (and we need 'from'). So we send also only to our user who sent the command infos.user.pipe.sendRaw('positions', {'x': params.x, 'y': params.y}, {'from': infos.user.pipe}); } else { return ['109', 'UNKNOWN_PIPE']; } return 1; });
/* Frame in which the ball can move */ #APE_MoveContainer { /* Set the frame design */ border: 1px solid black; background-color: lightgrey; width: 650px; height: 350px; cursor: pointer; /* Set this so the ball move inside the frame */ position: relative; } /* Overall ball */ .APE_User { /* Set this so the ball move inside the frame */ position: absolute; /* So the circle doesn't get ou of the frame */ min-width: 30px; /* User is hidden until APE is ready */ display: none; } .APE_User .circle { /* Create the circle */ width: 30px; height: 30px; border-radius: 15px; /* Set the style */ box-shadow: rgba(0, 0, 0, 0.3) 0 1px 3px; /* Set the default color */ background-color: yellow; /* Set the position inside the ball frame */ position: absolute; left: 50%; margin-left: -15px; } .APE_User .name { /* Center the text */ font-size: 12px; width: 100%; text-align: center; /* So the circle doesn't overlap */ margin-top: 35px }