(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_link: $.template
(
''
),
week_day: $.template
(
''
),
month_section: $.template
(
'#{month} #{year}
'
),
year_section: $.template
(
'#{year}
'
),
decade_section: $.template
(
'#{start} — #{end}
'
),
century_section: $.template
(
'#{start} — #{end}
'
),
range_item: $.template
(
''
)
},
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 );