view media/js/openwebchat.js @ 99:b9cdccd5fbe4 default tip

Added unit test (doctest).
author Atul Varma <varmaa@toolness.com>
date Sat, 02 May 2009 08:13:45 -0700
parents 3be28af79baf
children
line wrap: on
line source

var Utils = {
  deepCopy: function deepCopy(obj) {
    return JSON.parse(JSON.stringify(obj));
  },

  parseJsonDate: function parseJsonDate(str) {
    var regexp = /(\d+)-(\d+)-(\d+)T(\d+):(\d+):(\d+)(.*)/;
    var match = str.match(regexp);
    var year = Number(match[1]);
    var month = Number(match[2]) - 1;
    var date = Number(match[3]);
    var hrs = Number(match[4]);
    var min = Number(match[5]);
    var sec = Number(match[6]);
    return new Date(Date.UTC(year, month, date, hrs, min, sec));
  }
};

var OpenWebStorage = {
  UnsupportedError: function UnsupportedError() {},

  FakeStorage: function FakeStorage() {
    var contents = {};

    this.set = function set(key, value) {
      contents[key] = Utils.deepCopy(value);
    };

    this.get = function get(key, defaultValue) {
      if (key in contents)
        return Utils.deepCopy(contents[key]);
      return defaultValue;
    };
  },

  DOMLocalStorage: function DOMLocalStorage() {
    var self = this;
    var localStorage = window.localStorage;

    if (window.globalStorage)
      localStorage = window.globalStorage[document.location.hostname];

    if (!localStorage)
      throw new OpenWebStorage.UnsupportedError('No local DOM storage');

    self.set = function set(key, value) {
      localStorage[key] = JSON.stringify(value);
    };

    self.get = function get(key, defaultValue) {
      var value = defaultValue;

      if (localStorage[key])
        try {
          value = JSON.parse(localStorage[key].value);
        } catch (e) {}

      return value;
    };
  }
};

var OpenWebChat = {
  ClientStorage: function ClientStorage(owStorage, prefix) {
    var self = this;
    self.length = owStorage.get(prefix + 'length', 0);

    self.get = function get(id) {
      return owStorage.get(prefix + id);
    };

    self.append = function append(msg) {
      owStorage.set(prefix + self.length, msg);
      self.length += 1;
      owStorage.set(prefix + 'length', self.length);
    };
  },

  SEND_TIMEOUT: 10000,

  LISTEN_TIMEOUT: 60000,

  listenForMessages: function listenForMessages(options) {
    var self = this;

    jQuery.ajax(
      {type: 'GET',
       url: 'listen',
       data: {start: options.storage.length},
       dataType: 'json',
       timeout: self.LISTEN_TIMEOUT,
       error: function(xhr, textStatus, errorThrown) {
         if (textStatus == "timeout")
           // Start another long poll.
           self.listenForMessages(options);
         else
           options.onError([textStatus, errorThrown]);
       },
       success: function(data, textStatus) {
         // TODO: Make sure data.messages is an array.
         for (var i = 0; i < data.messages.length; i++)
           options.storage.append(data.messages[i]);

         options.onMessages(data.messages);
         // Start another long poll.
         self.listenForMessages(options);
       }
      });
  },

  sendMessage: function sendMessage(options) {
    jQuery.ajax(
      {type: 'POST',
       url: 'send',
       contentType: 'application/json',
       data: JSON.stringify(options.message),
       processData: false,
       timeout: this.LISTEN_TIMEOUT,
       error: function(xhr, textStatus, errorThrown) {
         options.onError([textStatus, errorThrown]);
       }
      });
  }
};

$(window).ready(
  function() {
    var ENTER_KEYCODE = 13;
    var RETURN_KEYCODE = 10;

    var owStorage;

    try {
      owStorage = new OpenWebStorage.DOMLocalStorage();
    } catch (e) {
      if (e instanceof OpenWebStorage.UnsupportedError)
        owStorage = new OpenWebStorage.FakeStorage();
      else
        throw e;
    }

    var convStorage = new OpenWebChat.ClientStorage(
      owStorage,
      '/conv' + document.location.pathname
    );

    $('#name').val(owStorage.get('name',
                                 $('#data .default-name').text()));
    $('#name').blur(
      function() {
        owStorage.set('name', $(this).val());
      });

    $('#outgoing-message').val(owStorage.get('lastMessage', ''));
    $('#outgoing-message').blur(
      function() {
        if ($(this).val())
          owStorage.set('lastMessage', $(this).val());
      });

    $('#outgoing-message').focus();

    function onKey(evt) {
      var self = this;
      var content = $(this).val();
      var author = $('#name').val();
      if (evt.keyCode == ENTER_KEYCODE ||
          evt.keyCode == RETURN_KEYCODE) {
        if (content) {
          owStorage.set('lastMessage', content);
          $(this).val('');
          var msg = {content: content,
                     time: new Date()};
          if (author)
            msg.author = author;
          OpenWebChat.sendMessage(
            {message: msg,
             onError: function(exception) {
               $(self).val(content);
             }
            });
        }
        evt.preventDefault();
      }
    }

    $('#outgoing-message').keydown(onKey);
    $('#outgoing-message').keyup(onKey);

    function scrollToBottom() {
      window.scrollTo(0, $('#bottom').position().top);
    }

    function msg2dom(msg) {
      var message = $('<div class="message"></div>');
      message.append('<div class="author"></div>');
      message.append('<div class="timestamp"></div>');
      message.append('<div class="content"></div>');
      message.append('<div class="raw-content"></div>');

      $('.content', message).html(msg.content);
      $('.raw-content', message).text(msg.content);

      $('.author', message).text(msg.author);
      $('.timestamp', message).text(msg.time);

      return message;
    }

    var lastAuthor;

    function onMessage(msg, container) {
      if (owStorage.get('lastMessage') == msg.content)
        owStorage.set('lastMessage', '');

      var message = msg2dom(msg);

      if (msg.author == lastAuthor)
        $('.author', message).hide();
      else
        lastAuthor = msg.author;

      var date = Utils.parseJsonDate(msg.time);
      msg = null;

      message.mousedown(
        function(evt) {
          if (evt.shiftKey) {
            evt.preventDefault();
            if ($('.content', this).css('display') == 'none') {
              $('.content', this).show();
              $('.raw-content', this).hide();
            } else {
              $('.content', this).hide();
              $('.raw-content', this).show();
            }
          }
        });

      message.mouseenter(
        function() {
          $(this).addClass('highlighted');
          $('.overlay').css({top: $(this).position().top});
          $('.overlay .sent').text(jQuery.timeago(date));
          $('.overlay').show();
        });

      message.mouseleave(
        function() {
          $(this).removeClass('highlighted');
          $('.overlay').hide();
        });

      container.append(message);
    }

    function onMessages(msgs) {
      var container = $('<div class="messages"></div>');

      for (var i = 0; i < msgs.length; i++)
        onMessage(msgs[i], container);

      container.hide();
      $('#incoming-messages').append(container);
      container.slideDown(scrollToBottom);
    }

    $('#incoming-messages').hide();
    for (var i = 0; i < convStorage.length; i++)
      onMessage(convStorage.get(i), $('#incoming-messages'));
    $('#incoming-messages').slideDown();

    var RECONNECT_SECONDS = 30;

    function startListening() {
      OpenWebChat.listenForMessages(
        {storage: convStorage,
         onMessages: onMessages,
         onError: function onError(exception) {
           var timeLeft = RECONNECT_SECONDS;
           if (window.console)
             window.console.log('The error', exception, 'occurred.');
           $('.error .seconds-left').text(timeLeft);
           $('.error .plural').show();
           $('.error').slideDown();
           var intervalId = window.setInterval(
             function() {
               timeLeft -= 1;
               if (timeLeft == 1)
                 $('.error .plural').hide();
               if (!timeLeft) {
                 window.clearInterval(intervalId);
                 $('.error').slideUp(startListening);
               } else
                 $('.error .seconds-left').text(timeLeft);
             },
             1000
           );
         }
        });
    }

    startListening();
  });