processOptions.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. import {hasProperty, pushUnique} from '../lib/utils.js';
  2. import {dateValue} from '../lib/date.js';
  3. import {reFormatTokens, parseDate} from '../lib/date-format.js';
  4. import {parseHTML} from '../lib/dom.js';
  5. import defaultOptions from './defaultOptions.js';
  6. const {
  7. language: defaultLang,
  8. format: defaultFormat,
  9. weekStart: defaultWeekStart,
  10. } = defaultOptions;
  11. // Reducer function to filter out invalid day-of-week from the input
  12. function sanitizeDOW(dow, day) {
  13. return dow.length < 6 && day >= 0 && day < 7
  14. ? pushUnique(dow, day)
  15. : dow;
  16. }
  17. function calcEndOfWeek(startOfWeek) {
  18. return (startOfWeek + 6) % 7;
  19. }
  20. // validate input date. if invalid, fallback to the original value
  21. function validateDate(value, format, locale, origValue) {
  22. const date = parseDate(value, format, locale);
  23. return date !== undefined ? date : origValue;
  24. }
  25. // Validate viewId. if invalid, fallback to the original value
  26. function validateViewId(value, origValue, max = 3) {
  27. const viewId = parseInt(value, 10);
  28. return viewId >= 0 && viewId <= max ? viewId : origValue;
  29. }
  30. // Create Datepicker configuration to set
  31. export default function processOptions(options, datepicker) {
  32. const inOpts = Object.assign({}, options);
  33. const config = {};
  34. const locales = datepicker.constructor.locales;
  35. let {
  36. format,
  37. language,
  38. locale,
  39. maxDate,
  40. maxView,
  41. minDate,
  42. pickLevel,
  43. startView,
  44. weekStart,
  45. } = datepicker.config || {};
  46. if (inOpts.language) {
  47. let lang;
  48. if (inOpts.language !== language) {
  49. if (locales[inOpts.language]) {
  50. lang = inOpts.language;
  51. } else {
  52. // Check if langauge + region tag can fallback to the one without
  53. // region (e.g. fr-CA → fr)
  54. lang = inOpts.language.split('-')[0];
  55. if (locales[lang] === undefined) {
  56. lang = false;
  57. }
  58. }
  59. }
  60. delete inOpts.language;
  61. if (lang) {
  62. language = config.language = lang;
  63. // update locale as well when updating language
  64. const origLocale = locale || locales[defaultLang];
  65. // use default language's properties for the fallback
  66. locale = Object.assign({
  67. format: defaultFormat,
  68. weekStart: defaultWeekStart
  69. }, locales[defaultLang]);
  70. if (language !== defaultLang) {
  71. Object.assign(locale, locales[language]);
  72. }
  73. config.locale = locale;
  74. // if format and/or weekStart are the same as old locale's defaults,
  75. // update them to new locale's defaults
  76. if (format === origLocale.format) {
  77. format = config.format = locale.format;
  78. }
  79. if (weekStart === origLocale.weekStart) {
  80. weekStart = config.weekStart = locale.weekStart;
  81. config.weekEnd = calcEndOfWeek(locale.weekStart);
  82. }
  83. }
  84. }
  85. if (inOpts.format) {
  86. const hasToDisplay = typeof inOpts.format.toDisplay === 'function';
  87. const hasToValue = typeof inOpts.format.toValue === 'function';
  88. const validFormatString = reFormatTokens.test(inOpts.format);
  89. if ((hasToDisplay && hasToValue) || validFormatString) {
  90. format = config.format = inOpts.format;
  91. }
  92. delete inOpts.format;
  93. }
  94. //*** dates ***//
  95. // while min and maxDate for "no limit" in the options are better to be null
  96. // (especially when updating), the ones in the config have to be undefined
  97. // because null is treated as 0 (= unix epoch) when comparing with time value
  98. let minDt = minDate;
  99. let maxDt = maxDate;
  100. if (inOpts.minDate !== undefined) {
  101. minDt = inOpts.minDate === null
  102. ? dateValue(0, 0, 1) // set 0000-01-01 to prevent negative values for year
  103. : validateDate(inOpts.minDate, format, locale, minDt);
  104. delete inOpts.minDate;
  105. }
  106. if (inOpts.maxDate !== undefined) {
  107. maxDt = inOpts.maxDate === null
  108. ? undefined
  109. : validateDate(inOpts.maxDate, format, locale, maxDt);
  110. delete inOpts.maxDate;
  111. }
  112. if (maxDt < minDt) {
  113. minDate = config.minDate = maxDt;
  114. maxDate = config.maxDate = minDt;
  115. } else {
  116. if (minDate !== minDt) {
  117. minDate = config.minDate = minDt;
  118. }
  119. if (maxDate !== maxDt) {
  120. maxDate = config.maxDate = maxDt;
  121. }
  122. }
  123. if (inOpts.datesDisabled) {
  124. config.datesDisabled = inOpts.datesDisabled.reduce((dates, dt) => {
  125. const date = parseDate(dt, format, locale);
  126. return date !== undefined ? pushUnique(dates, date) : dates;
  127. }, []);
  128. delete inOpts.datesDisabled;
  129. }
  130. if (inOpts.defaultViewDate !== undefined) {
  131. const viewDate = parseDate(inOpts.defaultViewDate, format, locale);
  132. if (viewDate !== undefined) {
  133. config.defaultViewDate = viewDate;
  134. }
  135. delete inOpts.defaultViewDate;
  136. }
  137. //*** days of week ***//
  138. if (inOpts.weekStart !== undefined) {
  139. const wkStart = Number(inOpts.weekStart) % 7;
  140. if (!isNaN(wkStart)) {
  141. weekStart = config.weekStart = wkStart;
  142. config.weekEnd = calcEndOfWeek(wkStart);
  143. }
  144. delete inOpts.weekStart;
  145. }
  146. if (inOpts.daysOfWeekDisabled) {
  147. config.daysOfWeekDisabled = inOpts.daysOfWeekDisabled.reduce(sanitizeDOW, []);
  148. delete inOpts.daysOfWeekDisabled;
  149. }
  150. if (inOpts.daysOfWeekHighlighted) {
  151. config.daysOfWeekHighlighted = inOpts.daysOfWeekHighlighted.reduce(sanitizeDOW, []);
  152. delete inOpts.daysOfWeekHighlighted;
  153. }
  154. //*** multi date ***//
  155. if (inOpts.maxNumberOfDates !== undefined) {
  156. const maxNumberOfDates = parseInt(inOpts.maxNumberOfDates, 10);
  157. if (maxNumberOfDates >= 0) {
  158. config.maxNumberOfDates = maxNumberOfDates;
  159. config.multidate = maxNumberOfDates !== 1;
  160. }
  161. delete inOpts.maxNumberOfDates;
  162. }
  163. if (inOpts.dateDelimiter) {
  164. config.dateDelimiter = String(inOpts.dateDelimiter);
  165. delete inOpts.dateDelimiter;
  166. }
  167. //*** pick level & view ***//
  168. let newPickLevel = pickLevel;
  169. if (inOpts.pickLevel !== undefined) {
  170. newPickLevel = validateViewId(inOpts.pickLevel, 2);
  171. delete inOpts.pickLevel;
  172. }
  173. if (newPickLevel !== pickLevel) {
  174. pickLevel = config.pickLevel = newPickLevel;
  175. }
  176. let newMaxView = maxView;
  177. if (inOpts.maxView !== undefined) {
  178. newMaxView = validateViewId(inOpts.maxView, maxView);
  179. delete inOpts.maxView;
  180. }
  181. // ensure max view >= pick level
  182. newMaxView = pickLevel > newMaxView ? pickLevel : newMaxView;
  183. if (newMaxView !== maxView) {
  184. maxView = config.maxView = newMaxView;
  185. }
  186. let newStartView = startView;
  187. if (inOpts.startView !== undefined) {
  188. newStartView = validateViewId(inOpts.startView, newStartView);
  189. delete inOpts.startView;
  190. }
  191. // ensure pick level <= start view <= max view
  192. if (newStartView < pickLevel) {
  193. newStartView = pickLevel;
  194. } else if (newStartView > maxView) {
  195. newStartView = maxView;
  196. }
  197. if (newStartView !== startView) {
  198. config.startView = newStartView;
  199. }
  200. //*** template ***//
  201. if (inOpts.prevArrow) {
  202. const prevArrow = parseHTML(inOpts.prevArrow);
  203. if (prevArrow.childNodes.length > 0) {
  204. config.prevArrow = prevArrow.childNodes;
  205. }
  206. delete inOpts.prevArrow;
  207. }
  208. if (inOpts.nextArrow) {
  209. const nextArrow = parseHTML(inOpts.nextArrow);
  210. if (nextArrow.childNodes.length > 0) {
  211. config.nextArrow = nextArrow.childNodes;
  212. }
  213. delete inOpts.nextArrow;
  214. }
  215. //*** misc ***//
  216. if (inOpts.disableTouchKeyboard !== undefined) {
  217. config.disableTouchKeyboard = 'ontouchstart' in document && !!inOpts.disableTouchKeyboard;
  218. delete inOpts.disableTouchKeyboard;
  219. }
  220. if (inOpts.orientation) {
  221. const orientation = inOpts.orientation.toLowerCase().split(/\s+/g);
  222. config.orientation = {
  223. x: orientation.find(x => (x === 'left' || x === 'right')) || 'auto',
  224. y: orientation.find(y => (y === 'top' || y === 'bottom')) || 'auto',
  225. };
  226. delete inOpts.orientation;
  227. }
  228. if (inOpts.todayBtnMode !== undefined) {
  229. switch(inOpts.todayBtnMode) {
  230. case 0:
  231. case 1:
  232. config.todayBtnMode = inOpts.todayBtnMode;
  233. }
  234. delete inOpts.todayBtnMode;
  235. }
  236. //*** copy the rest ***//
  237. Object.keys(inOpts).forEach((key) => {
  238. if (inOpts[key] !== undefined && hasProperty(defaultOptions, key)) {
  239. config[key] = inOpts[key];
  240. }
  241. });
  242. return config;
  243. }