├── .prettierrc ├── .husky └── pre-commit ├── .prettierignore ├── .vscode └── settings.json ├── .gitignore ├── favicon.ico ├── mdp-icon.png ├── demos ├── simple.html ├── input.html ├── max-picks.html ├── days-range.html ├── custom-date-format.html ├── min-max-date.html ├── disable-dates.html ├── preselect-dates.html ├── pickable-range.html ├── alt-field.html ├── disable-calendar.html ├── ui-calendar-methods.html ├── full-year.html └── index.html ├── jest.setup.js ├── eslint.config.mjs ├── jquery-ui.multidatespicker.css ├── bower.json ├── .github └── workflows │ └── ci.yml ├── README.md ├── package.json ├── index.js ├── jest.config.js ├── jquery-ui.multidatespicker.js ├── index.html └── __tests__ └── multidatespicker.test.js /.prettierrc: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npx lint-staged 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore artifacts: 2 | build 3 | coverage 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .eslintcache 2 | .idea 3 | /node_modules/ 4 | /coverage/ -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dubrox/Multiple-Dates-Picker-for-jQuery-UI/HEAD/favicon.ico -------------------------------------------------------------------------------- /mdp-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dubrox/Multiple-Dates-Picker-for-jQuery-UI/HEAD/mdp-icon.png -------------------------------------------------------------------------------- /demos/simple.html: -------------------------------------------------------------------------------- 1 |
2 | 5 | -------------------------------------------------------------------------------- /demos/input.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /demos/max-picks.html: -------------------------------------------------------------------------------- 1 |
2 | 5 | -------------------------------------------------------------------------------- /demos/days-range.html: -------------------------------------------------------------------------------- 1 |
2 | 8 | -------------------------------------------------------------------------------- /demos/custom-date-format.html: -------------------------------------------------------------------------------- 1 |
2 | 8 | -------------------------------------------------------------------------------- /demos/min-max-date.html: -------------------------------------------------------------------------------- 1 |
2 | 8 | -------------------------------------------------------------------------------- /jest.setup.js: -------------------------------------------------------------------------------- 1 | const jQuery = require("jquery"); 2 | global.$ = global.jQuery = window.$ = window.jQuery = jQuery; 3 | jQuery.ui = require("jquery-ui"); 4 | require("jquery-ui/ui/widgets/datepicker"); 5 | require("./jquery-ui.multidatespicker.js"); 6 | -------------------------------------------------------------------------------- /demos/disable-dates.html: -------------------------------------------------------------------------------- 1 |
2 | 9 | -------------------------------------------------------------------------------- /demos/preselect-dates.html: -------------------------------------------------------------------------------- 1 |
2 | 9 | -------------------------------------------------------------------------------- /demos/pickable-range.html: -------------------------------------------------------------------------------- 1 |
2 | 10 | -------------------------------------------------------------------------------- /demos/alt-field.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 9 | -------------------------------------------------------------------------------- /demos/disable-calendar.html: -------------------------------------------------------------------------------- 1 |
2 | 13 | -------------------------------------------------------------------------------- /demos/ui-calendar-methods.html: -------------------------------------------------------------------------------- 1 |
2 | 13 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import globals from "globals"; 2 | import pluginJs from "@eslint/js"; 3 | import eslintConfigPrettier from "eslint-config-prettier"; 4 | 5 | /** @type {import('eslint').Linter.Config[]} */ 6 | export default [ 7 | { languageOptions: { globals: globals.browser } }, 8 | eslintConfigPrettier, 9 | pluginJs.configs.recommended, 10 | { 11 | rules: { 12 | "no-fallthrough": "off", 13 | "no-undef": "warn", 14 | "no-unused-vars": "warn", 15 | }, 16 | }, 17 | ]; 18 | -------------------------------------------------------------------------------- /jquery-ui.multidatespicker.css: -------------------------------------------------------------------------------- 1 | /* jQuery UI Datepicker moving pixels fix */ 2 | table.ui-datepicker-calendar { 3 | border-collapse: separate; 4 | } 5 | .ui-datepicker-calendar td { 6 | border: 1px solid transparent; 7 | } 8 | 9 | /* jQuery UI Datepicker hide datepicker helper */ 10 | #ui-datepicker-div { 11 | display: none; 12 | } 13 | 14 | /* jQuery UI Datepicker emphasis on selected dates */ 15 | .ui-datepicker .ui-datepicker-calendar .ui-state-highlight a { 16 | background: #743620 none; 17 | color: white; 18 | } 19 | -------------------------------------------------------------------------------- /demos/full-year.html: -------------------------------------------------------------------------------- 1 | 11 |
12 | 21 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-ui-multidatespicker", 3 | "description": "Extension to the jQuery UI Calendar allowing multiple selections", 4 | "license": "(MIT OR GPLv2)", 5 | "authors": ["Luca Lauretta "], 6 | "main": ["jquery-ui.multidatespicker.js", "jquery-ui.multidatespicker.css"], 7 | "dependencies": { 8 | "jquery": "1.9.1 - 3.7.x", 9 | "jquery-ui": "1.7.0 - 1.14.x" 10 | }, 11 | "keywords": ["jquery", "jquery-ui", "calendar", "dates"], 12 | "ignore": ["**/.*", "*.ico", "*.png", "package.json", "index.html"] 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Run Tests on Pull Requests and Main Branch 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout the repository 17 | uses: actions/checkout@v2 18 | 19 | - name: Set up Node.js 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: "16" 23 | 24 | - name: Install dependencies 25 | run: npm install 26 | 27 | - name: Run tests 28 | run: npm test 29 | 30 | - name: Upload coverage reports to Codecov 31 | uses: codecov/codecov-action@v5 32 | with: 33 | token: ${{ secrets.CODECOV_TOKEN }} 34 | slug: dubrox/Multiple-Dates-Picker-for-jQuery-UI 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MultiDatesPicker for jQuery UI 2 | 3 | ![codecov](https://codecov.io/gh/dubrox/Multiple-Dates-Picker-for-jQuery-UI/branch/main/graph/badge.svg) 4 | 5 | This plugin extends the jQuery UI datepicker clendar, allowing to select more than one date, 6 | picking them one by one or by ranges relative to the clicked date. 7 | 8 | Take a look at [some demos](http://dubrox.github.io/Multiple-Dates-Picker-for-jQuery-UI/#demos). 9 | 10 | ## Install 11 | 12 | With any of the following package managers: 13 | 14 | `bower install jquery-ui-multidatespicker` 15 | 16 | `npm install jquery-ui-multidatespicker` 17 | 18 | `yarn add jquery-ui-multidatespicker` 19 | 20 | Download the [zip](https://github.com/dubrox/Multiple-Dates-Picker-for-jQuery-UI/archive/latest.zip) 21 | and place wherever you need it: 22 | 23 | ## Use 24 | 25 | For documentation and examples, check 26 | [the official MDP page](https://dubrox.github.io/Multiple-Dates-Picker-for-jQuery-UI). 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-ui-multidatespicker", 3 | "description": "Extension to the jQuery UI Calendar allowing multiple selections", 4 | "author": "Luca Lauretta ", 5 | "license": "(MIT OR GPLv2)", 6 | "version": "1.6.8", 7 | "main": "jquery-ui.multidatespicker.js", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/dubrox/Multiple-Dates-Picker-for-jQuery-UI.git" 11 | }, 12 | "dependencies": { 13 | "jquery": "1.9.1 - 3.7.x", 14 | "jquery-ui": "1.7.0 - 1.14.x" 15 | }, 16 | "keywords": [ 17 | "jquery", 18 | "jquery-ui", 19 | "calendar", 20 | "datepicker", 21 | "dates", 22 | "multidatespicker", 23 | "multidates", 24 | "multiple", 25 | "range" 26 | ], 27 | "type": "commonjs", 28 | "bugs": { 29 | "url": "https://github.com/dubrox/Multiple-Dates-Picker-for-jQuery-UI/issues" 30 | }, 31 | "homepage": "https://dubrox.github.io/Multiple-Dates-Picker-for-jQuery-UI", 32 | "scripts": { 33 | "format": "prettier --write --ignore-unknown", 34 | "lint": "eslint --cache --fix", 35 | "test": "jest", 36 | "prepare": "husky" 37 | }, 38 | "lint-staged": { 39 | "*.js": "npm run lint", 40 | "*.*": "npm run format" 41 | }, 42 | "devDependencies": { 43 | "@eslint/js": "^9.18.0", 44 | "eslint": "^9.18.0", 45 | "eslint-config-prettier": "^10.0.1", 46 | "esm": "^3.2.25", 47 | "globals": "^15.14.0", 48 | "husky": "^9.1.7", 49 | "jest": "^29.7.0", 50 | "jest-environment-jsdom": "^29.7.0", 51 | "jsom": "^1.0.0", 52 | "lint-staged": "^15.3.0", 53 | "prettier": "3.4.2" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /demos/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | MDP demo page 5 | 9 | 10 | 11 | 12 | 13 | 25 | 58 | 59 | 60 |
61 | Code used 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var extend = Object.assign || require("util")._extend; 2 | var Path = require("path"); 3 | 4 | /* 5 | * store original global keys 6 | */ 7 | var blacklist = Object.keys(global); 8 | blacklist.push("constructor"); 9 | 10 | /* 11 | * default config 12 | */ 13 | var defaults = { 14 | url: "http://localhost", 15 | globalize: true, 16 | console: true, 17 | useEach: false, 18 | skipWindowCheck: false, 19 | html: 20 | "" + 21 | "", 22 | }; 23 | 24 | /* 25 | * simple jsdom integration. 26 | * You can pass jsdom options in, too: 27 | * 28 | * require('./support/jsdom')({ 29 | * src: [ jquery ] 30 | * }) 31 | */ 32 | module.exports = function (_options) { 33 | var options = extend(extend({}, defaults), _options); 34 | 35 | var keys = []; 36 | 37 | var before = options.useEach ? global.beforeEach : global.before; 38 | var after = options.useEach ? global.afterEach : global.after; 39 | 40 | /* 41 | * register jsdom before the entire test suite 42 | */ 43 | 44 | before(function (next) { 45 | if (global.window && !options.skipWindowCheck) { 46 | throw new Error( 47 | "mocha-jsdom: already a browser environment, or mocha-jsdom invoked " + 48 | "twice. use 'skipWindowCheck' to disable this check.", 49 | ); 50 | } 51 | require("jsdom/lib/old-api").env( 52 | extend(extend({}, options), { done: done }), 53 | ); 54 | 55 | function done(errors, window) { 56 | if (options.globalize) { 57 | propagateToGlobal(window); 58 | } else { 59 | global.window = window; 60 | } 61 | 62 | if (options.console) { 63 | window.console = global.console; 64 | } 65 | 66 | if (errors) { 67 | return next(getError(errors)); 68 | } 69 | 70 | next(null); 71 | } 72 | }); 73 | 74 | /* 75 | * undo keys from being propagated to global after the test suite 76 | */ 77 | after(function () { 78 | if (options.globalize) { 79 | keys.forEach(function (key) { 80 | delete global[key]; 81 | }); 82 | } else { 83 | delete global.window; 84 | } 85 | }); 86 | 87 | /* 88 | * propagate keys from `window` to `global` 89 | */ 90 | function propagateToGlobal(window) { 91 | for (var key in window) { 92 | if (!Object.prototype.hasOwnProperty.call(window, key)) continue; 93 | if (~blacklist.indexOf(key)) continue; 94 | if (global[key]) { 95 | if (process.env.JSDOM_VERBOSE) { 96 | console.warn( 97 | "[jsdom] Warning: skipping cleanup of global['" + key + "']", 98 | ); 99 | } 100 | continue; 101 | } 102 | 103 | keys.push(key); 104 | global[key] = window[key]; 105 | } 106 | } 107 | 108 | /* 109 | * re-throws jsdom errors 110 | */ 111 | function getError(errors) { 112 | var data = errors[0].data; 113 | var err = data.error; 114 | err.message = err.message + " [jsdom]"; 115 | 116 | // clean up stack trace 117 | if (err.stack) { 118 | err.stack = err.stack 119 | .split("\n") 120 | .reduce(function (list, line) { 121 | if (line.match(/node_modules.+(jsdom|mocha)/)) { 122 | return list; 123 | } 124 | 125 | line = line 126 | .replace(/file:\/\/.* 28 | 33 | 38 | 39 | 66 | 76 | 77 | 78 |
79 |
80 |

MultiDatesPicker for jQuery UI

81 |

82 | MDP is a little plugin that enables jQuery UI calendar to manage 83 | multiple dates with the following features: 84 |

85 |
    86 |
  • Select date ranges.
  • 87 |
  • Pick multiple dates not in sequence.
  • 88 |
  • Define a maximum number of pickable dates.
  • 89 |
  • 90 | Define a range X days from where it is possible to 91 | select Y dates. 92 |
  • 93 |
  • Define unavailable dates.
  • 94 |
95 | 96 |
97 | Star 107 | 108 | 116 | 120 |
121 |
122 | 123 |
124 |
125 |

Install

126 |

127 | Use bower, npm, yarn or 128 | download the 129 | zip. Refer to the 133 | README 137 | for the installation details. 138 |

139 |
140 | 141 |
142 |

Bugs or features request?

143 |

144 | Please use the issue tracker on 145 | GitHub Issues 149 |

150 |
151 |
152 | 153 |
154 |

How to use it

155 |

156 | Being an extension to jQuery UI DatePicker you need to include both 157 | jQuery and jQuery UI (with datepicker module included!) javascript 158 | files to your HTML page, and right after that, include 159 | MultiDatesPicker. 160 |

161 |

162 | To apply it to an element, do it the same way as you would do with 163 | jQuery UI datepicker, but write multiDatesPicker instead of 164 | datepicker: 165 |
166 | $(selector).multiDatesPicker(options_for_datepicker_and_mdp); 169 |

170 | 171 |

MultiDatesPicker specific options

172 |
    173 |
  • 174 |

    addDates

    175 |

    176 | Adds an array of dates specified in a string, milliseconds or 177 | javascript date object format. 178 |
    179 | NOTE: the string format you should pass to multiDatePicker 181 | depends on the localization of datepicker, see this page for 182 | more infos on how to configure it. 184 |

    185 |
  • 186 |
  • 187 |

    addDisabledDates

    188 |

    189 | Disables an array of dates specified in a string, milliseconds or 190 | javascript date object format. 191 |
    192 | NOTE: the string format you should pass to multiDatePicker 194 | depends on the localization of datepicker, see this page for 195 | more infos on how to configure it. 197 |

    198 |
  • 199 |
  • 200 |

    separator

    201 |

    202 | Allows to specify a custom separator for the string representation 203 | of the dates selected (defaults ", "). 204 |

    205 |
  • 206 |
  • 207 |

    mode

    208 |

    209 | Allows to enable a different MDP modes: 'normal' (default) or 210 | 'daysRange'. 211 |

    212 |

    normal mode options

    213 |
      214 |
    • 215 |

      maxPicks

      216 |

      217 | Number of dates allowed to be selected (see demo). 221 |

      222 |
    • 223 |
    • 224 |

      pickableRange

      225 |

      226 | Limits the range of dates available for selection to a certain 227 | number of days from the first selection (see demo). 231 |

      232 |
    • 233 |
    • 234 |

      adjustRangeToDisabled

      235 |

      236 | A boolean that allows to maintain the number of pickable days 237 | even in case there are disabled days within the range 238 | specified in 'pickableRange'.
      239 | See the corresponding demo 240 | and try toggling this flag to see the results. 241 |

      242 |
    • 243 |
    244 |

    daysRange mode options

    245 |
      246 |
    • 247 |

      autoselectRange

      248 |

      249 | Array of two integers: the first sets the beginning of the 250 | range relative to the date clicked on; the last sets the end 251 | of the range. Both numbers may be negative (see demo). 255 |

      256 |
    • 257 |
    258 |
  • 259 |
260 | 261 |

Available methods:

262 |
    263 |
  • 264 |

    compareDates( date1, date2 )

    265 |

    266 | Compares two dates returning 1, 0 or -1 if date2 is greater, equal 267 | or smaller than date1 respectively. 268 |

    269 |
  • 270 |
  • 271 |

    gotDate( date, type )

    272 |

    273 | Returns the index of the date in the dates array, or false in case 274 | that date is not found. 275 |
    276 | The parameter dates can be a string or a date object. 277 |

    278 |

    279 | Example: 280 | $('#simpliest-usage').multiDatesPicker('gotDate', new 282 | Date()); 284 |

    285 |
  • 286 |
  • 287 |

    addDates( dates, type )

    288 |

    289 | Adds one or more dates to the calendar. 290 |
    291 | The parameter dates can be a string, a date object or an array (of 292 | strings or javascript date objects). 293 |

    294 |

    295 | Example adding today: 296 | $('#simpliest-usage').multiDatesPicker('addDates', new 298 | Date()); 300 |

    301 |
  • 302 |
  • 303 |

    removeIndexes( indexes, type )

    304 |

    305 | Removes one or more dates from the dates array using their 306 | indexes. 307 |
    308 | The parameter indexes can be an integer or an array of integers. 309 |

    310 |

    311 | Example removing first date: 312 | $('#simpliest-usage').multiDatesPicker('removeIndexes', 314 | 0); 316 |

    317 |
  • 318 |
  • 319 |

    removeDates( dates, type )

    320 |

    321 | Removes one or more dates from the dates array using their dates. 322 |
    323 | The parameter dates can be a single value or an array of 324 | milliseconds, strings or date object. 325 |

    326 |

    327 | Example removing today date: 328 | $('#simpliest-usage').multiDatesPicker('removeDates', new 330 | Date()); 332 |

    333 |
  • 334 |
  • 335 |

    resetDates( type )

    336 |

    337 | Removes all dates. 338 |
    339 | The array of dates to reset can be of type 'picked' 340 | (default) or 'disabled'. 341 |

    342 |

    343 | Example resetting disabled dates: 344 | $('#simpliest-usage').multiDatesPicker('resetDates', 346 | 'disabled'); 348 |

    349 |
  • 350 |
  • 351 |

    toggleDate( date, type )

    352 |

    353 | Adds/removes a single date from the calendar. 354 |
    355 | The date can be passed as string or as javascript date object. 356 |

    357 |

    358 | Example toggling today: 359 | $('#simpliest-usage').multiDatesPicker('toggleDate', new 361 | Date()); 363 |

    364 |
  • 365 |
  • 366 |

    getDates( format, type )

    367 |

    368 | Retrives the array of dates associated with the multiDatesPicker 369 | in the specified format: "string" (default) for localized string 370 | format, or "object" for javascript date object format. 371 |

    372 |

    373 | Example: 374 | var dates = 376 | $('#simpliest-usage').multiDatesPicker('getDates'); 378 |

    379 |
  • 380 |
  • 381 |

    value( string )

    382 |

    383 | If no parameter is passed, returns the string value that would be 384 | used in input elements. Otherwise parses the string for dates to 385 | add. 386 |

    387 |

    388 | Get Example: 389 | var dates = 391 | $('#simpliest-usage').multiDatesPicker('value'); 393 |

    394 |

    395 | Set Example: 396 | $('#simpliest-usage').multiDatesPicker('value', '2/19/1985, 398 | 11/14/2009'); 400 |

    401 |
  • 402 |
  • 403 |

    destroy()

    404 |

    Destroys the MDP and Datepicker instances on the element.

    405 |

    406 | Example: 407 | $('#simpliest-usage').multiDatesPicker('destroy'); 410 |

    411 |
  • 412 |
413 |
414 | 415 |
416 |

Use cases

417 |

You can find MDP implemented in the following sites:

418 | 427 |

428 | If you're using MDP in your site and would like to share it, simply 429 | contact me. You'd get free ad from here and 430 | we get more examples of implementation from you :) 431 |

432 |
433 | 434 |
435 |

Demos

436 |

437 | Here are some demos for you to understand how it works and what you 438 | can obtain with it.
439 | To see how it is implemented simply check the source code of this 440 | page: I've tried to keep the code simple and clear :) 441 |

442 |
    443 |
  • 444 |

    Simplest usage

    445 | 446 |

    447 | Just apply the plugin to an HTML element and you're ready to 448 | select multiple dates :) 449 |

    450 |
  • 451 | 452 |
  • 453 |

    Custom date format

    454 | 455 |

    456 | Same as previous example, but using custom date formats and custom 457 | default day. 458 |

    459 |
  • 460 | 461 |
  • 462 |

    Pre-select dates

    463 | 464 |

    465 | The name says it all: you can preselect some dates specifying them 466 | in an array.
    467 | Dates in the array can be a mix of object date and string dates. 468 |

    469 |
  • 470 | 471 |
  • 472 |

    Disable dates

    473 | 474 |

    475 | Again, the name says it all: you can specify some dates to 476 | disable.
    477 | Dates in the array can be a mix of object date and string dates. 478 |

    479 |
  • 480 | 481 |
  • 482 |

    Disable calendar

    483 | 484 |

    Disable a calendar picking functionality.

    485 |
  • 486 | 487 |
  • 488 |

    Using altField

    489 | 490 |

    491 | A way to have a calendar always displayed and a field that fills 492 | with selected dates. 493 |

    494 |
  • 495 | 496 |
  • 497 |

    Set maximum picks

    498 | 499 |

    500 | Set the maximum number of dates that can be picked. 501 |

    502 |
  • 503 | 504 |
  • 505 |

    Use pickableRange and adjustRangeToDisabled

    506 | 507 |

    508 | Define a range of dates to be allowed after the first date have 509 | been picked.
    510 | Some dates have been disabled to show up how 511 | adjustRangeToDisabled 512 | works. 513 |

    514 |
  • 515 | 516 |
  • 517 |

    Days range

    518 | 519 |

    520 | This way you can automatically select a range of days with respect 521 | to the day clicked. 522 |

    523 |

    524 | In this example the day range is set to [0, 5], which means from 525 | the day clicked to 5 days in advance.
    526 | You can also specify other combinations like: 527 |

    528 |
      529 |
    • 530 | [-1,2] from a day before to two days after the clicked day 531 |
    • 532 |
    • 533 | [1,3] from the day after to three days after the clicked day 534 |
    • 535 |
    • ...
    • 536 |
    537 |
  • 538 | 539 |
  • 540 |

    Min and Max date

    541 | 542 |

    543 | As with the jQuery Datespicker, you can define a minimum and 544 | maximum date from where to pick dates.
    545 | The values are relative to the current date. 546 |

    547 |
  • 548 | 549 |
  • 550 |

    From input

    551 | 552 |

    553 | Just an example of how it would work with an input text field. 554 |

    555 |
  • 556 | 557 |
  • 558 |

    UI Calendar methods

    559 | 560 |

    561 | Define beforeShow, beforeShowDay*, 563 | onSelect and onClose to apply custom 564 | behaviours. 565 |

    566 |

    567 | * Being that MDP needs 568 | beforeShowDay to change the way jQuery datepicker 569 | behaves, there may be cases in which your custom definition in MDP 570 | won't produce the same effects as if you were using it with 571 | datepicker alone. 572 |

    573 |
  • 574 | 575 |
  • 576 |

    Full year

    577 | 578 |

    579 | Just an example of how it would look to show the full year. 580 |

    581 |
  • 582 |
583 |
584 | 585 |
586 |

Tips

587 |

588 | MDP comes with a small CSS file that applies the following styles: 589 |

590 | 591 |

592 | To even further customize the way the calendar looks, just modify the 593 | jQuery UI's theme you're using.
594 | Multiple Dates Picker is about adding functionality not style :) 595 |

596 |
597 | 598 |
599 |

Things pending

600 |

601 | Apart from some features and bug fixes, there is need for a better 602 | documentation and a unit-test to guarantee that any improvements won't 603 | break the existent functionalities. 604 |

605 |

606 | I'll try to maintain this project in my spare time (it is not my 607 | primary business), and I welcome anyone who wants to help (just 608 | contact me :) 609 |

610 |
611 | 612 |
613 |

Contact me

614 |

615 | You're welcome to get in touch with me to collaborate to this project: 616 |

617 | 626 |
627 |
628 | 629 | 630 | -------------------------------------------------------------------------------- /__tests__/multidatespicker.test.js: -------------------------------------------------------------------------------- 1 | // @jest-environment jsdom 2 | 3 | describe("MDP Initialization", function () { 4 | let $input; 5 | let spyDatepickerUpdate; 6 | const normalDatepickerUpdates = 3; 7 | 8 | beforeEach(function () { 9 | document.body.innerHTML = ''; 10 | $input = $("#datepicker"); 11 | 12 | spyDatepickerUpdate = jest.spyOn($.datepicker, "_updateDatepicker"); 13 | 14 | // fix the offsetWidth to make the datepicker visible in jsdom 15 | jest.spyOn($input[0], "offsetWidth", "get").mockReturnValue(1); 16 | }); 17 | 18 | afterEach(function () { 19 | jest.restoreAllMocks(); 20 | }); 21 | 22 | it("should initialize the multi-date picker correctly", function () { 23 | $input.multiDatesPicker(); 24 | expect($input.hasClass("hasDatepicker")).toBe(true); 25 | }); 26 | 27 | it("should call a beforeShow event with default behavior", function () { 28 | $input.multiDatesPicker(); 29 | 30 | // triggers the beforeShow event 31 | $input.trigger("focus"); 32 | 33 | expect(spyDatepickerUpdate).toHaveBeenCalledTimes(normalDatepickerUpdates); 34 | }); 35 | 36 | it("should call a custom beforeShow event that interrupts the default behavior", function () { 37 | const beforeShow = jest.fn((_) => false); // returning false to interrupt the default behavior 38 | $input.multiDatesPicker({ beforeShow }); 39 | 40 | // triggers the beforeShow event with a custom function 41 | $input.trigger("focus"); 42 | 43 | expect(beforeShow).toHaveBeenCalled(); 44 | expect(spyDatepickerUpdate).toHaveBeenCalledTimes( 45 | normalDatepickerUpdates - 1, 46 | ); 47 | }); 48 | }); 49 | 50 | describe("setMode", function () { 51 | let $input, date; 52 | 53 | beforeEach(function () { 54 | // Initialize the input field 55 | document.body.innerHTML = ''; 56 | $input = $("#datepicker"); 57 | date = new Date(); 58 | 59 | $input.multiDatesPicker({ 60 | mode: "normal", 61 | maxPicks: 5, 62 | pickableRange: 7, 63 | adjustRangeToDisabled: true, 64 | addDisabledDates: [ 65 | new Date(date.setDate(10)), 66 | new Date(date.setDate(15)), 67 | ], 68 | }); 69 | }); 70 | 71 | it("should set mode to normal and restrict selection to maxPicks", function () { 72 | $input.multiDatesPicker("addDates", "10/10/2024"); 73 | $input.multiDatesPicker("addDates", "10/11/2024"); 74 | 75 | const selectedDates = $input.multiDatesPicker("getDates"); 76 | expect(selectedDates.length).toBe(2); 77 | expect(selectedDates).toContain("10/10/2024"); 78 | expect(selectedDates).toContain("10/11/2024"); 79 | expect(selectedDates).not.toContain("10/12/2024"); 80 | }); 81 | 82 | it("should set pickableRange and adjust minDate and maxDate accordingly", function () { 83 | const options = { pickableRange: 5 }; 84 | $input.multiDatesPicker("setMode", options); 85 | 86 | const currentDate = new Date(); 87 | const maxDate = new Date(currentDate); 88 | maxDate.setDate(currentDate.getDate() + options.pickableRange); 89 | $input.datepicker("option", "maxDate", maxDate); 90 | 91 | const actualMaxDate = $input.datepicker("option", "maxDate"); 92 | expect(actualMaxDate).toEqual(maxDate); 93 | }); 94 | 95 | it("should limit the pickable range to 7 days", function () { 96 | const options = $input.multiDatesPicker(); 97 | const mode = options[0].multiDatesPicker.mode; 98 | 99 | var startDate = new Date(); 100 | var endDate = new Date(startDate); 101 | endDate.setDate(startDate.getDate() + 6); 102 | 103 | $input.multiDatesPicker("addDates", [startDate, endDate]); 104 | var selectedDates = $input.multiDatesPicker("getDates"); 105 | var selectedStartDate = new Date(selectedDates[0]); 106 | var selectedEndDate = new Date(selectedDates[selectedDates.length - 1]); 107 | var range = (selectedEndDate - selectedStartDate) / (1000 * 60 * 60 * 24); 108 | 109 | expect(range).toBeLessThanOrEqual(7); 110 | expect(mode).toBe("normal"); 111 | }); 112 | 113 | it("should not allow selection of disabled dates in normal mode", function () { 114 | $input.multiDatesPicker("addDates", [new Date(date.setDate(10))]); 115 | const options = $input.multiDatesPicker(); 116 | const mode = options[0].multiDatesPicker.mode; 117 | 118 | var selectedDates = $input.multiDatesPicker("getDates"); 119 | var disabledDates = [ 120 | new Date(date.setDate(10)), 121 | new Date(date.setDate(15)), 122 | ]; 123 | var isDisabledDateSelected = selectedDates.some(function (date) { 124 | return disabledDates.some(function (disabledDate) { 125 | return new Date(date).getTime() === disabledDate.getTime(); 126 | }); 127 | }); 128 | 129 | expect(isDisabledDateSelected).toBe(false); 130 | expect(mode).toBe("normal"); 131 | }); 132 | }); 133 | 134 | describe("init", function () { 135 | let $input; 136 | 137 | beforeEach(function () { 138 | // Set up the input element before each test 139 | document.body.innerHTML = ''; 140 | $input = $("#datepicker"); 141 | }); 142 | 143 | it("should apply default settings when no options are provided", function () { 144 | // Call the init function without options 145 | $input.multiDatesPicker("init"); 146 | 147 | // Check that the datepicker was initialized with default settings 148 | const dateFormat = $input.datepicker("option", "dateFormat"); 149 | expect(dateFormat).toEqual("mm/dd/yy"); // Default format 150 | }); 151 | 152 | it("should merge custom options with default settings", function () { 153 | // Call init with custom options 154 | $input.multiDatesPicker("init", { 155 | dateFormat: "dd-mm-yy", 156 | minDate: new Date("10/01/2024"), 157 | maxDate: new Date("10/20/2024"), 158 | }); 159 | 160 | // Check that the custom dateFormat is applied 161 | const dateFormat = $input.datepicker("option", "dateFormat"); 162 | expect(dateFormat).toEqual("dd-mm-yy"); 163 | 164 | // Check minDate and maxDate options 165 | const minDate = $input.datepicker("option", "minDate"); 166 | const maxDate = $input.datepicker("option", "maxDate"); 167 | expect(minDate).toEqual(new Date("10/01/2024")); 168 | expect(maxDate).toEqual(new Date("10/20/2024")); 169 | }); 170 | 171 | it("should call custom onSelect event and toggle date on select", function () { 172 | const onSelectSpy = jest.fn(); 173 | 174 | // Call init with custom onSelect 175 | $input.multiDatesPicker("init", { 176 | onSelect: onSelectSpy, 177 | }); 178 | 179 | // Simulate selecting a date 180 | $input.datepicker("setDate", "10/11/2024"); 181 | $input.datepicker("option", "onSelect").call($input[0], "10/11/2024"); 182 | 183 | // Check if custom onSelect is called 184 | expect(onSelectSpy).toHaveBeenCalled(); 185 | 186 | // Check if the date was toggled in the multiDatesPicker instance 187 | const pickedDates = $input.multiDatesPicker("getDates"); 188 | expect(pickedDates).toContain("10/11/2024"); 189 | }); 190 | 191 | it("should correctly set disabled dates and update calendar range", function () { 192 | const $input = $('').appendTo( 193 | "body", 194 | ); 195 | 196 | // Initialize with options that set minDate and maxDate 197 | $input.multiDatesPicker("init", { 198 | minDate: "01/01/2024", 199 | maxDate: "12/31/2024", 200 | addDisabledDates: ["01/15/2024", "01/16/2024"], 201 | }); 202 | 203 | const minDate = $input.datepicker("option", "minDate"); 204 | const maxDate = $input.datepicker("option", "maxDate"); 205 | 206 | expect(minDate).not.toBeNull(); 207 | expect(maxDate).not.toBeNull(); 208 | 209 | // Clean up 210 | $input.remove(); 211 | }); 212 | 213 | it("should update altField with selected dates if specified", function () { 214 | // Set up altField 215 | document.body.innerHTML += ''; 216 | const $altField = $("#altField"); 217 | 218 | // Call init with altField option 219 | $input.multiDatesPicker("init", { 220 | altField: "#altField", 221 | }); 222 | 223 | // Simulate selecting a date 224 | $input.datepicker("setDate", "10/15/2024"); 225 | $input.datepicker("option", "onSelect").call($input[0], "10/15/2024"); 226 | 227 | // Check that the altField is updated with the selected date 228 | expect($altField.val()).toEqual("10/15/2024"); 229 | }); 230 | }); 231 | 232 | describe("invalid methods", function () { 233 | let $input; 234 | beforeEach(function () { 235 | document.body.innerHTML = ''; 236 | $input = $("#datepicker"); 237 | }); 238 | 239 | it("should error on non-existing method", function () { 240 | expect(function () { 241 | $input.multiDatesPicker("happyNewYear"); 242 | }).toThrowError( 243 | "Method happyNewYear does not exist on jQuery.multiDatesPicker", 244 | ); 245 | }); 246 | }); 247 | 248 | describe("dateConvert", function () { 249 | let $input; 250 | beforeEach(function () { 251 | document.body.innerHTML = ''; 252 | $input = $("#datepicker"); 253 | }); 254 | 255 | it("should error on unsupported date conversion", function () { 256 | expect(function () { 257 | $input.multiDatesPicker("dateConvert", {}, "object"); 258 | }).toThrowError("Received date is in a non supported format!"); 259 | }); 260 | }); 261 | 262 | describe("addDates", function () { 263 | let $input; 264 | beforeEach(function () { 265 | document.body.innerHTML = ''; 266 | $input = $("#datepicker"); 267 | }); 268 | 269 | it("should add a date", function () { 270 | $input.multiDatesPicker(); 271 | try { 272 | $input.multiDatesPicker("addDates", "05/15/2023"); 273 | const dates = $input.multiDatesPicker("getDates"); 274 | 275 | expect(dates.length).toBe(1); 276 | expect(dates[0]).toBe("05/15/2023"); 277 | } catch (error) { 278 | console.error("Error in add date test:", error); 279 | throw error; 280 | } 281 | }); 282 | 283 | it("should ignore invalid date string", function () { 284 | expect(function () { 285 | $input.multiDatesPicker("addDates", "invalid-date"); 286 | }).toThrowError("Missing number"); 287 | }); 288 | 289 | it("should ignore non-date objects", function () { 290 | expect(function () { 291 | $input.multiDatesPicker("addDates", { someKey: "someValue" }); 292 | }).toThrowError("Empty array of dates received."); 293 | }); 294 | 295 | it("should not allow adding a date beyond maxDate", function () { 296 | // Set maxDate to today 297 | var today = new Date(); 298 | $input.multiDatesPicker({ 299 | maxDate: today, 300 | }); 301 | 302 | // Add a future date beyond the maxDate 303 | var futureDate = new Date(today); 304 | futureDate.setDate(today.getDate() + 10); 305 | 306 | expect(function () { 307 | $input.multiDatesPicker("addDates", futureDate); 308 | }).toThrowError("Empty array of dates received."); 309 | }); 310 | 311 | it("should handle null input as invalid date", function () { 312 | expect(function () { 313 | // Trying to add null as a date 314 | $input.multiDatesPicker("addDates", null); 315 | }).toThrowError("Cannot read properties of null (reading 'length')"); 316 | }); 317 | }); 318 | 319 | describe("compareDates", function () { 320 | let $input; 321 | 322 | beforeEach(function () { 323 | document.body.innerHTML = ''; 324 | $input = $("#datepicker"); 325 | }); 326 | 327 | it("should return 0 when dates are the same", function () { 328 | const date1 = new Date("2024-10-10"); 329 | const date2 = new Date("2024-10-10"); 330 | const result = $input.multiDatesPicker("compareDates", date1, date2); 331 | 332 | expect(result).toBe(0); // They are the same day 333 | }); 334 | 335 | it("should return a positive number when date1 is later than date2", function () { 336 | const date1 = new Date("2024-10-15"); 337 | const date2 = new Date("2024-10-10"); 338 | const result = $input.multiDatesPicker("compareDates", date1, date2); 339 | 340 | expect(result).toBeGreaterThan(0); // date1 is later than date2 341 | }); 342 | 343 | it("should return a negative number when date1 is earlier than date2", function () { 344 | const date1 = new Date("2024-10-05"); 345 | const date2 = new Date("2024-10-10"); 346 | const result = $input.multiDatesPicker("compareDates", date1, date2); 347 | 348 | expect(result).toBeLessThan(0); 349 | }); 350 | 351 | it("should compare dates with different years", function () { 352 | const date1 = new Date("2025-10-10"); 353 | const date2 = new Date("2024-10-10"); 354 | const result = $input.multiDatesPicker("compareDates", date1, date2); 355 | 356 | expect(result).toBeGreaterThan(0); 357 | }); 358 | 359 | it("should compare dates with different months in the same year", function () { 360 | const date1 = new Date("2024-11-10"); 361 | const date2 = new Date("2024-10-10"); 362 | const result = $input.multiDatesPicker("compareDates", date1, date2); 363 | 364 | expect(result).toBeGreaterThan(0); 365 | }); 366 | 367 | it("should compare dates with different days in the same month", function () { 368 | const date1 = new Date("2024-10-12"); 369 | const date2 = new Date("2024-10-10"); 370 | const result = $input.multiDatesPicker("compareDates", date1, date2); 371 | 372 | expect(result).toBeGreaterThan(0); 373 | }); 374 | }); 375 | 376 | describe("removeDates", function () { 377 | let $input; 378 | 379 | function formatDate(date) { 380 | if (date === undefined) return undefined; // Handle undefined input 381 | const d = new Date(date); 382 | let month = "" + (d.getMonth() + 1); 383 | let day = "" + d.getDate(); 384 | const year = d.getFullYear(); 385 | 386 | if (month.length < 2) month = "0" + month; 387 | if (day.length < 2) day = "0" + day; 388 | 389 | return [month, day, year].join("/"); 390 | } 391 | 392 | beforeEach(function () { 393 | // Initialize the input with some dates 394 | document.body.innerHTML = ''; 395 | $input = $("#datepicker"); 396 | 397 | $input.multiDatesPicker({ 398 | addDates: ["10/10/2024", "10/12/2024", "10/14/2024", "10/16/2024"], 399 | }); 400 | }); 401 | 402 | it("should remove a single date", function () { 403 | const removed = $input.multiDatesPicker("removeDates", "10/12/2024"); 404 | 405 | const formattedRemoved = removed.map(formatDate); 406 | expect(formattedRemoved).toEqual(["10/12/2024"]); 407 | 408 | const remainingDates = $input.multiDatesPicker("getDates"); 409 | const formattedRemaining = remainingDates.map(formatDate); 410 | expect(formattedRemaining).toEqual([ 411 | "10/10/2024", 412 | "10/14/2024", 413 | "10/16/2024", 414 | ]); 415 | }); 416 | 417 | it("should remove multiple dates", function () { 418 | const removed = $input.multiDatesPicker("removeDates", [ 419 | "10/12/2024", 420 | "10/16/2024", 421 | ]); 422 | 423 | const formattedRemoved = removed.map(formatDate); 424 | expect(formattedRemoved).toEqual(["10/12/2024", "10/16/2024"]); 425 | 426 | const remainingDates = $input.multiDatesPicker("getDates"); 427 | const formattedRemaining = remainingDates.map(formatDate); 428 | expect(formattedRemaining).toEqual(["10/10/2024", "10/14/2024"]); 429 | }); 430 | 431 | it("should not remove a date that does not exist", function () { 432 | const removed = $input.multiDatesPicker("removeDates", "10/18/2024"); 433 | 434 | const formattedRemoved = removed.map(formatDate); 435 | expect(formattedRemoved).toEqual([undefined]); 436 | 437 | const remainingDates = $input.multiDatesPicker("getDates"); 438 | const formattedRemaining = remainingDates.map(formatDate); 439 | expect(formattedRemaining).toEqual([ 440 | "10/10/2024", 441 | "10/12/2024", 442 | "10/14/2024", 443 | "10/16/2024", 444 | ]); 445 | }); 446 | }); 447 | 448 | describe("toggleDate", function () { 449 | let $input; 450 | 451 | beforeEach(function () { 452 | document.body.innerHTML = ''; 453 | $input = $("#datepicker"); 454 | $input.multiDatesPicker({ 455 | addDates: ["10/10/2024", "10/12/2024", "10/14/2024"], 456 | }); 457 | }); 458 | 459 | it("should add a date that does not exist", function () { 460 | $input.multiDatesPicker("toggleDate", "10/16/2024"); 461 | const remainingDates = $input.multiDatesPicker("getDates"); 462 | expect(remainingDates).toContain("10/16/2024"); 463 | expect(remainingDates.length).toBe(4); 464 | }); 465 | 466 | it("should remove a date that exists", function () { 467 | $input.multiDatesPicker("toggleDate", "10/12/2024"); 468 | const remainingDates = $input.multiDatesPicker("getDates"); 469 | expect(remainingDates).not.toContain("10/12/2024"); 470 | expect(remainingDates.length).toBe(2); 471 | }); 472 | 473 | it("should handle daysRange mode", function () { 474 | $input.multiDatesPicker("destroy"); 475 | $input.multiDatesPicker({ 476 | mode: "daysRange", 477 | autoselectRange: [0, 2], 478 | }); 479 | 480 | $input.multiDatesPicker("toggleDate", "10/10/2024"); 481 | const remainingDates = $input.multiDatesPicker("getDates"); 482 | expect(remainingDates.length).toBe(2); 483 | expect(remainingDates).toContain("10/10/2024"); 484 | expect(remainingDates).toContain("10/11/2024"); 485 | }); 486 | 487 | it("should handle daysRange inverting the order of dates", function () { 488 | $input.multiDatesPicker("destroy"); 489 | $input.multiDatesPicker({ 490 | mode: "daysRange", 491 | autoselectRange: [2, 0], 492 | }); 493 | const date = "10/10/2024"; 494 | $input.multiDatesPicker("toggleDate", date); 495 | const dates = $input.multiDatesPicker("getDates"); 496 | expect(dates[0]).toBe(date); 497 | }); 498 | 499 | it("should throw an error when no date is provided", function () { 500 | expect(function () { 501 | $input.multiDatesPicker("toggleDate"); 502 | }).toThrow(); 503 | }); 504 | }); 505 | 506 | describe("multiDatesPicker value function", function () { 507 | let $input; 508 | 509 | beforeEach(function () { 510 | // Initialize the multiDatesPicker with a separator 511 | $input = $("").multiDatesPicker({ 512 | separator: ",", 513 | }); 514 | 515 | // Add dates to the input 516 | $input.multiDatesPicker("addDates", [ 517 | "10/10/2024", 518 | "10/12/2024", 519 | "10/14/2024", 520 | ]); 521 | }); 522 | 523 | it("should set the value by adding dates from a string", function () { 524 | const dateString = "10/16/2024,10/18/2024"; 525 | 526 | // Clear previous dates before setting new ones 527 | $input.multiDatesPicker("resetDates", "picked"); // Clear previously picked dates 528 | $input.multiDatesPicker("value", dateString); // Set new dates 529 | 530 | const selectedDates = $input.multiDatesPicker("getDates"); 531 | expect(selectedDates).toEqual(["10/16/2024", "10/18/2024"]); // Check if new dates are added 532 | }); 533 | 534 | it("should return the current dates as a string", function () { 535 | const valueString = $input.multiDatesPicker("value"); // Get dates as a string 536 | expect(valueString).toEqual("10/10/2024,10/12/2024,10/14/2024"); // Should return pre-set dates 537 | }); 538 | 539 | it("should return an empty string when no dates are selected", function () { 540 | // Initialize without any pre-set dates for this test 541 | $input = $("").multiDatesPicker({ separator: "," }); 542 | const valueString = $input.multiDatesPicker("value"); 543 | expect(valueString).toEqual(""); // Expect empty string when no dates are set 544 | }); 545 | }); 546 | 547 | describe("gotDate", function () { 548 | let $input; 549 | 550 | beforeEach(function () { 551 | // Initialize the multiDatesPicker with multiple dates 552 | document.body.innerHTML = ''; 553 | $input = $("#datepicker"); 554 | // $input.multiDatesPicker('addDates', ['10/10/2024', '10/12/2024', '10/14/2024']); 555 | 556 | // Add picked dates 557 | $input.multiDatesPicker("addDates", [ 558 | "10/10/2024", 559 | "10/12/2024", 560 | "10/14/2024", 561 | ]); 562 | 563 | // Add disabled dates 564 | $input.multiDatesPicker( 565 | "addDates", 566 | ["10/11/2024", "10/13/2024"], 567 | "disabled", 568 | ); 569 | }); 570 | 571 | it("should return the index of an existing date in the picked dates", function () { 572 | const index = $input.multiDatesPicker("gotDate", "10/12/2024"); 573 | expect(index).toEqual(1); // Index should be 1 for '10/12/2024' 574 | }); 575 | 576 | it("should return false for a non-existing date", function () { 577 | const index = $input.multiDatesPicker("gotDate", "10/15/2024"); 578 | expect(index).toEqual(false); 579 | }); 580 | 581 | it("should return the index of an existing date in the disabled dates", function () { 582 | const index = $input.multiDatesPicker("gotDate", "10/13/2024", "disabled"); 583 | expect(index).toEqual(1); 584 | }); 585 | 586 | it("should default to checking picked dates when no type is provided", function () { 587 | const index = $input.multiDatesPicker("gotDate", "10/12/2024"); 588 | expect(index).toEqual(1); 589 | }); 590 | }); 591 | 592 | describe("removeIndexes", function () { 593 | let $input; 594 | 595 | beforeEach(function () { 596 | document.body.innerHTML = ''; 597 | $input = $("#datepicker"); 598 | $input.multiDatesPicker("addDates", [ 599 | "10/10/2024", 600 | "10/12/2024", 601 | "10/14/2024", 602 | ]); 603 | }); 604 | 605 | it("should remove a single index from picked dates", function () { 606 | $input.multiDatesPicker("removeIndexes", 1); 607 | const pickedDates = $input.multiDatesPicker("getDates"); 608 | 609 | expect(pickedDates).toEqual(["10/10/2024", "10/14/2024"]); 610 | }); 611 | 612 | it("should remove multiple indexes from picked dates", function () { 613 | $input.multiDatesPicker("removeIndexes", [0, 2]); 614 | const pickedDates = $input.multiDatesPicker("getDates"); 615 | 616 | expect(pickedDates).toEqual(["10/12/2024"]); 617 | }); 618 | }); 619 | 620 | describe("resetDates", function () { 621 | let $input; 622 | 623 | beforeEach(function () { 624 | document.body.innerHTML = ''; 625 | $input = $("#datepicker"); 626 | $input.multiDatesPicker("addDates", [ 627 | "10/10/2024", 628 | "10/12/2024", 629 | "10/14/2024", 630 | ]); 631 | }); 632 | 633 | it("should reset picked dates", function () { 634 | let pickedDates = $input.multiDatesPicker("getDates"); 635 | 636 | expect(pickedDates).toEqual(["10/10/2024", "10/12/2024", "10/14/2024"]); 637 | 638 | $input.multiDatesPicker("resetDates", "picked"); 639 | 640 | pickedDates = $input.multiDatesPicker("getDates"); 641 | expect(pickedDates).toEqual([]); 642 | }); 643 | 644 | it("should reset picked dates by default if no type is provided", function () { 645 | let pickedDates = $input.multiDatesPicker("getDates"); 646 | expect(pickedDates).toEqual(["10/10/2024", "10/12/2024", "10/14/2024"]); 647 | 648 | $input.multiDatesPicker("resetDates"); 649 | 650 | pickedDates = $input.multiDatesPicker("getDates"); 651 | expect(pickedDates).toEqual([]); 652 | }); 653 | }); 654 | 655 | describe("getDates", function () { 656 | let $input; 657 | 658 | beforeEach(function () { 659 | document.body.innerHTML = ''; 660 | $input = $("#datepicker"); 661 | 662 | // Add picked dates 663 | $input.multiDatesPicker("addDates", [ 664 | "10/10/2024", 665 | "10/12/2024", 666 | "10/14/2024", 667 | ]); 668 | 669 | // Add disabled dates 670 | $input.multiDatesPicker( 671 | "addDates", 672 | ["10/11/2024", "10/13/2024"], 673 | "disabled", 674 | ); 675 | }); 676 | 677 | it("should return picked dates as a string array by default", function () { 678 | const pickedDates = $input.multiDatesPicker("getDates"); 679 | expect(pickedDates).toEqual(["10/10/2024", "10/12/2024", "10/14/2024"]); 680 | }); 681 | 682 | it("should return disabled dates as a string array", function () { 683 | const disabledDates = $input.multiDatesPicker( 684 | "getDates", 685 | "string", 686 | "disabled", 687 | ); 688 | expect(disabledDates).toEqual(["10/11/2024", "10/13/2024"]); 689 | }); 690 | 691 | it('should return picked dates as objects when format is "object"', function () { 692 | const pickedDates = $input.multiDatesPicker("getDates", "object"); 693 | expect(pickedDates).toEqual([ 694 | new Date("10/10/2024"), 695 | new Date("10/12/2024"), 696 | new Date("10/14/2024"), 697 | ]); 698 | }); 699 | 700 | it('should return disabled dates as objects when format is "object"', function () { 701 | const disabledDates = $input.multiDatesPicker( 702 | "getDates", 703 | "object", 704 | "disabled", 705 | ); 706 | expect(disabledDates).toEqual([ 707 | new Date("10/11/2024"), 708 | new Date("10/13/2024"), 709 | ]); 710 | }); 711 | 712 | it('should return picked dates as numbers when format is "number"', function () { 713 | const pickedDates = $input.multiDatesPicker("getDates", "number"); 714 | expect(pickedDates).toEqual([ 715 | Date.parse("10/10/2024"), 716 | Date.parse("10/12/2024"), 717 | Date.parse("10/14/2024"), 718 | ]); 719 | }); 720 | 721 | it('should return disabled dates as numbers when format is "number"', function () { 722 | const disabledDates = $input.multiDatesPicker( 723 | "getDates", 724 | "number", 725 | "disabled", 726 | ); 727 | expect(disabledDates).toEqual([ 728 | Date.parse("10/11/2024"), 729 | Date.parse("10/13/2024"), 730 | ]); 731 | }); 732 | 733 | it("should throw an error for unsupported formats", function () { 734 | expect(function () { 735 | $input.multiDatesPicker("getDates", "unsupportedFormat"); 736 | }).toThrowError('Format "unsupportedFormat" not supported!'); 737 | }); 738 | }); 739 | 740 | describe("sumDays", function () { 741 | let $input; 742 | 743 | beforeEach(function () { 744 | document.body.innerHTML = ''; 745 | $input = $("#datepicker"); 746 | }); 747 | 748 | it("should add positive days to a Date object", function () { 749 | const initialDate = new Date(2024, 9, 1); 750 | const n_days = 5; 751 | 752 | const result = $input.multiDatesPicker("sumDays", initialDate, n_days); 753 | const expectedDate = new Date(2024, 9, 6); 754 | 755 | expect(result).toEqual(expectedDate); 756 | }); 757 | 758 | it("should subtract days when n_days is negative", function () { 759 | const initialDate = new Date(2024, 9, 10); 760 | const n_days = -3; 761 | 762 | const result = $input.multiDatesPicker("sumDays", initialDate, n_days); 763 | const expectedDate = new Date(2024, 9, 7); 764 | 765 | expect(result).toEqual(expectedDate); 766 | }); 767 | 768 | it("should handle adding days to a date in string format", function () { 769 | const initialDate = "10/01/2024"; 770 | const n_days = 10; 771 | const result = $input.multiDatesPicker("sumDays", initialDate, n_days); 772 | expect(result).toBe("10/11/2024"); 773 | }); 774 | 775 | it("should handle leap years correctly", function () { 776 | const initialDate = new Date(2024, 1, 28); 777 | const n_days = 2; 778 | 779 | const result = $input.multiDatesPicker("sumDays", initialDate, n_days); 780 | const expectedDate = new Date(2024, 2, 1); 781 | 782 | expect(result).toEqual(expectedDate); 783 | }); 784 | 785 | it("should handle month overflow correctly", function () { 786 | const initialDate = new Date(2024, 9, 31); 787 | const n_days = 5; 788 | 789 | // Call sumDays and check result 790 | const result = $input.multiDatesPicker("sumDays", initialDate, n_days); 791 | const expectedDate = new Date(2024, 10, 5); 792 | 793 | expect(result).toEqual(expectedDate); 794 | }); 795 | }); 796 | 797 | describe("destroy", function () { 798 | let $input; 799 | 800 | beforeEach(function () { 801 | document.body.innerHTML = ''; 802 | $input = $("#datepicker"); 803 | $input.multiDatesPicker(); 804 | }); 805 | 806 | it("should set multiDatesPicker instance to null and destroy the datepicker", function () { 807 | expect($input[0].multiDatesPicker).toBeDefined(); 808 | 809 | $input.multiDatesPicker("destroy"); 810 | 811 | expect($input[0].multiDatesPicker).toBeNull(); 812 | expect($input.datepicker("getDate")).toBeNull(); 813 | }); 814 | }); 815 | --------------------------------------------------------------------------------