├── screenshots ├── Desktop user.gif └── Mobile user.gif ├── LICENSE ├── .gitignore ├── package.json ├── dist ├── css │ └── jquery-calendar.min.css └── js │ └── jquery-calendar.min.js ├── src ├── css │ └── jquery-calendar.css └── js │ └── jquery-calendar.js ├── example ├── example.html └── demo.html └── README.md /screenshots/Desktop user.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArrobeFr/jquery-calendar/HEAD/screenshots/Desktop user.gif -------------------------------------------------------------------------------- /screenshots/Mobile user.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArrobeFr/jquery-calendar/HEAD/screenshots/Mobile user.gif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Arrobe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "arrobefr-jquery-calendar", 3 | "version": "1.0.12", 4 | "description": "A responsive jquery calendar scheduler built with bootstrap and moment.js", 5 | "main": "dist/js/jquery-calendar.min.js", 6 | "scripts": { 7 | "css-minify": "cleancss --level 2 --output dist/css/jquery-calendar.min.css src/css/jquery-calendar.css", 8 | "js-minify": "uglifyjs --compress --mangle --toplevel --comments /^@/ --output dist/js/jquery-calendar.min.js src/js/jquery-calendar.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/ArrobeFr/jquery-calendar.git" 13 | }, 14 | "keywords": [ 15 | "jquery-plugin", 16 | "ecosystem:jquery", 17 | "moment.js", 18 | "bootstrap", 19 | "calendar", 20 | "scheduler", 21 | "responsive" 22 | ], 23 | "author": "Arrobe Fr (https://www.arrobe.fr)", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/ArrobeFr/jquery-calendar/issues" 27 | }, 28 | "homepage": "https://github.com/ArrobeFr/jquery-calendar#readme", 29 | "dependencies": { 30 | "bootstrap": "^3.3.7", 31 | "moment": "^2.22.1", 32 | "jquery": "^3.3.1", 33 | "jquery-touchswipe": "^1.6.18" 34 | }, 35 | "devDependencies": { 36 | "clean-css-cli": "^4.1.11", 37 | "uglify-js": "^3.3.26" 38 | }, 39 | "files": [ 40 | "dist/", 41 | "src/", 42 | "README.md", 43 | "LICENSE" 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /dist/css/jquery-calendar.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * @class Calendar ~jquery-calendar plugin~ (https://github.com/ArrobeFr/jquery-calendar) 3 | * @author Developped by Arrobe (https://www.arrobe.fr) 4 | * @license Licensed under MIT (https://github.com/ArrobeFr/jquery-calendar/blob/master/LICENSE) 5 | */.calendar{position:relative}.calendar-events-day>ul,.calendar-events>ul,.calendar-month-day-header>ul,.calendar-month-events-day>ul,.calendar-timeline>ul{list-style-type:none;padding:0}.calendar-timeline>ul>li:after{position:absolute;content:'';border-bottom:1px solid #eaeaea}.calendar-event{overflow:hidden}.calendar-categories{margin-bottom:10px;overflow-x:auto;width:100%;z-index:10}li.calendar-event>a{text-decoration:none}em.event-content,em.event-name{font-style:normal}.calendar .calendar-timeline{display:block;height:100%;left:0;position:absolute;top:0;width:100%}.calendar .calendar-timeline li{position:relative}.calendar .calendar-timeline li::after{background:#eaeaea;bottom:0;height:1px;left:60px;position:absolute;width:calc(100% - 60px)}.calendar .calendar-timeline li:last-of-type::after{display:none}.calendar .calendar-timeline li span{-ms-transform:translateY(-50%);-webkit-transform:translateY(-50%);display:inline-block;transform:translateY(-50%)}.calendar .calendar-events{float:left;margin-left:60px;position:relative;width:calc(100% - 60px);z-index:1}.calendar-events-day,.calendar-month-day-header{overflow:hidden;border:1px solid #eaeaea;float:left;margin-bottom:0}.calendar .calendar-events .calendar-day-header,.calendar-month-day-header>div{border-bottom:1px solid #eaeaea;display:table;padding:0;width:100%}.calendar .calendar-events .calendar-day-header>span{display:table-cell;font-weight:700;line-height:1.2;margin-bottom:0;padding:0 .5em;text-align:center;vertical-align:middle}.calendar-events-day>ul,.calendar-month-day-header>ul{-webkit-overflow-scrolling:touch;display:block;overflow:auto;overflow-x:hidden;padding:0;position:relative}.calendar-events-day>ul::after,.calendar-month-day-header>ul::after{clear:both;color:transparent;display:none;height:100%;opacity:0;width:1px}.calendar .calendar-events .calendar-event{-ms-flex-negative:1;-webkit-transition:opacity .2s,background .2s;border-radius:2px;box-shadow:inset 0 -3px 0 rgba(0,0,0,.2);box-shadow:0 10px 20px rgba(0,0,0,.1),inset 0 -3px 0 rgba(0,0,0,.2);flex-shrink:1;float:left;height:auto;left:-1px;margin-right:0;max-width:none;position:absolute;transition:opacity .2s,background .2s;width:calc(100% + 2px);z-index:3}.calendar-month-event{-ms-flex-negative:1;-webkit-transition:opacity .2s,background .2s;border-radius:2px;box-shadow:inset 0 -3px 0 rgba(0,0,0,.2);flex-shrink:1;float:left;left:-1px;margin-right:0;max-width:none;position:absolute;transition:opacity .2s,background .2s;width:calc(100% + 2px);z-index:3}.calendar .calendar-events .calendar-event:last-of-type{margin-right:0}.calendar .calendar-events .calendar-event a{display:block;height:100%;padding:2px}.calendar-events-day:not(:first-of-type),.calendar-month-day-header:not(:first-of-type){border-left-width:0}.calendar .calendar-events .calendar-event.selected-event{visibility:hidden}.calendar.loading .calendar-events .calendar-event{opacity:0}.calendar .event-date,.calendar .event-name{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;color:#fff;display:block;font-weight:700}.calendar .event-content{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;color:#fff;display:block}.calendar-daynote .event-content,.calendar-daynote .event-date,.calendar-daynote .event-name,.calendar-label-daynote{color:#000}.calendar .event-name{font-size:1.8rem}.calendar .event-date{font-size:1.4rem;line-height:1.2;margin-bottom:.2em;opacity:.7}.calendar-month{font-weight:700;border:1px solid #eaeaea;border-bottom:none}.calendar-month-day-header>div{text-align:center;font-weight:700}.calendar-month-events-day>span{color:#bdbdbd}.daytoweek,.monthtoweek,.weektoday,.weektomonth{cursor:pointer}.daytoweek:hover,.monthtoweek:hover,.weektoday:hover,.weektomonth:hover{background-color:#f5f5f5} -------------------------------------------------------------------------------- /src/css/jquery-calendar.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * @class Calendar ~jquery-calendar plugin~ (https://github.com/ArrobeFr/jquery-calendar) 3 | * @author Developped by Arrobe (https://www.arrobe.fr) 4 | * @license Licensed under MIT (https://github.com/ArrobeFr/jquery-calendar/blob/master/LICENSE) 5 | */ 6 | 7 | .calendar { 8 | position:relative 9 | } 10 | 11 | .calendar-events>ul,.calendar-timeline>ul,.calendar-events-day>ul,.calendar-month-day-header>ul,.calendar-month-events-day>ul { 12 | list-style-type:none; 13 | padding:0 14 | } 15 | 16 | .calendar-timeline>ul>li:after { 17 | position: absolute; 18 | content: ''; 19 | border-bottom: 1px solid rgb(234,234,234); 20 | } 21 | 22 | .calendar-event { 23 | overflow:hidden 24 | } 25 | 26 | .calendar-categories { 27 | margin-bottom:10px; 28 | overflow-x:auto; 29 | width:100%; 30 | z-index:10 31 | } 32 | 33 | li.calendar-event>a { 34 | text-decoration:none 35 | } 36 | 37 | em.event-name,em.event-content { 38 | font-style:normal 39 | } 40 | 41 | .calendar .calendar-timeline { 42 | display:block; 43 | height:100%; 44 | left:0; 45 | position:absolute; 46 | top:0; 47 | width:100% 48 | } 49 | 50 | .calendar .calendar-timeline li { 51 | position:relative 52 | } 53 | 54 | .calendar .calendar-timeline li::after { 55 | background:#EAEAEA; 56 | bottom:0; 57 | height:1px; 58 | left:60px; 59 | position:absolute; 60 | width:calc(100% - 60px) 61 | } 62 | 63 | .calendar .calendar-timeline li:last-of-type::after { 64 | display:none 65 | } 66 | 67 | .calendar .calendar-timeline li span { 68 | -ms-transform:translateY(-50%); 69 | -webkit-transform:translateY(-50%); 70 | display:inline-block; 71 | transform:translateY(-50%) 72 | } 73 | 74 | .calendar .calendar-events { 75 | float:left; 76 | margin-left:60px; 77 | position:relative; 78 | width:calc(100% - 60px); 79 | z-index:1 80 | } 81 | 82 | .calendar-events-day, .calendar-month-day-header { 83 | overflow: hidden; 84 | border:1px solid #EAEAEA; 85 | float:left; 86 | margin-bottom:0 87 | } 88 | 89 | .calendar .calendar-events .calendar-day-header, .calendar-month-day-header>div { 90 | border-bottom:1px solid #EAEAEA; 91 | display:table; 92 | padding:0; 93 | width:100% 94 | } 95 | 96 | .calendar .calendar-events .calendar-day-header > span { 97 | display:table-cell; 98 | font-weight:700; 99 | line-height:1.2; 100 | margin-bottom:0; 101 | padding:0 .5em; 102 | text-align:center; 103 | vertical-align:middle 104 | } 105 | 106 | .calendar-events-day > ul, .calendar-month-day-header > ul { 107 | -webkit-overflow-scrolling:touch; 108 | display:block; 109 | overflow:auto; 110 | overflow-x:hidden; 111 | padding:0; 112 | position:relative 113 | } 114 | 115 | .calendar-events-day > ul::after, .calendar-month-day-header > ul::after { 116 | clear:both; 117 | color:transparent; 118 | display:none; 119 | height:100%; 120 | opacity:0; 121 | width:1px 122 | } 123 | 124 | .calendar .calendar-events .calendar-event { 125 | -ms-flex-negative:1; 126 | -webkit-transition:opacity .2s,background .2s; 127 | border-radius:2px; 128 | box-shadow:inset 0 -3px 0 rgba(0,0,0,0.2); 129 | box-shadow:0 10px 20px rgba(0,0,0,0.1),inset 0 -3px 0 rgba(0,0,0,0.2); 130 | flex-shrink:1; 131 | float:left; 132 | height:auto; 133 | left:-1px; 134 | margin-right:0; 135 | max-width:none; 136 | position:absolute; 137 | transition:opacity .2s,background .2s; 138 | width:calc(100% + 2px); 139 | z-index:3 140 | } 141 | 142 | .calendar-month-event { 143 | -ms-flex-negative:1; 144 | -webkit-transition:opacity .2s,background .2s; 145 | border-radius:2px; 146 | box-shadow:inset 0 -3px 0 rgba(0,0,0,0.2); 147 | flex-shrink:1; 148 | float:left; 149 | left:-1px; 150 | margin-right:0; 151 | max-width:none; 152 | position:absolute; 153 | transition:opacity .2s,background .2s; 154 | width:calc(100% + 2px); 155 | z-index:3 156 | } 157 | 158 | .calendar .calendar-events .calendar-event:last-of-type { 159 | margin-right:0 160 | } 161 | 162 | .calendar .calendar-events .calendar-event a { 163 | display:block; 164 | height:100%; 165 | padding:2px 166 | } 167 | 168 | .calendar-events-day:not(:first-of-type), .calendar-month-day-header:not(:first-of-type) { 169 | border-left-width:0 170 | } 171 | 172 | .calendar .calendar-events .calendar-event.selected-event { 173 | visibility:hidden 174 | } 175 | 176 | .calendar.loading .calendar-events .calendar-event { 177 | opacity:0 178 | } 179 | 180 | .calendar .event-name,.calendar .event-date { 181 | -moz-osx-font-smoothing:grayscale; 182 | -webkit-font-smoothing:antialiased; 183 | color:#fff; 184 | display:block; 185 | font-weight:700 186 | } 187 | 188 | .calendar .event-content { 189 | -moz-osx-font-smoothing:grayscale; 190 | -webkit-font-smoothing:antialiased; 191 | color:#fff; 192 | display:block 193 | } 194 | 195 | .calendar-daynote .event-name,.calendar-daynote .event-date, .calendar-daynote .event-content, .calendar-label-daynote { 196 | color:#000; 197 | } 198 | 199 | .calendar .event-name { 200 | font-size:1.8rem; 201 | } 202 | 203 | .calendar .event-date { 204 | font-size:1.4rem; 205 | line-height:1.2; 206 | margin-bottom:.2em; 207 | opacity:.7 208 | } 209 | 210 | .calendar-month { 211 | font-weight: bold; 212 | border: 1px solid #EAEAEA; 213 | border-bottom: none; 214 | } 215 | 216 | .calendar-month-day-header>div { 217 | text-align: center; 218 | font-weight: bold; 219 | } 220 | 221 | .calendar-month-events-day>span { 222 | color: #BDBDBD ; 223 | } 224 | 225 | .weektoday, .monthtoweek, .daytoweek, .weektomonth { 226 | cursor: pointer; 227 | } 228 | 229 | .weektoday:hover, .monthtoweek:hover, .daytoweek:hover, .weektomonth:hover { 230 | background-color: #F5F5F5; 231 | } 232 | -------------------------------------------------------------------------------- /example/example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Calendar 15 | 16 | 17 | 296 |
297 |
298 |
299 |

Wow ! That calendar works !

300 |
301 |
302 |
303 |
304 | 305 | 306 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jquery-calendar 2 | 3 | A responsive jquery calendar scheduler built with bootstrap and moment.js 4 | 5 | ## Switch to bootstrap 4 6 | 7 | It was released with bootstrap 4 ! [Here it is](https://github.com/ArrobeFr/jquery-calendar-bs4) 8 | 9 | ## Screenshots 10 | 11 | Screenshots are made using the `example/example.html`. There is events on one week only, so some parts of calendar are empty. 12 | 13 | ### Desktop user 14 | 15 | ![screenshots/Desktop user.gif](https://raw.githubusercontent.com/ArrobeFr/jquery-calendar/master/screenshots/Desktop%20user.gif) 16 | 17 | ### Mobile user 18 | 19 | ![screenshots/Mobile user.gif](https://raw.githubusercontent.com/ArrobeFr/jquery-calendar/master/screenshots/Mobile%20user.gif) 20 | 21 | ## Demo 22 | 23 | [Here is a full demo](https://cdn.rawgit.com/ArrobeFr/jquery-calendar/7b9d42aa/example/demo.html) 24 | 25 | ## Installation 26 | 27 | ### Using NPM 28 | 29 | `npm install arrobefr-jquery-calendar` 30 | 31 | ### Using a CDN 32 | 33 | *use of the latest version on cdn.jsdelivr.net* 34 | 35 | ```html 36 | 37 | 38 | ``` 39 | 40 | ## Usage 41 | 42 | ### Simple example 43 | 44 | ```html 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | Calendar 59 | 60 | 61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | 85 | 86 | 87 | ``` 88 | 89 | ## Full documentation 90 | 91 | ### HTML 92 | 93 | Add a div somewhere that is unique, with an id for example 94 | 95 | ```html 96 |
97 | ``` 98 | 99 | ### JavaScript 100 | 101 | #### Configuration 102 | 103 | |Argument|Type|Default value|Link / Description| 104 | |--|--|--|--| 105 | |`locale`|*string*|**'fr'**|[See Moment.js docs](https://momentjs.com/docs/#/i18n/changing-locale/)| 106 | |`enableKeyboard`|*boolean*|**true**|Enables or disables the keyboard shortcuts| 107 | |`defaultView.largeScreen`|*string*|**'week'**|Defines the default view to load for large screen, **value must be 'day', 'week', 'month'**| 108 | |`defaultView.smallScreen`|*string*|**'day'**|Defines the default view to load for small screen, **value must be 'day', 'week', 'month'**| 109 | |`defaultView.smallScreenThreshold`|*integer*|**1000**|Defines the threshold to consider a screen small. The value is in pixels| 110 | |`weekday.timeline.fromHour`|*integer*|**7**|Start hour of timeline| 111 | |`weekday.timeline.toHour`|*integer*|**20**|End hour of timeline| 112 | |`weekday.timeline.intervalMinutes`|*integer*|**60**|The time interval of timeline ; each 5, 15, 30, 60, 120, ... minutes| 113 | |`weekday.timeline.format`|*string*|**'HH:mm'**|The time format in timeline and modal ; [see Moment.js docs](https://momentjs.com/docs/#/parsing/string-format/)| 114 | |`weekday.timeline.heightPx`|*integer*|**50**|The height in pixels of timeline, it must not be under the bootstrap font-size| 115 | |`weekday.timeline.autoResize`|*boolean*|**true**|If true, it resizes the timeline if events are out of interval [fromHour;toHour]. **It will only extend time interval, it will not reduce !**| 116 | |`weekday.dayline.weekdays`|*array*|**[1, 2, 3, 4, 5]**|The days to display ; 0 is first day of week depending of the locale ; [see Moment.js docs](https://momentjs.com/docs/#/get-set/iso-weekday/)| 117 | |`weekday.dayline.format`|*string*|**'dddd DD/MM'**|The time format of days ; [see Moment.js docs](https://momentjs.com/docs/#/parsing/string-format/)| 118 | |`weekday.dayline.heightPx`|*integer*|**30**|The height in pixels of dayline, it must not be under the bootstrap font-size| 119 | |`weekday.dayline.month.format`|*string*|**'MMMM YYYY'**|The time format of month header ; [see Moment.js docs](https://momentjs.com/docs/#/parsing/string-format/)| 120 | |`weekday.dayline.month.heightPx`|*integer*|**30**|The height in pixels of month header| 121 | |`weekday.dayline.month.weekFormat`|*string*|**'w'**|The format of week number ; [see Moment.js docs](https://momentjs.com/docs/#/parsing/string-format/)| 122 | |`unixTimestamp`|*integer*|**moment().format('X')**|Any timestamp in the week to display, defaults to current week| 123 | |`event.hover.delay`|*integer*|**500**|Time to wait hover before display full event| 124 | |`colors.events`|*array*|**[some colors from materialui]**|A set of colors used as background of events in hexadecimal format; example : `['#283593']` ; (Source colors using 800)[https://www.materialui.co/colors]| 125 | |`colors.daynotes`|*array*|**[some colors from materialui]**|A set of colors used as background of day notes in hexadecimal format; example : `['#283593']` ; (Source colors using 200)[https://www.materialui.co/colors]| 126 | |`colors.random`|*boolean*|**true**|Randomize the color order| 127 | |`categories.enable`|*boolean*|**true**|Enable or disable the categories header| 128 | |`categories.hover.delay`|*integer*|**500**|Milliseconds to wait before animation| 129 | |`now.enable`|*boolean*|**false**|Enable or disable a `
` that represents the current time (now)| 130 | |`now.refresh`|*boolean*|**false**|Enable or disable the refresh of this `
`, it follows the time| 131 | |`now.heightPx`|*integer*|**1**|The `
`'s weight| 132 | |`now.style`|*string*|**'solid'**|The `
`'s style, see [CSS border style docs](https://www.w3schools.com/cssref/pr_border-style.asp)| 133 | |`now.color`|*string*|**'#03A9F4'**|The `
`'s color| 134 | |`events`|*array*|**[]**|An array of events object, see the docs of **Events array** below| 135 | |`daynotes`|*array*|**[]**|An array of object, see the docs of **DayNotes array** below| 136 | 137 | #### Events array 138 | 139 | ##### Attributes 140 | 141 | The array of events contains objects that have these attributes : 142 | 143 | |Attribute|Type|Description| 144 | |--|--|--| 145 | |`start`|*integer*|The start timestamp of event| 146 | |`end`|*integer*|The end timestamp of event| 147 | |`title`|*string*|Any text| 148 | |`content`|*string*|HTML content| 149 | |`category`|*string*|**Optionnal** if you want different colors grouped by a category or something else| 150 | 151 | #### DayNotes array 152 | 153 | ##### Attributes 154 | 155 | The array of events contains objects that have these attributes : 156 | 157 | |Attribute|Type|Description| 158 | |--|--|--| 159 | |`time`|*integer*|Any timestamp in the day| 160 | |`title`|*string*|Any text| 161 | |`content`|*string*|HTML content| 162 | |`category`|*string*|**Optionnal** if you want different colors grouped by a category or something else| 163 | 164 | #### Functions 165 | 166 | ##### Example 167 | 168 | ```js 169 | var calendar = $('#calendar').Calendar({...}); 170 | var result = calendar.function(); // It is just an example, replace "function" by one of the list below 171 | ``` 172 | 173 | ##### Functions list 174 | 175 | |Function|Arguments|Return|Note| 176 | |--|--|--|--| 177 | |`init`||*Calendar instance*|**It must be called after any modification to re-draw the calendar**| 178 | |`getEvents`||Array of events objects|*Returns events loaded in this instance of Calendar*| 179 | |`setEvents`|Array of events objects|*Calendar instance*|**It replaces events !**| 180 | |`addEvents`|Array of events objects|*Calendar instance*|*It just adds events (it not replaces events)*| 181 | |`getDaynotes`||Array of day notes objects|*Returns day notes loaded in this instance of Calendar*| 182 | |`setDaynotes`|Array of day notes objects|*Calendar instance*|**It replaces day notes !**| 183 | |`addDaynotes`|Array of day notes objects|*Calendar instance*|*It just adds day notes (it not replaces day notes)*| 184 | |`getInitTime`||String : milliseconds|*It returns the time with string "ms"*| 185 | |`getViewInterval`||Array of 2 integers (unix timestamps)|*It returns the from and to timestamp of current view*| 186 | |`getNextViewInterval`||Array of 2 integers (unix timestamps)|*It returns the from and to timestamp of the next view (if user click or swipe to right)*| 187 | |`getPrevViewInterval`||Array of 2 integers (unix timestamps)|*It returns the from and to timestamp of the previous view (if user click or swipe to left)*| 188 | |`getTimestamp`||Integer : the current unix timestamp viewed|| 189 | |`setTimestamp`|Integer : a unix timestamp|*Calendar instance*|*It not affects the view, you have to call init to display the update*| 190 | |`getView`||String : the current view|*It returns 'day', 'week' or 'month'*| 191 | |`setView`|String : 'day' or 'week' or 'month'|*Calendar instance*|*It not affects the view, you have to call init to display the update*| 192 | |`getEventCategoryColor`|String : any category|String : a hexadecimal color prepended by #|It affects events only. Return example : `'#C62828'`| 193 | |`getEventCategoriesColors`||Array of objects|It affects events only. Return example : `[{category:"Personnal", color: "#FF8F00"}, {category:"Professionnal", color:"#AD1457"}]`| 194 | |`setEventCategoriesColors`|Array of objects|*Calendar instance*|It affects events only. *See example of* `getEventCategoriesColors`| 195 | |`getDaynoteCategoryColor`|String : any category|String : a hexadecimal color prepended by #|It affects day notes only. Return example : `'#EF9A9A'`| 196 | |`getDaynoteCategoriesColors`||Array of objects|It affects day notes only. Return example : `[{category:"Public holiday", color: "#B39DDB"}]`| 197 | |`setDaynoteCategoriesColors`|Array of objects|*Calendar instance*|It affects day notes only. *See example of* `getDaynoteCategoriesColors`| 198 | |`getEventColors`||Array of strings|It affects the events only. It returns an array of hexadecimal colors prepended by a #, example : `["#FF8F00", "#9E9D24", "#EF6C00"]`| 199 | |`setEventColors`|Array of strings|*Calendar instance*|It affects the events only. *See example of* `getEventColors`| 200 | |`getDaynoteColors`||Array of strings|It affects the day notes only. It returns an array of hexadecimal colors prepended by a #, example : `["#FF8F00", "#9E9D24", "#EF6C00"]`| 201 | |`setDaynoteColors`|Array of strings|*Calendar instance*|It affects the day notes only. *See example of* `getEventColors`| 202 | 203 | #### Events 204 | 205 | ##### Example 206 | 207 | ```js 208 | var calendar = $('#calendar').Calendar({...}); 209 | $('#calendar').on('event name', function(event, arg1, arg2, ...){...}); 210 | $('#calendar').unbind('event name').on('event name', function(event, arg1, arg2, ...){...}); 211 | ``` 212 | 213 | ##### Cancel default event action 214 | 215 | Example : deactivate the click on event or day note 216 | 217 | ```js 218 | var calendar = $('#calendar').Calendar({...}); 219 | $('#calendar').unbind('Calendar.event-click'); 220 | ``` 221 | 222 | ##### Events list 223 | 224 | ###### Calendar.init 225 | 226 | - `Calendar.init` 227 | - When 228 | - View changes (day, week or month) 229 | - View moves (left or right) 230 | - Manually called by you 231 | - Arguments 232 | - `event` 233 | - The jQuery event 234 | - `instance` 235 | - The Calendar instance 236 | - `before` 237 | - An array of 2 unix timestamp of the previous view (on left) 238 | - Example on a week `[1526248800, 1526853599]` 239 | - `current` 240 | - An array of 2 unix timestamp of the current view 241 | - Example on a week `[1526853600, 1527458399]` 242 | - `after` 243 | - An array of 2 unix timestamp of the next view (on right) 244 | - Example on a week `[1527458400, 1528063199]` 245 | - Example : 246 | 247 | ```js 248 | var calendar = $('#calendar').Calendar({...}); 249 | $('#calendar').on('Calendar.init', function(event, instance, before, current, after){ 250 | console.log(event); // jQuery event 251 | console.log(instance); // Equals to var calendar above 252 | console.log(before); // Array of the past view interval [unixTimestampFrom, unixTimestampTo] 253 | console.log(current); // Array of the current view interval [unixTimestampFrom, unixTimestampTo] 254 | console.log(after); // Array of the next view interval [unixTimestampFrom, unixTimestampTo] 255 | }); 256 | ``` 257 | 258 | ###### Calendar.daynote-mouseenter and Calendar.event-mouseenter 259 | 260 | - `Calendar.daynote-mouseenter` and `Calendar.event-mouseenter` 261 | - When 262 | - The mouse is hover an event or a day note for a while (see `event.hover.delay` under configuration) 263 | - Default 264 | - Enlarge the event or day note over the others 265 | - Arguments 266 | - `event` 267 | - The jQuery event 268 | - `instance` 269 | - The Calendar instance 270 | - `elem` 271 | - The jQuery element which triggered the event 272 | - Example : 273 | 274 | ```js 275 | var calendar = $('#calendar').Calendar({...}); 276 | $('#calendar').on('Calendar.daynote.mouseenter', function(event, instance, elem){ 277 | console.log(event); // jQuery event 278 | console.log(instance); // Equals to var calendar above 279 | console.log(elem); // Use elem to make an animation or somthing else 280 | }); 281 | ``` 282 | 283 | ###### Calendar.daynote-mouseleave and Calendar.event-mouseleave 284 | 285 | - `Calendar.daynote-mouseleave` and `Calendar.event-mouseleave` 286 | - It is the same as `Calendar.daynote-mouseenter` and `Calendar.event-mouseenter` but when the mouse leave the event 287 | - Default, restore the event or day note state before the default of `Calendar.daynote-mouseenter` and `Calendar.event-mouseenter` 288 | 289 | ###### Calendar.daynote-click and Calendar.event-click 290 | 291 | - `Calendar.daynote-click` and `Calendar.event-click` 292 | - When 293 | - The user click or touch an event or a day note 294 | - Default 295 | - Opens a bootstrap modal to display the event 296 | - Arguments 297 | - `event` 298 | - The jQuery event 299 | - `instance` 300 | - The Calendar instance 301 | - `elem` 302 | - The jQuery element which triggered the event 303 | - `evt` 304 | - The event object you gived which triggered the event (so you have : start, end, title, content, category, anything else if you gived more attributes) 305 | - Example : 306 | 307 | ```js 308 | var calendar = $('#calendar').Calendar({...}); 309 | $('#calendar').on('Calendar.daynote-click', function(event, instance, elem, evt){ 310 | console.log(event); // jQuery event 311 | console.log(instance); // Equals to var calendar above 312 | console.log(elem); // Use elem to make an animation or somthing else 313 | console.log(evt); // You have all informations to display it in a modal 314 | }); 315 | ``` 316 | 317 | ## Contributing 318 | 319 | Feel free to report bugs or make a pull request ;-) 320 | -------------------------------------------------------------------------------- /example/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Calendar 17 | 18 | 19 |
20 | 21 |
22 |
23 |

Calendar demo

24 | Fork me on GitHub 25 |
26 |
27 |
28 |
29 | 30 | 33 | 34 | 37 |
38 |
39 |
40 | 41 |
42 |
43 |
44 |
45 |
46 |
47 | 48 | 78 | 79 | 115 | 116 | 336 | 337 | 338 | -------------------------------------------------------------------------------- /dist/js/jquery-calendar.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @class Calendar ~jquery-calendar plugin~ (https://github.com/ArrobeFr/jquery-calendar) 3 | * @author Developped by Arrobe (https://www.arrobe.fr) 4 | * @license Licensed under MIT (https://github.com/ArrobeFr/jquery-calendar/blob/master/LICENSE) 5 | */ 6 | jQuery(document).ready(function(f){function n(e,t){if("function"==typeof moment){var n=["#C62828","#AD1457","#6A1B9A","#4527A0","#283593","#1565C0","#0277BD","#00838F","#00695C","#2E7D32","#558B2F","#9E9D24","#F9A825","#FF8F00","#EF6C00","#D84315","#4E342E","#424242","#37474F","#212121"],a=["#EF9A9A","#F48FB1","#CE93D8","#B39DDB","#9FA8DA","#90CAF9","#81D4FA","#80DEEA","#80CBC4","#A5D6A7","#C5E1A5","#E6EE9C","#FFF59D","#FFE082","#FFCC80","#FFAB91","#BCAAA4","#EEEEEE","#B0BEC5"];return this.conf={locale:t.locale?t.locale:"fr",view:"week",enableKeyboard:!t.enableKeyboard||t.enableKeyboard,defaultView:{largeScreen:t.defaultView&&t.defaultView.largeScreen?t.defaultView.largeScreen:"week",smallScreen:t.defaultView&&t.defaultView.smallScreen?t.defaultView.smallScreen:"day",smallScreenThreshold:t.defaultView&&t.defaultView.smallScreenThreshold?t.defaultView.smallScreenThreshold:1e3},weekday:{timeline:{fromHour:t.weekday&&t.weekday.timeline&&t.weekday.timeline.fromHour?t.weekday.timeline.fromHour:7,toHour:t.weekday&&t.weekday.timeline&&t.weekday.timeline.toHour?t.weekday.timeline.toHour:20,intervalMinutes:t.weekday&&t.weekday.timeline&&t.weekday.timeline.intervalMinutes?t.weekday.timeline.intervalMinutes:60,format:t.weekday&&t.weekday.timeline&&t.weekday.timeline.format?t.weekday.timeline.format:"HH:mm",heightPx:t.weekday&&t.weekday.timeline&&t.weekday.timeline.heightPx?t.weekday.timeline.heightPx:50,autoResize:!t.weekday||(!t.weekday.timeline||(void 0===t.weekday.timeline.autoResize||t.weekday.timeline.autoResize))},dayline:{weekdays:t.weekday&&t.weekday.dayline&&t.weekday.dayline.weekdays?t.weekday.dayline.weekdays:[0,1,2,3,4,5,6],format:t.weekday&&t.weekday.dayline&&t.weekday.dayline.format?t.weekday.dayline.format:"dddd DD/MM",heightPx:t.weekday&&t.weekday.dayline&&t.weekday.dayline.heightPx&&t.weekday.dayline.heightPx&&31=n.closest("ul").height()/2-t.conf.weekday.timeline.heightPx?(heightPx=parseInt(n.css("top"))+parseInt(n.css("height")),n.css("z-index",10).animate({height:heightPx,top:0,width:"100%",left:0},50)):(heightPx=n.closest("ul").height()-parseInt(n.css("top")),n.css("z-index",10).animate({height:heightPx,width:"100%",left:0},50)),n.find(".event-name").removeClass("hidden"),n.find(".event-content").removeClass("hidden"))};f(self.element).off("Calendar.event-mouseenter",e).on("Calendar.event-mouseenter",e),f(self.element).off("Calendar.daynote-mouseenter",e).on("Calendar.daynote-mouseenter",e);var t=function(e,t,n){e.isDefaultPrevented()||(n.css("z-index","auto").animate({height:parseFloat(n.attr("data-height"))+"px",top:parseFloat(n.attr("data-top")),width:parseFloat(n.attr("data-width"))+"%",left:parseFloat(n.attr("data-left"))+"%"},50),n.find(".event-content").addClass("hidden"))};f(self.element).off("Calendar.event-mouseleave",t).on("Calendar.event-mouseleave",t),f(self.element).off("Calendar.daynote-mouseleave",t).on("Calendar.daynote-mouseleave",t);var n=function(e,t,n,a){e.isDefaultPrevented()||(modal=f(t.element).find("#calendar-modal"),rgb=t.hexToRgb(n.attr("data-color")),modal.css("background","rgba("+rgb.r+", "+rgb.g+", "+rgb.b+", 0.5)"),modal.find(".modal-title").append(n.attr("data-title")+" "),modal.find(".modal-body").append(f("

").append(f(this).closest(".calendar-events-day").find("span").html()).append(" ").append(f("").text(n.find(".event-date").text()))),modal.find(".modal-body").append(n.find(".event-content").html()),modal.modal("show"),modal.on("hidden.bs.modal",function(e){f(e.target).find(".modal-title").html(""),f(e.target).find(".modal-body").html("")}))};f(self.element).off("Calendar.event-click",n).on("Calendar.event-click",n),f(self.element).off("Calendar.daynote-click",n).on("Calendar.daynote-click",n);var a=function(e,t,n){if(!e.isDefaultPrevented()){var a=t.element.find('.calendar-event[data-category="'+f(n).text()+'"]');"false"==f(n).attr("data-clicked")&&a.animate({opacity:0},200,function(){a.css("display","none"),f(n).css("background-color","#E0E0E0"),f(n).attr("data-clicked",!0)}),"true"==f(n).attr("data-clicked")&&(a.css("display","list-item"),f(n).css("background-color",f(n).attr("data-color")),a.animate({opacity:1},200,function(){f(n).attr("data-clicked",!1)}))}};f(self.element).off("Calendar.category-event-click",a).on("Calendar.category-event-click",a),f(self.element).off("Calendar.category-daynote-click",a).on("Calendar.category-daynote-click",a);var o=function(e,t,n){e.isDefaultPrevented()||t.element.find(".calendar-event").each(function(e,t){f(t).attr("data-category")!=n.text()&&f(t).css("opacity",.2)})};f(self.element).off("Calendar.category-event-mouseenter",o).on("Calendar.category-event-mouseenter",o),f(self.element).off("Calendar.category-daynote-mouseenter",o).on("Calendar.category-daynote-mouseenter",o);var i=function(e,t,n){e.isDefaultPrevented()||t.element.find(".calendar-event").each(function(e,t){f(t).css("opacity",1)})};f(self.element).off("Calendar.category-event-mouseleave",i).on("Calendar.category-event-mouseleave",i),f(self.element).off("Calendar.category-daynote-mouseleave",i).on("Calendar.category-daynote-mouseleave",i),this.binded=!0}},n.prototype.weekDrawTime=function(){f(this.element).append(f("
",{class:"calendar-timeline"})),f(this.element).find("div.calendar-timeline").css("padding-top",this.conf.weekday.dayline.heightPx+"px");var e=this.conf.weekday.dayline.month.heightPx;this.conf.categories.enable&&(e+=30),f(this.element).find("div.calendar-timeline").css("margin-top",e+"px"),f(this.element).find("div.calendar-timeline").append(f("
    ")),ul=f(this.element).find("div.calendar-timeline").find("ul"),time=moment(moment()).startOf("Week"),time.add(this.conf.weekday.timeline.fromHour,"H");for(var t=(60*(this.conf.weekday.timeline.toHour+1)-60*this.conf.weekday.timeline.fromHour)/this.conf.weekday.timeline.intervalMinutes,n=0;n"),li.append(f("").text(time.format(this.conf.weekday.timeline.format))),li.height(this.conf.weekday.timeline.heightPx),ul.append(li),time.add(this.conf.weekday.timeline.intervalMinutes,"m"),n++},n.prototype.weekDrawDays=function(){f(this.element).append(f("
    ",{class:"calendar-events"}));var e=f("
    ",{class:"calendar-month"}).css("height",this.conf.weekday.dayline.month.heightPx+"px").css("text-align","center").css("padding-top",(this.conf.weekday.dayline.month.heightPx-20)/2+"px");"week"==this.getView()&&(e.text(this.miscUcfirstString(moment.unix(this.conf.unixTimestamp).format(this.conf.weekday.dayline.month.format))),e.addClass("weektomonth")),"day"==this.getView()&&(e.text(this.miscUcfirstString(moment.unix(this.conf.unixTimestamp).format(this.conf.weekday.dayline.month.weekFormat))),e.addClass("daytoweek")),f(this.element).find("div.calendar-events").append(e),f(this.element).find("div.calendar-events").append(f("
      ")),ul=f(this.element).find("div.calendar-events").find("ul");for(var t=this.getViewDays(),n=0;n",{class:"calendar-events-day"}),li.css("width",100/t.length+"%"),(e=f("
      ",{class:"calendar-day-header"})).height(this.conf.weekday.dayline.heightPx),0==n&&"desktop"==this.mobileQuery()&&e.append(f("',modal+='',modal+="
      ",modal+='",modal+="
    ",modal+="
    ",modal+="
",f(this.element).append(modal)},n.prototype.drawNow=function(){if(this.conf.now.enable){var e=f("
",{class:"featurette-divider calendar-now"});e.css("width","100%"),e.css("position","absolute"),e.css("z-index",3),e.css("border-top",this.conf.now.heightPx+"px "+this.conf.now.style+" "+this.conf.now.color);var t=(moment().format("X")-moment().startOf("day").add(this.conf.weekday.timeline.fromHour,"h").format("X"))/60/this.conf.weekday.timeline.intervalMinutes*this.conf.weekday.timeline.heightPx-this.conf.weekday.timeline.heightPx/2;if(e.css("top",t+"px"),this.element.find('li.calendar-events-day[data-time="'+moment().startOf("day").format("X")+'"]').find("ul").append(e),this.conf.now.refresh){var n=this;setInterval(function(){var e=n.element.find("hr.calendar-now").remove();(e=f("
",{class:"featurette-divider calendar-now"})).css("width","100%"),e.css("position","absolute"),e.css("z-index",2),e.css("border-top",n.conf.now.heightPx+"px "+n.conf.now.style+" "+n.conf.now.color);var t=(moment().format("X")-moment().startOf("day").add(n.conf.weekday.timeline.fromHour,"h").format("X"))/60/n.conf.weekday.timeline.intervalMinutes*n.conf.weekday.timeline.heightPx-n.conf.weekday.timeline.heightPx/2;e.css("top",t+"px"),n.element.find('li.calendar-events-day[data-time="'+moment().startOf("day").format("X")+'"]').find("ul").append(e)},1e4)}}},n.prototype.hoverEventOrDaynote=function(){var t=this;this.element.find(".calendar-event").each(function(){var e;f(this).hover(function(){elem=f(this),e=setTimeout(function(){elem.hasClass("calendar-daynote")?f(t.element).trigger("Calendar.daynote-mouseenter",[t,elem]):f(t.element).trigger("Calendar.event-mouseenter",[t,elem])},t.conf.event.hover.delay)},function(){clearTimeout(e),elem=f(this),elem.hasClass("calendar-daynote")?f(t.element).trigger("Calendar.daynote-mouseleave",[t,elem]):f(t.element).trigger("Calendar.event-mouseleave",[t,elem])})})},n.prototype.clickEventOrDaynote=function(){(self=this).element.find(".calendar-event").each(function(){f(this).click(function(e){elem=f(e.target),"LI"===elem.prop("nodeName")||elem.hasClass("calendar-event")||(elem=elem.closest("li.calendar-event")),elem.hasClass("calendar-daynote")?f(self.element).trigger("Calendar.daynote-click",[self,elem,self.daynotes[parseInt(elem.attr("data-index"))]]):f(self.element).trigger("Calendar.event-click",[self,elem,self.events[parseInt(elem.attr("data-index"))]])})})},n.prototype.resizeTimeline=function(){for(var e=0;eparseInt(moment.unix(this.events[e].end).startOf("day").add(this.conf.weekday.timeline.toHour,"hour").format("X"))&&(this.conf.weekday.timeline.toHour=parseInt(moment.unix(this.events[e].end).hour()),this.conf.weekday.timeline.toHour<23&&this.conf.weekday.timeline.toHour++)},n.prototype.getViewDays=function(){var e=[];if("day"==this.getView()&&e.push(parseInt(moment.unix(this.conf.unixTimestamp).format("X"))),"week"==this.getView())for(var t=0;t=parseInt(this.fromTimestamp)&&parseInt(e[a])<=parseInt(this.toTimestamp)&&o.push(e.category);return o=this.miscUniqueArray(o)},n.prototype.getCategoryColor=function(e,t,n,a){for(var o=0;ot.start?1:-1}),this},n.prototype.addEvents=function(e){return this.events=this.events.concat(e),this.events.sort(function(e,t){return e.start>t.start?1:-1}),this},n.prototype.getDaynotes=function(){return this.daynotes},n.prototype.setDaynotes=function(e){return this.daynotes=e||[],this.daynotes.sort(function(e,t){return e.start>t.start?1:-1}),this},n.prototype.addDaynotes=function(e){return this.daynotes.concat(e),this.daynotes.sort(function(e,t){return e.start>t.start?1:-1}),this},n.prototype.getInitTime=function(){return this.initTime},n.prototype.getViewInterval=function(){return[this.fromTimestamp,this.toTimestamp]},n.prototype.getNextViewInterval=function(){return"day"==this.getView()?[parseInt(moment.unix(this.fromTimestamp).add(1,"d").format("X")),parseInt(moment.unix(this.toTimestamp).add(1,"d").format("X"))]:"week"==this.getView()?[parseInt(moment.unix(this.fromTimestamp).add(1,"w").format("X")),parseInt(moment.unix(this.toTimestamp).add(1,"w").format("X"))]:"month"==this.getView()?[parseInt(moment.unix(this.fromTimestamp).add(1,"M").format("X")),parseInt(moment.unix(this.toTimestamp).add(1,"M").format("X"))]:void 0},n.prototype.getPrevViewInterval=function(){return"day"==this.getView()?[parseInt(moment.unix(this.fromTimestamp).subtract(1,"d").format("X")),parseInt(moment.unix(this.toTimestamp).subtract(1,"d").format("X"))]:"week"==this.getView()?[parseInt(moment.unix(this.fromTimestamp).subtract(1,"w").format("X")),parseInt(moment.unix(this.toTimestamp).subtract(1,"w").format("X"))]:"month"==this.getView()?[parseInt(moment.unix(this.fromTimestamp).subtract(1,"M").format("X")),parseInt(moment.unix(this.toTimestamp).subtract(1,"M").format("X"))]:void 0},n.prototype.getTimestamp=function(){return this.conf.unixTimestamp},n.prototype.setTimestamp=function(e){return this.conf.unixTimestamp=parseInt(e),this},n.prototype.getView=function(){return this.conf.view},n.prototype.setView=function(e){return"day"!=e&&"week"!=e&&"month"!=e||(this.conf.view=e),this},n.prototype.miscDedupeArray=function(e){e=e.concat();for(var t=0;t 31) ? Args.weekday.dayline.heightPx : 31 : 31 : 31 : 31 : 31, 86 | month: { 87 | format: (Args.weekday) ? (Args.weekday.dayline) ? (Args.weekday.dayline.month) ? (Args.weekday.dayline.month.format) ? Args.weekday.dayline.month.format : 'MMMM YYYY' : 'MMMM YYYY' : 'MMMM YYYY' : 'MMMM YYYY', 88 | heightPx: (Args.weekday) ? (Args.weekday.dayline) ? (Args.weekday.dayline.month) ? (Args.weekday.dayline.month.heightPx) ? Args.weekday.dayline.month.heightPx : 30 : 30 : 30 : 30, 89 | weekFormat: (Args.weekday) ? (Args.weekday.dayline) ? (Args.weekday.dayline.month) ? (Args.weekday.dayline.month.weekFormat) ? Args.weekday.dayline.month.weekFormat : 'w' : 'w' : 'w' : 'w' 90 | } 91 | } 92 | }, 93 | month: { 94 | format: (Args.month) ? (Args.month.format) ? Args.month.format : 'MMMM YYYY' : 'MMMM YYYY', 95 | heightPx: (Args.month) ? (Args.month.heightPx) ? (Args.month.heightPx > 31) ? Args.month.heightPx : 31 : 31 : 31, 96 | weekline: { 97 | format: (Args.month) ? (Args.month.weekline) ? (Args.month.weekline.format) ? Args.month.weekline.format : 'w' : 'w' : 'w', 98 | heightPx: (Args.month) ? (Args.month.weekline) ? (Args.month.weekline.heightPx) ? Args.month.weekline.heightPx : 80 : 80 : 80 99 | }, 100 | dayheader: { 101 | weekdays: (Args.month) ? (Args.month.dayheader) ? (Args.month.dayheader.weekdays) ? Args.month.dayheader.weekdays : [0, 1, 2, 3, 4, 5, 6] : [0, 1, 2, 3, 4, 5, 6] : [0, 1, 2, 3, 4, 5, 6], 102 | format: (Args.month) ? (Args.month.dayheader) ? (Args.month.dayheader.format) ? Args.month.dayheader.format : 'dddd' : 'dddd' : 'dddd', 103 | heightPx: (Args.month) ? (Args.month.dayheader) ? (Args.month.dayheader.heightPx) ? Args.month.dayheader.heightPx : 30 : 30 : 30 104 | }, 105 | day: { 106 | format: (Args.month) ? (Args.month.day) ? (Args.month.day.format) ? Args.month.day.format : 'DD/MM' : 'DD/MM' : 'DD/MM' 107 | } 108 | }, 109 | unixTimestamp: (Args.unixTimestamp) ? Args.unixTimestamp : moment().format('X'), 110 | event: { 111 | hover: { 112 | delay: (Args.event) ? (Args.event.hover) ? (Args.event.hover.delay) ? Args.event.hover.delay : 500 : 500 : 500 113 | } 114 | }, 115 | colors: { 116 | events: (Args.colors) ? (Args.colors.events) ? Args.colors.events : eventColors : eventColors, 117 | daynotes: (Args.colors) ? (Args.colors.daynotes) ? Args.colors.daynotes : daynoteColors : daynoteColors, 118 | random: (Args.colors) ? (Args.colors.random) ? Args.colors.random : true : true 119 | }, 120 | categories: { 121 | enable: (Args.categories) ? (Args.categories.enable !== undefined) ? Args.categories.enable : true : true, 122 | hover: { 123 | delay: (Args.categories) ? (Args.categories.hover) ? (Args.categories.hover.delay) ? Args.categories.hover.delay : 500 : 500 : 500 124 | } 125 | }, 126 | now: { 127 | enable: (Args.now) ? (Args.now.enable !== undefined) ? (Args.now.enable) : false : false, 128 | refresh: (Args.now) ? (Args.now.refresh !== undefined) ? (Args.now.refresh) : false : false, 129 | heightPx: (Args.now) ? (Args.now.heightPx) ? (Args.now.heightPx) : 1 : 1, 130 | style: (Args.now) ? (Args.now.style) ? (Args.now.style) : 'solid' : 'solid', 131 | color: (Args.now) ? (Args.now.color) ? (Args.now.color) : '#03A9F4' : '#03A9F4' 132 | } 133 | }; 134 | 135 | // Sets moment's locale 136 | moment.locale(this.conf.locale); 137 | 138 | // Sets colors 139 | this.setEventColors(this.conf.colors.events); 140 | this.setDaynoteColors(this.conf.colors.daynotes); 141 | 142 | // Create array to associate colors and categories 143 | this.eventCategoryColor = []; 144 | this.daynoteCategoryColor = []; 145 | 146 | // Create array to associate colors and categories as defined by setEventCategoriesColors or setDaynoteCategoriesColors 147 | this.userEventCategoryColor = []; 148 | this.userDaynoteCategoryColor = []; 149 | 150 | // Load events 151 | this.setEvents((Args.events) ? Args.events : []); 152 | 153 | // Load day notes 154 | this.setDaynotes((Args.daynotes) ? Args.daynotes : []); 155 | 156 | // Define the default view 157 | if (this.mobileQuery() == 'mobile'){ 158 | this.setView(this.conf.defaultView.smallScreen); 159 | }else{ 160 | this.setView(this.conf.defaultView.largeScreen); 161 | } 162 | 163 | // Init 164 | this.element = element; 165 | this.initTime = false; 166 | return this; 167 | } 168 | 169 | Calendar.prototype.init = function() { 170 | var millis = Date.now(); 171 | this.element.addClass('loading'); 172 | this.calculateCurrentInterval(); 173 | $(this.element).trigger('Calendar.init', [ 174 | this, 175 | this.getPrevViewInterval(), 176 | this.getViewInterval(), 177 | this.getNextViewInterval() 178 | ]); 179 | $(this.element).html(''); 180 | if (!$(this.element).hasClass('calendar')){ 181 | $(this.element).addClass('calendar'); 182 | } 183 | if (this.getView() == 'day' || this.getView() == 'week'){ 184 | if (this.conf.weekday.timeline.autoResize){ 185 | this.resizeTimeline(); 186 | } 187 | } 188 | this.drawCategories(); 189 | if (this.getView() == 'day' || this.getView() == 'week'){ 190 | this.weekDrawTime(); 191 | this.weekDrawDays(); 192 | this.weekDrawDaynotes(); 193 | this.weekDrawEvents(); 194 | } 195 | if (this.getView() == 'month'){ 196 | this.monthDrawWeek(); 197 | this.monthDrawWeekNumbers(); 198 | this.monthDrawWeekDays(); 199 | this.monthDrawDaynotes(); 200 | this.monthDrawEvents(); 201 | } 202 | this.drawModal(); 203 | this.drawNow(); 204 | this.positionEvents(); 205 | this.hoverEventOrDaynote(); 206 | this.clickEventOrDaynote(); 207 | this.addBtnLeftRight(); 208 | this.addSwipe(); 209 | this.clickSwitchView(); 210 | this.keyboardSwitchView(); 211 | this.defaultEvents(); 212 | this.element.removeClass('loading'); 213 | this.initTime = (Date.now()-millis)+'ms'; 214 | return this; 215 | }; 216 | 217 | Calendar.prototype.defaultEvents = function() { 218 | if (this.binded == undefined){ 219 | var eventMouseenterDefault = function(event, self, elem){ 220 | if (!event.isDefaultPrevented()){ 221 | if (parseInt(elem.css('top')) >= (elem.closest('ul').height() / 2) - self.conf.weekday.timeline.heightPx){ 222 | heightPx = parseInt(elem.css('top')) + parseInt(elem.css('height')); 223 | elem 224 | .css('z-index', 10) 225 | .animate({ 226 | height:heightPx, 227 | top:0, 228 | width:'100%', 229 | left:0 230 | }, 50) 231 | ; 232 | }else{ 233 | heightPx = elem.closest('ul').height() - parseInt(elem.css('top')); 234 | elem 235 | .css('z-index', 10) 236 | .animate({ 237 | height:heightPx, 238 | width:'100%', 239 | left:0 240 | }, 50) 241 | ; 242 | } 243 | elem.find('.event-name').removeClass('hidden'); 244 | elem.find('.event-content').removeClass('hidden'); 245 | } 246 | }; 247 | $(self.element).off('Calendar.event-mouseenter', eventMouseenterDefault).on('Calendar.event-mouseenter', eventMouseenterDefault); 248 | $(self.element).off('Calendar.daynote-mouseenter', eventMouseenterDefault).on('Calendar.daynote-mouseenter', eventMouseenterDefault); 249 | var eventMouseleaveDefault = function(event, self, elem){ 250 | if (!event.isDefaultPrevented()){ 251 | elem 252 | .css('z-index', 'auto') 253 | .animate({ 254 | height:parseFloat(elem.attr('data-height'))+'px', 255 | top:parseFloat(elem.attr('data-top')), 256 | width:parseFloat(elem.attr('data-width'))+'%', 257 | left:parseFloat(elem.attr('data-left'))+'%' 258 | }, 50) 259 | ; 260 | elem.find('.event-content').addClass('hidden'); 261 | } 262 | }; 263 | $(self.element).off('Calendar.event-mouseleave', eventMouseleaveDefault).on('Calendar.event-mouseleave', eventMouseleaveDefault); 264 | $(self.element).off('Calendar.daynote-mouseleave', eventMouseleaveDefault).on('Calendar.daynote-mouseleave', eventMouseleaveDefault); 265 | var eventClickDefault = function(event, self, elem, evt){ 266 | if (!event.isDefaultPrevented()){ 267 | modal = $(self.element).find('#calendar-modal'); 268 | rgb = self.hexToRgb(elem.attr('data-color')); 269 | modal.css('background', 'rgba('+rgb.r+', '+rgb.g+', '+rgb.b+', 0.5)'); 270 | modal.find('.modal-title').append(elem.attr('data-title')+' '); 271 | modal.find('.modal-body').append( 272 | $('

').append( 273 | $(this).closest('.calendar-events-day').find('span').html() 274 | ).append( 275 | ' ' 276 | ).append( 277 | $('').text(elem.find('.event-date').text()) 278 | ) 279 | ); 280 | modal.find('.modal-body').append(elem.find('.event-content').html()); 281 | modal.modal('show'); 282 | modal.on('hidden.bs.modal', function (e) { 283 | $(e.target).find('.modal-title').html(''); 284 | $(e.target).find('.modal-body').html(''); 285 | }); 286 | } 287 | }; 288 | $(self.element).off('Calendar.event-click', eventClickDefault).on('Calendar.event-click', eventClickDefault); 289 | $(self.element).off('Calendar.daynote-click', eventClickDefault).on('Calendar.daynote-click', eventClickDefault); 290 | var eventCategoryClickDefault = function(event, self, elem){ 291 | if (!event.isDefaultPrevented()){ 292 | var events = self.element.find('.calendar-event[data-category="'+$(elem).text()+'"]'); 293 | if ($(elem).attr('data-clicked') == 'false'){ 294 | events.animate({ 295 | opacity: 0 296 | }, 200, function(){ 297 | events.css('display', 'none'); 298 | $(elem).css('background-color', '#E0E0E0'); 299 | $(elem).attr('data-clicked', true); 300 | }); 301 | } 302 | if ($(elem).attr('data-clicked') == 'true'){ 303 | events.css('display', 'list-item'); 304 | $(elem).css('background-color', $(elem).attr('data-color')); 305 | events.animate({ 306 | opacity: 1 307 | }, 200, function(){ 308 | $(elem).attr('data-clicked', false); 309 | }); 310 | } 311 | } 312 | }; 313 | $(self.element).off('Calendar.category-event-click', eventCategoryClickDefault).on('Calendar.category-event-click', eventCategoryClickDefault); 314 | $(self.element).off('Calendar.category-daynote-click', eventCategoryClickDefault).on('Calendar.category-daynote-click', eventCategoryClickDefault); 315 | var eventCategoryMouseenterDefault = function(event, self, elem){ 316 | if (!event.isDefaultPrevented()){ 317 | self.element.find('.calendar-event').each(function(i, e){ 318 | if ($(e).attr('data-category') != elem.text()){ 319 | $(e).css('opacity', 0.2); 320 | } 321 | }); 322 | } 323 | }; 324 | $(self.element).off('Calendar.category-event-mouseenter', eventCategoryMouseenterDefault).on('Calendar.category-event-mouseenter', eventCategoryMouseenterDefault); 325 | $(self.element).off('Calendar.category-daynote-mouseenter', eventCategoryMouseenterDefault).on('Calendar.category-daynote-mouseenter', eventCategoryMouseenterDefault); 326 | var eventCategoryMouseleaveDefault = function(event, self, elem){ 327 | if (!event.isDefaultPrevented()){ 328 | self.element.find('.calendar-event').each(function(i, e){ 329 | $(e).css('opacity', 1); 330 | }); 331 | } 332 | }; 333 | $(self.element).off('Calendar.category-event-mouseleave', eventCategoryMouseleaveDefault).on('Calendar.category-event-mouseleave', eventCategoryMouseleaveDefault); 334 | $(self.element).off('Calendar.category-daynote-mouseleave', eventCategoryMouseleaveDefault).on('Calendar.category-daynote-mouseleave', eventCategoryMouseleaveDefault); 335 | this.binded = true; 336 | } 337 | }; 338 | 339 | Calendar.prototype.weekDrawTime = function() { 340 | $(this.element).append($('
', { 341 | class: 'calendar-timeline' 342 | })); 343 | $(this.element).find('div.calendar-timeline').css('padding-top', this.conf.weekday.dayline.heightPx+'px'); 344 | var marginTop = this.conf.weekday.dayline.month.heightPx; 345 | if (this.conf.categories.enable){ 346 | marginTop += 30; 347 | } 348 | $(this.element).find('div.calendar-timeline').css('margin-top', marginTop+'px'); 349 | 350 | $(this.element).find('div.calendar-timeline').append($('
    ')); 351 | 352 | ul = $(this.element).find('div.calendar-timeline').find('ul'); 353 | 354 | time = moment(moment()).startOf('Week'); 355 | time.add(this.conf.weekday.timeline.fromHour, 'H'); 356 | 357 | var limit = (((this.conf.weekday.timeline.toHour+1)*60) - (this.conf.weekday.timeline.fromHour * 60)) / this.conf.weekday.timeline.intervalMinutes; 358 | var i = 0; 359 | while (i < limit){ 360 | li = $('
  • '); 361 | li.append($('').text(time.format(this.conf.weekday.timeline.format))); 362 | li.height(this.conf.weekday.timeline.heightPx); 363 | ul.append(li); 364 | time.add(this.conf.weekday.timeline.intervalMinutes, 'm'); 365 | i++; 366 | } 367 | }; 368 | 369 | Calendar.prototype.weekDrawDays = function() { 370 | $(this.element).append($('
    ', { 371 | class: 'calendar-events' 372 | })); 373 | 374 | var div = $('
    ', { 375 | class: 'calendar-month' 376 | }) 377 | .css('height', this.conf.weekday.dayline.month.heightPx+'px') 378 | .css('text-align', 'center') 379 | .css('padding-top', (this.conf.weekday.dayline.month.heightPx-20)/2+'px') 380 | ; 381 | if (this.getView() == 'week'){ 382 | div.text(this.miscUcfirstString(moment.unix(this.conf.unixTimestamp).format(this.conf.weekday.dayline.month.format))); 383 | div.addClass('weektomonth'); 384 | } 385 | if (this.getView() == 'day'){ 386 | div.text(this.miscUcfirstString(moment.unix(this.conf.unixTimestamp).format(this.conf.weekday.dayline.month.weekFormat))); 387 | div.addClass('daytoweek'); 388 | } 389 | $(this.element).find('div.calendar-events').append(div); 390 | 391 | $(this.element).find('div.calendar-events').append($('
      ')); 392 | 393 | ul = $(this.element).find('div.calendar-events').find('ul'); 394 | 395 | var days = this.getViewDays(); 396 | 397 | for (var i=0; i', { 401 | class: 'calendar-events-day' 402 | }); 403 | li.css('width',100/days.length+'%'); 404 | div = $('
      ', { 405 | class: 'calendar-day-header' 406 | }); 407 | div.height(this.conf.weekday.dayline.heightPx); 408 | if (i == 0 && this.mobileQuery() == 'desktop'){ 409 | div.append($(''; 990 | modal+= ''; 991 | modal+= '
      '; 992 | modal+= ''; 994 | modal+= '
    '; 995 | modal+= '
    '; 996 | modal+= '
'; 997 | $(this.element).append(modal); 998 | }; 999 | 1000 | Calendar.prototype.drawNow = function() { 1001 | if (this.conf.now.enable){ 1002 | var hr = $('
', { 1003 | class: 'featurette-divider calendar-now' 1004 | }); 1005 | hr.css('width', '100%'); 1006 | hr.css('position', 'absolute'); 1007 | hr.css('z-index', 3); 1008 | hr.css('border-top', this.conf.now.heightPx+'px '+this.conf.now.style+' '+this.conf.now.color); 1009 | var top = ((moment().format('X') - moment().startOf('day').add(this.conf.weekday.timeline.fromHour, 'h').format('X')) / 60 / this.conf.weekday.timeline.intervalMinutes * this.conf.weekday.timeline.heightPx) - (this.conf.weekday.timeline.heightPx / 2); 1010 | hr.css('top', top+'px'); 1011 | this.element.find('li.calendar-events-day[data-time="'+moment().startOf('day').format('X')+'"]').find('ul').append(hr); 1012 | if (this.conf.now.refresh){ 1013 | var self = this; 1014 | setInterval(function(){ 1015 | var hr = self.element.find('hr.calendar-now').remove(); 1016 | var hr = $('
', { 1017 | class: 'featurette-divider calendar-now' 1018 | }); 1019 | hr.css('width', '100%'); 1020 | hr.css('position', 'absolute'); 1021 | hr.css('z-index', 2); 1022 | hr.css('border-top', self.conf.now.heightPx+'px '+self.conf.now.style+' '+self.conf.now.color); 1023 | var top = ((moment().format('X') - moment().startOf('day').add(self.conf.weekday.timeline.fromHour, 'h').format('X')) / 60 / self.conf.weekday.timeline.intervalMinutes * self.conf.weekday.timeline.heightPx) - (self.conf.weekday.timeline.heightPx / 2); 1024 | hr.css('top', top+'px'); 1025 | self.element.find('li.calendar-events-day[data-time="'+moment().startOf('day').format('X')+'"]').find('ul').append(hr); 1026 | }, 10000); 1027 | } 1028 | } 1029 | }; 1030 | 1031 | Calendar.prototype.hoverEventOrDaynote = function() { 1032 | var self = this; 1033 | this.element.find('.calendar-event').each(function(){ 1034 | var setTimeoutConst; 1035 | $(this).hover( 1036 | function(){ 1037 | elem = $(this); 1038 | setTimeoutConst = setTimeout(function(){ 1039 | if (elem.hasClass('calendar-daynote')){ 1040 | $(self.element).trigger('Calendar.daynote-mouseenter', [ 1041 | self, 1042 | elem 1043 | ]); 1044 | }else{ 1045 | $(self.element).trigger('Calendar.event-mouseenter', [ 1046 | self, 1047 | elem 1048 | ]); 1049 | } 1050 | 1051 | }, self.conf.event.hover.delay); 1052 | }, 1053 | function(){ 1054 | clearTimeout(setTimeoutConst); 1055 | elem = $(this); 1056 | if (elem.hasClass('calendar-daynote')){ 1057 | $(self.element).trigger('Calendar.daynote-mouseleave', [ 1058 | self, 1059 | elem 1060 | ]); 1061 | }else{ 1062 | $(self.element).trigger('Calendar.event-mouseleave', [ 1063 | self, 1064 | elem 1065 | ]); 1066 | } 1067 | 1068 | } 1069 | ); 1070 | }); 1071 | }; 1072 | 1073 | Calendar.prototype.clickEventOrDaynote = function() { 1074 | self = this; 1075 | this.element.find('.calendar-event').each(function(){ 1076 | $(this).click(function(event){ 1077 | elem = $(event.target); 1078 | if (elem.prop('nodeName') !== 'LI' && !elem.hasClass('calendar-event')){ 1079 | elem = elem.closest('li.calendar-event'); 1080 | } 1081 | if (elem.hasClass('calendar-daynote')){ 1082 | $(self.element).trigger('Calendar.daynote-click', [ 1083 | self, 1084 | elem, 1085 | self.daynotes[parseInt(elem.attr('data-index'))] 1086 | ]); 1087 | }else{ 1088 | $(self.element).trigger('Calendar.event-click', [ 1089 | self, 1090 | elem, 1091 | self.events[parseInt(elem.attr('data-index'))] 1092 | ]); 1093 | } 1094 | }); 1095 | }); 1096 | }; 1097 | 1098 | Calendar.prototype.resizeTimeline = function() { 1099 | for (var j=0; j parseInt(moment.unix(this.events[j].end).startOf('day').add(this.conf.weekday.timeline.toHour, 'hour').format('X'))){ 1104 | this.conf.weekday.timeline.toHour = parseInt(moment.unix(this.events[j].end).hour()); 1105 | if (this.conf.weekday.timeline.toHour < 23){ 1106 | this.conf.weekday.timeline.toHour++; 1107 | } 1108 | } 1109 | } 1110 | }; 1111 | 1112 | Calendar.prototype.getViewDays = function() { 1113 | var days = []; 1114 | if (this.getView() == 'day'){ 1115 | days.push(parseInt(moment.unix(this.conf.unixTimestamp).format('X'))); 1116 | } 1117 | if (this.getView() == 'week'){ 1118 | for (var i=0; i= parseInt(this.fromTimestamp) && parseInt(e[attribute2]) <= parseInt(this.toTimestamp)){ 1279 | categories.push(e.category); 1280 | } 1281 | } 1282 | categories = this.miscUniqueArray(categories); 1283 | return categories; 1284 | }; 1285 | 1286 | Calendar.prototype.getCategoryColor = function(category, object, colors, color) { 1287 | for (var i=0; i 0) ? this.userEventCategoryColor : []; 1317 | for (var i=0; i 0) ? this.userDaynoteCategoryColor : []; 1342 | for (var i=0; i b.start) ? 1 : -1;}); 1390 | return this; 1391 | }; 1392 | 1393 | Calendar.prototype.addEvents = function(events) { 1394 | this.events = this.events.concat(events); 1395 | this.events.sort(function(a,b) {return (a.start > b.start) ? 1 : -1;}); 1396 | return this; 1397 | }; 1398 | 1399 | Calendar.prototype.getDaynotes = function() { 1400 | return this.daynotes; 1401 | }; 1402 | 1403 | Calendar.prototype.setDaynotes = function(daynotes) { 1404 | this.daynotes = (daynotes) ? daynotes : []; 1405 | this.daynotes.sort(function(a,b) {return (a.start > b.start) ? 1 : -1;}); 1406 | return this; 1407 | }; 1408 | 1409 | Calendar.prototype.addDaynotes = function(daynotes) { 1410 | this.daynotes.concat(daynotes); 1411 | this.daynotes.sort(function(a,b) {return (a.start > b.start) ? 1 : -1;}); 1412 | return this; 1413 | }; 1414 | 1415 | Calendar.prototype.getInitTime = function() { 1416 | return this.initTime; 1417 | }; 1418 | 1419 | Calendar.prototype.getViewInterval = function() { 1420 | return [this.fromTimestamp, this.toTimestamp]; 1421 | }; 1422 | 1423 | Calendar.prototype.getNextViewInterval = function() { 1424 | if (this.getView() == 'day'){ 1425 | return [ 1426 | parseInt(moment.unix(this.fromTimestamp).add(1, 'd').format('X')), 1427 | parseInt(moment.unix(this.toTimestamp).add(1, 'd').format('X')) 1428 | ]; 1429 | } 1430 | if (this.getView() == 'week'){ 1431 | return [ 1432 | parseInt(moment.unix(this.fromTimestamp).add(1, 'w').format('X')), 1433 | parseInt(moment.unix(this.toTimestamp).add(1, 'w').format('X')) 1434 | ]; 1435 | } 1436 | if (this.getView() == 'month'){ 1437 | return [ 1438 | parseInt(moment.unix(this.fromTimestamp).add(1, 'M').format('X')), 1439 | parseInt(moment.unix(this.toTimestamp).add(1, 'M').format('X')) 1440 | ]; 1441 | } 1442 | }; 1443 | 1444 | Calendar.prototype.getPrevViewInterval = function() { 1445 | if (this.getView() == 'day'){ 1446 | return [ 1447 | parseInt(moment.unix(this.fromTimestamp).subtract(1, 'd').format('X')), 1448 | parseInt(moment.unix(this.toTimestamp).subtract(1, 'd').format('X')) 1449 | ]; 1450 | } 1451 | if (this.getView() == 'week'){ 1452 | return [ 1453 | parseInt(moment.unix(this.fromTimestamp).subtract(1, 'w').format('X')), 1454 | parseInt(moment.unix(this.toTimestamp).subtract(1, 'w').format('X')) 1455 | ]; 1456 | } 1457 | if (this.getView() == 'month'){ 1458 | return [ 1459 | parseInt(moment.unix(this.fromTimestamp).subtract(1, 'M').format('X')), 1460 | parseInt(moment.unix(this.toTimestamp).subtract(1, 'M').format('X')) 1461 | ]; 1462 | } 1463 | }; 1464 | 1465 | Calendar.prototype.getTimestamp = function() { 1466 | return this.conf.unixTimestamp; 1467 | }; 1468 | 1469 | Calendar.prototype.setTimestamp = function(timestamp) { 1470 | this.conf.unixTimestamp = parseInt(timestamp); 1471 | return this; 1472 | }; 1473 | 1474 | Calendar.prototype.getView = function(){ 1475 | return this.conf.view; 1476 | }; 1477 | 1478 | Calendar.prototype.setView = function(view){ 1479 | if (view == 'day' || view == 'week' || view == 'month') { 1480 | this.conf.view = view; 1481 | } 1482 | return this; 1483 | }; 1484 | 1485 | Calendar.prototype.miscDedupeArray = function(a) { 1486 | a = a.concat(); 1487 | for (var i=0; i