更新后重绘 jQueryUI.datepicker 中的自定义列
Redraw custom column in jQueryUI.datepicker after update
我正在尝试自定义 jQueryUI 的日期选择器来为我的客户做一些很酷的事情,但我被卡住了。我在日历中添加了一个自定义列来显示每周的租金。可悲的是,jQueryUI 的日历通过在每次点击时重新绘制自身来工作,因此我的自定义列被删除,显然如果他们更改月份也会发生这种情况。
我正在寻找最佳实践来检测我的列是否已消失并重新绘制。我希望 jQueryUI.datepicker 在完成绘制时触发某种 "done" 事件,但那没有发生。而且它的所有钩子都在绘制日历之前。感谢任何帮助。
密码
/**
* 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 我制作原型的地方:fiddle
我发现了一些关于自定义列的其他堆栈问题,但 none 到目前为止解决了更新时要做什么的问题。我真的不想使用可能导致一些奇怪行为的 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.
提前致谢!
所以我猜这是可行的。但是当日历出现在行之前时,会导致屏幕上的元素 "flashing"。我也许可以用一些 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);
});
},
我正在尝试自定义 jQueryUI 的日期选择器来为我的客户做一些很酷的事情,但我被卡住了。我在日历中添加了一个自定义列来显示每周的租金。可悲的是,jQueryUI 的日历通过在每次点击时重新绘制自身来工作,因此我的自定义列被删除,显然如果他们更改月份也会发生这种情况。
我正在寻找最佳实践来检测我的列是否已消失并重新绘制。我希望 jQueryUI.datepicker 在完成绘制时触发某种 "done" 事件,但那没有发生。而且它的所有钩子都在绘制日历之前。感谢任何帮助。
密码
/**
* 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 我制作原型的地方:fiddle
我发现了一些关于自定义列的其他堆栈问题,但 none 到目前为止解决了更新时要做什么的问题。我真的不想使用可能导致一些奇怪行为的 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.
提前致谢!
所以我猜这是可行的。但是当日历出现在行之前时,会导致屏幕上的元素 "flashing"。我也许可以用一些 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);
});
},