Если когда то вам приходилось создавать формы с полями типа «Дата», то вам может быть хорошо известно, что средствами html пока ничего путного создать нельзя. Самые ленивые создают просто одно текстовое поле, вроде:
1 |
<input name="get_the_date" type="text" value="01.01.2010" /> |
Ещё часто можно увидеть варианты на эту же тему, где присутствует сразу три поля, которые предлагают выбрать отдельно дату, месяц и год из выпадающих списков тега Select.
Возможно когда-нибудь, html разовьется до того, что создать дату можно будет как то так:
1 |
<input name="get_the_date" type="date" value="01.01.2010" /> |
И тогда выпадающий из под текстового поля календарик порадует нас своей формой, зависящей от фантазии разработчиков того или иного браузера, и позволит нам парой кликов выбрать нужную дату. А пока мы ждем тех славных времен, давайте попробуем призвать на помощь великий и могучий javasctipt, повелевающий DOM.
Чтобы не затягивать повествование, сразу скажу что я хочу получить на выходе. Эта файл каскадных стилей, для формирования внешнего вида календарика, и файл с описанием функции-класса календаря. А DOM структура html кода, вставляющая календарь, пока по моей задумке на бумаге выглядит вот так:
Это контейнер — с заданной шириной (класса — calendarHolder), а внутри элемент INPUT — со строковым отображением даты и контейнер для выпадающего календарика за один месяц (класса — widgetCalendar).
Если отбросить само проектирование, то объявление класса получается такое:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
/*calendar funcs*/ /*объявим массив ссылок на все календари объявляемые на странице */ var calendarsObjs = []; /*эта функция служит для регистрации нового календаря, idobj - id контейна DIV.calendarHolder, а lang - язык (в данном примере ru - для русского, иначе английский), чтобы можно было показать наименования месяцев на понятном пользователю языке*/ function register_new_calendar(idobj, lang) { var obj = document.getElementById(idobj); a = new CalendarObj(obj, lang); calendarsObjs.push(a); return a; } /*ещё одно общая функция, позволяющая найти экземпляр класса по одному из DOM элементов - указанному INPUT или DIV.calendarHolder, параметр index - указывает вернуть сам объект или только его индекс в массиве calendarsObjs */ function find_CalendarObj_class(Obj, index) { for (var i = calendarsObjs.length - 1; i>=0; i--) { if (calendarsObjs[i].htmlContainer == Obj || calendarsObjs[i].inputObj == Obj) return index ? i : calendarsObjs[i]; } return null; } /* сама функция класса календарика, входные параметры такие же как и у функции-регистратора - register_new_calendar */ function CalendarObj(container, lang) { this.htmlContainer = container; this.widget = null; //указатель на контейнер DIV.widgetCalendar this.inputObj = null; //указатель на элемент формы INPUT, где хранится текстовое значение выбранной даты this.lang = lang; //язык, на котором отображаются названия месяцев года /*функция - обработчик некого события, когда мы хотим прятать календарик. Не используется в данном случае, но если вы найдете ей применение - то можете прицепить на какое либо событие в качестве обработчика */ this.hideCalendar = function(ev) { targ = (ev.target) ? ev.target : ev.target = ev.srcElement; obj = find_CalendarObj_class(targ.parentNode, false); if (obj != null) obj.widget.style.display = 'none'; else alert('Calendar object error'); } /*функция - обработчик события onfocus нашего текстового поля. Показывает календарик. Ниже мы прицепим данную функцию как обработчик. Входным параметром является объект Event. */ this.showCalendar = function(ev) { targ = (ev.target) ? ev.target : ev.target = ev.srcElement; obj = find_CalendarObj_class(targ.parentNode, false); if (obj != null) obj.doWidget(0, 0); else alert('Calendar object error'); } /* Функция возвращает объект Date, который соответствует значению введенному в текстовое поле. Если формат даты не удаётся распознать - возвращает текущую дату.*/ this.getDate = function() { var dtStr = this.inputObj.value; var expr = /([\d]{1,2}).([\d]{1,2}).([\d]{4})/; var res = expr.exec(dtStr); var D = new Date(); if (res != null) { var D = new Date(res[3], res[2] - 1 , res[1]); if (D.toString() == 'Invalid Date') D = new Date(); } return D; } /* Функция устанавливает строковое значение в текстовом поле, используя для этого входной параметр DT типа Date */ this.setDate = function(DT) { var mn = DT.getMonth() + 1; var da = DT.getDate(); this.inputObj.value = (da < 10 ? '0' : '') + da + '.' + (mn < 10 ? '0' : '') + mn + '.' + DT.getFullYear(); } /* Тоже устанавливает дату в текстовом поле, но входным параметром является число милисекунд, а также функция прячет календарик - используем эту функцию при выборе значения в календаре */ this.setNHide = function(DTint) { var DT = new Date(DTint); this.setDate(DT); this.widget.style.display = 'none'; } /* функция создаёт html код календарика и показывает его. На входе два параметра, которые предназначены для смещения текущего значения месяца - mn, и года - ye. Задаются как дельта, т.е. +1, -1 или 0, если смещения нет */ this.doWidget = function(mn, ye) { /* mn = +-1 */ var dt = this.getDate(); var currentDate = new Date(dt.getFullYear() + ye, dt.getMonth() + mn, dt.getDate()); var index = find_CalendarObj_class(this.htmlContainer, true); var ws = ""; switch (this.lang) { case 'ru': var mntArray = ['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь']; break; default: var mntArray = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; break; } /* HTML код календарика, помимо календаря, содержит у нас две прокрутки - по месяцам и по годам. Зная индекс экземпляра класса в общем массиве - мы будет вызывать нужную построения календарика на-прямую. */ ws = '<div onclick="calendarsObjs[' + index + '].widget.style.display = \'none\'" class="close">x</div>'; ws += '<div class="clear"> </div>'; ws += '<div style="text-align: center;">'; ws += ' <a class="monthSlide" href="#" onclick="calendarsObjs[' + index + '].doWidget(-1, 0);"><</a>'; ws += ' <div class="monthTab" onclick="calendarsObjs[' + index + '].doWidget(0, 0);">' + mntArray[currentDate.getMonth()] + '</div>'; ws += ' <a class="monthSlide" href="#" onclick="calendarsObjs[' + index + '].doWidget(1, 0);">></a>'; ws += '</div>'; ws += '<div class="clear"> </div>'; ws += '<div style="text-align: center;">'; ws += ' <a class="monthSlide" href="#" onclick="calendarsObjs[' + index + '].doWidget(0, -1);"><</a>'; ws += ' <div class="monthTab" onclick="calendarsObjs[' + index + '].doWidget(0, 0);">' + currentDate.getFullYear() + '</div>'; ws += '<a class="monthSlide" href="#" onclick="calendarsObjs[' + index + '].doWidget(0, 1);">></a>'; ws += '</div>' ws += '<div class="clear"> </div>'; ws += '<div class="month">'; dtm = [currentDate.getMonth(), currentDate.getFullYear()]; dateF = new Date(dtm[1], dtm[0], 1); dateL = new Date(dtm[1], dtm[0] + 1, 1); /*сместимся от первого числа текущего месяца к ближайшему понедельнику. Смещаемся назад по времени, т.к. не хотим потерять неделю текущего месяца. */ wd = dateF.getDay() - 1; switch (wd){ case -1: dateCRS = new Date(dtm[1], dtm[0], -5); break; default: dateCRS = new Date(dtm[1], dtm[0], 1 - wd); } /*Собираем месяц в отдельном аккумуляторе - строке (для оптимизации, можно было сразу и в ws добавлять) */ var out = '<div class="clear"> </div>'; var nowDate = new Date(); var nowDateStr = nowDate.getFullYear() + '/' + (nowDate.getMonth() + 1) + '/' + nowDate.getDate(); /* цикл по неделям */ while (dateCRS.getTime() <= dateL.getTime()) { k = 7; /* цикл по дням недели */ while(k-- > 0) { /* стилями зададим раскраску отдельных дней - текущий день, а также день из другого месяца*/ addStyle = nowDateStr == dateCRS.getFullYear() + '/' + (dateCRS.getMonth() + 1) + '/' + dateCRS.getDate() ? 'curr' : ''; if (dateCRS < dateF || dateCRS >= dateL) addStyle += ' nonM'; /* при выборе для вызовем ф-цию установки даты */ out += '<div class="dayH"><a href="#" onclick="calendarsObjs[' + index + '].setNHide(' + dateCRS.valueOf() + ')"' + ' class="day ' + addStyle + '">' + dateCRS.getDate() + '</a></div>'; dateCRS.setDate(dateCRS.getDate( ) + 1); } out += '<div class="clear"> </div>'; } //HTML календаря собран, объединим все в одной переменной ws += out + '</div>'; /*устанавливаем дату, показываем календарик */ this.setDate(currentDate); this.widget.innerHTML = ws; this.widget.style.display = 'block'; } /* ищем составные части нашего DOM объекта */ for (var i = container.childNodes.length - 1 ; i >= 0; i--) { var t = container.childNodes[i]; if (t.className == 'widgetCalendar' && t.tagName == 'DIV') { t.style.width = this.htmlContainer.style.width; this.widget = t; } else if (t.tagName == 'INPUT' && t.type == 'text') { this.inputObj = t; //когда обнаружен элемент INPUT - приаттачим к нему событие onfocus, как и собирались if (t.addEventListener) t.addEventListener('focus', this.showCalendar, false); else t.attachEvent("onfocus", this.showCalendar); } } /* тут бы логично предупреждение показывать, если inputObj или widget не найден. Но, сейчас это делать не охота :) Если хотите, напишите сами. */ } |
Вот и все! Осталось только добавить немного CSS. Давайте разберемся и с ними.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
/* очень полезный элемент в каждой верстке - вставка такого элемента обеспечит нам гарантированно новую строку, какие бы float элементы у нас не были в текущем контейнере*/ div.calendarHolder .clear { clear:both; height:1px; line-height:1px; font-size:1px; } /* растянем input по ширине calendarHolder */ div.calendarHolder input { width: 100%; text-align: center; } /*область календарика - рамочка, мелкий шрифт, изначально - спрятан */ div.widgetCalendar { position: absolute; border: 1px solid #888; font-size: 10px; font-family: arial; padding: 3px; clear:left; width: 100%; z-index: 10000000; display: none; background-color: #FFF; } /*первый конейнер для формирования дня - нужен для задания общей ширины а именно 100% ширины / 7 дней = 14.28...%*/ div.widgetCalendar .dayH { width: 14%; float: left; text-align: center; padding-top: 1px; display: block; } /*день месяца с фиксированной шириной*/ div.widgetCalendar .day { display: block; width: 15px; text-align: right; color: #000; text-decoration: none; } /*прокрутки для месяца и года*/ div.widgetCalendar .monthTab { font-size: 11px; margin: 0px 0px 5px 0px; width: 50%; clear: none; float: left; } div.widgetCalendar .monthSlide { text-decoration: none; color: #000; font-weight: bold; cursor: pointer; width: 20%; float: left; } /*реакция на перекрытие курсором активных полей (день или прокрутка месяца и года)*/ div.widgetCalendar .day:hover, div.widgetCalendar .monthSlide:hover { background-color:#d8dfea !important; cursor: pointer; } /*дополнительные стили для текущего дня*/ div.widgetCalendar .curr { font-weight: bolder; background-color: #D5D5D5; } /*дополнительные стили для дней из других месяцев, попавших к нам в календарь */ div.widgetCalendar .nonM { color: #eee; } /*кнопочка - закрыть, спецаиальное отрицательное смещение margin позволяется нам заползти в область padding родительского конетейнера и визуально примкнуть к рамочке*/ div.widgetCalendar .close { float:right; clear:both; padding:1px 5px 2px 5px; background-color: #666; margin: 0px; margin-top:-3px; margin-right:-3px; color: #fff; cursor: pointer; } |
Осталось только связать все части в HTML коде. Как это сделать читайте в разделе Сервисы -> Календарь.
Народ подскажите как зделать так что бы можно было выбрать ту дату которая ещё не наступила типо если сёдня 10 июня в календарике можно будет выбрать 11 июля 12 июля и т.д. но сегодняшнюю дату и те даты которые уже прошли выбрать было нельзя!
Помогите! Я ещё вернусь=)
Если глянуть код js виджета (/widgets/calendar/widget-calendar.js), то там основной цикл отрисовки календарика начинается со строки 115. В цикл надо добавить ветвление с условием (dateCRS <= nowDate) и для элементов (дней календарика), которые отвечают условию, из ссылки убрать код onclick, для остальных — оставить все как есть. Получится, что прошедшие уже даты нельзя будет выбрать. Это, конечно, только часть модификаций, которые надо сделать, для полного контроля за вводимой информацией.
admin, я не очень хорошо разбираюсь в java скриптах если тебе не трудно то пожалуйста напиши код =)
Вроде и не трудно… Наверное, я недостаточно замотивирован чтобы помочь тебе. Если уж тебе не охота повозиться с полсотней строк чужого кода, то стоит ли вообще тебе этим заниматься?
Здравствуйте!
Очень замечательный календарик и без всяких заморочек с фреймворками
А не подскажите как прикрутить функцию показа календарика showCalendar
на событие onclick иконки которую я хочу поставить рядом с полем input
Чтобы календарик показывался по клику на иконку
Я еще только учусь
Заранее благодарен за совет
Это довольно просто. Когда вы создали объект DTobj для вашего календаря, см раздел сервисы->календарь, то, вызывая метод данного объекта doWidget(0, 0), вы будете заставлять календарик открыться. Т.е. вам надо будет указать что то вроде:
<img src=’…’ alt=’…’ onclick=’DTobj.doWidget(0, 0);’ />
Параметры метода doWidget — это относительное смещение от текущей даты для месяца и года.
Большое спасибо за совет!
Еще 3 вопроса:
1. Как добавить в календарик верхнюю строку с днями недели?
Я так понял, что в функции HTML кода календарика в том месте где
собирается месяц в аккумуляторе…
Наверное нужно выводить массив с днями в отдельном цикле
перед циклом вывода самой таблицы дней?
Если я ошибаюсь подскажите правильное направление.
2. Вы писали что нужно написать обработчик ошибки если экземпляра класса не окажется…
В каких случаях это может произойти, если мы его сами инициализируем?
3. Захотел «я принарядить» календарик и в стилях прописал в разделе
div.widgetCalendar
background-image: url(images/calendar_background.png);
Но картинка не умещается в календарик как я не уменьшал размеры ( даже до 10px в ширине) и высоте…
Все равно отображается половина картинки. А картинка — это красивая рамка в стиле vista
Заранее благодарен
Отвечаю на вопросы…
1. Дни недели можно вывести, добавив после объявления переменной out соответствующий HTML код. Например, что то вроде
out += ‘<div class=»dayH»>ПН</div>’;
out += ‘<div class=»dayH»>ВТ</div>’;
out += ‘<div class=»dayH»>СР</div>’;
out += ‘<div class=»dayH»>ЧТ</div>’;
out += ‘<div class=»dayH»>ПТ</div>’;
out += ‘<div class=»dayH»>СБ</div>’;
out += ‘<div class=»dayH»>ВС</div>’;
out += ‘<div class=»clear»> </div>’;
2. Как говориться: неисповедимы пути браузера…
3. Должно работать (в firefox работает). Календарик фиксирован по ширине — 150 пикс, высота зависит от кол-ва недель, на которые приходится месяц. Плавающую высоту придется учесть при подборе фона.