function Phonebook (sessionid, onloadcallback, onselectcallback, containerid, phonebookid) {

   var _handler;
   var _grid;
   var _sessionid;
   var _phonebookid;
   var _containerid;
   var _onloadcallback;
   var _onselectcallback;

   _onloadcallback = onloadcallback;
   _onselectcallback = onselectcallback;
   _sessionid = sessionid;
   _phonebookid = phonebookid;
   _containerid = containerid;

   this.get = function () {
      _get ()
   }

   function _get () {

      if (! _handler) {
         if (window.XMLHttpRequest) _handler = new XMLHttpRequest ();
         else _handler = new ActiveXObject ("Microsoft.XMLHTTP");
      }

      if (_handler) {

         var hostname = window.location.hostname.toString ();

         _handler.open ("POST", "http://" + hostname + "/perl/mxmd/webchat/addressbook", true);
         _handler.onreadystatechange = onresponse;
         _handler.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');

         var str = "<addressbook><task>get</task><sessionid>" + _sessionid + "</sessionid></addressbook>";
         _handler.send (str);

      } else if (_onloadcallback) {
         _onloadcallback ({'status': 0, 'msg': 'Unable to load addressbook'});
      }
   }

   this.add = function (name, number) {

      if (! _handler) {
         if (window.XMLHttpRequest) _handler = new XMLHttpRequest ();
         else _handler = new ActiveXObject ("Microsoft.XMLHTTP");
      }

      if (_handler) {

         var hostname = window.location.hostname.toString ();

         _handler.open ("POST", "http://" + hostname + "/perl/mxmd/webchat/addressbook", true);
         _handler.onreadystatechange = onresponse;
         _handler.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');

         if (number == null)
            number = "";
         if (name == null)
            name = ""

         var str = "<addressbook><task>add</task><sessionid>" + sessionid + "</sessionid><data>";
         str += "<name><![CDATA[" + name + "]]></name>";
         str += "<number><![CDATA[" + number + "]]></number>";

         str += "</data></addressbook>";
         _handler.send (str);
      }
   }

   this.update = function (friendid, name) {
      _update (friendid, name);
   }

   function _update (friendid, name) {

      if (! friendid)
         return;

      if (! _handler) {
         if (window.XMLHttpRequest) _handler = new XMLHttpRequest ();
         else _handler = new ActiveXObject ("Microsoft.XMLHTTP");
      }

      if (_handler) {

         var hostname = window.location.hostname.toString ();

         _handler.open ("POST", "http://" + hostname + "/perl/mxmd/webchat/addressbook", true);
         _handler.onreadystatechange = onresponse;
         _handler.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');

         if (name == null)
            name = ""

         var str = "<addressbook><task>update</task><sessionid>" + sessionid + "</sessionid><data>";
         str += "<id><![CDATA[" + friendid + "]]></id>";
         str += "<name><![CDATA[" + name + "]]></name>";

         str += "</data></addressbook>";
         _handler.send (str);
      }
   }

   this.remove = function (friendid) {
      if (! friendid)
         return;

      if (! _handler) {
         if (window.XMLHttpRequest) _handler = new XMLHttpRequest ();
         else _handler = new ActiveXObject ("Microsoft.XMLHTTP");
      }

      if (_handler) {

         var hostname = window.location.hostname.toString ();

         _handler.open ("POST", "http://" + hostname + "/perl/mxmd/webchat/addressbook", true);
         _handler.onreadystatechange = onresponse;
         _handler.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');

         var str = "<addressbook><task>delete</task><sessionid>" + sessionid + "</sessionid><data>";
         str += "<id><![CDATA[" + friendid + "]]></id>";

         str += "</data></addressbook>";
         _handler.send (str);
      }
   }

   this.show = function () {
      this.get ();
   }
   this.refresh = function () {
      this.get ();
   }
   this.selected = function () {
      var rowid = _grid.getSelectedRowId ();
      if (! rowid) return null;
      return {'name': _grid.cells (rowid, 0).getValue (), 'cellnumber': _grid.cells (rowid, 1).getValue(), 'friendid': rowid};
   }

   this.hide = function () {
      document.getElementById (_containerid).style.visibility='hidden';
   }

   function onresponse () {

      if (_handler.readyState != 4 || _handler.status != 200)
         return;

      var xml = _handler.responseXML;
      if (xml == null || ! findNode (xml, 'ok')) {
         if (_onloadcallback) _onloadcallback ({'status': 0, 'msg': 'Unexpected error, please try again later'});
         return;
      }

      var status = findNodeValue (xml, 'status');
      var msg    = findNodeValue (xml, 'msg');

      var task = findNodeValue (xml, 'task');
      switch (task) {

         case 'get':
            var phonebookdata = findNode (xml, "data");
            if (phonebookdata)
               _build (phonebookdata);
            break;
         case 'add':
         case 'update':
         case 'delete':
            var status = findNodeValue (xml, "status");
            if (status && status == 1)
               _get ();
            break;
      }

      if (_onloadcallback) _onloadcallback ({'status': status, 'msg': msg});
   }

   function _build (data) {

      var elem = document.getElementById (_containerid);
      if (elem == null)
         return;

      elem.style.visibility='hidden';

      _grid = new dhtmlXGridObject(_phonebookid);

      _grid.setImagePath("dhtmlx/imgs/");
      _grid.setHeader("Name,Number");
      _grid.setInitWidths("*,105");
      _grid.setColAlign("left,left");
      _grid.setSkin("light");
      _grid.setColSorting("str,str");
      _grid.setColTypes("ed,ro");
      _grid.enableResizing ("false,false");
      _grid.enableAutoHeight ("true", 145);
      _grid.enableAutoWidth ("true");
      _grid.attachEvent ("onRowSelect", gridOnRowSelect);
      _grid.attachEvent ("onEditCell", gridOnCellEdit);
      _grid.init();
      _grid.parse(data)

      var height = document.getElementById(_phonebookid).style.height;
      var top = parseInt(height.split ('px')[0]);
      document.getElementById ('addressbookfooter').style.top = (top + 5) + 'px';
      document.getElementById ('addressbooktip').style.top = (top + 30) + 'px';

      if (_grid.getRowsNum() == 0) {
         document.getElementById ('addressbooknoentries').style.display = 'block';
         document.getElementById ('addressbooktip').innerHTML="<strong>Tip:</strong> Start adding friends to your<br/>Phone Book by clicking on the green plus sign";
         fadeIn ('addressbooktip');
      } else {
         document.getElementById ('addressbooknoentries').style.display = 'none';
         document.getElementById ('addressbooktip').innerHTML="<strong>Tip:</strong> Add more friends to your Phone Book.<br/>Start by clicking on the green plus sign";
         fadeIn ('addressbooktip');
      }

      elem.style.visibility='visible';
   }

   function gridOnRowSelect (rowid, ind) {

      var name = _grid.cells (rowid, 0).getValue ();
      var cellnumber = _grid.cells (rowid, 1).getValue();

      if (cellnumber != null && cellnumber != '') {

         if (_onselectcallback)
            _onselectcallback ({'name': name, 'cellnumber': cellnumber, 'rowid': rowid, 'cellid': ind});

         if (name == null || name == "") {
            document.getElementById ('addressbooktip').innerHTML="<strong>Tip:</strong> Double-click in the name column to Edit";
         } else {
            document.getElementById ('addressbooktip').innerHTML="<strong>Tip:</strong> Add more friends to your Phone Book.<br/>Start by clicking on the green plus sign";
         }
         fadeIn ('addressbooktip');
      }
   }

   function gridOnCellEdit (stage, rowid, ind, nValue, oValue) {
      if (stage == 2 && nValue != oValue)
         _update (rowid, nValue);
      return true;
   }

   function findNodeValue (node, tag) { try { return node.getElementsByTagName (tag)[0].firstChild.nodeValue; } catch (err) { return null; } }
   function findAttrValue (node, tag) { try { return node.attributes.getNamedItem (tag).value; } catch (err) { return null; } }
   function findNode (node, tag) { try { return node.getElementsByTagName (tag)[0]; } catch (err) { return null; } }

}

