    /**
     * @class Ext.ensible.cal.DayBodyView
     * @extends Ext.ensible.cal.CalendarView
     * This is the scrolling container within the day and week views where non-all-day events are displayed.
     * Normally you should not need to use this class directly -- instead you should use {@link Ext.ensible.cal.DayView DayView}
     * which aggregates this class and the {@link Ext.ensible.cal.DayHeaderView DayHeaderView} into the single unified view
     * presented by {@link Ext.ensible.cal.CalendarPanel CalendarPanel}.
     * @constructor
     * @param {Object} config The config object
     */
    Ext.ensible.cal.DayBodyView = Ext.extend(Ext.ensible.cal.CalendarView, {
        //private
        dayColumnElIdDelimiter: '-day-col-',
        hourIncrement: 60,

        //private
        initComponent : function(){
            Ext.ensible.cal.DayBodyView.superclass.initComponent.call(this);

            if(this.readOnly === true){
                this.enableEventResize = false;
            }
            this.incrementsPerHour = this.hourIncrement / this.ddIncrement;
            this.minEventHeight = this.minEventDisplayMinutes / (this.hourIncrement / this.hourHeight);

            this.addEvents({
                /**
                 * @event beforeeventresize
                 * Fires after the user drags the resize handle of an event to resize it, but before the resize operation is carried out.
                 * This is a cancelable event, so returning false from a handler will cancel the resize operation.
                 * @param {Ext.ensible.cal.DayBodyView} this
                 * @param {Ext.ensible.cal.EventRecord} rec The original {@link Ext.ensible.cal.EventRecord record} for the event that was resized
                 * @param {Object} data An object containing the new start and end dates that will be set into the event record if the
                 * event is not canceled. Format of the object is: {StartDate: [date], EndDate: [date]}
                 */
                beforeeventresize: true,
                /**
                 * @event eventresize
                 * Fires after the user has drag-dropped the resize handle of an event and the resize operation is complete. If you need
                 * to cancel the resize operation you should handle the {@link #beforeeventresize} event and return false from your handler function.
                 * @param {Ext.ensible.cal.DayBodyView} this
                 * @param {Ext.ensible.cal.EventRecord} rec The {@link Ext.ensible.cal.EventRecord record} for the event that was resized
                 * containing the updated start and end dates
                 */
                eventresize: true,
                /**
                 * @event dayclick
                 * Fires after the user clicks within the view container and not on an event element. This is a cancelable event, so
                 * returning false from a handler will cancel the click without displaying the event editor view. This could be useful
                 * for validating that a user can only create events on certain days.
                 * @param {Ext.ensible.cal.DayBodyView} this
                 * @param {Date} dt The date/time that was clicked on
                 * @param {Boolean} allday True if the day clicked on represents an all-day box, else false. Clicks within the
                 * DayBodyView always return false for this param.
                 * @param {Ext.Element} el The Element that was clicked on
                 */
                dayclick: true
            });
        },

        //private
        initDD : function(){
            var cfg = {
                view: this,
                createText: this.ddCreateEventText,
                moveText: this.ddMoveEventText,
                resizeText: this.ddResizeEventText,
                ddIncrement: this.ddIncrement,
                ddGroup: this.ddGroup || this.id+'-DayViewDD'
            };

            this.el.ddScrollConfig = {
                // scrolling is buggy in IE/Opera for some reason.  A larger vthresh
                // makes it at least functional if not perfect
                vthresh: Ext.isIE || Ext.isOpera ? 100 : 40,
                hthresh: -1,
                frequency: 50,
                increment: 100,
                ddGroup: this.ddGroup || this.id+'-DayViewDD'
            };

            this.dragZone = new Ext.ensible.cal.DayViewDragZone(this.el, Ext.apply({
                containerScroll: true
            }, cfg));

            this.dropZone = new Ext.ensible.cal.DayViewDropZone(this.el, cfg);
        },

        //private
        refresh : function(reloadData){
            Ext.ensible.log('refresh (DayBodyView)');
            var top = this.el.getScroll().top;

            Ext.ensible.cal.DayBodyView.superclass.refresh.call(this, reloadData);

            // skip this if the initial render scroll position has not yet been set.
            // necessary since IE/Opera must be deferred, so the first refresh will
            // override the initial position by default and always set it to 0.
            if(this.scrollReady){
                this.scrollTo(top);
            }
        },

        /**
         * Scrolls the container to the specified vertical position. If the view is large enough that
         * there is no scroll overflow then this method will have no affect.
         * @param {Number} y The new vertical scroll position in pixels
         * @param {Boolean} defer (optional) True to slightly defer the call, false to execute immediately.
         * This method will automatically defer itself for IE and Opera (even if you pass false) otherwise
         * the scroll position will not update in those browsers. You can optionally pass true, however, to
         * force the defer in all browsers, or use your own custom conditions to determine whether this is needed.
         * Note that this method should not generally need to be called directly as scroll position is managed internally.
         */
        scrollTo : function(y, defer){
            defer = defer || (Ext.isIE || Ext.isOpera);
            if(defer){
                (function(){
                    this.el.scrollTo('top', y);
                    this.scrollReady = true;
                }).defer(10, this);
            }
            else{
                this.el.scrollTo('top', y);
                this.scrollReady = true;
            }
        },

        // private
        afterRender : function(){
            if(!this.tpl){
                this.tpl = new Ext.ensible.cal.DayBodyTemplate({
                    id: this.id,
                    dayCount: this.dayCount,
                    showTodayText: this.showTodayText,
                    todayText: this.todayText,
                    showTime: this.showTime,
                    showHourSeparator: this.showHourSeparator,
                    viewStartHour: this.viewStartHour,
                    viewEndHour: this.viewEndHour,
                    hourIncrement: this.hourIncrement,
                    hourHeight: this.hourHeight
                });
            }
            this.tpl.compile();

            this.addClass('ext-cal-body-ct');

            Ext.ensible.cal.DayBodyView.superclass.afterRender.call(this);

            // default scroll position to scrollStartHour (7am by default) or min view hour if later
            var startHour = Math.max(this.scrollStartHour, this.viewStartHour),
                scrollStart = Math.max(0, startHour - this.viewStartHour);

            if(scrollStart > 0){
                this.scrollTo(scrollStart * this.hourHeight);
            }
        },

        // private
        forceSize: Ext.emptyFn,

        // private -- called from DayViewDropZone
        onEventResize : function(rec, data){
            if(this.fireEvent('beforeeventresize', this, rec, data) !== false){
                var D = Ext.ensible.Date,
                    start = Ext.ensible.cal.EventMappings.StartDate.name,
                    end = Ext.ensible.cal.EventMappings.EndDate.name;

                if(D.compare(rec.data[start], data.StartDate) === 0 &&
                    D.compare(rec.data[end], data.EndDate) === 0){
                    // no changes
                    return;
                }
                rec.set(start, data.StartDate);
                rec.set(end, data.EndDate);
                this.onEventUpdate(null, rec);

                this.fireEvent('eventresize', this, rec);
            }
        },

        // inherited docs
        getEventBodyMarkup : function(){
            if(!this.eventBodyMarkup){
                this.eventBodyMarkup = ['{Title}',
                    '',
                        ' ',
                    '',
                    '',
                        ' ',
                    ''
    //                '',
    //                    ' ',
    //                '',
    //                '',
    //                    ' ',
    //                ''
                ].join('');
            }
            return this.eventBodyMarkup;
        },

        // inherited docs
        getEventTemplate : function(){
            if(!this.eventTpl){
                this.eventTpl = !(Ext.isIE || Ext.isOpera) ?
                    new Ext.XTemplate(
                        '',
                            '', this.getEventBodyMarkup(), '',
                            this.enableEventResize ? ' ' : '',
                        ''
                    )
                    : new Ext.XTemplate(
                        '',
                            ' ',
                            '',
                                '',
                                    this.getEventBodyMarkup(),
                                '',
                                this.enableEventResize ? ' ' : '',
                            '',
                            ' ',
                        ''
                    );
                this.eventTpl.compile();
            }
            return this.eventTpl;
        },

        /**
         * Returns the XTemplate that is bound to the calendar's event store (it expects records of type
         * {@link Ext.ensible.cal.EventRecord}) to populate the calendar views with all-day events.
         * Internally this method by default generates different markup for browsers that support CSS border radius
         * and those that don't. This method can be overridden as needed to customize the markup generated.
         * Note that this method calls {@link #getEventBodyMarkup} to retrieve the body markup for events separately
         * from the surrounding container markup.  This provdes the flexibility to customize what's in the body without
         * having to override the entire XTemplate. If you do override this method, you should make sure that your
         * overridden version also does the same.
         * @return {Ext.XTemplate} The event XTemplate
         */
        getEventAllDayTemplate : function(){
            if(!this.eventAllDayTpl){
                var tpl, body = this.getEventBodyMarkup();

                tpl = !(Ext.isIE || Ext.isOpera) ?
                    new Ext.XTemplate(
                        '',
                            body,
                        ''
                    )
                    : new Ext.XTemplate(
                        '',
                        '',
                            '',
                                '',
                                    body,
                                '',
                            '',
                        ''
                    );
                tpl.compile();
                this.eventAllDayTpl = tpl;
            }
            return this.eventAllDayTpl;
        },

        // private
        getTemplateEventData : function(evt){
            var M = Ext.ensible.cal.EventMappings,
                extraClasses = [this.getEventSelectorCls(evt[M.EventId.name])],
                data = {},
                colorCls = 'x-cal-default',
                title = evt[M.Title.name],
                fmt = Ext.ensible.Date.use24HourTime ? 'G:i ' : 'g:ia ',
                recurring = evt[M.RRule.name] != '';

            this.getTemplateEventBox(evt);

            if(this.calendarStore && evt[M.CalendarId.name]){
                var rec = this.calendarStore.getById(evt[M.CalendarId.name]);
                if(rec){
                    colorCls = 'x-cal-' + rec.data[Ext.ensible.cal.CalendarMappings.ColorId.name];
                }
            }
            colorCls += (evt._renderAsAllDay ? '-ad' : '') + (Ext.isIE || Ext.isOpera ? '-x' : '');
            extraClasses.push(colorCls);

            if(this.getEventClass){
                var rec = this.getEventRecord(evt[M.EventId.name]),
                    cls = this.getEventClass(rec, !!evt._renderAsAllDay, data, this.store);
                extraClasses.push(cls);
            }

            data._extraCls = extraClasses.join(' ');
            data._isRecurring = evt.Recurrence && evt.Recurrence != '';
            data._isReminder = evt[M.Reminder.name] && evt[M.Reminder.name] != '';
            data.Title = (evt[M.IsAllDay.name] ? '' : evt[M.StartDate.name].format(fmt)) + (!title || title.length == 0 ? this.defaultEventTitleText : title);

            return Ext.applyIf(data, evt);
        },

        // private
        getEventPositionOffsets: function(){
            return {
                top: 1,
                height: -2
            }
        },

        // private
        getTemplateEventBox : function(evt){
            var heightFactor = this.hourHeight / this.hourIncrement,
                start = evt[Ext.ensible.cal.EventMappings.StartDate.name],
                end = evt[Ext.ensible.cal.EventMappings.EndDate.name],
                startOffset = Math.max(start.getHours() - this.viewStartHour, 0),
                endOffset = Math.min(end.getHours() - this.viewStartHour, this.viewEndHour - this.viewStartHour),
                startMins = startOffset * this.hourIncrement,
                endMins = endOffset * this.hourIncrement,
                viewEndDt = end.clearTime(true).add(Date.HOUR, this.viewEndHour),
                evtOffsets = this.getEventPositionOffsets();

            if(start.getHours() >= this.viewStartHour){
                // only add the minutes if the start is visible, otherwise it offsets the event incorrectly
                startMins += start.getMinutes();
            }
            if(end <= viewEndDt){
                // only add the minutes if the end is visible, otherwise it offsets the event incorrectly
                endMins += end.getMinutes();
            }

            evt._left = 0;
            evt._width = 100;
            evt._top = startMins * heightFactor + evtOffsets.top;
            evt._height = Math.max(((endMins - startMins) * heightFactor), this.minEventHeight) + evtOffsets.height;
        },

        // private
        renderItems: function(){
            var day = 0, evts = [];
            for(; day < this.dayCount; day++){
                var ev = emptyCells = skipped = 0,
                    d = this.eventGrid[0][day],
                    ct = d ? d.length : 0,
                    evt;

                for(; ev < ct; ev++){
                    evt = d[ev];
                    if(!evt){
                        continue;
                    }
                    var item = evt.data || evt.event.data,
                        M = Ext.ensible.cal.EventMappings,
                        ad = item[M.IsAllDay.name] === true,
                        span = Ext.ensible.Date.diffDays(item[M.StartDate.name], item[M.EndDate.name]) > 0,
                        renderAsAllDay = ad || span;

                    if(renderAsAllDay){
                        // this event is already rendered in the header view
                        continue;
                    }
                    Ext.apply(item, {
                        cls: 'ext-cal-ev',
                        _positioned: true
                    });
                    evts.push({
                        data: this.getTemplateEventData(item),
                        date: this.viewStart.add(Date.DAY, day)
                    });
                }
            }

            // overlapping event pre-processing loop
            var i = j = 0, overlapCols = [], l = evts.length, prevDt;
            for(; i -1){
                    var dt = this.getDateFromId(el.id, this.dayElIdDelimiter);
                    this.onDayClick(Date.parseDate(dt, 'Ymd'), true, Ext.get(this.getDayId(dt)));
                    return;
                }
            }
            var day = this.getDayAt(e.xy[0], e.xy[1]);
            if(day && day.date){
                this.onDayClick(day.date, false, null);
            }
        }
    });

    Ext.reg('extensible.daybodyview', Ext.ensible.cal.DayBodyView);