Get source code

APE demo : APE real time chat

About this demo

This is a simple chat demo, showing the real-time reactivity of APE. Here, APE is not used as a middleware, and the built-in message queue system is used to enable the communication between users on channels.

Which features are used?

  • ServerSide JavaScript :
    • nickname.js
  • Sessions
  • Mootools

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">
// Add Session.js to APE JSF to handle multitab and page refresh
APE.Config.scripts.push(APE.Config.baseUrl+'/Source/Core/Session.js');

// Initialize APE_Client
var chat = new APE.Chat({'container':$('ape_master_container')});

// Connect to the APE Server
chat.load({
	identifier: 'chatdemo', // Identifier of the application 
	channel: 'chatdemo' // Channel to join at startup
});
</script>
demo.js
APE.Chat = new Class({

	Extends: APE.Client,

	Implements: Options,

	options: {
		container: null,
		logs_limit: 10,
		container: document.body
	},

	initialize: function(options) {
		this.setOptions(options);

		this.els = {};
		this.currentPipe = null;
		this.logging = true;

		this.onRaw('data', this.rawData);
		this.onCmd('send', this.cmdSend);
		this.onError('004', this.reset);

		this.onError('006', this.promptName);
		this.onError('007', this.promptName);

		this.addEvent('load', this.start);
		this.addEvent('ready', this.createChat);
		this.addEvent('uniPipeCreate', this.setPipeName);
		this.addEvent('uniPipeCreate', this.createPipe);
		this.addEvent('multiPipeCreate', this.createPipe);
		this.addEvent('userJoin', this.createUser);
		this.addEvent('userLeft', this.deleteUser);
	},

	promptName: function(errorRaw) {
		this.els.namePrompt = {};
		this.els.namePrompt.div = new Element('form', {
			'class': 'ape_name_prompt',
			'text': 'Choose a nickname : '
		}).inject(this.options.container)
		this.els.namePrompt.div.addEvent('submit', function(ev) {
			ev.stop();
			this.options.name = this.els.namePrompt.input.get('value');
			this.els.namePrompt.div.dispose();
			this.start()
		}.bindWithEvent(this));
		this.els.namePrompt.input = new Element('input', {
			'class': 'text'
		}).inject(this.els.namePrompt.div);
		new Element('input', {
			'class': 'submit',
			'type': 'submit',
			'value': 'GO!'
		}).inject(this.els.namePrompt.div)
		var error;
		if (errorRaw) {
			if (errorRaw.data.code == 007) error = 'This nick is already in use';
			if (errorRaw.data.code == 006) error = 'Bad nick, a nick must contain a-z 0-9 characters';
			if (error) {
				new Element('div', {
					'styles': {
						'padding-top': 5,
						'font-weight': 'bold'
					},
					'text': error
				}).inject(this.els.namePrompt.div);
			}
		}
	},

	start: function() {
		//If name is not set & it's not a session restore ask user for his nickname
		if (!this.options.name && !this.core.options.restore) {
			this.promptName();
		} else {
			var opt = {
				'sendStack': false,
				'request': 'stack'
			};

			this.core.start({
				'name': this.options.name
			}, opt);

			if (this.core.options.restore) {
				this.core.getSession('currentPipe', function(resp) {
					this.setCurrentPipe(resp.data.sessions.currentPipe);
				}.bind(this), opt);
			}

			this.core.request.stack.send();
		}
	},

	setPipeName: function(pipe, options) {
		if (options.name) {
			pipe.name = options.name;
			return;
		}
		if (options.from) {
			pipe.name = options.from.properties.name;
		} else {
			pipe.name = options.pipe.properties.name;
		}
	},

	getCurrentPipe: function() {
		return this.currentPipe;
	},

	setCurrentPipe: function(pubid, save) {
		save = !save;
		if (this.currentPipe) {
			this.currentPipe.els.tab.addClass('unactive');
			this.currentPipe.els.container.addClass('ape_none');
		}
		this.currentPipe = this.core.getPipe(pubid);
		this.currentPipe.els.tab.removeClass('new_message');
		this.currentPipe.els.tab.removeClass('unactive');
		this.currentPipe.els.container.removeClass('ape_none');
		this.scrollMsg(this.currentPipe);
		if (save) this.core.setSession({
			'currentPipe': this.currentPipe.getPubid()
		});
		return this.currentPipe;
	},

	cmdSend: function(data, pipe) {
		this.writeMessage(pipe, data.msg, this.core.user);
	},

	rawData: function(raw, pipe) {
		this.writeMessage(pipe, raw.data.msg, raw.data.from);
	},

	parseMessage: function(message) {
		return decodeURIComponent(message);
	},

	notify: function(pipe) {
		pipe.els.tab.addClass('new_message');
	},

	scrollMsg: function(pipe) {
		var scrollSize = pipe.els.message.getScrollSize();
		pipe.els.message.scrollTo(0, scrollSize.y);
	},

	writeMessage: function(pipe, message, from) {
		//Append message to last message
		if (pipe.lastMsg && pipe.lastMsg.from.pubid == from.pubid) {
			var cnt = pipe.lastMsg.el;
		} else { //Create new one
			//Create message container
			var msg = new Element('div', {
				'class': 'ape_message_container'
			});
			var cnt = new Element('div', {
				'class': 'msg_top'
			}).inject(msg);
			if (from) {
				new Element('div', {
					'class': 'ape_user',
					'text': from.properties.name
				}).inject(msg, 'top');
			}
			new Element('div', {
				'class': 'msg_bot'
			}).inject(msg);
			msg.inject(pipe.els.message);
		}
		new Element('div', {
			'text': this.parseMessage(message),
			'class': 'ape_message'
		}).inject(cnt);

		this.scrollMsg(pipe);

		pipe.lastMsg = {
			from: from,
			el: cnt
		};

		//notify 
		if (this.getCurrentPipe().getPubid() != pipe.getPubid()) {
			this.notify(pipe);
		}
	},

	createUser: function(user, pipe) {
		user.el = new Element('div', {
			'class': 'ape_user'
		}).inject(pipe.els.users);
		new Element('a', {
			'text': user.properties.name,
			'href': 'javascript:void(0)',
			'events': {
				'click': function(ev, user) {
					this.core.getPipe(user.pubid)
					this.setCurrentPipe(user.pubid);
				}.bindWithEvent(this, [user])
			}
		}).inject(user.el, 'inside');
	},

	deleteUser: function(user, pipe) {
		user.el.dispose();
	},

	createPipe: function(pipe, options) {
		var tmp;

		//Define some pipe variables to handle logging and pipe elements
		pipe.els = {};
		pipe.logs = new Array();

		//Container
		pipe.els.container = new Element('div', {
			'class': 'ape_pipe ape_none'
		}).inject(this.els.pipeContainer);

		//Message container
		pipe.els.message = new Element('div', {
			'class': 'ape_messages'
		}).inject(pipe.els.container, 'inside');

		//If pipe has a users list 
		if (pipe.users) {
			pipe.els.usersRight = new Element('div', {
				'class': 'users_right'
			}).inject(pipe.els.container);

			pipe.els.users = new Element('div', {
				'class': 'ape_users_list'
			}).inject(pipe.els.usersRight);;
		}
		//Add tab
		pipe.els.tab = new Element('div', {
			'class': 'ape_tab unactive'
		}).inject(this.els.tabs);

		tmp = new Element('a', {
			'text': pipe.name,
			'href': 'javascript:void(0)',
			'events': {
				'click': function(pipe) {
					this.setCurrentPipe(pipe.getPubid())
				}.bind(this, [pipe])
			}
		}).inject(pipe.els.tab);

		//Hide other pipe and show this one
		this.setCurrentPipe(pipe.getPubid());
	},

	createChat: function() {
		this.els.pipeContainer = new Element('div', {
			'id': 'ape_container'
		});
		this.els.pipeContainer.inject(this.options.container);

		this.els.more = new Element('div', {
			'id': 'more'
		}).inject(this.options.container, 'after');
		this.els.tabs = new Element('div', {
			'id': 'tabbox_container'
		}).inject(this.els.more);
		this.els.sendboxContainer = new Element('div', {
			'id': 'ape_sendbox_container'
		}).inject(this.els.more);

		this.els.sendBox = new Element('div', {
			'id': 'ape_sendbox'
		}).inject(this.els.sendboxContainer, 'bottom');
		this.els.sendboxForm = new Element('form', {
			'events': {
				'submit': function(ev) {
					ev.stop();
					var val = this.els.sendbox.get('value');
					if (val != '') {
						this.getCurrentPipe().send(val);
						this.els.sendbox.set('value', '');
					}
				}.bindWithEvent(this)
			}
		}).inject(this.els.sendBox);
		this.els.sendbox = new Element('input', {
			'type': 'text',
			'id': 'sendbox_input',
			'autocomplete': 'off'
		}).inject(this.els.sendboxForm);
		this.els.send_button = new Element('input', {
			'type': 'button',
			'id': 'sendbox_button',
			'value': ''
		}).inject(this.els.sendboxForm);
	},

	reset: function() {
		this.core.clearSession();
		if (this.els.pipeContainer) {
			this.els.pipeContainer.dispose();
			this.els.more.dispose();
		}
		this.core.initialize(this.core.options);
	}
});
ssjs/nickname.js
var userlist = new $H;

Ape.registerHookCmd("connect", function(params, cmd) {

	if (!$defined(params.name)) return 0;
	if (userlist.has(params.name.toLowerCase())) return ["005", "NICK_USED"];
	if (params.name.length > 16 || params.name.test('[^a-zA-Z0-9]', 'i')) return ["006", "BAD_NICK"];


	cmd.user.setProperty('name', params.name);

	return 1;
});

Ape.addEvent('adduser', function(user) {
	userlist.set(user.getProperty('name').toLowerCase(), true);
});

Ape.addEvent('deluser', function(user) {
	userlist.erase(user.getProperty('name').toLowerCase());
});
demo.css
#ape_message_container a {
	outline:none;
}

#more,#ape_container {
	width:699px;
	margin:auto;
}

#ape_container {
	padding-top:25px;
	height:385px;
	border:1px solid #bbbdbe;
	border-right:0;
	background:url(Images/demochat_top.png) repeat-x;
}

#ape_sendbox_container {
	width:699px;
}

#ape_sendbox {
	width:100%;
	height:38px;
	background:url(Images/sendboxbg.png) no-repeat;
}

#ape_sendbox form {
	margin:0;
}

#tabbox_container {
	width:525px;
	position:absolute;
	margin-top:-24px;
	margin-left:10px;
	margin:-25px 0 0 5px;
}

#sendbox_input {
	background:none;
	border:none;
	float:left;
	margin-left:12px;
	margin-top:11px;
	width:607px;
	padding:0;
	margin-bottom:0;
}

#sendbox_button {
	border:none;
	width:72px;
	padding-top:5px;
	height:38px;
	background:url(Images/sendbutton.png) no-repeat;
	float:right;
	display:block;
}

.ape_users_list {
	background:url(Images/users_bg.png) repeat-x right #656f7c;
	margin-left:4px;
	width:165px;
	height:380px;
	overflow:auto;
	padding-top:5px;
}

.users_right {
	float:right;
	width:170px;
	background:url(Images/users_left.png) repeat-y top left;
	height:385px;
}

.ape_users_list .ape_user {
	width:133px;
	line-height:25px;
	background:url(Images/userback_users.png) no-repeat;
	height:25px;
	padding-left:15px;
	font-weight:700;
	font-size:13px;
	margin-bottom:5px;
	margin-left:10px;
}

.ape_users_list .ape_user a {
	color:#000!important;
	text-decoration:none;
}

.ape_none {
	display:none;
}

.ape_pipe {
	background:url(Images/buffer_bg.png) repeat-x #eef1f7;
	width:100%;
	height:385px;
}

.ape_messages {
	float:left;
	height:358px;
	overflow:auto;
	width:528px;
}

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

.ape_message {
	padding-left:18px;
	background:url(Images/msg_mid.png) 0 5px repeat-y;
	color:#646465;
	overflow:hidden;
}

.ape_message_container .ape_user {
	margin-bottom:4px;
}

.ape_message_container .ape_user a {
	color:#656f7c;
	font-size:13px;
	font-weight:700;
	text-decoration:none;
}

.msg_bot {
	height:8px;
	background:url(Images/msg_bot.png);
}

.ape_message_container {
	white-space:normal;
	position:relative!important;
	width:479px;
	margin-top:5px;
	margin-left:10px;
}

.ape_tab a {
	color:#545454;
	font-size:12px;
	text-decoration:none;
}

.ape_tab.new_message {
	font-weight:700;
}

.ape_tab {
	float:left;
	background:#e9ebed;
	border:1px solid #bbbdbe;
	border-bottom:none;
	margin-top:1px;
	height:23px;
	line-height:20px;
	padding:0 8px;
}

.ape_tab.unactive {
	border-bottom:0;
	margin-top:2px;
	height:21px;
}

.ape_name_prompt {
	-moz-border-radius-bottomleft:5px;
	-moz-border-radius-bottomright:5px;
	-moz-border-radius-topleft:5px;
	-moz-border-radius-topright:5px;
	-webkit-border-bottom-left-radius:5px;
	-webkit-border-bottom-right-radius:5px;
	-webkit-border-top-left-radius:5px;
	-webkit-border-top-right-radius:5px;
	background-color:#d4e1eb;
	padding:10px;
	color:#000;
	width:700px;
	margin:auto;
	text-align:center;
	font-size:14px;
}

.ape_name_prompt input.submit {
	border:1px solid #DDD;
	margin-left:15px;
}

.ape_name_prompt input.text {
	-moz-border-radius-bottomleft:5px;
	-moz-border-radius-bottomright:5px;
	-moz-border-radius-topleft:5px;
	-moz-border-radius-topright:5px;
	-webkit-border-bottom-left-radius:5px;
	-webkit-border-bottom-right-radius:5px;
	-webkit-border-top-left-radius:5px;
	-webkit-border-top-right-radius:5px;
	border:1px solid #DDD;
	font-size:12px;
	margin:0;
	padding:2px;
	font-weight:700;
	text-align:center;
	width:180px;
}

Embed and share this demo

Share: