更新后在jQueryUI.datepicker中重绘自定义列

时间:2015-10-07 18:46:44

标签: javascript jquery jquery-ui datepicker calendar

我试图自定义jQueryUI的datepicker为我的客户做一些很酷的事情,但我被困了。我已在日历中添加了自定义列,以显示每周的租借费率。遗憾的是,jQueryUI的日历通过在每次点击时重绘自身来实现,因此我的自定义列被删除了,显然如果它们改变月份也会发生这种情况。

我正在寻找最佳做法来检测我的专栏是否已经消失并重新绘制。我希望jQueryUI.datepicker解雇某种"完成"事件完成后,但事情并非如此。所有它的钩子都是在绘制日历之前。任何帮助表示赞赏。

代码

/**
 * A Wrapper for jQueryUI.datepicker.
 */

(function( factory ){
    factory( jQuery );
}( function( $ ){

    function MpCalendar(options) {
        options = options || {}

        this._curInst = null;
        this._defaults = {
            highlightDateCSS: "mpcalendar-highlight",
            weeklyRateCSS: "mpcalendar-weekly-rate",
            weeklyRateHeading: "Rate"
        };

        this.settings = $.extend({}, this._defaults, options);
    };

    $.extend(MpCalendar.prototype , {
        /* Create a new instance of the object. */
        _newInst: function(target) {
            return {
                element: target,
                blockDates: {},
                weeklyRates: [],
                input1: [],
                input2: []
            };
        },

        /* Retrieve a previous instance of the object. */
        _getInst: function(target) {
            try {
                return $.data(target, "mpcalendar");
            } catch(e) {
                throw "Missing instance for this MpCalendar.";
            }
        },

        /* Attach the calendar to the target element */
        _attachMpCalendar: function(target, settings) {
            //Check that we were given a div or span.  We're only making inline calendars.
            var nodeName = target.nodeName.toLowerCase();
            if(nodeName !== ("div" || "span"))
                throw new Error('Target must be a div or span got "'+nodeName+'" instead.');

            var self = this;

            var inst = this._newInst($(target));
            inst.settings = $.extend({}, settings || {}, {
                beforeShowDay: function(date) {
                    return self._beforeShowDay(inst, date);
                },
                onSelect: function(date, datepicker) {
                    return self._onSelect(inst, date, datepicker);
                }
            });

            //Make sure showWeek is true.
            inst.settings.showWeek = true;

            //Make sure we have inputs to use.
            inst.input1 = $(inst.element.data('mpinput1'));
            if(!inst.input1.length)
                throw new Error('Could not find mpinput1.');

            inst.input2 = $(inst.element.data('mpinput2'));
            if(!inst.input2.length)
                throw new Error('Could not find mpinput2.');

            //Initiate block dates found in the settings.
            if(typeof inst.settings.blockDates === "object")
                this._setBlockDates(inst, inst.settings.blockDates);

            //Initiat weekly rates found in the settings.
            if(typeof inst.settings.weeklyRates === "object")
                this._setWeeklyRates(inst, inst.settings.weeklyRates);

            //Initiate datepicker.
            inst.element.datepicker(inst.settings);

            //Draw extra rates column.
            this._attachRates(inst);

            //Store our instance.
            $.data(target, "mpcalendar", inst);
        },

        /* Set block dates with the given list of dates */
        _setBlockDatesMpCalendar: function(target, dates) {
            if(typeof dates !== "object")
                throw new Error('Expected dates to be an "object" got "' + typeof dates + '" instead.');

            var inst = this._getInst(target);

            this._setBlockDates(inst, dates);
        },

        /* Add a given date to the block list */
        _addBlockDateMpCalendar: function(target, date, status) {
            var inst = this._getInst(target);

            this._addBlockDate(inst, date, status);
        },

        /* Remove a given date from the block list */
        _removeBlockDateMpCalendar: function(target, date) {
            var inst = this._getInst(target);

            this._removeBlockDate(inst, date);
        },

        /* Set Weekly Rates with the given list of rates */
        _setWeeklyRatesMpCalendar: function(target, rates) {
            if(!(Array.isArray(rates)))
                throw new Error('Expected rates to be an "array" got "' + typeof rates + '" instead.');

            var inst = this._getInst(target);

            this._setWeeklyRates(inst, rates);
        },

        /* Set the Rate for a single Week */
        _setWeeklyRateMpCalendar: function(target, week, rate) {
            if(typeof week !== "number")
                week = parseInt(week);

            if(typeof rate !== "number")
                rate = parseFloat(rate);

            var inst = this._getInst(target);

            this._setWeeklyRate(inst, week, rate);
        },

        /**
         * Return an array of Date objects contianing the dates selected on the calendar.
         * 
         * @param {object} target
         * @returns {Array}
         */
        _getSelectedDatesMpCalendar: function(target) {
            var inst = this._getInst(target);

            return this._getSelectedDates(inst);
        },

        /**
         * Return the CSS Class used for the specified date or false if the date is not blocked.
         * 
         * @param {object} target
         * @param {Date} date
         * @returns {string}
         */
        _isBlockedDateMpCalendar: function(target, date) {
            var inst = this._getInst(target);

            return this._isBlockedDate(inst, date);
        },

        /* Attach our custom weekly rates column */
        _attachRates: function(inst) {
            var self = this;

            //Attach header and empty rates.
            var heading = $('<th>'+ this.settings.weeklyRateHeading +'</th>');
            var tdata = $('<td class="'+this.settings.weeklyRateCSS+'"></td>');
            inst.element.find('.ui-datepicker-calendar thead tr').append(heading);
            inst.element.find('.ui-datepicker-calendar tbody tr').append(tdata);

            inst.element.find('td.ui-datepicker-week-col').each(function(){
                var week = parseInt($(this).text());
                var rate = inst.weeklyRates[week] || "Test";

                $(this).closest('tr').find('.'+ self.settings.weeklyRateCSS).html(rate);
            });
        },

        _isBlockedDate: function(inst, date) {
            if(!(date instanceof Date))
                throw new Error('Expected date to be instance of Date.');

            try {
                var vacancyStatus = inst.blockDates[date.getFullYear()][date.getMonth()][date.getDate()] || false;

                return vacancyStatus;
            } catch(e) {

            }

            return false;
        },

        _getSelectedDates: function(inst) {
            var dates = [];

            try {
                var date1 = $.datepicker.parseDate($.datepicker._defaults.dateFormat, inst.input1.val());
                var date2 = $.datepicker.parseDate($.datepicker._defaults.dateFormat, inst.input2.val());

                if((date1 || date2) === null)
                    return dates;

                while(date1 <= date2) {
                    dates.push(new Date(date1));
                    date1.setDate(date1.getDate() + 1);
                }
            } catch(e) {
                //Guess we don't have any dates.
            }

            return dates;
        },

        _setBlockDates: function(inst, dates) {
            inst.blockDates = {};

            for(var date in dates) {
                if(typeof dates[date] !== 'string')
                    continue;

                this._addBlockDate(inst, date, dates[date]);
            }
        },

        _setWeeklyRates: function(inst, rates) {
            inst.weeklyRates = [];

            for(var week in rates) {
                var rate = rates[week];

                if(typeof week !== 'number')
                    week = parseInt(week);

                if(typeof rate !== 'number')
                    rate = parseFloat(rate);

                this._setWeeklyRate(inst, week, rate);
            }
        },

        _removeBlockDate: function(inst, date) {
            try {
                var datetime = new Date(date);
                var day = datetime.getDate();
                var month = datetime.getMonth();
                var year = datetime.getFullYear();

                delete inst.blockDates[year][month][day];
            } catch(e) {
                //The date probably never existed any way.
            }
        },

        _addBlockDate: function(inst, date, status) {
            if(typeof status !== "string")
                throw new Error('Expected class name to be typeof "string" got "' + typeof status + '".');

            try {
                var datetime = new Date(date);

                var day = datetime.getDate();
                var month = datetime.getMonth();
                var year = datetime.getFullYear();

                if(typeof inst.blockDates[year] !== "object")
                    inst.blockDates[year] = {};

                if(typeof inst.blockDates[year][month] !== "object")
                    inst.blockDates[year][month] = {};

                inst.blockDates[year][month][day] = status;
            } catch(e) {
                throw new Error('Error adding block date: "' + e.message + '".');
            }
        },

        _setWeeklyRate: function(inst, week, rate) {
            inst.weeklyRates[week] = rate;
        },

        /* Function attached to datepicker's beforeShowDay, handles showing blocked dates and range selection */
        _beforeShowDay: function(inst, date) {
            var cssClasses = [];

            try {
                var vacancyStatus = inst.blockDates[date.getFullYear()][date.getMonth()][date.getDate()];

                if(vacancyStatus !== undefined)
                    cssClasses.push(vacancyStatus);
            } catch(e) {
                //There is no blockDate set.
            }

            try {
                var date1 = $.datepicker.parseDate($.datepicker._defaults.dateFormat, inst.input1.val());
                var date2 = $.datepicker.parseDate($.datepicker._defaults.dateFormat, inst.input2.val());

                var highlight = ((date.getTime() === date1.getTime()) || (date2 && date >= date1 && date <= date2)) ? this.settings.highlightDateCSS : '';

                cssClasses.push(highlight);
            } catch(e) {
                //Oh well.
            }

            if(cssClasses.length > 0)
                return [true, cssClasses.join(' '), null];

            return [true, '', null];
        },

        /* Function attached to datepicker's onSelect, allows for rangeselection */
        _onSelect: function(inst, dateText, datepicker) {
            var date1 = $.datepicker.parseDate($.datepicker._defaults.dateFormat, inst.input1.val());
            var date2 = $.datepicker.parseDate($.datepicker._defaults.dateFormat, inst.input2.val());
            var selectedDate = $.datepicker.parseDate($.datepicker._defaults.dateFormat, dateText);


            if (!date1 || date2) {
                inst.input1.val(dateText);
                inst.input2.val("");
                inst.element.datepicker('refresh');
            } else if( selectedDate < date1 ) {
                inst.input2.val( inst.input1.val() );
                inst.input1.val( dateText );
                inst.element.datepicker('refresh');
            } else {
                inst.input2.val(dateText);
                inst.element.datepicker('refresh');
            }
        },

        /* Because we are wrapping datepicker, this handles jQuery calls to internal functions for both MpCalendar and datepicker */
        _callFunction: function(target, option) {
            var otherArgs = Array.prototype.slice.call(arguments, 2);

            if(typeof this["_"+option+"MpCalendar"] === "function")
                return this["_"+option+"MpCalendar"].apply(this, [target].concat(otherArgs));

            var inst = this._getInst(target);
            inst.element.datepicker.apply(inst.element.datepicker(), [option].concat(otherArgs));
        }
    });

    //jQuery extension for using MpCalendar.
    $.fn.mpcalendar = function(options) {
        var otherArgs = Array.prototype.slice.call(arguments, 1);

        //If they are calling for one of our static methods, pass the call to MpCalendar and return the value.
        if(typeof options === "string" && (options === "isBlockedDate" || options === "getSelectedDates"))
            return $.mpcalendar["_"+options+"MpCalendar"].apply($.mpcalendar, [ this[0] ].concat(otherArgs));

        //Else, call the appropriate function and return.
        return this.each( function() {
            typeof options === "string" ?
                $.mpcalendar._callFunction.apply($.mpcalendar, [ this, options ].concat(otherArgs)) :
                $.mpcalendar._attachMpCalendar(this, options);
        });
    };

    $.mpcalendar = new MpCalendar();

    return $.mpcalendar;
}));

我在小提琴原型中的小提琴:fiddle

我发现了一些关于自定义列的其他堆栈问题,但到目前为止还没有一个问题解决了如何处理更新的问题。我真的不想使用可能导致一些奇怪行为的setIntval()。并且我不确定在删除时附加事件是否有效,datepicker会在附加新绘制的日历之前在包含div上调用.empty(),但这并不意味着我的删除事件会在日历甚至存在之前开始绘图?或者可能根本不绘制.empty()

  

为了避免内存泄漏,jQuery会在删除元素本身之前从子元素中删除其他构造(如数据和事件处理程序)。

提前致谢!

1 个答案:

答案 0 :(得分:0)

所以我认为这是有效的。但是当日历出现在行之前时,屏幕上的元素会“闪烁”。我或许可以通过一些CSS解决这个问题,但如果有人有更好的想法,请告诉我。与此同时,我更新了原始问题中链接的小提琴。

代码

/* Attach our custom weekly rates column */
        _attachRates: function(inst) {
            var self = this;

            //Attach header and empty rates.
            var heading = $('<th>'+ this.settings.weeklyRateHeading +'</th>');
            var tdata = $('<td class="'+this.settings.weeklyRateCSS+'"></td>');
            inst.element.find('.ui-datepicker-calendar thead tr').append(heading);
            inst.element.find('.ui-datepicker-calendar tbody tr').append(tdata);

            inst.element.find('td.ui-datepicker-week-col').each(function(){
                var week = parseInt($(this).text());
                var rate = inst.weeklyRates[week] || "Test";

                $(this).closest('tr').find('.'+ self.settings.weeklyRateCSS).html(rate);
            });

            inst.element.find('.ui-datepicker-calendar').first().on('remove', function(){
                $(this).off("remove");
                setTimeout(function(){
                    self._attachRates(inst);
                }, 1);
            });
        },