├── .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 | ![](https://year-calendar.github.io/assets/img/calendar.png) 5 | 6 | [![CircleCI](https://img.shields.io/circleci/project/github/year-calendar/js-year-calendar/master.svg)](https://circleci.com/gh/year-calendar/js-year-calendar/tree/master) 7 | [![CodeCov](https://img.shields.io/codecov/c/github/year-calendar/js-year-calendar/master.svg)](https://codecov.io/gh/year-calendar/js-year-calendar) 8 | [![NPM](https://img.shields.io/npm/dw/js-year-calendar.svg)](https://www.npmjs.com/package/js-year-calendar) 9 | 10 | This library is also available for: 11 | 12 | [![React.js](https://year-calendar.github.io/assets/img/react.png)](https://github.com/year-calendar/rc-year-calendar) 13 | [![Vue.js](https://year-calendar.github.io/assets/img/vue.png)](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 | } --------------------------------------------------------------------------------