Get source code

APE demo : Move demo

About this demo

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 libape-setcord server-side module has been created to handle the coordinates of the users.

Which features are used?

  • nickname.js
  • move.js
  • Sessions
  • ServerSide JavaScript

Study the source code

Check out the Client JavaScript, HTML and ServerSide JavaScript source code of this demo by reading the following :

<div id="ape_master_container"></div>

<script type="text/javaScript">
	//Create random string used as user nickname
	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;
	}
	window.addEvent('domready', function() {
		var client = new APE.Move({
			'container': document.getElementById('ape_master_container'),
		});
		client.load({
			'identifier': 'movedemo',
			'channel': 'move',
			'connectOptions': {'name': rand_chars(5)}
		});
	});
</script>
move.js
APE.Move = new Class({
	Extends: APE.Client,
	Implements: Options,
	options: {
		container: document.body
	},
	initialize: function(options) {
		this.setOptions(options);
		this.addEvent('ready', this.initPlayground);
		this.addEvent('userJoin', this.createUser);
		this.addEvent('multiPipeCreate', function(pipe, options) {
			this.pipe = pipe;
		});
		this.addEvent('userLeft', this.deleteUser);
		this.onRaw('positions', this.rawPositions);
		this.onRaw('data', this.rawData);
		this.onCmd('send', this.cmdSend);
		this.onError('004', this.reset);
	},
	deleteUser: function(user, pipe) {
		user.element.dispose();
	},
	cmdSend: function(param, pipe) {
		this.writeMessage(pipe, param.msg, this.core.user);
	},
	rawData: function(raw, pipe) {
		this.writeMessage(pipe, raw.data.msg, raw.data.from);
	},
	rawPositions: function(raw, pipe) {
		this.movePoint(raw.data.from, raw.data.x, raw.data.y);
	},
	parseMessage: function(message) {
		return decodeURIComponent(message);
	},
	writeMessage: function(pipe, message, sender) {
		//Define sender
		sender = this.pipe.getUser(sender.pubid);
		//destroy old message
		sender.element.getElements('.ape_message_container').destroy();
		//Create message container
		var msg = new Element('div', {
			'opacity': '0',
			'class': 'ape_message_container'
		});
		var cnt = new Element('div', {
			'class': 'msg_top'
		}).inject(msg);
		//Add message
		new Element('div', {
			'text': this.parseMessage(message),
			'class': 'ape_message'
		}).inject(cnt);
		new Element('div', {
			'class': 'msg_bot'
		}).inject(cnt);
		//Inject message
		msg.inject(sender.element);
		//Show message
		var fx = msg.morph({
			'opacity': '1'
		});
		//Delete old message
		(function(el) {
			$(el).morph({
				'opacity': '0'
			});
			(function() {
				$(this).dispose();
			}).delay(300, el);
		}).delay(3000, this, msg);
	},
	createUser: function(user, pipe) {
		if (user.properties.x) {
			var x = user.properties.x;
			var y = user.properties.y;
		} else {
			var x = 8;
			var y = 8;
		}
		var pos = this.element.getCoordinates();
		x = x.toInt() + pos.left;
		y = y.toInt() + pos.top;
		user.element = new Element('div', {
			'class': 'demo_box_container',
			'styles': {
				'left': x + 'px',
				'top': y + 'px'
			}
		}).inject(this.element, 'inside');
		new Element('div', {
			'class': 'user',
			'styles': {
				'background-color': 'rgb(' + this.userColor(user.properties.name) + ')'
			}
		}).inject(user.element, 'inside');
		var span = new Element('span', {
			'text': user.properties.name
		}).inject(user.element, 'inside');
		if (user.pubid == this.core.user.pubid) {
			span.addClass('you');
			span.set('text', 'You');
			if (!this.core.options.restore) {
				var offset = this.element.getPosition();
				this.sendpos($random(0, 640) + offset.x, $random(0, 300) + offset.y);
			}
		}
	},
	userColor: function(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 color.join(', ');
	},
	sendpos: function(x, y) {
		var pos = this.posToRelative(x, y);
		this.pipe.request.send('SETPOS', {
			'x': pos.x.toInt(),
			'y': pos.y.toInt()
		});
		this.movePoint(this.core.user, pos.x, pos.y);
	},

	posToRelative: function(x, y) {
		var pos = this.element.getCoordinates();
		x = x - pos.left - 36;
		y = y - pos.top - 46;
		if (x < 0) x = 10;
		if (x > pos.width) x = pos.width - 10;
		if (y < 0) y = 10;
		if (y > pos.height) y = pos.height - 10;
		return {
			'x': x,
			'y': y
		};
	},
	movePoint: function(user, x, y) {
		var user = this.pipe.getUser(user.pubid);
		var el = user.element;
		var fx = el.retrieve('fx');
		if (!fx) {
			fx = new Fx.Morph(el, {
				'duration': 300,
				'fps': 100
			});
			el.store('fx', fx);
		}
		el.retrieve('fx').cancel();
		pos = this.element.getCoordinates();
		x = x.toInt();
		y = y.toInt();
		//Save position in user properties
		user.properties.x = x;
		user.properties.y = y;
		fx.start({
			'left': pos.left + x,
			'top': pos.top + y
		});
	},
	initPlayground: function() {
		this.element = this.options.container;
		this.els = {};
		this.els.move_box = new Element('div', {
			'class': 'move_box'
		}).inject(this.element);
		this.els.move_box.addEvent('mousedown', function(ev) {
			ev.stop();
			this.sendpos(ev.page.x, ev.page.y);
		}.bindWithEvent(this));
		var el1 = new Element('div', {
			'id': 'moveOverlay',
			'styles': {
				'opacity': 0.5
			}
		}).inject(this.element, 'top');
		var el2 = new Element('div', {
			'id': 'moveOverlay',
			'styles': {
				'background': 'none',
				'z-index': 6
			},
			'text': 'Click on the grey area to move your ball'
		}).inject(this.element, 'top');
		var clear = function() {
				el1.fade('out');
				el2.fade('out');
				el1.get('morph').addEvent('complete', function() {
					el1.dispose();
					el2.dispose();
				});
			};
		el2.addEvent('click', function() {
			el1.destroy();
			el2.destroy();
		});
		clear.delay(1500);
		this.els.more = new Element('div', {
			'id': 'more'
		}).inject(this.element, 'inside');
		this.els.sendboxContainer = new Element('div', {
			'id': 'ape_sendbox_container'
		}).inject(this.els.more);
		this.els.sendBox = new Element('div', {
			'text': 'Say : ',
			'id': 'ape_sendbox'
		}).inject(this.els.sendboxContainer, 'bottom');
		this.els.sendbox = new Element('input', {
			'type': 'text',
			'id': 'sendbox_input',
			'autocomplete': 'off',
			'events': {
				'keypress': function(ev) {
					if (ev.code == 13) {
						var val = this.els.sendbox.get('value');
						if (val != '') {
							this.pipe.send(val);
							$(ev.target).set('value', '');
						}
					}
				}.bind(this)
			}
		}).inject(this.els.sendBox);
		this.els.sendButton = new Element('input', {
			'type': 'button',
			'id': 'sendbox_button',
			'value': 'Send',
			'events': {
				'click': function() {
					var val = this.els.sendbox.get('value');
					if (val != '') {
						this.pipe.send(val);
						$(ev.target).set('value', '');
					}
				}.bind(this)
			}
		}).inject(this.els.sendBox);
	},
	reset: function() {
		this.core.clearSession();
		if (this.element) {
			this.element.empty();
		}
		this.core.initialize(this.core.options);
	}

});
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;

	infos.user.setProperty('x', params.x);
	infos.user.setProperty('y', params.y);

	var chan = Ape.getChannelByPubid(params.pipe);

	if (chan) {
		chan.pipe.sendRaw('positions', {
			'x': params.x,
			'y': params.y
		}, {
			'from': infos.user.pipe,
			'restrict': infos.subuser
		});
	} else {
		return ['109', 'UNKNOWN_PIPE'];
	}

	return 1;
});
demo.css
#more,#ape_master_container {
	width:685px;
	margin:auto;
}

#ape_master_container {
	height:385px;
	background:url(Images/bg.png);
	border:8px solid #4c4c4c;
}

#sendbox_input {
	border:none;
	font-weight:700;
	width:540px;
	margin-right:10px;
	border:none;
	background:none;
	color:#FFF;
}

.demo_box_container .user {
	width:100%;
	height:100%;
}

.demo_box_container {
	width:36px;
	height:36px;
	position:absolute;
}

.demo_box_container .user {
	background:url(Images/round.png);
	-background-image:none;
	filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true,sizingMethod=crop src='Images/round.png');
}

.demo_box_container span {
	color:#fff;
	font-size:14px;
}

#ape_sendbox_container {
	border-left:4px #FFF;
	background:url(Images/bottom.png);
	width:686px;
	margin-left:-8px;
	margin-top:385px;
	height:49px;
	margin-top:345px;
	color:#FFF;
	font-weight:700;
	padding-left:15px;
	line-height:51px;
	font-size:13px;
}

#sendbox_button {
	color:#d58025;
	width:70px;
	height:35px;
	background:none;
	font-weight:700;
	border:none;
}

.ape_message_container {
	white-space:normal;
	width:244px;
	margin-left:21px;
	margin-top:-77px;
}

.msg_top {
	background:url(Images/msg_top.png) no-repeat;
	padding-top:5px;
	height:17px;
}

.ape_message {
	background:url(Images/msg_mid.png) repeat-y;
	padding-left:24px;
	overflow:hidden;
	font-size:16px;
	line-height:20px;
	font-family:Verdana;
	color:#5f5f60;
}

.msg_bot {
	background:url(Images/msg_bot.png) repeat-y;
	height:9px;
}

.move_box {
	height:345px;
	width:682px;
	position:absolute;
}

Embed and share this demo