calendar.js 59 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562
  1. /*!
  2. * # Fomantic-UI - Calendar
  3. * http://github.com/fomantic/Fomantic-UI/
  4. *
  5. *
  6. * Released under the MIT license
  7. * http://opensource.org/licenses/MIT
  8. *
  9. */
  10. ;(function ($, window, document, undefined) {
  11. 'use strict';
  12. $.isFunction = $.isFunction || function(obj) {
  13. return typeof obj === "function" && typeof obj.nodeType !== "number";
  14. };
  15. window = (typeof window != 'undefined' && window.Math == Math)
  16. ? window
  17. : (typeof self != 'undefined' && self.Math == Math)
  18. ? self
  19. : Function('return this')()
  20. ;
  21. $.fn.calendar = function(parameters) {
  22. var
  23. $allModules = $(this),
  24. moduleSelector = $allModules.selector || '',
  25. time = new Date().getTime(),
  26. performance = [],
  27. query = arguments[0],
  28. methodInvoked = (typeof query == 'string'),
  29. queryArguments = [].slice.call(arguments, 1),
  30. returnedValue,
  31. timeGapTable = {
  32. '5': {'row': 4, 'column': 3 },
  33. '10': {'row': 3, 'column': 2 },
  34. '15': {'row': 2, 'column': 2 },
  35. '20': {'row': 3, 'column': 1 },
  36. '30': {'row': 2, 'column': 1 }
  37. }
  38. ;
  39. $allModules
  40. .each(function () {
  41. var
  42. settings = ( $.isPlainObject(parameters) )
  43. ? $.extend(true, {}, $.fn.calendar.settings, parameters)
  44. : $.extend({}, $.fn.calendar.settings),
  45. className = settings.className,
  46. namespace = settings.namespace,
  47. selector = settings.selector,
  48. formatter = settings.formatter,
  49. parser = settings.parser,
  50. metadata = settings.metadata,
  51. timeGap = timeGapTable[settings.minTimeGap],
  52. error = settings.error,
  53. eventNamespace = '.' + namespace,
  54. moduleNamespace = 'module-' + namespace,
  55. $module = $(this),
  56. $input = $module.find(selector.input),
  57. $container = $module.find(selector.popup),
  58. $activator = $module.find(selector.activator),
  59. element = this,
  60. instance = $module.data(moduleNamespace),
  61. isTouch,
  62. isTouchDown = false,
  63. focusDateUsedForRange = false,
  64. module
  65. ;
  66. module = {
  67. initialize: function () {
  68. module.debug('Initializing calendar for', element, $module);
  69. isTouch = module.get.isTouch();
  70. module.setup.config();
  71. module.setup.popup();
  72. module.setup.inline();
  73. module.setup.input();
  74. module.setup.date();
  75. module.create.calendar();
  76. module.bind.events();
  77. module.instantiate();
  78. },
  79. instantiate: function () {
  80. module.verbose('Storing instance of calendar');
  81. instance = module;
  82. $module.data(moduleNamespace, instance);
  83. },
  84. destroy: function () {
  85. module.verbose('Destroying previous calendar for', element);
  86. $module.removeData(moduleNamespace);
  87. module.unbind.events();
  88. },
  89. setup: {
  90. config: function () {
  91. if (module.get.minDate() !== null) {
  92. module.set.minDate($module.data(metadata.minDate));
  93. }
  94. if (module.get.maxDate() !== null) {
  95. module.set.maxDate($module.data(metadata.maxDate));
  96. }
  97. },
  98. popup: function () {
  99. if (settings.inline) {
  100. return;
  101. }
  102. if (!$activator.length) {
  103. $activator = $module.children().first();
  104. if (!$activator.length) {
  105. return;
  106. }
  107. }
  108. if ($.fn.popup === undefined) {
  109. module.error(error.popup);
  110. return;
  111. }
  112. if (!$container.length) {
  113. //prepend the popup element to the activator's parent so that it has less chance of messing with
  114. //the styling (eg input action button needs to be the last child to have correct border radius)
  115. var $activatorParent = $activator.parent(),
  116. domPositionFunction = $activatorParent.closest(selector.append).length !== 0 ? 'appendTo' : 'prependTo';
  117. $container = $('<div/>').addClass(className.popup)[domPositionFunction]($activatorParent);
  118. }
  119. $container.addClass(className.calendar);
  120. var onVisible = settings.onVisible;
  121. var onHidden = settings.onHidden;
  122. if (!$input.length) {
  123. //no input, $container has to handle focus/blur
  124. $container.attr('tabindex', '0');
  125. onVisible = function () {
  126. module.focus();
  127. return settings.onVisible.apply($container, arguments);
  128. };
  129. onHidden = function () {
  130. module.blur();
  131. return settings.onHidden.apply($container, arguments);
  132. };
  133. }
  134. var onShow = function () {
  135. //reset the focus date onShow
  136. module.set.focusDate(module.get.date());
  137. module.set.mode(settings.startMode);
  138. return settings.onShow.apply($container, arguments);
  139. };
  140. var on = settings.on || ($input.length ? 'focus' : 'click');
  141. var options = $.extend({}, settings.popupOptions, {
  142. popup: $container,
  143. on: on,
  144. hoverable: on === 'hover',
  145. onShow: onShow,
  146. onVisible: onVisible,
  147. onHide: settings.onHide,
  148. onHidden: onHidden
  149. });
  150. module.popup(options);
  151. },
  152. inline: function () {
  153. if ($activator.length && !settings.inline) {
  154. return;
  155. }
  156. $container = $('<div/>').addClass(className.calendar).appendTo($module);
  157. if (!$input.length) {
  158. $container.attr('tabindex', '0');
  159. }
  160. },
  161. input: function () {
  162. if (settings.touchReadonly && $input.length && isTouch) {
  163. $input.prop('readonly', true);
  164. }
  165. },
  166. date: function () {
  167. if (settings.initialDate) {
  168. var date = parser.date(settings.initialDate, settings);
  169. module.set.date(date, settings.formatInput, false);
  170. } else if ($module.data(metadata.date) !== undefined) {
  171. var date = parser.date($module.data(metadata.date), settings);
  172. module.set.date(date, settings.formatInput, false);
  173. } else if ($input.length) {
  174. var val = $input.val();
  175. var date = parser.date(val, settings);
  176. module.set.date(date, settings.formatInput, false);
  177. }
  178. }
  179. },
  180. create: {
  181. calendar: function () {
  182. var i, r, c, p, row, cell, pageGrid;
  183. var mode = module.get.mode();
  184. var today = new Date();
  185. var date = module.get.date();
  186. var focusDate = module.get.focusDate();
  187. var display = focusDate || date || settings.initialDate || today;
  188. display = module.helper.dateInRange(display);
  189. if (!focusDate) {
  190. focusDate = display;
  191. module.set.focusDate(focusDate, false, false);
  192. }
  193. var isYear = mode === 'year';
  194. var isMonth = mode === 'month';
  195. var isDay = mode === 'day';
  196. var isHour = mode === 'hour';
  197. var isMinute = mode === 'minute';
  198. var isTimeOnly = settings.type === 'time';
  199. var multiMonth = Math.max(settings.multiMonth, 1);
  200. var monthOffset = !isDay ? 0 : module.get.monthOffset();
  201. var minute = display.getMinutes();
  202. var hour = display.getHours();
  203. var day = display.getDate();
  204. var startMonth = display.getMonth() + monthOffset;
  205. var year = display.getFullYear();
  206. var columns = isDay ? settings.showWeekNumbers ? 8 : 7 : isHour ? 4 : timeGap['column'];
  207. var rows = isDay || isHour ? 6 : timeGap['row'];
  208. var pages = isDay ? multiMonth : 1;
  209. var container = $container;
  210. var tooltipPosition = container.hasClass("left") ? "right center" : "left center";
  211. container.empty();
  212. if (pages > 1) {
  213. pageGrid = $('<div/>').addClass(className.grid).appendTo(container);
  214. }
  215. for (p = 0; p < pages; p++) {
  216. if (pages > 1) {
  217. var pageColumn = $('<div/>').addClass(className.column).appendTo(pageGrid);
  218. container = pageColumn;
  219. }
  220. var month = startMonth + p;
  221. var firstMonthDayColumn = (new Date(year, month, 1).getDay() - settings.firstDayOfWeek % 7 + 7) % 7;
  222. if (!settings.constantHeight && isDay) {
  223. var requiredCells = new Date(year, month + 1, 0).getDate() + firstMonthDayColumn;
  224. rows = Math.ceil(requiredCells / 7);
  225. }
  226. var yearChange = isYear ? 10 : isMonth ? 1 : 0;
  227. var monthChange = isDay ? 1 : 0;
  228. var dayChange = isHour || isMinute ? 1 : 0;
  229. var prevNextDay = isHour || isMinute ? day : 1;
  230. var prevDate = new Date(year - yearChange, month - monthChange, prevNextDay - dayChange, hour);
  231. var nextDate = new Date(year + yearChange, month + monthChange, prevNextDay + dayChange, hour);
  232. var prevLast = isYear ? new Date(Math.ceil(year / 10) * 10 - 9, 0, 0) :
  233. isMonth ? new Date(year, 0, 0) : isDay ? new Date(year, month, 0) : new Date(year, month, day, -1);
  234. var nextFirst = isYear ? new Date(Math.ceil(year / 10) * 10 + 1, 0, 1) :
  235. isMonth ? new Date(year + 1, 0, 1) : isDay ? new Date(year, month + 1, 1) : new Date(year, month, day + 1);
  236. var tempMode = mode;
  237. if (isDay && settings.showWeekNumbers){
  238. tempMode += ' andweek';
  239. }
  240. var table = $('<table/>').addClass(className.table).addClass(tempMode).appendTo(container);
  241. var textColumns = columns;
  242. //no header for time-only mode
  243. if (!isTimeOnly) {
  244. var thead = $('<thead/>').appendTo(table);
  245. row = $('<tr/>').appendTo(thead);
  246. cell = $('<th/>').attr('colspan', '' + columns).appendTo(row);
  247. var headerDate = isYear || isMonth ? new Date(year, 0, 1) :
  248. isDay ? new Date(year, month, 1) : new Date(year, month, day, hour, minute);
  249. var headerText = $('<span/>').addClass(className.link).appendTo(cell);
  250. headerText.text(formatter.header(headerDate, mode, settings));
  251. var newMode = isMonth ? (settings.disableYear ? 'day' : 'year') :
  252. isDay ? (settings.disableMonth ? 'year' : 'month') : 'day';
  253. headerText.data(metadata.mode, newMode);
  254. if (p === 0) {
  255. var prev = $('<span/>').addClass(className.prev).appendTo(cell);
  256. prev.data(metadata.focusDate, prevDate);
  257. prev.toggleClass(className.disabledCell, !module.helper.isDateInRange(prevLast, mode));
  258. $('<i/>').addClass(className.prevIcon).appendTo(prev);
  259. }
  260. if (p === pages - 1) {
  261. var next = $('<span/>').addClass(className.next).appendTo(cell);
  262. next.data(metadata.focusDate, nextDate);
  263. next.toggleClass(className.disabledCell, !module.helper.isDateInRange(nextFirst, mode));
  264. $('<i/>').addClass(className.nextIcon).appendTo(next);
  265. }
  266. if (isDay) {
  267. row = $('<tr/>').appendTo(thead);
  268. if(settings.showWeekNumbers) {
  269. cell = $('<th/>').appendTo(row);
  270. cell.text(settings.text.weekNo);
  271. cell.addClass(className.weekCell);
  272. textColumns--;
  273. }
  274. for (i = 0; i < textColumns; i++) {
  275. cell = $('<th/>').appendTo(row);
  276. cell.text(formatter.dayColumnHeader((i + settings.firstDayOfWeek) % 7, settings));
  277. }
  278. }
  279. }
  280. var tbody = $('<tbody/>').appendTo(table);
  281. i = isYear ? Math.ceil(year / 10) * 10 - 9 : isDay ? 1 - firstMonthDayColumn : 0;
  282. for (r = 0; r < rows; r++) {
  283. row = $('<tr/>').appendTo(tbody);
  284. if(isDay && settings.showWeekNumbers){
  285. cell = $('<th/>').appendTo(row);
  286. cell.text(module.get.weekOfYear(year,month,i+1-settings.firstDayOfWeek));
  287. cell.addClass(className.weekCell);
  288. }
  289. for (c = 0; c < textColumns; c++, i++) {
  290. var cellDate = isYear ? new Date(i, month, 1, hour, minute) :
  291. isMonth ? new Date(year, i, 1, hour, minute) : isDay ? new Date(year, month, i, hour, minute) :
  292. isHour ? new Date(year, month, day, i) : new Date(year, month, day, hour, i * settings.minTimeGap);
  293. var cellText = isYear ? i :
  294. isMonth ? settings.text.monthsShort[i] : isDay ? cellDate.getDate() :
  295. formatter.time(cellDate, settings, true);
  296. cell = $('<td/>').addClass(className.cell).appendTo(row);
  297. cell.text(cellText);
  298. cell.data(metadata.date, cellDate);
  299. var adjacent = isDay && cellDate.getMonth() !== ((month + 12) % 12);
  300. var disabled = (!settings.selectAdjacentDays && adjacent) || !module.helper.isDateInRange(cellDate, mode) || settings.isDisabled(cellDate, mode) || module.helper.isDisabled(cellDate, mode) || !module.helper.isEnabled(cellDate, mode);
  301. if (disabled) {
  302. var disabledDate = module.helper.findDayAsObject(cellDate, mode, settings.disabledDates);
  303. if (disabledDate !== null && disabledDate[metadata.message]) {
  304. cell.attr("data-tooltip", disabledDate[metadata.message]);
  305. cell.attr("data-position", tooltipPosition);
  306. }
  307. } else {
  308. var eventDate = module.helper.findDayAsObject(cellDate, mode, settings.eventDates);
  309. if (eventDate !== null) {
  310. cell.addClass(eventDate[metadata.class] || settings.eventClass);
  311. if (eventDate[metadata.message]) {
  312. cell.attr("data-tooltip", eventDate[metadata.message]);
  313. cell.attr("data-position", tooltipPosition);
  314. }
  315. }
  316. }
  317. var active = module.helper.dateEqual(cellDate, date, mode);
  318. var isToday = module.helper.dateEqual(cellDate, today, mode);
  319. cell.toggleClass(className.adjacentCell, adjacent);
  320. cell.toggleClass(className.disabledCell, disabled);
  321. cell.toggleClass(className.activeCell, active && !adjacent);
  322. if (!isHour && !isMinute) {
  323. cell.toggleClass(className.todayCell, !adjacent && isToday);
  324. }
  325. // Allow for external modifications of each cell
  326. var cellOptions = {
  327. mode: mode,
  328. adjacent: adjacent,
  329. disabled: disabled,
  330. active: active,
  331. today: isToday
  332. };
  333. formatter.cell(cell, cellDate, cellOptions);
  334. if (module.helper.dateEqual(cellDate, focusDate, mode)) {
  335. //ensure that the focus date is exactly equal to the cell date
  336. //so that, if selected, the correct value is set
  337. module.set.focusDate(cellDate, false, false);
  338. }
  339. }
  340. }
  341. if (settings.today) {
  342. var todayRow = $('<tr/>').appendTo(tbody);
  343. var todayButton = $('<td/>').attr('colspan', '' + columns).addClass(className.today).appendTo(todayRow);
  344. todayButton.text(formatter.today(settings));
  345. todayButton.data(metadata.date, today);
  346. }
  347. module.update.focus(false, table);
  348. }
  349. }
  350. },
  351. update: {
  352. focus: function (updateRange, container) {
  353. container = container || $container;
  354. var mode = module.get.mode();
  355. var date = module.get.date();
  356. var focusDate = module.get.focusDate();
  357. var startDate = module.get.startDate();
  358. var endDate = module.get.endDate();
  359. var rangeDate = (updateRange ? focusDate : null) || date || (!isTouch ? focusDate : null);
  360. container.find('td').each(function () {
  361. var cell = $(this);
  362. var cellDate = cell.data(metadata.date);
  363. if (!cellDate) {
  364. return;
  365. }
  366. var disabled = cell.hasClass(className.disabledCell);
  367. var active = cell.hasClass(className.activeCell);
  368. var adjacent = cell.hasClass(className.adjacentCell);
  369. var focused = module.helper.dateEqual(cellDate, focusDate, mode);
  370. var inRange = !rangeDate ? false :
  371. ((!!startDate && module.helper.isDateInRange(cellDate, mode, startDate, rangeDate)) ||
  372. (!!endDate && module.helper.isDateInRange(cellDate, mode, rangeDate, endDate)));
  373. cell.toggleClass(className.focusCell, focused && (!isTouch || isTouchDown) && (!adjacent || (settings.selectAdjacentDays && adjacent)) && !disabled);
  374. cell.toggleClass(className.rangeCell, inRange && !active && !disabled);
  375. });
  376. }
  377. },
  378. refresh: function () {
  379. module.create.calendar();
  380. },
  381. bind: {
  382. events: function () {
  383. module.debug('Binding events');
  384. $container.on('mousedown' + eventNamespace, module.event.mousedown);
  385. $container.on('touchstart' + eventNamespace, module.event.mousedown);
  386. $container.on('mouseup' + eventNamespace, module.event.mouseup);
  387. $container.on('touchend' + eventNamespace, module.event.mouseup);
  388. $container.on('mouseover' + eventNamespace, module.event.mouseover);
  389. if ($input.length) {
  390. $input.on('input' + eventNamespace, module.event.inputChange);
  391. $input.on('focus' + eventNamespace, module.event.inputFocus);
  392. $input.on('blur' + eventNamespace, module.event.inputBlur);
  393. $input.on('click' + eventNamespace, module.event.inputClick);
  394. $input.on('keydown' + eventNamespace, module.event.keydown);
  395. } else {
  396. $container.on('keydown' + eventNamespace, module.event.keydown);
  397. }
  398. }
  399. },
  400. unbind: {
  401. events: function () {
  402. module.debug('Unbinding events');
  403. $container.off(eventNamespace);
  404. if ($input.length) {
  405. $input.off(eventNamespace);
  406. }
  407. }
  408. },
  409. event: {
  410. mouseover: function (event) {
  411. var target = $(event.target);
  412. var date = target.data(metadata.date);
  413. var mousedown = event.buttons === 1;
  414. if (date) {
  415. module.set.focusDate(date, false, true, mousedown);
  416. }
  417. },
  418. mousedown: function (event) {
  419. if ($input.length) {
  420. //prevent the mousedown on the calendar causing the input to lose focus
  421. event.preventDefault();
  422. }
  423. isTouchDown = event.type.indexOf('touch') >= 0;
  424. var target = $(event.target);
  425. var date = target.data(metadata.date);
  426. if (date) {
  427. module.set.focusDate(date, false, true, true);
  428. }
  429. },
  430. mouseup: function (event) {
  431. //ensure input has focus so that it receives keydown events for calendar navigation
  432. module.focus();
  433. event.preventDefault();
  434. event.stopPropagation();
  435. isTouchDown = false;
  436. var target = $(event.target);
  437. if (target.hasClass("disabled")) {
  438. return;
  439. }
  440. var parent = target.parent();
  441. if (parent.data(metadata.date) || parent.data(metadata.focusDate) || parent.data(metadata.mode)) {
  442. //clicked on a child element, switch to parent (used when clicking directly on prev/next <i> icon element)
  443. target = parent;
  444. }
  445. var date = target.data(metadata.date);
  446. var focusDate = target.data(metadata.focusDate);
  447. var mode = target.data(metadata.mode);
  448. if (date && settings.onSelect.call(element, date, module.get.mode()) !== false) {
  449. var forceSet = target.hasClass(className.today);
  450. module.selectDate(date, forceSet);
  451. }
  452. else if (focusDate) {
  453. module.set.focusDate(focusDate);
  454. }
  455. else if (mode) {
  456. module.set.mode(mode);
  457. }
  458. },
  459. keydown: function (event) {
  460. var keyCode = event.which;
  461. if (keyCode === 27 || keyCode === 9) {
  462. //esc || tab
  463. module.popup('hide');
  464. }
  465. if (module.popup('is visible')) {
  466. if (keyCode === 37 || keyCode === 38 || keyCode === 39 || keyCode === 40) {
  467. //arrow keys
  468. var mode = module.get.mode();
  469. var bigIncrement = mode === 'day' ? 7 : mode === 'hour' ? 4 : mode === 'minute' ? timeGap['column'] : 3;
  470. var increment = keyCode === 37 ? -1 : keyCode === 38 ? -bigIncrement : keyCode == 39 ? 1 : bigIncrement;
  471. increment *= mode === 'minute' ? settings.minTimeGap : 1;
  472. var focusDate = module.get.focusDate() || module.get.date() || new Date();
  473. var year = focusDate.getFullYear() + (mode === 'year' ? increment : 0);
  474. var month = focusDate.getMonth() + (mode === 'month' ? increment : 0);
  475. var day = focusDate.getDate() + (mode === 'day' ? increment : 0);
  476. var hour = focusDate.getHours() + (mode === 'hour' ? increment : 0);
  477. var minute = focusDate.getMinutes() + (mode === 'minute' ? increment : 0);
  478. var newFocusDate = new Date(year, month, day, hour, minute);
  479. if (settings.type === 'time') {
  480. newFocusDate = module.helper.mergeDateTime(focusDate, newFocusDate);
  481. }
  482. if (module.helper.isDateInRange(newFocusDate, mode)) {
  483. module.set.focusDate(newFocusDate);
  484. }
  485. } else if (keyCode === 13) {
  486. //enter
  487. var mode = module.get.mode();
  488. var date = module.get.focusDate();
  489. if (date && !settings.isDisabled(date, mode) && !module.helper.isDisabled(date, mode) && module.helper.isEnabled(date, mode)) {
  490. module.selectDate(date);
  491. }
  492. //disable form submission:
  493. event.preventDefault();
  494. event.stopPropagation();
  495. }
  496. }
  497. if (keyCode === 38 || keyCode === 40) {
  498. //arrow-up || arrow-down
  499. event.preventDefault(); //don't scroll
  500. module.popup('show');
  501. }
  502. },
  503. inputChange: function () {
  504. var val = $input.val();
  505. var date = parser.date(val, settings);
  506. module.set.date(date, false);
  507. },
  508. inputFocus: function () {
  509. $container.addClass(className.active);
  510. },
  511. inputBlur: function () {
  512. $container.removeClass(className.active);
  513. if (settings.formatInput) {
  514. var date = module.get.date();
  515. var text = formatter.datetime(date, settings);
  516. $input.val(text);
  517. }
  518. },
  519. inputClick: function () {
  520. module.popup('show');
  521. }
  522. },
  523. get: {
  524. weekOfYear: function(weekYear,weekMonth,weekDay) {
  525. // adapted from http://www.merlyn.demon.co.uk/weekcalc.htm
  526. var ms1d = 864e5, // milliseconds in a day
  527. ms7d = 7 * ms1d; // milliseconds in a week
  528. return function() { // return a closure so constants get calculated only once
  529. var DC3 = Date.UTC(weekYear, weekMonth, weekDay + 3) / ms1d, // an Absolute Day Number
  530. AWN = Math.floor(DC3 / 7), // an Absolute Week Number
  531. Wyr = new Date(AWN * ms7d).getUTCFullYear();
  532. return AWN - Math.floor(Date.UTC(Wyr, 0, 7) / ms7d) + 1;
  533. }();
  534. },
  535. date: function () {
  536. return module.helper.sanitiseDate($module.data(metadata.date)) || null;
  537. },
  538. inputDate: function() {
  539. return $input.val();
  540. },
  541. focusDate: function () {
  542. return $module.data(metadata.focusDate) || null;
  543. },
  544. startDate: function () {
  545. var startModule = module.get.calendarModule(settings.startCalendar);
  546. return (startModule ? startModule.get.date() : $module.data(metadata.startDate)) || null;
  547. },
  548. endDate: function () {
  549. var endModule = module.get.calendarModule(settings.endCalendar);
  550. return (endModule ? endModule.get.date() : $module.data(metadata.endDate)) || null;
  551. },
  552. minDate: function() {
  553. return $module.data(metadata.minDate) || null;
  554. },
  555. maxDate: function() {
  556. return $module.data(metadata.maxDate) || null;
  557. },
  558. monthOffset: function () {
  559. return $module.data(metadata.monthOffset) || 0;
  560. },
  561. mode: function () {
  562. //only returns valid modes for the current settings
  563. var mode = $module.data(metadata.mode) || settings.startMode;
  564. var validModes = module.get.validModes();
  565. if ($.inArray(mode, validModes) >= 0) {
  566. return mode;
  567. }
  568. return settings.type === 'time' ? 'hour' :
  569. settings.type === 'month' ? 'month' :
  570. settings.type === 'year' ? 'year' : 'day';
  571. },
  572. validModes: function () {
  573. var validModes = [];
  574. if (settings.type !== 'time') {
  575. if (!settings.disableYear || settings.type === 'year') {
  576. validModes.push('year');
  577. }
  578. if (!(settings.disableMonth || settings.type === 'year') || settings.type === 'month') {
  579. validModes.push('month');
  580. }
  581. if (settings.type.indexOf('date') >= 0) {
  582. validModes.push('day');
  583. }
  584. }
  585. if (settings.type.indexOf('time') >= 0) {
  586. validModes.push('hour');
  587. if (!settings.disableMinute) {
  588. validModes.push('minute');
  589. }
  590. }
  591. return validModes;
  592. },
  593. isTouch: function () {
  594. try {
  595. document.createEvent('TouchEvent');
  596. return true;
  597. }
  598. catch (e) {
  599. return false;
  600. }
  601. },
  602. calendarModule: function (selector) {
  603. if (!selector) {
  604. return null;
  605. }
  606. if (!(selector instanceof $)) {
  607. selector = $(selector).first();
  608. }
  609. //assume range related calendars are using the same namespace
  610. return selector.data(moduleNamespace);
  611. }
  612. },
  613. set: {
  614. date: function (date, updateInput, fireChange) {
  615. updateInput = updateInput !== false;
  616. fireChange = fireChange !== false;
  617. date = module.helper.sanitiseDate(date);
  618. date = module.helper.dateInRange(date);
  619. var mode = module.get.mode();
  620. var text = formatter.datetime(date, settings);
  621. if (fireChange && settings.onChange.call(element, date, text, mode) === false) {
  622. return false;
  623. }
  624. module.set.focusDate(date);
  625. if (settings.isDisabled(date, mode)) {
  626. return false;
  627. }
  628. var endDate = module.get.endDate();
  629. if (!!endDate && !!date && date > endDate) {
  630. //selected date is greater than end date in range, so clear end date
  631. module.set.endDate(undefined);
  632. }
  633. module.set.dataKeyValue(metadata.date, date);
  634. if (updateInput && $input.length) {
  635. $input.val(text);
  636. }
  637. },
  638. startDate: function (date, refreshCalendar) {
  639. date = module.helper.sanitiseDate(date);
  640. var startModule = module.get.calendarModule(settings.startCalendar);
  641. if (startModule) {
  642. startModule.set.date(date);
  643. }
  644. module.set.dataKeyValue(metadata.startDate, date, refreshCalendar);
  645. },
  646. endDate: function (date, refreshCalendar) {
  647. date = module.helper.sanitiseDate(date);
  648. var endModule = module.get.calendarModule(settings.endCalendar);
  649. if (endModule) {
  650. endModule.set.date(date);
  651. }
  652. module.set.dataKeyValue(metadata.endDate, date, refreshCalendar);
  653. },
  654. focusDate: function (date, refreshCalendar, updateFocus, updateRange) {
  655. date = module.helper.sanitiseDate(date);
  656. date = module.helper.dateInRange(date);
  657. var isDay = module.get.mode() === 'day';
  658. var oldFocusDate = module.get.focusDate();
  659. if (isDay && date && oldFocusDate) {
  660. var yearDelta = date.getFullYear() - oldFocusDate.getFullYear();
  661. var monthDelta = yearDelta * 12 + date.getMonth() - oldFocusDate.getMonth();
  662. if (monthDelta) {
  663. var monthOffset = module.get.monthOffset() - monthDelta;
  664. module.set.monthOffset(monthOffset, false);
  665. }
  666. }
  667. var changed = module.set.dataKeyValue(metadata.focusDate, date, refreshCalendar);
  668. updateFocus = (updateFocus !== false && changed && refreshCalendar === false) || focusDateUsedForRange != updateRange;
  669. focusDateUsedForRange = updateRange;
  670. if (updateFocus) {
  671. module.update.focus(updateRange);
  672. }
  673. },
  674. minDate: function (date) {
  675. date = module.helper.sanitiseDate(date);
  676. if (settings.maxDate !== null && settings.maxDate <= date) {
  677. module.verbose('Unable to set minDate variable bigger that maxDate variable', date, settings.maxDate);
  678. } else {
  679. module.setting('minDate', date);
  680. module.set.dataKeyValue(metadata.minDate, date);
  681. }
  682. },
  683. maxDate: function (date) {
  684. date = module.helper.sanitiseDate(date);
  685. if (settings.minDate !== null && settings.minDate >= date) {
  686. module.verbose('Unable to set maxDate variable lower that minDate variable', date, settings.minDate);
  687. } else {
  688. module.setting('maxDate', date);
  689. module.set.dataKeyValue(metadata.maxDate, date);
  690. }
  691. },
  692. monthOffset: function (monthOffset, refreshCalendar) {
  693. var multiMonth = Math.max(settings.multiMonth, 1);
  694. monthOffset = Math.max(1 - multiMonth, Math.min(0, monthOffset));
  695. module.set.dataKeyValue(metadata.monthOffset, monthOffset, refreshCalendar);
  696. },
  697. mode: function (mode, refreshCalendar) {
  698. module.set.dataKeyValue(metadata.mode, mode, refreshCalendar);
  699. },
  700. dataKeyValue: function (key, value, refreshCalendar) {
  701. var oldValue = $module.data(key);
  702. var equal = oldValue === value || (oldValue <= value && oldValue >= value); //equality test for dates and string objects
  703. if (value) {
  704. $module.data(key, value);
  705. } else {
  706. $module.removeData(key);
  707. }
  708. refreshCalendar = refreshCalendar !== false && !equal;
  709. if (refreshCalendar) {
  710. module.refresh();
  711. }
  712. return !equal;
  713. }
  714. },
  715. selectDate: function (date, forceSet) {
  716. module.verbose('New date selection', date);
  717. var mode = module.get.mode();
  718. var complete = forceSet || mode === 'minute' ||
  719. (settings.disableMinute && mode === 'hour') ||
  720. (settings.type === 'date' && mode === 'day') ||
  721. (settings.type === 'month' && mode === 'month') ||
  722. (settings.type === 'year' && mode === 'year');
  723. if (complete) {
  724. var canceled = module.set.date(date) === false;
  725. if (!canceled && settings.closable) {
  726. module.popup('hide');
  727. //if this is a range calendar, show the end date calendar popup and focus the input
  728. var endModule = module.get.calendarModule(settings.endCalendar);
  729. if (endModule) {
  730. endModule.popup('show');
  731. endModule.focus();
  732. }
  733. }
  734. } else {
  735. var newMode = mode === 'year' ? (!settings.disableMonth ? 'month' : 'day') :
  736. mode === 'month' ? 'day' : mode === 'day' ? 'hour' : 'minute';
  737. module.set.mode(newMode);
  738. if (mode === 'hour' || (mode === 'day' && module.get.date())) {
  739. //the user has chosen enough to consider a valid date/time has been chosen
  740. module.set.date(date);
  741. } else {
  742. module.set.focusDate(date);
  743. }
  744. }
  745. },
  746. changeDate: function (date) {
  747. module.set.date(date);
  748. },
  749. clear: function () {
  750. module.set.date(undefined);
  751. },
  752. popup: function () {
  753. return $activator.popup.apply($activator, arguments);
  754. },
  755. focus: function () {
  756. if ($input.length) {
  757. $input.focus();
  758. } else {
  759. $container.focus();
  760. }
  761. },
  762. blur: function () {
  763. if ($input.length) {
  764. $input.blur();
  765. } else {
  766. $container.blur();
  767. }
  768. },
  769. helper: {
  770. isDisabled: function(date, mode) {
  771. return mode === 'day' && ((settings.disabledDaysOfWeek.indexOf(date.getDay()) !== -1) || settings.disabledDates.some(function(d){
  772. if(typeof d === 'string') {
  773. d = module.helper.sanitiseDate(d);
  774. }
  775. if (d instanceof Date) {
  776. return module.helper.dateEqual(date, d, mode);
  777. }
  778. if (d !== null && typeof d === 'object' && d[metadata.date]) {
  779. return module.helper.dateEqual(date, module.helper.sanitiseDate(d[metadata.date]), mode);
  780. }
  781. }));
  782. },
  783. isEnabled: function(date, mode) {
  784. if (mode === 'day') {
  785. return settings.enabledDates.length === 0 || settings.enabledDates.some(function(d){
  786. if(typeof d === 'string') {
  787. d = module.helper.sanitiseDate(d);
  788. }
  789. if (d instanceof Date) {
  790. return module.helper.dateEqual(date, d, mode);
  791. }
  792. if (d !== null && typeof d === 'object' && d[metadata.date]) {
  793. return module.helper.dateEqual(date, module.helper.sanitiseDate(d[metadata.date]), mode);
  794. }
  795. });
  796. } else {
  797. return true;
  798. }
  799. },
  800. findDayAsObject: function(date, mode, dates) {
  801. if (mode === 'day') {
  802. var i = 0, il = dates.length;
  803. var d;
  804. for (; i < il; i++) {
  805. d = dates[i];
  806. if(typeof d === 'string') {
  807. d = module.helper.sanitiseDate(d);
  808. }
  809. if (d instanceof Date && module.helper.dateEqual(date, d, mode)) {
  810. var dateObject = {};
  811. dateObject[metadata.date] = d;
  812. return dateObject;
  813. }
  814. else if (d !== null && typeof d === 'object' && d[metadata.date] && module.helper.dateEqual(date,module.helper.sanitiseDate(d[metadata.date]), mode) ) {
  815. return d;
  816. }
  817. }
  818. }
  819. return null;
  820. },
  821. sanitiseDate: function (date) {
  822. if (!date) {
  823. return undefined;
  824. }
  825. if (!(date instanceof Date)) {
  826. date = parser.date('' + date, settings);
  827. }
  828. if (!date || date === null || isNaN(date.getTime())) {
  829. return undefined;
  830. }
  831. return date;
  832. },
  833. dateDiff: function (date1, date2, mode) {
  834. mode = mode || 'day';
  835. var isTimeOnly = settings.type === 'time';
  836. var isYear = mode === 'year';
  837. var isYearOrMonth = isYear || mode === 'month';
  838. var isMinute = mode === 'minute';
  839. var isHourOrMinute = isMinute || mode === 'hour';
  840. //only care about a minute accuracy of settings.minTimeGap
  841. date1 = new Date(
  842. isTimeOnly ? 2000 : date1.getFullYear(),
  843. isTimeOnly ? 0 : isYear ? 0 : date1.getMonth(),
  844. isTimeOnly ? 1 : isYearOrMonth ? 1 : date1.getDate(),
  845. !isHourOrMinute ? 0 : date1.getHours(),
  846. !isMinute ? 0 : settings.minTimeGap * Math.floor(date1.getMinutes() / settings.minTimeGap));
  847. date2 = new Date(
  848. isTimeOnly ? 2000 : date2.getFullYear(),
  849. isTimeOnly ? 0 : isYear ? 0 : date2.getMonth(),
  850. isTimeOnly ? 1 : isYearOrMonth ? 1 : date2.getDate(),
  851. !isHourOrMinute ? 0 : date2.getHours(),
  852. !isMinute ? 0 : settings.minTimeGap * Math.floor(date2.getMinutes() / settings.minTimeGap));
  853. return date2.getTime() - date1.getTime();
  854. },
  855. dateEqual: function (date1, date2, mode) {
  856. return !!date1 && !!date2 && module.helper.dateDiff(date1, date2, mode) === 0;
  857. },
  858. isDateInRange: function (date, mode, minDate, maxDate) {
  859. if (!minDate && !maxDate) {
  860. var startDate = module.get.startDate();
  861. minDate = startDate && settings.minDate ? new Date(Math.max(startDate, settings.minDate)) : startDate || settings.minDate;
  862. maxDate = settings.maxDate;
  863. }
  864. minDate = minDate && new Date(minDate.getFullYear(), minDate.getMonth(), minDate.getDate(), minDate.getHours(), settings.minTimeGap * Math.ceil(minDate.getMinutes() / settings.minTimeGap));
  865. return !(!date ||
  866. (minDate && module.helper.dateDiff(date, minDate, mode) > 0) ||
  867. (maxDate && module.helper.dateDiff(maxDate, date, mode) > 0));
  868. },
  869. dateInRange: function (date, minDate, maxDate) {
  870. if (!minDate && !maxDate) {
  871. var startDate = module.get.startDate();
  872. minDate = startDate && settings.minDate ? new Date(Math.max(startDate, settings.minDate)) : startDate || settings.minDate;
  873. maxDate = settings.maxDate;
  874. }
  875. minDate = minDate && new Date(minDate.getFullYear(), minDate.getMonth(), minDate.getDate(), minDate.getHours(), settings.minTimeGap * Math.ceil(minDate.getMinutes() / settings.minTimeGap));
  876. var isTimeOnly = settings.type === 'time';
  877. return !date ? date :
  878. (minDate && module.helper.dateDiff(date, minDate, 'minute') > 0) ?
  879. (isTimeOnly ? module.helper.mergeDateTime(date, minDate) : minDate) :
  880. (maxDate && module.helper.dateDiff(maxDate, date, 'minute') > 0) ?
  881. (isTimeOnly ? module.helper.mergeDateTime(date, maxDate) : maxDate) :
  882. date;
  883. },
  884. mergeDateTime: function (date, time) {
  885. return (!date || !time) ? time :
  886. new Date(date.getFullYear(), date.getMonth(), date.getDate(), time.getHours(), time.getMinutes());
  887. }
  888. },
  889. setting: function (name, value) {
  890. module.debug('Changing setting', name, value);
  891. if ($.isPlainObject(name)) {
  892. $.extend(true, settings, name);
  893. }
  894. else if (value !== undefined) {
  895. if ($.isPlainObject(settings[name])) {
  896. $.extend(true, settings[name], value);
  897. }
  898. else {
  899. settings[name] = value;
  900. }
  901. }
  902. else {
  903. return settings[name];
  904. }
  905. },
  906. internal: function (name, value) {
  907. if( $.isPlainObject(name) ) {
  908. $.extend(true, module, name);
  909. }
  910. else if(value !== undefined) {
  911. module[name] = value;
  912. }
  913. else {
  914. return module[name];
  915. }
  916. },
  917. debug: function () {
  918. if (!settings.silent && settings.debug) {
  919. if (settings.performance) {
  920. module.performance.log(arguments);
  921. }
  922. else {
  923. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  924. module.debug.apply(console, arguments);
  925. }
  926. }
  927. },
  928. verbose: function () {
  929. if (!settings.silent && settings.verbose && settings.debug) {
  930. if (settings.performance) {
  931. module.performance.log(arguments);
  932. }
  933. else {
  934. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  935. module.verbose.apply(console, arguments);
  936. }
  937. }
  938. },
  939. error: function () {
  940. if (!settings.silent) {
  941. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  942. module.error.apply(console, arguments);
  943. }
  944. },
  945. performance: {
  946. log: function (message) {
  947. var
  948. currentTime,
  949. executionTime,
  950. previousTime
  951. ;
  952. if (settings.performance) {
  953. currentTime = new Date().getTime();
  954. previousTime = time || currentTime;
  955. executionTime = currentTime - previousTime;
  956. time = currentTime;
  957. performance.push({
  958. 'Name': message[0],
  959. 'Arguments': [].slice.call(message, 1) || '',
  960. 'Element': element,
  961. 'Execution Time': executionTime
  962. });
  963. }
  964. clearTimeout(module.performance.timer);
  965. module.performance.timer = setTimeout(module.performance.display, 500);
  966. },
  967. display: function () {
  968. var
  969. title = settings.name + ':',
  970. totalTime = 0
  971. ;
  972. time = false;
  973. clearTimeout(module.performance.timer);
  974. $.each(performance, function (index, data) {
  975. totalTime += data['Execution Time'];
  976. });
  977. title += ' ' + totalTime + 'ms';
  978. if (moduleSelector) {
  979. title += ' \'' + moduleSelector + '\'';
  980. }
  981. if ((console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  982. console.groupCollapsed(title);
  983. if (console.table) {
  984. console.table(performance);
  985. }
  986. else {
  987. $.each(performance, function (index, data) {
  988. console.log(data['Name'] + ': ' + data['Execution Time'] + 'ms');
  989. });
  990. }
  991. console.groupEnd();
  992. }
  993. performance = [];
  994. }
  995. },
  996. invoke: function (query, passedArguments, context) {
  997. var
  998. object = instance,
  999. maxDepth,
  1000. found,
  1001. response
  1002. ;
  1003. passedArguments = passedArguments || queryArguments;
  1004. context = element || context;
  1005. if (typeof query == 'string' && object !== undefined) {
  1006. query = query.split(/[\. ]/);
  1007. maxDepth = query.length - 1;
  1008. $.each(query, function (depth, value) {
  1009. var camelCaseValue = (depth != maxDepth)
  1010. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  1011. : query
  1012. ;
  1013. if ($.isPlainObject(object[camelCaseValue]) && (depth != maxDepth)) {
  1014. object = object[camelCaseValue];
  1015. }
  1016. else if (object[camelCaseValue] !== undefined) {
  1017. found = object[camelCaseValue];
  1018. return false;
  1019. }
  1020. else if ($.isPlainObject(object[value]) && (depth != maxDepth)) {
  1021. object = object[value];
  1022. }
  1023. else if (object[value] !== undefined) {
  1024. found = object[value];
  1025. return false;
  1026. }
  1027. else {
  1028. module.error(error.method, query);
  1029. return false;
  1030. }
  1031. });
  1032. }
  1033. if ($.isFunction(found)) {
  1034. response = found.apply(context, passedArguments);
  1035. }
  1036. else if (found !== undefined) {
  1037. response = found;
  1038. }
  1039. if (Array.isArray(returnedValue)) {
  1040. returnedValue.push(response);
  1041. }
  1042. else if (returnedValue !== undefined) {
  1043. returnedValue = [returnedValue, response];
  1044. }
  1045. else if (response !== undefined) {
  1046. returnedValue = response;
  1047. }
  1048. return found;
  1049. }
  1050. };
  1051. if (methodInvoked) {
  1052. if (instance === undefined) {
  1053. module.initialize();
  1054. }
  1055. module.invoke(query);
  1056. }
  1057. else {
  1058. if (instance !== undefined) {
  1059. instance.invoke('destroy');
  1060. }
  1061. module.initialize();
  1062. }
  1063. })
  1064. ;
  1065. return (returnedValue !== undefined)
  1066. ? returnedValue
  1067. : this
  1068. ;
  1069. };
  1070. $.fn.calendar.settings = {
  1071. name : 'Calendar',
  1072. namespace : 'calendar',
  1073. silent: false,
  1074. debug: false,
  1075. verbose: false,
  1076. performance: false,
  1077. type : 'datetime', // picker type, can be 'datetime', 'date', 'time', 'month', or 'year'
  1078. firstDayOfWeek : 0, // day for first day column (0 = Sunday)
  1079. constantHeight : true, // add rows to shorter months to keep day calendar height consistent (6 rows)
  1080. today : false, // show a 'today/now' button at the bottom of the calendar
  1081. closable : true, // close the popup after selecting a date/time
  1082. monthFirst : true, // month before day when parsing/converting date from/to text
  1083. touchReadonly : true, // set input to readonly on touch devices
  1084. inline : false, // create the calendar inline instead of inside a popup
  1085. on : null, // when to show the popup (defaults to 'focus' for input, 'click' for others)
  1086. initialDate : null, // date to display initially when no date is selected (null = now)
  1087. startMode : false, // display mode to start in, can be 'year', 'month', 'day', 'hour', 'minute' (false = 'day')
  1088. minDate : null, // minimum date/time that can be selected, dates/times before are disabled
  1089. maxDate : null, // maximum date/time that can be selected, dates/times after are disabled
  1090. ampm : true, // show am/pm in time mode
  1091. disableYear : false, // disable year selection mode
  1092. disableMonth : false, // disable month selection mode
  1093. disableMinute : false, // disable minute selection mode
  1094. formatInput : true, // format the input text upon input blur and module creation
  1095. startCalendar : null, // jquery object or selector for another calendar that represents the start date of a date range
  1096. endCalendar : null, // jquery object or selector for another calendar that represents the end date of a date range
  1097. multiMonth : 1, // show multiple months when in 'day' mode
  1098. minTimeGap : 5,
  1099. showWeekNumbers : null, // show Number of Week at the very first column of a dayView
  1100. disabledDates : [], // specific day(s) which won't be selectable and contain additional information.
  1101. disabledDaysOfWeek : [], // day(s) which won't be selectable(s) (0 = Sunday)
  1102. enabledDates : [], // specific day(s) which will be selectable, all other days will be disabled
  1103. eventDates : [], // specific day(s) which will be shown in a different color and using tooltips
  1104. centuryBreak : 60, // starting short year until 99 where it will be assumed to belong to the last century
  1105. currentCentury : 2000, // century to be added to 2-digit years (00 to {centuryBreak}-1)
  1106. selectAdjacentDays : false, // The calendar can show dates from adjacent month. These adjacent month dates can also be made selectable.
  1107. // popup options ('popup', 'on', 'hoverable', and show/hide callbacks are overridden)
  1108. popupOptions: {
  1109. position: 'bottom left',
  1110. lastResort: 'bottom left',
  1111. prefer: 'opposite',
  1112. hideOnScroll: false
  1113. },
  1114. text: {
  1115. days: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],
  1116. months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
  1117. monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
  1118. today: 'Today',
  1119. now: 'Now',
  1120. am: 'AM',
  1121. pm: 'PM',
  1122. weekNo: 'Week'
  1123. },
  1124. formatter: {
  1125. header: function (date, mode, settings) {
  1126. return mode === 'year' ? settings.formatter.yearHeader(date, settings) :
  1127. mode === 'month' ? settings.formatter.monthHeader(date, settings) :
  1128. mode === 'day' ? settings.formatter.dayHeader(date, settings) :
  1129. mode === 'hour' ? settings.formatter.hourHeader(date, settings) :
  1130. settings.formatter.minuteHeader(date, settings);
  1131. },
  1132. yearHeader: function (date, settings) {
  1133. var decadeYear = Math.ceil(date.getFullYear() / 10) * 10;
  1134. return (decadeYear - 9) + ' - ' + (decadeYear + 2);
  1135. },
  1136. monthHeader: function (date, settings) {
  1137. return date.getFullYear();
  1138. },
  1139. dayHeader: function (date, settings) {
  1140. var month = settings.text.months[date.getMonth()];
  1141. var year = date.getFullYear();
  1142. return month + ' ' + year;
  1143. },
  1144. hourHeader: function (date, settings) {
  1145. return settings.formatter.date(date, settings);
  1146. },
  1147. minuteHeader: function (date, settings) {
  1148. return settings.formatter.date(date, settings);
  1149. },
  1150. dayColumnHeader: function (day, settings) {
  1151. return settings.text.days[day];
  1152. },
  1153. datetime: function (date, settings) {
  1154. if (!date) {
  1155. return '';
  1156. }
  1157. var day = settings.type === 'time' ? '' : settings.formatter.date(date, settings);
  1158. var time = settings.type.indexOf('time') < 0 ? '' : settings.formatter.time(date, settings, false);
  1159. var separator = settings.type === 'datetime' ? ' ' : '';
  1160. return day + separator + time;
  1161. },
  1162. date: function (date, settings) {
  1163. if (!date) {
  1164. return '';
  1165. }
  1166. var day = date.getDate();
  1167. var month = settings.text.months[date.getMonth()];
  1168. var year = date.getFullYear();
  1169. return settings.type === 'year' ? year :
  1170. settings.type === 'month' ? month + ' ' + year :
  1171. (settings.monthFirst ? month + ' ' + day : day + ' ' + month) + ', ' + year;
  1172. },
  1173. time: function (date, settings, forCalendar) {
  1174. if (!date) {
  1175. return '';
  1176. }
  1177. var hour = date.getHours();
  1178. var minute = date.getMinutes();
  1179. var ampm = '';
  1180. if (settings.ampm) {
  1181. ampm = ' ' + (hour < 12 ? settings.text.am : settings.text.pm);
  1182. hour = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour;
  1183. }
  1184. return hour + ':' + (minute < 10 ? '0' : '') + minute + ampm;
  1185. },
  1186. today: function (settings) {
  1187. return settings.type === 'date' ? settings.text.today : settings.text.now;
  1188. },
  1189. cell: function (cell, date, cellOptions) {
  1190. }
  1191. },
  1192. parser: {
  1193. date: function (text, settings) {
  1194. if (text instanceof Date) {
  1195. return text;
  1196. }
  1197. if (!text) {
  1198. return null;
  1199. }
  1200. text = ('' + text).trim().toLowerCase();
  1201. if (text.length === 0) {
  1202. return null;
  1203. }
  1204. var textDate = new Date(text);
  1205. if(!isNaN(textDate.getDate())) {
  1206. return textDate;
  1207. }
  1208. var i, j, k;
  1209. var minute = -1, hour = -1, day = -1, month = -1, year = -1;
  1210. var isAm = undefined;
  1211. var isTimeOnly = settings.type === 'time';
  1212. var isDateOnly = settings.type.indexOf('time') < 0;
  1213. var words = text.split(settings.regExp.dateWords);
  1214. var numbers = text.split(settings.regExp.dateNumbers);
  1215. if (!isDateOnly) {
  1216. //am/pm
  1217. isAm = $.inArray(settings.text.am.toLowerCase(), words) >= 0 ? true :
  1218. $.inArray(settings.text.pm.toLowerCase(), words) >= 0 ? false : undefined;
  1219. //time with ':'
  1220. for (i = 0; i < numbers.length; i++) {
  1221. var number = numbers[i];
  1222. if (number.indexOf(':') >= 0) {
  1223. if (hour < 0 || minute < 0) {
  1224. var parts = number.split(':');
  1225. for (k = 0; k < Math.min(2, parts.length); k++) {
  1226. j = parseInt(parts[k]);
  1227. if (isNaN(j)) {
  1228. j = 0;
  1229. }
  1230. if (k === 0) {
  1231. hour = j % 24;
  1232. } else {
  1233. minute = j % 60;
  1234. }
  1235. }
  1236. }
  1237. numbers.splice(i, 1);
  1238. }
  1239. }
  1240. }
  1241. if (!isTimeOnly) {
  1242. //textual month
  1243. for (i = 0; i < words.length; i++) {
  1244. var word = words[i];
  1245. if (word.length <= 0) {
  1246. continue;
  1247. }
  1248. word = word.substring(0, Math.min(word.length, 3));
  1249. for (j = 0; j < settings.text.months.length; j++) {
  1250. var monthString = settings.text.months[j];
  1251. monthString = monthString.substring(0, Math.min(word.length, Math.min(monthString.length, 3))).toLowerCase();
  1252. if (monthString === word) {
  1253. month = j + 1;
  1254. break;
  1255. }
  1256. }
  1257. if (month >= 0) {
  1258. break;
  1259. }
  1260. }
  1261. //year > settings.centuryBreak
  1262. for (i = 0; i < numbers.length; i++) {
  1263. j = parseInt(numbers[i]);
  1264. if (isNaN(j)) {
  1265. continue;
  1266. }
  1267. if (j >= settings.centuryBreak && i === numbers.length-1) {
  1268. if (j <= 99) {
  1269. j += settings.currentCentury - 100;
  1270. }
  1271. year = j;
  1272. numbers.splice(i, 1);
  1273. break;
  1274. }
  1275. }
  1276. //numeric month
  1277. if (month < 0) {
  1278. for (i = 0; i < numbers.length; i++) {
  1279. k = i > 1 || settings.monthFirst ? i : i === 1 ? 0 : 1;
  1280. j = parseInt(numbers[k]);
  1281. if (isNaN(j)) {
  1282. continue;
  1283. }
  1284. if (1 <= j && j <= 12) {
  1285. month = j;
  1286. numbers.splice(k, 1);
  1287. break;
  1288. }
  1289. }
  1290. }
  1291. //day
  1292. for (i = 0; i < numbers.length; i++) {
  1293. j = parseInt(numbers[i]);
  1294. if (isNaN(j)) {
  1295. continue;
  1296. }
  1297. if (1 <= j && j <= 31) {
  1298. day = j;
  1299. numbers.splice(i, 1);
  1300. break;
  1301. }
  1302. }
  1303. //year <= settings.centuryBreak
  1304. if (year < 0) {
  1305. for (i = numbers.length - 1; i >= 0; i--) {
  1306. j = parseInt(numbers[i]);
  1307. if (isNaN(j)) {
  1308. continue;
  1309. }
  1310. if (j <= 99) {
  1311. j += settings.currentCentury;
  1312. }
  1313. year = j;
  1314. numbers.splice(i, 1);
  1315. break;
  1316. }
  1317. }
  1318. }
  1319. if (!isDateOnly) {
  1320. //hour
  1321. if (hour < 0) {
  1322. for (i = 0; i < numbers.length; i++) {
  1323. j = parseInt(numbers[i]);
  1324. if (isNaN(j)) {
  1325. continue;
  1326. }
  1327. if (0 <= j && j <= 23) {
  1328. hour = j;
  1329. numbers.splice(i, 1);
  1330. break;
  1331. }
  1332. }
  1333. }
  1334. //minute
  1335. if (minute < 0) {
  1336. for (i = 0; i < numbers.length; i++) {
  1337. j = parseInt(numbers[i]);
  1338. if (isNaN(j)) {
  1339. continue;
  1340. }
  1341. if (0 <= j && j <= 59) {
  1342. minute = j;
  1343. numbers.splice(i, 1);
  1344. break;
  1345. }
  1346. }
  1347. }
  1348. }
  1349. if (minute < 0 && hour < 0 && day < 0 && month < 0 && year < 0) {
  1350. return null;
  1351. }
  1352. if (minute < 0) {
  1353. minute = 0;
  1354. }
  1355. if (hour < 0) {
  1356. hour = 0;
  1357. }
  1358. if (day < 0) {
  1359. day = 1;
  1360. }
  1361. if (month < 0) {
  1362. month = 1;
  1363. }
  1364. if (year < 0) {
  1365. year = new Date().getFullYear();
  1366. }
  1367. if (isAm !== undefined) {
  1368. if (isAm) {
  1369. if (hour === 12) {
  1370. hour = 0;
  1371. }
  1372. } else if (hour < 12) {
  1373. hour += 12;
  1374. }
  1375. }
  1376. var date = new Date(year, month - 1, day, hour, minute);
  1377. if (date.getMonth() !== month - 1 || date.getFullYear() !== year) {
  1378. //month or year don't match up, switch to last day of the month
  1379. date = new Date(year, month, 0, hour, minute);
  1380. }
  1381. return isNaN(date.getTime()) ? null : date;
  1382. }
  1383. },
  1384. // callback when date changes, return false to cancel the change
  1385. onChange: function (date, text, mode) {
  1386. return true;
  1387. },
  1388. // callback before show animation, return false to prevent show
  1389. onShow: function () {
  1390. },
  1391. // callback after show animation
  1392. onVisible: function () {
  1393. },
  1394. // callback before hide animation, return false to prevent hide
  1395. onHide: function () {
  1396. },
  1397. // callback after hide animation
  1398. onHidden: function () {
  1399. },
  1400. // callback before item is selected, return false to prevent selection
  1401. onSelect: function (date, mode) {
  1402. },
  1403. // is the given date disabled?
  1404. isDisabled: function (date, mode) {
  1405. return false;
  1406. },
  1407. selector: {
  1408. popup: '.ui.popup',
  1409. input: 'input',
  1410. activator: 'input',
  1411. append: '.inline.field,.inline.fields'
  1412. },
  1413. regExp: {
  1414. dateWords: /[^A-Za-z\u00C0-\u024F]+/g,
  1415. dateNumbers: /[^\d:]+/g
  1416. },
  1417. error: {
  1418. popup: 'UI Popup, a required component is not included in this page',
  1419. method: 'The method you called is not defined.'
  1420. },
  1421. className: {
  1422. calendar: 'calendar',
  1423. active: 'active',
  1424. popup: 'ui popup',
  1425. grid: 'ui equal width grid',
  1426. column: 'column',
  1427. table: 'ui celled center aligned unstackable table',
  1428. prev: 'prev link',
  1429. next: 'next link',
  1430. prevIcon: 'chevron left icon',
  1431. nextIcon: 'chevron right icon',
  1432. link: 'link',
  1433. cell: 'link',
  1434. disabledCell: 'disabled',
  1435. weekCell: 'disabled',
  1436. adjacentCell: 'adjacent',
  1437. activeCell: 'active',
  1438. rangeCell: 'range',
  1439. focusCell: 'focus',
  1440. todayCell: 'today',
  1441. today: 'today link'
  1442. },
  1443. metadata: {
  1444. date: 'date',
  1445. focusDate: 'focusDate',
  1446. startDate: 'startDate',
  1447. endDate: 'endDate',
  1448. minDate: 'minDate',
  1449. maxDate: 'maxDate',
  1450. mode: 'mode',
  1451. monthOffset: 'monthOffset',
  1452. message: 'message',
  1453. class: 'class'
  1454. },
  1455. eventClass: 'blue'
  1456. };
  1457. })(jQuery, window, document);