├── .babelrc
├── .circleci
└── config.yml
├── .gitignore
├── LICENSE
├── README.md
├── gulpfile.js
├── locales
├── js-year-calendar.ca.js
├── js-year-calendar.cs.js
├── js-year-calendar.de.js
├── js-year-calendar.es.js
├── js-year-calendar.eu.js
├── js-year-calendar.fi.js
├── js-year-calendar.fr.js
├── js-year-calendar.hr.js
├── js-year-calendar.hu.js
├── js-year-calendar.id.js
├── js-year-calendar.is.js
├── js-year-calendar.it.js
├── js-year-calendar.ja.js
├── js-year-calendar.ko.js
├── js-year-calendar.lt.js
├── js-year-calendar.lv.js
├── js-year-calendar.ms.js
├── js-year-calendar.nl.js
├── js-year-calendar.pl.js
├── js-year-calendar.pt.js
├── js-year-calendar.ro.js
├── js-year-calendar.ru.js
├── js-year-calendar.sk.js
├── js-year-calendar.sr.js
├── js-year-calendar.th.js
├── js-year-calendar.tr.js
├── js-year-calendar.ua.js
├── js-year-calendar.uk.js
├── js-year-calendar.uz.js
├── js-year-calendar.zh-CN.js
└── js-year-calendar.zh-TW.js
├── package-lock.json
├── package.json
├── src
├── less
│ └── js-year-calendar.less
└── ts
│ ├── interfaces
│ ├── CalendarContextMenuItem.ts
│ ├── CalendarDataSourceElement.ts
│ ├── CalendarDayEventObject.ts
│ ├── CalendarOptions.ts
│ ├── CalendarPeriodChangedEventObject.ts
│ ├── CalendarRangeEventObject.ts
│ ├── CalendarRenderEndEventObject.ts
│ └── CalendarYearChangedEventObject.ts
│ └── js-year-calendar.ts
├── tests
├── actions.js
├── auto-init.js
├── events.js
├── initialization.js
├── locales.js
└── methods.js
└── tsconfig.json
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-typescript",
4 | "@babel/preset-env"
5 | ],
6 | "plugins": [
7 | "@babel/plugin-proposal-class-properties",
8 | "@babel/plugin-transform-modules-umd"
9 | ]
10 | }
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 | orbs:
3 | codecov: codecov/codecov@1.0.4
4 | jobs:
5 | test:
6 | docker:
7 | - image: circleci/node:10
8 | steps:
9 | - checkout
10 | - run: 'npm install'
11 | - run: 'npm run build'
12 | - run: 'npm run test'
13 | - codecov/upload:
14 | file: coverage/coverage-final.json
15 | workflows:
16 | version: 2.1
17 | build_and_test:
18 | jobs:
19 | - test
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | coverage
4 | docs
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # js-year-calendar
2 | A fully customizable year calendar widget
3 |
4 | 
5 |
6 | [](https://circleci.com/gh/year-calendar/js-year-calendar/tree/master)
7 | [](https://codecov.io/gh/year-calendar/js-year-calendar)
8 | [](https://www.npmjs.com/package/js-year-calendar)
9 |
10 | This library is also available for:
11 |
12 | [](https://github.com/year-calendar/rc-year-calendar)
13 | [](https://github.com/year-calendar/v-year-calendar)
14 |
15 | ## Requirements
16 |
17 | This plugin uses pure javascript. No library is required.
18 |
19 | ## Installation
20 |
21 | You can get the widget using the following methods:
22 | - From the [GitHub repository](https://github.com/year-calendar/js-year-calendar/releases)
23 | - From the Node package manager, using the following command: `npm install js-year-calendar`
24 | - From Yarn, using the following command: `yarn add js-year-calendar`
25 | - From the CDN, by adding the following script directly in your HTML page:
26 |
27 | ``
28 |
29 | AND
30 |
31 | ``
32 |
33 | ## Initialization
34 |
35 | If you're using javascript modules, don't forget to import the library:
36 |
37 | ```
38 | import Calendar from 'js-year-calendar';
39 | import 'js-year-calendar/dist/js-year-calendar.css';
40 | ```
41 |
42 | ## Usage
43 |
44 | You can create a calendar using the following javascript code :
45 | ```
46 | new Calendar('.calendar')
47 | ```
48 |
49 | Or
50 |
51 | ```
52 | new Calendar(document.querySelector('.calendar'));
53 | ```
54 |
55 | Where `.calendar` is the selector of a `DIV` element that should contain your calendar.
56 |
57 | You can also use the following HTML if you don't want to use javascript to initialize the calendar
58 | ```
59 |
60 | ```
61 | The calendar will be automatically created when the page will finish loading
62 |
63 | ## Using options
64 |
65 | You can specify options to customize the calendar:
66 | ```
67 | new Calendar('.calendar', {
68 | style: 'background',
69 | minDate: new Date()
70 | })
71 | ```
72 |
73 | You can find the exhaustive list of options in the [documentation](https://year-calendar.github.io/js-year-calendar/documentation).
74 |
75 | ## Language
76 |
77 | If you want to use the calendar in a different language, you should import the locale file corresponding to the language you want to use, and then set the `language` prop of the calendar:
78 |
79 | ```
80 | import Calendar from 'js-year-calendar';
81 | import 'js-year-calendar/locales/js-year-calendar.fr';
82 | ```
83 |
84 | OR
85 |
86 | ```
87 |
88 |
89 | ```
90 |
91 | Then
92 |
93 | ```
94 | new Calendar('.calendar', {
95 | language: 'fr'
96 | })
97 | ```
98 |
99 | The list of available languages is available [here](https://github.com/year-calendar/js-year-calendar/tree/master/locales)
100 |
101 | ## Updating calendar
102 |
103 | You can update the calendar after being instantiated:
104 | ```
105 | const calendar = new Calendar('.calendar');
106 |
107 | calendar.setStyle('background');
108 | calendar.setMaxDate(new Date());
109 | ```
110 |
111 | You can find the exhaustive list of methods in the [documentation](https://year-calendar.github.io/js-year-calendar/documentation).
112 |
113 | ## Events
114 |
115 | You can bind events to the calendar at initialization
116 | ```
117 | const calendar = new Calendar('.calendar', {
118 | clickDay: function(e) {
119 | alert('Click on day ' + e.date.toString());
120 | }
121 | });
122 | ```
123 |
124 | or later
125 |
126 | ```
127 | new Calendar('.calendar');
128 | document.querySelector('.calendar').addEventListener('clickDay', function(e) {
129 | alert('Click on day ' + e.date.toString());
130 | });
131 | ```
132 |
133 | You can find the exhaustive list of events in the [documentation](https://year-calendar.github.io/js-year-calendar/documentation).
134 |
135 | ## Migrating v1.x to v2.x
136 |
137 | If you are using the dataSource option as a function (callback or promise), the first parameter has changed:
138 | ```
139 | new Calendar('#calendar', {
140 | dataSource: (year) => {
141 | console.log(year);
142 | }
143 | }
144 | ```
145 | becomes
146 | ```
147 | new Calendar('#calendar', {
148 | dataSource: (period) => {
149 | console.log(period.year);
150 | }
151 | }
152 | ```
153 |
154 | For more details, check [this PR](https://github.com/year-calendar/js-year-calendar/pull/32)
155 |
156 | ## Migrating from bootstrap-year-calendar
157 |
158 | This widget is based on the [bootstrap-year-calendar](https://github.com/Paul-DS/bootstrap-year-calendar) widget.
159 | If you were using this widget, these are the modifications to apply to successfully migrate your project:
160 |
161 | ### Initialization
162 |
163 | The project doesn't use jQuery anymore, so the initialization of the calendar will be using pure Javascript.
164 |
165 | The old code:
166 | ```
167 | $('.calendar').calendar({ /* Options */ })
168 | ```
169 |
170 | Will be replaced by:
171 | ```
172 | new Calendar('.calendar', { /* Options */ });
173 | ```
174 |
175 | Or
176 |
177 | ```
178 | new Calendar($('.calendar').get(0), { /* Options */ });
179 | // Use ".get(0)" to get the DOM element from the jQuery element
180 | ```
181 |
182 | ### Get the calendar from the DOM element
183 |
184 | Given that the widget doesn't rely on jQuery, it won't be possible to get the calendar instance from the DOM element anymore:
185 | ```
186 | $('.calendar').data('calendar').set...();
187 | ```
188 |
189 | You will have to store the instance of the calendar by yourself:
190 | ```
191 | const calendar = new Calendar('.calendar');
192 |
193 | ...
194 |
195 | calendar.set...();
196 | ```
197 |
198 | ## What next
199 |
200 | Check the [documentation](https://year-calendar.github.io/js-year-calendar/documentation) and [examples](https://year-calendar.github.io/rc-year-calendar/examples) pages to discover all the functionalities.
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | const gulp = require('gulp');
2 | const ts = require('gulp-typescript');
3 | const babel = require('gulp-babel');
4 | const less = require('gulp-less');
5 | const uglify = require('gulp-uglify');
6 | const cleanCss = require('gulp-clean-css');
7 | const rename = require('gulp-rename');
8 |
9 | const compileTS = function() {
10 | const tsProject = ts.createProject('tsconfig.json');
11 | // Use to generate the definition type
12 | return gulp.src('src/ts/**/*.ts')
13 | .pipe(tsProject())
14 | .dts
15 | .pipe(gulp.dest('dist'));
16 | }
17 |
18 | const exportJS = function() {
19 | return gulp.src('src/ts/js-year-calendar.ts')
20 | .pipe(babel())
21 | .pipe(gulp.dest('dist'));
22 | }
23 |
24 | const minifyJS = function() {
25 | return gulp.src('dist/js-year-calendar.js')
26 | .pipe(rename({ suffix: '.min' }))
27 | .pipe(uglify())
28 | .pipe(gulp.dest('dist'));
29 | }
30 |
31 | const scripts = gulp.series(compileTS, exportJS, minifyJS);
32 |
33 | const styles = function() {
34 | return gulp.src('src/less/js-year-calendar.less')
35 | .pipe(less())
36 | .pipe(gulp.dest('dist'))
37 | .pipe(rename({ suffix: '.min' }))
38 | .pipe(cleanCss())
39 | .pipe(gulp.dest('dist'));
40 | }
41 |
42 | gulp.task('build', gulp.series(scripts, styles));
43 |
44 | gulp.task('watch', function () {
45 | gulp.watch('src/ts/**/*.ts', scripts);
46 | gulp.watch('src/less/**/*.less', styles);
47 | });
--------------------------------------------------------------------------------
/locales/js-year-calendar.ca.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Catalan translation for js-year-calendar
3 | * David Ramirez
4 | * Based on
5 | * Catalan translation for bootstrap-datepicker
6 | * J. Garcia
7 | */
8 |
9 | Calendar.locales['ca'] = {
10 | days: ["Diumenge", "Dilluns", "Dimarts", "Dimecres", "Dijous", "Divendres", "Dissabte"],
11 | daysShort: ["Diu", "Dill", "Dim", "Dmc", "Dij", "Div", "Dis"],
12 | daysMin: ["Dg", "Dl", "Dt", "Dc", "Dj", "Dv", "Ds"],
13 | months: ["Gener", "Febrer", "Març", "Abril", "Maig", "Juny", "Juliol", "Agost", "Setembre", "Octubre", "Novembre", "Desembre"],
14 | monthsShort: ["Gen", "Feb", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Oct", "Nov", "Dec"],
15 | weekShort: 'S',
16 | weekStart: 1
17 | };
--------------------------------------------------------------------------------
/locales/js-year-calendar.cs.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Czech translation for js-year-calendar
3 | * @ptica
4 | * Based on
5 | * German translation for js-year-calendar
6 | * Paul DAVID-SIVELLE
7 | * and moment.js locale configuration by author : petrbela : https://github.com/petrbela
8 | */
9 |
10 | Calendar.locales['cs'] = {
11 | days: ["Neděle", "Pondělí", "Úterý", "Středa", "Čtvrtek", "Pátek", "Sobota"],
12 | daysShort: ["Ne", "Po", "Út", "St", "Čt", "Pá", "So"],
13 | daysMin: ["Ne", "Po", "Út", "St", "Čt", "Pá", "So"],
14 | months: ["Leden", "Únor", "Březen", "Duben", "Květen", "Červen", "Červenec", "Srpen", "Září", "Říjen", "Listopad", "Prosinec"],
15 | monthsShort: ["Led", "Úno", "Bře", "Dub", "Kvě", "Čvn", "Čvc", "Srp", "Zář", "Říj", "Lis", "Pro"],
16 | weekShort: 'T',
17 | weekStart: 1
18 | };
--------------------------------------------------------------------------------
/locales/js-year-calendar.de.js:
--------------------------------------------------------------------------------
1 | /**
2 | * German translation for js-year-calendar
3 | * Paul DAVID-SIVELLE
4 | * Based on
5 | * German translation for bootstrap-datepicker
6 | * Sam Zurcher
7 | */
8 |
9 | Calendar.locales['de'] = {
10 | days: ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"],
11 | daysShort: ["Son", "Mon", "Die", "Mit", "Don", "Fre", "Sam"],
12 | daysMin: ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"],
13 | months: ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"],
14 | monthsShort: ["Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"],
15 | weekShort: 'W',
16 | weekStart: 1
17 | };
--------------------------------------------------------------------------------
/locales/js-year-calendar.es.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Spanish translation for js-year-calendar
3 | * Paul DAVID-SIVELLE
4 | * Based on
5 | * Spanish translation for bootstrap-datepicker
6 | * Bruno Bonamin
7 | */
8 |
9 | Calendar.locales['es'] = {
10 | days: ["Domingo", "Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado"],
11 | daysShort: ["Dom", "Lun", "Mar", "Mié", "Jue", "Vie", "Sáb"],
12 | daysMin: ["Do", "Lu", "Ma", "Mi", "Ju", "Vi", "Sa"],
13 | months: ["Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"],
14 | monthsShort: ["Ene", "Feb", "Mar", "Abr", "May", "Jun", "Jul", "Ago", "Sep", "Oct", "Nov", "Dic"],
15 | weekShort: 'S',
16 | weekStart: 1
17 | };
--------------------------------------------------------------------------------
/locales/js-year-calendar.eu.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Basque translation for js-year-calendar
3 | * Iker Ibarguren Berasaluze
4 | * Based on * Basque translation for bootstrap-datepicker
5 | * by Karrikas ( karrikas@karrikas.com )
6 | */
7 |
8 | Calendar.locales['eu'] = {
9 | days: ["Igandea", "Astelehena", "Asteartea", "Asteazkena", "Osteguna", "Ostirala", "Larunbata"],
10 | daysShort: [ "Ig.", "Al.", "Ar.", "Az.", "Og.", "Ol.", "Lr." ],
11 | daysMin: [ "Ig", "Al", "Ar", "Az", "Og", "Ol", "Lr" ],
12 | months: [ "Urtarrila", "Otsaila", "Martxoa", "Apirila", "Maiatza", "Ekaina", "Uztaila", "Abuztua", "Iraila", "Urria", "Azaroa", "Abendua" ],
13 | monthsShort: [ "Urt", "Ots", "Mar.", "Api", "Mai", "Eka", "Uzt", "Abu", "Ira", "Urr", "Aza", "Abe" ],
14 | weekShort: 'A',
15 | weekStart: 1
16 | };
--------------------------------------------------------------------------------
/locales/js-year-calendar.fi.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Finnish translation for js-year-calendar
3 | * Tuomas Jaakola (iqqmuT)
4 | */
5 |
6 | Calendar.locales['fi'] = {
7 | days: ["sunnuntai", "maanantai", "tiistai", "keskiviikko", "torstai", "perjantai", "lauantai"],
8 | daysShort: ["su", "ma", "ti", "ke", "to", "pe", "la"],
9 | daysMin: ["su", "ma", "ti", "ke", "to", "pe", "la"],
10 | months: ["tammikuu", "helmikuu", "maaliskuu", "huhtikuu", "toukokuu", "kesäkuu", "heinäkuu", "elokuu", "syyskuu", "lokakuu", "marraskuu", "joulukuu"],
11 | monthsShort: ["tammi", "helmi", "maalis", "huhti", "touko", "kesä", "heinä", "elo", "syys", "loka", "marras", "joulu"],
12 | weekShort: 'V',
13 | weekStart: 1
14 | };
15 |
--------------------------------------------------------------------------------
/locales/js-year-calendar.fr.js:
--------------------------------------------------------------------------------
1 | /**
2 | * French translation for js-year-calendar
3 | * Paul DAVID-SIVELLE
4 | * Based on
5 | * French translation for bootstrap-datepicker
6 | * Nico Mollet
7 | */
8 |
9 | Calendar.locales['fr'] = {
10 | days: ["Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"],
11 | daysShort: ["Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam"],
12 | daysMin: ["D", "L", "Ma", "Me", "J", "V", "S"],
13 | months: ["Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"],
14 | monthsShort: ["Jan", "Fév", "Mar", "Avr", "Mai", "Jui", "Jul", "Aou", "Sep", "Oct", "Nov", "Déc"],
15 | weekShort:'S',
16 | weekStart: 1
17 | };
--------------------------------------------------------------------------------
/locales/js-year-calendar.hr.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Croatian translation for js-year-calendar
3 | * Davor Došlnec
4 | */
5 |
6 | Calendar.locales['hr'] = {
7 | days: ["Nedjelja", "Ponedjeljak", "Utorak", "Srijeda", "Četvrtak", "Petak", "Subota"],
8 | daysShort: ["Ned", "Pon", "Uto", "Sri", "Čet", "Pet", "Sub"],
9 | daysMin: ["Ne", "Po", "Ut", "Sr", "Če", "Pe", "Su"],
10 | months: ["Siječanj", "Veljača", "Ožujak", "Travanj", "Svibanj", "Lipanj", "Srpanj", "Kolovoz", "Rujan", "Listopad", "Studeni", "Prosinac"],
11 | monthsShort: ["Sij", "Vel", "Ožu", "Tra", "Svi", "Lip", "Srp", "Kol", "Ruj", "Lis", "Stu", "Pro"],
12 | weekShort: 'T',
13 | weekStart: 1
14 | };
--------------------------------------------------------------------------------
/locales/js-year-calendar.hu.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Hungarian translation for js-year-calendar
3 | * Tamas Jozsef Balku
4 | * Based on
5 | * Hungarian translation for bootstrap-datepicker
6 | * Tamas Jozsef Balku
7 | */
8 |
9 | Calendar.locales['hu'] = {
10 | days: [ "Vasárnap","Hétfő", "Kedd", "Szerda", "Csütörtök", "Péntek", "Szombat"],
11 | daysShort: ["Vas", "Hét", "Ked", "Sze", "Csü", "Pén", "Szo"],
12 | daysMin: ["Va", "Hé", "Ke", "Sz", "Cs", "Pé", "Sz"],
13 | months: ["Január", "Február", "Március", "Április", "Május", "Június", "Július", "Augusztus", "Szeptember", "Október", "November", "December"],
14 | monthsShort: ["Jan", "Feb", "Már", "Ápr", "Máj", "Jún", "Júl", "Aug", "Sze", "Okt", "Nov", "Dec"],
15 | weekShort: 'h',
16 | weekStart: 1
17 | };
--------------------------------------------------------------------------------
/locales/js-year-calendar.id.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Indonesian translation for js-year-calendar
3 | * Kiki Ldc
4 | * Based on
5 | * Indonesian translation for bootstrap-datepicker
6 | * Kiki Ldc <7x24th@gmail.com>
7 | */
8 |
9 | Calendar.locales ['id'] = {
10 | days: ["Minggu", "Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu"],
11 | daysShort: ["Ming", "Sen", "Sel", "Rab", "kam", "Jum", "Sab"],
12 | daysMin: ["Mg", "Sn", "Sl", "Rb", "Km", "Jm", "Sb"],
13 | months: ["Januari", "Februari", "Maret", "April", "Mai", "Juni", "Juli", "Agustus", "September", "Oktober", "November", "Desember" ],
14 | monthsShort: ["Jan", "Feb", "Mar", "Apr", "Mai", "Jun", "Jul", "Agt", "Sep", "Okt", "Nov", "Des"],
15 | weekShort: 'W',
16 | weekStart: 1
17 | };
--------------------------------------------------------------------------------
/locales/js-year-calendar.is.js:
--------------------------------------------------------------------------------
1 | /**
2 | * German translation for js-year-calendar
3 | * Paul DAVID-SIVELLE
4 | * Based on
5 | * German translation for bootstrap-datepicker
6 | * Knútur Óli Magnússon
7 | */
8 |
9 | Calendar.locales['is'] = {
10 | days: ["Sunnudagur", "Mánudagur", "Þriðjudagur", "Miðvikudagur", "Fimmtudagur", "Föstudagur", "Laugardagur"],
11 | daysShort: ["Sun", "Mán", "Þri", "Mið", "Fim", "Fös", "Lau"],
12 | daysMin: ["Su", "Má", "Þr", "Mi", "Fi", "Fö", "La"],
13 | months: ["Janúar", "Febrúar", "Mars", "Apríl", "Maí", "Júní", "Júlí", "Ágúst", "September", "Október", "Nóvember", "Desember"],
14 | monthsShort: ["Jan", "Feb", "Mar", "Apr", "Maí", "Jún", "Júl", "Agú", "Sep", "Okt", "Nóv", "Des"],
15 | weekShort: 'W',
16 | weekStart: 1
17 | };
--------------------------------------------------------------------------------
/locales/js-year-calendar.it.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Italian translation for js-year-calendar
3 | * Paul DAVID-SIVELLE
4 | * Based on
5 | * Italian translation for bootstrap-datepicker
6 | * Enrico Rubboli
7 | */
8 |
9 | Calendar.locales['it'] = {
10 | days: ["Domenica", "Lunedì", "Martedì", "Mercoledì", "Giovedì", "Venerdì", "Sabato"],
11 | daysShort: ["Dom", "Lun", "Mar", "Mer", "Gio", "Ven", "Sab"],
12 | daysMin: ["Do", "Lu", "Ma", "Me", "Gi", "Ve", "Sa"],
13 | months: ["Gennaio", "Febbraio", "Marzo", "Aprile", "Maggio", "Giugno", "Luglio", "Agosto", "Settembre", "Ottobre", "Novembre", "Dicembre"],
14 | monthsShort: ["Gen", "Feb", "Mar", "Apr", "Mag", "Giu", "Lug", "Ago", "Set", "Ott", "Nov", "Dic"],
15 | weekShort: 'S',
16 | weekStart: 1,
17 | };
--------------------------------------------------------------------------------
/locales/js-year-calendar.ja.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Japanese translation for js-year-calendar
3 | * Paul DAVID-SIVELLE
4 | * Based on
5 | * Japanese translation for bootstrap-datepicker
6 | * Norio Suzuki
7 | */
8 |
9 | Calendar.locales['ja'] = {
10 | days: ["日曜", "月曜", "火曜", "水曜", "木曜", "金曜", "土曜"],
11 | daysShort: ["日", "月", "火", "水", "木", "金", "土"],
12 | daysMin: ["日", "月", "火", "水", "木", "金", "土"],
13 | months: ["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"],
14 | monthsShort: ["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"],
15 | weekShort: '週',
16 | weekStart:0
17 | };
--------------------------------------------------------------------------------
/locales/js-year-calendar.ko.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Korean translation for js-year-calendar
3 | * minho kim
4 | */
5 | Calendar.locales['ko'] = {
6 | days: ["일", "월", "화", "수", "목", "금", "토"],
7 | daysShort: ["일", "월", "화", "수", "목", "금", "토"],
8 | daysMin: ["일", "월", "화", "수", "목", "금", "토"],
9 | months: ["1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월"],
10 | monthsShort: ["1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월"],
11 | weekShort: 'T',
12 | weekStart: 1
13 | };
14 |
--------------------------------------------------------------------------------
/locales/js-year-calendar.lt.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Lithuanian translation for js-year-calendar
3 | * Mantas Urbonas mantas.urbonas@gmail.com
4 | *
5 | * either MIT or BSD or Apache 2 licensed - choose whatever license suits you most.
6 | */
7 |
8 | Calendar.locales['lt'] = {
9 | days: ["Sekmadienis", "Pirmadienis", "Antradienis", "Trečiadienis", "Ketvirtadienis", "Penktadienis", "Šeštadienis"],
10 | daysShort: ["Sek", "Pir", "Ant", "Tre", "Ket", "Pen", "Šeš"],
11 | daysMin: ["Se", "Pr", "An", "Tr", "Kt", "Pn", "Še"],
12 | months: ["Sausis", "Vasaris", "Kovas", "Balandis", "Gegužė", "Birželis", "Liepa", "Rugpjūtis", "Rugsėjis", "Spalis", "Lapkritis", "Gruodis"],
13 | monthsShort: ["Sau", "Vas", "Kov", "Bal", "Geg", "Bir", "Lie", "Rgp", "Rug", "Spa", "Lap", "Gru"],
14 | weekShort: 'S',
15 | weekStart: 1
16 | };
--------------------------------------------------------------------------------
/locales/js-year-calendar.lv.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Latvian translation for js-year-calendar
3 | * Imants Horsts
4 | */
5 |
6 | Calendar.locales['lv'] = {
7 | days: ["Svētdiena", "Pirmdiena", "Otrdiena", "Trešdiena", "Ceturtdiena", "Piektdiena", "Sestdiena"],
8 | daysShort: ["Svt", "Prm", "Otr", "Tre", "Ctr", "Pkt", "Sst"],
9 | daysMin: ["Sv", "P", "O", "T", "C", "Pk", "S"],
10 | months: ["Janvāris", "Februāris", "Marts", "Aprīlis", "Maijs", "Jūnijs", "Jūlijs", "Augusts", "Septembris", "Oktobris", "Novembris", "Decembris"],
11 | monthsShort: ["Jan", "Feb", "Mar", "Apr", "Mai", "Jūn", "Jūl", "Aug", "Sep", "Okt", "Nov", "Dec"],
12 | weekShort: 'N',
13 | weekStart: 1
14 | };
--------------------------------------------------------------------------------
/locales/js-year-calendar.ms.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Malay translation for js-year-calendar
3 | * Chia Wei Lim
4 | */
5 | Calendar.locales['ms'] = {
6 | days: ["Ahad", "Isnin", "Selasa", "Rabu", "Khamis", "Jumaat", "Sabtu"],
7 | daysShort: ["Ahd", "Isn", "Sel", "Rab", "Kha", "Jum", "Sab"],
8 | daysMin: ["Ahd", "Isn", "Sel", "Rab", "Kha", "Jum", "Sab"],
9 | months: ["Januari", "Februari", "Mac", "April", "Mei", "Jun", "Julai", "Ogos", "September", "Oktober", "November", "Disember"],
10 | monthsShort: ["Jan", "Feb", "Mac", "Apr", "Mei", "Jun", "Jul", "Ogos", "Sep", "Okt", "Nov", "Dis"],
11 | weekShort: 'M',
12 | weekStart: 1
13 | };
14 |
--------------------------------------------------------------------------------
/locales/js-year-calendar.nl.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Dutch translation for js-year-calendar
3 | * Paul DAVID-SIVELLE
4 | * Based on
5 | * Dutch translation for bootstrap-datepicker
6 | * Reinier Goltstein
7 | */
8 |
9 | Calendar.locales['nl'] = {
10 | days: ["zondag", "maandag", "dinsdag", "woensdag", "donderdag", "vrijdag", "zaterdag"],
11 | daysShort: ["zo", "ma", "di", "wo", "do", "vr", "za"],
12 | daysMin: ["zo", "ma", "di", "wo", "do", "vr", "za"],
13 | months: ["januari", "februari", "maart", "april", "mei", "juni", "juli", "augustus", "september", "oktober", "november", "december"],
14 | monthsShort: ["jan", "feb", "mrt", "apr", "mei", "jun", "jul", "aug", "sep", "okt", "nov", "dec"],
15 | weekShort: 'w',
16 | weekStart: 1
17 | };
--------------------------------------------------------------------------------
/locales/js-year-calendar.pl.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Polish translation for js-year-calendar
3 | * Robert 'Wooya' Gaudyn
4 | */
5 |
6 | Calendar.locales['pl'] = {
7 | days: ["Niedziela", "Poniedziałek", "Wtorek", "Środa", "Czwartek", "Piatek", "Sobota"],
8 | daysShort: ["Nie", "Pon", "Wto", "Śro", "Czw", "Pią", "Sob"],
9 | daysMin: ["Ni", "Po", "Wt", "Śr", "Cz", "Pi", "So"],
10 | months: ["Styczeń", "Luty", "Marzec", "Kwiecień", "Maj", "Czerwiec", "Lipiec", "Sierpień", "Wrzesień", "Październik", "Listopad", "Grudzień"],
11 | monthsShort: ["Sty", "Lut", "Mar", "Kwi", "Maj", "Cze", "Lip", "Sie", "Wrz", "Paź", "Lis", "Gru"],
12 | weekShort: 'W',
13 | weekStart: 1
14 | };
--------------------------------------------------------------------------------
/locales/js-year-calendar.pt.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Portuguese translation for js-year-calendar
3 | * Paul DAVID-SIVELLE
4 | * Based on
5 | * Portuguese translation for bootstrap-datepicker
6 | * Original code: Cauan Cabral
7 | * Tiago Melo
8 | */
9 |
10 | Calendar.locales['pt'] = {
11 | days: ["Domingo", "Segunda", "Terça", "Quarta", "Quinta", "Sexta", "Sábado"],
12 | daysShort: ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb"],
13 | daysMin: ["Do", "Se", "Te", "Qu", "Qu", "Se", "Sa"],
14 | months: ["Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho", "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro"],
15 | monthsShort: ["Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez"],
16 | weekShort: 'S',
17 | weekStart:0
18 | };
--------------------------------------------------------------------------------
/locales/js-year-calendar.ro.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Romanian translation for js-year-calendar
3 | * Gelu Lupaș
4 | */
5 |
6 | Calendar.locales['fr'] = {
7 | days: ["Duminică", "Luni", "Marți", "Miercuri", "Joi", "Vineri", "Sâmbătă"],
8 | daysShort: ["Du", "Lu", "Ma", "Mi", "Jo", "Vi", "Sa"],
9 | daysMin: ["D", "L", "Ma", "Mi", "J", "V", "S"],
10 | months: ["ianuarie", "februarie", "martie", "aprilie", "mai", "iunie", "iulie", "august", "septembrie", "octombrie", "noiembrie", "decembrie"],
11 | monthsShort: ["ian", "feb", "mar", "apr", "mai", "iun", "iul", "aug", "sep", "oct", "noi", "dec"],
12 | weekShort: 'S',
13 | weekStart: 1
14 | };
15 |
--------------------------------------------------------------------------------
/locales/js-year-calendar.ru.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Russian translation for js-year-calendar
3 | * Paul DAVID-SIVELLE
4 | * Based on
5 | * Russian translation for bootstrap-datepicker
6 | * Victor Taranenko
7 | */
8 |
9 | Calendar.locales['ru'] = {
10 | days: ["Воскресенье", "Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота"],
11 | daysShort: ["Вск", "Пнд", "Втр", "Срд", "Чтв", "Птн", "Суб"],
12 | daysMin: ["Вс", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб"],
13 | months: ["Январь", "Февраль", "Март", "Апрель", "Май", "Июнь", "Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь"],
14 | monthsShort: ["Янв", "Фев", "Мар", "Апр", "Май", "Июн", "Июл", "Авг", "Сен", "Окт", "Ноя", "Дек"],
15 | weekShort: 'н',
16 | weekStart: 1
17 | };
--------------------------------------------------------------------------------
/locales/js-year-calendar.sk.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Slovak translation for js-year-calendar
3 | * Marian Sulgan
4 | * marian@sulgan.sk
5 | */
6 |
7 | Calendar.locales['sk'] = {
8 | days: ["Nedeľa", "Pondelok", "Utorok", "Streda", "Štvrtok", "Piatok", "Sobota"],
9 | daysShort: ["Ned", "Pon", "Uto", "Str", "Štv", "Pia", "Sob"],
10 | daysMin: ["Ne", "Po", "Ut", "St", "Št", "Pia", "So"],
11 | months: ["Január", "Február", "Marec", "Apríl", "Máj", "Jún", "Júl", "August", "September", "Október", "November", "December"],
12 | monthsShort: ["Jan", "Feb", "Mar", "Apr", "Máj", "Jún", "Júl", "Aug", "Sep", "Okt", "Nov", "Dec"],
13 | weekShort: 'W',
14 | weekStart: 1
15 | };
--------------------------------------------------------------------------------
/locales/js-year-calendar.sr.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Serbian translation for js-year-calendar
3 | * Lazarevic Ivica
4 | */
5 |
6 | Calendar.locales['sr'] = {
7 | days: ["Недеља", "Понедељак", "Уторак", "Среда", "Четвртак", "Петак", "Субота"],
8 | daysShort: ["Нед", "Пон", "Уто", "Сре", "Чет", "Пет", "Суб"],
9 | daysMin: ["Не", "По", "Ут", "Ср", "Че", "Пе", "Су"],
10 | months: ["Јануар", "Фебруар", "Март", "Април", "Мај", "Јун", "Јул", "Август", "Септембар", "Октобар", "Новембар", "Децембар"],
11 | monthsShort: ["Јан", "Феб", "Мар", "Апр", "Мај", "Јун", "Јул", "Авг", "Сеп", "Окт", "Нов", "Дец"],
12 | weekShort: 'н',
13 | weekStart: 1
14 | };
--------------------------------------------------------------------------------
/locales/js-year-calendar.th.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Thai translation for js-year-calendar
3 | * xypherbo
4 | *
5 | */
6 |
7 | Calendar.locales["th"] = {
8 | days: ["อาทิตย์", "จันทร์", "อังคาร", "พุธ", "พฤหัสบดี", "ศุกร์", "เสาร์"],
9 | daysShort: ["อา.", "จ.", "อ.", "พ.", "พฤ.", "ศ.", "ส."],
10 | daysMin: ["อา.", "จ.", "อ.", "พ.", "พฤ.", "ศ.", "ส."],
11 | months: ["มกราคม", "กุมภาพันธ์", "มีนาคม", "เมษายน", "พฤษภาคม", "มิถุนายน", "กรกฎาคม", "สิงหาคม", "กันยายน", "ตุลาคม", "พฤศจิกายน", "ธันวาคม"],
12 | monthsShort: ["ม.ค.", "ก.พ.", "มี.ค.", "เม.ย.", "พ.ค.", "มิ.ย.", "ก.ค.", "ส.ค.", "ก.ย.", "ต.ค.", "พ.ย.", "ธ.ค."],
13 | weekShort: "ส.",
14 | weekStart: 0
15 | };
16 |
--------------------------------------------------------------------------------
/locales/js-year-calendar.tr.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Turkish translation for js-year-calendar
3 | * Paul DAVID-SIVELLE
4 | * Based on
5 | * Turkish translation for bootstrap-datepicker
6 | * Serkan Algur
7 | */
8 |
9 | Calendar.locales['tr'] = {
10 | days: ["Pazar", "Pazartesi", "Salı", "Çarşamba", "Perşembe", "Cuma", "Cumartesi"],
11 | daysShort: ["Pz", "Pzt", "Sal", "Çrş", "Prş", "Cu", "Cts"],
12 | daysMin: ["Pz", "Pzt", "Sa", "Çr", "Pr", "Cu", "Ct"],
13 | months: ["Ocak", "Şubat", "Mart", "Nisan", "Mayıs", "Haziran", "Temmuz", "Ağustos", "Eylül", "Ekim", "Kasım", "Aralık"],
14 | monthsShort: ["Oca", "Şub", "Mar", "Nis", "May", "Haz", "Tem", "Ağu", "Eyl", "Eki", "Kas", "Ara"],
15 | weekShort: 'H',
16 | weekStart: 1
17 | };
--------------------------------------------------------------------------------
/locales/js-year-calendar.ua.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Ukrainian translation for js-year-calendar
3 | * Petro Franko
4 | */
5 |
6 | Calendar.locales['ua'] = {
7 | days: ["Неділя", "Понеділок", "Вівторок", "Середа", "Четвер", "П'ятниця", "Субота"],
8 | daysShort: ["Нед", "Пон", "Вт", "Сер", "Чет", "Пт", "Суб"],
9 | daysMin: ["Нд", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб"],
10 | months: ["Січень", "Лютий", "Березень", "Квітень", "Травень", "Червень", "Липень", "Серпень", "Вересень", "Жовтень", "Листопад", "Грудень"],
11 | monthsShort: ["Січ", "Лют", "Бер", "Кві", "Тра", "Чер", "Лип", "Сер", "Вер", "Жов", "Лис", "Гру"],
12 | weekShort: 'Т',
13 | weekStart: 1
14 | };
15 |
--------------------------------------------------------------------------------
/locales/js-year-calendar.uk.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Ukrainian translation for js-year-calendar
3 | * by Vadym Korolyuk
4 | */
5 |
6 | Calendar.locales['uk'] = {
7 | days: ["Неділя", "Понеділок", "Вівторок", "Середа", "Четвер", "П'ятниця", "Субота"],
8 | daysShort: ["Нед", "Пон", "Вів", "Сер", "Чет", "Птн", "Суб"],
9 | daysMin: ["Нд", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб"],
10 | months: ["Січень", "Лютий", "Березень", "Квітень", "Травень", "Червень", "Липень","Серпень", "Вересень", "Жовтень", "Листопад", "Грудень"],
11 | monthsShort: ["Січ", "Лют", "Бер", "Кві", "Тра", "Чер", "Лип", "Сер", "Вер", "Жов", "Лис","Гру"],
12 | weekShort: 'н',
13 | weekStart: 1
14 | };
--------------------------------------------------------------------------------
/locales/js-year-calendar.uz.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * Uzbek translation for js-year-calendar
4 | * Oqil Karimov
5 | * Based on
6 | * Uzbek translation for moment
7 | */
8 |
9 | Calendar.locales['uz'] = {
10 | days: ["Yakshanba", "Dushanba", "Seshanba", "Chorshanba", "Payshanba", "Juma", "Shanba"],
11 | daysShort: ["Yak","Dush","Sesh","Chor","Pay","Jum","Shan"],
12 | daysMin: ["Ya","Du","Se","Cho","Pa","Ju","Sha"],
13 | months: ["Yanvar", "Fevral", "Mart", "Aprel", "May", "Iyun", "Iyul", "Avgust", "Sentabr", "Oktabr", "Noyabr", "Dekabr"],
14 | monthsShort: ["Yan", "Fev", "Mar", "Apr", "May", "Iyun", "Iyul", "Avg", "Sen", "Okt", "Noy", "Dek"],
15 | weekShort: 'h',
16 | weekStart: 1
17 | };
18 |
--------------------------------------------------------------------------------
/locales/js-year-calendar.zh-CN.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Simplified Chinese translation js-year-calendar
3 | * William Hernández <>
4 | * Based on
5 | * Simplified Chinese translation for bootstrap-datepicker
6 | * Yuan Cheung
7 | */
8 |
9 | Calendar.locales['zh-CN'] = {
10 | days: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"],
11 | daysShort: ["周日", "周一", "周二", "周三", "周四", "周五", "周六"],
12 | daysMin: ["日", "一", "二", "三", "四", "五", "六"],
13 | months: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"],
14 | monthsShort: ["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"],
15 | weekShort: '周',
16 | weekStart: 1
17 | };
--------------------------------------------------------------------------------
/locales/js-year-calendar.zh-TW.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Traditional Chinese translation js-year-calendar
3 | * Based on
4 | * Traditional Chinese translation for bootstrap-datepicker
5 | * Rung-Sheng Jang
6 | * FrankWu
7 | */
8 |
9 | Calendar.locales['zh-TW'] = {
10 | days: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"],
11 | daysShort: ["週日", "週一", "週二", "週三", "週四", "週五", "週六"],
12 | daysMin: ["日", "一", "二", "三", "四", "五", "六"],
13 | months: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"],
14 | monthsShort: ["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"],
15 | weekShort: '週',
16 | weekStart: 1
17 | };
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "js-year-calendar",
3 | "version": "2.0.0",
4 | "description": "A fully customizable year calendar widget",
5 | "main": "./dist/js-year-calendar.js",
6 | "scripts": {
7 | "watch": "gulp watch",
8 | "build": "gulp build",
9 | "prepare": "npm run build && npm run test && npm run doc",
10 | "test": "jest",
11 | "doc": "typedoc src/ts"
12 | },
13 | "pre-commit": [
14 | "build",
15 | "test"
16 | ],
17 | "files": [
18 | "dist",
19 | "locales"
20 | ],
21 | "repository": {
22 | "type": "git",
23 | "url": "git+https://github.com/year-calendar/js-year-calendar.git"
24 | },
25 | "keywords": [
26 | "calendar",
27 | "year",
28 | "javascript",
29 | "widget"
30 | ],
31 | "author": "Paul-DS",
32 | "license": "Apache-2.0",
33 | "bugs": {
34 | "url": "https://github.com/year-calendar/js-year-calendar/issues"
35 | },
36 | "homepage": "https://year-calendar.github.io/",
37 | "devDependencies": {
38 | "@babel/core": "^7.15.0",
39 | "@babel/plugin-proposal-class-properties": "^7.14.5",
40 | "@babel/plugin-transform-modules-umd": "^7.14.5",
41 | "@babel/preset-env": "^7.15.0",
42 | "@babel/preset-typescript": "^7.15.0",
43 | "gulp": "^4.0.2",
44 | "gulp-babel": "^8.0.0",
45 | "gulp-clean-css": "^4.3.0",
46 | "gulp-less": "^5.0.0",
47 | "gulp-rename": "^2.0.0",
48 | "gulp-typescript": "^5.0.1",
49 | "gulp-uglify": "^3.0.2",
50 | "jest": "^27.0.6",
51 | "typedoc": "^0.19.2",
52 | "typescript": "^4.0.8"
53 | },
54 | "jest": {
55 | "testMatch": [
56 | "**/tests/*.[jt]s?(x)"
57 | ],
58 | "collectCoverage": true,
59 | "testEnvironment": "jsdom"
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/less/js-year-calendar.less:
--------------------------------------------------------------------------------
1 | /* =========================================================
2 | * JS year calendar v0.1.0
3 | * Repo: https://github.com/year-calendar/js-year-calendar
4 | * =========================================================
5 | * Created by Paul David-Sivelle
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | * ========================================================= */
19 |
20 | /* Main */
21 | .calendar {
22 | padding: 4px;
23 | -webkit-border-radius: 4px;
24 | -moz-border-radius: 4px;
25 | border-radius: 4px;
26 | direction: ltr;
27 | overflow-x: hidden;
28 | -webkit-touch-callout: none;
29 | -webkit-user-select: none;
30 | -khtml-user-select: none;
31 | -moz-user-select: none;
32 | -ms-user-select: none;
33 | user-select: none;
34 |
35 | &:after {
36 | /* Apply the right height on the calendar div, even if the months elements are floating */
37 | clear: both;
38 | content: "";
39 | display:block;
40 | }
41 |
42 | .calendar-rtl {
43 | direction: rtl;
44 |
45 | .calendar-rtl table tr td span {
46 | float: right;
47 | }
48 | }
49 |
50 | table {
51 | margin: auto;
52 | border-spacing: 0;
53 |
54 | td,
55 | th {
56 | text-align: center;
57 | width: 20px;
58 | height: 20px;
59 | border: none;
60 | padding: 4px 5px;
61 | font-size:12px;
62 | }
63 | }
64 |
65 | /* Header */
66 | .calendar-header
67 | {
68 | width:100%;
69 | margin-bottom:20px;
70 | border: 1px solid #ddd;
71 |
72 | table {
73 | width:100%;
74 |
75 | th {
76 | font-size:22px;
77 | padding:5px 10px;
78 | cursor: pointer;
79 |
80 | &:hover {
81 | background: #eeeeee;
82 | }
83 |
84 | &.disabled,
85 | &.disabled:hover {
86 | background: none;
87 | cursor: default;
88 | color:white;
89 | }
90 |
91 | &.prev,
92 | &.next {
93 | width: 20px;
94 | }
95 | }
96 | }
97 |
98 | .year-title {
99 | font-weight:bold;
100 | text-align:center;
101 | height:20px;
102 | width:auto;
103 | }
104 |
105 | .year-neighbor {
106 | opacity: 0.4;
107 |
108 | @media (max-width: 991px) {
109 | display: none;
110 | }
111 | }
112 |
113 | .year-neighbor2 {
114 | opacity: 0.2;
115 |
116 | @media (max-width: 767px) {
117 | display: none;
118 | }
119 | }
120 | }
121 |
122 | /* Months */
123 | .months-container {
124 | width:100%;
125 | display:none;
126 | flex-wrap: wrap;
127 |
128 | .month-container {
129 | float: left;
130 | text-align:center;
131 | padding:0;
132 |
133 | &.month-2 {
134 | width: 16.66666667%;
135 | }
136 |
137 | &.month-3 {
138 | width: 25%;
139 | }
140 |
141 | &.month-4 {
142 | width: 33.33333333%;
143 | }
144 |
145 | &.month-6 {
146 | width: 50%;
147 | }
148 |
149 | &.month-12 {
150 | width: 100%;
151 | }
152 | }
153 | }
154 |
155 | table.month {
156 | th.month-title {
157 | font-size:16px;
158 | padding-bottom: 5px;
159 | }
160 |
161 | th.day-header {
162 | font-size:14px;
163 | }
164 |
165 | tr td,
166 | tr th
167 | {
168 | padding:0;
169 |
170 | &.hidden {
171 | display:none;
172 | }
173 | }
174 |
175 | td.week-number {
176 | cursor: default;
177 | font-weight:bold;
178 | border-right:1px solid #eee;
179 | padding:5px;
180 | }
181 |
182 | td.day {
183 | &.round-left {
184 | -webkit-border-radius: 8px 0 0 8px;
185 | -moz-border-radius: 8px 0 0 8px;
186 | border-radius: 8px 0 0 8px;
187 | }
188 |
189 | &.round-right {
190 | webkit-border-radius: 0 8px 8px 0 ;
191 | -moz-border-radius: 0 8px 8px 0;
192 | border-radius: 0 8px 8px 0;
193 | }
194 |
195 | .day-content {
196 | -webkit-border-radius: 4px;
197 | -moz-border-radius: 4px;
198 | border-radius: 4px;
199 | padding: 5px 6px;
200 | }
201 | }
202 |
203 | td.old,
204 | td.new,
205 | td.old:hover,
206 | td.new:hover {
207 | background: none;
208 | cursor: default;
209 | }
210 |
211 | td.disabled,
212 | td.disabled:hover {
213 | color: #dddddd;
214 |
215 | .day-content:hover {
216 | background: none;
217 | cursor: default;
218 | }
219 | }
220 |
221 | td.range .day-content {
222 | background: rgba(0, 0, 0, 0.2);
223 | -webkit-border-radius: 0;
224 | -moz-border-radius: 0;
225 | border-radius: 0;
226 | }
227 |
228 | td.range.range-start .day-content {
229 | border-top-left-radius:4px;
230 | border-bottom-left-radius:4px;
231 | }
232 |
233 | td.range.range-end .day-content {
234 | border-top-right-radius:4px;
235 | border-bottom-right-radius:4px;
236 | }
237 | }
238 |
239 |
240 | /* Loading */
241 | .calendar-loading-container {
242 | position: relative;
243 | text-align: center;
244 | min-height: 200px;
245 |
246 | .calendar-loading {
247 | position: absolute;
248 | top: 50%;
249 | left: 50%;
250 | transform: translateX(-50%) translateY(-50%)
251 | }
252 | }
253 |
254 | .calendar-spinner {
255 | margin: 20px auto;
256 | width: 80px;
257 | text-align: center;
258 |
259 | > div {
260 | width: 16px;
261 | height: 16px;
262 | margin: 5px;
263 | background-color: #333;
264 | border-radius: 100%;
265 | display: inline-block;
266 | -webkit-animation: sk-bouncedelay 1s infinite ease-in-out both;
267 | animation: sk-bouncedelay 1s infinite ease-in-out both;
268 |
269 | &.bounce1 {
270 | -webkit-animation-delay: -0.32s;
271 | animation-delay: -0.32s;
272 | }
273 |
274 | &.bounce2 {
275 | -webkit-animation-delay: -0.16s;
276 | animation-delay: -0.16s;
277 | }
278 | }
279 | }
280 | }
281 |
282 | /* Context menu */
283 | .calendar-context-menu,
284 | .calendar-context-menu .submenu {
285 | border: 1px solid #ddd;
286 | background-color: white;
287 | box-shadow: 2px 2px 5px rgba(0, 0, 0, .2);
288 | -webkit-box-shadow: 2px 2px 5px rgba(0, 0, 0, .2);
289 | position:absolute;
290 | display:none;
291 | }
292 |
293 | .calendar-context-menu .item {
294 | position: relative;
295 |
296 | .content {
297 | padding:5px 10px;
298 | cursor:pointer;
299 | display:table;
300 | width:100%;
301 | white-space: nowrap;
302 | box-sizing: border-box;
303 |
304 | &:hover {
305 | background:#eee;
306 | }
307 |
308 | .text {
309 | display:table-cell;
310 | }
311 |
312 | .arrow {
313 | display:table-cell;
314 | padding-left:10px;
315 | text-align:right;
316 | }
317 | }
318 |
319 | .submenu {
320 | top: -1px; /* Compensate for the border */
321 |
322 | &:not(.open-left) {
323 | left: 100%;
324 | }
325 |
326 | &.open-left {
327 | right: 100%;
328 | }
329 | }
330 |
331 | &:hover > .submenu {
332 | display:block;
333 | }
334 | }
335 |
336 | .table-striped .calendar table.month tr td,
337 | .table-striped .calendar table.month tr th {
338 | background-color: transparent;
339 | }
340 |
341 | table.month td.day .day-content:hover {
342 | background: rgba(0, 0, 0, 0.2);
343 | cursor: pointer;
344 | }
345 |
346 | @-webkit-keyframes sk-bouncedelay {
347 | 0%, 80%, 100% {
348 | -webkit-transform: scale(0)
349 | }
350 |
351 | 40% {
352 | -webkit-transform: scale(1.0)
353 | }
354 | }
355 |
356 | @keyframes sk-bouncedelay {
357 | 0%, 80%, 100% {
358 | -webkit-transform: scale(0);
359 | transform: scale(0);
360 | }
361 |
362 | 40% {
363 | -webkit-transform: scale(1.0);
364 | transform: scale(1.0);
365 | }
366 | }
367 |
--------------------------------------------------------------------------------
/src/ts/interfaces/CalendarContextMenuItem.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Represent a context menu item for the calendar.
3 | */
4 | export default interface CalendarContextMenuItem {
5 | /**
6 | * The text of the menu item.
7 | */
8 | text: string;
9 |
10 | /**
11 | * A function to be called when the item is clicked.
12 | */
13 | click?: (event: T) => void;
14 |
15 | /**
16 | * The list of sub menu items.
17 | */
18 | items?: CalendarContextMenuItem[];
19 |
20 | /**
21 | * Indicates if the item should be visible
22 | */
23 | visible?: boolean | Function;
24 | }
25 |
--------------------------------------------------------------------------------
/src/ts/interfaces/CalendarDataSourceElement.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Represent an element to display in the calendar.
3 | */
4 | export default interface CalendarDataSourceElement {
5 | /**
6 | * The name of the element. Used for context menu or specific events.
7 | */
8 | name?: string;
9 |
10 | /**
11 | * The color of the element. This property will be computed automatically if not defined.
12 | */
13 | color?: string;
14 |
15 | /**
16 | * The date of the beginning of the element range.
17 | */
18 | startDate: Date;
19 |
20 | /**
21 | * The date of the end of the element range.
22 | */
23 | endDate: Date;
24 |
25 | /**
26 | * Indicates whether only the half of start day of the element range should be rendered.
27 | */
28 | startHalfDay?: boolean;
29 |
30 | /**
31 | * Indicates whether only the half of last day of the element range should be rendered.
32 | */
33 | endHalfDay?: boolean;
34 | }
--------------------------------------------------------------------------------
/src/ts/interfaces/CalendarDayEventObject.ts:
--------------------------------------------------------------------------------
1 | import CalendarDataSourceElement from './CalendarDataSourceElement'
2 |
3 | export default interface CalendarDayEventObject {
4 | /**
5 | * The element that contain the fired day.
6 | */
7 | element: HTMLElement;
8 |
9 | /**
10 | * The fired date.
11 | */
12 | date: Date;
13 |
14 | /**
15 | * The data source elements present on the fired day.
16 | */
17 | events: T[];
18 | }
--------------------------------------------------------------------------------
/src/ts/interfaces/CalendarOptions.ts:
--------------------------------------------------------------------------------
1 | import CalendarDataSourceElement from './CalendarDataSourceElement'
2 | import CalendarContextMenuItem from './CalendarContextMenuItem'
3 | import CalendarDayEventObject from './CalendarDayEventObject'
4 | import CalendarRenderEndEventObject from './CalendarRenderEndEventObject'
5 | import CalendarRangeEventObject from './CalendarRangeEventObject'
6 | import CalendarYearChangedEventObject from './CalendarYearChangedEventObject'
7 | import CalendarPeriodChangedEventObject from './CalendarPeriodChangedEventObject'
8 |
9 | /**
10 | * Options used for calendar customization.
11 | */
12 | export default interface CalendarOptions {
13 |
14 | /**
15 | * Specifies whether the user can select a range which overlapping an other element present in the datasource.
16 | */
17 | allowOverlap?: boolean;
18 |
19 | /**
20 | * Specifies whether the beginning and the end of each range should be displayed as half selected day.
21 | */
22 | alwaysHalfDay?: boolean;
23 |
24 | /**
25 | * Specifies the items of the default context menu.
26 | */
27 | contextMenuItems?: CalendarContextMenuItem[];
28 |
29 | /**
30 | * Specify a custom renderer for days.
31 | *
32 | * The HTML Element passed in parameter represent a sub element of the "day" div. If you need to access the "day" div, use `element.parentElement`.
33 | *
34 | * This function is called during render for each day.
35 | */
36 | customDayRenderer?: (element: HTMLElement, currentDate: Date) => void;
37 |
38 | /**
39 | * Specify a custom renderer for data source. Works only with the style set to "custom".
40 | *
41 | * The HTML Element passed in parameter represent a sub element of the "day" div. If you need to access the "day" div, use `element.parentElement`.
42 | *
43 | * This function is called during render for each day containing at least one event.
44 | */
45 | customDataSourceRenderer?: (element: HTMLElement, currentDate: Date, events: T[]) => void;
46 |
47 | /**
48 | * The elements that must be displayed on the calendar.
49 | *
50 | * Could be:
51 | * - The datasource
52 | * - A function that returns the datasource
53 | * - An async function that will call the callback function with the datasource
54 | * - An async function that returns a Promise to get the datasource
55 | */
56 | dataSource?: T[] | ((currentYear: number) => T[] | Promise) | ((currentYear: number, done: (result: T[]) => void) => void);
57 |
58 | /**
59 | * The days that must be displayed as disabled.
60 | */
61 | disabledDays?: Date[];
62 |
63 | /**
64 | * The days of the week that must be displayed as disabled (0 for Sunday, 1 for Monday, etc.).
65 | */
66 | disabledWeekDays?: number[];
67 |
68 | /**
69 | * The days of the week that must not be displayed (0 for Sunday, 1 for Monday, etc.).
70 | */
71 | hiddenWeekDays?: number[];
72 |
73 | /**
74 | * Specifies whether the data source must be rendered on disabled days.
75 | */
76 | displayDisabledDataSource?: boolean;
77 |
78 | /**
79 | * Specifies whether the weeks number are displayed.
80 | */
81 | displayWeekNumber?: boolean;
82 |
83 | /**
84 | * Specifies whether the calendar header is displayed.
85 | */
86 | displayHeader?: boolean;
87 |
88 | /**
89 | * Specifies whether the default context menu must be displayed when right clicking on a day.
90 | */
91 | enableContextMenu?: boolean;
92 |
93 | /**
94 | * Specifies whether the range selection is enabled.
95 | */
96 | enableRangeSelection?: boolean;
97 |
98 | /**
99 | * The language/culture used for calendar rendering.
100 | * Don't forget to import the corresponding language file. For more information, check the language section of the readme.
101 | */
102 | language?: string;
103 |
104 | /**
105 | * The HTML used to render the loading component.
106 | */
107 | loadingTemplate: string | HTMLElement;
108 |
109 | /**
110 | * The date until which days are enabled.
111 | */
112 | maxDate?: Date;
113 |
114 | /**
115 | * The date from which days are enabled.
116 | */
117 | minDate?: Date;
118 |
119 | /**
120 | * The number of months displayed by the calendar.
121 | */
122 | numberMonthsDisplayed?: number;
123 |
124 | /**
125 | * Specifies whether the beginning and the end of each range should be displayed as rounded cells.
126 | */
127 | roundRangeLimits?: boolean;
128 |
129 | /**
130 | * The date on which the calendar should be opened.
131 | * The day is not considered (only the month and the year).
132 | */
133 | startDate?: Date;
134 |
135 | /**
136 | * The year on which the calendar should be opened.
137 | * If `startDate` is provided, this option will be ignored.
138 | */
139 | startYear?: number;
140 |
141 | /**
142 | * Specifies the style used for displaying datasource ("background", "border" or "custom").
143 | */
144 | style?: string;
145 |
146 | /**
147 | * The starting day of the week. This option overrides the parameter define in the language file.
148 | */
149 | weekStart?: number;
150 |
151 | /**
152 | * Function fired when a day is clicked.
153 | */
154 | clickDay?: (e: CalendarDayEventObject) => void;
155 |
156 | /**
157 | * Function fired when a day is right clicked.
158 | */
159 | dayContextMenu?: (e: CalendarDayEventObject) => void;
160 |
161 | /**
162 | * Function fired when the mouse enter on a day.
163 | */
164 | mouseOnDay?: (e: CalendarDayEventObject) => void;
165 |
166 | /**
167 | * Function fired when the mouse leaves a day.
168 | */
169 | mouseOutDay?: (e: CalendarDayEventObject) => void;
170 |
171 | /**
172 | * Function fired when the calendar rendering is ended.
173 | */
174 | renderEnd?: (e: CalendarRenderEndEventObject) => void;
175 |
176 | /**
177 | * Function fired when a date range is selected.
178 | */
179 | selectRange?: (e: CalendarRangeEventObject) => void;
180 |
181 | /**
182 | * Function fired when the visible year of the calendar is changed.
183 | * This function will be fired only if the calendar is used in a full year mode. Otherwise, use `periodChanged` event.
184 | */
185 | yearChanged?: (e: CalendarYearChangedEventObject) => void;
186 |
187 | /**
188 | * Function fired when the visible period of the calendar is changed.
189 | */
190 | periodChanged?: (e: CalendarPeriodChangedEventObject) => void;
191 | }
--------------------------------------------------------------------------------
/src/ts/interfaces/CalendarPeriodChangedEventObject.ts:
--------------------------------------------------------------------------------
1 | export default interface CalendarPeriodChangedEventObject {
2 | /**
3 | * The beginning of the new period.
4 | */
5 | startDate: Date;
6 |
7 | /**
8 | * The end of the new period.
9 | */
10 | endDate: Date;
11 |
12 | /**
13 | * Indicates whether the automatic render after period changing must be prevented.
14 | */
15 | preventRendering: boolean;
16 | }
--------------------------------------------------------------------------------
/src/ts/interfaces/CalendarRangeEventObject.ts:
--------------------------------------------------------------------------------
1 | export default interface CalendarRangeEventObject {
2 | /**
3 | * The beginning of the selected range.
4 | */
5 | startDate: Date;
6 |
7 | /**
8 | * The end of the selected range.
9 | */
10 | endDate: Date;
11 | }
--------------------------------------------------------------------------------
/src/ts/interfaces/CalendarRenderEndEventObject.ts:
--------------------------------------------------------------------------------
1 | export default interface CalendarRenderEndEventObject {
2 | /**
3 | * The rendered year.
4 | */
5 | currentYear: number;
6 | }
--------------------------------------------------------------------------------
/src/ts/interfaces/CalendarYearChangedEventObject.ts:
--------------------------------------------------------------------------------
1 | export default interface CalendarYearChangedEventObject {
2 | /**
3 | * The new year.
4 | */
5 | currentYear: number;
6 |
7 | /**
8 | * Indicates whether the automatic render after year changing must be prevented.
9 | */
10 | preventRendering: boolean;
11 | }
--------------------------------------------------------------------------------
/src/ts/js-year-calendar.ts:
--------------------------------------------------------------------------------
1 | /* =========================================================
2 | * JS year calendar v1.0.0
3 | * Repo: https://github.com/year-calendar/js-year-calendar
4 | * =========================================================
5 | * Created by Paul David-Sivelle
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | * ========================================================= */
19 |
20 | import CalendarContextMenuItem from './interfaces/CalendarContextMenuItem';
21 | import CalendarDataSourceElement from './interfaces/CalendarDataSourceElement';
22 | import CalendarOptions from './interfaces/CalendarOptions';
23 | import CalendarYearChangedEventObject from './interfaces/CalendarYearChangedEventObject';
24 | import CalendarPeriodChangedEventObject from './interfaces/CalendarPeriodChangedEventObject';
25 | import CalendarDayEventObject from './interfaces/CalendarDayEventObject';
26 | import CalendarRenderEndEventObject from './interfaces/CalendarRenderEndEventObject';
27 | import CalendarRangeEventObject from './interfaces/CalendarRangeEventObject';
28 |
29 | // NodeList forEach() polyfill
30 | if (typeof NodeList !== "undefined" && !NodeList.prototype.forEach) {
31 | NodeList.prototype.forEach = function (callback, thisArg) {
32 | thisArg = thisArg || window;
33 | for (var i = 0; i < this.length; i++) {
34 | callback.call(thisArg, this[i], i, this);
35 | }
36 | };
37 | }
38 |
39 | // Element closest() polyfill
40 | if (typeof Element !== "undefined" && !Element.prototype.matches) {
41 | const prototype:any = Element.prototype;
42 | Element.prototype.matches = prototype.msMatchesSelector || prototype.webkitMatchesSelector;
43 | }
44 |
45 | if (typeof Element !== "undefined" && !Element.prototype.closest) {
46 | Element.prototype.closest = function(s) {
47 | var el = this;
48 | if (!document.documentElement.contains(el)) return null;
49 | do {
50 | if (el.matches(s)) return el;
51 | el = el.parentElement || el.parentNode;
52 | } while (el !== null && el.nodeType == 1);
53 | return null;
54 | };
55 | }
56 |
57 | /**
58 | * Calendar instance.
59 | */
60 | export default class Calendar {
61 | protected element: HTMLElement;
62 | protected options: CalendarOptions;
63 | protected _startDate: Date;
64 | protected _dataSource: T[];
65 | protected _mouseDown: boolean;
66 | protected _rangeStart: Date;
67 | protected _rangeEnd: Date;
68 | protected _responsiveInterval: any;
69 | protected _nbCols: number;
70 |
71 | protected static locales = {
72 | en: {
73 | days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
74 | daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
75 | daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"],
76 | months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
77 | monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
78 | weekShort: 'W',
79 | weekStart:0
80 | }
81 | };
82 |
83 | protected static colors = ['#2C8FC9', '#9CB703', '#F5BB00', '#FF4A32', '#B56CE2', '#45A597'];
84 |
85 | /**
86 | * Fired when a day is clicked.
87 | * @event
88 | * @example
89 | * ```
90 | *
91 | * document.querySelector('.calendar').addEventListener('clickDay', function(e) {
92 | * console.log("Click on day: " + e.date + " (" + e.events.length + " events)");
93 | * })
94 | * ```
95 | */
96 | public clickDay: CalendarDayEventObject;
97 |
98 | /**
99 | * Fired when a day is right clicked.
100 | * @event
101 | * @example
102 | * ```
103 | *
104 | * document.querySelector('.calendar').addEventListener('clickDay', function(e) {
105 | * console.log("Right click on day: " + e.date + " (" + e.events.length + " events)");
106 | * })
107 | * ```
108 | */
109 | public dayContextMenu: CalendarDayEventObject;
110 |
111 | /**
112 | * Fired when the mouse enter in a day.
113 | * @event
114 | * @example
115 | * ```
116 | *
117 | * document.querySelector('.calendar').addEventListener('mouseOnDay', function(e) {
118 | * console.log("Mouse enter in a day: " + e.date + " (" + e.events.length + " events)");
119 | * })
120 | * ```
121 | */
122 | public mouseOnDay: CalendarDayEventObject;
123 |
124 | /**
125 | * Fired when the mouse leave a day.
126 | * @event
127 | * @example
128 | * ```
129 | *
130 | * document.querySelector('.calendar').addEventListener('mouseOutDay', function(e) {
131 | * console.log("Mouse leave a day: " + e.date + " (" + e.events.length + " events)");
132 | * })
133 | * ```
134 | */
135 | public mouseOutDay: CalendarDayEventObject;
136 |
137 | /**
138 | * Fired when the calendar rendering is ended.
139 | * @event
140 | * @example
141 | * ```
142 | *
143 | * document.querySelector('.calendar').addEventListener('renderEnd', function(e) {
144 | * console.log("Render end for year: " + e.currentYear);
145 | * })
146 | * ```
147 | */
148 | public renderEnd: CalendarRenderEndEventObject;
149 |
150 | /**
151 | * Fired when a date range is selected.
152 | *
153 | * Don't forget to enable the `enableRangeSelection` option to be able to use the range selection functionality.
154 | * @event
155 | * @example
156 | * ```
157 | *
158 | * document.querySelector('.calendar').addEventListener('selectRange', function(e) {
159 | * console.log("Select the range: " + e.startDate + " - " + e.endDate);
160 | * })
161 | * ```
162 | */
163 | public selectRange: CalendarRangeEventObject;
164 |
165 | /**
166 | * Triggered after the changing the current year.
167 | * Works only if the calendar is used in a full year mode. Otherwise, use `periodChanged` event.
168 | * @event
169 | * @example
170 | * ```
171 | *
172 | * document.querySelector('.calendar').addEventListener('yearChanged', function(e) {
173 | * console.log("New year selected: " + e.currentYear);
174 | * })
175 | * ```
176 | */
177 | public yearChanged: CalendarYearChangedEventObject;
178 |
179 | /**
180 | * Triggered after the changing the visible period.
181 | * @event
182 | * @example
183 | * ```
184 | *
185 | * document.querySelector('.calendar').addEventListener('periodChanged', function(e) {
186 | * console.log(`New period selected: ${e.startDate} ${e.endDate}`);
187 | * })
188 | * ```
189 | */
190 | public periodChanged: CalendarPeriodChangedEventObject;
191 |
192 | /**
193 | * Create a new calendar.
194 | * @param element The element (or the selector to an element) in which the calendar should be created.
195 | * @param options [Optional] The options used to customize the calendar
196 | */
197 | constructor(element: HTMLElement|string, options: CalendarOptions = null) {
198 | if (element instanceof HTMLElement) {
199 | this.element = element;
200 | }
201 | else if (typeof element === "string") {
202 | this.element = document.querySelector(element);
203 | }
204 | else {
205 | throw new Error("The element parameter should be a DOM node or a selector");
206 | }
207 |
208 | this.element.classList.add('calendar');
209 |
210 | this._initializeEvents(options);
211 | this._initializeOptions(options);
212 |
213 | let startYear = new Date().getFullYear();
214 | let startMonth = 0;
215 |
216 | if (this.options.startDate) {
217 | startYear = this.options.startDate.getFullYear();
218 | startMonth = this.options.startDate.getMonth();
219 | }
220 | else if (this.options.startYear) {
221 | startYear = this.options.startYear;
222 | }
223 |
224 | this.setStartDate(new Date(startYear, startMonth, 1));
225 | }
226 |
227 | protected _initializeOptions(opt: any): void {
228 | if (opt == null) {
229 | opt = {};
230 | }
231 |
232 | this.options = {
233 | startYear: !isNaN(parseInt(opt.startYear)) ? parseInt(opt.startYear) : null,
234 | startDate: opt.startDate instanceof Date ? opt.startDate : null,
235 | numberMonthsDisplayed: !isNaN(parseInt(opt.numberMonthsDisplayed)) && opt.numberMonthsDisplayed > 0 && opt.numberMonthsDisplayed <= 12 ? parseInt(opt.numberMonthsDisplayed) : 12,
236 | minDate: opt.minDate instanceof Date ? opt.minDate : null,
237 | maxDate: opt.maxDate instanceof Date ? opt.maxDate : null,
238 | language: (opt.language != null && Calendar.locales[opt.language] != null) ? opt.language : 'en',
239 | allowOverlap: opt.allowOverlap != null ? opt.allowOverlap : true,
240 | displayWeekNumber: opt.displayWeekNumber != null ? opt.displayWeekNumber : false,
241 | displayDisabledDataSource: opt.displayDisabledDataSource != null ? opt.displayDisabledDataSource : false,
242 | displayHeader: opt.displayHeader != null ? opt.displayHeader : true,
243 | alwaysHalfDay: opt.alwaysHalfDay != null ? opt.alwaysHalfDay : false,
244 | enableRangeSelection: opt.enableRangeSelection != null ? opt.enableRangeSelection : false,
245 | disabledDays: opt.disabledDays instanceof Array ? opt.disabledDays : [],
246 | disabledWeekDays: opt.disabledWeekDays instanceof Array ? opt.disabledWeekDays : [],
247 | hiddenWeekDays: opt.hiddenWeekDays instanceof Array ? opt.hiddenWeekDays : [],
248 | roundRangeLimits: opt.roundRangeLimits != null ? opt.roundRangeLimits : false,
249 | dataSource: opt.dataSource instanceof Array || typeof opt.dataSource === "function" ? opt.dataSource : [],
250 | style: opt.style == 'background' || opt.style == 'border' || opt.style == 'custom' ? opt.style : 'border',
251 | enableContextMenu: opt.enableContextMenu != null ? opt.enableContextMenu : false,
252 | contextMenuItems: opt.contextMenuItems instanceof Array ? opt.contextMenuItems : [],
253 | customDayRenderer : typeof opt.customDayRenderer === "function" ? opt.customDayRenderer : null,
254 | customDataSourceRenderer : typeof opt.customDataSourceRenderer === "function" ? opt.customDataSourceRenderer : null,
255 | weekStart: !isNaN(parseInt(opt.weekStart)) ? parseInt(opt.weekStart) : null,
256 | loadingTemplate: typeof opt.loadingTemplate === "string" || opt.loadingTemplate instanceof HTMLElement ? opt.loadingTemplate : null
257 | };
258 |
259 | if (this.options.dataSource instanceof Array) {
260 | this._dataSource = this.options.dataSource;
261 | this._initializeDatasourceColors();
262 | }
263 | }
264 |
265 | protected _initializeEvents(opt): void {
266 | if (opt == null) {
267 | opt = [];
268 | }
269 |
270 | if (opt.yearChanged) { this.element.addEventListener('yearChanged', opt.yearChanged); }
271 | if (opt.periodChanged) { this.element.addEventListener('periodChanged', opt.periodChanged); }
272 | if (opt.renderEnd) { this.element.addEventListener('renderEnd', opt.renderEnd); }
273 | if (opt.clickDay) { this.element.addEventListener('clickDay', opt.clickDay); }
274 | if (opt.dayContextMenu) { this.element.addEventListener('dayContextMenu', opt.dayContextMenu); }
275 | if (opt.selectRange) { this.element.addEventListener('selectRange', opt.selectRange); }
276 | if (opt.mouseOnDay) { this.element.addEventListener('mouseOnDay', opt.mouseOnDay); }
277 | if (opt.mouseOutDay) { this.element.addEventListener('mouseOutDay', opt.mouseOutDay); }
278 | }
279 |
280 | protected _fetchDataSource(callback: (dataSource: T[]) => void) {
281 | if (typeof this.options.dataSource === "function") {
282 | const getDataSource:any = this.options.dataSource;
283 | const currentPeriod = this.getCurrentPeriod();
284 | const fetchParameters = {
285 | year: currentPeriod.startDate.getFullYear(),
286 | startDate: currentPeriod.startDate,
287 | endDate: currentPeriod.endDate,
288 | };
289 |
290 | if (getDataSource.length == 2) {
291 | // 2 parameters, means callback method
292 | getDataSource(fetchParameters, callback);
293 | }
294 | else {
295 | // 1 parameter, means synchronous or promise method
296 | var result = getDataSource(fetchParameters);
297 |
298 | if (result instanceof Array) {
299 | callback(result);
300 | }
301 | if (result && result.then) {
302 | result.then(callback);
303 | }
304 | }
305 | }
306 | else {
307 | callback(this.options.dataSource);
308 | }
309 | }
310 |
311 | protected _initializeDatasourceColors(): void {
312 | for (var i = 0; i < this._dataSource.length; i++) {
313 | if (this._dataSource[i].color == null) {
314 | this._dataSource[i].color = Calendar.colors[i % Calendar.colors.length];
315 | }
316 | }
317 | }
318 |
319 | /**
320 | * Renders the calendar.
321 | */
322 | public render(isLoading: boolean = false): void {
323 | // Clear the calendar (faster method)
324 | while (this.element.firstChild) {
325 | this.element.removeChild(this.element.firstChild);
326 | }
327 |
328 | if (this.options.displayHeader) {
329 | this._renderHeader();
330 | }
331 |
332 | if (isLoading) {
333 | this._renderLoading();
334 | }
335 | else {
336 | this._renderBody();
337 | this._renderDataSource();
338 |
339 | this._applyEvents();
340 |
341 | // Fade animation
342 | var months = this.element.querySelector('.months-container') as HTMLElement;
343 | months.style.opacity = '0';
344 | months.style.display = 'flex';
345 | months.style.transition = 'opacity 0.5s';
346 | setTimeout(() => {
347 | months.style.opacity = '1';
348 |
349 | setTimeout(() => months.style.transition = '', 500);
350 | }, 0);
351 |
352 | const currentPeriod = this.getCurrentPeriod();
353 | this._triggerEvent('renderEnd', {
354 | currentYear: currentPeriod.startDate.getFullYear(),
355 | startDate: currentPeriod.startDate,
356 | endDate: currentPeriod.endDate
357 | });
358 | }
359 | }
360 |
361 | protected _renderHeader(): void {
362 | var header = document.createElement('div');
363 | header.classList.add('calendar-header');
364 |
365 | var headerTable = document.createElement('table');
366 |
367 | const period = this.getCurrentPeriod();
368 |
369 | // Left arrow
370 | var prevDiv = document.createElement('th');
371 | prevDiv.classList.add('prev');
372 |
373 | if (this.options.minDate != null && this.options.minDate >= period.startDate) {
374 | prevDiv.classList.add('disabled');
375 | }
376 |
377 | var prevIcon = document.createElement('span');
378 | prevIcon.innerHTML = "‹";
379 |
380 | prevDiv.appendChild(prevIcon);
381 |
382 | headerTable.appendChild(prevDiv);
383 |
384 | if (this._isFullYearMode()) {
385 | // Year N-2
386 | var prev2YearDiv = document.createElement('th');
387 | prev2YearDiv.classList.add('year-title');
388 | prev2YearDiv.classList.add('year-neighbor2');
389 | prev2YearDiv.textContent = (this._startDate.getFullYear() - 2).toString();
390 |
391 | if (this.options.minDate != null && this.options.minDate > new Date(this._startDate.getFullYear() - 2, 11, 31)) {
392 | prev2YearDiv.classList.add('disabled');
393 | }
394 |
395 | headerTable.appendChild(prev2YearDiv);
396 |
397 | // Year N-1
398 | var prevYearDiv = document.createElement('th');
399 | prevYearDiv.classList.add('year-title');
400 | prevYearDiv.classList.add('year-neighbor');
401 | prevYearDiv.textContent = (this._startDate.getFullYear() - 1).toString();
402 |
403 | if (this.options.minDate != null && this.options.minDate > new Date(this._startDate.getFullYear() - 1, 11, 31)) {
404 | prevYearDiv.classList.add('disabled');
405 | }
406 |
407 | headerTable.appendChild(prevYearDiv);
408 | }
409 |
410 | // Current year
411 | var yearDiv = document.createElement('th');
412 | yearDiv.classList.add('year-title');
413 |
414 | if (this._isFullYearMode()) {
415 | yearDiv.textContent = this._startDate.getFullYear().toString();
416 | }
417 | else if (this.options.numberMonthsDisplayed == 12) {
418 | yearDiv.textContent = `${period.startDate.getFullYear()} - ${(period.endDate.getFullYear())}`;
419 | }
420 | else if (this.options.numberMonthsDisplayed > 1) {
421 | yearDiv.textContent =
422 | `${Calendar.locales[this.options.language].months[period.startDate.getMonth()]} ${period.startDate.getFullYear()} - ${Calendar.locales[this.options.language].months[period.endDate.getMonth()]} ${period.endDate.getFullYear()}`;
423 | }
424 | else {
425 | yearDiv.textContent = `${Calendar.locales[this.options.language].months[period.startDate.getMonth()]} ${period.startDate.getFullYear()}`;
426 | }
427 |
428 | headerTable.appendChild(yearDiv);
429 |
430 | if (this._isFullYearMode()) {
431 | // Year N+1
432 | var nextYearDiv = document.createElement('th');
433 | nextYearDiv.classList.add('year-title');
434 | nextYearDiv.classList.add('year-neighbor');
435 | nextYearDiv.textContent = (this._startDate.getFullYear() + 1).toString();
436 |
437 | if (this.options.maxDate != null && this.options.maxDate < new Date(this._startDate.getFullYear() + 1, 0, 1)) {
438 | nextYearDiv.classList.add('disabled');
439 | }
440 |
441 | headerTable.appendChild(nextYearDiv);
442 |
443 | // Year N+2
444 | var next2YearDiv = document.createElement('th');
445 | next2YearDiv.classList.add('year-title');
446 | next2YearDiv.classList.add('year-neighbor2');
447 | next2YearDiv.textContent = (this._startDate.getFullYear() + 2).toString();
448 |
449 | if (this.options.maxDate != null && this.options.maxDate < new Date(this._startDate.getFullYear() + 2, 0, 1)) {
450 | next2YearDiv.classList.add('disabled');
451 | }
452 |
453 | headerTable.appendChild(next2YearDiv);
454 | }
455 |
456 | // Right arrow
457 | var nextDiv = document.createElement('th');
458 | nextDiv.classList.add('next');
459 |
460 | if (this.options.maxDate != null && this.options.maxDate <= period.endDate) {
461 | nextDiv.classList.add('disabled');
462 | }
463 |
464 | var nextIcon = document.createElement('span');
465 | nextIcon.innerHTML = "›";
466 |
467 | nextDiv.appendChild(nextIcon);
468 |
469 | headerTable.appendChild(nextDiv);
470 |
471 | header.appendChild(headerTable);
472 |
473 | this.element.appendChild(header);
474 | }
475 |
476 | protected _renderBody(): void {
477 | var monthsDiv = document.createElement('div');
478 | monthsDiv.classList.add('months-container');
479 |
480 | let monthStartDate = new Date(this._startDate.getTime());
481 |
482 | for (var m = 0; m < this.options.numberMonthsDisplayed; m++) {
483 | /* Container */
484 | var monthDiv = document.createElement('div');
485 | monthDiv.classList.add('month-container');
486 | monthDiv.dataset.monthId = m.toString();
487 |
488 | if (this._nbCols) {
489 | monthDiv.classList.add(`month-${this._nbCols}`);
490 | }
491 |
492 | var table = document.createElement('table');
493 | table.classList.add('month');
494 |
495 | /* Month header */
496 | var thead = document.createElement('thead');
497 |
498 | var titleRow = document.createElement('tr');
499 |
500 | var titleCell = document.createElement('th');
501 | titleCell.classList.add('month-title');
502 | titleCell.setAttribute('colspan', this.options.displayWeekNumber ? '8' : '7');
503 | titleCell.textContent = Calendar.locales[this.options.language].months[monthStartDate.getMonth()];
504 |
505 | titleRow.appendChild(titleCell);
506 | thead.appendChild(titleRow);
507 |
508 | var headerRow = document.createElement('tr');
509 |
510 | if (this.options.displayWeekNumber) {
511 | var weekNumberCell = document.createElement('th');
512 | weekNumberCell.classList.add('week-number');
513 | weekNumberCell.textContent = Calendar.locales[this.options.language].weekShort;
514 | headerRow.appendChild(weekNumberCell);
515 | }
516 |
517 | var weekStart = this.getWeekStart();
518 | var d = weekStart;
519 | do
520 | {
521 | var headerCell = document.createElement('th');
522 | headerCell.classList.add('day-header');
523 | headerCell.textContent = Calendar.locales[this.options.language].daysMin[d];
524 |
525 | if (this._isHidden(d)) {
526 | headerCell.classList.add('hidden');
527 | }
528 |
529 | headerRow.appendChild(headerCell);
530 |
531 | d++;
532 | if (d >= 7)
533 | d = 0;
534 | }
535 | while (d != weekStart)
536 |
537 | thead.appendChild(headerRow);
538 | table.appendChild(thead);
539 |
540 | /* Days */
541 | var currentDate = new Date(monthStartDate.getTime());
542 | var lastDate = new Date(monthStartDate.getFullYear(), monthStartDate.getMonth() + 1, 0);
543 |
544 | while (currentDate.getDay() != weekStart)
545 | {
546 | currentDate.setDate(currentDate.getDate() - 1);
547 | }
548 |
549 | while (currentDate <= lastDate)
550 | {
551 | var row = document.createElement('tr');
552 |
553 | if (this.options.displayWeekNumber) {
554 | var weekNumberCell = document.createElement('td');
555 | var currentThursday = new Date(currentDate.getTime()); // Week number is computed based on the thursday
556 | currentThursday.setDate(currentThursday.getDate() - weekStart + 4);
557 | weekNumberCell.classList.add('week-number');
558 | weekNumberCell.textContent = this.getWeekNumber(currentThursday).toString();
559 | row.appendChild(weekNumberCell);
560 | }
561 |
562 | do
563 | {
564 | var cell = document.createElement('td');
565 | cell.classList.add('day');
566 |
567 | if (this._isHidden(currentDate.getDay())) {
568 | cell.classList.add('hidden');
569 | }
570 |
571 | if (currentDate < monthStartDate) {
572 | cell.classList.add('old');
573 | }
574 | else if (currentDate > lastDate) {
575 | cell.classList.add('new');
576 | }
577 | else {
578 | if (this._isDisabled(currentDate)) {
579 | cell.classList.add('disabled');
580 | }
581 |
582 | var cellContent = document.createElement('div');
583 | cellContent.classList.add('day-content');
584 | cellContent.textContent = currentDate.getDate().toString();
585 | cell.appendChild(cellContent);
586 |
587 | if (this.options.customDayRenderer) {
588 | this.options.customDayRenderer(cellContent, currentDate);
589 | }
590 | }
591 |
592 | row.appendChild(cell);
593 |
594 | currentDate.setDate(currentDate.getDate() + 1);
595 | }
596 | while (currentDate.getDay() != weekStart)
597 |
598 | table.appendChild(row);
599 | }
600 |
601 | monthDiv.appendChild(table);
602 |
603 | monthsDiv.appendChild(monthDiv);
604 |
605 | monthStartDate.setMonth(monthStartDate.getMonth() + 1);
606 | }
607 |
608 | this.element.appendChild(monthsDiv);
609 | }
610 |
611 | protected _renderLoading(): void {
612 | var container = document.createElement('div');
613 | container.classList.add('calendar-loading-container');
614 | container.style.minHeight = (this._nbCols * 200) + 'px';
615 |
616 | var loading = document.createElement('div');
617 | loading.classList.add('calendar-loading');
618 |
619 | if (this.options.loadingTemplate) {
620 | if (typeof this.options.loadingTemplate === "string") {
621 | loading.innerHTML = this.options.loadingTemplate;
622 | }
623 | else if (this.options.loadingTemplate instanceof HTMLElement) {
624 | loading.appendChild(this.options.loadingTemplate);
625 | }
626 | }
627 | else {
628 | var spinner = document.createElement('div');
629 | spinner.classList.add('calendar-spinner');
630 |
631 | for (let i = 1; i <= 3; i++) {
632 | var bounce = document.createElement('div');
633 | bounce.classList.add(`bounce${i}`);
634 | spinner.appendChild(bounce);
635 | }
636 |
637 | loading.appendChild(spinner);
638 | }
639 |
640 | container.appendChild(loading);
641 | this.element.appendChild(container);
642 | }
643 |
644 | protected _renderDataSource(): void {
645 | if (this._dataSource != null && this._dataSource.length > 0) {
646 | this.element.querySelectorAll('.month-container').forEach((month: HTMLElement) => {
647 | var monthId = parseInt(month.dataset.monthId);
648 | const currentYear = this._startDate.getFullYear();
649 | const currentMonth = this._startDate.getMonth() + monthId;
650 |
651 | var firstDate = new Date(currentYear, currentMonth, 1);
652 | var lastDate = new Date(currentYear, currentMonth + 1, 1);
653 |
654 | if ((this.options.minDate == null || lastDate > this.options.minDate) && (this.options.maxDate == null || firstDate <= this.options.maxDate))
655 | {
656 | var monthData = [];
657 |
658 | for (var i = 0; i < this._dataSource.length; i++) {
659 | if (!(this._dataSource[i].startDate >= lastDate) || (this._dataSource[i].endDate < firstDate)) {
660 | monthData.push(this._dataSource[i]);
661 | }
662 | }
663 |
664 | if (monthData.length > 0) {
665 | month.querySelectorAll('.day-content').forEach((day: HTMLElement) => {
666 | var currentDate = new Date(currentYear, currentMonth, parseInt(day.textContent));
667 | var nextDate = new Date(currentYear, currentMonth, currentDate.getDate() + 1);
668 |
669 | var dayData = [];
670 |
671 | if ((this.options.minDate == null || currentDate >= this.options.minDate) && (this.options.maxDate == null || currentDate <= this.options.maxDate))
672 | {
673 | for (var i = 0; i < monthData.length; i++) {
674 | if (monthData[i].startDate < nextDate && monthData[i].endDate >= currentDate) {
675 | dayData.push(monthData[i]);
676 | }
677 | }
678 |
679 | if (dayData.length > 0 && (this.options.displayDisabledDataSource || !this._isDisabled(currentDate)))
680 | {
681 | this._renderDataSourceDay(day, currentDate, dayData);
682 | }
683 | }
684 | });
685 | }
686 | }
687 | });
688 | }
689 | }
690 |
691 | protected _renderDataSourceDay(elt: HTMLElement, currentDate: Date, events: T[]): void {
692 | const parent = elt.parentElement;
693 |
694 | switch (this.options.style)
695 | {
696 | case 'border':
697 | var weight = 0;
698 |
699 | if (events.length == 1) {
700 | weight = 4;
701 | }
702 | else if (events.length <= 3) {
703 | weight = 2;
704 | }
705 | else {
706 | parent.style.boxShadow = 'inset 0 -4px 0 0 black';
707 | }
708 |
709 | if (weight > 0)
710 | {
711 | var boxShadow = '';
712 |
713 | for (var i = 0; i < events.length; i++)
714 | {
715 | if (boxShadow != '') {
716 | boxShadow += ",";
717 | }
718 |
719 | boxShadow += `inset 0 -${(i + 1) * weight}px 0 0 ${events[i].color}`;
720 | }
721 |
722 | parent.style.boxShadow = boxShadow;
723 | }
724 | break;
725 |
726 | case 'background':
727 | parent.style.backgroundColor = events[events.length - 1].color;
728 |
729 | var currentTime = currentDate.getTime();
730 |
731 | if (events[events.length - 1].startDate.getTime() == currentTime)
732 | {
733 | parent.classList.add('day-start');
734 |
735 | if (events[events.length - 1].startHalfDay || this.options.alwaysHalfDay) {
736 | parent.classList.add('day-half');
737 |
738 | // Find color for other half
739 | var otherColor = 'transparent';
740 | for (var i = events.length - 2; i >= 0; i--) {
741 | if (events[i].startDate.getTime() != currentTime || (!events[i].startHalfDay && !this.options.alwaysHalfDay)) {
742 | otherColor = events[i].color;
743 | break;
744 | }
745 | }
746 |
747 | parent.style.background = `linear-gradient(-45deg, ${events[events.length - 1].color}, ${events[events.length - 1].color} 49%, ${otherColor} 51%, ${otherColor})`;
748 | }
749 | else if (this.options.roundRangeLimits) {
750 | parent.classList.add('round-left');
751 | }
752 | }
753 | else if (events[events.length - 1].endDate.getTime() == currentTime)
754 | {
755 | parent.classList.add('day-end');
756 |
757 | if (events[events.length - 1].endHalfDay || this.options.alwaysHalfDay) {
758 | parent.classList.add('day-half');
759 |
760 | // Find color for other half
761 | var otherColor = 'transparent';
762 | for (var i = events.length - 2; i >= 0; i--) {
763 | if (events[i].endDate.getTime() != currentTime || (!events[i].endHalfDay && !this.options.alwaysHalfDay)) {
764 | otherColor = events[i].color;
765 | break;
766 | }
767 | }
768 |
769 | parent.style.background = `linear-gradient(135deg, ${events[events.length - 1].color}, ${events[events.length - 1].color} 49%, ${otherColor} 51%, ${otherColor})`;
770 | }
771 | else if (this.options.roundRangeLimits) {
772 | parent.classList.add('round-right');
773 | }
774 | }
775 | break;
776 |
777 | case 'custom':
778 | if (this.options.customDataSourceRenderer) {
779 | this.options.customDataSourceRenderer.call(this, elt, currentDate, events);
780 | }
781 | break;
782 | }
783 | }
784 |
785 | protected _applyEvents(): void {
786 | if (this.options.displayHeader) {
787 | /* Header buttons */
788 | this.element.querySelectorAll('.year-neighbor, .year-neighbor2').forEach(element => {
789 | element.addEventListener('click', e => {
790 | if (!(e.currentTarget as HTMLElement).classList.contains('disabled')) {
791 | this.setYear(parseInt((e.currentTarget as HTMLElement).textContent));
792 | }
793 | });
794 | });
795 |
796 | this.element.querySelector('.calendar-header .prev').addEventListener('click', e => {
797 | if (!(e.currentTarget as HTMLElement).classList.contains('disabled')) {
798 | var months = this.element.querySelector('.months-container') as HTMLElement;
799 |
800 | months.style.transition = 'margin-left 0.1s';
801 | months.style.marginLeft = '100%';
802 | setTimeout(() => {
803 | months.style.visibility = 'hidden';
804 | months.style.transition = '';
805 | months.style.marginLeft = '0';
806 |
807 | setTimeout(() => {
808 | this.setStartDate(new Date(this._startDate.getFullYear(), this._startDate.getMonth() - this.options.numberMonthsDisplayed, 1));
809 | }, 50);
810 | }, 100);
811 | }
812 | });
813 |
814 | this.element.querySelector('.calendar-header .next').addEventListener('click', e => {
815 | if (!(e.currentTarget as HTMLElement).classList.contains('disabled')) {
816 | var months = this.element.querySelector('.months-container') as HTMLElement;
817 |
818 | months.style.transition = 'margin-left 0.1s';
819 | months.style.marginLeft = '-100%';
820 | setTimeout(() => {
821 | months.style.visibility = 'hidden';
822 | months.style.transition = '';
823 | months.style.marginLeft = '0';
824 |
825 | setTimeout(() => {
826 | this.setStartDate(new Date(this._startDate.getFullYear(), this._startDate.getMonth() + this.options.numberMonthsDisplayed, 1));
827 | }, 50);
828 | }, 100);
829 | }
830 | });
831 | }
832 |
833 | var cells = this.element.querySelectorAll('.day:not(.old):not(.new):not(.disabled)');
834 |
835 | cells.forEach(cell => {
836 | /* Click on date */
837 | cell.addEventListener('click', (e: MouseEvent) => {
838 | e.stopPropagation();
839 |
840 | var date = this._getDate(e.currentTarget);
841 | this._triggerEvent('clickDay', {
842 | element: e.currentTarget,
843 | date: date,
844 | events: this.getEvents(date)
845 | });
846 | });
847 |
848 | /* Click right on date */
849 | cell.addEventListener('contextmenu', e => {
850 | if (this.options.enableContextMenu)
851 | {
852 | e.preventDefault();
853 | if (this.options.contextMenuItems.length > 0)
854 | {
855 | this._openContextMenu(e.currentTarget as HTMLElement);
856 | }
857 | }
858 |
859 | var date = this._getDate(e.currentTarget);
860 | this._triggerEvent('dayContextMenu', {
861 | element: e.currentTarget,
862 | date: date,
863 | events: this.getEvents(date)
864 | });
865 | });
866 |
867 | /* Range selection */
868 | if (this.options.enableRangeSelection) {
869 | cell.addEventListener('mousedown', (e: MouseEvent) => {
870 | if (e.which == 1) {
871 | var currentDate = this._getDate(e.currentTarget);
872 |
873 | if (this.options.allowOverlap || this.isThereFreeSlot(currentDate))
874 | {
875 | this._mouseDown = true;
876 | this._rangeStart = this._rangeEnd = currentDate;
877 | this._refreshRange();
878 | }
879 | }
880 | });
881 |
882 | cell.addEventListener('mouseenter', e => {
883 | if (this._mouseDown) {
884 | var currentDate = this._getDate(e.currentTarget);
885 |
886 | if (!this.options.allowOverlap)
887 | {
888 | var newDate = new Date(this._rangeStart.getTime());
889 |
890 | if (newDate < currentDate) {
891 | var nextDate = new Date(newDate.getFullYear(), newDate.getMonth(), newDate.getDate() + 1);
892 | while (newDate < currentDate) {
893 | if (!this.isThereFreeSlot(nextDate, false))
894 | {
895 | break;
896 | }
897 |
898 | newDate.setDate(newDate.getDate() + 1);
899 | nextDate.setDate(nextDate.getDate() + 1);
900 | }
901 | }
902 | else {
903 | var nextDate = new Date(newDate.getFullYear(), newDate.getMonth(), newDate.getDate() - 1);
904 | while (newDate > currentDate) {
905 | if (!this.isThereFreeSlot(nextDate, true))
906 | {
907 | break;
908 | }
909 |
910 | newDate.setDate(newDate.getDate() - 1);
911 | nextDate.setDate(nextDate.getDate() - 1);
912 | }
913 | }
914 |
915 | currentDate = newDate;
916 | }
917 |
918 | var oldValue = this._rangeEnd;
919 | this._rangeEnd = currentDate;
920 |
921 | if (oldValue.getTime() != this._rangeEnd.getTime()) {
922 | this._refreshRange();
923 | }
924 | }
925 | });
926 | }
927 |
928 | /* Hover date */
929 | cell.addEventListener('mouseenter', e => {
930 | if (!this._mouseDown)
931 | {
932 | var date = this._getDate(e.currentTarget);
933 | this._triggerEvent('mouseOnDay', {
934 | element: e.currentTarget,
935 | date: date,
936 | events: this.getEvents(date)
937 | });
938 | }
939 | });
940 |
941 | cell.addEventListener('mouseleave', e => {
942 | var date = this._getDate(e.currentTarget);
943 | this._triggerEvent('mouseOutDay', {
944 | element: e.currentTarget,
945 | date: date,
946 | events: this.getEvents(date)
947 | });
948 | });
949 | });
950 |
951 | if (this.options.enableRangeSelection) {
952 | // Release range selection
953 | window.addEventListener('mouseup', e => {
954 | if (this._mouseDown) {
955 | this._mouseDown = false;
956 | this._refreshRange();
957 |
958 | var minDate = this._rangeStart < this._rangeEnd ? this._rangeStart : this._rangeEnd;
959 | var maxDate = this._rangeEnd > this._rangeStart ? this._rangeEnd : this._rangeStart;
960 |
961 | this._triggerEvent('selectRange', {
962 | startDate: minDate,
963 | endDate: maxDate,
964 | events: this.getEventsOnRange(minDate, new Date(maxDate.getFullYear(), maxDate.getMonth(), maxDate.getDate() + 1))
965 | });
966 | }
967 | });
968 | }
969 |
970 | /* Responsive management */
971 | if (this._responsiveInterval) {
972 | clearInterval(this._responsiveInterval);
973 | this._responsiveInterval = null;
974 | }
975 |
976 | this._computeMonthsSize();
977 | this._responsiveInterval = setInterval(() => this._computeMonthsSize(), 300);
978 | }
979 |
980 | protected _computeMonthsSize(): void {
981 | if (this.element.querySelector('.month') == null) {
982 | return;
983 | }
984 |
985 | var calendarSize = this.element.offsetWidth;
986 | var monthSize = (this.element.querySelector('.month') as HTMLElement).offsetWidth + 10;
987 | this._nbCols = null;
988 |
989 | if (monthSize * 6 < calendarSize && this.options.numberMonthsDisplayed >= 6) {
990 | this._nbCols = 2;
991 | }
992 | else if (monthSize * 4 < calendarSize && this.options.numberMonthsDisplayed >= 4) {
993 | this._nbCols = 3;
994 | }
995 | else if (monthSize * 3 < calendarSize && this.options.numberMonthsDisplayed >= 3) {
996 | this._nbCols = 4;
997 | }
998 | else if (monthSize * 2 < calendarSize && this.options.numberMonthsDisplayed >= 2) {
999 | this._nbCols = 6;
1000 | }
1001 | else {
1002 | this._nbCols = 12;
1003 | }
1004 |
1005 | this.element.querySelectorAll('.month-container').forEach(month => {
1006 | if (!month.classList.contains(`month-${this._nbCols}`)) {
1007 | ['month-2', 'month-3', 'month-4', 'month-6', 'month-12'].forEach(className => {
1008 | month.classList.remove(className);
1009 | });
1010 | month.classList.add(`month-${this._nbCols}`);
1011 | }
1012 | });
1013 | }
1014 |
1015 | protected _refreshRange(): void {
1016 | this.element.querySelectorAll('td.day.range').forEach(day => day.classList.remove('range'));
1017 | this.element.querySelectorAll('td.day.range-start').forEach(day => day.classList.remove('range-start'));
1018 | this.element.querySelectorAll('td.day.range-end').forEach(day => day.classList.remove('range-end'));
1019 |
1020 | if (this._mouseDown) {
1021 | var minDate = this._rangeStart < this._rangeEnd ? this._rangeStart : this._rangeEnd;
1022 | var maxDate = this._rangeEnd > this._rangeStart ? this._rangeEnd : this._rangeStart;
1023 |
1024 | this.element.querySelectorAll('.month-container').forEach((month: HTMLElement) => {
1025 | var monthId = parseInt(month.dataset.monthId);
1026 | const monthStartDate = new Date(this._startDate.getFullYear(), this._startDate.getMonth() + monthId, 1);
1027 | const monthEndDate = new Date(this._startDate.getFullYear(), this._startDate.getMonth() + monthId + 1, 1);
1028 |
1029 | if (minDate.getTime() < monthEndDate.getTime() && maxDate.getTime() >= monthStartDate.getTime()) {
1030 | month.querySelectorAll('td.day:not(.old):not(.new)').forEach(day => {
1031 | var date = this._getDate(day);
1032 | if (date >= minDate && date <= maxDate) {
1033 | day.classList.add('range');
1034 |
1035 | if (date.getTime() == minDate.getTime()) {
1036 | day.classList.add('range-start');
1037 | }
1038 |
1039 | if (date.getTime() == maxDate.getTime()) {
1040 | day.classList.add('range-end');
1041 | }
1042 | }
1043 | });
1044 | }
1045 | });
1046 | }
1047 | }
1048 |
1049 | protected _getElementPosition(element: HTMLElement): {top: number, left: number} {
1050 | let top = 0, left = 0;
1051 |
1052 | do {
1053 | top += element.offsetTop || 0;
1054 | left += element.offsetLeft || 0;
1055 | element = element.offsetParent as HTMLElement;
1056 | } while(element);
1057 |
1058 | return { top, left };
1059 | }
1060 |
1061 | protected _openContextMenu(elt: HTMLElement): void {
1062 | var contextMenu = document.querySelector('.calendar-context-menu') as HTMLElement;
1063 |
1064 | if (contextMenu !== null) {
1065 | contextMenu.style.display = 'none';
1066 |
1067 | // Clear the context menu (faster method)
1068 | while (contextMenu.firstChild) {
1069 | contextMenu.removeChild(contextMenu.firstChild);
1070 | }
1071 | }
1072 | else {
1073 | contextMenu = document.createElement('div');
1074 | contextMenu.classList.add('calendar-context-menu');
1075 | document.body.appendChild(contextMenu);
1076 | }
1077 |
1078 | var date = this._getDate(elt);
1079 | var events = this.getEvents(date);
1080 |
1081 | for (var i = 0; i < events.length; i++) {
1082 | var eventItem = document.createElement('div');
1083 | eventItem.classList.add('item');
1084 | eventItem.style.paddingLeft = '4px';
1085 | eventItem.style.boxShadow = `inset 4px 0 0 0 ${events[i].color}`;
1086 |
1087 | var eventItemContent = document.createElement('div');
1088 | eventItemContent.classList.add('content');
1089 |
1090 | var text = document.createElement('span');
1091 | text.classList.add('text');
1092 | text.textContent = events[i].name;
1093 | eventItemContent.appendChild(text);
1094 |
1095 | var icon = document.createElement('span');
1096 | icon.classList.add('arrow');
1097 | icon.innerHTML = "›";
1098 | eventItemContent.appendChild(icon);
1099 |
1100 | eventItem.appendChild(eventItemContent);
1101 |
1102 | this._renderContextMenuItems(eventItem, this.options.contextMenuItems, events[i]);
1103 |
1104 | contextMenu.appendChild(eventItem);
1105 | }
1106 |
1107 | if (contextMenu.children.length > 0) {
1108 | const position = this._getElementPosition(elt);
1109 | contextMenu.style.left = (position.left + 25) + 'px';
1110 | contextMenu.style.right = '';
1111 | contextMenu.style.top = (position.top + 25) + 'px';
1112 | contextMenu.style.display = 'block';
1113 |
1114 | if (contextMenu.getBoundingClientRect().right > document.body.offsetWidth) {
1115 | contextMenu.style.left = '';
1116 | contextMenu.style.right = '0';
1117 | }
1118 |
1119 | // Launch the position check once the whole context menu tree will be rendered
1120 | setTimeout(() => this._checkContextMenuItemsPosition(), 0);
1121 |
1122 | const closeContextMenu = (event: Event) => {
1123 | if (event.type !== 'click' || !contextMenu.contains((event as MouseEvent).target as Node)) {
1124 | contextMenu.style.display = 'none';
1125 |
1126 | window.removeEventListener('click', closeContextMenu);
1127 | window.removeEventListener('resize', closeContextMenu);
1128 | window.removeEventListener('scroll', closeContextMenu);
1129 | }
1130 | };
1131 |
1132 | window.addEventListener('click', closeContextMenu);
1133 | window.addEventListener('resize', closeContextMenu);
1134 | window.addEventListener('scroll', closeContextMenu);
1135 | }
1136 | }
1137 |
1138 | protected _renderContextMenuItems(parent: HTMLElement, items: CalendarContextMenuItem[], evt: T): void {
1139 | var subMenu = document.createElement('div');
1140 | subMenu.classList.add('submenu');
1141 |
1142 | for (var i = 0; i < items.length; i++) {
1143 | if (items[i].visible === false || (typeof items[i].visible === "function" && !(items[i] as any).visible(evt))) {
1144 | continue;
1145 | }
1146 |
1147 | var menuItem = document.createElement('div');
1148 | menuItem.classList.add('item');
1149 |
1150 | var menuItemContent = document.createElement('div');
1151 | menuItemContent.classList.add('content');
1152 |
1153 | var text = document.createElement('span');
1154 | text.classList.add('text');
1155 | text.textContent = items[i].text;
1156 | menuItemContent.appendChild(text);
1157 |
1158 | if (items[i].click) {
1159 | (function(index) {
1160 | menuItemContent.addEventListener('click', () => {
1161 | (document.querySelector('.calendar-context-menu') as HTMLElement).style.display = 'none';
1162 | items[index].click(evt);
1163 | });
1164 | })(i);
1165 | }
1166 |
1167 | menuItem.appendChild(menuItemContent);
1168 |
1169 | if (items[i].items && items[i].items.length > 0) {
1170 | var icon = document.createElement('span');
1171 | icon.classList.add('arrow');
1172 | icon.innerHTML = "›";
1173 | menuItemContent.appendChild(icon);
1174 |
1175 | this._renderContextMenuItems(menuItem, items[i].items, evt);
1176 | }
1177 |
1178 | subMenu.appendChild(menuItem);
1179 | }
1180 |
1181 | if (subMenu.children.length > 0)
1182 | {
1183 | parent.appendChild(subMenu);
1184 | }
1185 | }
1186 |
1187 | protected _checkContextMenuItemsPosition(): void {
1188 | const menus = document.querySelectorAll('.calendar-context-menu .submenu');
1189 |
1190 | menus.forEach(menu => {
1191 | const htmlMenu = menu as HTMLElement;
1192 | htmlMenu.style.display = 'block';
1193 | htmlMenu.style.visibility = 'hidden';
1194 | });
1195 |
1196 | menus.forEach(menu => {
1197 | const htmlMenu = menu as HTMLElement;
1198 | if (htmlMenu.getBoundingClientRect().right > document.body.offsetWidth) {
1199 | htmlMenu.classList.add('open-left');
1200 | } else {
1201 | htmlMenu.classList.remove('open-left');
1202 | }
1203 | });
1204 |
1205 | menus.forEach(menu => {
1206 | const htmlMenu = menu as HTMLElement;
1207 | htmlMenu.style.display = '';
1208 | htmlMenu.style.visibility = '';
1209 | });
1210 | }
1211 |
1212 | protected _getDate(elt): Date {
1213 | var day = elt.querySelector('.day-content').textContent;
1214 | var monthId = parseInt(elt.closest('.month-container').dataset.monthId);
1215 |
1216 | return new Date(this._startDate.getFullYear(), this._startDate.getMonth() + monthId, day);
1217 | }
1218 |
1219 | protected _triggerEvent(eventName: string, parameters: any) {
1220 | var event:any = null;
1221 |
1222 | if (typeof Event === "function") {
1223 | event = new Event(eventName);
1224 | }
1225 | else {
1226 | event = document.createEvent('Event');
1227 | event.initEvent(eventName, false, false);
1228 | }
1229 |
1230 | event.calendar = this;
1231 |
1232 | for (var i in parameters) {
1233 | event[i] = parameters[i];
1234 | }
1235 |
1236 | this.element.dispatchEvent(event);
1237 |
1238 | return event;
1239 | }
1240 |
1241 | protected _isDisabled(date: Date): boolean {
1242 | if ((this.options.minDate != null && date < this.options.minDate) || (this.options.maxDate != null && date > this.options.maxDate))
1243 | {
1244 | return true;
1245 | }
1246 |
1247 | if (this.options.disabledWeekDays.length > 0) {
1248 | for (var d = 0; d < this.options.disabledWeekDays.length; d++) {
1249 | if (date.getDay() == this.options.disabledWeekDays[d]) {
1250 | return true;
1251 | }
1252 | }
1253 | }
1254 |
1255 | if (this.options.disabledDays.length > 0) {
1256 | for (var d = 0; d < this.options.disabledDays.length; d++) {
1257 | if (date.getTime() == this.options.disabledDays[d].getTime()) {
1258 | return true;
1259 | }
1260 | }
1261 | }
1262 |
1263 | return false;
1264 | }
1265 |
1266 | protected _isHidden(day: number): boolean {
1267 | if (this.options.hiddenWeekDays.length > 0) {
1268 | for (var d = 0; d < this.options.hiddenWeekDays.length; d++) {
1269 | if (day == this.options.hiddenWeekDays[d]) {
1270 | return true;
1271 | }
1272 | }
1273 | }
1274 |
1275 | return false;
1276 | }
1277 |
1278 | protected _isFullYearMode(): boolean {
1279 | return this._startDate.getMonth() == 0 && this.options.numberMonthsDisplayed == 12;
1280 | }
1281 |
1282 | /**
1283 | * Gets the week number for a specified date.
1284 | *
1285 | * @param date The specified date.
1286 | */
1287 | public getWeekNumber(date: Date): number {
1288 | // Algorithm from https://weeknumber.net/how-to/javascript
1289 | var workingDate = new Date(date.getTime());
1290 | workingDate.setHours(0, 0, 0, 0);
1291 | // Thursday in current week decides the year.
1292 | workingDate.setDate(workingDate.getDate() + 3 - (workingDate.getDay() + 6) % 7);
1293 | // January 4 is always in week 1.
1294 | var week1 = new Date(workingDate.getFullYear(), 0, 4);
1295 | // Adjust to Thursday in week 1 and count number of weeks from date to week1.
1296 | return 1 + Math.round(((workingDate.getTime() - week1.getTime()) / 86400000
1297 | - 3 + (week1.getDay() + 6) % 7) / 7);
1298 | }
1299 |
1300 | /**
1301 | * Gets the data source elements for a specified day.
1302 | *
1303 | * @param date The specified day.
1304 | */
1305 | public getEvents(date: Date): T[] {
1306 | return this.getEventsOnRange(date, new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1));
1307 | }
1308 |
1309 | /**
1310 | * Gets the data source elements for a specified range of days.
1311 | *
1312 | * @param startDate The beginning of the day range (inclusive).
1313 | * @param endDate The end of the day range (non inclusive).
1314 | */
1315 | public getEventsOnRange(startDate: Date, endDate: Date): T[] {
1316 | var events = [];
1317 |
1318 | if (this._dataSource && startDate && endDate) {
1319 | for (var i = 0; i < this._dataSource.length; i++) {
1320 | if (this._dataSource[i].startDate < endDate && this._dataSource[i].endDate >= startDate) {
1321 | events.push(this._dataSource[i]);
1322 | }
1323 | }
1324 | }
1325 |
1326 | return events;
1327 | }
1328 |
1329 | /**
1330 | * Check if there is no event on the first part, last part or on the whole specified day.
1331 | *
1332 | * @param date The specified day.
1333 | * @param after Whether to check for a free slot on the first part (if `false`) or the last part (if `true`) of the day. If `null`, this will check on the whole day.
1334 | *
1335 | * Usefull only if using the `alwaysHalfDay` option of the calendar, or the `startHalfDay` or `endHalfDay` option of the datasource.
1336 | */
1337 | public isThereFreeSlot(date: Date, after: Boolean = null): Boolean {
1338 | const events = this.getEvents(date);
1339 |
1340 | if (after === true) {
1341 | return !events.some(evt => (!this.options.alwaysHalfDay && !evt.endHalfDay) || evt.endDate > date);
1342 | }
1343 | else if (after === false) {
1344 | return !events.some(evt => (!this.options.alwaysHalfDay && !evt.startHalfDay) || evt.startDate < date);
1345 | }
1346 | else {
1347 | return this.isThereFreeSlot(date, false) || this.isThereFreeSlot(date, true);
1348 | }
1349 | }
1350 |
1351 | /**
1352 | * Gets the period displayed on the calendar.
1353 | */
1354 | public getCurrentPeriod(): { startDate: Date, endDate: Date } {
1355 | const startDate = new Date(this._startDate.getTime());
1356 | const endDate = new Date(this._startDate.getTime());
1357 | endDate.setMonth(endDate.getMonth() + this.options.numberMonthsDisplayed);
1358 | endDate.setTime(endDate.getTime() - 1);
1359 |
1360 | return { startDate, endDate };
1361 | }
1362 |
1363 | /**
1364 | * Gets the year displayed on the calendar.
1365 | * If the calendar is not used in a full year configuration, this will return the year of the first date displayed in the calendar.
1366 | */
1367 | public getYear(): number | null {
1368 | return this._isFullYearMode() ? this._startDate.getFullYear() : null;
1369 | }
1370 |
1371 | /**
1372 | * Sets the year displayed on the calendar.
1373 | * If the calendar is not used in a full year configuration, this will set the start date to January 1st of the given year.
1374 | *
1375 | * @param year The year to displayed on the calendar.
1376 | */
1377 | public setYear(year: number | string): void {
1378 | var parsedYear = parseInt(year as string);
1379 | if (!isNaN(parsedYear)) {
1380 | this.setStartDate(new Date(parsedYear, 0 , 1));
1381 | }
1382 | }
1383 |
1384 | /**
1385 | * Gets the first date displayed on the calendar.
1386 | */
1387 | public getStartDate(): Date {
1388 | return this._startDate;
1389 | }
1390 |
1391 | /**
1392 | * Sets the first date that should be displayed on the calendar.
1393 | *
1394 | * @param startDate The first date that should be displayed on the calendar.
1395 | */
1396 | public setStartDate(startDate: Date): void {
1397 | if (startDate instanceof Date) {
1398 | this.options.startDate = startDate;
1399 | this._startDate = new Date(startDate.getFullYear(), startDate.getMonth(), 1);
1400 |
1401 | // Clear the calendar (faster method)
1402 | while (this.element.firstChild) {
1403 | this.element.removeChild(this.element.firstChild);
1404 | }
1405 |
1406 | if (this.options.displayHeader) {
1407 | this._renderHeader();
1408 | }
1409 |
1410 | const newPeriod = this.getCurrentPeriod();
1411 | const periodEventResult = this._triggerEvent('periodChanged', { startDate: newPeriod.startDate, endDate: newPeriod.endDate, preventRendering: false });
1412 | let yearEventResult = null;
1413 |
1414 | if (this._isFullYearMode()) {
1415 | yearEventResult = this._triggerEvent('yearChanged', { currentYear: this._startDate.getFullYear(), preventRendering: false });
1416 | }
1417 |
1418 | if (typeof this.options.dataSource === "function") {
1419 | this.render(true);
1420 |
1421 | this._fetchDataSource(dataSource => {
1422 | this._dataSource = dataSource;
1423 | this._initializeDatasourceColors();
1424 | this.render(false);
1425 | })
1426 | }
1427 | else {
1428 | if (!periodEventResult.preventRendering && (!yearEventResult || !yearEventResult.preventRedering)) {
1429 | this.render();
1430 | }
1431 | }
1432 | }
1433 | }
1434 |
1435 | /**
1436 | * Gets the number of months displayed by the calendar.
1437 | */
1438 | public getNumberMonthsDisplayed(): number {
1439 | return this.options.numberMonthsDisplayed;
1440 | }
1441 |
1442 | /**
1443 | * Sets the number of months displayed that should be displayed by the calendar.
1444 | *
1445 | * This method causes a refresh of the calendar.
1446 | *
1447 | * @param numberMonthsDisplayed Number of months that should be displayed by the calendar.
1448 | * @param preventRedering Indicates whether the rendering should be prevented after the property update.
1449 | */
1450 | public setNumberMonthsDisplayed(numberMonthsDisplayed: number | string, preventRendering: boolean = false): void {
1451 | var parsedNumber = parseInt(numberMonthsDisplayed as string);
1452 | if (!isNaN(parsedNumber) && parsedNumber > 0 && parsedNumber <= 12) {
1453 | this.options.numberMonthsDisplayed = parsedNumber;
1454 |
1455 | if (!preventRendering) {
1456 | this.render();
1457 | }
1458 | }
1459 | }
1460 |
1461 | /**
1462 | * Gets the minimum date of the calendar.
1463 | */
1464 | public getMinDate(): Date {
1465 | return this.options.minDate;
1466 | }
1467 |
1468 | /**
1469 | * Sets the minimum date of the calendar.
1470 | *
1471 | * This method causes a refresh of the calendar.
1472 | *
1473 | * @param minDate The minimum date to set.
1474 | * @param preventRedering Indicates whether the rendering should be prevented after the property update.
1475 | */
1476 | public setMinDate(date: Date, preventRendering: boolean = false): void {
1477 | if (date instanceof Date || date === null) {
1478 | this.options.minDate = date;
1479 |
1480 | if (!preventRendering) {
1481 | this.render();
1482 | }
1483 | }
1484 | }
1485 |
1486 | /**
1487 | * Gets the maximum date of the calendar.
1488 | */
1489 | public getMaxDate(): Date {
1490 | return this.options.maxDate;
1491 | }
1492 |
1493 | /**
1494 | * Sets the maximum date of the calendar.
1495 | *
1496 | * This method causes a refresh of the calendar.
1497 | *
1498 | * @param maxDate The maximum date to set.
1499 | * @param preventRedering Indicates whether the rendering should be prevented after the property update.
1500 | */
1501 | public setMaxDate(date: Date, preventRendering: boolean = false): void {
1502 | if (date instanceof Date || date === null) {
1503 | this.options.maxDate = date;
1504 |
1505 | if (!preventRendering) {
1506 | this.render();
1507 | }
1508 | }
1509 | }
1510 |
1511 | /**
1512 | * Gets the current style used for displaying data source.
1513 | */
1514 | public getStyle(): string {
1515 | return this.options.style;
1516 | }
1517 |
1518 | /**
1519 | * Sets the style to use for displaying data source.
1520 | *
1521 | * This method causes a refresh of the calendar.
1522 | *
1523 | * @param style The style to use for displaying data source ("background", "border" or "custom").
1524 | * @param preventRedering Indicates whether the rendering should be prevented after the property update.
1525 | */
1526 | public setStyle(style: string, preventRendering: boolean = false): void {
1527 | this.options.style = style == 'background' || style == 'border' || style == 'custom' ? style : 'border';
1528 |
1529 | if (!preventRendering) {
1530 | this.render();
1531 | }
1532 | }
1533 |
1534 | /**
1535 | * Gets a value indicating whether the user can select a range which overlapping an other element present in the datasource.
1536 | */
1537 | public getAllowOverlap(): boolean {
1538 | return this.options.allowOverlap;
1539 | }
1540 |
1541 | /**
1542 | * Sets a value indicating whether the user can select a range which overlapping an other element present in the datasource.
1543 | *
1544 | * @param allowOverlap Indicates whether the user can select a range which overlapping an other element present in the datasource.
1545 | */
1546 | public setAllowOverlap(allowOverlap: boolean): void {
1547 | this.options.allowOverlap = allowOverlap;
1548 | }
1549 |
1550 | /**
1551 | * Gets a value indicating whether the weeks number are displayed.
1552 | */
1553 | public getDisplayWeekNumber(): boolean {
1554 | return this.options.displayWeekNumber;
1555 | }
1556 |
1557 | /**
1558 | * Sets a value indicating whether the weeks number are displayed.
1559 | *
1560 | * This method causes a refresh of the calendar.
1561 | *
1562 | * @param displayWeekNumber Indicates whether the weeks number are displayed.
1563 | * @param preventRedering Indicates whether the rendering should be prevented after the property update.
1564 | */
1565 | public setDisplayWeekNumber(displayWeekNumber: boolean, preventRendering: boolean = false): void {
1566 | this.options.displayWeekNumber = displayWeekNumber;
1567 |
1568 | if (!preventRendering) {
1569 | this.render();
1570 | }
1571 | }
1572 |
1573 | /**
1574 | * Gets a value indicating whether the calendar header is displayed.
1575 | */
1576 | public getDisplayHeader(): boolean {
1577 | return this.options.displayHeader;
1578 | }
1579 |
1580 | /**
1581 | * Sets a value indicating whether the calendar header is displayed.
1582 | *
1583 | * This method causes a refresh of the calendar.
1584 | *
1585 | * @param displayHeader Indicates whether the calendar header is displayed.
1586 | * @param preventRedering Indicates whether the rendering should be prevented after the property update.
1587 | */
1588 | public setDisplayHeader(displayHeader: boolean, preventRendering: boolean = false): void {
1589 | this.options.displayHeader = displayHeader;
1590 |
1591 | if (!preventRendering) {
1592 | this.render();
1593 | }
1594 | }
1595 |
1596 | /**
1597 | * Gets a value indicating whether the data source must be rendered on disabled days.
1598 | */
1599 | public getDisplayDisabledDataSource(): boolean {
1600 | return this.options.displayDisabledDataSource;
1601 | }
1602 |
1603 | /**
1604 | * Sets a value indicating whether the data source must be rendered on disabled days.
1605 | *
1606 | * This method causes a refresh of the calendar.
1607 | *
1608 | * @param displayDisabledDataSource Indicates whether the data source must be rendered on disabled days.
1609 | * @param preventRedering Indicates whether the rendering should be prevented after the property update.
1610 | */
1611 | public setDisplayDisabledDataSource(displayDisabledDataSource: boolean, preventRendering: boolean = false): void {
1612 | this.options.displayDisabledDataSource = displayDisabledDataSource;
1613 |
1614 | if (!preventRendering) {
1615 | this.render();
1616 | }
1617 | }
1618 |
1619 | /**
1620 | * Gets a value indicating whether the beginning and the end of each range should be displayed as half selected day.
1621 | */
1622 | public getAlwaysHalfDay(): boolean {
1623 | return this.options.alwaysHalfDay;
1624 | }
1625 |
1626 | /**
1627 | * Sets a value indicating whether the beginning and the end of each range should be displayed as half selected day.
1628 | *
1629 | * This method causes a refresh of the calendar.
1630 | *
1631 | * @param alwaysHalfDay Indicates whether the beginning and the end of each range should be displayed as half selected day.
1632 | * @param preventRedering Indicates whether the rendering should be prevented after the property update.
1633 | */
1634 | public setAlwaysHalfDay(alwaysHalfDay: boolean, preventRendering: boolean = false): void {
1635 | this.options.alwaysHalfDay = alwaysHalfDay;
1636 |
1637 | if (!preventRendering) {
1638 | this.render();
1639 | }
1640 | }
1641 |
1642 | /**
1643 | * Gets a value indicating whether the user can make range selection.
1644 | */
1645 | public getEnableRangeSelection(): boolean {
1646 | return this.options.enableRangeSelection;
1647 | }
1648 |
1649 | /**
1650 | * Sets a value indicating whether the user can make range selection.
1651 | *
1652 | * This method causes a refresh of the calendar.
1653 | *
1654 | * @param enableRangeSelection Indicates whether the user can make range selection.
1655 | * @param preventRedering Indicates whether the rendering should be prevented after the property update.
1656 | */
1657 | public setEnableRangeSelection(enableRangeSelection: boolean, preventRendering: boolean = false): void {
1658 | this.options.enableRangeSelection = enableRangeSelection;
1659 |
1660 | if (!preventRendering) {
1661 | this.render();
1662 | }
1663 | }
1664 |
1665 | /**
1666 | * Gets the disabled days.
1667 | */
1668 | public getDisabledDays(): Date[] {
1669 | return this.options.disabledDays;
1670 | }
1671 |
1672 | /**
1673 | * Sets the disabled days.
1674 | *
1675 | * This method causes a refresh of the calendar.
1676 | *
1677 | * @param disableDays The disabled days to set.
1678 | * @param preventRedering Indicates whether the rendering should be prevented after the property update.
1679 | */
1680 | public setDisabledDays(disabledDays: Date[], preventRendering: boolean = false): void {
1681 | this.options.disabledDays = disabledDays instanceof Array ? disabledDays : [];
1682 |
1683 | if (!preventRendering) {
1684 | this.render();
1685 | }
1686 | }
1687 |
1688 | /**
1689 | * Gets the disabled days of the week.
1690 | */
1691 | public getDisabledWeekDays(): number[] {
1692 | return this.options.disabledWeekDays;
1693 | }
1694 |
1695 | /**
1696 | * Sets the disabled days of the week.
1697 | *
1698 | * This method causes a refresh of the calendar.
1699 | *
1700 | * @param disabledWeekDays The disabled days of the week to set.
1701 | * @param preventRedering Indicates whether the rendering should be prevented after the property update.
1702 | */
1703 | public setDisabledWeekDays(disabledWeekDays: number[], preventRendering: boolean = false): void {
1704 | this.options.disabledWeekDays = disabledWeekDays instanceof Array ? disabledWeekDays : [];
1705 |
1706 | if (!preventRendering) {
1707 | this.render();
1708 | }
1709 | }
1710 |
1711 | /**
1712 | * Gets the hidden days of the week.
1713 | */
1714 | public getHiddenWeekDays(): number[] {
1715 | return this.options.hiddenWeekDays;
1716 | }
1717 |
1718 | /**
1719 | * Sets the hidden days of the week.
1720 | *
1721 | * This method causes a refresh of the calendar.
1722 | *
1723 | * @param hiddenWeekDays The hidden days of the week to set.
1724 | * @param preventRedering Indicates whether the rendering should be prevented after the property update.
1725 | */
1726 | public setHiddenWeekDays(hiddenWeekDays: number[], preventRendering: boolean = false): void {
1727 | this.options.hiddenWeekDays = hiddenWeekDays instanceof Array ? hiddenWeekDays : [];
1728 |
1729 | if (!preventRendering) {
1730 | this.render();
1731 | }
1732 | }
1733 |
1734 | /**
1735 | * Gets a value indicating whether the beginning and the end of each range should be displayed as rounded cells.
1736 | */
1737 | public getRoundRangeLimits(): boolean {
1738 | return this.options.roundRangeLimits;
1739 | }
1740 |
1741 | /**
1742 | * Sets a value indicating whether the beginning and the end of each range should be displayed as rounded cells.
1743 | *
1744 | * This method causes a refresh of the calendar.
1745 | *
1746 | * @param roundRangeLimits Indicates whether the beginning and the end of each range should be displayed as rounded cells.
1747 | * @param preventRedering Indicates whether the rendering should be prevented after the property update.
1748 | */
1749 | public setRoundRangeLimits(roundRangeLimits: boolean, preventRendering: boolean = false): void {
1750 | this.options.roundRangeLimits = roundRangeLimits;
1751 |
1752 | if (!preventRendering) {
1753 | this.render();
1754 | }
1755 | }
1756 |
1757 | /**
1758 | * Gets a value indicating whether the default context menu must be displayed when right clicking on a day.
1759 | */
1760 | public getEnableContextMenu(): boolean {
1761 | return this.options.enableContextMenu;
1762 | }
1763 |
1764 | /**
1765 | * Sets a value indicating whether the default context menu must be displayed when right clicking on a day.
1766 | *
1767 | * This method causes a refresh of the calendar.
1768 | *
1769 | * @param enableContextMenu Indicates whether the default context menu must be displayed when right clicking on a day.
1770 | * @param preventRedering Indicates whether the rendering should be prevented after the property update.
1771 | */
1772 | public setEnableContextMenu(enableContextMenu: boolean, preventRendering: boolean = false): void {
1773 | this.options.enableContextMenu = enableContextMenu;
1774 |
1775 | if (!preventRendering) {
1776 | this.render();
1777 | }
1778 | }
1779 |
1780 | /**
1781 | * Gets the context menu items.
1782 | */
1783 | public getContextMenuItems(): CalendarContextMenuItem[] {
1784 | return this.options.contextMenuItems;
1785 | }
1786 |
1787 | /**
1788 | * Sets new context menu items.
1789 | *
1790 | * This method causes a refresh of the calendar.
1791 | *
1792 | * @param contextMenuItems The new context menu items.
1793 | * @param preventRedering Indicates whether the rendering should be prevented after the property update.
1794 | */
1795 | public setContextMenuItems(contextMenuItems: CalendarContextMenuItem[], preventRendering: boolean = false): void {
1796 | this.options.contextMenuItems = contextMenuItems instanceof Array ? contextMenuItems : [];
1797 |
1798 | if (!preventRendering) {
1799 | this.render();
1800 | }
1801 | }
1802 |
1803 | /**
1804 | * Gets the custom day renderer.
1805 | */
1806 | public getCustomDayRenderer(): (element: HTMLElement, currentDate: Date) => void {
1807 | return this.options.customDayRenderer;
1808 | }
1809 |
1810 | /**
1811 | * Sets the custom day renderer.
1812 | *
1813 | * This method causes a refresh of the calendar.
1814 | *
1815 | * @param handler The function used to render the days. This function is called during render for each day.
1816 | * @param preventRedering Indicates whether the rendering should be prevented after the property update.
1817 | */
1818 | public setCustomDayRenderer(customDayRenderer: (element: HTMLElement, currentDate: Date) => void, preventRendering: boolean = false): void {
1819 | this.options.customDayRenderer = typeof customDayRenderer === "function" ? customDayRenderer : null;
1820 |
1821 | if (!preventRendering) {
1822 | this.render();
1823 | }
1824 | }
1825 |
1826 | /**
1827 | * Gets the custom data source renderer.
1828 | */
1829 | public getCustomDataSourceRenderer(): (element: HTMLElement, currentDate: Date, events: T[]) => void {
1830 | return this.options.customDataSourceRenderer;
1831 | }
1832 |
1833 | /**
1834 | * Sets the custom data source renderer. Works only with the style set to "custom".
1835 | *
1836 | * This method causes a refresh of the calendar.
1837 | *
1838 | * @param handler The function used to render the data source. This function is called during render for each day containing at least one event.
1839 | * @param preventRedering Indicates whether the rendering should be prevented after the property update.
1840 | */
1841 | public setCustomDataSourceRenderer(customDataSourceRenderer: (element: HTMLElement, currentDate: Date, events: T[]) => void, preventRendering: boolean = false): void {
1842 | this.options.customDataSourceRenderer = typeof customDataSourceRenderer === "function" ? customDataSourceRenderer : null;
1843 |
1844 | if (!preventRendering) {
1845 | this.render();
1846 | }
1847 | }
1848 |
1849 | /**
1850 | * Gets the language used for calendar rendering.
1851 | */
1852 | public getLanguage(): string {
1853 | return this.options.language;
1854 | }
1855 |
1856 | /**
1857 | * Sets the language used for calendar rendering.
1858 | *
1859 | * This method causes a refresh of the calendar.
1860 | *
1861 | * @param language The language to use for calendar redering.
1862 | * @param preventRedering Indicates whether the rendering should be prevented after the property update.
1863 | */
1864 | public setLanguage(language: string, preventRendering: boolean = false): void {
1865 | if (language != null && Calendar.locales[language] != null) {
1866 | this.options.language = language;
1867 |
1868 | if (!preventRendering) {
1869 | this.render();
1870 | }
1871 | }
1872 | }
1873 |
1874 | /**
1875 | * Gets the current data source.
1876 | */
1877 | public getDataSource(): T[] | ((currentYear: number) => T[] | Promise) | ((currentYear: number, done: (result: T[]) => void) => void) {
1878 | return this.options.dataSource;
1879 | }
1880 |
1881 | /**
1882 | * Sets a new data source.
1883 | *
1884 | * This method causes a refresh of the calendar.
1885 | *
1886 | * @param dataSource The new data source.
1887 | * @param preventRedering Indicates whether the rendering should be prevented after the property update.
1888 | */
1889 | public setDataSource(dataSource: T[] | ((currentYear: number) => T[] | Promise) | ((currentYear: number, done: (result: T[]) => void) => void), preventRendering: boolean = false): void {
1890 | this.options.dataSource = dataSource instanceof Array || typeof dataSource === "function" ? dataSource : [];
1891 |
1892 | if (typeof this.options.dataSource === "function") {
1893 | this.render(true);
1894 |
1895 | this._fetchDataSource(dataSource => {
1896 | this._dataSource = dataSource;
1897 | this._initializeDatasourceColors();
1898 | this.render(false);
1899 | });
1900 | }
1901 | else {
1902 | this._dataSource = this.options.dataSource;
1903 | this._initializeDatasourceColors();
1904 | if (!preventRendering) {
1905 | this.render();
1906 | }
1907 | }
1908 | }
1909 |
1910 | /**
1911 | * Gets the starting day of the week.
1912 | */
1913 | public getWeekStart(): number {
1914 | return this.options.weekStart !== null ? this.options.weekStart : Calendar.locales[this.options.language].weekStart;
1915 | }
1916 |
1917 | /**
1918 | * Sets the starting day of the week.
1919 | *
1920 | * This method causes a refresh of the calendar.
1921 | *
1922 | * @param weekStart The starting day of the week. This option overrides the parameter define in the language file.
1923 | * @param preventRedering Indicates whether the rendering should be prevented after the property update.
1924 | */
1925 | public setWeekStart(weekStart: number | string, preventRendering: boolean = false): void {
1926 | this.options.weekStart = !isNaN(parseInt(weekStart as string)) ? parseInt(weekStart as string) : null;
1927 |
1928 | if (!preventRendering) {
1929 | this.render();
1930 | }
1931 | }
1932 |
1933 | /**
1934 | * Gets the loading template.
1935 | */
1936 | public getLoadingTemplate(): string | HTMLElement {
1937 | return this.options.loadingTemplate;
1938 | }
1939 |
1940 | /**
1941 | * Sets the loading template.
1942 | *
1943 | * @param loadingTemplate The loading template.
1944 | */
1945 | public setLoadingTemplate(loadingTemplate: string | HTMLElement): void {
1946 | this.options.loadingTemplate = typeof loadingTemplate === "string" || loadingTemplate instanceof HTMLElement ? loadingTemplate : null;
1947 | }
1948 |
1949 | /**
1950 | *
1951 | * Add a new element to the data source.
1952 | *
1953 | * This method causes a refresh of the calendar.
1954 | *
1955 | * @param element The element to add.
1956 | * @param preventRendering Indicates whether the calendar shouldn't be refreshed once the event added.
1957 | */
1958 | public addEvent(evt: T, preventRendering: boolean = false) {
1959 | this._dataSource.push(evt);
1960 |
1961 | if (!preventRendering) {
1962 | this.render();
1963 | }
1964 | }
1965 | }
1966 |
1967 | declare global {
1968 | interface Window { Calendar: any; }
1969 | }
1970 |
1971 | if (typeof window === "object") {
1972 | window.Calendar = Calendar;
1973 |
1974 | document.addEventListener("DOMContentLoaded", () => {
1975 | document.querySelectorAll('[data-provide="calendar"]').forEach((element: HTMLElement) => new Calendar(element));
1976 | });
1977 | }
1978 |
--------------------------------------------------------------------------------
/tests/actions.js:
--------------------------------------------------------------------------------
1 | require('../dist/js-year-calendar');
2 |
3 | beforeEach(() => {
4 | document.body.innerHTML = '';
5 | });
6 |
7 | const getDay = (month, day) => {
8 | return document.querySelectorAll(`#calendar .month-container:nth-child(${month + 1}) .day:not(.old):not(.new)`)[day - 1];
9 | };
10 |
11 | const currentYear = new Date().getFullYear();
12 |
13 | const triggerEvent = (el, eventToTrigger) => {
14 | var ev = new MouseEvent(eventToTrigger, { which: 1, bubbles: true, cancelable: true });
15 | el.dispatchEvent(ev);
16 | };
17 |
18 | test('click on previous year', done => {
19 | const calendar = new Calendar('#calendar');
20 |
21 | // Previous year
22 | document.querySelectorAll('.year-neighbor')[0].click();
23 | setTimeout(() => {
24 | expect(calendar.getYear()).toEqual(currentYear - 1);
25 | done();
26 | }, 200);
27 | });
28 |
29 | test('click on next year', done => {
30 | const calendar = new Calendar('#calendar');
31 |
32 | // Previous year
33 | document.querySelectorAll('.year-neighbor')[1].click();
34 | setTimeout(() => {
35 | expect(calendar.getYear()).toEqual(currentYear + 1);
36 | done();
37 | }, 200);
38 | });
39 |
40 | test('click on previous 2 year', done => {
41 | const calendar = new Calendar('#calendar');
42 |
43 | // Previous year
44 | document.querySelectorAll('.year-neighbor2')[0].click();
45 | setTimeout(() => {
46 | expect(calendar.getYear()).toEqual(currentYear - 2);
47 | done();
48 | }, 200);
49 | });
50 |
51 | test('click on next 2 year', done => {
52 | const calendar = new Calendar('#calendar');
53 |
54 | // Previous year
55 | document.querySelectorAll('.year-neighbor2')[1].click();
56 | setTimeout(() => {
57 | expect(calendar.getYear()).toEqual(currentYear + 2);
58 | done();
59 | }, 200);
60 | });
61 |
62 |
63 | test('click on previous arrow', done => {
64 | const calendar = new Calendar('#calendar');
65 |
66 | // Previous year
67 | document.querySelector('.prev').click();
68 | setTimeout(() => {
69 | expect(calendar.getYear()).toEqual(currentYear - 1);
70 | done();
71 | }, 200);
72 | });
73 |
74 | test('click on next arrow', done => {
75 | const calendar = new Calendar('#calendar');
76 |
77 | // Previous year
78 | document.querySelector('.next').click();
79 | setTimeout(() => {
80 | expect(calendar.getYear()).toEqual(currentYear + 1);
81 | done();
82 | }, 200);
83 | });
84 |
85 | test('range selection without option', () => {
86 | const calendar = new Calendar('#calendar');
87 |
88 | var begin = getDay(2, 2);
89 | var end = getDay(2, 10);
90 | triggerEvent(begin, 'mousedown');
91 | triggerEvent(end, 'mouseenter');
92 |
93 | // Option enable range selection is not enabled. The range shouldn't exist.
94 | expect(begin.classList).not.toContain('range');
95 | expect(end.classList).not.toContain('range');
96 | });
97 |
98 | test('range selection with option', () => {
99 | const calendar = new Calendar('#calendar', { enableRangeSelection: true });
100 |
101 | var begin = getDay(2, 2);
102 | var end = getDay(2, 10);
103 | triggerEvent(begin, 'mousedown');
104 | triggerEvent(end, 'mouseenter');
105 |
106 | expect(getDay(2, 1).classList).not.toContain('range');
107 | expect(begin.classList).toContain('range');
108 | expect(begin.classList).toContain('range-start');
109 | expect(getDay(2, 5).classList).toContain('range');
110 | expect(end.classList).toContain('range');
111 | expect(end.classList).toContain('range-end');
112 | expect(getDay(2, 11).classList).not.toContain('range');
113 | });
114 |
115 | test('range selection without overlap', () => {
116 | const calendar = new Calendar('#calendar', {
117 | enableRangeSelection: true,
118 | allowOverlap: false,
119 | dataSource: [
120 | { startDate: new Date(currentYear, 2, 6), endDate: new Date(currentYear, 2, 15) }
121 | ]
122 | });
123 |
124 | var begin = getDay(2, 2);
125 | var end = getDay(2, 10);
126 | triggerEvent(begin, 'mousedown');
127 | triggerEvent(end, 'mouseenter');
128 |
129 | expect(getDay(2, 5).classList).toContain('range');
130 | expect(getDay(2, 5).classList).toContain('range-end');
131 | expect(getDay(2, 7).classList).not.toContain('range');
132 | expect(end.classList).not.toContain('range');
133 | });
134 |
135 | test('range selection with overlap', () => {
136 | const calendar = new Calendar('#calendar', {
137 | enableRangeSelection: true,
138 | allowOverlap: true,
139 | dataSource: [
140 | { startDate: new Date(currentYear, 2, 6), endDate: new Date(currentYear, 2, 15)}
141 | ]
142 | });
143 |
144 | var begin = getDay(2, 2);
145 | var end = getDay(2, 10);
146 | triggerEvent(begin, 'mousedown');
147 | triggerEvent(end, 'mouseenter');
148 |
149 | expect(getDay(2, 5).classList).toContain('range');
150 | expect(getDay(2, 5).classList).not.toContain('range-end');
151 | expect(end.classList).toContain('range');
152 | expect(end.classList).toContain('range-end');
153 | });
154 |
155 | test('context menu', () => {
156 | const calendar = new Calendar('#calendar', {
157 | enableContextMenu: false,
158 | contextMenuItems: [
159 | {
160 | text: 'Test 1',
161 | items: [{ text: 'Subtest 1'}]
162 | },
163 | { text: 'Test 2' }
164 | ],
165 | dataSource: [
166 | { name: 'Event 1', startDate: new Date(currentYear, 2, 6), endDate: new Date(currentYear, 2, 15)}
167 | ]
168 | });
169 |
170 | var checkContextMenuVisible = () =>
171 | document.querySelector('.calendar-context-menu')
172 | && document.querySelector('.calendar-context-menu').style.display == 'block';
173 |
174 | // Context menu not enabled
175 | triggerEvent(getDay(2, 10), 'contextmenu');
176 | expect(checkContextMenuVisible()).toBeFalsy();
177 |
178 | calendar.setEnableContextMenu(true);
179 |
180 | // Day with no event
181 | triggerEvent(getDay(2, 2), 'contextmenu');
182 | expect(checkContextMenuVisible()).toBeFalsy();
183 |
184 | // Day with events
185 | triggerEvent(getDay(2, 10), 'contextmenu');
186 | expect(checkContextMenuVisible()).toBeTruthy();
187 | expect(document.querySelector('.calendar-context-menu').children.length).toEqual(1);
188 | expect(document.querySelector('.calendar-context-menu > .item > .content > .text').textContent).toEqual('Event 1');
189 | expect(document.querySelectorAll('.calendar-context-menu > .item > .submenu > .item').length).toEqual(2);
190 | expect(document.querySelector('.calendar-context-menu > .item > .submenu > .item:first-child > .content > .text').textContent).toEqual('Test 1');
191 | expect(document.querySelectorAll('.calendar-context-menu > .item > .submenu > .item:first-child > .submenu > .item').length).toEqual(1);
192 | expect(document.querySelector('.calendar-context-menu > .item > .submenu > .item:first-child > .submenu > .item > .content > .text').textContent).toEqual('Subtest 1');
193 | expect(document.querySelector('.calendar-context-menu > .item > .submenu > .item:nth-child(2) > .content > .text').textContent).toEqual('Test 2');
194 | });
--------------------------------------------------------------------------------
/tests/auto-init.js:
--------------------------------------------------------------------------------
1 | require('../dist/js-year-calendar');
2 |
3 | test('instantiate calendar with selector', () => {
4 | document.body.innerHTML = '';
5 |
6 | // Manually trigger the DOMContentLoaded event
7 | var event = new Event("Event");
8 | event.initEvent("DOMContentLoaded", true, true);
9 | document.dispatchEvent(event);
10 |
11 | expect(document.querySelector('#calendar').children.length).toEqual(2);
12 | });
--------------------------------------------------------------------------------
/tests/events.js:
--------------------------------------------------------------------------------
1 | require('../dist/js-year-calendar');
2 |
3 | beforeEach(() => {
4 | document.body.innerHTML = '';
5 | });
6 |
7 | const getDay = (month, day) => {
8 | return document.querySelectorAll(`#calendar .month-container:nth-child(${month + 1}) .day:not(.old):not(.new)`)[day - 1];
9 | };
10 |
11 | const currentYear = new Date().getFullYear();
12 |
13 | const triggerEvent = (el, eventToTrigger) => {
14 | var ev = new MouseEvent(eventToTrigger, { which: 1, bubbles: true, cancelable: true });
15 | el.dispatchEvent(ev);
16 | };
17 |
18 | const testDayEvent = (eventName, eventToTrigger) => {
19 | const simplifyEventsParams = e => ({ calendar: e.calendar, date: e.date, eltId: e.element.id, events: e.events.map(ev => ev.name) });
20 |
21 | const eventInit = jest.fn(simplifyEventsParams);
22 | const eventAdded = jest.fn(simplifyEventsParams);
23 |
24 | document.querySelector('#calendar').addEventListener(eventName, eventAdded);
25 |
26 | const calendar = new Calendar('#calendar', {
27 | [eventName]: eventInit,
28 | dataSource: [
29 | {
30 | name: "test1",
31 | startDate: new Date(currentYear, 6, 10),
32 | endDate: new Date(currentYear, 6, 20)
33 | },
34 | {
35 | name: "test2",
36 | startDate: new Date(currentYear, 7, 10),
37 | endDate: new Date(currentYear, 7, 20)
38 | }
39 | ]
40 | });
41 |
42 | let dayElt = getDay(6, 8);
43 | dayElt.id = "test1";
44 | triggerEvent(dayElt, eventToTrigger);
45 | expect(eventInit).toHaveNthReturnedWith(1, { calendar, date: new Date(currentYear, 6, 8), eltId: "test1", events: [] });
46 | expect(eventAdded).toHaveNthReturnedWith(1, { calendar,date: new Date(currentYear, 6, 8), eltId: "test1", events: [] });
47 |
48 | dayElt = getDay(6, 12);
49 | dayElt.id = "test2";
50 | triggerEvent(dayElt, eventToTrigger);
51 | expect(eventInit).toHaveNthReturnedWith(2, { calendar, date: new Date(currentYear, 6, 12), eltId: "test2", events: ['test1'] });
52 | expect(eventAdded).toHaveNthReturnedWith(2, { calendar, date: new Date(currentYear, 6, 12), eltId: "test2", events: ['test1'] });
53 | };
54 |
55 | test('click day event', () => {
56 | testDayEvent('clickDay', 'click');
57 | });
58 |
59 | test('day context menu event', () => {
60 | testDayEvent('dayContextMenu', 'contextmenu');
61 | });
62 |
63 | test('mouse on day event', () => {
64 | testDayEvent('mouseOnDay', 'mouseenter');
65 | });
66 |
67 | test('mouse out day event', () => {
68 | testDayEvent('mouseOutDay', 'mouseleave');
69 | });
70 |
71 | test('render end event', () => {
72 | const renderEndInit = jest.fn(e => ({ calendar: e.calendar, currentYear: e.currentYear, startDate: e.startDate, endDate: e.endDate }));
73 | const renderEndAdded = jest.fn(e => ({ calendar: e.calendar, currentYear: e.currentYear, startDate: e.startDate, endDate: e.endDate }));
74 |
75 | document.querySelector('#calendar').addEventListener('renderEnd', renderEndAdded);
76 | const calendar = new Calendar('#calendar', { renderEnd: renderEndInit });
77 |
78 | calendar.setYear(2000);
79 |
80 | let endDate = new Date(currentYear + 1, 0, 1);
81 | endDate.setTime(endDate.getTime() - 1);
82 | expect(renderEndInit).toHaveNthReturnedWith(1, { calendar, currentYear, startDate: new Date(currentYear, 0, 1), endDate: endDate });
83 | expect(renderEndAdded).toHaveNthReturnedWith(1, { calendar, currentYear, startDate: new Date(currentYear, 0, 1), endDate: endDate });
84 |
85 | endDate = new Date(2001, 0, 1);
86 | endDate.setTime(endDate.getTime() - 1);
87 | expect(renderEndInit).toHaveNthReturnedWith(2, { calendar, currentYear: 2000, startDate: new Date(2000, 0, 1), endDate: endDate });
88 | expect(renderEndAdded).toHaveNthReturnedWith(2, { calendar, currentYear: 2000, startDate: new Date(2000, 0, 1), endDate: endDate });
89 | });
90 |
91 |
92 | test('select range event', () => {
93 | const selectRangeInit = jest.fn(e => ({ calendar: e.calendar, startDate: e.startDate, endDate: e.endDate }));
94 | const selectRangeAdded = jest.fn(e => ({ calendar: e.calendar, startDate: e.startDate, endDate: e.endDate }));
95 |
96 | document.querySelector('#calendar').addEventListener('selectRange', selectRangeAdded);
97 | const calendar = new Calendar('#calendar', { enableRangeSelection: true, selectRange: selectRangeInit });
98 |
99 | triggerEvent(getDay(8, 10), "mousedown");
100 | triggerEvent(getDay(9, 20), "mouseenter");
101 | triggerEvent(getDay(9, 20), "mouseup");
102 |
103 | expect(selectRangeInit).toHaveNthReturnedWith(1, { calendar, startDate: new Date(currentYear, 8, 10), endDate: new Date(currentYear, 9, 20) });
104 | expect(selectRangeAdded).toHaveNthReturnedWith(1, { calendar, startDate: new Date(currentYear, 8, 10), endDate: new Date(currentYear, 9, 20) });
105 | });
106 |
107 | test('year changed event', () => {
108 | const yearChangedInit = jest.fn(e => ({ calendar: e.calendar, currentYear: e.currentYear }));
109 | const yearChangedAdded = jest.fn(e => ({ calendar: e.calendar, currentYear: e.currentYear }));
110 |
111 | document.querySelector('#calendar').addEventListener('yearChanged', yearChangedAdded);
112 | const calendar = new Calendar(document.querySelector('#calendar'), { yearChanged: yearChangedInit });
113 |
114 | calendar.setYear(2000);
115 |
116 | expect(yearChangedInit).toHaveNthReturnedWith(1, { calendar, currentYear });
117 | expect(yearChangedAdded).toHaveNthReturnedWith(1, { calendar, currentYear });
118 |
119 | expect(yearChangedInit).toHaveNthReturnedWith(2, { calendar, currentYear: 2000 });
120 | expect(yearChangedAdded).toHaveNthReturnedWith(2, { calendar, currentYear: 2000 });
121 | });
--------------------------------------------------------------------------------
/tests/initialization.js:
--------------------------------------------------------------------------------
1 | require('../dist/js-year-calendar');
2 | require('../locales/js-year-calendar.fr');
3 |
4 | beforeEach(() => {
5 | document.body.innerHTML = '';
6 | });
7 |
8 | const getDay = (month, day) => {
9 | return document.querySelectorAll(`#calendar .month-container:nth-child(${month + 1}) .day:not(.old):not(.new)`)[day - 1];
10 | };
11 |
12 | const currentYear = new Date().getFullYear();
13 |
14 | test('instantiate calendar with element', () => {
15 | const calendar = new Calendar(document.querySelector('#calendar'));
16 |
17 | expect(document.querySelector('#calendar').children.length).toEqual(2);
18 |
19 | // Header
20 | expect(document.querySelector('#calendar .calendar-header')).not.toBeNull();
21 | expect(document.querySelector('#calendar .calendar-header .prev')).not.toBeNull();
22 | expect(document.querySelector('#calendar .calendar-header .next')).not.toBeNull();
23 | expect(document.querySelectorAll('#calendar .year-neighbor').length).toEqual(2);
24 | expect(document.querySelectorAll('#calendar .year-neighbor2').length).toEqual(2);
25 | expect(document.querySelector('#calendar .year-title:not(.year-neighbor):not(.year-neighbor2').textContent).toEqual(currentYear.toString());
26 |
27 | // Body
28 | expect(document.querySelector('#calendar .months-container')).not.toBeNull();
29 | expect(document.querySelector('#calendar .months-container').children.length).toEqual(12);
30 |
31 | const currentDate = new Date();
32 | currentDate.setMonth(0);
33 | currentDate.setDate(1);
34 |
35 | while (currentDate.getFullYear() == currentYear) {
36 | expect(getDay(currentDate.getMonth(), currentDate.getDate()).textContent).toEqual(currentDate.getDate().toString());
37 | currentDate.setDate(currentDate.getDate() + 1);
38 | }
39 | });
40 |
41 | test('instantiate calendar with selector', () => {
42 | const calendar = new Calendar('#calendar');
43 |
44 | expect(document.querySelector('#calendar').children.length).toEqual(2);
45 | });
46 |
47 | test('instantiate calendar with other', () => {
48 | expect(() => new Calendar(null)).toThrow();
49 | });
50 |
51 | test('instantiate calendar with start date', () => {
52 | const calendar = new Calendar('#calendar', { startDate: new Date(2000, 5, 1) });
53 |
54 | expect(document.querySelectorAll('#calendar .year-title').length).toEqual(1);
55 | expect(document.querySelector('#calendar .year-title').textContent).toEqual("2000 - 2001");
56 | expect(document.querySelectorAll('#calendar .month').length).toEqual(12);
57 | });
58 |
59 | test('instantiate calendar with start date and number months displayed', () => {
60 | const calendar = new Calendar('#calendar', { startDate: new Date(2000, 10, 1), numberMonthsDisplayed: 3 });
61 |
62 | expect(document.querySelectorAll('#calendar .year-title').length).toEqual(1);
63 | expect(document.querySelector('#calendar .year-title').textContent).toEqual("November 2000 - January 2001");
64 | expect(document.querySelectorAll('#calendar .month').length).toEqual(3);
65 | });
66 |
67 | test('instantiate calendar with start date and single month displayed', () => {
68 | const calendar = new Calendar('#calendar', { startDate: new Date(2000, 5, 1), numberMonthsDisplayed: 1 });
69 |
70 | expect(document.querySelectorAll('#calendar .year-title').length).toEqual(1);
71 | expect(document.querySelector('#calendar .year-title').textContent).toEqual("June 2000");
72 | expect(document.querySelectorAll('#calendar .month').length).toEqual(1);
73 | });
74 |
75 | test('instantiate calendar with start year', () => {
76 | const calendar = new Calendar('#calendar', { startYear: 2000 });
77 |
78 | expect(document.querySelectorAll('#calendar .year-title').length).toEqual(5);
79 | expect(document.querySelector('#calendar .year-title:not(.year-neighbor):not(.year-neighbor2').textContent).toEqual("2000");
80 | expect(document.querySelectorAll('#calendar .month').length).toEqual(12);
81 | });
82 |
83 | test('instantiate calendar with min date', () => {
84 | const calendar = new Calendar('#calendar', { minDate: new Date(currentYear, 2, 5) });
85 |
86 | expect(Array.from(getDay(2, 4).classList)).toContain('disabled');
87 | expect(Array.from(getDay(2, 5).classList)).not.toContain('disabled');
88 |
89 | calendar.setYear(currentYear - 1);
90 |
91 | expect(Array.from(getDay(0, 1).classList)).toContain('disabled');
92 | expect(Array.from(getDay(11, 31).classList)).toContain('disabled');
93 |
94 | calendar.setYear(currentYear + 1);
95 |
96 | expect(Array.from(getDay(0, 1).classList)).not.toContain('disabled');
97 | expect(Array.from(getDay(11, 31).classList)).not.toContain('disabled');
98 | });
99 |
100 | test('instantiate calendar with max date', () => {
101 | const calendar = new Calendar('#calendar', { maxDate: new Date(currentYear, 7, 18) });
102 |
103 | expect(Array.from(getDay(7, 18).classList)).not.toContain('disabled');
104 | expect(Array.from(getDay(7, 19).classList)).toContain('disabled');
105 |
106 | calendar.setYear(currentYear - 1);
107 |
108 | expect(Array.from(getDay(0, 1).classList)).not.toContain('disabled');
109 | expect(Array.from(getDay(11, 31).classList)).not.toContain('disabled');
110 |
111 | calendar.setYear(currentYear + 1);
112 |
113 | expect(Array.from(getDay(0, 1).classList)).toContain('disabled');
114 | expect(Array.from(getDay(11, 31).classList)).toContain('disabled');
115 | });
116 |
117 | test('instantiate calendar with language', () => {
118 | const calendar = new Calendar('#calendar', { language: 'fr' });
119 |
120 | expect(document.querySelector('.month-container:first-child .month-title').textContent).toEqual("Janvier");
121 | expect(document.querySelectorAll('.month-container:first-child .day-header')[0].textContent).toEqual("L");
122 | });
123 |
124 | test('instantiate calendar with default style', () => {
125 | const calendar = new Calendar('#calendar', {
126 | dataSource: [
127 | {
128 | startDate: new Date(currentYear, 6, 10),
129 | endDate: new Date(currentYear, 6, 20)
130 | },
131 | {
132 | startDate: new Date(currentYear, 6, 19),
133 | endDate: new Date(currentYear, 6, 20)
134 | },
135 | {
136 | startDate: new Date(currentYear, 6, 20),
137 | endDate: new Date(currentYear, 6, 20)
138 | }
139 | ]
140 | });
141 |
142 | expect(getDay(6, 9).style.boxShadow).toBeFalsy();
143 |
144 | for (let i = 10; i <= 18; i++) {
145 | expect(getDay(6, i).style.boxShadow).toBeTruthy();
146 | expect(getDay(6, i).style.boxShadow.split(',').length).toEqual(1);
147 | }
148 |
149 | expect(getDay(6, 19).style.boxShadow).toBeTruthy();
150 | expect(getDay(6, 19).style.boxShadow.split(',').length).toEqual(2);
151 |
152 | expect(getDay(6, 20).style.boxShadow).toBeTruthy();
153 | expect(getDay(6, 20).style.boxShadow.split(',').length).toEqual(3);
154 |
155 | expect(getDay(6, 21).style.boxShadow).toBeFalsy();
156 | });
157 |
158 | test('instantiate calendar with background style', () => {
159 | const calendar = new Calendar('#calendar', {
160 | style: 'background',
161 | dataSource: [
162 | {
163 | startDate: new Date(currentYear, 6, 10),
164 | endDate: new Date(currentYear, 6, 20)
165 | }
166 | ]
167 | });
168 |
169 | expect(getDay(6, 9).style.backgroundColor).toBeFalsy();
170 |
171 | for (let i = 10; i <= 20; i++) {
172 | expect(getDay(6, i).style.backgroundColor).toBeTruthy();
173 | }
174 |
175 | expect(getDay(6, 21).style.backgroundColor).toBeFalsy();
176 | });
177 |
178 | test('instantiate calendar with no header', () => {
179 | const calendar = new Calendar('#calendar', { displayHeader: false });
180 |
181 | expect(document.querySelector('#calendar').children.length).toEqual(1);
182 | expect(Array.from(document.querySelector('#calendar').children[0].classList)).toContain('months-container');
183 | });
184 |
185 | test('instantiate calendar with week numbers', () => {
186 | const calendar = new Calendar('#calendar', { displayWeekNumber: true });
187 |
188 | expect(document.querySelector('.month-container:first-child .week-number').textContent).toEqual("W");
189 |
190 | document.querySelectorAll('.month-container tr:not(:first-child)').forEach(tr => {
191 | expect(tr.children.length).toEqual(8);
192 | })
193 | });
194 |
195 | test('instantiate calendar with round limits', () => {
196 | const calendar = new Calendar('#calendar', {
197 | style: 'background',
198 | roundRangeLimits: true,
199 | dataSource: [
200 | {
201 | startDate: new Date(currentYear, 6, 10),
202 | endDate: new Date(currentYear, 6, 20)
203 | }
204 | ]
205 | });
206 |
207 | expect(Array.from(getDay(6, 10).classList)).toContain("round-left");
208 | expect(Array.from(getDay(6, 20).classList)).toContain("round-right");
209 | });
210 |
211 | test('instantiate calendar with disable days', () => {
212 | const calendar = new Calendar('#calendar', {
213 | disabledDays: [
214 | new Date(currentYear, 4, 1),
215 | new Date(currentYear, 5, 1)
216 | ]
217 | });
218 |
219 | expect(Array.from(getDay(4, 1).classList)).toContain("disabled");
220 | expect(Array.from(getDay(4, 2).classList)).not.toContain("disabled");
221 |
222 | expect(Array.from(getDay(5, 1).classList)).toContain("disabled");
223 | expect(Array.from(getDay(5, 2).classList)).not.toContain("disabled");
224 | });
225 |
226 | test('instantiate calendar with disabled week days', () => {
227 | const calendar = new Calendar('#calendar', {
228 | disabledWeekDays: [1, 3, 5]
229 | });
230 |
231 | const currentDate = new Date();
232 | currentDate.setMonth(0);
233 | currentDate.setDate(1);
234 |
235 | while (currentDate.getFullYear() == currentYear) {
236 | var check = expect(Array.from(getDay(currentDate.getMonth(), currentDate.getDate()).classList));
237 |
238 | if (currentDate.getDay() == 1 || currentDate.getDay() == 3 || currentDate.getDay() == 5) {
239 | check.toContain('disabled');
240 | }
241 | else {
242 | check.not.toContain('disabled');
243 | }
244 |
245 | currentDate.setDate(currentDate.getDate() + 1);
246 | }
247 | });
248 |
249 | test('instantiate calendar with hidden week days', () => {
250 | const calendar = new Calendar('#calendar', {
251 | hiddenWeekDays: [0, 2, 4]
252 | });
253 |
254 | const currentDate = new Date();
255 | currentDate.setMonth(0);
256 | currentDate.setDate(1);
257 |
258 | while (currentDate.getFullYear() == currentYear) {
259 | var check = expect(Array.from(getDay(currentDate.getMonth(), currentDate.getDate()).classList));
260 |
261 | if (currentDate.getDay() == 0 || currentDate.getDay() == 2 || currentDate.getDay() == 4) {
262 | check.toContain('hidden');
263 | }
264 | else {
265 | check.not.toContain('hidden');
266 | }
267 |
268 | currentDate.setDate(currentDate.getDate() + 1);
269 | }
270 | });
271 |
272 | test('instantiate calendar with display disabled data source', () => {
273 | const calendar = new Calendar('#calendar', {
274 | style: 'background',
275 | disabledDays: [new Date(currentYear, 6, 15)],
276 | displayDisabledDataSource: true,
277 | dataSource: [
278 | {
279 | startDate: new Date(currentYear, 6, 10),
280 | endDate: new Date(currentYear, 6, 20)
281 | }
282 | ]
283 | });
284 |
285 | expect(getDay(6, 14).style.backgroundColor).toBeTruthy();
286 | expect(getDay(6, 15).style.backgroundColor).toBeTruthy();
287 |
288 | calendar.setDisplayDisabledDataSource(false);
289 |
290 | expect(getDay(6, 14).style.backgroundColor).toBeTruthy();
291 | expect(getDay(6, 15).style.backgroundColor).toBeFalsy();
292 | });
293 |
294 | test('instantiate calendar with data source function', () => {
295 | const dataSource = jest.fn(period => [
296 | {
297 | startDate: new Date(period.year, 6, period.year - 2000),
298 | endDate: new Date(period.year, 6, period.year - 2000)
299 | }
300 | ]);
301 |
302 | const calendar = new Calendar('#calendar', {
303 | startYear: 2001,
304 | dataSource: dataSource
305 | });
306 |
307 | expect(dataSource).toHaveBeenCalledTimes(1);
308 |
309 | let endDate = new Date(2002, 0, 1);
310 | endDate.setTime(endDate.getTime() - 1);
311 | expect(dataSource).toHaveBeenLastCalledWith({ year: 2001, startDate: new Date(2001, 0, 1), endDate });
312 | expect(getDay(6, 1).style.boxShadow).toBeTruthy();
313 | expect(getDay(6, 2).style.boxShadow).toBeFalsy();
314 |
315 | calendar.setYear(2002);
316 | expect(dataSource).toHaveBeenCalledTimes(2);
317 | endDate.setFullYear(2002);
318 | expect(dataSource).toHaveBeenLastCalledWith({ year: 2002, startDate: new Date(2002, 0, 1), endDate });
319 | expect(getDay(6, 1).style.boxShadow).toBeFalsy();
320 | expect(getDay(6, 2).style.boxShadow).toBeTruthy();
321 | });
322 |
323 | test('instantiate calendar with data source function and custom period', () => {
324 | const dataSource = jest.fn(period => [
325 | {
326 | startDate: new Date(period.startDate.getFullYear(), period.startDate.getMonth(), period.startDate.getMonth()),
327 | endDate: new Date(period.startDate.getFullYear(), period.startDate.getMonth(), period.startDate.getMonth()),
328 | }
329 | ]);
330 |
331 | const calendar = new Calendar('#calendar', {
332 | startDate: new Date(2001, 2, 1),
333 | numberMonthsDisplayed: 2,
334 | dataSource: dataSource
335 | });
336 |
337 | expect(dataSource).toHaveBeenCalledTimes(1);
338 |
339 | let endDate = new Date(2001, 4, 1);
340 | endDate.setTime(endDate.getTime() - 1);
341 | expect(dataSource).toHaveBeenLastCalledWith({ year: 2001, startDate: new Date(2001, 2, 1), endDate });
342 | expect(getDay(0, 2).style.boxShadow).toBeTruthy();
343 | expect(getDay(0, 6).style.boxShadow).toBeFalsy();
344 |
345 | calendar.setStartDate(new Date(2001, 6, 1));
346 | expect(dataSource).toHaveBeenCalledTimes(2);
347 | endDate = new Date(2001, 8, 1);
348 | endDate.setTime(endDate.getTime() - 1);
349 | expect(dataSource).toHaveBeenLastCalledWith({ year: 2001, startDate: new Date(2001, 6, 1), endDate });
350 | expect(getDay(0, 2).style.boxShadow).toBeFalsy();
351 | expect(getDay(0, 6).style.boxShadow).toBeTruthy();
352 | });
353 |
354 | test('instantiate calendar with data source callback function', () => {
355 | const dataSource = jest.fn((period, callback) => {
356 | callback([
357 | {
358 | startDate: new Date(period.year, 6, period.year - 2000),
359 | endDate: new Date(period.year, 6, period.year - 2000)
360 | }
361 | ]);
362 | });
363 |
364 | const calendar = new Calendar('#calendar', {
365 | startYear: 2001,
366 | dataSource: dataSource
367 | });
368 |
369 | expect(dataSource).toHaveBeenCalledTimes(1);
370 | expect(getDay(6, 1).style.boxShadow).toBeTruthy();
371 | expect(getDay(6, 2).style.boxShadow).toBeFalsy();
372 |
373 | calendar.setYear(2002);
374 | expect(dataSource).toHaveBeenCalledTimes(2);
375 | expect(getDay(6, 1).style.boxShadow).toBeFalsy();
376 | expect(getDay(6, 2).style.boxShadow).toBeTruthy();
377 | });
378 |
379 | test('instantiate calendar with data source promise function', done => {
380 | const dataSource = jest.fn(period => new Promise((resolve, reject) => {
381 | resolve([
382 | {
383 | startDate: new Date(period.year, 6, period.year - 2000),
384 | endDate: new Date(period.year, 6, period.year - 2000)
385 | }
386 | ]);
387 | }));
388 |
389 | const calendar = new Calendar('#calendar', {
390 | startYear: 2001,
391 | dataSource: dataSource
392 | });
393 |
394 | expect(dataSource).toHaveBeenCalledTimes(1);
395 | const endDate = new Date(2002, 0, 1);
396 | endDate.setTime(endDate.getTime() - 1);
397 | expect(dataSource).toHaveBeenLastCalledWith({ year: 2001, startDate: new Date(2001, 0, 1), endDate });
398 |
399 | setTimeout(() => {
400 | expect(getDay(6, 1).style.boxShadow).toBeTruthy();
401 | expect(getDay(6, 2).style.boxShadow).toBeFalsy();
402 |
403 | calendar.setYear(2002);
404 | expect(dataSource).toHaveBeenCalledTimes(2);
405 | endDate.setFullYear(2002);
406 | expect(dataSource).toHaveBeenLastCalledWith({ year: 2002, startDate: new Date(2002, 0, 1), endDate });
407 |
408 | setTimeout(() => {
409 | expect(getDay(6, 1).style.boxShadow).toBeFalsy();
410 | expect(getDay(6, 2).style.boxShadow).toBeTruthy();
411 | done();
412 | }, 0);
413 | }, 0);
414 | });
--------------------------------------------------------------------------------
/tests/locales.js:
--------------------------------------------------------------------------------
1 | require('../dist/js-year-calendar');
2 |
3 | // Import all files in the locales folder
4 | require('fs').readdirSync(__dirname + '/../locales/').forEach(function(file) {
5 | require('../locales/' + file);
6 | });
7 |
8 | var languages = Object.keys(Calendar.locales);
9 |
10 | test.each(languages)('check locale %s', language => {
11 | expect(Calendar.locales[language].days.length).toEqual(7);
12 | expect(Calendar.locales[language].daysShort.length).toEqual(7);
13 | expect(Calendar.locales[language].daysMin.length).toEqual(7);
14 | expect(Calendar.locales[language].months.length).toEqual(12);
15 | expect(Calendar.locales[language].monthsShort.length).toEqual(12);
16 | expect(Calendar.locales[language].weekShort.length).toBeGreaterThanOrEqual(1)
17 | expect(Calendar.locales[language].weekShort.length).toBeLessThanOrEqual(2);
18 | expect(Calendar.locales[language].weekStart).toBeGreaterThanOrEqual(0)
19 | expect(Calendar.locales[language].weekStart).toBeLessThanOrEqual(6);
20 | });
--------------------------------------------------------------------------------
/tests/methods.js:
--------------------------------------------------------------------------------
1 | require('../dist/js-year-calendar');
2 | require('../locales/js-year-calendar.fr');
3 |
4 | beforeEach(() => {
5 | document.body.innerHTML = '';
6 | });
7 |
8 | const currentYear = new Date().getFullYear();
9 |
10 | test('get / set allow overlap method', () => {
11 | const calendar = new Calendar('#calendar', { allowOverlap: true });
12 |
13 | expect(calendar.getAllowOverlap()).toEqual(true);
14 |
15 | calendar.setAllowOverlap(false);
16 | expect(calendar.getAllowOverlap()).toEqual(false);
17 |
18 | calendar.setAllowOverlap(true);
19 | expect(calendar.getAllowOverlap()).toEqual(true);
20 | });
21 |
22 | test('get / set always half day method', () => {
23 | const calendar = new Calendar('#calendar', { alwaysHalfDay: true });
24 |
25 | expect(calendar.getAlwaysHalfDay()).toEqual(true);
26 |
27 | calendar.setAlwaysHalfDay(false);
28 | expect(calendar.getAlwaysHalfDay()).toEqual(false);
29 |
30 | calendar.setAlwaysHalfDay(true);
31 | expect(calendar.getAlwaysHalfDay()).toEqual(true);
32 | });
33 |
34 | test('get / set context menu items method', () => {
35 | const items = [{ name: 'test1'}, { name: 'test2' }];
36 |
37 | const calendar = new Calendar('#calendar', { contextMenuItems: items });
38 |
39 | expect(calendar.getContextMenuItems()).toEqual(items);
40 |
41 | calendar.setContextMenuItems([]);
42 | expect(calendar.getContextMenuItems()).toEqual([]);
43 |
44 | calendar.setContextMenuItems(items);
45 | expect(calendar.getContextMenuItems()).toEqual(items);
46 | });
47 |
48 | test('get / set custom datasource renderer method', () => {
49 | const customRenderer = () => {};
50 |
51 | const calendar = new Calendar('#calendar', { customDataSourceRenderer: customRenderer });
52 |
53 | expect(calendar.getCustomDataSourceRenderer()).toEqual(customRenderer);
54 |
55 | calendar.setCustomDataSourceRenderer(null);
56 | expect(calendar.getCustomDataSourceRenderer()).toEqual(null);
57 |
58 | calendar.setCustomDataSourceRenderer(customRenderer);
59 | expect(calendar.getCustomDataSourceRenderer()).toEqual(customRenderer);
60 | });
61 |
62 | test('get / set custom day renderer method', () => {
63 | const customRenderer = () => {};
64 |
65 | const calendar = new Calendar('#calendar', { customDayRenderer: customRenderer });
66 |
67 | expect(calendar.getCustomDayRenderer()).toEqual(customRenderer);
68 |
69 | calendar.setCustomDayRenderer(null);
70 | expect(calendar.getCustomDayRenderer()).toEqual(null);
71 |
72 | calendar.setCustomDayRenderer(customRenderer);
73 | expect(calendar.getCustomDayRenderer()).toEqual(customRenderer);
74 | });
75 |
76 | test('get / set datasource method', () => {
77 | const items = [{ name: 'test1'}, { name: 'test2' }];
78 |
79 | const calendar = new Calendar('#calendar', { dataSource: items });
80 |
81 | expect(calendar.getDataSource()).toEqual(items);
82 |
83 | calendar.setDataSource([]);
84 | expect(calendar.getDataSource()).toEqual([]);
85 |
86 | calendar.setDataSource(items);
87 | expect(calendar.getDataSource()).toEqual(items);
88 |
89 | // Dynamic data source
90 | const dataSource = jest.fn(() => []);
91 | calendar.setDataSource(dataSource);
92 | expect(calendar.getDataSource()).toEqual(dataSource);
93 |
94 | expect(dataSource).toHaveBeenCalledTimes(1);
95 | const endDate = new Date(currentYear + 1, 0, 1);
96 | endDate.setTime(endDate.getTime() - 1);
97 | expect(dataSource).toHaveBeenLastCalledWith({ year: currentYear, startDate: new Date(currentYear, 0, 1), endDate });
98 | });
99 |
100 | test('get / set disabled days method', () => {
101 | const items = [new Date(2000, 1, 2), new Date(2020, 5, 8)];
102 |
103 | const calendar = new Calendar('#calendar', { disabledDays: items });
104 |
105 | expect(calendar.getDisabledDays()).toEqual(items);
106 |
107 | calendar.setDisabledDays([]);
108 | expect(calendar.getDisabledDays()).toEqual([]);
109 |
110 | calendar.setDisabledDays(items);
111 | expect(calendar.getDisabledDays()).toEqual(items);
112 | });
113 |
114 | test('get / set disabled week days method', () => {
115 | const items = [1, 5];
116 |
117 | const calendar = new Calendar('#calendar', { disabledWeekDays: items });
118 |
119 | expect(calendar.getDisabledWeekDays()).toEqual(items);
120 |
121 | calendar.setDisabledWeekDays([]);
122 | expect(calendar.getDisabledWeekDays()).toEqual([]);
123 |
124 | calendar.setDisabledWeekDays(items);
125 | expect(calendar.getDisabledWeekDays()).toEqual(items);
126 | });
127 |
128 | test('get / set display disabled data source method', () => {
129 | const calendar = new Calendar('#calendar', { displayDisabledDataSource: true });
130 |
131 | expect(calendar.getDisplayDisabledDataSource()).toEqual(true);
132 |
133 | calendar.setDisplayDisabledDataSource(false);
134 | expect(calendar.getDisplayDisabledDataSource()).toEqual(false);
135 |
136 | calendar.setDisplayDisabledDataSource(true);
137 | expect(calendar.getDisplayDisabledDataSource()).toEqual(true);
138 | });
139 |
140 | test('get / set display header method', () => {
141 | const calendar = new Calendar('#calendar', { displayHeader: true });
142 |
143 | expect(calendar.getDisplayHeader()).toEqual(true);
144 |
145 | calendar.setDisplayHeader(false);
146 | expect(calendar.getDisplayHeader()).toEqual(false);
147 |
148 | calendar.setDisplayHeader(true);
149 | expect(calendar.getDisplayHeader()).toEqual(true);
150 | });
151 |
152 | test('get / set display week number method', () => {
153 | const calendar = new Calendar('#calendar', { displayWeekNumber: true });
154 |
155 | expect(calendar.getDisplayWeekNumber()).toEqual(true);
156 |
157 | calendar.setDisplayWeekNumber(false);
158 | expect(calendar.getDisplayWeekNumber()).toEqual(false);
159 |
160 | calendar.setDisplayWeekNumber(true);
161 | expect(calendar.getDisplayWeekNumber()).toEqual(true);
162 | });
163 |
164 | test('get / set enable context menu method', () => {
165 | const calendar = new Calendar('#calendar', { enableContextMenu: true });
166 |
167 | expect(calendar.getEnableContextMenu()).toEqual(true);
168 |
169 | calendar.setEnableContextMenu(false);
170 | expect(calendar.getEnableContextMenu()).toEqual(false);
171 |
172 | calendar.setEnableContextMenu(true);
173 | expect(calendar.getEnableContextMenu()).toEqual(true);
174 | });
175 |
176 | test('get / set enable range selection method', () => {
177 | const calendar = new Calendar('#calendar', { enableRangeSelection: true });
178 |
179 | expect(calendar.getEnableRangeSelection()).toEqual(true);
180 |
181 | calendar.setEnableRangeSelection(false);
182 | expect(calendar.getEnableRangeSelection()).toEqual(false);
183 |
184 | calendar.setEnableRangeSelection(true);
185 | expect(calendar.getEnableRangeSelection()).toEqual(true);
186 | });
187 |
188 | test('get events method', () => {
189 | const calendar = new Calendar('#calendar', {
190 | dataSource: [
191 | {
192 | startDate: new Date(currentYear, 6, 10),
193 | endDate: new Date(currentYear, 6, 20)
194 | },
195 | {
196 | startDate: new Date(currentYear, 6, 19),
197 | endDate: new Date(currentYear, 6, 20)
198 | },
199 | {
200 | startDate: new Date(currentYear, 6, 20),
201 | endDate: new Date(currentYear, 6, 20)
202 | }
203 | ]
204 | });
205 |
206 | expect(calendar.getEvents(new Date(currentYear, 6, 9)).length).toEqual(0);
207 | expect(calendar.getEvents(new Date(currentYear, 6, 18)).length).toEqual(1);
208 | expect(calendar.getEvents(new Date(currentYear, 6, 19)).length).toEqual(2);
209 | expect(calendar.getEvents(new Date(currentYear, 6, 20)).length).toEqual(3);
210 | });
211 |
212 | test('get events on range method', () => {
213 | const calendar = new Calendar('#calendar', {
214 | dataSource: [
215 | {
216 | startDate: new Date(currentYear, 6, 10),
217 | endDate: new Date(currentYear, 6, 15)
218 | },
219 | {
220 | startDate: new Date(currentYear, 6, 20),
221 | endDate: new Date(currentYear, 6, 22)
222 | },
223 | {
224 | startDate: new Date(currentYear, 6, 25),
225 | endDate: new Date(currentYear, 6, 27)
226 | }
227 | ]
228 | });
229 |
230 | expect(calendar.getEventsOnRange(new Date(currentYear, 6, 5), new Date(currentYear, 6, 8)).length).toEqual(0);
231 | expect(calendar.getEventsOnRange(new Date(currentYear, 6, 8), new Date(currentYear, 6, 12)).length).toEqual(1);
232 | expect(calendar.getEventsOnRange(new Date(currentYear, 6, 8), new Date(currentYear, 6, 18)).length).toEqual(1);
233 | expect(calendar.getEventsOnRange(new Date(currentYear, 6, 8), new Date(currentYear, 6, 20, 1)).length).toEqual(2);
234 | expect(calendar.getEventsOnRange(new Date(currentYear, 6, 8), new Date(currentYear, 7, 1)).length).toEqual(3);
235 | });
236 |
237 | test('get / set hidden week days method', () => {
238 | const items = [1, 5];
239 |
240 | const calendar = new Calendar('#calendar', { hiddenWeekDays: items });
241 |
242 | expect(calendar.getHiddenWeekDays()).toEqual(items);
243 |
244 | calendar.setHiddenWeekDays([]);
245 | expect(calendar.getHiddenWeekDays()).toEqual([]);
246 |
247 | calendar.setHiddenWeekDays(items);
248 | expect(calendar.getHiddenWeekDays()).toEqual(items);
249 | });
250 |
251 | test('get / set language method', () => {
252 | const calendar = new Calendar('#calendar', { language: 'fr' });
253 |
254 | expect(calendar.getLanguage()).toEqual('fr');
255 |
256 | calendar.setLanguage('en');
257 | expect(calendar.getLanguage()).toEqual('en');
258 |
259 | // Non existent language, should keep english
260 | calendar.setLanguage('zz');
261 | expect(calendar.getLanguage()).toEqual('en');
262 | });
263 |
264 | test('get / set loading template method', () => {
265 | const calendar = new Calendar('#calendar', { loadingTemplate: 'Test' });
266 |
267 | expect(calendar.getLoadingTemplate()).toEqual('Test');
268 |
269 | calendar.setLoadingTemplate(null);
270 | expect(calendar.getLoadingTemplate()).toBeNull;
271 |
272 | calendar.setLoadingTemplate('Test 2');
273 | expect(calendar.getLoadingTemplate()).toEqual('Test 2');
274 | });
275 |
276 | test('get / set max date method', () => {
277 | const date = new Date(2010, 2, 5);
278 | const calendar = new Calendar('#calendar', { maxDate: date });
279 |
280 | expect(calendar.getMaxDate()).toEqual(date);
281 |
282 | calendar.setMaxDate(null);
283 | expect(calendar.getMaxDate()).toEqual(null);
284 |
285 | calendar.setMaxDate(date);
286 | expect(calendar.getMaxDate()).toEqual(date);
287 | });
288 |
289 | test('get / set min date method', () => {
290 | const date = new Date(2010, 2, 5);
291 | const calendar = new Calendar('#calendar', { minDate: date });
292 |
293 | expect(calendar.getMinDate()).toEqual(date);
294 |
295 | calendar.setMinDate(null);
296 | expect(calendar.getMinDate()).toEqual(null);
297 |
298 | calendar.setMinDate(date);
299 | expect(calendar.getMinDate()).toEqual(date);
300 | });
301 |
302 | test('get / set round range limits method', () => {
303 | const calendar = new Calendar('#calendar', { roundRangeLimits: true });
304 |
305 | expect(calendar.getRoundRangeLimits()).toEqual(true);
306 |
307 | calendar.setRoundRangeLimits(false);
308 | expect(calendar.getRoundRangeLimits()).toEqual(false);
309 |
310 | calendar.setRoundRangeLimits(true);
311 | expect(calendar.getRoundRangeLimits()).toEqual(true);
312 | });
313 |
314 | test('get / set style method', () => {
315 | const calendar = new Calendar('#calendar', { style: 'custom' });
316 |
317 | expect(calendar.getStyle()).toEqual('custom');
318 |
319 | calendar.setStyle('background');
320 | expect(calendar.getStyle()).toEqual('background');
321 |
322 | // Invalid style
323 | calendar.setStyle('test');
324 | expect(calendar.getStyle()).toEqual('border');
325 |
326 | // Invalid style
327 | calendar.setStyle(1);
328 | expect(calendar.getStyle()).toEqual('border');
329 | });
330 |
331 | test('get week number method', () => {
332 | const calendar = new Calendar('#calendar');
333 |
334 | expect(calendar.getWeekNumber(new Date(2000, 0, 1))).toEqual(52);
335 | expect(calendar.getWeekNumber(new Date(2000, 0, 5))).toEqual(1);
336 | });
337 |
338 | test('get / set week start method', () => {
339 | const calendar = new Calendar('#calendar', { weekStart: 5 });
340 |
341 | expect(calendar.getWeekStart()).toEqual(5);
342 |
343 | calendar.setWeekStart(2);
344 | expect(calendar.getWeekStart()).toEqual(2);
345 |
346 | calendar.setWeekStart(null);
347 | expect(calendar.getWeekStart()).toEqual(0);
348 |
349 | calendar.setLanguage('fr');
350 | expect(calendar.getWeekStart()).toEqual(1); // By default, it will take the week start of the current locale
351 | });
352 |
353 | test('get / set year method', () => {
354 | const calendar = new Calendar('#calendar', { startYear: 2000 });
355 |
356 | expect(calendar.getYear()).toEqual(2000);
357 |
358 | calendar.setYear(2010);
359 | expect(calendar.getYear()).toEqual(2010);
360 |
361 | calendar.setYear('test');
362 | expect(calendar.getYear()).toEqual(2010);
363 | });
364 |
365 | test('add events method', () => {
366 | const calendar = new Calendar('#calendar', {
367 | dataSource: [
368 | {
369 | startDate: new Date(currentYear, 6, 10),
370 | endDate: new Date(currentYear, 6, 20)
371 | }
372 | ]
373 | });
374 |
375 | calendar.addEvent({ startDate: new Date(currentYear, 7, 1), endDate: new Date(currentYear, 8, 1) });
376 | expect(calendar.getDataSource().length).toEqual(2);
377 | });
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": true,
4 | "lib": ["ES6", "DOM"],
5 | "moduleResolution": "node"
6 | },
7 | "typedocOptions": {
8 | "mode": "file",
9 | "out": "docs",
10 | "excludePrivate": true,
11 | "excludeProtected": true,
12 | "excludeNotExported": true,
13 | "target": "ES6"
14 | }
15 | }
--------------------------------------------------------------------------------