(function( $ ) { classes.Calendar = new Class.create(); classes.Calendar.prototype = { /** * replace - селектор или объект, который будет заменён календарём, * options - список настроек, которые будут объеденены со стандартными */ initialize: function( replace, options ) { // I love IE 7,8 :) $( document ).ready( $.proxy( this, '_init', replace, options ) ); }, /** * Инициализация календаря, проводится по $( document ).ready() */ _init: function( replace, options ) { if( !this._setObject( replace, options ) ) { return false; } this._initConst(); $.extend( true, this, options ); this._setTriggerList(); this._setChooseRange( this.min_date, this.max_date ); this._generateShortMonthName(); // январь => янв, февраль => фев this._buildBlock(); // создание каркаса календаря this._observe(); // обработчики событий this.loaded = {}; // список загруженных лет for( var year in this.event_list ) { if( year = parseInt( year, 10 ) ) { this.loaded[ year ] = true; } } }, /** * Создаёт из массива месяцев, массив укороченных месяцев * ( январь => янв, февраль => фев ... ) * для представления "Год" */ _generateShortMonthName: function() { this.msg.short_month_name = []; for( var i = 0; i < 12; ++ i ) { this.msg.short_month_name.push( this.msg.month_name[ i ].slice( 0, 3 ) ); } }, /** * Определение DOM-объекта, который станет календарём * Если в качестве replace задан не задан DOM объект, и при этом * заданы options.create - будет создан новый объект с id равным replace, * который будет помещён в anchor ( либо если не задано в document.body ) */ _setObject: function( replace, options ) { this.replace = $id( replace ); if( !this.replace ) { if( (typeof options == 'object') && options.create ) { var anchor = options.anchor ? $( options.anchor ).get(0) : $( document.body ); if( !anchor ) { return false; } $( anchor ).append( '
' ); this.replace = $( '#' + replace, anchor ).get(0); } } return this.replace; }, /** * эта функция помещает уже существующую функцию в новую, в которой вызываются * все назначенные через $( __календарь__ ).bind обработчики */ _triggerFn: function( name ) { var that = this, fn = that[ name ]; return function() { var result = fn.apply( that, arguments ), trigger = $( that ).triggerHandler( name, that.trigger_data[ name ], arguments ); // если функция-триггер должна заменить результат функции, она (триггер) должна // вернуть объект с полем __result, которое и заменит результат if( (typeof trigger == 'object') && (typeof trigger.__result !== 'undefined') ) { return trigger.__result; } return result; } }, /** * Помещение существующих функций заданных в .trigger_list в "обёртки" для $().bind() */ _setTriggerList: function() { if( this.trigger_function ) { for( var i = 0, n = this.trigger_list.length; i < n; ++ i ) { var name = this.trigger_list[ i ]; if( this[ name ] instanceof Function ) { this[ name ] = this._triggerFn( this.trigger_list[ i ] ); } } } }, /** * Функция задаёт минимальную и максимальную даты для просмотра и выбора */ _setChooseRange: function( min, max ) { this.min_date = this._convertDateToObject( min ); this.max_date = this._convertDateToObject( max ); }, /** * Постройка основного каркаса календаря */ _buildBlock: function() { var data = {}; for( var name in this.b_class ) { data[ name ] = this.b_class[ name ] = this.class_prefix + this.b_class[ name ]; } this.section_mode = 'month'; for( var name in this.msg ) { data[ 'msg_' + name ] = this.msg[ name ]; } this._data = data; $( this.replace ) .html( this.templates.block.evaluate( data ) ) .addClass( data.calendar ) .addClass( data[ (this.can_set_date ? 'can_set' : 'cant_set' ) + '_date'] ); if( this.drop && !this.field ) { this.drop = false; } if( this.drop ) { $( this.replace ) .hide() .css( 'position', 'absolute' ); } this.header = $id( '.' + data.header, this.replace ); this.section = $id( '.' + data.section, this.replace ); this.day_chooser = $id( '.' + data.day_chooser, this.replace ); this.section_chooser = $id( '.' + data.section_chooser, this.replace ); this.day_list = $id( '.' + data.day_list, this.replace ); this.week_place = $id( '.' + data.week, this.replace ); this.arrow_list = $( '.' + data.arrow, this.replace ); this.arrow_back = this.arrow_list.filter( '.' + data.back ); this.arrow_next = this.arrow_list.filter( '.' + data.next ); this._buildMonth(); this._buildWeekDay(); }, /** * Подсчёт числа дней в месяце */ _getDayInMonth: function( year, month ) { return 32 - (new Date( year, month, 32).getDate()); }, /** * Постройка строки дней недели */ _buildWeekDay: function() { var week_html = ''; for( var i = 1; i <= 7; ++ i ) { week_html += this.templates.week_day.evaluate( { week_day_class: this.b_class.week_day, week_day_inner: this.b_class.week_day_inner, week_day_name: this.msg.week_day_name[ i ] } ) } $( this.week_place ).html( week_html ); }, _setMonthSection: function() { $( this.day_chooser ).show(); $( this.section_chooser ).hide(); this.section_mode = 'month'; this._buildMonth(); }, /** * Постройка представления "Месяц" (список дней) */ _buildMonth: function() { var y = this.current.year, m = this.current.month this.section_mode = 'month'; this._buildMonthSection( y, m ); this._buildDayList( y, m ); this.current_day_item = $( '.' + this.b_class.day_selected, this.replace ); }, /** * Постройка шапки представления "Месяц" */ _buildMonthSection: function( y, m ) { $( this.section ).html( this.templates.month_section.evaluate( { month_section: this.b_class.month_section, choose_month: this.msg.choose_month, month_section: this.b_class.choose_section, month: this.msg.month_name[ m ], year: y } ) ); }, /** * Постройка списка дней */ _buildDayList: function( y, m ) { var day_count_back = this._getDayInMonth( y, m - 1 ), day_count = this._getDayInMonth( y, m ), first_week_date = new Date( y, m, 1 ).getDay(); if( !first_week_date ) { first_week_date = 7; } var cur_day, day_list = '', old_day_count = first_week_date - 1, new_day_count = 7 - (( day_count + old_day_count ) % 7); if( this.always_show_other_month_day ) { if( old_day_count == 0 ) { old_day_count = 7; } } else { if( new_day_count == 7 ) { new_day_count = 0; } } if( old_day_count > 0 ) { for( var i = 1; i <= old_day_count; ++ i ) { cur_day = new Date( y, parseInt( m, 10 ) - 1, day_count_back - old_day_count + i ); day_list += this._buildDay( cur_day, this.b_class.no_current_month ); } } for( var i = 1; i <= day_count; ++ i ) { day_list += this._buildDay( new Date( y, m, i ) ); } if( new_day_count > 0 ) { for( var i = 1; i <= new_day_count; ++ i ) { cur_day = new Date( y, parseInt( m, 10 ) + 1, i ); day_list += this._buildDay( cur_day, this.b_class.no_current_month ); } } $( this.day_list ).html( day_list ); this._checkArrowEnable(); if( this.drop && this.auto_position ) { this._autoPosition(); } }, /** * Постройка дня */ _buildDay: function( day, spec_class ) { var y = day.getFullYear(), m = day.getMonth(), d = day.getDate(), template = '', event_list; if( !spec_class ) { spec_class = ''; } if( (y == this.selected.year) && (m == this.selected.month) && (d == this.selected.day) ) { spec_class += ' ' + this.b_class.day_selected; } if( (y == this.today.year) && (m == this.today.month) && (d == this.today.day) ) { spec_class += ' ' + this.b_class.today; } if( this.can_set_date ) { spec_class += ' ' + (this._checkCanSelect( y, m, d ) ? this.b_class.day_choose : this.b_class.day_foreigh ) ; } var param = { day_class: this.b_class.day_class, day_inner_class: this.b_class.day_inner_class, spec_class: spec_class ? spec_class : '', year: y, month: m, day: d }; if( (event_list = this._getEventList( y, m, d ) ) ) { spec_class += ' ' + this.b_class.day_active; if( !this.choose_day_fn ) { param.link = event_list[0].link, param.title = event_list[0].title; template = '_link' } else { this.choose_day_fn(); } } var html = this.templates['day' + template].evaluate( param ); return html; }, /* * Проверка на возможность выбора года (или месяца заданного года, * или дня месяца года, взависимости от наличия аргументов) */ _checkCanSelect: function( y, m, d ) { var min = this.min_date, max = this.max_date, set_m = typeof m !== 'undefined', set_d = typeof d !== 'undefined', date; min = new Date( min.year, set_m ? min.month : 0, set_d ? min.day : 1 ); max = new Date( max.year, set_m ? max.month : 11, set_d ? max.day : 2 ); date = new Date( y, set_m ? m : 0, set_d ? d : 1 ); if( this.trigger_function ) { this.trigger_data._checkCanSelect = { date: date, min: min, max: max, value: (date >= min) && (date <= max) } } return (date >= min) && (date <= max); }, /** * Загрузка основных настроек класса */ _initConst: function() { var today = this._convertDateToObject( new Date() ); $.extend( this, { min_date: new Date( 1111, 1, 1 ), max_date: new Date( 9999, 11, 30 ) // min_date: new Date( 2009, 3, 1 ), // test // max_date: new Date( 2016, 9, 25 ) } ); this._setChooseRange( this.min_date, this.max_date ); $.extend( this, { msg: { week_day_name: [ 'Нульник', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс' ], month_name: [ 'Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь' ], slide: 'Пролистать', choose_month: 'Выбрать месяц', choose_year: 'Выбрать год', choose_decade: 'Выбрать десятилетие' }, today: today, current: this._fixDate( today ), selected: {}, event_list: {}, class_prefix: 'jqc_', b_class: { calendar: 'calendar', can_set_date: 'can_set_date', cant_set_date: 'cant_set_date', header: 'header', arrow: 'arrow', arrow_img: 'arrow_img', back: 'back', next: 'next', arrow_inactive: 'arrow_inactive', section: 'section', month_section: 'month_section', year_section: 'year_section', decade_section: 'decade_section', century_section: 'century_section', choose_section: 'choose_section', body: 'body', day_chooser: 'day_chooser', week: 'week', week_day: 'week_day', week_day_inner: 'week_day_inner', day_list: 'day_list', day_class: 'day', day_inner_class: 'day_inner', no_current_month: 'no_day', day_selected: 'day_selected', day_choose: 'day_choose', today: 'today', day_foreigh: 'day_foreigh', section_chooser: 'section_chooser', range: 'range', range_inner: 'range_inner', range_selected: 'range_selected', range_choose: 'range_choose', range_inactive: 'range_inactive', range_decade: 'range_decade' }, templates: { block: $.template ( '
'+ // шапка '
'+ // стрелка назад '
'+ // текущий раздел '
'+ // стрелка вперёд '
'+ '
'+ // выбираемое содержимое // выбор даты '
'+ '
'+ // дни недели '
'+ // дни '
'+ // выбор месяца или года, или десятка лет ''+ '
' ), day: $.template ( '
'+ '
#{day}
'+ '
' ), day_link: $.template ( '
'+ '
'+ '#{day}'+ '
'+ '
' ), week_day: $.template ( '
'+ '
#{week_day_name}
'+ '
' ), month_section: $.template ( '
#{month} #{year}
' ), year_section: $.template ( '
#{year}
' ), decade_section: $.template ( '
#{start} — #{end}
' ), century_section: $.template ( '
#{start} — #{end}
' ), range_item: $.template ( '
'+ '
'+ '#{title}'+ '
'+ '
' ) }, sectionList: [ 'month', 'year', 'decade', 'century' ], // список секций (для справки и пары функций) always_show_other_month_day: true, // дополнять дни идущие до текущегог месяца и после, даже если // для этого придётся вставить неделю year_list_count: 12, // количество доступных лет для выбора в списке can_set_date: true, // возможность выбора даты update_url: false, // ссылка по которой запрашиваются события has_event: {}, // года, которые имеют хотя бы 1 событие, но сами события не загружены loaded: {}, // года, события которых (или их отсутствие) уже загружены field: false, // input-поле в которое записывается значение даты выбранное пользователем fieldSynchronize: true, // календарь будет пытаться установить дату вбитую пользователем drop: false, // флаг того, что поле отображается только во время фокуса .field auto_position: true, // автоматически позиционировать календарь при открытии field_block: false, // блок относительно которого следует позиционировать календарь при появлении pos_dy: 3, // вертикальный отступ от .field hide_animate_delay: 100, // время анимации скрытия create: false, // вслучае если объект для замены не найден но задан .create - создаёт его anchor: false, // в объекте указанном в .anchor (или по умолчанию в document.body) trigger_function: true, // помещать каждую заданную функцию класса в контейнер с trigger-ом trigger_list: [ // список функций для ^ '_changeDate'/*, '_checkCanSelect', '_load', '_findEvent', '_chooseRange', '_arrowClick', '_chooseDay',*/ ], trigger_data: {} // данные которые будут передаваться в функции указанные в bind() } ); }, /** * Установка обработчиков событий */ _observe: function() { var list = this.b_class; if( this.can_set_date ) { $( '.' + list.day_choose, this.replace ).live( 'click', $.proxy( this, '_chooseDay' ) ); } $( '.' + list.range_choose, this.replace ).live( 'click', $.proxy( this, '_chooseRange' ) ); $( this.arrow_list ).click( $.proxy( this, '_arrowClick' ) ); $( this.section ).click( $.proxy( this, '_upSection', false ) ); if( this.drop && this.field ) { $( this.field ) .bind( 'focus', $.proxy( this, '_fieldFocus' ) ) .bind( 'click', $.proxy( this, '_fieldClick' ) ) .bind( 'keydown', $.proxy( this, '_fieldKeyDown' ) ) .bind( 'keyup', $.proxy( this, '_fieldKeyUp' ) ); $( document.body ).bind( 'click', $.proxy( this, '_bodyClick' ) ); $( this.replace ).bind( 'click', $.proxy( this, '_calendarClick' ) ); this.hide_result_fn = $.proxy( this, '_hideResult' ); } }, _setSection: function( mode ) { if( this.sectionList.indexOf( mode ) !== -1 ) { this[ '_set' + mode.slice(0,1).toUpperCase() + mode.slice(1) + 'Section' ](); return true; } return false; }, /** * Обработка нажатия на заголовок секции ( умельчение масштаба выбора даты ) */ _upSection: function( mode ) { if( !mode ) { mode = this.section_mode; } var index = this.sectionList.indexOf( mode ); if( (index > -1) && (index < this.sectionList.length - 1) ) { return this._setSection( this.sectionList[ index + 1 ] ); } return false; }, /** * Перевод объекта Date в объект с year, month && day */ _convertDateToObject: function( date ) { if( date instanceof Date ) { return { year: date.getFullYear(), month: date.getMonth(), day: date.getDate() } } else { return date; } }, /** * Возвращает переданный год, скорректированный исходя из ограничений * минимальной и максимальной даты */ _fixYear: function( year ) { var result = Math.max( Math.min( parseInt( year, 10 ), this.max_date.year), this.min_date.year ); return result; }, /** * Возвращает месяц, скорректированный исходя из ограничений * минимальной и максимальной даты */ _fixMonth: function( year, month ) { month = year > this.min_date.year ? month : Math.max( month, this.min_date.month ); month = year < this.max_date.year ? month : Math.min( month, this.max_date.month ); return month; }, /** * Возвращает день, скорректированный исходя из ограничений * минимальной и максимальной даты */ _fixDay: function( year, month, day ) { var min = this.min_date, max = this.max_date, m = (year * 12) + month; day = m > (min.year * 12) + min.month ? day : Math.max( this.min_date.day, day ); day = m < (max.year * 12) + max.month ? day : Math.min( this.max_date.day, day ); return day; }, /** * Возвращает дату в формате объекта (year, month, day), скорректированную * исходя из ограничений максимальной и минимальной даты */ _fixDate: function( date ) { if( date instanceof Date ) { date = this._convertDateToObject( date ); } var result = {}; result.year = this._fixYear( date.year ) result.month = this._fixMonth( result.year, date.month ); result.date = this._fixDay( result.year, result.month, date.day ); return result; }, /** * Выбор представления "Десятилетие" (список лет) */ _setDecadeSection: function() { $( this.day_chooser ).hide(); $( this.section_chooser ).show(); this.section_mode = 'decade'; this._buildDecade( this.current.year ) }, /** * Постройка представления "Десятилетие" (список лет) */ _buildDecade: function( center, no_query ) { center = parseInt( center, 10 ); this.year_list_count = Math.max( 10, this.year_list_count ); // в режиме столетие this.year_list_count = this.year_list_count = this.year_list_count - (this.year_list_count % 2); // каждый блок равен 10 годам, посему лет отображаемых в декаде должно быть >= 10 // также это число должно быть чётным var count = this.year_list_count, html = '', diff = count / 2, start, end that = this; if( this.max_date.year - this.min_date.year <= this.year_list_count ) { start = this.min_date.year; end = start + count - 1; } else { start = center - diff; end = center + diff - 1; } if( end - start > count ) { return false; // на всякий пожарный } if( !this.can_set_date && !no_query ) { var need_preview = []; for( var i = start; i <= end; ++ i ) { if( !this.loaded[ i ] && (typeof this.has_event[ i ] === 'undefined') && this._checkCanSelect( i ) ) { need_preview.push( i ); } } if( need_preview.length && this.update_url ) { this._load( need_preview, function() { that._buildDecade( center, true ); } ); return; } } $( this.section ).html( this.templates.decade_section.evaluate( { decade_section: this.b_class.decade_section, choose_decade: this.can_set_date ? this.msg.choose_decade : '', spec_class: this.can_set_date ? this.b_class.choose_section : '', start: start, end: end } ) ); for( var i = start; i <= end; ++ i ) { var param = { range_class: this.b_class.range, range_inner: this.b_class.range_inner, title: i, value: i, spec_class: '' }; if( this.selected.year == i ) { param.spec_class += ' ' + this.b_class.range_selected; } if( this.today.year == i ) { param.spec_class += ' ' + this.b_class.today; } var can = ( this.can_set_date || // календарь находится в режиме "можно выбрать любую дату" this.event_list[ i ] || // на этот год уже приходится хотя бы 1 событие, загруженное ранее this.has_event[ i ] // этот год содержит хотя бы 1 событие, но их список не загружен ) && this._checkCanSelect( i ); // диапазон выбора дат позволяет выбрать этот год param.spec_class += ' ' + (can ? this.b_class.range_choose : this.b_class.range_inactive); html += this.templates.range_item.evaluate( param ); } this.current.decade = { start: start, center: start + Math.floor( ( end - start ) / 2 ), end: end }; $( this.section_chooser ).html( html ); this.section_mode = 'decade'; this._checkArrowEnable(); if( this.drop && this.auto_position ) { this._autoPosition(); } }, /** * Проверка на доступность стрелок "назад" и "вперёд" */ _checkArrowEnable: function() { var back_toggle = false, next_toggle = false, current = this.current, min = this.min_date, max = this.max_date; switch( this.section_mode ) { case 'decade': { back_toggle = min.year >= this.current.decade.start; next_toggle = max.year <= this.current.decade.end; break; } case 'year': { back_toggle = min.year >= current.year; next_toggle = max.year <= current.year; break; } case 'month': { var min_ym = (min.year * 12) + min.month, max_ym = (max.year * 12) + max.month, ym = ( parseInt( current.year, 10 ) * 12) + current.month; back_toggle = ym <= min_ym; next_toggle = ym >= max_ym; break; } case 'century': { back_toggle = this.current.century.start <= min.year; next_toggle = this.current.century.end >= max.year; break; } } this.arrow_back.toggleClass( this.b_class.arrow_inactive, back_toggle ); this.arrow_next.toggleClass( this.b_class.arrow_inactive, next_toggle ); }, /** * Установка представления "Год" (список месяцев) */ _setYearSection: function() { $( this.day_chooser ).hide(); $( this.section_chooser ).show(); this.section_mode = 'year'; this._buildYear(); }, /** * Постройка представления "Год" (список месяцев) */ _buildYear: function( no_query ) { var html = '', that = this; if( !this.can_set_date && !no_query && !this.loaded[ this.current.year ] && this.update_url ) { this._load( this.current.year, function() { that._buildYear( true ); } ); return; } $( this.section ).html( this.templates.year_section.evaluate( { year_section: this.b_class.year_section, spec_class: this.b_class.choose_section, choose_year: this.msg.choose_year, year: this.current.year } ) ); for( var i = 0; i < 12; ++ i ) { var param = { range_class: this.b_class.range, title: this.msg.short_month_name[ i ], range_inner: this.b_class.range_inner, value: i, spec_class: '' }; if( (this.selected.year == this.current.year) && (this.selected.month == i ) ) { param.spec_class += this.b_class.range_selected; } if( (this.current.year == ' ' + this.today.year ) && (i == this.today.month) ) { param.spec_class += ' ' + this.b_class.today; } var can = ( this.can_set_date || ( this.event_list[ this.current.year ] && this.event_list[ this.current.year ][ i ] ) ) && this._checkCanSelect( this.current.year, i ); param.spec_class += ' ' + (can ? this.b_class.range_choose : this.b_class.range_inactive); html += this.templates.range_item.evaluate( param ); } $( this.section_chooser ).html( html ); this.section_mode = 'year'; this._checkArrowEnable(); if( this.drop && this.auto_position ) { this._autoPosition(); } }, /** * Выбор даты из клика по DOM-объекту дня */ _chooseDay: function( e ) { var item = $( e.currentTarget ), day = item.attr('_day'), month = item.attr('_month'), year = item.attr('_year'); this._changeDate( false, year, month, day, item ); }, /** * Смена выбранной даты */ _changeDate: function( date, y, m, d, item, ignore_field ) { if( date ) { var date = this._convertDateToObject( date ); y = date.year; m = date.month; d = date.day; } else { y = parseInt( y, 10 ); m = parseInt( m, 10 ); d = parseInt( d, 10 ); } // если дата не изменилась if( (this.selected.year == y) && (this.selected.month == m) && (this.selected.day == d) ) { return false; } // если дата лежит вне диапазона доступных дат if( !this._checkCanSelect( y, m, d ) ) { return false; } this.selected = { year: y, month: m, day: d } $( this.current_day_item ).removeClass( this.b_class.day_selected ); if( (this.section_mode !== 'month') || (this.current.month != m) || (this.current.year != y) || !item ) { this.current = { year: y, month: m }; this._setSection( 'month' ); } else { item.addClass( this.b_class.day_selected ); this.current_day_item = item; } if( this.field ) { if( !ignore_field ) { var fin = (d > 9 ? d : '0' + d) + '.' + (m + 1 > 9 ? m + 1 : '0' + (m + 1)) + '.' + y; $( this.field ).val( fin ); if( this.drop ) { $( this.field ).focus(); this._hide(); } } } }, /** * Клик по стрелке навигации */ _arrowClick: function( e, no_query ) { var arrow = e.currentTarget, direction = $( arrow ).hasClass( this.b_class.next ) ? +1 : -1, current = this.current, that = this; switch( this.section_mode ) { case 'month': { var event, date; if( !this.can_set_date ) { event = this._findEvent( direction, current.year, current.month ); if( event ) { if( !event.load ) { date = new Date( event.year, event.month, 1 ); } else { if( this.update_url && !no_query ) { this._load( event.load, function() { that._arrowClick( e, true ); } ); return; } } } } if( this.can_set_date || !date ) { var date = new Date( this.current.year, parseInt( this.current.month, 10 ) + direction, 1 ); } this.current = { year: date.getFullYear(), month: date.getMonth() }; this._buildMonth(); break; } case 'year': { var year = parseInt( this.current.year, 10 ) + direction; if( !this.can_set_date && !this.loaded[ year ] && !no_query && this.update_url ) { this._load( year, function() { that._arrowClick( e, true ); } ); return; } else { this.current.year = year; this._buildYear(); break; } } case 'decade': { this._buildDecade( this.current.decade.center + this.year_list_count * direction + 1 ); break; } case 'century': { var century = this.current.century; century.start =century.start + 100 * direction; century.end = century.end + 100 * direction; this._buildCentury(); break; } } }, /** * Подгрузка данных о событиях * year - год либо массив лет * fn - функция которая должна быть вызвана по приходу ответа с сервера */ _load: function( year, fn ) { $( this.replace ).addClass( 'loading' ); $.get ( this.update_url, {id: 322, y: year}, $.proxy( this, '_loadResult', fn, year ) ); }, /** * Подгрузка данных с сервера */ _loadResult: function( fn, year, response ) { $( this.replace ).removeClass( 'loading' ); response = eval( '(' + response + ')' ); if( response.error ) { Notify._error( response.error ); return; } if( typeof response.calendar == 'object' ) { if( response.calendar.preview ) { $.extend( this.has_event, response.calendar ); fn(); } else { this.loaded[ year ] = true; if( !(response.calendar instanceof Array ) ) { for( var name in response.calendar ) { var year = parseInt( response.calendar[ name ], 10 ); if( year ) { this.event_list[ year ] = response.calendar[ year ]; } } } $.extend( true, this.event_list, response.calendar ); } fn(); } }, /** * Поиск ближайшего события (подгрузка данных не производится) */ _findEvent: function( direction, y, m ) { if( !y || (typeof m == 'undefined') ) { return false; } var min_year = this.min_date.year, max_year = this.max_date.year; var cy = y; do { if( !this.loaded[ cy ] ) { return {load: cy}; } if( this.event_list[ cy ] ) { var cm = cy == y ? m : direction > 0 ? 0 : 11; do { direction > 0 ? ++ cm : -- cm; if( this.event_list[ cy ][ cm ] ) { return {year: cy, month: cm}; } } while( (cm > -1) && (cm < 11) ); } direction > 0 ? ++ cy : -- cy; } while( direction > 0 ? cy <= max_year : cy >= min_year ); return false; }, /** * Возвращает список событий на заданный день либо false */ _getEventList: function( y, m, d ) { if( this.event_list && this.event_list[ y ] && this.event_list[ y ][ m ] && this.event_list[ y ][ m ][ d ] ) { var item = this.event_list[ y ][ m ][ d ]; if( !(item instanceof Array) ) { this.event_list[ y ][ m ][ d ] = [ item ]; } else if( !item.length ) { return false; } return this.event_list[ y ][ m ][ d ]; } }, /** * Выбор ячейки (диапазона) в режимах отличных от выбора дня */ _chooseRange: function( e ) { var item = e.currentTarget, value = $( item ).attr('_value'); switch( this.section_mode ) { case 'year': { this.current.month = parseInt( value, 10 ); $( this.day_chooser ).show(); $( this.section_chooser ).hide(); this.section_mode = 'month'; this._buildMonth(); break; } case 'decade': { this.current.year = parseInt( value, 10 ); this.section_mode = 'year'; this._buildYear(); break; } case 'century': { this.section_mode = 'decade'; this._buildDecade( parseInt( value, 10 ) + 5 ); } } }, /** * Постройка представления "Столетие" */ _buildCentury: function() { var current = this.current.century, html = ''; $( this.section ).html( this.templates.century_section.evaluate( { century_section: this.b_class.century_section, start: current.start, end: current.end } ) ); for( var i = 0; i < 10; ++ i ) { var c_start = current.start + i * 10, c_end = c_start + 9, param = { range_class: this.b_class.range, range_inner: this.b_class.range_inner, spec_class: this.b_class.range_decade, value: c_start, title: c_start + '-
' + c_end + ' ' } if( (this.selected.year >= c_start) && (this.selected.year <= c_end) ) { param.spec_class += ' ' + this.b_class.range_selected; } if( (this.today.year >= c_start) && (this.today.year <= c_end) ) { param.spec_class += ' ' + this.b_class.today; } var can = false; for( var t = c_start; t <= c_end; ++ t ) { if( this._checkCanSelect( t ) ) { can = true; break; } } param.spec_class += ' ' + (can ? this.b_class.range_choose : this.b_class.range_inactive); html += this.templates.range_item.evaluate( param ); } $( this.section_chooser ).html( html ); this._checkArrowEnable(); if( this.drop && this.auto_position ) { this._autoPosition(); } }, /** * Установка представления "Век" */ _setCenturySection: function() { $( this.day_chooser ).hide(); $( this.section_chooser ).show(); this.section_mode = 'century'; this.current.century = {}; this.current.century.start = Math.floor( this.current.decade.center / 100 ) * 100, this.current.century.end = this.current.century.start + 99; this._buildCentury( this.current.year ); }, /** * Автоматическое позиционирование календаря при заданных * .drop == true * .field == DOM.element * .auto_position == true * Если задан .field_block => при рассчётах будет использоваться .field_block, * иначе .field */ _autoPosition: function() { if( !this.field ) { return false; } var // связанное-поле f_block = this.field_block ? $( this.field_block ) : $( this.field ), f_offset = f_block.offset(), f_left = f_offset.left, f_top = f_offset.top, f_h = f_block.outerHeight(), // блок календаря c_w = $( this.replace ).outerWidth(), c_h = $( this.replace ).outerHeight(), w_w = $( window ).width(), w_h = $( window ).height(), w_top = $( window ).scrollTop(), left, top; if( ( this._ap_direction != 'bottom' ) && ( ( this._ap_direction == 'top' ) || (f_top + f_h + this.pos_dy + c_h > w_h + w_top) ) ) { top = f_top - c_h - this.pos_dy; this._ap_direction = 'top'; } else { this._ap_direction = 'bottom'; top = f_top + f_h + this.pos_dy; } if( f_left + c_w > w_w ) { left = w_w - c_w; } else { left = f_left; } left = Math.max( 0, left ); $( this.replace ).offset( { top: top, left: left } ); }, /** * Попытка задать значение каледнаря исходя из значения связанного поля */ _fieldSetValue: function() { var value = $( this.field ).val(), m = value.match( /(\d{1,2})[\.\,\/\\]{1}(\d{1,2})[\.\,\/\\]{1}(\d{1,4})/ ); if( m ) { var date = new Date( m[3], m[2] - 1, m[1] ), str = date.toString(); if( (str !== 'NaN') && (str !== 'Invalid Date') ) { this._changeDate( date, false, false, false, false, true ); } return date; } else { return false; } }, /** * Связанное input-поле теряет фокус */ _fieldFocus: function() { this._show(); }, /** * Показ календаря */ _show: function() { if( window.__active_calendar && (this !== window.__active_calendar) ) { window.__active_calendar._hide(); } this.__hide = false; var visible = $( this.replace ).css('display') !== 'none'; $( this.replace ).show(); if( this.drop && this.auto_position && !visible ) { this._autoPosition(); } window.__active_calendar = this; }, /** * Переключение видимости календаря */ _toggle: function() { var visible = $( this.replace ).css( 'display' ) !== 'none'; visible ? this._hide() : this._show(); }, /** * Нажатие клавиши при фокусе в связанном поле. Клавиша Tab скрывает календарь * ( т.к. фокус должен перейти в след.объект ) */ _fieldKeyDown: function( e ) { if( e.keyCode == 9 /* Tab */ ) { this._hide(); } }, /** * Отпускание клавиши при фокусе в связанном поле. Если задан .fieldSynchronize * календарь пытается установить введённую в input дату-значение */ _fieldKeyUp: function( e ) { if( this.fieldSynchronize ) { this._fieldSetValue(); } }, /** * Клик по связанному полю */ _fieldClick: function() { this._show(); }, /** * Клик по document.body */ _bodyClick: function() { if( !this.__hide ) { this.__hide = true; } else { this._hide(); } }, /** * Клик по календарю */ _calendarClick: function() { this.__hide = false; }, /** * Скрытие календаря */ _hide: function() { $( this.replace ).fadeOut( this.hide_animate_delay ); }, /** * Получение текущего значения даты в календаре. * Если задан as_object возвращает объект с { year, month, day }. * Если дата не задана -> false */ _getValue: function( as_object ) { if( this.selected && this.selected.day ) { return as_object ? $.extend( {}, this.selected ) : new Date( this.selected.year, this.selected.month, this.selected.day ) } return false; }, /** * Возвращает строковое значение (19.11.1989) выбранной даты. * Если дата не задана - возвращает '' */ toString: function() { var date = this._getValue( true ); if( date ) { date.month ++; return '' + ( date.day < 10 ? '0' : '' ) + date.day + '.' + ( date.month < 10 ? '0' : '' ) + date.month + '.' + date.year; } else { return ''; } }, /** * Установка даты. * Если date_year является объектом класса Date, аргументы month и day * игнорируются, иначе date_year должен содержать значение года */ _setValue: function( date_year, month, day ) { if( date_year instanceof Date ) { this._changeDate( date_year ); } else { this._changeDate( false, date_year, month, day ); } } } })( jQuery );