Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F1429552
mw.widgets.CalendarWidget.js
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
21 KB
Referenced Files
None
Subscribers
None
mw.widgets.CalendarWidget.js
View Options
/*!
* MediaWiki Widgets – CalendarWidget class.
*
* @copyright 2011-2015 MediaWiki Widgets Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/* global moment */
(
function
()
{
/**
* @classdesc Calendar widget.
*
* You will most likely want to use {@link mw.widgets.DateInputWidget} instead of CalendarWidget directly.
*
* @class
* @extends OO.ui.Widget
* @mixes OO.ui.mixin.TabIndexedElement
* @mixes OO.ui.mixin.FloatableElement
* @mixes OO.ui.mixin.ClippableElement
*
* @constructor
* @description Creates an mw.widgets.CalendarWidget object.
* @param {Object} [config] Configuration options
* @param {boolean} [config.lazyInitOnToggle=false] Don't build most of the interface until
* `.toggle( true )` is called. Meant to be used when the calendar is not immediately visible.
* @param {string} [config.precision='day'] Date precision to use, 'day' or 'month'
* @param {string|null} [config.duoDecade='prev'] Alignment of years to display in picker, use 'prev' or 'next'
* 'prev' is previous and current decades
* 'next' is current and next decades
* @param {string|null} [config.date=null] Day or month date (depending on `precision`), in the format
* 'YYYY-MM-DD' or 'YYYY-MM'. When null, the calendar will show today's date, but not select
* it.
*/
mw
.
widgets
.
CalendarWidget
=
function
MWWCalendarWidget
(
config
)
{
// Config initialization
config
=
config
||
{};
// Parent constructor
mw
.
widgets
.
CalendarWidget
.
super
.
call
(
this
,
config
);
// Mixin constructors
OO
.
ui
.
mixin
.
TabIndexedElement
.
call
(
this
,
Object
.
assign
(
{},
config
,
{
$tabIndexed
:
this
.
$element
}
)
);
OO
.
ui
.
mixin
.
ClippableElement
.
call
(
this
,
Object
.
assign
(
{
$clippable
:
this
.
$element
},
config
)
);
OO
.
ui
.
mixin
.
FloatableElement
.
call
(
this
,
config
);
// Flipping implementation derived from MenuSelectWidget
// Initial vertical positions other than 'center' will result in
// the menu being flipped if there is not enough space in the container.
// Store the original position so we know what to reset to.
this
.
originalVerticalPosition
=
this
.
verticalPosition
;
// Properties
this
.
lazyInitOnToggle
=
!!
config
.
lazyInitOnToggle
;
this
.
precision
=
config
.
precision
||
'day'
;
this
.
duoDecade
=
config
.
duoDecade
||
'prev'
;
// Currently selected date (day or month)
this
.
date
=
null
;
// Current UI state (date and precision we're displaying right now)
this
.
moment
=
null
;
this
.
displayLayer
=
this
.
getDisplayLayers
()[
0
];
// 'month', 'year', 'duodecade'
this
.
$header
=
$
(
'<div>'
).
addClass
(
'mw-widget-calendarWidget-header'
);
this
.
$bodyOuterWrapper
=
$
(
'<div>'
).
addClass
(
'mw-widget-calendarWidget-body-outer-wrapper'
);
this
.
$bodyWrapper
=
$
(
'<div>'
).
addClass
(
'mw-widget-calendarWidget-body-wrapper'
);
this
.
$body
=
$
(
'<div>'
).
addClass
(
'mw-widget-calendarWidget-body'
);
// Events
this
.
$element
.
on
(
{
focus
:
this
.
onFocus
.
bind
(
this
),
mousedown
:
this
.
onClick
.
bind
(
this
),
keydown
:
this
.
onKeyDown
.
bind
(
this
)
}
);
// Initialization
this
.
$element
.
addClass
(
'mw-widget-calendarWidget'
)
.
append
(
this
.
$header
,
this
.
$bodyOuterWrapper
.
append
(
this
.
$bodyWrapper
.
append
(
this
.
$body
)
)
);
if
(
!
this
.
lazyInitOnToggle
)
{
this
.
buildHeaderButtons
();
}
this
.
setDate
(
config
.
date
!==
undefined
?
config
.
date
:
null
);
};
/* Inheritance */
OO
.
inheritClass
(
mw
.
widgets
.
CalendarWidget
,
OO
.
ui
.
Widget
);
OO
.
mixinClass
(
mw
.
widgets
.
CalendarWidget
,
OO
.
ui
.
mixin
.
TabIndexedElement
);
OO
.
mixinClass
(
mw
.
widgets
.
CalendarWidget
,
OO
.
ui
.
mixin
.
FloatableElement
);
OO
.
mixinClass
(
mw
.
widgets
.
CalendarWidget
,
OO
.
ui
.
mixin
.
ClippableElement
);
/* Events */
/**
* A change event is emitted when the chosen date changes.
*
* @event mw.widgets.CalendarWidget.change
* @param {string} date Day or month date, in the format 'YYYY-MM-DD' or 'YYYY-MM'
*/
/* Static properties */
/**
* Positions to flip to if there isn't room in the container for the
* menu in a specific direction.
*
* @name mw.widgets.CalendarWidget.flippedPositions
* @type {Object.<string,string>}
*/
mw
.
widgets
.
CalendarWidget
.
static
.
flippedPositions
=
{
below
:
'above'
,
above
:
'below'
,
top
:
'bottom'
,
bottom
:
'top'
};
/* Methods */
/**
* Get the date format ('YYYY-MM-DD' or 'YYYY-MM', depending on precision), which is used
* internally and for dates accepted by #setDate and returned by #getDate.
*
* @private
* @return {string} Format
*/
mw
.
widgets
.
CalendarWidget
.
prototype
.
getDateFormat
=
function
()
{
return
{
day
:
'YYYY-MM-DD'
,
month
:
'YYYY-MM'
}[
this
.
precision
];
};
/**
* Get the date precision this calendar uses, 'day' or 'month'.
*
* @private
* @return {string} Precision, 'day' or 'month'
*/
mw
.
widgets
.
CalendarWidget
.
prototype
.
getPrecision
=
function
()
{
return
this
.
precision
;
};
/**
* Get list of possible display layers.
*
* @private
* @return {string[]} Layers
*/
mw
.
widgets
.
CalendarWidget
.
prototype
.
getDisplayLayers
=
function
()
{
return
[
'month'
,
'year'
,
'duodecade'
].
slice
(
this
.
precision
===
'month'
?
1
:
0
);
};
/**
* Update the calendar.
*
* @private
* @param {string|null} [fade=null] Direction in which to fade out current calendar contents,
* 'previous', 'next', 'up' or 'down'; or 'auto', which has the same result as 'previous' or
* 'next' depending on whether the current date is later or earlier than the previous.
*/
mw
.
widgets
.
CalendarWidget
.
prototype
.
updateUI
=
function
(
fade
)
{
const
$bodyWrapper
=
this
.
$bodyWrapper
;
if
(
this
.
lazyInitOnToggle
)
{
// We're being called from the constructor and not being shown yet, do nothing
return
;
}
if
(
this
.
displayLayer
===
this
.
previousDisplayLayer
&&
this
.
date
===
this
.
previousDate
&&
this
.
previousMoment
&&
this
.
previousMoment
.
isSame
(
this
.
moment
,
this
.
precision
===
'month'
?
'month'
:
'day'
)
)
{
// Already displayed
return
;
}
if
(
fade
===
'auto'
)
{
if
(
!
this
.
previousMoment
)
{
fade
=
null
;
}
else
if
(
this
.
previousMoment
.
isBefore
(
this
.
moment
,
this
.
precision
===
'month'
?
'month'
:
'day'
)
)
{
fade
=
'next'
;
}
else
if
(
this
.
previousMoment
.
isAfter
(
this
.
moment
,
this
.
precision
===
'month'
?
'month'
:
'day'
)
)
{
fade
=
'previous'
;
}
else
{
fade
=
null
;
}
}
let
items
=
[];
if
(
this
.
$oldBody
)
{
this
.
$oldBody
.
remove
();
}
this
.
$oldBody
=
this
.
$body
.
addClass
(
'mw-widget-calendarWidget-old-body'
);
// Clone without children
this
.
$body
=
$
(
this
.
$body
[
0
].
cloneNode
(
false
)
)
.
removeClass
(
'mw-widget-calendarWidget-old-body'
)
.
toggleClass
(
'mw-widget-calendarWidget-body-month'
,
this
.
displayLayer
===
'month'
)
.
toggleClass
(
'mw-widget-calendarWidget-body-year'
,
this
.
displayLayer
===
'year'
)
.
toggleClass
(
'mw-widget-calendarWidget-body-duodecade'
,
this
.
displayLayer
===
'duodecade'
);
const
today
=
moment
();
const
selected
=
moment
(
this
.
getDate
(),
this
.
getDateFormat
()
);
switch
(
this
.
displayLayer
)
{
case
'month'
:
this
.
labelButton
.
setLabel
(
this
.
moment
.
format
(
'MMMM YYYY'
)
);
this
.
labelButton
.
toggle
(
true
);
this
.
upButton
.
toggle
(
true
);
// First week displayed is the first week spanned by the month, unless it begins on Monday, in
// which case first week displayed is the previous week. This makes the calendar "balanced"
// and also neatly handles 28-day February sometimes spanning only 4 weeks.
// eslint-disable-next-line no-case-declarations
const
currentDay
=
moment
(
this
.
moment
).
startOf
(
'month'
).
subtract
(
1
,
'day'
).
startOf
(
'week'
);
// Day-of-week labels. Localisation-independent: works with weeks starting on Saturday, Sunday
// or Monday.
for
(
let
w
=
0
;
w
<
7
;
w
++
)
{
items
.
push
(
$
(
'<div>'
)
.
addClass
(
'mw-widget-calendarWidget-day-heading'
)
.
text
(
currentDay
.
format
(
'dd'
)
)
);
currentDay
.
add
(
1
,
'day'
);
}
currentDay
.
subtract
(
7
,
'days'
);
// Actual calendar month. Always displays 6 weeks, for consistency (months can span 4 to 6
// weeks).
for
(
let
i
=
0
;
i
<
42
;
i
++
)
{
items
.
push
(
$
(
'<div>'
)
.
addClass
(
'mw-widget-calendarWidget-item mw-widget-calendarWidget-day'
)
.
toggleClass
(
'mw-widget-calendarWidget-day-additional'
,
!
currentDay
.
isSame
(
this
.
moment
,
'month'
)
)
.
toggleClass
(
'mw-widget-calendarWidget-day-today'
,
currentDay
.
isSame
(
today
,
'day'
)
)
.
toggleClass
(
'mw-widget-calendarWidget-item-selected'
,
currentDay
.
isSame
(
selected
,
'day'
)
)
.
text
(
currentDay
.
format
(
'D'
)
)
.
data
(
'date'
,
currentDay
.
date
()
)
.
data
(
'month'
,
currentDay
.
month
()
)
.
data
(
'year'
,
currentDay
.
year
()
)
);
currentDay
.
add
(
1
,
'day'
);
}
break
;
case
'year'
:
this
.
labelButton
.
setLabel
(
this
.
moment
.
format
(
'YYYY'
)
);
this
.
labelButton
.
toggle
(
true
);
this
.
upButton
.
toggle
(
true
);
// eslint-disable-next-line no-case-declarations
const
currentMonth
=
moment
(
this
.
moment
).
startOf
(
'year'
);
for
(
let
m
=
0
;
m
<
12
;
m
++
)
{
items
.
push
(
$
(
'<div>'
)
.
addClass
(
'mw-widget-calendarWidget-item mw-widget-calendarWidget-month'
)
.
toggleClass
(
'mw-widget-calendarWidget-item-selected'
,
currentMonth
.
isSame
(
selected
,
'month'
)
)
.
text
(
currentMonth
.
format
(
'MMMM'
)
)
.
data
(
'month'
,
currentMonth
.
month
()
)
);
currentMonth
.
add
(
1
,
'month'
);
}
// Shuffle the array to display months in columns rather than rows:
// | Jan | Jul |
// | Feb | Aug |
// | Mar | Sep |
// | Apr | Oct |
// | May | Nov |
// | Jun | Dec |
items
=
[
items
[
0
],
items
[
6
],
items
[
1
],
items
[
7
],
items
[
2
],
items
[
8
],
items
[
3
],
items
[
9
],
items
[
4
],
items
[
10
],
items
[
5
],
items
[
11
]
];
break
;
case
'duodecade'
:
this
.
labelButton
.
setLabel
(
null
);
this
.
labelButton
.
toggle
(
false
);
this
.
upButton
.
toggle
(
false
);
// eslint-disable-next-line no-case-declarations
let
currentYear
;
if
(
this
.
duoDecade
===
'prev'
)
{
currentYear
=
moment
(
{
year
:
Math
.
floor
(
(
this
.
moment
.
year
()
-
10
)
/
10
)
*
10
}
);
}
else
if
(
this
.
duoDecade
===
'next'
)
{
currentYear
=
moment
(
{
year
:
Math
.
floor
(
this
.
moment
.
year
()
/
10
)
*
10
}
);
}
for
(
let
y
=
0
;
y
<
20
;
y
++
)
{
items
.
push
(
$
(
'<div>'
)
.
addClass
(
'mw-widget-calendarWidget-item mw-widget-calendarWidget-year'
)
.
toggleClass
(
'mw-widget-calendarWidget-item-selected'
,
currentYear
.
isSame
(
selected
,
'year'
)
)
.
text
(
currentYear
.
format
(
'YYYY'
)
)
.
data
(
'year'
,
currentYear
.
year
()
)
);
currentYear
.
add
(
1
,
'year'
);
}
break
;
}
this
.
$body
.
append
(
...
items
);
$bodyWrapper
.
removeClass
(
'mw-widget-calendarWidget-body-wrapper-fade-up'
)
.
removeClass
(
'mw-widget-calendarWidget-body-wrapper-fade-down'
)
.
removeClass
(
'mw-widget-calendarWidget-body-wrapper-fade-previous'
)
.
removeClass
(
'mw-widget-calendarWidget-body-wrapper-fade-next'
);
let
needsFade
=
this
.
previousDisplayLayer
!==
this
.
displayLayer
;
if
(
this
.
displayLayer
===
'month'
)
{
needsFade
=
needsFade
||
!
this
.
moment
.
isSame
(
this
.
previousMoment
,
'month'
);
}
else
if
(
this
.
displayLayer
===
'year'
)
{
needsFade
=
needsFade
||
!
this
.
moment
.
isSame
(
this
.
previousMoment
,
'year'
);
}
else
if
(
this
.
displayLayer
===
'duodecade'
)
{
needsFade
=
needsFade
||
(
Math
.
floor
(
this
.
moment
.
year
()
/
20
)
*
20
!==
Math
.
floor
(
this
.
previousMoment
.
year
()
/
20
)
*
20
);
}
if
(
fade
&&
needsFade
)
{
this
.
$oldBody
.
find
(
'.mw-widget-calendarWidget-item-selected'
)
.
removeClass
(
'mw-widget-calendarWidget-item-selected'
);
if
(
fade
===
'previous'
||
fade
===
'up'
)
{
this
.
$body
.
insertBefore
(
this
.
$oldBody
);
}
else
if
(
fade
===
'next'
||
fade
===
'down'
)
{
this
.
$body
.
insertAfter
(
this
.
$oldBody
);
}
setTimeout
(
()
=>
{
// The following classes are used here:
// * mw-widget-calendarWidget-body-wrapper-fade-up
// * mw-widget-calendarWidget-body-wrapper-fade-down
// * mw-widget-calendarWidget-body-wrapper-fade-previous
// * mw-widget-calendarWidget-body-wrapper-fade-next
$bodyWrapper
.
addClass
(
'mw-widget-calendarWidget-body-wrapper-fade-'
+
fade
);
}
);
}
else
{
this
.
$oldBody
.
replaceWith
(
this
.
$body
);
}
this
.
previousMoment
=
moment
(
this
.
moment
);
this
.
previousDisplayLayer
=
this
.
displayLayer
;
this
.
previousDate
=
this
.
date
;
this
.
$body
.
on
(
'click'
,
this
.
onBodyClick
.
bind
(
this
)
);
};
/**
* Construct and display buttons to navigate the calendar.
*
* @private
*/
mw
.
widgets
.
CalendarWidget
.
prototype
.
buildHeaderButtons
=
function
()
{
this
.
labelButton
=
new
OO
.
ui
.
ButtonWidget
(
{
tabIndex
:
-
1
,
label
:
''
,
framed
:
false
,
classes
:
[
'mw-widget-calendarWidget-labelButton'
]
}
);
// FIXME This button is actually not clickable because labelButton covers it,
// should it just be a plain icon?
this
.
upButton
=
new
OO
.
ui
.
ButtonWidget
(
{
tabIndex
:
-
1
,
framed
:
false
,
icon
:
'collapse'
,
classes
:
[
'mw-widget-calendarWidget-upButton'
]
}
);
this
.
prevButton
=
new
OO
.
ui
.
ButtonWidget
(
{
tabIndex
:
-
1
,
framed
:
false
,
icon
:
'previous'
,
classes
:
[
'mw-widget-calendarWidget-prevButton'
]
}
);
this
.
nextButton
=
new
OO
.
ui
.
ButtonWidget
(
{
tabIndex
:
-
1
,
framed
:
false
,
icon
:
'next'
,
classes
:
[
'mw-widget-calendarWidget-nextButton'
]
}
);
this
.
labelButton
.
connect
(
this
,
{
click
:
'onUpButtonClick'
}
);
this
.
upButton
.
connect
(
this
,
{
click
:
'onUpButtonClick'
}
);
this
.
prevButton
.
connect
(
this
,
{
click
:
'onPrevButtonClick'
}
);
this
.
nextButton
.
connect
(
this
,
{
click
:
'onNextButtonClick'
}
);
this
.
$header
.
append
(
this
.
prevButton
.
$element
,
this
.
nextButton
.
$element
,
this
.
labelButton
.
$element
,
this
.
upButton
.
$element
);
};
/**
* Handle click events on the "up" button, switching to less precise view.
*
* @private
*/
mw
.
widgets
.
CalendarWidget
.
prototype
.
onUpButtonClick
=
function
()
{
const
layers
=
this
.
getDisplayLayers
(),
currentLayer
=
layers
.
indexOf
(
this
.
displayLayer
);
if
(
currentLayer
!==
layers
.
length
-
1
)
{
// One layer up
this
.
displayLayer
=
layers
[
currentLayer
+
1
];
this
.
updateUI
(
'up'
);
}
else
{
this
.
updateUI
();
}
};
/**
* Handle click events on the "previous" button, switching to previous pane.
*
* @private
*/
mw
.
widgets
.
CalendarWidget
.
prototype
.
onPrevButtonClick
=
function
()
{
switch
(
this
.
displayLayer
)
{
case
'month'
:
this
.
moment
.
subtract
(
1
,
'month'
);
break
;
case
'year'
:
this
.
moment
.
subtract
(
1
,
'year'
);
break
;
case
'duodecade'
:
this
.
moment
.
subtract
(
20
,
'years'
);
break
;
}
this
.
updateUI
(
'previous'
);
};
/**
* Handle click events on the "next" button, switching to next pane.
*
* @private
*/
mw
.
widgets
.
CalendarWidget
.
prototype
.
onNextButtonClick
=
function
()
{
switch
(
this
.
displayLayer
)
{
case
'month'
:
this
.
moment
.
add
(
1
,
'month'
);
break
;
case
'year'
:
this
.
moment
.
add
(
1
,
'year'
);
break
;
case
'duodecade'
:
this
.
moment
.
add
(
20
,
'years'
);
break
;
}
this
.
updateUI
(
'next'
);
};
/**
* Handle click events anywhere in the body of the widget, which contains the matrix of days,
* months or years to choose. Maybe change the pane or switch to more precise view, depending on
* what gets clicked.
*
* @private
* @param {jQuery.Event} e Click event
*/
mw
.
widgets
.
CalendarWidget
.
prototype
.
onBodyClick
=
function
(
e
)
{
const
$target
=
$
(
e
.
target
),
layers
=
this
.
getDisplayLayers
(),
currentLayer
=
layers
.
indexOf
(
this
.
displayLayer
);
if
(
$target
.
data
(
'year'
)
!==
undefined
)
{
this
.
moment
.
year
(
$target
.
data
(
'year'
)
);
}
if
(
$target
.
data
(
'month'
)
!==
undefined
)
{
this
.
moment
.
month
(
$target
.
data
(
'month'
)
);
}
if
(
$target
.
data
(
'date'
)
!==
undefined
)
{
this
.
moment
.
date
(
$target
.
data
(
'date'
)
);
}
if
(
currentLayer
===
0
)
{
this
.
setDateFromMoment
();
this
.
updateUI
(
'auto'
);
}
else
{
// One layer down
this
.
displayLayer
=
layers
[
currentLayer
-
1
];
this
.
updateUI
(
'down'
);
}
};
/**
* Set the date.
*
* @param {string|null} [date=null] Day or month date, in the format 'YYYY-MM-DD' or 'YYYY-MM'.
* When null, the calendar will show today's date, but not select it. When invalid, the date
* is not changed.
*/
mw
.
widgets
.
CalendarWidget
.
prototype
.
setDate
=
function
(
date
)
{
const
mom
=
date
!==
null
?
moment
(
date
,
this
.
getDateFormat
()
)
:
moment
();
if
(
mom
.
isValid
()
)
{
this
.
moment
=
mom
;
if
(
date
!==
null
)
{
this
.
setDateFromMoment
();
}
else
if
(
this
.
date
!==
null
)
{
this
.
date
=
null
;
this
.
emit
(
'change'
,
this
.
date
);
}
this
.
displayLayer
=
this
.
getDisplayLayers
()[
0
];
this
.
updateUI
();
}
};
/**
* Set the date that is shown in the calendar, but not the selected date.
*
* @param {Object} mom Moment object
*/
mw
.
widgets
.
CalendarWidget
.
prototype
.
setMoment
=
function
(
mom
)
{
if
(
mom
.
isValid
()
)
{
this
.
moment
=
mom
;
this
.
updateUI
();
}
};
/**
* Reset the user interface of this widget to reflect selected date.
*/
mw
.
widgets
.
CalendarWidget
.
prototype
.
resetUI
=
function
()
{
this
.
moment
=
this
.
getDate
()
!==
null
?
moment
(
this
.
getDate
(),
this
.
getDateFormat
()
)
:
moment
();
this
.
displayLayer
=
this
.
getDisplayLayers
()[
0
];
this
.
updateUI
();
};
/**
* Set the date from moment object.
*
* @private
*/
mw
.
widgets
.
CalendarWidget
.
prototype
.
setDateFromMoment
=
function
()
{
// Switch to English locale to avoid number formatting. We want the internal value to be
// '2015-07-24' and not '٢٠١٥-٠٧-٢٤' even if the UI language is Arabic.
const
newDate
=
moment
(
this
.
moment
).
locale
(
'en'
).
format
(
this
.
getDateFormat
()
);
if
(
this
.
date
!==
newDate
)
{
this
.
date
=
newDate
;
this
.
emit
(
'change'
,
this
.
date
);
}
};
/**
* Get current date, in the format 'YYYY-MM-DD' or 'YYYY-MM', depending on precision. Digits will
* not be localised.
*
* @return {string|null} Date string
*/
mw
.
widgets
.
CalendarWidget
.
prototype
.
getDate
=
function
()
{
return
this
.
date
;
};
/**
* Handle focus events.
*
* @private
*/
mw
.
widgets
.
CalendarWidget
.
prototype
.
onFocus
=
function
()
{
this
.
displayLayer
=
this
.
getDisplayLayers
()[
0
];
this
.
updateUI
(
'down'
);
};
/**
* Handle mouse click events.
*
* @private
* @param {jQuery.Event} e Mouse click event
* @return {boolean|undefined} False to cancel the default event
*/
mw
.
widgets
.
CalendarWidget
.
prototype
.
onClick
=
function
(
e
)
{
if
(
!
this
.
isDisabled
()
&&
e
.
which
===
1
)
{
// Prevent unintended focussing
return
false
;
}
};
/**
* Handle key down events.
*
* @private
* @param {jQuery.Event} e Key down event
* @return {boolean|undefined} False to cancel the default event
*/
mw
.
widgets
.
CalendarWidget
.
prototype
.
onKeyDown
=
function
(
e
)
{
const
dir
=
OO
.
ui
.
Element
.
static
.
getDir
(
this
.
$element
),
nextDirectionKey
=
dir
===
'ltr'
?
OO
.
ui
.
Keys
.
RIGHT
:
OO
.
ui
.
Keys
.
LEFT
,
prevDirectionKey
=
dir
===
'ltr'
?
OO
.
ui
.
Keys
.
LEFT
:
OO
.
ui
.
Keys
.
RIGHT
;
let
changed
=
true
;
if
(
!
this
.
isDisabled
()
)
{
switch
(
e
.
which
)
{
case
prevDirectionKey
:
this
.
moment
.
subtract
(
1
,
this
.
precision
===
'month'
?
'month'
:
'day'
);
break
;
case
nextDirectionKey
:
this
.
moment
.
add
(
1
,
this
.
precision
===
'month'
?
'month'
:
'day'
);
break
;
case
OO
.
ui
.
Keys
.
UP
:
this
.
moment
.
subtract
(
1
,
this
.
precision
===
'month'
?
'month'
:
'week'
);
break
;
case
OO
.
ui
.
Keys
.
DOWN
:
this
.
moment
.
add
(
1
,
this
.
precision
===
'month'
?
'month'
:
'week'
);
break
;
case
OO
.
ui
.
Keys
.
PAGEUP
:
this
.
moment
.
subtract
(
1
,
this
.
precision
===
'month'
?
'year'
:
'month'
);
break
;
case
OO
.
ui
.
Keys
.
PAGEDOWN
:
this
.
moment
.
add
(
1
,
this
.
precision
===
'month'
?
'year'
:
'month'
);
break
;
default
:
changed
=
false
;
break
;
}
if
(
changed
)
{
this
.
displayLayer
=
this
.
getDisplayLayers
()[
0
];
this
.
setDateFromMoment
();
this
.
updateUI
(
'auto'
);
return
false
;
}
}
};
/**
* @inheritdoc
*/
mw
.
widgets
.
CalendarWidget
.
prototype
.
toggle
=
function
(
visible
)
{
visible
=
visible
===
undefined
?
!
this
.
visible
:
!!
visible
;
const
change
=
visible
!==
this
.
isVisible
();
if
(
this
.
lazyInitOnToggle
&&
visible
)
{
this
.
lazyInitOnToggle
=
false
;
this
.
buildHeaderButtons
();
this
.
updateUI
();
}
// Flipping implementation derived from MenuSelectWidget
if
(
change
&&
visible
)
{
// Reset position before showing the popup again. It's possible we no longer need to flip
// (e.g. if the user scrolled).
this
.
setVerticalPosition
(
this
.
originalVerticalPosition
);
}
// Parent method
mw
.
widgets
.
CalendarWidget
.
super
.
prototype
.
toggle
.
call
(
this
,
visible
);
if
(
change
)
{
this
.
togglePositioning
(
visible
&&
!!
this
.
$floatableContainer
);
this
.
toggleClipping
(
visible
);
// Flipping implementation derived from MenuSelectWidget
if
(
(
this
.
isClippedVertically
()
||
this
.
isFloatableOutOfView
()
)
&&
this
.
originalVerticalPosition
!==
'center'
)
{
// If opening the menu in one direction causes it to be clipped, flip it
const
originalHeight
=
this
.
$element
.
height
();
this
.
setVerticalPosition
(
this
.
constructor
.
static
.
flippedPositions
[
this
.
originalVerticalPosition
]
);
if
(
this
.
isClippedVertically
()
||
this
.
isFloatableOutOfView
()
)
{
// If flipping also causes it to be clipped, open in whichever direction
// we have more space
const
flippedHeight
=
this
.
$element
.
height
();
if
(
originalHeight
>
flippedHeight
)
{
this
.
setVerticalPosition
(
this
.
originalVerticalPosition
);
}
}
}
// Note that we do not flip the menu's opening direction if the clipping changes
// later (e.g. after the user scrolls), that seems like it would be annoying
}
return
this
;
};
}()
);
File Metadata
Details
Attached
Mime Type
text/x-asm
Expires
Sat, May 16, 17:45 (9 h, 24 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
a5/1b/653a44295701c7b95a1a59f589a1
Default Alt Text
mw.widgets.CalendarWidget.js (21 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment