Get source code

APE demo : Move demo

You

About this demo

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.

Which features are used?

  • ServerSide JavaScript :
    • nickname.js
    • move.js
  • JQuery

Study the source code

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 -->
move.js
/**
 * 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(', ')+')';
}
ssjs/move.js
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;
});
demo.css
/* 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
}

Embed and share this demo