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

标签 javascript jquery jquery-ui datepicker calendar

我正在尝试自定义 jQueryUI 的日期选择器来为我的客户做一些很酷的事情,但我陷入了困境。我在日历中添加了一个自定义列来显示每周的租金。遗憾的是,jQueryUI 的日历通过在每次点击时重新绘制自身来工作,因此我的自定义列被删除,显然如果他们更改月份也会发生这种情况。

我正在寻找最佳实践来检测我的列是否消失并重新绘制它。我希望 jQueryUI.datepicker 在完成绘制时触发某种“完成”事件,但这并没有发生。所有的钩子(Hook)都是在绘制日历之前。如有任何帮助,我们将不胜感激。

代码

/**
 * 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;
}));

我一直在制作原型(prototype)的 fiddle :fiddle

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

To avoid memory leaks, jQuery removes other constructs such as data and event handlers from the child elements before removing the elements themselves.

提前致谢!

最佳答案

所以我想这可行。但当日历出现在行之前时,会导致元素在屏幕上“闪烁”。我也许可以用一些 CSS 来解决这个问题,但如果有人有更好的主意,请告诉我。与此同时,我更新了原始问题中链接的 fiddle 。

代码

/* 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);
            });
        },

关于javascript - 更新后在 jQueryUI.datepicker 中重绘自定义列,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33000111/

相关文章:

javascript - 嵌套列表,动态菜单 : creating a Next Page button

javascript - 我无法让容器 <div> 的内容正确居中

javascript - 更改页面后停止 jQuery 中的 Ajax 请求

jquery - 如何使用 jQuery UI 组合框创建新值

javascript - 分机 JS 4 : Grid List Filter is NOT updated

javascript - 我需要一个选择内部链接的快捷方式才能使用此功能

javascript - 从下拉列表中删除多个重复选项

javascript - 如何将 jquery ui Slide Ranger 中的最小值和最大值获取到 2 个单独的变量中?

javascript - event.preventDefault 是否正在取消更改事件?

javascript - 是否可以只提取可滚动 div 的可见内容?