Day Class

if(!javaxt) var javaxt={};
if(!javaxt.dhtml) javaxt.dhtml={};
if(!javaxt.dhtml.calendar) javaxt.dhtml.calendar={};

//******************************************************************************
//**  Day View
//*****************************************************************************/
/**
 *   Used to render a day
 *
 ******************************************************************************/

javaxt.dhtml.calendar.Day = function(parent, config) {
    this.className = "javaxt.dhtml.calendar.Day";

    var me = this;


  //DOM elements
    var el;
    var bodyDiv;
    var footerRow;
    var multidayRow, multidayEventsTable;
    var currTimeDiv, getCurrentDate;


  //Class variables
    var rendered;
    var startDate, endDate;
    var cells = {};
    var widths = {};
    var rowHeights = [];
    var scrollWidth;
    var scrollable = true;


  //Config options
    var days = 1;
    var date;
    var store;
    var eventHeight = 17; //Only applies to multiday events
    var eventPadding = 2;
    var holdDelay = 500;
    var debug = false;


  //Browser detection used to adjust event padding
    var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
    var isIE = window.navigator.userAgent.indexOf("MSIE ") > -1 ||
               window.navigator.userAgent.indexOf("Trident/") > -1;



  //**************************************************************************
  //** Constructor
  //**************************************************************************
  /** Creates a new instance of the calendar control. */

    var init = function(){

      //Call super
        new javaxt.dhtml.calendar.View(me, config);

      //Set store
        store = config.eventStore==null ? new javaxt.dhtml.calendar.EventStore() : config.eventStore;

      //Set number of days to render
        if (config.days!=null) days = config.days;

      //Set event size, padding, and spacing
        function isNumeric(n){ return !isNaN(parseFloat(n)) && isFinite(n); }
        if (isNumeric(config.eventHeight)) eventHeight = parseInt(config.eventHeight);
        if (isNumeric(config.eventPadding)) eventPadding = parseInt(config.eventPadding);


      //Specify function used to get current date
        getCurrentDate = config.getCurrentDate==null ?
        getCurrentDate = function(){return new Date();} : config.getCurrentDate;



      //Configure renderers
        if (config.renderers){
            for (var rendererName in config.renderers) {
                if (config.renderers.hasOwnProperty(rendererName)) {
                    if (me[rendererName]){

                      //Override the default renderer
                        (function(rendererName) {
                            me[rendererName] = function(){
                                var renderer = config.renderers[rendererName];
                                return renderer.apply(me, arguments);
                            };

                        })(rendererName);
                    }
                }
            }
        }


      //Call the beforerender callback
        rendered = false;
        var listener = me.getListener('beforerender');
        if (listener!=null) listener.callback.apply(listener.scope, [me]);

      //Set date and render the calendar
        me.setDate(config.date);

      //Call the afterrender callback
        listener = me.getListener('afterrender');
        if (listener!=null) listener.callback.apply(listener.scope, [me]);
        rendered = true;

      //Call the update callback
        listener = me.getListener('update');
        if (listener!=null) listener.callback.apply(listener.scope, [me]);
    };


  //**************************************************************************
  //** hasHours
  //**************************************************************************
    this.hasHours = function(){
        return true;
    };


  //**************************************************************************
  //** show
  //**************************************************************************
    this.show = function(){
        parent.appendChild(el);
    };


  //**************************************************************************
  //** hide
  //**************************************************************************
    this.hide = function(){
        parent.removeChild(el);
    };


  //**************************************************************************
  //** showFooter
  //**************************************************************************
    this.showFooter = function(){
        footerRow.style.display = '';
    };


  //**************************************************************************
  //** hideFooter
  //**************************************************************************
    this.hideFooter = function(){
        footerRow.style.display = "none";
    };


  //**************************************************************************
  //** enableTouch
  //**************************************************************************
    this.enableTouch = function(){
        scrollable = true;
    };


  //**************************************************************************
  //** disableTouch
  //**************************************************************************
    this.disableTouch = function(){
        scrollable = false;
    };


  //**************************************************************************
  //** renderTable
  //**************************************************************************
  /** Used to render a new table for the current date. */

    var renderTable = function(){


      //Remove any previously rendered table
        if (el!=null){
            for (var i=0; i<parent.childNodes.length; i++){
                if (parent.childNodes[i]==el){
                    parent.removeChild(el);
                    break;
                }
            }
        }


      //Reset variables
        cells = {};
        widths = {};
        rowHeights = [];



      //Create main table
        var div, tbody, tr, td;
        tbody = createTable();
        var table = el = tbody.parentNode;
        table.className = "javaxt-noselect";
        table.style.cursor = "default";
        parent.appendChild(table);


      //Create header row
        tr = document.createElement("tr");
        tr.setAttribute("desc", "header");
        tr.className = "javaxt-cal-header";
        tbody.appendChild(tr);
        td = document.createElement("td");
        td.style.width = "100%";
        td.style.height = "inherit";
        tr.appendChild(td);
        var header = td;



      //Create row for multiday events
        tr = document.createElement("tr");
        tr.setAttribute("desc", "multiday events");
        tr.className = "javaxt-cal-multiday-header";
        tr.style.display = "none";
        tr.style.visibility = "hidden"; //visible
        multidayRow = tr;
        tbody.appendChild(tr);
        td = document.createElement("td");
        td.style.width = "100%";
        td.style.height = "inherit";
        tr.appendChild(td);
        var multidayDiv = document.createElement('div');
        multidayDiv.setAttribute("desc", "multiday-div");
        multidayDiv.style.width = "100%";
        multidayDiv.style.height = "inherit";
        multidayDiv.style.position = "relative";
        td.appendChild(multidayDiv);



      //Create body row
        tr = document.createElement("tr");
        tr.setAttribute("desc", "body");
        tr.className = "javaxt-cal-body";
        tbody.appendChild(tr);
        td = document.createElement("td");
        td.style.width = "100%";
        td.style.height = "100%";
        tr.appendChild(td);

        bodyDiv = document.createElement('div');
        bodyDiv.setAttribute("desc", "body-div");
        bodyDiv.style.width = "100%";
        bodyDiv.style.height = "100%";
        bodyDiv.style.position = "relative";
        td.appendChild(bodyDiv);
        div = document.createElement('div');
        div.style.width = "100%";
        div.style.height = "100%";
        div.style.position = "absolute";
        div.style.overflow = 'scroll';
        div.style.overflowX = 'hidden';
        bodyDiv.appendChild(div);
        bodyDiv = div;

      //Add logic to enable/disable scroll. This is important for touch devices.
        bodyDiv.addEventListener('touchstart', function(e) {
            if (!scrollable) e.preventDefault();
        }, false);
        bodyDiv.addEventListener('touchmove', function(e) {
            if (!scrollable) e.preventDefault();
        }, false);


      //Add scroll listener
        var listener = me.getListener('scroll');
        if (listener){
            div.addEventListener('scroll', function(e) {
                listener.callback.apply(listener.scope, [me]);
            }, false);
        }


      //Create footer row
        tr = document.createElement("tr");
        tr.setAttribute("desc", "footer");
        tr.className = "javaxt-cal-footer";
        tbody.appendChild(tr);
        td = document.createElement("td");
        td.style.width = "100%";
        td.style.height = "inherit";
        tr.appendChild(td);
        footerRow = tr;
        var footer = td;


      //Create current time indicator
        currTimeDiv = document.createElement("div");
        currTimeDiv.className = "javaxt-cal-current-time-indicator";
        currTimeDiv.style.position = "absolute";
        currTimeDiv.style.width = "100%";
        currTimeDiv.style.display = "none";
        bodyDiv.appendChild(currTimeDiv);


      //Populate header
        tbody = createTable();
        header.appendChild(tbody.parentNode);
        tr = document.createElement("tr");
        tbody.appendChild(tr);
        td = document.createElement("td");
        tr.appendChild(td);
        var spacerUL = document.createElement('div');
        td.appendChild(spacerUL);

        var d = new Date(startDate);
        for (var i=0; i<days; i++){
            td = document.createElement('td');
            td.className = "javaxt-cal-header-col";
            td.style.width = (100/days) + '%';
            td.style.height = "100%";
            td.appendChild(me.createColumnHeader(d.getDay()));
            tr.appendChild(td);
            d.setDate(d.getDate()+1);
        }

        td = document.createElement("td");
        tr.appendChild(td);
        var spacerUR = document.createElement('div');
        td.appendChild(spacerUR);


      //Populate footer
        tbody = createTable();
        footer.appendChild(tbody.parentNode);
        tr = document.createElement("tr");
        tbody.appendChild(tr);
        td = document.createElement("td");
        tr.appendChild(td);
        var spacerLL = document.createElement('div');
        td.appendChild(spacerLL);

        var d = new Date(startDate);
        for (var i=0; i<days; i++){
            td = document.createElement('td');
            td.className = "javaxt-cal-footer-col";
            td.style.width = (100/days) + '%';
            td.style.height = "100%";
            td.appendChild(me.createColumnFooter(d.getDay()));
            tr.appendChild(td);
            d.setDate(d.getDate()+1);
        }

        td = document.createElement("td");
        tr.appendChild(td);
        var spacerLR = document.createElement('div');
        td.appendChild(spacerLR);



      //Populate multiday div
        var multidayContent = document.createElement('div');
        multidayContent.setAttribute("desc", "multiday-content");
        multidayContent.style.width = "100%";
        multidayContent.style.height = "100%";
        multidayContent.style.position = "relative";
        multidayDiv.appendChild(multidayContent);
        div = document.createElement('div');
        multidayContent.appendChild(div);
        div.style.width = "100%";
        div.style.height = "100%";
        div.style.position = "absolute";
        div.style.overflow = 'scroll';
        div.style.overflowX = 'hidden';
        tbody = createTable();
        div.appendChild(tbody.parentNode);
        tr = document.createElement("tr");
        tbody.appendChild(tr);
        td = document.createElement("td");
        tr.appendChild(td);
        var spacerML = document.createElement('div');
        td.appendChild(spacerML);
        for (var i=0; i<days; i++){
            td = document.createElement('td');
            td.className = "javaxt-cal-multiday-col";
            td.style.width = (100/days) + '%';
            td.style.height = "1px";
            tr.appendChild(td);
        }
        multidayEventsTable = tbody;





      //Populate body
        tbody = createTable();
        bodyDiv.appendChild(tbody.parentNode);
        var row = document.createElement('tr');
        tbody.appendChild(row);



      //Create left column to render hours
        var leftCol = document.createElement('td');
        leftCol.style.verticalAlign = "top";
        row.appendChild(leftCol);
        var hours = document.createElement('table');
        hours.style.borderCollapse = "collapse";
        hours.cellSpacing = 0;
        hours.cellPadding = 0;
        leftCol.appendChild(hours);
        tbody = document.createElement('tbody');
        hours.appendChild(tbody);
        for (var i=0; i<24; i++){
            var tr = document.createElement('tr');
            tbody.appendChild(tr);
            var td = document.createElement('td');
            td.className = "javaxt-cal-hour" + (i==23 ? " javaxt-cal-hour-last" : "");
            if (i==0) td.style.borderTop = "0px";
            td.appendChild(me.createHourLabel(i));
            tr.appendChild(td);
        }



      //Create main table used to render days
        var d = new Date(startDate);
        for (var i=0; i<days; i++){

            td = document.createElement('td');
            td.className = 'javaxt-cal-cell';
            td.style.width = (100/days) + '%';
            td.style.height = "100%";
            td.style.verticalAlign = "top";
            td.valign="top";
            row.appendChild(td);


          //Create div used to store events and the table grid
            var outerDiv = document.createElement('div');
            outerDiv.style.width = "100%";
            outerDiv.style.height = "100%";
            outerDiv.style.position = "relative";
            outerDiv.style.cursor = "inherit";
            var innerDiv = document.createElement('div');
            innerDiv.style.width = "100%";
            innerDiv.style.height = "100%";
            innerDiv.style.position = "absolute";
            innerDiv.style.whiteSpace = 'nowrap';
            innerDiv.style.overflow = 'hidden';
            innerDiv.style.cursor = "inherit";
            outerDiv.appendChild(innerDiv);


            var cell = innerDiv;
            var cellID = (d.getMonth()+1) + "-" + d.getDate() + "-" + d.getFullYear();
            cell.date = new Date(d);
            cells[cellID] = cell;
            td.appendChild(outerDiv);


          //Create table
            var innerTable = document.createElement('table');
            innerTable.cellSpacing = 0;
            innerTable.cellPadding = 0;
            innerTable.style.width = "100%";
            innerTable.style.borderCollapse = "collapse";
            tbody = document.createElement('tbody');
            innerTable.appendChild(tbody);
            cell.appendChild(innerTable);



          //Add 1 row for every 30 minutes
            for (var j=0; j<24*2; j++){
                var tr = document.createElement('tr');
                tbody.appendChild(tr);

                var col = document.createElement('td');
                col.className = "javaxt-cal-half-hour" + (j % 2 == 0 ? "" : " javaxt-cal-half-hour-sep") + (j==47 ? " javaxt-cal-hour-last" : "");
                if (j==0) col.style.borderTop = "0px";

                tr.appendChild(col);
                addListeners(tr, holdDelay);
            }


            d.setDate(d.getDate()+1);
        }



      //Update the spacer in the left column of the header
        var lt = _getRect(leftCol.childNodes[0]).width;
        spacerUL.style.width =
        spacerML.style.width =
        spacerLL.style.width = lt + 'px';


      //Update the spacer in the right column of the header
        var bodyWidth = _getRect(bodyDiv).width;
        var tableWidth = _getRect(leftCol.parentNode.parentNode.parentNode).width;
        scrollWidth = Math.abs(bodyWidth-tableWidth);
        spacerUR.style.width =
        spacerLR.style.width = scrollWidth + 'px';


      //Update position of the current time indicator
        currTimeDiv.style.left = lt + 'px';
        updateCurrTime();


      //Kick off scheduled task to periodically update the current time indicator
        var epoch = getCurrentDate().getTime() / 1000;
        var secondsSinceLastTimerTrigger = epoch % 60;
        var secondsUntilNextTimerTrigger = 60 - secondsSinceLastTimerTrigger;
        setTimeout(function() {
            setInterval(updateCurrTime, 60*1000);
            updateCurrTime();
        }, secondsUntilNextTimerTrigger*1000);



      //Scroll to start of workday
        me.scrollTo(6.5);


      //Call the "update" listener
        if (rendered){
            var listener = me.getListener('update');
            if (listener!=null) listener.callback.apply(listener.scope, [me]);
        }
    };


  //**************************************************************************
  //** addListeners
  //**************************************************************************
  /** Used to initialize a cell for click and hold events. */

    var addListeners = function(cell, holdDelay){


      //This timeout, started on mousedown, triggers the beginning of a hold
        var holdStarter = null;


      //This flag indicates the user is currently holding the mouse down
        var holdActive = false;


      //OnClick
        //cell.onclick = NOTHING!! not using onclick at all - onmousedown and onmouseup take care of everything


      //MouseDown
        var onMouseDown = function(e){

            var tr = this;

          //Set the holdStarter and wait for the predetermined delay, and then begin a hold
            holdStarter = setTimeout(function() {
                holdStarter = null;
                holdActive = true;


                //start hold
                var listener = me.getListener('cellhold');
                if (listener){
                    var callback = listener.callback;
                    var scope = listener.scope;
                    var date = getDate();
                    callback.apply(scope, [date, tr, me, e]);
                }

            }, holdDelay);

        };



      //MouseUp
        var onMouseUp = function(e){

            var tr = this;


          //Get date associated with the selected cell
            var date = getDate();



          //If the mouse is released immediately (i.e., a click), before the
          //holdStarter runs, then cancel the holdStarter and do the click
            if (holdStarter) {
                clearTimeout(holdStarter);


                var listener = me.getListener('cellclick');
                if (listener){
                    var callback = listener.callback;
                    var scope = listener.scope;
                    callback.apply(scope, [date, tr, me, e]);
                }

            }

          //Otherwise, if the mouse was being held, end the hold
            else if (holdActive) {
                holdActive = false;

              //End hold
                var listener = me.getListener('cellrelease');
                if (listener){
                    var callback = listener.callback;
                    var scope = listener.scope;
                    callback.apply(scope, [date, tr, me, e]);
                }
            }
        };


        cell.onmousedown = onMouseDown;
        cell.onmouseup = onMouseUp;
        cell.ontouchstart = function(e) {

            var touch = e.touches[0];
            var x = touch.pageX;
            var y = touch.pageY;

            this.onmousedown.apply(this, [{
                clientX: x,
                clientY: y
            }]);
        };
        cell.ontouchend = function(e){
            var touch = e.changedTouches[0];
            var x = touch.pageX;
            var y = touch.pageY;

            this.onmouseup.apply(this, [{
                clientX: x,
                clientY: y
            }]);
        };

      //Get date associated with the selected cell
        var getDate = function(){

            var tbody = cell.parentNode;
            var _cell = tbody.parentNode.parentNode;
            var date = new Date(_cell.date);

            var hour = 0;
            for (var i=0; i<tbody.childNodes.length; i++){
                var tr = tbody.childNodes[i];
                if (tr===cell){
                    hour = (i/2);
                    break;
                }
            }
            date.setTime(date.getTime() + ((hour*60) * 60 * 1000));
            return date;
        };
    };


  //**************************************************************************
  //** createColumnHeader
  //**************************************************************************
  /** Returns a div used to indicate the day of the week. The div is inserted
   *  into a column header that corresponds to a given date. This method can
   *  be safely overridden to generate custom headers.
   */
    this.createColumnHeader = function(i){
        var outerDiv = document.createElement('div');
        outerDiv.style.width = "100%";
        outerDiv.style.height = "100%";
        outerDiv.style.position = "relative";
        outerDiv.style.cursor = "inherit";
        var innerDiv = document.createElement('div');
        innerDiv.style.width = "100%";
        innerDiv.style.height = "100%";
        innerDiv.style.position = "absolute";
        innerDiv.style.whiteSpace = 'nowrap';
        innerDiv.style.overflow = 'hidden';
        innerDiv.style.cursor = "inherit";


        var text = javaxt.dhtml.calendar.Utils.dayNames[i];
        if (days>1) text = text.substring(0,3);
        //else text+= ", " + javaxt.dhtml.calendar.Utils.monthNames[d.getMonth()] + " " + d.getDate();
        innerDiv.innerHTML = text;


        outerDiv.appendChild(innerDiv);
        return outerDiv;
    };



    this.createColumnFooter = function(i){
        return document.createElement('div');
    };



  //**************************************************************************
  //** createHourLabel
  //**************************************************************************
  /** Returns a div used to indicate the hour of day (e.g. 12pm). This method
   *  can be safely overridden to generate custom labels for hours.
   */
    this.createHourLabel = function(hour){
        var div = document.createElement('div');
        div.style.float = "right";
        div.style.padding = "0px 7px 0px 7px";


      //Update hour and set meridian
        var meridian = 'AM';
        if (hour==0) hour = 12;
        else if (hour==12) meridian = 'PM';
        else if (hour>12){
            hour = (hour-12);
            meridian = 'PM';
        }

      //Create table to render the hour and meridian
        var hdr = document.createElement('table');
        hdr.cellSpacing = 0;
        hdr.cellPadding = 0;
        div.appendChild(hdr);
        var t = document.createElement('tbody');
        hdr.appendChild(t);
        var tr = document.createElement('tr');
        t.appendChild(tr);
        var td = document.createElement('td');
        td.className = "javaxt-cal-label-hour";
        td.innerHTML = hour;
        tr.appendChild(td);
        td = document.createElement('td');
        td.className = "javaxt-cal-label-meridian";
        td.innerHTML = meridian;
        tr.appendChild(td);

        return div;
    };


  //**************************************************************************
  //** updateCurrTime
  //**************************************************************************
  /** Used to update the position of the current time indicator.
   */
    var updateCurrTime = function(){

        var date = getCurrentDate();
        var cellID = (date.getMonth()+1) + "-" + date.getDate() + "-" + date.getFullYear();
        var cell = cells[cellID];
        if (cell){

            var hours = date.getHours();
            var h1 = getVerticalOffset(hours, cell);
            var h2 = getVerticalOffset(hours+1, cell);

            var pixelsPerMinute = (h2-h1)/60;
            var h = h1+(date.getMinutes()*pixelsPerMinute);

            currTimeDiv.style.display = '';
            currTimeDiv.style.top = h + "px";
        }
        else{
            currTimeDiv.style.display = "none";
        }
    };


  //**************************************************************************
  //** scrollTo
  //**************************************************************************
  /** Used to scroll to a specific time of day.
   *  @param hour Time of day, specified as a decimal (e.g. 6.5 for 6:30 AM)
   */
    this.scrollTo = function(hour){
        var cellID = (date.getMonth()+1) + "-" + date.getDate() + "-" + date.getFullYear();
        var cell = cells[cellID];
        var offset = getVerticalOffset(hour, cell);
        bodyDiv.scrollTop = offset+1; //+1 for border
    };


  //**************************************************************************
  //** getScrollDiv
  //**************************************************************************
    this.getScrollDiv = function(){
        return bodyDiv;
    };


  //**************************************************************************
  //** getVerticalOffset
  //**************************************************************************
  /** Returns the relative, vertical offset of a row in a cell.
   */
    var getVerticalOffset = function(hour, cell){

      //Compute row heights and cache the values for subsequent use. Caching
      //the row heights improves load times by approx 500ms in IE and 1000ms
      //in FF. Caching assumes that row heights are the same accross all cells
      //and that the row  heights do not change.
        if (rowHeights.length==0){
            var tbody = cell.childNodes[0].childNodes[0];
            var rows = tbody.childNodes;
            for (var i=0; i<rows.length; i++){
                var tr = rows[i];
                var rect = _getRect(tr);
                rowHeights.push(rect.height);
            }
        }

      //Compute vertical offset
        var offset = 0;
        for (var i=0; i<rowHeights.length; i++){
            var rowHeight = rowHeights[i];
            offset += rowHeight;
            var h = i*0.5;
            if (h>=hour){
                return (offset-rowHeight);
            }
        }
        return 0;
    };


  //**************************************************************************
  //** getEventStore
  //**************************************************************************
    this.getEventStore = function(){
        return store;
    };


  //**************************************************************************
  //** addEvent
  //**************************************************************************
    this.addEvent = function(event){
        addEvent(event);
    };


  //**************************************************************************
  //** addEvent
  //**************************************************************************
  /** Private method used to add an event to the view.
   *  @param deferUpdates Option to postpone or defer updating the event
   *  width and position when there are overlapping events. This option is
   *  provided for optimization.
   */
    var addEvent = function(event, deferUpdates){

        log("Adding " + event.getSubject() + "...");
        var numDays = event.numDays();


      //Check if we've already rendered the given event
        if (numDays>=1){
            for (var i=1; i<multidayEventsTable.childNodes.length; i++){
                var tr = multidayEventsTable.childNodes[i];
                for (var j=1; j<tr.childNodes.length; j++){
                    var td = tr.childNodes[j];
                    if (td.childNodes.length>0){
                        var div = td.childNodes[0];
                        if (div.event.equals(event)){
                            return;
                        }
                    }
                }
            }

        }
        else{
            var divs = getDivs(event.getStartDate());
            for (var i=0; i<divs.length; i++){
                var div = divs[i];
                if (div.event.equals(event)){
                    return;
                }
            }
        }



      //If we're still here, add the event
        if (numDays>=1){
            addMultiDayEvent(event);
        }
        else{
            addSingleDayEvent(event, deferUpdates);
        }


      //Update the event store
        store.add(event);
    };


  //**************************************************************************
  //** addEvents
  //**************************************************************************
  /** Used to add multiple events to the view. This method is recommended for
   *  bulk loading and is significantly faster than calling addEvent multiple
   *  times.
   */
    this.addEvents = function(events){


      //Add events but defer updating position and widths of overlapping events
        for (var i=0; i<events.length; i++){
            addEvent(events[i], true);
        }



      //Generate list of all single-day events in the view
        var singleDayEvents = [];
        events = me.getEvents();
        for (var i=0; i<events.length; i++){
            var event = events[i];
            if (event.numDays()<1){
                var div = getDiv(event);
                if (div!=null) singleDayEvents.push(event);
            }
        }


      //Local variables
        var processedEvents = [];
        var isProcessed = function(event){
            for (var i=0; i<processedEvents.length; i++){
                if (processedEvents[i].equals(event)){
                    return true;
                }
            }
            return false;
        };



      //Iterate through single-day events and update widths
        for (var i=0; i<singleDayEvents.length; i++){
            var event = singleDayEvents[i];

            var updateWidth = !isProcessed(event);


            if (updateWidth){
                var overlappingEvents = getOverlappingEvents(event);
                if (overlappingEvents.length>0){

                  //Compute number of columns
                    var numColumns = 2;
                    if (overlappingEvents.length>1){

                      //The following logic is somewhat flawed and may yeild
                      //a column count slightly higher than expected...
                        for (var j=0; j<overlappingEvents.length; j++){
                            var _event = overlappingEvents[j];
                            var _startDate = _event.getStartDate();
                            var _endDate = _event.getEndDate();
                            var numIntersects = 0;
                            for (var k=0; k<overlappingEvents.length; k++){
                                if (!overlappingEvents[k].equals(_event)){
                                    var a = overlappingEvents[k].getStartDate();
                                    var b = overlappingEvents[k].getEndDate();
                                    if (_startDate.getTime() < b.getTime() && a.getTime() < _endDate.getTime()){
                                        numIntersects++;
                                    }
                                }
                            }

                            if (numIntersects>0){
                                var _numColumns = 1 + 1 + numIntersects;
                                if (_numColumns>numColumns) numColumns = _numColumns;
                            }

                        }
                    }


                    var width = (Math.floor((1/(numColumns))*100));
                    log("Update " + event.getSubject() + " width to " + width + "%");
                    var outerDiv = getDiv(event);
                    outerDiv.style.width = width + "%";


                    for (var j=0; j<overlappingEvents.length; j++){
                        var _event = overlappingEvents[j];
                        var _outerDiv = getDiv(_event);

                        var _width = parseInt(_outerDiv.style.width);
                        if (widths["_"+_width]!=null) _width = widths["_"+_width];
                        if (_width<width) width = _width;

                        _outerDiv.style.width = width + "%";

                        processedEvents.push(_event);
                    }
                }
            }

            processedEvents.push(event);
        }



      //Iterate through single-day events and update position of overlapping events
        processedEvents = [];
        for (var i=0; i<singleDayEvents.length; i++){
            var event = singleDayEvents[i];
            if (!isProcessed(event)){

                var overlappingEvents = getOverlappingEvents(event);
                if (overlappingEvents.length>0){

                    for (var j=0; j<overlappingEvents.length; j++){
                        var _event = overlappingEvents[j];
                        var _outerDiv = getDiv(_event);
                        var _width = parseInt(_outerDiv.style.width);
                        if (widths["_"+_width]!=null) _width = widths["_"+_width];

                        updatePosition(_outerDiv, _event, _width);
                        processedEvents.push(_event);
                    }

                }
            }
            processedEvents.push(event);
        }
        processedEvents = null;




      //Fill any gaps
        for (var i=0; i<singleDayEvents.length; i++){
            var event = singleDayEvents[i];
            var outerDiv = getDiv(event);


            var width = parseInt(outerDiv.style.width);
            if (widths["_"+width]!=null) width = widths["_"+width];
            var numColumns = Math.floor(100/width);

            if (numColumns>1){

                var left = parseInt(outerDiv.style.left);
                var colIndex = Math.floor(left/width)+1;


                log(event.getSubject() + " is in column " + colIndex + "/" + numColumns);

                if (colIndex===(numColumns-1)){
                    var overlappingEvents = getOverlappingEvents(event);
                    var nextEvent = overlappingEvents[overlappingEvents.length-1];
                    var nextDiv = getDiv(nextEvent);
                    var orgWidth = parseInt(outerDiv.style.width);
                    var newWidth = parseInt(nextDiv.style.left) - left;
                    if (newWidth>orgWidth){
                        log("   ++ Updating " + event.getSubject() + " width to " + newWidth + "% (was " + orgWidth + "%)");
                        outerDiv.style.width = newWidth + "%";
                    }
                }

            }
        }

    };


  //**************************************************************************
  //** removeEvent
  //**************************************************************************

    this.removeEvent = function(event){

      //Update event store
        store.remove(event);


      //Remove div
        if (event.numDays()>=1){

            for (var i=1; i<multidayEventsTable.childNodes.length; i++){
                var tr = multidayEventsTable.childNodes[i];
                for (var j=1; j<tr.childNodes.length; j++){
                    var td = tr.childNodes[j];
                    if (td.childNodes.length>0){
                        var div = td.childNodes[0];
                        if (div.event.equals(event)){

                          //Remove event div
                            td.removeChild(div);


                          //Remove colspan
                            var colSpan = td.colSpan;
                            if (colSpan>=2){
                                td.colSpan = 1;
                                var nextSibling = td.nextSibling;
                                var clone = td.cloneNode(true);
                                //var tr = td.parentNode;
                                for (var k=0; k<colSpan; k++){
                                    if (nextSibling){
                                        tr.insertBefore(clone, nextSibling);
                                    }
                                    else{
                                        tr.appendChild(clone);
                                    }
                                }
                            }


                          //Delete the row if we can
                            if (multidayEventsTable.childNodes.length>1){
                                var hasEvents = false;
                                for (var k=1; k<tr.childNodes.length; k++){
                                    if (tr.childNodes[k].childNodes.length>0){
                                        hasEvents = true;
                                        break;
                                    }
                                }

                                if (!hasEvents){
                                    multidayEventsTable.removeChild(tr);
                                }
                            }


                          //Update table height
                            var maxHeight = (multidayEventsTable.childNodes.length-1)*(eventHeight+eventPadding);
                            if (maxHeight==0){
                                multidayRow.style.display = "none";
                                multidayRow.style.visibility = "hidden";
                            }
                            else{
                                multidayEventsTable.parentNode.parentNode.parentNode.style.height = (maxHeight+(eventPadding)) + "px";
                            }


                            return;
                        }
                    }
                }
            }

        }
        else{

          //Remove event div
            var divs = getDivs(event.getStartDate());
            for (var i=0; i<divs.length; i++){
                var div = divs[i];
                if (div.event.equals(event)){
                    var parentNode = div.parentNode;
                    parentNode.removeChild(div);
                    break;
                }
            }


          //Update overlapping events as needed
            var overlappingEvents = getOverlappingEvents(event);
            if (overlappingEvents.length>0){

              //Remove overlapping events
                for (var i=0; i<overlappingEvents.length; i++){
                    for (var j=0; j<divs.length; j++){
                        var div = divs[j];
                        if (div.event.equals(overlappingEvents[i])){
                            var parentNode = div.parentNode;
                            parentNode.removeChild(div);
                            break;
                        }
                    }
                }

              //Add overlapping events
                me.addEvents(overlappingEvents);
            }
        }
    };


  //**************************************************************************
  //** getEvents
  //**************************************************************************
  /** Returns an array of all the events rendered in the view.
   */
    this.getEvents = function(){

        var events = [];

      //Find events that occur with a single day
        var d = new Date(date);
        if (days>1 && d.getDay()>0) d.setDate(d.getDate()-d.getDay());
        for (var i=0; i<days; i++){
            var divs = getDivs(d);

            for (var j=0; j<divs.length; j++){
                var div = divs[j];
                var event = div.event;
                events.push(event);
            }

            d.setDate(d.getDate()+1);
        }



      //Find multi-day events events
        for (var i=1; i<multidayEventsTable.childNodes.length; i++){
            var tr = multidayEventsTable.childNodes[i];
            for (var j=1; j<tr.childNodes.length; j++){
                var td = tr.childNodes[j];
                if (td.childNodes.length>0){
                    var div = td.childNodes[0];
                    var event = div.event;

                    var addEvent = true;
                    for (var k=0; k<events.length; k++){
                        if (events[k].equals(event)){
                            addEvent = false;
                            break;
                        }
                    }

                    if (addEvent) events.push(event);
                }
            }
        }

        return events;
    };


  //**************************************************************************
  //** clear
  //**************************************************************************
  /** Used to remove all the events from the view.
   */
    this.clear = function(){

      //Remove events that occur with a single day
        var d = new Date(date);
        if (days>1 && d.getDay()>0) d.setDate(d.getDate()-d.getDay());
        for (var i=0; i<days; i++){
            var divs = getDivs(d);

            for (var j=0; j<divs.length; j++){
                var div = divs[j];
                var event = div.event;
                store.remove(event);
                var parentNode = div.parentNode;
                parentNode.removeChild(div);
            }

            d.setDate(d.getDate()+1);
        }



      //Remove any multi-day events
        var row = multidayEventsTable.childNodes[0];
        while (row.nextSibling){
            var tr = row.nextSibling;

            for (var j=1; j<tr.childNodes.length; j++){
                var td = tr.childNodes[j];
                if (td.childNodes.length>0){
                    var div = td.childNodes[0];
                    var event = div.event;
                    store.remove(event);
                }
            }

            multidayEventsTable.removeChild(tr);
        }
        multidayRow.style.display = "none";
        multidayRow.style.visibility = "hidden";
    };


  //**************************************************************************
  //** refresh
  //**************************************************************************
  /** Used to re-render all the events in the cell.
   */
    this.refresh = function(){
        var events = me.getEvents();
        me.clear();
        me.addEvents(events);
        me.scrollTo(6.5);
    };


  //**************************************************************************
  //** addSingleDayEvent
  //**************************************************************************
  /** Used to render events that start and end on the same day.
   */
    var addSingleDayEvent = function(event, deferUpdates){

        if (deferUpdates!=true) deferUpdates = false;

      //Find cell used to render event
        var d = event.getStartDate();
        var cellID = (d.getMonth()+1) + "-" + d.getDate() + "-" + d.getFullYear();
        var cell = cells[cellID];
        if (cell==null) return;



        var width = 100;
        var overlappingEvents, rects, leftCoords;



      //Update position and width of any overlapping events
        if (!deferUpdates){

          //Get bounding rectangles of the overlapping events
            rects = [];
            var overlappingEvents = getOverlappingEvents(event);
            for (var i=0; i<overlappingEvents.length; i++){
                var _event = overlappingEvents[i];
                var _outerDiv = getDiv(_event);
                if (_outerDiv!=null){
                    rects.push(_getRect(_outerDiv));
                }
            }


          //Generate list of the left coordinates of any overlapping events
            leftCoords = [];
            for (var i=0; i<rects.length; i++){
                var x = rects[i].left;
                var addCoord = true;
                for (j=0; j<leftCoords.length; j++){
                    if (leftCoords[j]==x){
                        addCoord = false;
                        break;
                    }
                }
                if (addCoord) leftCoords.push(x);
            }
            leftCoords.sort(function(a,b){
                return a-b;
            });



          //Compute width for new event. Note that the current logic for calculating
          //the number of columns is flawed because it relies on overlapping events.
          //The logic in the for loop is a hacky workaround.
            var numColumns = leftCoords.length;
            width = (Math.floor((1/(numColumns+1))*100));
            for (var i=0; i<overlappingEvents.length; i++){
                var _event = overlappingEvents[i];
                var _outerDiv = getDiv(_event);
                if (_outerDiv!=null){
                    var _width = parseInt(_outerDiv.style.width);
                    if (widths["_"+_width]!=null) _width = widths["_"+_width];
                    if (_width<width) width = _width;
                }
            }



          //Update position and width of any overlapping events
            for (var i=0; i<overlappingEvents.length; i++){

                var _event = overlappingEvents[i];
                var _outerDiv = getDiv(_event);
                if (_outerDiv!=null){
                    var _width = parseInt(_outerDiv.style.width);
                    if (widths["_"+_width]!=null) _width = widths["_"+_width];
                    if (width<_width) _width = width;

                    log(event.getSubject()  + " intersects " + _event.getSubject());
                    log("   Moving " + _event.getSubject() + " to 0 and setting width to " + _width + "% (was " + _outerDiv.style.width + ")");

                    _outerDiv.style.width = _width + "%";
                    _outerDiv.style.left = "0px";
                    updatePadding(_outerDiv);
                    if (!deferUpdates) updatePosition(_outerDiv, _event, _width);
                }
            }
        }



      //Create an absolute div within the cell to render the event
        var outerDiv = document.createElement('div');
        outerDiv.style.width = width + "%";
        outerDiv.style.left = "0px";
        updatePadding(outerDiv);
        outerDiv.style.position = "absolute";
        var startTime = event.getStartDate().getHours() + (event.getStartDate().getMinutes()/60);
        var endTime = event.getEndDate().getHours() + (event.getEndDate().getMinutes()/60);
        var y = getVerticalOffset(startTime, cell);
        var h = getVerticalOffset(endTime, cell)-y;
        outerDiv.style.top = y + "px";
        outerDiv.style.height = h + 'px';
        outerDiv.event = event;
        cell.appendChild(outerDiv);


      //Create div used to render the event
        var div = event.createDiv();


      //Wrap event in a table to ensure proper padding (I couldn't figure out how to do it with divs)
        var tbody = createTable();
        outerDiv.appendChild(tbody.parentNode);
        var tr = document.createElement('tr');
        tbody.appendChild(tr);
        var td = document.createElement('td');
        tr.appendChild(td);
        td.style.verticalAlign = "top";
        td.style.width = "100%";
        td.style.height = "100%";
        td.style.padding = ((eventPadding/2)+1) + "px " + eventPadding + "px " + (eventPadding/2) + "px"; //+1 for half-hour border
        td.appendChild(div);


      //Update vertical padding. Each browser pads tables differently. Chrome
      //doesn't apply any padding so the table fits perfectly in the div. IE
      //puts the 1px table below where we expect and adds 1px to the bottom.
      //FF adds 1 px to the bottom. This might have something to do with how
      //browsers interpret the cellpadding and cellspacing attributes.
        if (isIE) {
            td.style.paddingTop = (eventPadding/2) + "px";
            td.style.paddingBottom = (eventPadding+1) + "px";
        }
        if (isFirefox){
            td.style.paddingTop = (eventPadding/2) + "px";
            td.style.paddingBottom = (((eventPadding/2)+1)*2) + "px";
        }



      //Initialize mouse events
        if (event.isEditable()) initDrag(outerDiv, me, holdDelay);
        else{
            outerDiv.onclick = function(e){
                var listener = me.getListener('eventclick');
                if (listener!=null){
                    var callback = listener.callback;
                    var scope = listener.scope;
                    callback.apply(scope, [this.event, this, me, e]);
                }
            };
        }



      //Update position and width of the new div
        if (!deferUpdates){
            if (overlappingEvents.length>0){

              //Update position of the new div as needed
                log("Update " + event.getSubject() + "?");
                updatePosition(outerDiv, event, width);


              //If there are any divs to the right, ensure that the width spans
              //to the closest div
                var rightDiv;
                var r1 = _getRect(outerDiv);
                var _right = r1.right;
                for (var i=0; i<leftCoords.length; i++){
                    if (leftCoords[i]>=_right){


                        for (var j=0; j<rects.length; j++){
                            var x = rects[j].left;
                            if (x==leftCoords[i]){
                                rightDiv = getDiv(overlappingEvents[j]);
                                break;
                            }
                        }

                        break;
                    }
                    if (rightDiv!=null) break;
                }

                if (rightDiv!=null){
                    var _left = parseInt(outerDiv.style.left);
                    var newWidth = parseInt(rightDiv.style.left) - _left;
                    log("   ++ Updating " + event.getSubject() + " width to " + newWidth + "% (was " + width + "%)");
                    outerDiv.style.width = newWidth + "%";
                }

            }
        }
    };


  //**************************************************************************
  //** addMultiDayEvent
  //**************************************************************************
  /** Used to render multi-day events
   */
    var addMultiDayEvent = function(event){

      //Check whether the event is in range
        if (event.getEndDate()<startDate || event.getStartDate()>endDate) return;


        var startColID, endColID;
        var continueLeft = false;
        var continueRight = false;
        var a = javaxt.dhtml.calendar.Utils.getDaysBetween(startDate, event.getStartDate());
        var b = javaxt.dhtml.calendar.Utils.getDaysBetween(startDate, event.getEndDate());
        //console.log(event.getSubject() + " " + a + " --> " + b);

        startColID = a;
        if (a<0){
            startColID = 0;
            continueLeft = true;
        }
        startColID = Math.floor(startColID);



        endColID = startColID+b;
        if (endColID>days-1){
            endColID = days-1;
            continueRight = true;
        }
        endColID = Math.floor(endColID);


        //console.log("use cols " + startColID + " - " + endColID);



      //Find start/end columns in the multiday events table
        var startCol, endCol;
        for (var i=1; i<multidayEventsTable.childNodes.length; i++){ //skip first row
            var tr = multidayEventsTable.childNodes[i];
            var idx = 0;
            for (var j=1; j<tr.childNodes.length; j++){ //skip first column
                var td = tr.childNodes[j];

                if (idx==startColID){
                    if (td.childNodes.length==0){
                        //console.log("Found start!");
                        //console.log(td);
                        startCol = td;
                    }
                    else{
                        i = multidayEventsTable.childNodes.length;
                        break;
                    }
                }

                if (idx==endColID){
                    if (td.childNodes.length==0){
                        //console.log("Found end!");
                        //console.log(td);
                        endCol = td;


                      //check if any cells between the startCol and endCol are occupied
                        if (startCol!=endCol){
                            var previousCol = td.previousSibling;
                            while (previousCol!=startCol){
                                if (previousCol.childNodes.length>0){
                                    startCol = endCol = null;
                                    break;
                                }

                                previousCol = previousCol.previousSibling;
                            }
                        }
                    }


                    i = multidayEventsTable.childNodes.length;
                    break;
                }


                var colSpan = td.colSpan;
                if (colSpan>=2) idx+=colSpan;
                else idx++;
            }
        }



      //Add new row to the multidayEventsTable as needed
        if (!startCol || !endCol){
            var tr = document.createElement("tr");
            multidayEventsTable.appendChild(tr);
            var td = document.createElement("td");
            td.className = "javaxt-cal-multiday-col-spacer";
            tr.appendChild(td);


          //Remove height from spacer col of previous row and set current col height
            if (multidayEventsTable.childNodes.length>1){
                tr.previousSibling.childNodes[0].style.height = '';
            }
            td.style.height = "100%";



          //Add days
            for (var i=0; i<days; i++){
                td = document.createElement('td');
                td.className = "javaxt-cal-multiday-col";
                tr.appendChild(td);

                if (i==startColID) startCol = td;
                if (i==endColID) endCol = td;
            }
        }


      //Add colspan as needed
        var colSpan = (endColID-startColID)+1;
        if (colSpan>=2){
            javaxt.dhtml.calendar.Utils.addColSpan(startCol, colSpan);
        }


      //Add event to the startCol
        var outerDiv = document.createElement('div');
        outerDiv.style.width = "100%";
        outerDiv.style.height = "100%";
        outerDiv.style.position = "absolute";

        var innerDiv = document.createElement('div');
        innerDiv.style.height = "100%";
        var paddingLeft = continueLeft ? "0px" : eventPadding + "px";
        var paddingRight = continueRight ? "0px" : eventPadding + "px";
        innerDiv.style.padding = "0px " + paddingRight + " 0px " + paddingLeft; //Horizontal padding
        innerDiv.style.position = "relative";
        outerDiv.appendChild(innerDiv);

        var div = event.createDiv(continueLeft, continueRight);
        div.style.height = "100%";
        innerDiv.appendChild(div);


      //Wrap the outerdiv to ensure proper overflow
        var wrapper = document.createElement('div');
        wrapper.style.width = "100%";
        wrapper.style.height = eventHeight + "px";
        wrapper.style.position = "relative";
        wrapper.style.marginTop = (multidayEventsTable.childNodes.length>2 ? (eventPadding*2) : 0) + "px";
        wrapper.appendChild(outerDiv);
        wrapper.event = event;
        wrapper.onclick = function(e){
            var listener = me.getListener('eventclick');
            if (listener!=null){
                var callback = listener.callback;
                var scope = listener.scope;
                callback.apply(scope, [this.event, this, me, e]);
            }
        };

        startCol.appendChild(wrapper);


      //Update the visibility of the multiday row
        multidayRow.style.display = '';
        multidayRow.style.visibility = "visible";


      //Update table height
        var numMultiDayEvents = multidayEventsTable.childNodes.length-1;
        var h = numMultiDayEvents*(eventHeight+eventPadding);
        h = (h+(eventPadding*2));
        var multidayEventsDiv = multidayEventsTable.parentNode.parentNode.parentNode;
        multidayEventsDiv.style.height =  h + "px";




      //Check whether the columns are aligned. For some browsers (e.g. Firefox),
      //the vertical scroll bar doesn't show up until the overflow container
      //reaches a certain height. Without the scroll bar, the multiday event
      //columns become misaligned with the cells.
        var h1 = multidayEventsTable.childNodes[0].childNodes[1];
        var c1;
        for (var id in cells) {
            if (cells.hasOwnProperty(id)) {
                c1 = cells[id].parentNode.parentNode;
                break;
            }
        }

      //Update the height of the vertical scroll bar until it becomes visible.
      //This should work assuming the multidayEventsTable and the header
       var orgHeight = h;
        while (h1.offsetWidth>c1.offsetWidth){
            if (h<scrollWidth*2) h = scrollWidth*2;
            else h++;
            multidayEventsDiv.style.height =  h + "px";

          //If the widths don't align after growing to 100px in height, then
          //there's something wrong - possibly a css style issue with borders.
            if (h===100){
                multidayEventsDiv.style.height = orgHeight + "px";
                break;
            }
        }

    };


  //**************************************************************************
  //** getDOM
  //**************************************************************************
    this.getDOM = function(){
        return el;
    };


  //**************************************************************************
  //** next
  //**************************************************************************
    this.next = function(){
        var d = new Date(startDate);
        d.setDate(d.getDate()+days);
        me.setDate(d);
    };


  //**************************************************************************
  //** back
  //**************************************************************************
    this.back = function(){
        var d = new Date(startDate);
        d.setDate(d.getDate()-days);
        me.setDate(d);
    };


  //**************************************************************************
  //** setDate
  //**************************************************************************
    this.setDate = function(d){
        if (d==null) d = getCurrentDate(); //null date is sometimes passed in from the constructor


        if (date!=null){
            if (d.getFullYear()==date.getFullYear() && d.getMonth()==date.getMonth() && d.getDate()==date.getDate()){
                date = d;
                return;
            }
        }


        date = d;
        startDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
        if (days>1 && startDate.getDay()>0) startDate.setDate(startDate.getDate()-startDate.getDay());

        endDate = new Date(startDate);
        endDate.setDate(endDate.getDate()+days);


        renderTable();
        loadEvents();
    };


  //**************************************************************************
  //** getDate
  //**************************************************************************
    this.getDate = function(){
        return date;
    };


  //**************************************************************************
  //** getDateRange
  //**************************************************************************
  /** Returns the start/end dates represented by this view. */

    this.getDateRange = function(){
        return {
            startDate: new Date(startDate),
            endDate: new Date(endDate)
        };
    };


  //**************************************************************************
  //** getTitle
  //**************************************************************************
  /** Returns a title for the current view. */

    this.getTitle = function(){
        if (days==1){
            var month = javaxt.dhtml.calendar.Utils.monthNames[date.getMonth()];
            return (month + " " + date.getDate() + ", " + date.getFullYear());
        }
        else{
            var range = me.getDateRange();
            var startDate = range.startDate;
            var endDate = range.endDate;
            endDate.setDate(endDate.getDate()-1);

            var startMonth = javaxt.dhtml.calendar.Utils.monthNames[startDate.getMonth()];
            var endMonth = javaxt.dhtml.calendar.Utils.monthNames[endDate.getMonth()];

            var a = startMonth + " " + startDate.getDate();
            var b = endDate.getDate() + ", " + endDate.getFullYear();
            if (startDate.getMonth()==endDate.getMonth() && startDate.getFullYear()==endDate.getFullYear()){
                return (a + " - " + b);
            }
            else{
                if (startDate.getFullYear()==endDate.getFullYear()){
                    return (a + " - " + endMonth + " " + b);
                }
                else{
                    return (a + ", " + startDate.getFullYear() + " - " + endMonth + " " + b);
                }
            }
        }
    };


  //**************************************************************************
  //** loadEvents
  //**************************************************************************
    var loadEvents = function(){

        var events = store.getEvents();
        for (var i=0; i<events.length; i++){

            if (events[i].getStartDate().getTime()<endDate.getTime() &&
                events[i].getEndDate().getTime()>=startDate.getTime()){
                me.addEvent(events[i]);
            }
        }
    };


  //**************************************************************************
  //** updatePosition
  //**************************************************************************
  /** Function used to move a div from left to right until we find an area
   *  that doesn't intersect another div.
   */
    var updatePosition = function(div, _event, width){

      //Get bounding rectangle of the div. Subtract 1 pixel for intesection test.
        var r1 = _getRect(div);
        r1 = {
            left: r1.left+1,
            right: r1.right-1,
            top: r1.top+1,
            bottom: r1.bottom-1
        };


      //Check whether the div intersect any other divs. Shift div
      //to the right until we find a
        var divs = getDivs(_event.getStartDate());
        var x = 0;
        while (x<=100){

            var shiftRight = false;
            for (var j=0; j<divs.length; j++){



                if (div==divs[j]){}
                else{


                    var r2 = _getRect(divs[j]);
                    r2 = {
                        left: r2.left+1,
                        right: r2.right-1,
                        top: r2.top+1,
                        bottom: r2.bottom-1
                    };

                    if (intersects(r1, r2)){
                        log("   -- " + _event.getSubject() + " intersects " + divs[j].innerHTML);
                        log(r1.left + " vs " + r2.left);
                        log(r1.right + " vs " + r2.right);
                        shiftRight = true;
                        break;
                    }
                }
            }

            if (shiftRight){
                x++;
                div.style.left = (x*width) + "%";
                updatePadding(div);
                r1 = _getRect(div);
                r1 = {
                    left: r1.left+1,
                    right: r1.right-1,
                    top: r1.top+1,
                    bottom: r1.bottom-1
                };
                log("   -- " + _event.getSubject() + " is now here: " + div.style.left + " (column " + (x+1) + ")");
            }
            else{
                break;
            }
        }

        log("   -- " + _event.getSubject() + " is in column " + (x+1) + "/" + Math.floor(100/width));

        var _colIndex = x+1;
        var _numColumns = Math.floor(100/width);
        var _left = (x*width);
        if (_colIndex==_numColumns){

          //If the div is in the right column, ensure that the width
          //extends all the way to the edge (e.g. make 33% to 34%).
            if (_left+width<100){
                var newWidth = (width+(100-(_left+width)));
                log("   -- Updating " + _event.getSubject() + " width to " + newWidth + "% (was " + width + "%)");
                div.style.width = newWidth + "%";
                widths["_"+newWidth] = width;
            }
        }
    };


  //**************************************************************************
  //** updatePadding
  //**************************************************************************
  /** Used to add/remove left padding to an event div
   */
    var updatePadding = function(outerDiv){

        var td;
        var el = outerDiv;
        while (el.childNodes.length>0){
            el = el.childNodes[0];
            if (el.tagName.toUpperCase()==="TD"){
                td = el;
                break;
            }
        }

        if (td){
            var left = parseInt(outerDiv.style.left);
            if (left===0){
                td.style.paddingLeft = eventPadding+"px";
            }
            else{
                td.style.paddingLeft = "0px";
            }
        }
    };


  //**************************************************************************
  //** sortEvents
  //**************************************************************************
  /** Used to sort events by order of appearance in the cell - left to right,
   *  top to bottom.
   */
    var sortEvents = function(events){

        var _events = [];
        for (var i=0; i<events.length; i++){
            _events.push(events[i]);
        }

        var divs = [];
        for (var id in cells) {
            if (cells.hasOwnProperty(id)) {
                var cell = cells[id];
                for (var i=0; i<cell.childNodes.length; i++){
                    var el = cell.childNodes[i];
                    var tagName = el.tagName.toLowerCase();
                    if (tagName=="div" && el.event!=null) divs.push(el);
                }
            }
        }


        var getRow = function(event){
            var startRow = event.getStartDate().getHours()*2;
            if (event.getStartDate().getMinutes()>=30) startRow+=1;
            return startRow;
        };


        events.sort(function(event1, event2){
            var y1 = getRow(event1);
            var y2 = getRow(event2);

            var div1, div2;
            for (var i=0; i<_events.length; i++){
                if (_events[i].equals(event1)){
                    div1 = divs[i];
                }
                if (_events[i].equals(event2)){
                    div2 = divs[i];
                }

                if (div1!=null && div2!=null) break;
            }
            var x1 = parseInt(div1.style.left);
            var x2 = parseInt(div2.style.left);

            var a = x1 + (y1*1000);
            var b = x2 + (y2*1000);
            return a-b;
        });

        return events;
    };


  //**************************************************************************
  //** getOverlappingEvents
  //**************************************************************************
  /** Returns a list of events that overlap a given event. This method ignores
   *  multi-day events and events that are not in the current view.
   */
    var getOverlappingEvents = function(event){

        var arr = [];
        var overlappingEvents = store.getOverlappingEvents(event);
        for (var i=0; i<overlappingEvents.length; i++){
            var _event = overlappingEvents[i];
            if (_event.numDays()<1){//Ignore mulitday events
                var _div = getDiv(_event);
                if (_div!=null){ //Ignore events that are out of view
                    arr.push(_event);
                }
            }
        }
        return sortEvents(arr);
    };


  //**************************************************************************
  //** getDivs
  //**************************************************************************
  /** Returns an array of event divs for a given day. Ignores multiday events.
   */
    var getDivs = function(date){
        var divs = [];
        var d = new Date(date);
        var cellID = (d.getMonth()+1) + "-" + d.getDate() + "-" + d.getFullYear();
        var cell = cells[cellID];
        if (cell!=null){
            for (var i=0; i<cell.childNodes.length; i++){
                var el = cell.childNodes[i];
                var tagName = el.tagName.toLowerCase();
                if (tagName=="div" && el.event!=null) divs.push(el);
            }
        }
        return divs;
    };


  //**************************************************************************
  //** getDiv
  //**************************************************************************
  /** Returns the div associated with a given event.
   */
    var getDiv = function(event){

        if (event.numDays()>=1){
            //TODO: Find multiday event div...
        }
        else{
            var divs = getDivs(event.getStartDate());
            for (var i=0; i<divs.length; i++){
                var div = divs[i];
                if (div.event.equals(event)) return div;
            }
        }
        return null;
    };


  //**************************************************************************
  //** getCells
  //**************************************************************************
  /** Returns an array of cells - one for each day in the view. A cell is
   *  defined by a date/id and a bounding rectangle. These cells are used
   *  when dragging events.
   */
    this.getCells = function(){
        var arr = [];
        for (var id in cells) {
            if (cells.hasOwnProperty(id)) {
                var div = cells[id];
                var rect = _getRect(div);
                arr.push({
                    id: id,
                    rect: rect
                });
            }
        }
        return arr;
    };


  //**************************************************************************
  //** intersects
  //**************************************************************************
  /** Used to test whether two rectangles intersect.
   */
    var intersects = function(r1, r2) {
      return !(r2.left > r1.right ||
               r2.right < r1.left ||
               r2.top > r1.bottom ||
               r2.bottom < r1.top);
    };


  //**************************************************************************
  //** createTable
  //**************************************************************************
    var createTable = function(){
        var table = document.createElement('table');
        table.style.width = "100%";
        table.style.height = "100%";
        table.cellSpacing = 0;
        table.cellPadding = 0;
        table.style.borderCollapse = "collapse";
        var tbody = document.createElement('tbody');
        table.appendChild(tbody);
        return tbody;
    };


    var _getRect = javaxt.dhtml.calendar.Utils.getRect;
    var initDrag = javaxt.dhtml.calendar.Utils.initDrag;
    var log = function(str){if(debug)console.log(str);};


    init();
};