Первый пост в моем блоге - https://blog.cdswyda.com/post/2017121010
Календарь элементов управления и многое другое слишком много, чтобы упомянуть, почему мы должны перестроить колесо?
Поскольку большинство элементов управления календарем используются для выбора дат, необходимо отображать в календаре различное содержимое.Таких элементов управления календарем немного, и пробная версия неудовлетворительна.
Итак, создайте еще одно колесо, а теперь приступайте к разработке элемента управления календарем, основанного на использовании механизма компонентов, завершенного ранее.
нужно
Просто организуйте требования следующим образом:
- просмотр месяца
- Поддерживает вставку произвольного содержимого в каждый день календаря.
- Связанные ссылки события
- Получить дату начала и окончания текущего представления календаря
- Установить выбранную дату
Анализ реализации
Во-первых, давайте взглянем на календарь, поставляемый с системой, чтобы увидеть, каковы характеристики календаря.
В месяце от 28 до 31 дня, но для сохранения полной структуры в календаре будут некоторые даты из предыдущего и следующего месяца, поэтому, чтобы подвести итог, месяц должен показывать полные 6 недель дат.
Затем, пока вы получаете дату начала текущего месяца, вы можете рисовать календарь.
Как рассчитать дату начала в представлении календаря текущего месяца? Он был проанализирован ранее и для обеспечения полноты показывает часть количества дней в предыдущем месяце, поэтому необходимо рассчитывать только вперед, начиная с 1-го числа текущего месяца.
开始日期 = 当月1号的日期 - 当月1号的星期
结束日期 = 开始日期 + 42天
Эта проблема проясняется, и я чувствую, что нет никаких серьезных препятствий для реализации такого календаря.
Необходимая структурная подготовка
Сначала создайте базовую структуру, показанную ниже.
в:
- Левая и правая часть головы — персонализированные области для размещения произвольного контента в реальном использовании. Середина используется для отображения текущего месяца и кнопок переключения.
- Нарисуйте весь календарь в основной области с помощью
- Неделя с понедельника по воскресенье или с воскресенья по понедельник отображается в объявлении. Это содержимое не изменится при переключении месяца и может быть подготовлено напрямую.
- tbody используется для рисования переменной даты, и вы можете оставить контейнер пустым, когда будете готовы.
- Область стопы используется для размещения любого контента в реальном использовании.
- Область меню используется для панели, всплывающей при переключении дат
нарисовать календарь
После инициализации структуры календаря можно приступать к рисованию календаря.
Рассчитать даты начала и окончания в месяце
Сначала завершите расчет времени начала и окончания
{
// 初始化当前月份的开始日期和结束日期
_initStartEnd: function () {
// 当月1号
var currMonth = moment(this.currMonth, 'YYYY-MM'),
// 当月1号是周几 the ISO day of the week with 1 being Monday and 7 being Sunday.
firstDay_weekday = currMonth.isoWeekday(),
startDateOfMonth,
endDateOfMonth;
if (!this.dayStartFromSunday) {
// 开始为周一 则向前减少周几的天数-1即为 开始的日期
startDateOfMonth = currMonth.subtract(firstDay_weekday - 1, 'day');
} else {
// 开始为周日 则直接向前周几的天数即可
startDateOfMonth = currMonth.subtract(firstDay_weekday, 'day');
}
endDateOfMonth = startDateOfMonth.clone().add(41, 'day');
this.startDateOfMonth = startDateOfMonth;
this.endDateOfMonth = endDateOfMonth;
}
}
Поскольку нужно обработать много дат, а обработка дат в JavaScript сильно различается в разных браузерах, момент.js используется непосредственно для единообразной обработки даты.
Из-за различных привычек использования неясно, будет ли начало недели понедельником или воскресеньем, поэтому его можно использовать непосредственно в качестве конфигурации.
График дня месяца
Дата начала и дата окончания месяца были рассчитаны выше, поэтому просто используйте обход для рисования.
Поскольку мы используем табличную реализацию, нам нужно рисовать по строкам.
Реализация выглядит следующим образом:
{
// 日历可变部分的渲染
_render: function () {
this._initStartEnd();
var weeks = 6,
days = 7,
curDate = this.startDateOfMonth.clone(),
tr;
var start = this.startDateOfMonth.format('YYYY-MM-DD'),
end = this.endDateOfMonth.format('YYYY-MM-DD');
// 清空 并开始新的渲染
this._clearDays();
this._renderTitle();
for (var i = 0; i < weeks; ++i) {
tr = document.createElement('tr');
tr.className = 'ep-calendar-week';
this._daysBody.appendChild(tr);
for (var j = 0; j < days; ++j) {
// 渲染一天 并递增
this._renderDay(curDate, tr);
curDate.add(1, 'day');
}
}
},
// 每天的渲染
_renderDay: function (date, currTr) {
var td = document.createElement('td'),
tdInner = document.createElement('div'),
text = document.createElement('span'),
day = date.isoWeekday(),
// 返回的月份是0-11
month = date.month() + 1;
tdInner.appendChild(text);
td.appendChild(tdInner);
td.className = 'ep-calendar-date';
tdInner.className = 'ep-calendar-date-inner';
// 完整日期
td.setAttribute('data-date', date.format('YYYY-MM-DD'));
// 对应的iso星期
td.setAttribute('data-isoweekday', day);
// 周末标记text.className
if (day === 6 || day === 7) {
td.className += ' ep-calenday-weekend';
}
// 非本月标记
// substr 在ie8下有问题
// if (month != parseInt(this.currMonth.substr(-2))) {
if (month != parseInt(this.currMonth.substr(5), 10)) {
td.className += ' ep-calendar-othermonth';
}
// 今天标记
if (this.today == date.format('YYYY-MM-DD')) {
td.className += ' ep-calendar-today';
}
// 每天渲染时发生 还未插入页面
var renderEvent = this.fire('cellRender', {
// 当天的完整日期
date: date.format('YYYY-MM-DD'),
// 当天的iso星期
isoWeekday: day,
// 日历dom
el: this.el,
// 当前单元格
tdEl: td,
// 日期文本
dateText: date.date(),
// 日期class
dateCls: 'ep-calendar-date-text',
// 需要注入的额外的html
extraHtml: '',
isHeader: false
});
// 处理对dayText内容和样式的更改
text.innerText = renderEvent.dateText;
text.className = renderEvent.dateCls;
// 添加新增内容
if (renderEvent.extraHtml) {
jQuery(renderEvent.extraHtml).appendTo(tdInner);
}
currTr.appendChild(renderEvent.tdEl);
// 每天渲染后发生 插入到页面
this.fire('afterCellRender', {
date: date.format('YYYY-MM-DD'),
isoWeekday: day,
el: this.el,
tdEl: td,
dateText: text.innerText,
dateCls: text.className,
extraHtml: renderEvent.extraHtml,
isHeader: false
});
}
}
Просто нарисуйте 42 дня с даты начала по порядку.
Для гибкости различные события запускаются в разное время рисования, и соответствующие события могут быть привязаны при использовании для выполнения персонализированных операций.
Также для удобства и гибкости при отрисовке даты в соответствующий dom добавляются соответствующие атрибуты даты и недели.
В этом процессе необходимо отметить, является ли дата выходным, в этом месяце, выбрана ли она, является ли сегодня и так далее.
нарисуй что-нибудь еще
В дополнение к вышесказанному необходимо нарисовать выбор года и месяца, заголовок и т. д., на самом деле это просто изменение содержимого существующих элементов dom и не будет расширяться.
Переключить реализацию месяца
Вышеуказанное было в основном рисовать календарь. Это более просто переключить месяц. Он использует в соответствии с новым месяцем, чтобы пересчитать дату начала, опустошить исходный контент и перенаправить.
{
// 设置月份
setMonth: function (ym) {
var date = moment(ym, 'YYYY-MM');
if (date.isValid()) {
var oldMonth = this.currMonth,
aimMonth = date.format('YYYY-MM');
// 月份变动前
this.fire('beforeMonthChange', {
el: this.el,
oldMonth: oldMonth,
newMonth: aimMonth
});
this.currMonth = aimMonth;
this.render();
// 月份变动后
this.fire('afterMonthChange', {
el: this.el,
oldMonth: oldMonth,
newMonth: aimMonth
});
} else {
throw new Error(ym + '是一个不合法的日期');
}
}
}
Обработка
Событий для обработки много, здесь в качестве индикации используется только щелчок даты.
{
// 初始化事件
_initEvent: function () {
var my = this;
jQuery(this.el)
// 日期单元格
.on('click', '.ep-calendar-date', function (e) {
var date = this.getAttribute('data-date'),
ev = my.fire('dayClick', {
ev: e,
date: date,
day: this.getAttribute('data-isoweekday'),
el: my.el,
tdEl: this
});
// 如果修改事件对象的cancel为true后 则不进行后续的选中操作
if (!ev.cancel) {
my.setSelected(date);
}
})
}
}
Поскольку элемент dom, соответствующий дате, всегда будет добавляться и удаляться, если событие напрямую привязано к элементу dom даты, событие необходимо перепривязывать после каждого добавления, что очень хлопотно.
Вы можете напрямую использовать механизм прокси-сервера событий, чтобы связать событие с домом всего календаря, так что событие нужно будет инициализировать только один раз при его создании, что является простым, эффективным и экономит память.
использовать
Наша основная цель добавления этого элемента управления — поддержка отрисовки произвольного содержимого в календаре.Как его использовать?
var testCalendar = epctrl.init('Calendar', {
el: '#date',
// 资源加载过程中的事件需要直接在这里指定
events: {
beforeSourceLoad: function (e) {
// 资源加载前,在加入我们的皮肤样式文件
e.cssUrl.push('./test-skin.css');
}
}
});
// 日期部分渲染前 支持动态获取数据
testCalendar.on('beforeDateRender', function (e) {
var startDate = e.startDate,
endDate = e.endDate;
// 如果需要动态获取数据
// 则将获取数据的ajax加到事件对象的ajax属性上即可
// 日期渲染的cellRender事件将在ajax成功获取数据后执行
e.ajax = $.ajax({
url: 'getDateInfo.xxx',
// 将当月视图的开始和结束时间传递过去
data: {
start: startDate,
end: endDate
}
});
});
// 控制渲染过程 可插入任意内容或修改原来的内容
testCalendar.on('cellRender', function (e) {
if (!e.isHeader) {
// 如:周五周六则插入周末 否则插入工作日
e.extraHtml = '<div>' + (e.isoWeekday > 5 ? '周末': '工作日') + '</div>';
}
});
Суммировать
Выше приведены основные этапы управления календарем в виде месяца.
Эта реализация календаря основана на расширении базового класса управления, и его необходимая функция — это всего лишь набор механизмов событий, вы можете обратиться кРеализовать собственный механизм событий
Выше анализируются только ключевые этапы и основной код.Для удобства использования и масштабируемости в реальном коде необходимо решить множество проблем. Исходный код и документация следующие, если вам интересно, вы можете прочитать:календарь просмотра месяца