自制聊天系统

今天是星期五,周末又到了,又是一个人在家路代码的黄金时期,这次给大家分享一个用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页面

 
        
  1. .search-tool是一个搜索框,可以进行选择。
  2. .list-canvas 是好友列表
    可能有人看不懂这个script里面写的是啥,其实就是一个简单的模板用于渲染数据,后面会说明。

        
发送

.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效果消失

    // 当接收到节,调用onMessage回调函数  
    connection.addHandler(onMessage, null, 'message', null, null, null);
    // 当接收到节,调用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

我们来看看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) : (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;
}

还记得这段代码么

// 当接收到节,调用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) {
    // 解析出的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上,供大家下载。

你可能感兴趣的:(自制聊天系统)