nest-parrot
Version:
Parrot built on react
1,235 lines (1,222 loc) • 42.1 kB
JSX
(function(window, $, moment, React, ReactDOM, $pt) {
var NDateTime = React.createClass($pt.defineCellComponent({
displayName: 'NDateTime',
mixins: [$pt.mixins.PopoverMixin],
statics: {
POP_FIX_ON_BOTTOM: false,
FORMAT: 'YYYY/MM/DD',
HEADER_MONTH_FORMAT: 'MMMM',
HEADER_YEAR_FORMAT: 'YYYY',
VALUE_FORMAT: $pt.ComponentConstants.Default_Date_Format,
FORMAT_TYPES: {
// use binary
ALL: 64 + 32 + 16 + 8 + 4 + 2,
YMD: 64 + 32 + 16,
YM: 64 + 32,
HM: 8 + 4,
HMS: 8 + 4 + 2,
YEAR: 64,
MONTH: 32,
DAY: 16,
HOUR: 8,
MINUTE: 4,
SECOND: 2,
MILLSECOND: 1
},
CLOCK_RADIUS: 100,
CLOCK_HOUR_PERCENTAGE: 0.6,
CLOCK_BIG_ENGRAVE_LENGTH: 8,
CLOCK_SMALL_ENGRAVE_LENGTH: 4,
CLOCK_CHAR_POS: {
TOP: {X: 100, Y: -2},
LEFT: {X: -1, Y: 99},
RIGHT: {X: 201, Y: 99},
BOTTOM: {X: 100, Y: 203}
},
CLOCK_HAND_OFFSET: 10,
CLOSE_TEXT: 'Close',
TODAY_TEXT: 'Now',
CLEAR_TEXT: 'Clear',
DATE_SWITCH_TEXT: 'Date',
TIME_SWITCH_TEXT: 'Time',
FIXED_WEEKDS: false
},
getDefaultProps: function() {
return {
defaultOptions: {
locale: 'en',
headerMonthFirst: true,
bodyYearFormat: 'YYYY',
icons: {
calendar: 'calendar',
today: 'crosshairs',
clear: 'trash-o',
close: 'close'
}
}
};
},
beforeDidUpdate: function() {
this.setValueToTextInput(this.getValueFromModel());
},
beforeDidMount: function() {
this.setValueToTextInput(this.getValueFromModel());
},
beforeWillUnmount: function() {
this.destroyPopover();
},
renderIcon: function(options) {
var css = {
fa: true,
'fa-fw': true
};
css['fa-' + options.icon] = true;
if (options.className) {
css[options.className] = true;
}
return <span className={$pt.LayoutHelper.classSet(css)} onClick={options.click}/>;
},
renderInputArea: function() {
// desktop
var css = {
'input-group-addon': true,
link: true,
disabled: !this.isEnabled()
};
return (<div className='input-group'
onClick={this.onCalendarButtonClicked}
ref='comp'>
<input type='text'
className='form-control'
disabled={!this.isEnabled()}
onChange={this.onTextInputChange}
onFocus={this.onTextInputFocused}
onBlur={this.onTextInputBlurred}
aria-readonly={this.isMobile() ? true : false}
readOnly={this.isMobile() ? true : false}
ref='text'/>
<span className={$pt.LayoutHelper.classSet(css)}>
{this.renderIcon({icon: this.getIcon('calendar'), click: this.onCalendarButtonClicked})}
</span>
</div>)
},
render: function() {
if (this.isViewMode()) {
return this.renderInViewMode();
}
var divCSS = {
'n-disabled': !this.isEnabled()
};
divCSS[this.getComponentCSS('n-datetime')] = true;
return (<div className={$pt.LayoutHelper.classSet(divCSS)}>
{this.renderInputArea()}
{this.renderNormalLine()}
{this.renderFocusLine()}
</div>);
},
renderHeaderMonth: function(date) {
return (<span onClick={this.renderPopover.bind(this, {date: date, type: NDateTime.FORMAT_TYPES.MONTH})}
className='header-date-btn'
key='header-month'>
{this.convertValueToString(date, this.getHeaderMonthFormat())}
</span>);
},
renderHeaderYear: function(date) {
return (<span onClick={this.renderPopover.bind(this, {date: date, type: NDateTime.FORMAT_TYPES.YEAR})}
className='header-date-btn'
key='header-year'>
{this.convertValueToString(date, this.getHeaderYearFormat())}
</span>);
},
renderDayHeader: function(date) {
var mainHeader = [this.renderHeaderMonth(date), ' ', this.renderHeaderYear(date)];
var monthFirst = this.getComponentOption('headerMonthFirst');
return (<div className='calendar-header day-view'>
{this.renderIcon({
icon: 'angle-double-left',
className: 'header-btn left',
click: this.renderPopover.bind(this, {date: date.clone().subtract(1, 'y'), type: NDateTime.FORMAT_TYPES.DAY})
})}
{this.renderIcon({
icon: 'angle-left',
className: 'header-btn left',
click: this.renderPopover.bind(this, {date: date.clone().subtract(1, 'M'), type: NDateTime.FORMAT_TYPES.DAY})
})}
{monthFirst ? mainHeader : mainHeader.reverse()}
{this.renderIcon({
icon: 'angle-double-right',
className: 'header-btn right',
click: this.renderPopover.bind(this, {date: date.clone().add(1, 'y'), type: NDateTime.FORMAT_TYPES.DAY})
})}
{this.renderIcon({
icon: 'angle-right',
className: 'header-btn right',
click: this.renderPopover.bind(this, {date: date.clone().add(1, 'M'), type: NDateTime.FORMAT_TYPES.DAY})
})}
</div>);
},
getWeekdayHeader: function(date) {
var orgLocale = moment.locale();
moment.locale(this.getLocale());
var header = moment.weekdaysMin();
moment.locale(orgLocale);
var firstDayOfWeek = this.getFirstDayOfWeek();
// move how many to last
var removed = header.splice(0, firstDayOfWeek);
header = header.concat.apply(header, removed);
return header;
},
getDaysOfDayBody: function(date) {
var days = this.getDaysOfMonth(date);
var firstDay = date.clone();
firstDay.date(1); // set to the first day of month
var dayOfWeekOfFirstDay = this.getDayOfWeek(firstDay);
var firstDayOfWeek = this.getFirstDayOfWeek();
var gapDaysOfPrevMonth = 0;
if (dayOfWeekOfFirstDay >= firstDayOfWeek) {
gapDaysOfPrevMonth = dayOfWeekOfFirstDay - firstDayOfWeek;
} else {
gapDaysOfPrevMonth = dayOfWeekOfFirstDay + 7 - firstDayOfWeek;
}
// calculate
var index = 0;
var viewDays = [];
var viewDay = null;
// gap days by previous month
for (index = 1; index <= gapDaysOfPrevMonth; index++) {
viewDay = firstDay.clone();
viewDay.subtract(index, 'd');
viewDays.splice(0, 0, viewDay);
}
// this month
for (index = 0; index < days; index++) {
viewDay = firstDay.clone();
viewDay.add(index, 'd');
viewDays.push(viewDay);
}
// gap days by next month
var gapDaysOfNextMonth = 7 - viewDays.length % 7;
gapDaysOfNextMonth = (gapDaysOfNextMonth == 7) ? 0 : gapDaysOfNextMonth;
var lastDay = viewDays[viewDays.length - 1];
for (index = 1; index <= gapDaysOfNextMonth; index++) {
viewDay = lastDay.clone();
viewDay.add(index, 'd');
viewDays.push(viewDay);
}
if (NDateTime.FIXED_WEEKDS) {
lastDay = viewDays[viewDays.length - 1];
var dayCount = viewDays.length;
if (dayCount < 42) {
for (index = 1 ; index <= 42 - dayCount; index++) {
viewDay = lastDay.clone();
viewDay.add(index, 'd');
viewDays.push(viewDay);
}
}
}
return viewDays;
},
renderDayBody: function(date) {
var _this = this;
var header = this.getWeekdayHeader();
var days = this.getDaysOfDayBody(date);
var currentMonth = date.month();
var value = this.getValueFromModel();
var today = this.getToday();
var min = this.getMinDate();
var max = this.getMaxDate();
return (<div className='calendar-body day-view'>
<div className='day-view-body-header row'>
{header.map(function(weekday, weekdayIndex) {
return <div className='cell-7-1' key={'weekday-' + weekdayIndex}>{weekday}</div>;
})}
</div>
<div className='day-view-body-body row'>
{days.map(function(day, dayIndex) {
var css = {
'cell-7-1': true,
'gap-day': (day.month() != currentMonth),
'disable-day': (day.isBefore(min) || day.isAfter(max)),
today: day.isSame(today, 'day'),
'current-value': value != null && day.isSame(value, 'day')
};
var click = _this.onDaySelected.bind(_this, day);
if (css['disable-day'] === true) {
click = null;
}
return (<div className={$pt.LayoutHelper.classSet(css)}
onClick={click}
key={'day-' + dayIndex}>
<span>{day.date()}</span>
</div>);
})}
</div>
</div>);
},
renderMonthHeader: function(date) {
return (<div className='calendar-header month-view'>
{this.renderIcon({
icon: 'angle-double-left',
className: 'header-btn left',
click: this.renderPopover.bind(this, {date: date.clone().subtract(10, 'y'), type: NDateTime.FORMAT_TYPES.MONTH})
})}
{this.renderIcon({
icon: 'angle-left',
className: 'header-btn left',
click: this.renderPopover.bind(this, {date: date.clone().subtract(1, 'y'), type: NDateTime.FORMAT_TYPES.MONTH})
})}
{this.renderHeaderYear(date)}
{this.renderIcon({
icon: 'angle-double-right',
className: 'header-btn right',
click: this.renderPopover.bind(this, {date: date.clone().add(10, 'y'), type: NDateTime.FORMAT_TYPES.MONTH})
})}
{this.renderIcon({
icon: 'angle-right',
className: 'header-btn right',
click: this.renderPopover.bind(this, {date: date.clone().add(1, 'y'), type: NDateTime.FORMAT_TYPES.MONTH})
})}
</div>);
},
renderMonthBody: function(date) {
var _this = this;
var orgLocale = moment.locale();
moment.locale(this.getLocale());
var months = moment.monthsShort('-MMM-');
moment.locale(orgLocale);
var day = this.getValueFromModel();
if (day == null) {
day = this.getToday();
}
var value = this.getValueFromModel();
var today = this.getToday();
return (<div className='calendar-body month-view'>
<div className='month-view-body-body row'>
{months.map(function(month, index) {
var selectedDay = day.clone();
selectedDay.year(date.year());
selectedDay.month(index);
var css = {
'cell-4-1': true,
today: index == today.month(),
'current-value': value != null && index == value.month()
};
return (<div className={$pt.LayoutHelper.classSet(css)}
onClick={_this.onMonthSelected.bind(_this, selectedDay)}
key={index}>
{month}
</div>);
})}
</div>
</div>);
},
renderYearHeader: function(date) {
var yearHeader = [
this.convertValueToString(date.clone().subtract(10, 'y'), this.getHeaderYearFormat()),
' - ',
this.convertValueToString(date.clone().add(9, 'y'), this.getHeaderYearFormat())
];
return (<div className='calendar-header year-view'>
{this.renderIcon({
icon: 'angle-double-left',
className: 'header-btn left',
click: this.renderPopover.bind(this, {date: date.clone().subtract(40, 'y'), type: NDateTime.FORMAT_TYPES.YEAR})
})}
{this.renderIcon({
icon: 'angle-left',
className: 'header-btn left',
click: this.renderPopover.bind(this, {date: date.clone().subtract(20, 'y'), type: NDateTime.FORMAT_TYPES.YEAR})
})}
{yearHeader}
{this.renderIcon({
icon: 'angle-double-right',
className: 'header-btn right',
click: this.renderPopover.bind(this, {date: date.clone().add(40, 'y'), type: NDateTime.FORMAT_TYPES.YEAR})
})}
{this.renderIcon({
icon: 'angle-right',
className: 'header-btn right',
click: this.renderPopover.bind(this, {date: date.clone().add(20, 'y'), type: NDateTime.FORMAT_TYPES.YEAR})
})}
</div>);
},
renderYearBody: function(date) {
var _this = this;
var day = this.getValueFromModel();
if (day == null) {
day = this.getToday();
}
var value = this.getValueFromModel();
var today = this.getToday();
var years = [];
for (var index = -10; index <= 9; index++) {
var year = date.clone().set({month: day.month(), date: day.date(), hour: day.hour(), minute: day.minute(), second: day.second(), millisecond: day.millisecond()});
year.add(index, 'y');
years.push(year);
}
return (<div className='calendar-body month-view'>
<div className='year-view-body-body row'>
{years.map(function(year, yearIndex) {
var css = {
'cell-4-1': true,
today: year.year() == today.year(),
'current-value': value != null && year.year() == value.year()
};
return (<div className={$pt.LayoutHelper.classSet(css)}
onClick={_this.onYearSelected.bind(_this, year)}
key={yearIndex}>
{year.format(_this.getBodyYearFormat())}
</div>);
})}
</div>
</div>);
},
renderPopoverContentFooterButton: function(options) {
return (<div className='cell-3-1' onClick={options.click}>
{this.renderIcon({icon: this.getIcon(options.icon)})}
</div>);
},
renderPopoverContentFooterForMobile: function(today, type) {
var viewSwitch = null;
if (this.hasDateToDisplay() && this.hasTimeToDisplay()) {
viewSwitch = [];
// is date view, show time switch
viewSwitch.push(<a href='javascript:void(0);'
onClick={this.switchToTimeViewOnMobile}
className={'time-switch' + (this.state.dateViewWhenMobilePhone ? '' : ' hidden')}
key='date'>
<span>{NDateTime.TIME_SWITCH_TEXT}</span>
</a>);
// not date view, show date switch
viewSwitch.push(<a href='javascript:void(0);'
onClick={this.switchToDateViewOnMobile}
className={'date-switch' + (this.state.dateViewWhenMobilePhone ? ' hidden' : '')}
key='time'>
<span>{NDateTime.DATE_SWITCH_TEXT}</span>
</a>);
}
return (<div className='calendar-footer row'>
<div>
<a href='javascript:void(0);' onClick={this.hidePopover}>
<span>{NDateTime.CLOSE_TEXT}</span>
</a>
<a href='javascript:void(0);' onClick={this.renderPopover.bind(this, {
date: null,
type: type,
set: true
})}>
<span>{NDateTime.CLEAR_TEXT}</span>
</a>
<a href='javascript:void(0);' onClick={this.renderPopover.bind(this, {
date: today,
type: type,
set: true
})}>
<span>{NDateTime.TODAY_TEXT}</span>
</a>
{viewSwitch}
</div>
</div>);
},
renderPopoverContentFooterForDesk: function(today, type) {
return (<div className='calendar-footer row'>
{this.renderPopoverContentFooterButton({
icon: 'today',
click: this.renderPopover.bind(this, {
date: today,
type: type,
set: true
})
})}
{this.renderPopoverContentFooterButton({
icon: 'clear',
click: this.renderPopover.bind(this, {
date: null,
type: type,
set: true
})
})}
{this.renderPopoverContentFooterButton({
icon: 'close',
click: this.hidePopover
})}
</div>);
},
renderPopoverContentFooter: function(today, type) {
if (this.isMobilePhone()) {
return this.renderPopoverContentFooterForMobile(today, type);
} else {
return this.renderPopoverContentFooterForDesk(today, type);
}
},
renderEngrave: function(degree, radius, length, className, offset) {
var startLength = radius - length;
return (<line className={className}
x1={startLength * Math.cos(Math.PI * 2 * degree / 360) + offset}
y1={offset - startLength * Math.sin(Math.PI * 2 * degree / 360)}
x2={radius * Math.cos(Math.PI * 2 * degree / 360) + offset}
y2={offset - radius * Math.sin(Math.PI * 2 * degree / 360)}
key={degree}/>);
},
render12HourDial: function(date, popoverType) {
var _this = this;
var am = date.hour() <= 11; // 0-23
var hourRadius = this.getHourRadius();
return (<g key='hour-12-dial'>
<text className={'text hour-12 am' + (am ? ' yes' : '')}
onClick={this.onAMPMSelected.bind(this, true, popoverType)}
x={0}
y={0}>AM</text>
<text className={'text hour-12 pm' + (am ? '' : ' yes')}
onClick={this.onAMPMSelected.bind(this, false, popoverType)}
x={NDateTime.CLOCK_RADIUS * 2}
y={0}>PM</text>
<text className='text hour-12 top-num'
x={NDateTime.CLOCK_CHAR_POS.TOP.X}
y={NDateTime.CLOCK_CHAR_POS.TOP.Y + NDateTime.CLOCK_RADIUS - hourRadius}>0</text>
<text className='text hour-12 left-num'
x={NDateTime.CLOCK_CHAR_POS.LEFT.X + NDateTime.CLOCK_RADIUS - hourRadius}
y={NDateTime.CLOCK_CHAR_POS.LEFT.Y}>9</text>
<text className='text hour-12 right-num'
x={NDateTime.CLOCK_CHAR_POS.RIGHT.X - NDateTime.CLOCK_RADIUS + hourRadius}
y={NDateTime.CLOCK_CHAR_POS.RIGHT.Y}>3</text>
<text className='text hour-12 bottom-num'
x={NDateTime.CLOCK_CHAR_POS.BOTTOM.X}
y={NDateTime.CLOCK_CHAR_POS.BOTTOM.Y - NDateTime.CLOCK_RADIUS + hourRadius}>6</text>
{[30, 60, 120, 150, 210, 240, 300, 330].map(function(degree) {
return _this.renderEngrave(degree,
hourRadius,
NDateTime.CLOCK_SMALL_ENGRAVE_LENGTH,
'big',
NDateTime.CLOCK_RADIUS);
})}
</g>);
},
render24HourDial: function() {
var _this = this;
var hourRadius = this.getHourRadius();
return (<g key='hour-24-dial'>
<text className='text hour-24 top-num'
x={NDateTime.CLOCK_CHAR_POS.TOP.X}
y={NDateTime.CLOCK_CHAR_POS.TOP.Y + NDateTime.CLOCK_RADIUS - hourRadius}>0</text>
<text className='text hour-24 left-num'
x={NDateTime.CLOCK_CHAR_POS.LEFT.X + NDateTime.CLOCK_RADIUS - hourRadius}
y={NDateTime.CLOCK_CHAR_POS.LEFT.Y}>18</text>
<text className='text hour-24 right-num'
x={NDateTime.CLOCK_CHAR_POS.RIGHT.X - NDateTime.CLOCK_RADIUS + hourRadius}
y={NDateTime.CLOCK_CHAR_POS.RIGHT.Y}>6</text>
<text className='text hour-24 bottom-num'
x={NDateTime.CLOCK_CHAR_POS.BOTTOM.X}
y={NDateTime.CLOCK_CHAR_POS.BOTTOM.Y - NDateTime.CLOCK_RADIUS + hourRadius}>12</text>
{[45, 135, 225, 315].map(function(degree) {
return _this.renderEngrave(degree,
hourRadius,
NDateTime.CLOCK_BIG_ENGRAVE_LENGTH,
'big',
NDateTime.CLOCK_RADIUS);
})}
{[15, 30, 60, 75, 105, 120, 150, 165, 195, 210, 240, 255, 285, 300, 330, 345].map(function(degree) {
return _this.renderEngrave(degree,
hourRadius,
NDateTime.CLOCK_SMALL_ENGRAVE_LENGTH,
'small',
NDateTime.CLOCK_RADIUS);
})}
</g>);
},
renderMinuteDial: function() {
if (!this.hasMinute()) {
// no minute need to display
return null;
}
var _this = this;
return (<g key='minute-dial'>
<text className='text minute top-num'
x={NDateTime.CLOCK_CHAR_POS.TOP.X}
y={NDateTime.CLOCK_CHAR_POS.TOP.Y}>0</text>
<text className='text minute left-num'
x={NDateTime.CLOCK_CHAR_POS.LEFT.X}
y={NDateTime.CLOCK_CHAR_POS.LEFT.Y}>45</text>
<text className='text minute right-num'
x={NDateTime.CLOCK_CHAR_POS.RIGHT.X}
y={NDateTime.CLOCK_CHAR_POS.RIGHT.Y}>15</text>
<text className='text minute bottom-num'
x={NDateTime.CLOCK_CHAR_POS.BOTTOM.X}
y={NDateTime.CLOCK_CHAR_POS.BOTTOM.Y}>30</text>
{[30, 60, 120, 150, 210, 240, 300, 330].map(function(degree) {
return _this.renderEngrave(degree,
NDateTime.CLOCK_RADIUS,
NDateTime.CLOCK_BIG_ENGRAVE_LENGTH,
'big',
NDateTime.CLOCK_RADIUS);
})}
{[6, 12, 18, 24, 36, 42, 48, 54, 66, 72, 78, 84,
96, 102, 108, 114, 126, 132, 138, 144, 156, 162, 168, 174,
186, 192, 198, 204, 216, 222, 228, 234, 246, 252, 258, 264,
276, 282, 288, 294, 306, 312, 318, 324, 336, 342, 348, 354].map(function(degree) {
return _this.renderEngrave(degree,
NDateTime.CLOCK_RADIUS,
NDateTime.CLOCK_SMALL_ENGRAVE_LENGTH,
'small',
NDateTime.CLOCK_RADIUS);
})}
</g>);
},
renderHourHand: function(date, offset) {
var hour = date.hour();
var degree = null;
if (this.is12Hour()) {
degree = 450 - hour * 30;
} else {
degree = 450 - hour * 15;
}
var hourRadius = this.getHourRadius();
return (<line x1={offset + NDateTime.CLOCK_HAND_OFFSET * Math.cos(Math.PI * 2 * (degree - 180) / 360)}
y1={offset - NDateTime.CLOCK_HAND_OFFSET * Math.sin(Math.PI * 2 * (degree - 180) / 360)}
x2={offset + (hourRadius - NDateTime.CLOCK_HAND_OFFSET) * Math.cos(Math.PI * 2 * (degree) / 360)}
y2={offset - (hourRadius - NDateTime.CLOCK_HAND_OFFSET) * Math.sin(Math.PI * 2 * (degree) / 360)}
className='hour-hand' />);
},
renderMinuteHand: function(date, radius, offset) {
if (!this.hasMinute()) {
// no minute need to display
return null;
}
var minute = date.minute();
var degree = 450 - minute * 6;
return (<line x1={offset + NDateTime.CLOCK_HAND_OFFSET * Math.cos(Math.PI * 2 * (degree - 180) / 360)}
y1={offset - NDateTime.CLOCK_HAND_OFFSET * Math.sin(Math.PI * 2 * (degree - 180) / 360)}
x2={offset + (radius - NDateTime.CLOCK_HAND_OFFSET) * Math.cos(Math.PI * 2 * (degree) / 360)}
y2={offset - (radius - NDateTime.CLOCK_HAND_OFFSET) * Math.sin(Math.PI * 2 * (degree) / 360)}
className='minute-hand' />);
},
renderSecondHand: function(date, radius, offset) {
var popoverType = this.guessDisplayFormatType();
if (!this.hasSecond()) {
// no minute need to display
return null;
}
var _this = this;
var second = date.second();
var degree = 450 - second * 6;
return (<line x1={offset + NDateTime.CLOCK_HAND_OFFSET * Math.cos(Math.PI * 2 * (degree - 180) / 360)}
y1={offset - NDateTime.CLOCK_HAND_OFFSET * Math.sin(Math.PI * 2 * (degree - 180) / 360)}
x2={offset + (radius) * Math.cos(Math.PI * 2 * (degree) / 360)}
y2={offset - (radius) * Math.sin(Math.PI * 2 * (degree) / 360)}
className='second-hand' />);
},
renderTime: function(date, popoverType) {
var _this = this;
var allPopoverType = this.guessDisplayFormatType();
if (!this.hasTimeToDisplay(allPopoverType)) {
return null;
}
var styles = {
float: 'left',
width: this.hasDateToDisplay(allPopoverType) ? '50%' : '100%'
};
var titleFormat = 'HH';
if (this.hasSecond()) {
titleFormat = 'HH:mm:ss';
} else if (this.hasMinute()) {
titleFormat = 'HH:mm';
}
var hiddenWhenMobile = this.state.dateViewWhenMobilePhone ? ' hidden' : '';
return (<div className={'time-view' + hiddenWhenMobile} style={styles}>
<div className='calendar-header'>
{date.format(titleFormat)}
</div>
<div className='calendar-body'>
<div className='time-view-body-body'>
<div style={{height: NDateTime.CLOCK_RADIUS * 2, width: NDateTime.CLOCK_RADIUS * 2, position: 'relative'}}>
<svg className='clock'
height={NDateTime.CLOCK_RADIUS * 2}
width={NDateTime.CLOCK_RADIUS * 2}>
{this.renderMinuteDial()}
{this.is12Hour() ? this.render12HourDial(date, popoverType) : this.render24HourDial()}
{this.renderHourHand(date, NDateTime.CLOCK_RADIUS)}
{this.renderMinuteHand(date, NDateTime.CLOCK_RADIUS, NDateTime.CLOCK_RADIUS)}
{this.renderSecondHand(date, NDateTime.CLOCK_RADIUS, NDateTime.CLOCK_RADIUS)}
</svg>
<div style={{position: 'absolute',
backgroundColor: 'transparent',
top: 0,
left: 0,
height: NDateTime.CLOCK_RADIUS * 2,
width: NDateTime.CLOCK_RADIUS * 2}}
onClick={this.onClockClicked.bind(this, popoverType)}/>
</div>
</div>
</div>
</div>);
},
renderPopoverContent: function(options) {
var date = options ? options.date : null;
var popoverType = options ? options.type : null;
date = date ? date : this.getValueFromModel();
date = date ? date : this.getToday();
if (popoverType == null) {
popoverType = this.getInitialPopoverType() || this.guessDisplayFormatType();
if (this.isMobilePhone() && this.hasDateToDisplay(popoverType)) {
// first time show in mobile phone
this.state.dateViewWhenMobilePhone = true;
}
}
var styles = {
float: 'left',
width: this.hasTimeToDisplay(this.guessDisplayFormatType()) ? '50%' : '100%'
};
if ((popoverType & NDateTime.FORMAT_TYPES.DAY) != 0) {
// has day, YMD
this.startClockInterval(NDateTime.FORMAT_TYPES.DAY, date);
return (<div className="popover-content row">
<div className='date-view' style={styles}>
{this.renderDayHeader(date)}
{this.renderDayBody(date)}
</div>
{this.renderTime(date, NDateTime.FORMAT_TYPES.DAY)}
{this.renderPopoverContentFooter(this.getToday(), NDateTime.FORMAT_TYPES.DAY)}
</div>);
} else if ((popoverType & NDateTime.FORMAT_TYPES.MONTH) != 0) {
// has month, YM
this.startClockInterval(NDateTime.FORMAT_TYPES.MONTH, date);
return (<div className="popover-content row">
<div className='date-view' style={styles}>
{this.renderMonthHeader(date)}
{this.renderMonthBody(date)}
</div>
{this.renderTime(date, NDateTime.FORMAT_TYPES.MONTH)}
{this.renderPopoverContentFooter(this.getToday(), NDateTime.FORMAT_TYPES.MONTH)}
</div>);
} else if ((popoverType & NDateTime.FORMAT_TYPES.YEAR) != 0) {
// has year, YEAR
this.startClockInterval(NDateTime.FORMAT_TYPES.YEAR, date);
return (<div className="popover-content row">
<div className='date-view' style={styles}>
{this.renderYearHeader(date)}
{this.renderYearBody(date)}
</div>
{this.renderTime(date, NDateTime.FORMAT_TYPES.YEAR)}
{this.renderPopoverContentFooter(this.getToday(), NDateTime.FORMAT_TYPES.YEAR)}
</div>);
} else {
this.startClockInterval(popoverType, date);
// only time
return (<div className="popover-content row">
{this.renderTime(date, this.guessDisplayFormatType())}
{this.renderPopoverContentFooter(this.getToday(), popoverType)}
</div>);
}
},
isPopoverMatchComponentWidth: function() {
return false;
},
hasPopoverContentWrapper: function() {
return false;
},
getPopoverContainerCSS: function() {
var displayFormatType = this.guessDisplayFormatType();
return $pt.LayoutHelper.classSet({
'n-datetime': true,
'time-only': !this.hasDateToDisplay(displayFormatType) && this.hasTimeToDisplay(displayFormatType),
'date-only': this.hasDateToDisplay(displayFormatType) && !this.hasTimeToDisplay(displayFormatType),
'date-and-time': this.hasDateToDisplay(displayFormatType) && this.hasTimeToDisplay(displayFormatType)
});
},
beforeRenderPopover: function(options) {
if (!options) {
options = {};
}
if (options.set) {
this.setValueToModel(options.date);
}
},
beforeDestoryPopover: function() {
this.stopClockInterval();
},
stopClockInterval: function() {
if (this.state.clockInterval) {
clearTimeout(this.state.clockInterval.handler);
this.state.clockInterval = null;
}
},
startClockInterval: function(popoverType, currentTime) {
if (!this.getComponentOption('runClock', true)) {
return;
}
var value = this.getValueFromModel();
if (value == null) {
this.stopClockInterval();
this.state.clockInterval = {
handler: setTimeout(function() {
this.renderPopover({date: currentTime.add(1, 's'), type: popoverType});
}.bind(this), 1000),
type: popoverType
};
} else {
this.stopClockInterval();
}
},
/**
* check display type has time or not
* @returns {boolean}
*/
hasTimeToDisplay: function(type) {
if (typeof type === 'undefined') {
type = this.guessDisplayFormatType();
}
return (type &
(NDateTime.FORMAT_TYPES.HOUR | NDateTime.FORMAT_TYPES.MINUTE |
NDateTime.FORMAT_TYPES.SECOND | NDateTime.FORMAT_TYPES.MILLSECOND)) != 0;
},
/**
* check display type has date or not
* @returns {boolean}
*/
hasDateToDisplay: function(type) {
if (typeof type === 'undefined') {
type = this.guessDisplayFormatType();
}
return (type & NDateTime.FORMAT_TYPES.YMD) != 0;
},
/**
* guess display format type
* @returns {number} format type
* @see NDateTime.FORMAT_TYPES
*/
guessDisplayFormatType: function() {
var format = this.getPrimaryDisplayFormat();
var hasYear = format.indexOf('Y') != -1;
var hasMonth = format.indexOf('M') != -1;
var hasDay = format.indexOf('D') != -1;
var hasHour = format.indexOf('H') != -1;
var hasMinute = format.indexOf('m') != -1;
var hasSecond = format.indexOf('s') != -1;
var hasMillsecond = format.indexOf('S') != -1;
return (hasYear ? NDateTime.FORMAT_TYPES.YEAR : 0) +
(hasMonth ? NDateTime.FORMAT_TYPES.MONTH : 0) +
(hasDay ? NDateTime.FORMAT_TYPES.DAY : 0) +
(hasHour ? NDateTime.FORMAT_TYPES.HOUR : 0) +
(hasMinute ? NDateTime.FORMAT_TYPES.MINUTE : 0) +
(hasSecond ? NDateTime.FORMAT_TYPES.SECOND : 0) +
(hasMillsecond ? NDateTime.FORMAT_TYPES.MILLSECOND : 0);
},
onCalendarButtonClicked: function() {
if (!this.isEnabled() || this.isViewMode()) {
// do nothing
return;
}
this.showPopover();
if (!this.isMobilePhone()) {
this.getTextInput().focus();
} else {
this.getTextInput().blur();
}
},
onTextInputFocused: function () {
$(ReactDOM.findDOMNode(this.refs.focusLine)).toggleClass('focus');
$(ReactDOM.findDOMNode(this.refs.normalLine)).toggleClass('focus');
},
onTextInputBlurred: function () {
$(ReactDOM.findDOMNode(this.refs.focusLine)).toggleClass('focus');
$(ReactDOM.findDOMNode(this.refs.normalLine)).toggleClass('focus');
var text = this.getTextInput().val();
if (text.length == 0 || text.isBlank()) {
this.setValueToModel(null);
} else {
var date = this.convertValueFromString(text, this.getDisplayFormat());
if (date == null && text.length != 0) {
// invalid date
this.setValueToModel(null);
this.setValueToTextInput(null);
} else {
this.setValueToModel(date);
this.setValueToTextInput(this.getValueFromModel());
}
}
},
onYearSelected: function(date) {
this.setValueToModel(date);
var type = this.guessDisplayFormatType();
// no time display and only year display, hide
if (!this.hasTimeToDisplay(type) && ((type & NDateTime.FORMAT_TYPES.MONTH) == 0)) {
this.hidePopover();
} else {
this.renderPopover({date: date, type: NDateTime.FORMAT_TYPES.MONTH});
}
},
onMonthSelected: function(date) {
this.setValueToModel(date);
var type = this.guessDisplayFormatType();
// no time display and no day display, hide
if (!this.hasTimeToDisplay(type) && ((type & NDateTime.FORMAT_TYPES.DAY) == 0)) {
this.hidePopover();
} else {
this.renderPopover({date: date, type: NDateTime.FORMAT_TYPES.DAY});
}
},
onDaySelected: function(date) {
this.setValueToModel(date);
var type = this.guessDisplayFormatType();
// no time display, hide
if (!this.hasTimeToDisplay(type)) {
this.hidePopover();
} else {
this.renderPopover({date: date, type: NDateTime.FORMAT_TYPES.DAY});
}
},
onClockClicked: function(popoverType, evt) {
var offset = $(evt.target).offset();
// be careful of the quadrant
var point = {
x: (evt.pageX - offset.left) - NDateTime.CLOCK_RADIUS,
y: NDateTime.CLOCK_RADIUS - (evt.pageY - offset.top)
};
// window.console.log('Mouse Point: ' + point.x + ',' + point.y);
// calculate the radius length of point
var length = Math.sqrt(Math.pow(point.x, 2) + Math.pow(point.y, 2));
// window.console.log('Point radius: ' + length);
// calculate it is what
if (length > 101) {
// do nothing
return;
}
var eventType = NDateTime.FORMAT_TYPES.SECOND;
if (length > (NDateTime.CLOCK_RADIUS - NDateTime.CLOCK_HAND_OFFSET)) {
// change second or minute or hour
if (this.hasSecond()) {
eventType = NDateTime.FORMAT_TYPES.SECOND;
} else if (this.hasMinute()) {
eventType = NDateTime.FORMAT_TYPES.MINUTE;
} else {
eventType = NDateTime.FORMAT_TYPES.HOUR;
}
} else if (length > (NDateTime.CLOCK_RADIUS * NDateTime.CLOCK_HOUR_PERCENTAGE)) {
// change minute or hour
if (this.hasMinute()) {
eventType = NDateTime.FORMAT_TYPES.MINUTE;
} else {
eventType = NDateTime.FORMAT_TYPES.HOUR;
}
} else {
// change hour
eventType = NDateTime.FORMAT_TYPES.HOUR;
}
// window.console.log('Event Type: ' + eventType);
// calculate degree in coordinate system
var degree = 0;
if (point.x == 0) {
degree = point.y >= 0 ? 90 : 270;
} else {
// atan is from -Math.PI/2 to Math.PI/2
degree = Math.atan(point.y / point.x) * 180 / Math.PI;
// transform to coordinate system degree
if (point.x > 0 && point.y >= 0) {
// do nothing
} else if (point.x < 0) {
degree += 180;
} else {
degree += 360;
}
}
// transform to real clock coordinate system
if (degree <= 90) {
degree = 90 - degree;
} else {
degree = 450 - degree;
}
// window.console.log('Degree: ' + degree);
var currentHour, hour, minute, second;
var date = this.getValueFromModel();
date = date == null ? this.getToday() : date;
currentHour = date.hour();
if (eventType == NDateTime.FORMAT_TYPES.SECOND) {
second = Math.floor(degree / 6) + (degree % 6 < 3 ? 0 : 1);
date.second(second);
} else if (eventType == NDateTime.FORMAT_TYPES.MINUTE) {
minute = Math.floor(degree / 6) + (degree % 6 < 3 ? 0 : 1);
date.minute(minute);
} else if (this.is12Hour()) {
hour = Math.floor(degree / 30) + (degree % 30 < 15 ? 0 : 1);
date.hour(currentHour <= 11 ? hour : (hour + 12));
} else {
hour = Math.floor(degree / 15) + (degree % 15 < 7.5 ? 0 : 1);
date.hour(hour);
}
// window.console.log('Hour: [' + hour + '], Minute: [' + minute + '], Second: [' + second + ']');
this.renderPopover({date: date, type: popoverType, set: true});
},
onAMPMSelected: function(isAM, type) {
var value = this.getValueFromModel();
value = value == null ? this.getToday() : value;
var hour = value.hour();
if (isAM) {
hour = hour > 11 ? (hour - 12) : hour;
} else {
hour = hour <= 11 ? (hour + 12) : hour;
}
value.hour(hour);
this.setValueToModel(value);
this.renderPopover({date: value, type: type});
},
switchToTimeViewOnMobile: function(evt) {
var target = $(evt.target);
if (target[0].tagName === 'SPAN') {
target = target.closest('a');
}
target.addClass('hidden').siblings('.date-switch').removeClass('hidden');
this.state.dateViewWhenMobilePhone = false;
var parent = target.closest('.popover-content');
parent.children('.time-view').removeClass('hidden');
parent.children('.date-view').addClass('hidden');
},
switchToDateViewOnMobile: function(evt) {
var target = $(evt.target);
if (target[0].tagName === 'SPAN') {
target = target.closest('a');
}
target.addClass('hidden').siblings('.time-switch').removeClass('hidden');
this.state.dateViewWhenMobilePhone = true;
var parent = target.closest('.popover-content');
parent.children('.time-view').addClass('hidden');
parent.children('.date-view').removeClass('hidden');
},
onTextInputChange: function() {
// since the text input might be incorrect date format,
// or use un-strict mode to format
// cannot know the result of moment format
// move process to changing at blur event
var text = this.getTextInput().val();
if (text.length == 0 || text.isBlank()) {
this.setValueToModel(null);
} else {
var date = this.convertValueFromString(text, this.getDisplayFormat(), true);
if (date == null && text.length != 0) {
// TODO invalid date, do nothing now. donot know how to deal with it...
} else {
this.setValueToModel(date);
}
}
},
onModelChanged: function (evt) {
var newValue = evt.new;
var text = this.getTextInput().val();
if (newValue == null || (newValue + '').isBlank()) {
if (text == null || text.isBlank()) {
// do nothing
} else {
this.forceUpdate();
}
} else {
var valueDate = this.getValueFromModel();
var textDate = this.convertValueFromString(text, this.getDisplayFormat(), true);
if (valueDate.isSame(textDate)) {
// do nothing
} else {
this.forceUpdate();
}
}
},
getValueFromModel: function () {
return this.convertValueFromString(this.getModel().get(this.getDataId()));
},
setValueToModel: function (value) {
var formattedValue = value;
if (value != null) {
if (typeof value === 'string') {
if (value.isBlank()) {
formattedValue = null;
} else {
formattedValue = moment(value, this.getPrimaryDisplayFormat())
.format(this.getValueFormat());
}
} else {
formattedValue = value.format(this.getPrimaryDisplayFormat());
formattedValue = moment(formattedValue, this.getPrimaryDisplayFormat()).format(this.getValueFormat());
}
}
this.getModel().set(this.getDataId(), formattedValue);
},
setValueToTextInput: function(value) {
this.getTextInput().val(this.convertValueToString(value, this.getPrimaryDisplayFormat()));
},
/**
* convert value from string
* @param value {string}
* @param format {string} optional, use value format if not passed
* @returns {moment}
*/
convertValueFromString: function (value, format, useStrict) {
var date = (value == null || value.isBlank()) ? null : moment(value, format ? format : this.getValueFormat(), this.getLocale(), useStrict === true ? true : undefined);
return (date == null || !date.isValid()) ? null : date;
},
/**
* convert value to string
* @param value {moment}
* @param format {string} optional, use value format if not passed
* @returns {string}
*/
convertValueToString: function(value, format) {
return value == null ? null : value.format(format ? format : this.getValueFormat());
},
/**
* get first day of week
* returns {number} 0-6, sunday to saturday
*/
getFirstDayOfWeek: function() {
return this.getMomentLocaleData().firstDayOfWeek();
},
/**
* get day of week.
* returns {number} 0-6, sunday to saturday
*/
getDayOfWeek: function(date) {
return date.day();
},
/**
* get days count of month
* @returns {number}
*/
getDaysOfMonth: function(date) {
var month = date.month() + 1;
switch(month) {
case 1: case 3: case 5: case 7: case 8: case 10: case 12:
return 31;
case 4: case 6: case 9: case 11:
return 30;
case 2:
return date.isLeapYear() ? 29 : 28;
default:
// never run to here
window.console.warn('Something wrong with momentjs.');
window.console.warn(date);
return 31;
}
},
getComponent: function() {
return $(ReactDOM.findDOMNode(this.refs.comp));
},
getTextInput: function() {
return $(ReactDOM.findDOMNode(this.refs.text));
},
/**
* get display format
* @returns {string}
*/
getDisplayFormat: function() {
var format = this.getComponentOption('format');
return format ? format : NDateTime.FORMAT;
},
getPrimaryDisplayFormat: function() {
var format = this.getDisplayFormat();
if (Array.isArray(format)) {
return format[0];
} else {
return format;
}
},
getHeaderMonthFormat: function() {
var format = this.getComponentOption('headerMonthFormat');
return format ? format : NDateTime.HEADER_MONTH_FORMAT;
},
getHeaderYearFormat: function() {
var format = this.getComponentOption('headerYearFormat');
return format ? format : NDateTime.HEADER_YEAR_FORMAT;
},
getBodyYearFormat: function() {
return this.getComponentOption('bodyYearFormat');
},
getMinDate: function() {
var min = this.getComponentOption('min');
if (min == null) {
min = moment('0001-01-01');
} else if (typeof min === 'function') {
min = min.call(this);
}
return min;
},
getMaxDate: function() {
var max = this.getComponentOption('max');
if (max == null) {
max = moment('9999-12-31');
} else if (typeof max === 'function') {
max = max.call(this);
}
return max;
},
/**
* get value format
* @returns {string}
*/
getValueFormat: function() {
var valueFormat = this.getComponentOption('valueFormat');
return valueFormat ? valueFormat : NDateTime.VALUE_FORMAT;
},
is12Hour: function() {
// seems mobile doesn't support event on svg and its inner nodes
// so doesn't support 12 hours format in mobile equipments
return this.getComponentOption('hour') == 12 && !this.isMobile();
},
getHourRadius: function() {
var hourRadius = NDateTime.CLOCK_RADIUS;
if (this.hasMinute()) {
// with minute and second
hourRadius *= NDateTime.CLOCK_HOUR_PERCENTAGE;
}
return hourRadius;
},
hasYear: function() {
return (this.guessDisplayFormatType() & NDateTime.FORMAT_TYPES.YEAR) != 0;
},
hasMonth: function() {
return (this.guessDisplayFormatType() & NDateTime.FORMAT_TYPES.MONTH) != 0;
},
hasDay: function() {
return (this.guessDisplayFormatType() & NDateTime.FORMAT_TYPES.DAY) != 0;
},
hasHour: function() {
return (this.guessDisplayFormatType() & NDateTime.FORMAT_TYPES.HOUR) != 0;
},
hasMinute: function() {
return (this.guessDisplayFormatType() & NDateTime.FORMAT_TYPES.MINUTE) != 0;
},
hasSecond: function() {
return (this.guessDisplayFormatType() & NDateTime.FORMAT_TYPES.SECOND) != 0;
},
/**
* get icon definition by given icon key
* @returns {string}
*/
getIcon: function(key) {
return this.getComponentOption('icons')[key];
},
getTextInViewMode: function() {
return this.convertValueToString(this.getValueFromModel(), this.getPrimaryDisplayFormat());
},
getLocale: function() {
return this.getComponentOption('locale');
},
getMomentLocaleData: function() {
return moment.localeData(this.getLocale());
},
getToday: function() {
var today = moment().locale(this.getLocale());
var defaultTime = this.getComponentOption('defaultTime');
if (defaultTime) {
if (typeof defaultTime === 'function') {
today = defaultTime.call(this, today);
} else {
today = defaultTime;
}
}
return today;
},
getInitialPopoverType: function() {
return this.getComponentOption('popoverType');
}
}));
$pt.Components.NDateTime = NDateTime;
$pt.LayoutHelper.registerComponentRenderer($pt.ComponentConstants.Date, function (model, layout, direction, viewMode) {
return <$pt.Components.NDateTime {...$pt.LayoutHelper.transformParameters(model, layout, direction, viewMode)}/>;
});
}(window, jQuery, moment, React, ReactDOM, $pt));