今天是星期五,周末又到了,又是一个人在家路代码的黄金时期,这次给大家分享一个用js做的在线聊天界面。
1 参数设置
var jid = ""; // 当前登录的JID
var current_target_name = ''; // 当前聊天对象
var last_target_name = ''; // 上次聊天对象
var max_msg_count = 50; // 最大储存信息数量
var isFirst = true; // 是否首次登入
//通讯录
var friends_name_arr = [];//name1,name2... 这个数组是装好友名字的 ['张三',‘李四’]
//记录好友上线状态
var online_friends_arr = [];//{name1,status1},{name2,status2} .... 对象包括名字和状态(在线,离线)
// 在线好友和离线好友的数组
var online_friends_names = [];
var offline_friends_names = [];
//登陆用户与密码
var username_str=''; // 这是啥 后面会解释
var username = '';
var password = '';
//消息盒子 用于保存离线消息(不在线时,受到的好友信息会放到这里)
var message_box = [];
相信通过上面的参数大家已经看出来了这是哪一处戏,无非就是一个即使聊天的工具。
2 Html页面
<!-- 好友列表区域 -->
<div class="user-list-canvas">
<!-- 搜索区域 -->
<div class="search-canvas">
<div class="search-img"></div>
<input class="search-tool" type="text" placeholder="搜索">
</div>
<!-- 列表区域 -->
<ul class="list-canvas">
</ul>
<script type="text/template" id="friend-row">
<li class="friend-canvas {name}"> // 在每个li上标记该好友的名字
<div class="friend-logo">
<span class="friend-logo-lab">{logo}</span>
<span class="noread-num num-{noReadNum}">{noReadNum}</span> //未读消息数量
</div>
<div class="friend-info">
<span class="username" style="display: none">{name}</span>
<span class="nickName">{nickName}</span> //昵称
<span class="last-message">{lastMessage}</span> //受到的最后一条消息
</div>
</li>
</script>
</div>
- .search-tool是一个搜索框,可以进行选择。
- .list-canvas 是好友列表
可能有人看不懂这个script里面写的是啥,其实就是一个简单的模板用于渲染数据,后面会说明。
<!-- 聊天区域 -->
<div class="chat-content-canvas">
<div class="chat-content-title">
<!-- 记录当前聊天对象 -->
<div class="current-name"></div>
<div class="status-logo" style="display: none;"></div>
<div class="current-status" style="display: none;"></div>
<div class="clear-content"></div>
</div>
<!-- 记录当前聊天内容 -->
<div class="chat-content-panel">
</div>
<!-- 发出信息模板 -->
<script type="text/template" id="send-message-model">
<div class="message-row">
<div class="send-message-style">{txt}</div>
<div class="message-info send-message-info">
<span>{date}</span>
<span></span>
<div>
</div>
</script>
<!-- 接收信息模板 -->
<script type="text/template" id="receive-message-model">
<div class="message-row">
<div class="receive-message-style">{txt}</div>
<div class="message-info receive-message-info">
<span>{date}</span>
<span></span>
<div>
</div>
</script>
</div>
<!-- 书写区域 -->
<div class="send-content-canvas">
<textarea class="message-canvas" placeholder="请输入要发送的信息"></textarea>
<div class="send-button">发送</div>
</div>
</div>
.send-content-canvas是书写区域,点击发送会把消息发出去,渲染到发送消息的模板。
好了,页面就是这样的,已经很简洁了,下面来看功能实现的代码。
3 Js代码
// 连接状态改变的事件
function onConnect(status) {
if (status == Strophe.Status.CONNFAIL) {
alert("连接失败!");
} else if (status == Strophe.Status.AUTHFAIL) {
alert("登录失败!");
} else if (status == Strophe.Status.DISCONNECTED) {
loginOutFunc();
} else if (status == Strophe.Status.CONNECTED) {
loginInFunc();
}
}
上面展示了几种状态的函数执行,我们来看看登录后如何操作。
//用户登陆
function loginInFunc(){
connected = true;
connection.send($pres().tree());
$('#btn-login').html('断开'); //这是一个切换按钮,用于登录和登出。
$("#chat-panel").show(); // 聊天面板展开
$("#loading-background").hide(); //loading效果消失
// 当接收到<message>节,调用onMessage回调函数
connection.addHandler(onMessage, null, 'message', null, null, null);
// 当接收到<presence>节,调用onPresenceOn回调函数
connection.addHandler(onPresenceOn, null, 'presence', null, null, null); **这2步先不管**
//上线状态
changeOnlineStatus('online');
//获取好友列表
get_roster();
}
登录后会拉取好友信息渲染,简单说渲染之后就会直到那些好友在线,那些不在线。
// 好友列表
var friend_list_data = []; 里面存放的是person
var friend_arr = [];
function onRoster(iq) {
$(".list-canvas").html('');
friends_name_arr = [];
friend_list_data = [];
$(iq).find('item').each(function () {
var jid = $(this).attr('jid'),
// name = $(this).attr('name')!=undefined ? $(this).attr('name') : jid.substring(0,jid.indexOf('@')),
name = jid.substring(0,jid.indexOf('@')),
nickName = $(this).attr('name')?$(this).attr('name'):name;
//构建了一个person对象
var person = new Person(name.charAt(0).toUpperCase() , name , nickName , 'offline');
$.each(message_box, function(index, val) {
if(val.from == name){
person.addNoReadNum();
}
});
friend_list_data.push(person);
//记录好友name数组
friends_name_arr.push(name);
});
//数据都是通过iq传递过来的
//初始化,获取未上线的好友名称数组
$.each(friends_name_arr, function(index, val) {
if(online_friends_names.indexOf(val) == -1){
offline_friends_names.push(val);
}
});
offline_friends_names.sort();//排序
//填充好友
fillFriendTemplate();
update_friend_status();
return true;
}
$.each(message_box, function(index, val) {
if(val.from == name){
person.addNoReadNum();
}
}); 遍历离线消息,如果该名字相等,这个人的未读信息加1.
$.each(friends_name_arr, function(index, val) {
if(online_friends_names.indexOf(val) == -1){
offline_friends_names.push(val);
}
}); 将所有好友名单,分成online和offline的。
问题来了:现在我们已经获得在线的friend_list_data和friend_name_arr,那么如何将它渲染到页面上呢,还有好友的的状态会改变的,在线的会下线,离线的会上线,如何实现实时监测。
先来看模板渲染把
//填充模板 填充好友模板
//friend_list_data ==> Person {logo: "A", name: "alice", status: "offline", lastMessage: "", message_arr: Array[0]}
function fillFriendTemplate(){
// 获取好友模板html
var html = $("#friend-row").html();
friend_arr = [];
//对数组进行排序 按名字进行排序 by是一个排序函数 这里不贴出来了
friend_list_data.sort(by("name"));
$.each(friend_list_data, function(index, val) {
friend_arr.push(formatTemplate(val , html));
});
// 这一步把.list-canvas的好友列表渲染出来了
$('.list-canvas').append(friend_arr.join(''));
//渲染最后一条信息
$.each(friends_name_arr, function(index, val) {
var key = val<username_str ? (val+"_"+username_str) : (username_str+"_"+val);
var messages = getLocalMessages(key);
if(messages){
$("."+val).find('.last-message').html(messages[0].txt);
}
});
}
我们来看看formatTemplate干了啥
function formatTemplate(dta, tmpl) {
var format = {
name: function(x) {
return x
}
};
return tmpl.replace(/{(\w+)}/g, function(m1, m2) {
if (!m2)
return "";
return (format && format[m2]) ? format[m2](dta[m2]) : dta[m2];
});
}
这是一个很好的模板替换函数,还记得之前的script里面写html吗,就是通过这种方式替换的。
$.each(friends_name_arr, function(index, val) {
var key = val<username_str ? (val+""+username_str) : (username_str+""+val);
var messages = getLocalMessages(key);
if(messages){
$("."+val).find('.last-message').html(messages[0].txt);
}
}); 这一步是把每个好友和自己最后一次聊天的内容放到.last-message里面
这里我们用到了localstorage缓存,看看是如何写的把
//获取本地消息
function getLocalMessages(name){
return JSON.parse(localStorage.getItem(name));
}
非常简单的一段代码
接下来我们看看怎么实现上下线的监听,有点像qq呀
//监听presence
function onPresenceOn(pres){
var from_jid = $(pres).attr('from');
var name = from_jid.substring(0,from_jid.indexOf('@'));
var status = $(pres).find('status').text();
//好友下线
if($(pres).attr('type') == "unavailable"){
********
}else{
********
}
return true;
}
还记得这段代码么
// 当接收到<presence>节,调用onPresenceOn回调函数
connection.addHandler(onPresenceOn, null, 'presence', null, null, null);
就是在这个代码里面我们能听到qq里面的敲门声。其实就是监听好友上下线的。。
//好友下线
if($(pres).attr('type') == "unavailable"){
var dom_str = '.'+name;
//修改好友状态-下线
update_status_action(name , 'offline');
//更新上下线数组
if(offline_friends_names.indexOf(name) == -1){
online_friends_names.splice(online_friends_names.indexOf(name) , 1);
offline_friends_names.push(name);
offline_friends_names.sort();
}
if($(dom_str).hasClass('selected-friend')){
$(dom_str).removeClass('selected-friend');
}
上面是这种情况,当前选中的好友的如果下线,会把select-friend类去掉。
/好友上线
online_friends_arr.push({'name':name , 'status':status});
//更新上下线数组
if(online_friends_names.indexOf(name) == -1 && (name!=username_str)){
offline_friends_names.splice(offline_friends_names.indexOf(name) , 1);
//记录上线好友名称
online_friends_names.push(name);
online_friends_names.sort();//排序
}
update_status_action(name,status);
还记得我们之前提到的messagebox把,它记录了所有的离线消息,那么设想这样一个情景:
好友A不在线,我给他发了一条信息,那么这条信息将会被存入到messagebox,这就是所谓的离线缓存。继续看
message_box = [{from:from_name , to:to_name , txt:txt_msg , date:date}] 谁发的,发给谁,信息内容,时间
function onMessage(msg) {
// 解析出<message>的from、type属性,以及body子元素
var from = msg.getAttribute('from');
var from_name = from.substring(0,from.indexOf('@'));
var to = msg.getAttribute('to');
var to_name = to.substring(0,to.indexOf('@'));
var type = msg.getAttribute('type');
var elems = msg.getElementsByTagName('body');
var body = elems[0];
var txt_msg = Strophe.getText(body);
var date = new Date().format("yyyy-MM-dd hh:mm:ss");
//离线 保存至信息箱
if(type == "chat"){
if(friend_list_data.length == 0){
message_box.push({from:from_name , to:to_name , txt:txt_msg , date:date});
}else{
if(elems.length > 0){
var person = findPerson(from_name);
person.message_arr.push({from:from_name , to:to_name , txt:txt_msg , date:date});
//消息已收到
connection.send($msg({to: from}).c('received', {xmlns: 'urn:xmpp:receipts'}));
}
//判断是否是当前聊天窗口对象发送过来的信息
update_message_action({from:from_name , to:to_name , txt:txt_msg , date:date} , (current_target_name===from_name?true:false));
}
localSaveMessage({from:from_name , to:to_name , txt:txt_msg , date:date});
}else{
// 信息反馈
}
return true;
}
onMessage这个东西很眼熟把,就像socket接收到message一样。
var from = msg.getAttribute('from');
var from_name = from.substring(0,from.indexOf('@')); 发送消息人的name
var to = msg.getAttribute('to');
var to_name = to.substring(0,to.indexOf('@')); 发给谁
var type = msg.getAttribute('type'); // 消息发送类型
var elems = msg.getElementsByTagName('body');
var body = elems[0];
var txt_msg = Strophe.getText(body); // 消息内容
var date = new Date().format("yyyy-MM-dd hh:mm:ss");
上面代码有一处位置是错的
if(friend_list_data.length == 0){
message_box.push({from:from_name , to:to_name , txt:txt_msg , date:date});
}
好友列表为0的时候,往离线盒子推送,显然是错的。这里应该判断to_name的状态是否离线,离线就发送到离线邮箱。
// 查找好友(非搜索)
function findPerson(name){
var idx = -1;
$.each(friend_list_data, function(index, val) {
if(val.name == name){
idx = index;
}
});
return friend_list_data[idx];
}
if(findPerson(to_name).getStatus() == 'offline'){
message_box.push({from:from_name , to:to_name , txt:txt_msg , date:date});
}
如果是在线好友,就不用发送到离线信箱,进行下面的操作即可。
//消息渲染
function update_message_action(msg , flag){
var html = '';
// flag判断消息是否来自当前聊天对象
if(flag){
// 发出信息 send-message-model
if(msg.from == username_str){
html = $('#send-message-model').html();
var msg_context = formatTemplate(msg , html);
$('.chat-content-panel').append(msg_context);
$("."+msg.to).find('.last-message').html(msg.txt); // 发给谁,谁的最后一条消息就是这个
}else{
// 收到信息 receive-message-model
html = $('#receive-message-model').html();
var msg_context = formatTemplate(msg , html);
$('.chat-content-panel').append(msg_context);
$("."+msg.from).find('.last-message').html(msg.txt);
}
//滚动条置底
$('.chat-content-panel').get(0).scrollTop = $('.chat-content-panel').get(0).scrollHeight;
}else{
var person = findPerson(msg.from);
person.addNoReadNum();
$("."+msg.from).find('.noread-num').html(person.getNoReadNum());
$('.'+person.name).find('.noread-num').removeClass('num-0');
$("."+msg.from).find('.last-message').html(msg.txt);
}
}
无论发送还是接收,$('.chat-content-panel')都会追加内容。由于写作匆忙,很多位置还未更正,下回我会同步到github上,供大家下载。