├── .npmignore
├── .jshintignore
├── website
├── images
│ └── anytime.png
├── js
│ └── index.js
├── styles
│ ├── _skin.scss
│ └── index.scss
└── index.jade
├── .gitignore
├── src
├── lib
│ ├── get-time-separator.js
│ ├── create-button.js
│ ├── get-year-list.js
│ ├── create-moment.js
│ ├── get-month-details.js
│ └── create-slider.js
├── anytime.css
├── anytime.d.ts
└── anytime.js
├── test
├── browser-env.js
├── get-month-details.test.js
├── create-moment.test.js
├── get-year-list.test.js
└── anytime.test.js
├── .jshintrc
├── package.json
├── README.md
├── gulpfile.js
└── dist
└── anytime.js
/.npmignore:
--------------------------------------------------------------------------------
1 | dist/
2 | test/
3 | website/
4 |
--------------------------------------------------------------------------------
/.jshintignore:
--------------------------------------------------------------------------------
1 | coverage/
2 | dist/
3 | node_modules/
4 | test/
5 | website/
6 | gulpfile.js
7 |
--------------------------------------------------------------------------------
/website/images/anytime.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bengourley/anytime/HEAD/website/images/anytime.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | coverage/
2 | node_modules/
3 | website/build/
4 | .DS_Store
5 | .zuulrc
6 | npm-debug.log
7 | .publish
8 |
--------------------------------------------------------------------------------
/src/lib/get-time-separator.js:
--------------------------------------------------------------------------------
1 | module.exports = getTimeSeparator
2 |
3 | function getTimeSeparator() {
4 | var colonEl = document.createElement('span')
5 | colonEl.classList.add('anytime-picker__time-separator')
6 | colonEl.textContent = ':'
7 | return colonEl
8 | }
9 |
--------------------------------------------------------------------------------
/src/lib/create-button.js:
--------------------------------------------------------------------------------
1 | module.exports = createButton
2 |
3 | function createButton(text, classes) {
4 | var button = document.createElement('button')
5 | classes.forEach(function (c) { button.classList.add(c) })
6 | button.textContent = text
7 | return button
8 | }
9 |
--------------------------------------------------------------------------------
/src/anytime.css:
--------------------------------------------------------------------------------
1 | .anytime-picker { display: none; position: absolute; top: 0; left: 0; }
2 | .anytime-picker--is-visible { display: block; }
3 | .anytime-picker__dates { width: 200px; }
4 | .anytime-picker__dates > * { box-sizing: border-box; width: 14.28%; display: inline-block; }
5 |
--------------------------------------------------------------------------------
/website/js/index.js:
--------------------------------------------------------------------------------
1 | var anytime = require('../../src/anytime')
2 | , time = document.getElementsByClassName('js-splash-input')[0]
3 | , button = document.getElementsByClassName('js-splash-button')[0]
4 | , picker = new anytime({ input: time, button: button, anchor: button })
5 |
6 | picker.render()
7 |
8 | window.picker = picker
9 |
--------------------------------------------------------------------------------
/test/browser-env.js:
--------------------------------------------------------------------------------
1 | module.exports = createBrowserEnv
2 |
3 | var jsdom = require('jsdom')
4 |
5 | function createBrowserEnv(cb) {
6 | jsdom.env('', function (errors, window) {
7 | if (errors) return cb(new Error(errors))
8 | global.window = window
9 | global.document = window.document
10 | cb()
11 | })
12 | }
13 |
--------------------------------------------------------------------------------
/src/lib/get-year-list.js:
--------------------------------------------------------------------------------
1 | module.exports = getYearList
2 |
3 | function getYearList(min, max) {
4 | if (parseInt(min, 10) !== min || parseInt(max, 10) !== max) throw new Error('min and max years must be integers')
5 | if (min > max) throw new Error('min year must be before max year')
6 | var years = []
7 | for (var i = min; i <= max; i++) years.push(i)
8 | return years
9 | }
10 |
--------------------------------------------------------------------------------
/src/lib/create-moment.js:
--------------------------------------------------------------------------------
1 | module.exports = createMoment
2 |
3 | function createMoment(value) {
4 | var m = this.options.moment
5 | , args = [ value !== null ? value : undefined ]
6 | if (typeof value === 'string') args.push(this.options.format)
7 | if (this.options.timezone && typeof m.tz === 'function') {
8 | args.push(this.options.timezone)
9 | return m.tz.apply(m, args)
10 | }
11 | return m.apply(null, args)
12 | }
13 |
--------------------------------------------------------------------------------
/src/lib/get-month-details.js:
--------------------------------------------------------------------------------
1 | module.exports = getMonthDetails
2 |
3 | var moment = require('moment')
4 |
5 | /*
6 | * Given the year and month, this function returns which day of the
7 | * week that month starts, and how many days it has.
8 | */
9 | function getMonthDetails(month, year) {
10 | var start = moment({ year: year, month: month })
11 | return { startDay: start.isoWeekday(), length: start.endOf('month').date() }
12 | }
13 |
--------------------------------------------------------------------------------
/test/get-month-details.test.js:
--------------------------------------------------------------------------------
1 | var getMonthDetails = require('../src/lib/get-month-details')
2 | , assert = require('assert')
3 |
4 | describe('getMonthDetails()', function () {
5 |
6 | it('should respond with the correct length and start day for a variety of examples', function () {
7 |
8 | // NB. Month is 0-based, day is 1-based starting from Monday
9 |
10 | assert.deepEqual(getMonthDetails(11, 2014), { startDay: 1, length: 31 })
11 | assert.deepEqual(getMonthDetails(0, 2015), { startDay: 4, length: 31 })
12 | assert.deepEqual(getMonthDetails(8, 1988), { startDay: 4, length: 30 })
13 | assert.deepEqual(getMonthDetails(1, 2004), { startDay: 7, length: 29 })
14 |
15 | })
16 |
17 | })
18 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | { "asi": true
2 | , "boss": true
3 | , "browser": true
4 | , "camelcase": true
5 | , "curly": false
6 | , "devel": false
7 | , "devel": true
8 | , "eqeqeq": true
9 | , "eqnull": true
10 | , "es5": false
11 | , "evil": false
12 | , "immed": false
13 | , "indent": 2
14 | , "jquery": true
15 | , "latedef": false
16 | , "laxbreak": true
17 | , "laxcomma": true
18 | , "maxcomplexity": 7
19 | , "maxdepth": 4
20 | , "maxstatements": 25
21 | , "newcap": true
22 | , "node": true
23 | , "noempty": false
24 | , "nonew": true
25 | , "quotmark": "single"
26 | , "smarttabs": true
27 | , "strict": false
28 | , "trailing": false
29 | , "undef": true
30 | , "unused": true
31 | , "predef":
32 | [ "describe"
33 | , "it"
34 | , "before"
35 | , "beforeEach"
36 | , "after"
37 | , "afterEach"
38 | ]
39 | }
40 |
--------------------------------------------------------------------------------
/src/lib/create-slider.js:
--------------------------------------------------------------------------------
1 | module.exports = createSlider
2 |
3 | function createSlider(options) {
4 |
5 | var sliderEl = document.createElement('div')
6 | sliderEl.classList.add('anytime-picker__slider')
7 | sliderEl.classList.add(options.className)
8 |
9 | var sliderTitleEl = document.createElement('span')
10 | sliderTitleEl.classList.add('anytime-picker__slider--title')
11 | sliderTitleEl.textContent = options.title
12 |
13 | sliderEl.appendChild(sliderTitleEl)
14 |
15 | var sliderInputEl = document.createElement('input')
16 | sliderInputEl.classList.add('anytime-picker__slider--input')
17 | sliderInputEl.type = 'range'
18 | sliderInputEl.min = options.min
19 | sliderInputEl.max = options.max
20 | sliderInputEl.value = options.value
21 |
22 | sliderEl.appendChild(sliderInputEl)
23 |
24 | return sliderEl
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/test/create-moment.test.js:
--------------------------------------------------------------------------------
1 | var createMoment = require('../src/lib/create-moment')
2 | , assert = require('assert')
3 |
4 | describe('createMoment()', function () {
5 |
6 | it('should use the `moment` setting to create the instance', function (done) {
7 |
8 | var value = '123'
9 |
10 | function spyMoment(val) {
11 | assert.equal(value, val)
12 | done()
13 | }
14 |
15 | createMoment.call({ options: { moment: spyMoment } }, value)
16 |
17 | })
18 |
19 | it('should use pass the format option so that dates are correctly parsed', function (done) {
20 |
21 | var value = '123'
22 | , format = 'YYYY-MM-DD'
23 |
24 | function spyMoment(val, f) {
25 | assert.equal(value, val)
26 | assert.equal(format, f)
27 | done()
28 | }
29 |
30 | createMoment.call({ options: { moment: spyMoment, format: format } }, value)
31 |
32 | })
33 |
34 | })
35 |
--------------------------------------------------------------------------------
/test/get-year-list.test.js:
--------------------------------------------------------------------------------
1 | var getYearList = require('../src/lib/get-year-list')
2 | , assert = require('assert')
3 |
4 | describe('getYearList()', function () {
5 |
6 | it('should list years between and inclusive of the given start/end years', function () {
7 | assert.deepEqual(
8 | [ 1984, 1985, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994
9 | , 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005
10 | ]
11 | , getYearList(1984, 2005))
12 | })
13 |
14 | it('should error if end year is less than start year', function () {
15 | assert.throws(function () { getYearList(2006, 2001) })
16 | })
17 |
18 | it('should error if either year is not an integer', function () {
19 | assert.throws(function () { getYearList('1996', 2001) })
20 | assert.throws(function () { getYearList('2006', '2050') })
21 | assert.throws(function () { getYearList(2006, '2050') })
22 | assert.throws(function () { getYearList(2.04, 2080) })
23 | })
24 |
25 | it('should work with a single year range', function () {
26 | assert.deepEqual([ 2015 ], getYearList(2015, 2015))
27 | })
28 |
29 | })
30 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "anytime",
3 | "version": "1.4.2",
4 | "description": "A date/time picker",
5 | "author": "Ben Gourley",
6 | "license": "ISC",
7 | "publishConfig": {
8 | "registry": "http://registry.npmjs.org"
9 | },
10 | "homepage": "http://bengourley.github.io/anytime/",
11 | "repository": {
12 | "type": "git",
13 | "url": "http://github.com/bengourley/anytime.git"
14 | },
15 | "main": "src/anytime.js",
16 | "typings": "src/anytime.d.ts",
17 | "scripts": {
18 | "lint": "jshint . --reporter=./node_modules/jshint-full-path/index.js",
19 | "pretest": "npm run-script lint",
20 | "test": "istanbul cover ./node_modules/.bin/_mocha -- -R spec test",
21 | "posttest": "istanbul check-coverage && rm -rf coverage",
22 | "build": "gulp build:lib",
23 | "prepublish": "npm test && npm prune && npm run-script build",
24 | "buildwebsite": "gulp build:web",
25 | "watch": "gulp watch:web",
26 | "deploy": "gulp deploy"
27 | },
28 | "dependencies": {
29 | "lodash.assign": "*",
30 | "lodash.throttle": "*",
31 | "moment": "^2.11.1",
32 | "pad-number": "^0.0.4"
33 | },
34 | "devDependencies": {
35 | "del": "^2.2.0",
36 | "gulp": "^3.9.0",
37 | "gulp-autoprefixer": "^3.1.0",
38 | "gulp-gh-pages": "^0.5.4",
39 | "gulp-cssnano": "^2.1.0",
40 | "gulp-header": "^1.7.1",
41 | "gulp-htmlmin": "^1.3.0",
42 | "gulp-jade": "^1.1.0",
43 | "gulp-sass": "^2.1.1",
44 | "istanbul": "^0.4.2",
45 | "jsdom": "^7.2.2",
46 | "jshint": "^2.9.1",
47 | "jshint-full-path": "^1.1.1",
48 | "mocha": "^2.3.4",
49 | "moment-timezone": "^0.5.0",
50 | "st": "^1.1.0",
51 | "webpack": "^1.12.11"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # anytime
2 |
3 | [](https://nodei.co/npm/anytime/)
4 |
5 | [Documentation](https://bengourley.github.io/anytime)
6 |
7 | A date/time picker.
8 |
9 | 
10 |
11 | ## Time, anyone?
12 |
13 | I *really* didn't want to write this module but there are no good open source alternatives. In our CMSs at [clock](https://github.com/clocklimited/) we have tonnes of instances where a **time** needs to be selected: article live dates, offer expiry dates and other scheduling.
14 |
15 | Until now we've made-do with the bloaty [jQuery UI datepicker](http://jqueryui.com/datepicker/) with the hacky [timepicker extension](http://trentrichardson.com/examples/timepicker/). I thought, "surely, someone must have built a decent, modular date *and* time picker by now?". [pikaday](https://github.com/dbushell/Pikaday) comes close – at least it's on npm, but you still have to rely on a choice of [three](https://github.com/stas/Pikaday) [different](https://github.com/xeeali/Pikaday) [forks](https://github.com/owenmead/Pikaday) for time picking.
16 |
17 | So please join me, on a journey of modularity and package managed glory in creating a date/time picker once and for all!
18 |
19 | Stay tuned.
20 |
21 | ### [Documentation Site](https://bengourley.github.io/anytime)
22 |
23 | ## Credits
24 | * [Ben Gourley](https://github.com/bengourley/)
25 | * [Eugene Cheung](https://github.com/arkon/)
26 |
27 | ## Licence
28 | Copyright (c) 2014 - present, Ben Gourley
29 |
30 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
31 |
32 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
33 |
--------------------------------------------------------------------------------
/website/styles/_skin.scss:
--------------------------------------------------------------------------------
1 | //
2 | // ANYTIME DATE/TIME PICKER
3 | // ========================
4 | //
5 |
6 | $base--border-radius: 0.3em;
7 | $color--black: #333;
8 | $color--border: #ccc;
9 | $color--grey--light: #ddd;
10 | $color--primary: #cb573e;
11 | $color--white: #fff;
12 |
13 | .anytime-picker {
14 | background-color: $color--white;
15 | border: 1px solid $color--border;
16 | border-radius: $base--border-radius;
17 | color: $color--black;
18 | display: none;
19 | line-height: 1;
20 | padding: 3px;
21 | position: absolute;
22 | width: 240px;
23 | z-index: 100;
24 | }
25 |
26 | .anytime-picker--is-visible {
27 | display: block
28 | }
29 |
30 | .anytime-picker__dropdown {
31 | display: inline-block;
32 | height: 24px;
33 | margin: 0 3px;
34 | width: auto;
35 | }
36 |
37 | .anytime-picker__dates .anytime-picker__day-name {
38 | font-size: 11px;
39 | padding-bottom: 3px;
40 | text-align: center;
41 | }
42 |
43 | .anytime-picker__dates {
44 | margin: 3px 0;
45 | width: auto;
46 |
47 | span,
48 | .anytime-picker__date {
49 | display: inline-block;
50 | padding: 8px 3px;
51 | width: (100% / 7);
52 | }
53 | }
54 |
55 | .anytime-picker__date {
56 | background: $color--grey--light;
57 | border: 0;
58 | box-shadow: inset 0 0 0 1px $color--white; // Fake table cell borders
59 | color: $color--black;
60 | cursor: pointer;
61 | position: relative;
62 | text-align: center;
63 | transition: background-color 0.4s ease, color 0.4s ease;
64 |
65 | &:hover,
66 | &:focus {
67 | background: $color--primary;
68 | color: $color--white;
69 | outline: 0;
70 | transition-duration: 0.1s;
71 | }
72 | }
73 |
74 | .anytime-picker__header,
75 | .anytime-picker__footer {
76 | padding: 5px;
77 | text-align: center;
78 |
79 | .anytime-picker__dropdown,
80 | .anytime-picker__button {
81 | margin: 3px 2px;
82 | vertical-align: middle;
83 | }
84 | }
85 |
86 | .anytime-picker__footer {
87 | background-color: $color--grey--light;
88 |
89 | .anytime-picker__button {
90 | font-size: 13px;
91 | line-height: 1em;
92 | }
93 | }
94 |
95 | .anytime-picker__time {
96 | padding: 10px 0;
97 | text-align: center;
98 |
99 | .anytime-picker__dropdown {
100 | width: 30%;
101 | }
102 | }
103 |
104 | .anytime-picker__button {
105 | background-color: $color--grey--light;
106 | border: 1px solid shade($color--grey--light, 10%);
107 | border-radius: $base--border-radius;
108 | color: $color--black;
109 | cursor: pointer;
110 | display: inline-block;
111 | font-family: inherit;
112 | line-height: 0.4;
113 | min-width: 25px;
114 | overflow: visible; // removes padding in IE
115 | padding: 6px 7px;
116 | position: relative;
117 | text-align: center;
118 | text-decoration: none;
119 | transition: all 0.3s ease;
120 | vertical-align: middle;
121 |
122 | &:focus,
123 | &:hover {
124 | background-color: shade($color--grey--light, 15%);
125 | border-color: shade($color--grey--light, 25%);
126 | color: $color--black;
127 | outline: 0;
128 | text-decoration: none;
129 | transition-duration: 0.1s;
130 | }
131 |
132 | &:active {
133 | background-color: shade($color--grey--light, 25%);
134 | border-color: shade($color--grey--light, 35%);
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/website/styles/index.scss:
--------------------------------------------------------------------------------
1 | @import '../../src/anytime.css';
2 | @import '_skin';
3 |
4 | * {
5 | box-sizing: border-box;
6 | }
7 |
8 | body {
9 | background-color: #cb573e;
10 | color: #fffddc;
11 | font: 300 112.5%/1.7 'Roboto', sans-serif;
12 | }
13 |
14 | ::placeholder {
15 | color: #fff;
16 | }
17 |
18 | a {
19 | color: #fffddc;
20 | }
21 |
22 | h1,
23 | h2,
24 | h3,
25 | h4,
26 | h5 {
27 | a {
28 | text-decoration: none;
29 |
30 | &:hover {
31 | text-decoration: underline;
32 | }
33 | }
34 | }
35 |
36 | pre {
37 | font: 16px/1.5 'Consolas', monospace;
38 | }
39 |
40 | p,
41 | tr {
42 | code {
43 | background-color: rgba(#fffddc, 0.1);
44 | padding: 5px 3px;
45 | }
46 | }
47 |
48 | table {
49 | border: none;
50 | display: block;
51 | font-size: 0.8em;
52 | overflow: auto;
53 | }
54 |
55 | td {
56 | background: none;
57 | border: none;
58 | border-collapse: collapse;
59 | border-spacing: 0;
60 | margin: 0;
61 | padding: 0.5em;
62 |
63 | thead & {
64 | border-bottom: 1px solid #fffddc;
65 | font-weight: 700;
66 | }
67 |
68 | tbody & {
69 | border-bottom: 1px solid rgba(#fffddc, 0.1);
70 | max-width: 20%;
71 | }
72 | }
73 |
74 | .main-container {
75 | margin: 0 auto;
76 | max-width: 700px;
77 | padding: 50px 0;
78 | }
79 |
80 | .main-title {
81 | font-size: 50px;
82 | font-weight: 100;
83 | margin-bottom: 0;
84 | text-align: center;
85 | text-shadow: 3px 3px rgba(0,0,0,0.2);
86 | text-transform: uppercase;
87 | }
88 |
89 | .subtitle {
90 | font-size: 15px;
91 | font-weight: 100;
92 | margin-bottom: 80px;
93 | margin-top: 0;
94 | text-align: center;
95 | text-transform: uppercase;
96 | }
97 |
98 | .main-footer {
99 | padding: 50px 0;
100 | text-align: center;
101 | }
102 |
103 | .terminal {
104 | background: #e3e3e3;
105 | border-radius: 0.5em;
106 | margin: 6em 0 3em;
107 | padding-top: 2.3em;
108 | position: relative;
109 | }
110 |
111 | .terminal::before,
112 | .terminal::after,
113 | .terminal__content::after {
114 | background: #c8c8c8;
115 | border-radius: 50%;
116 | content: '';
117 | height: 0.75em;
118 | position: absolute;
119 | top: 0.85em;
120 | width: 0.75em;
121 | }
122 |
123 | .terminal::before {
124 | left: 1em;
125 | }
126 |
127 | .terminal::after {
128 | left: 2.25em;
129 | }
130 |
131 | .terminal__content::after {
132 | left: 3.5em;
133 | }
134 |
135 | .terminal__content {
136 | background: #fff;
137 | border-radius: 0 0 .5em .5em;
138 | color: #000;
139 | display: block;
140 | overflow: scroll;
141 | padding: 1em 1.5em;
142 | }
143 |
144 | .splash__image {
145 | text-align: center;
146 | }
147 |
148 | .splash__input {
149 | background-color: rgba(#fff, 0.2);
150 | border-radius: 0.2em;
151 | border: 3px solid rgba(#fff, 0.3);
152 | line-height: 1.75em;
153 | margin: 3em 0;
154 | padding: 0.5em;
155 | position: relative;
156 | width: 100%;
157 |
158 | input {
159 | background: transparent;
160 | border: none;
161 | color: #fff;
162 | font-weight: 300;
163 | font: inherit;
164 | width: 100%;
165 | }
166 |
167 | > button {
168 | background-color: darken(#cb573e, 20%);
169 | border: none;
170 | border-radius: 0.2em;
171 | color: #fffddc;
172 | cursor: pointer;
173 | font: 100 0.8em/1 'Roboto', sans-serif;
174 | padding: 10px 13px 7px;
175 | position: absolute;
176 | right: 0.5em;
177 | text-transform: uppercase;
178 |
179 | &:hover {
180 | background-color: darken(#cb573e, 10%)
181 | }
182 | }
183 | }
184 |
185 | .splash__prose {
186 | margin: 5em 0;
187 | }
188 |
189 | .code-sample {
190 | background-color: #fffddc;
191 | color: #cb573e;
192 | overflow: auto;
193 | padding: 1em;
194 | }
195 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | ////////////////////////////////////////////////////////////////////////////////
4 | // Dependencies //
5 | ////////////////////////////////////////////////////////////////////////////////
6 |
7 | var autoprefixer = require('gulp-autoprefixer')
8 | , del = require('del')
9 | , ghPages = require('gulp-gh-pages')
10 | , gulp = require('gulp')
11 | , header = require('gulp-header')
12 | , htmlmin = require('gulp-htmlmin')
13 | , http = require('http')
14 | , jade = require('gulp-jade')
15 | , nano = require('gulp-cssnano')
16 | , sass = require('gulp-sass')
17 | , st = require('st')
18 | , webpack = require('webpack')
19 |
20 |
21 | ////////////////////////////////////////////////////////////////////////////////
22 | // Config //
23 | ////////////////////////////////////////////////////////////////////////////////
24 |
25 | var paths =
26 | { webSrcHtml: './website/index.jade'
27 | , webSrcJs: './website/js/index.js'
28 | , webSrcScss: './website/styles/index.scss'
29 | , webDest: './website/build/'
30 | , libSrcJs: './src/anytime.js'
31 | , libDest: './dist/'
32 | }
33 |
34 | var webpackPlugins =
35 | [ new webpack.optimize.DedupePlugin()
36 | , new webpack.optimize.OccurenceOrderPlugin()
37 | , new webpack.optimize.UglifyJsPlugin(
38 | { compress: { warnings: false }
39 | , output: { comments: false }
40 | , sourceMap: false
41 | }
42 | )
43 | ]
44 |
45 |
46 | ////////////////////////////////////////////////////////////////////////////////
47 | // Gulp tasks for the website //
48 | ////////////////////////////////////////////////////////////////////////////////
49 |
50 | // Delete generated website files
51 | gulp.task('clean:web', function() {
52 | del.sync(paths.webDest, { force: true })
53 | })
54 |
55 | // Process main HTML file
56 | gulp.task('html', function() {
57 | return gulp.src(paths.webSrcHtml)
58 | .pipe(jade())
59 | .pipe(htmlmin({ removeComments: true, collapseWhitespace: true }))
60 | .pipe(gulp.dest(paths.webDest))
61 | })
62 |
63 | // Process SCSS files
64 | gulp.task('styles', function() {
65 | return gulp.src(paths.webSrcScss)
66 | .pipe(sass().on('error', sass.logError))
67 | .pipe(autoprefixer({ browsers: ['last 2 versions'] }))
68 | .pipe(nano())
69 | .pipe(gulp.dest(paths.webDest))
70 | })
71 |
72 | // Process JS scripts
73 | gulp.task('scripts', function(cb) {
74 | webpack(
75 | { entry: paths.webSrcJs
76 | , output:
77 | { path: paths.webDest
78 | , filename: 'index.js'
79 | }
80 | , plugins: webpackPlugins
81 | }
82 | , function(webpack_err, stats) {
83 | if (webpack_err) cb(webpack_err)
84 | cb()
85 | }
86 | )
87 | })
88 |
89 | // Compile library website
90 | gulp.task('build:web', ['clean:web', 'html', 'styles', 'scripts'])
91 |
92 | // Launch static server for website (localhost:8080) and watch for changes
93 | gulp.task('watch:web', ['build:web'], function() {
94 | gulp.watch(paths.webSrcHtml, ['html'])
95 | gulp.watch(paths.webSrcScss, ['styles'])
96 | gulp.watch(paths.webSrcJs, ['scripts'])
97 |
98 | http.createServer(
99 | st(
100 | { path: paths.webDest
101 | , index: 'index.html'
102 | }
103 | )
104 | ).listen(8080)
105 | })
106 |
107 | // Deploy to GitHub Pages
108 | gulp.task('deploy', ['build:web'], function() {
109 | return gulp.src(paths.webDest + '/**/*')
110 | .pipe(ghPages())
111 | })
112 |
113 |
114 | ////////////////////////////////////////////////////////////////////////////////
115 | // Gulp tasks for the library //
116 | ////////////////////////////////////////////////////////////////////////////////
117 |
118 | // Delete generated Anytime files
119 | gulp.task('clean:lib', function() {
120 | del.sync(paths.libDest, { force: true })
121 | })
122 |
123 | var buildLib = function(filename, externals, cb) {
124 | var pkg = require('./package.json')
125 |
126 | var banner =
127 | [ '/**'
128 | , ' * <%= pkg.name %> - <%= pkg.description %>'
129 | , ' * @version <%= pkg.version %>'
130 | , ' * @link <%= pkg.homepage %>'
131 | , ' * @license <%= pkg.license %>'
132 | , ' */'
133 | , ''
134 | ].join('\n')
135 |
136 | // Bundle with Webpack
137 | webpack(
138 | { entry: paths.libSrcJs
139 | , output:
140 | { path: paths.libDest
141 | , filename: filename
142 | , library: 'anytime'
143 | , libraryTarget: 'umd'
144 | }
145 | , plugins: webpackPlugins
146 | , externals: externals
147 | }
148 | , function(webpack_err, stats) {
149 | if (webpack_err) cb(webpack_err)
150 |
151 | gulp.src(paths.libDest + filename)
152 | .pipe(header(banner, { pkg: pkg } ))
153 | .pipe(gulp.dest(paths.libDest))
154 | .on('end', cb)
155 | }
156 | )
157 | }
158 |
159 | gulp.task('build:lib-with-moment', function(cb) {
160 | buildLib('anytime-with-moment.js', {}, cb);
161 | })
162 |
163 | gulp.task('build:lib-without-moment', function(cb) {
164 | buildLib('anytime.js', { 'moment': 'moment' }, cb);
165 | })
166 |
167 | // Compile Anytime library
168 | gulp.task('build:lib', ['clean:lib', 'build:lib-with-moment', 'build:lib-without-moment'])
169 |
--------------------------------------------------------------------------------
/src/anytime.d.ts:
--------------------------------------------------------------------------------
1 | export interface AnytimeOptions {
2 | /**
3 | * An input whose value should update when the picker’s value changes.
4 | * @default null
5 | */
6 | input?: HTMLInputElement;
7 |
8 | /**
9 | * An element that the picker will orient itself near when displayed.
10 | * If options.input is not provided, this option is required.
11 | * @default options.input
12 | */
13 | anchor?: HTMLElement;
14 |
15 | /**
16 | * An element that when clicked will show/hide the picker interface.
17 | * @default null
18 | */
19 | button?: HTMLElement;
20 |
21 | /**
22 | * The earliest year that can be shown or selected in the picker interface.
23 | * @default 1960
24 | */
25 | minYear?: number;
26 |
27 | /**
28 | * The latest year that can be shown or selected in the picker interface.
29 | * @default 2030
30 | */
31 | maxYear?: number;
32 |
33 | /**
34 | * By default anytime will show every minute.
35 | * Set this to 5 or 15 etc to show fewer options at greater intervals.
36 | * @default 1
37 | */
38 | minuteIncrement?: number;
39 |
40 | /**
41 | * The distance (px) from options.anchor the picker interface should be displayed.
42 | * @default 5
43 | */
44 | offset?: number;
45 |
46 | /**
47 | * The initial value to set the picker’s internal value.
48 | * @default null
49 | */
50 | initialValue?: Date;
51 |
52 | /**
53 | * Value to indicate which month/year to display when picker is first shown.
54 | * If options.initialValue is selected, that will take precedence.
55 | * @default new Date()
56 | */
57 | initialView?: Date;
58 |
59 | /**
60 | * moment-style date format string.
61 | * @default 'h:mma on dddd D MMMM YYYY'
62 | */
63 | format?: string;
64 |
65 | /**
66 | * By default anytime uses an instance of moment in the browser’s timezone with the English locale.
67 | * If you want to use a different language or a different timezone, you must load in a locale to moment
68 | * and/or pass in a version of moment-timezone.
69 | * @type moment or moment-timezone
70 | * @default moment
71 | */
72 | moment?: any;
73 |
74 | /**
75 | * moment-style timezone string (e.g. 'Europe/London').
76 | * Only functions if moment-timezone is provided as options.moment!
77 | * @default Browser’s timezone
78 | */
79 | timezone?: string
80 |
81 | /**
82 | *
83 | */
84 | showTime?: boolean;
85 |
86 | /**
87 | * Use sliders instead of the default dropdowns for the time input.
88 | * @default false
89 | */
90 | timeSliders?: boolean;
91 |
92 | /**
93 | * Choose whether to abbreviate month names, e.g "Jan" vs. "January".
94 | * @default true
95 | */
96 | shortMonthNames?: boolean;
97 |
98 | /**
99 | * Set the text of the button that closes the picker interface.
100 | * @default 'Done'
101 | */
102 | doneText?: string;
103 |
104 | /**
105 | * Set the text of the button that clears the picker value and closes the picker interface.
106 | * @default 'Clear'
107 | */
108 | clearText?: string;
109 |
110 | /**
111 | * Set the text of the label before the time sliders.
112 | * @default 'Time:'
113 | */
114 | timeSlidersText?: string;
115 |
116 | /**
117 | * Set the text of the label before the hour slider.
118 | * @default 'Hour:'
119 | */
120 | timeSlidersHourText?: string;
121 |
122 | /**
123 | * Set the text of the label before the minute slider.
124 | * @default 'Minute:'
125 | */
126 | timeSlidersMinuteText?: string;
127 | }
128 |
129 | export interface Anytime {
130 | /**
131 | * Instantiates and returns a new picker with the provided options.
132 | */
133 | new (options?: AnytimeOptions): Anytime;
134 |
135 | /**
136 | * Renders the picker interface. This method must be called before show() is called.
137 | */
138 | render(): void;
139 |
140 | /**
141 | * Displays the picker interface.
142 | */
143 | show(): void;
144 |
145 | /**
146 | * Removes the picker interface from display.
147 | */
148 | hide(): void;
149 |
150 | /**
151 | * Shows the picker if it is currently hidden, hides it if currently displayed.
152 | */
153 | toggle(): void;
154 |
155 | /**
156 | * Update the internal value of the picker. This will also update the related input (if there is one).
157 | * @param {string | Date} val An ISO8601 string or Date object. Passing in null clears the picker.
158 | */
159 | update(val: string | Date): void;
160 |
161 | /**
162 | * Update the internal value of the picker. This will also update the related input (if there is one).
163 | * @param {function} fn A function where you can manipulate the internal moment object.
164 | * The moment object must be returned.
165 | */
166 | update(fn: (m: any) => any): void;
167 |
168 | /**
169 | * When a value is selected (or cleared) with the picker, the change event will emit with the new value.
170 | * @param {string} event This must be "change".
171 | * @param {function} fn A callback function with the new value.
172 | */
173 | on(event: string, fn: (d: Date) => any): void;
174 |
175 | /**
176 | * Removes all event listeners and removes the picker interface element from the DOM.
177 | */
178 | destroy(): void;
179 | }
180 |
181 | export var anytime: Anytime;
182 |
183 | export default anytime;
184 |
--------------------------------------------------------------------------------
/test/anytime.test.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert')
2 | , moment = require('moment-timezone')
3 | , createBrowserEnv = require('./browser-env')
4 |
5 | describe('anytime', function () {
6 |
7 | beforeEach(createBrowserEnv)
8 |
9 | // Clear the module cache for anytime so that it can be reloaded again.
10 | // This is needed to test option defaults that are set in the module scope
11 | beforeEach(function () { delete require.cache[require.resolve('../')] })
12 |
13 | describe('destroy()', function () {
14 |
15 | it('should remove any bound events', function () {
16 |
17 | var Picker = require('../src/anytime')
18 | , p = new Picker({ input: document.createElement('input') })
19 |
20 | p.render()
21 | p.on('change', function () {})
22 | assert(Object.keys(p._events).length)
23 | p.destroy()
24 | assert.equal(Object.keys(p._events).length, 0)
25 |
26 | })
27 |
28 | it('should remove the element from the DOM', function () {
29 |
30 | var Picker = require('../src/anytime')
31 | , parent = document.createElement('div')
32 | , p = new Picker({ input: document.createElement('input') })
33 |
34 | parent.appendChild(p.render().el)
35 |
36 | p.on('change', function () {})
37 | assert.equal(parent.childNodes.length, 1)
38 | p.destroy()
39 | assert.equal(parent.childNodes.length, 0)
40 |
41 | })
42 |
43 | })
44 |
45 | describe('timezone', function () {
46 | it('should allow you to pass in a timezone which modifies all displayed dates', function () {
47 | var Picker = require('../src/anytime')
48 | , parent = document.createElement('input')
49 | , moment = require('moment-timezone')
50 | , p = new Picker(
51 | { input: parent
52 | , moment: moment
53 | , timezone: 'America/New_York'
54 | , format: 'z'
55 | })
56 |
57 | p.render()
58 |
59 | p.update(new Date(Date.UTC(2015, 4, 11, 0, 0, 0)))
60 | assert.equal(parent.value, 'EDT')
61 | })
62 |
63 | it('should display the correct time in the time input', function () {
64 | // 9am UTC may 11th is 5am new york time
65 | var Picker = require('../src/anytime')
66 | , parent = document.createElement('input')
67 | , date = new Date(Date.UTC(2015, 4, 11, 9, 0, 0))
68 | , moment = require('moment-timezone')
69 | , p = new Picker(
70 | { input: parent
71 | , moment: moment
72 | , timezone: 'America/New_York'
73 | , initialValue: date
74 | })
75 |
76 | p.render()
77 |
78 | var hourSelect = p.el.querySelector('.anytime-picker__dropdown--hours')
79 | assert.equal(hourSelect.value, '5')
80 | })
81 | })
82 |
83 | describe('selected day', function () {
84 | it('should add a class to the selected day', function () {
85 | var Picker = require('../src/anytime')
86 | , parent = document.createElement('input')
87 | , p = new Picker({ input: parent })
88 | , date = moment().toDate()
89 |
90 | p.render()
91 | p.update(date)
92 |
93 | var day = p.el.querySelector('button[data-date=\'' + date.getDate() + '\']')
94 |
95 | assert(day.getAttribute('class').indexOf('anytime-picker__date--selected') > -1, 'Should have a class on it')
96 | })
97 |
98 | it('should update the class when the date changes', function () {
99 | var Picker = require('../src/anytime')
100 | , parent = document.createElement('input')
101 | , p = new Picker({ input: parent, initialValue: moment('2015-04-01') })
102 | , cls = 'anytime-picker__date--selected'
103 |
104 | p.render()
105 | p.update(moment('2015-04-10').toDate())
106 | p.update(moment('2015-04-12').toDate())
107 |
108 | var firstSelectedDay = p.el.querySelector('button[data-date=\'10\']')
109 | , secondSelectedDay = p.el.querySelector('button[data-date=\'12\']')
110 |
111 | assert(firstSelectedDay.getAttribute('class').indexOf(cls) === -1, 'Should not have a class on it')
112 | assert(secondSelectedDay.getAttribute('class').indexOf(cls) > -1, 'Should have a class on it')
113 | })
114 |
115 | it('should only have the class in the correct month', function () {
116 | var Picker = require('../src/anytime')
117 | , parent = document.createElement('input')
118 | , selectedDay = 13
119 | , p = new Picker({ input: parent, initialValue: moment('2015-02-' + selectedDay).toDate() })
120 |
121 | p.render()
122 | p.showNextMonth()
123 |
124 | var day = p.el.querySelector('button[data-date=\'' + selectedDay + '\']')
125 |
126 | assert(day.getAttribute('class').indexOf('anytime-picker__date--selected') === -1, 'Should not have a class on it')
127 | })
128 |
129 | it('should only have the class in the correct year', function () {
130 | var Picker = require('../src/anytime')
131 | , parent = document.createElement('input')
132 | , selectedDay = 13
133 | , p = new Picker({ input: parent, initialValue: moment('2015-02-' + selectedDay).toDate() })
134 |
135 | p.render()
136 |
137 | // Pushing the date forward by a year
138 | for (var i = 0; i <= 11; i += 1) {
139 | p.showNextMonth()
140 | }
141 |
142 | var day = p.el.querySelector('button[data-date=\'' + selectedDay + '\']')
143 |
144 | assert(day.getAttribute('class').indexOf('anytime-picker__date--selected') === -1, 'Should not have a class on it')
145 | })
146 | })
147 |
148 | describe('current day', function () {
149 | it('should add a class to the current day', function () {
150 | var Picker = require('../src/anytime')
151 | , parent = document.createElement('input')
152 | , p = new Picker({ input: parent })
153 | , date = moment()
154 | , currentDay = +date.format('D')
155 |
156 | p.render()
157 |
158 | var day = p.el.querySelector('button[data-date=\'' + currentDay + '\']')
159 |
160 | assert(day.getAttribute('class').indexOf('anytime-picker__date--current') > -1, 'Should have a class on it')
161 | })
162 |
163 | it('should only have the class in the correct month', function () {
164 | var Picker = require('../src/anytime')
165 | , parent = document.createElement('input')
166 | , p = new Picker({ input: parent })
167 | , date = moment()
168 | , currentDay = +date.format('D')
169 |
170 | p.render()
171 |
172 | p.showNextMonth()
173 |
174 | var day = p.el.querySelector('button[data-date=\'' + currentDay + '\']')
175 |
176 | assert(day.getAttribute('class').indexOf('anytime-picker__date--current') === -1, 'Should not have a class on it')
177 | })
178 |
179 | it('should only have the class in the correct year', function () {
180 | var Picker = require('../src/anytime')
181 | , parent = document.createElement('input')
182 | , p = new Picker({ input: parent })
183 | , date = moment()
184 | , currentDay = +date.format('D')
185 |
186 | p.render()
187 |
188 | // Pushing the date forward by a year
189 | for (var i = 0; i <= 11; i += 1) {
190 | p.showNextMonth()
191 | }
192 |
193 | var day = p.el.querySelector('button[data-date=\'' + currentDay + '\']')
194 |
195 | assert(day.getAttribute('class').indexOf('anytime-picker__date--current') === -1, 'Should not have a class on it')
196 | })
197 | })
198 |
199 | describe('minutes', function () {
200 |
201 | it('should output 60 minutes', function () {
202 | var Picker = require('../src/anytime')
203 | , parent = document.createElement('input')
204 | , p = new Picker({ input: parent })
205 |
206 | p.render()
207 |
208 | var minutes = p.el.querySelector('.anytime-picker__dropdown--minutes')
209 | assert.equal(minutes.length, 60)
210 | })
211 |
212 | })
213 |
214 | it('should not throw when rendered with a null initialValue', function () {
215 | var Picker = require('../src/anytime')
216 | , parent = document.createElement('input')
217 | , p = new Picker({ input: parent, initialValue: null })
218 |
219 | assert.doesNotThrow(function () {
220 | p.render()
221 | }, /setAttribute/)
222 | })
223 |
224 | describe('locale', function () {
225 |
226 | it('should default to english', function () {
227 |
228 | var Picker = require('../src/anytime')
229 | , parent = document.createElement('input')
230 | , p = new Picker({ input: parent, initialValue: new Date() })
231 |
232 | assert.equal('Jan', p.monthNames[0])
233 | assert.equal('en', p.value.locale())
234 |
235 | })
236 |
237 | it('should use a provided locale', function () {
238 |
239 | var Picker = require('../src/anytime')
240 | , parent = document.createElement('input')
241 | , moment = require('moment')
242 |
243 | moment.locale('fr')
244 |
245 | var p = new Picker({ input: parent, initialValue: new Date(), moment: moment })
246 |
247 | assert.equal('janv.', p.monthNames[0])
248 | assert.equal('fr', p.value.locale())
249 |
250 | })
251 |
252 | it('should set "done" and "clear" button text to provided option', function () {
253 | var Picker = require('../src/anytime')
254 | , parent = document.createElement('input')
255 | , p = new Picker({
256 | input: parent,
257 | initialValue: null ,
258 | doneText: 'Set Time',
259 | clearText: 'Goodbye'
260 | })
261 |
262 | p.render()
263 |
264 | var doneButton = p.el.querySelector('.anytime-picker__button--done')
265 | assert.equal(doneButton.textContent, 'Set Time', 'should set done button text')
266 | var clearButton = p.el.querySelector('.anytime-picker__button--clear')
267 | assert.equal(clearButton.textContent, 'Goodbye', 'should set clear button text')
268 | })
269 |
270 | })
271 |
272 | })
273 |
--------------------------------------------------------------------------------
/website/index.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | html
3 | head
4 | meta(charset='utf8')
5 | title Anytime – a JavaScript date and time picker
6 | link(rel='stylesheet', href='https://fonts.googleapis.com/css?family=Roboto:300,100,400,700')
7 | link(rel='stylesheet', href='./index.css')
8 | body
9 | .main-container
10 | header.main-header
11 | h1.main-title Anytime
12 | h2.subtitle A JS date and time picker
13 | .splash
14 | .splash__input
15 | input(type='text', disabled, placeholder='If not now, when?').js-splash-input
16 | button.js-splash-button Select a time
17 | .splash__prose
18 | ul
19 | li Say goodbye to jQuery and jQuery UI!
20 | li Modularity FTW! For use with browserify
21 | li Only 4 lines of essential CSS (the rest is up to you)
22 | li Works in modern browsers: Chrome, FF, Safari, Edge
23 |
24 | pre.terminal: code.terminal__content $ npm i --save anytime
25 |
26 | .docs
27 | h1: a(id='usage', href='#usage') Usage
28 |
29 | p The only supported way of using Anytime is with browserify.
30 |
31 | pre.code-sample: code.
32 | var anytime = require('anytime')
33 |
34 | p.
35 | Here’s
36 | the minimum CSS required to get Anytime functional. It’ll look pretty bare
37 | and I’m sure you’ll want to style it up. There are plenty of classes on each
38 | component that you can hook into for styling.
39 |
40 | h2: a(id='constructor', href='#constructor') Creating a date/time picker:
41 | pre.code-sample: code.
42 | var p = new anytime(options)
43 |
44 | h3: a(id='options', href='#options') Options
45 | table(cellpadding='0' cellspacing='0')
46 | thead
47 | tr
48 | td Name
49 | td Type
50 | td Description
51 | td Default
52 |
53 | tbody
54 | tr
55 | td: code input
56 | td HTMLInputElement
57 | td: p An input whose value should update when the picker’s value changes.
58 | td: code null
59 |
60 | tr
61 | td: code anchor
62 | td HTMLElement
63 | td: p An element that the picker will orient itself near when displayed. If options.input is not provided, this option is required.
64 | td: code options.input
65 |
66 | tr
67 | td: code button
68 | td HTMLElement
69 | td: p An element that when clicked will show/hide the picker interface.
70 | td: code null
71 |
72 | tr
73 | td: code minYear
74 | td Number
75 | td: p The earliest year that can be shown or selected in the picker interface.
76 | td: code 1960
77 |
78 | tr
79 | td: code maxYear
80 | td Number
81 | td: p The latest year that can be shown or selected in the picker interface.
82 | td: code 2030
83 |
84 | tr
85 | td: code minuteIncrement
86 | td Number
87 | td: p By default anytime will show every minute. Set this to 5 or 15 etc to show fewer options at greater intervals.
88 | td: code 1
89 |
90 | tr
91 | td: code offset
92 | td Number
93 | td: p The distance (px) from options.anchor the picker interface should be displayed.
94 | td: code 5
95 |
96 | tr
97 | td: code initialValue
98 | td Date
99 | td: p The initial value to set the picker’s internal value.
100 | td: code null
101 |
102 | tr
103 | td: code initialView
104 | td Date
105 | td: p Value to indicate which month/year to display when picker is first shown. If options.initialValue is selected, that will take precedence.
106 | td: code new Date()
107 |
108 | tr
109 | td: code format
110 | td String
111 | td: p moment-style date format string.
112 | td: code 'h:mma on dddd D MMMM YYYY'
113 |
114 | tr
115 | td: code moment
116 | td moment, moment-timezone
117 | td: p By default anytime uses an instance of moment in the browser’s timezone with the English locale. If you want to use a different language or a different timezone, you must load in a locale to moment and/or pass in a version of moment-timezone.
118 | td: code moment
119 |
120 | tr
121 | td: code timezone
122 | td String
123 | td: p moment-style timezone string (e.g. 'Europe/London'). Only functions if moment-timezone is provided as options.moment!
124 | td Browser’s timezone
125 |
126 | tr
127 | td: code showTime
128 | td Boolean
129 | td: p Display the time picker portion.
130 | td: code true
131 |
132 | tr
133 | td: code timeSliders
134 | td Boolean
135 | td: p Use sliders instead of the default dropdowns for the time input.
136 | td: code false
137 |
138 | tr
139 | td: code shortMonthNames
140 | td Boolean
141 | td: p Choose whether to abbreviate month names, e.g "Jan" vs. "January".
142 | td: code true
143 |
144 | tr
145 | td: code doneText
146 | td String
147 | td: p Set the text of the button that closes the picker interface.
148 | td: code 'Done'
149 |
150 | tr
151 | td: code clearText
152 | td String
153 | td: p Set the text of the button that clears the picker value and closes the picker interface.
154 | td: code 'Clear'
155 |
156 | tr
157 | td: code timeSlidersText
158 | td String
159 | td: p Set the text of the label before the time sliders.
160 | td: code 'Time:'
161 |
162 | tr
163 | td: code timeSlidersHourText
164 | td String
165 | td: p Set the text of the label before the hour slider.
166 | td: code 'Hour:'
167 |
168 | tr
169 | td: code timeSlidersMinuteText
170 | td String
171 | td: p Set the text of the label before the minute slider.
172 | td: code 'Minute:'
173 |
174 | h2: a(id='api', href='#api') API
175 |
176 | h3: a(id='render', href='#render') p.render()
177 | p Renders the picker interface. This method must be called before p.show() is called.
178 |
179 | h3: a(id='show', href='#show') p.show()
180 | p Displays the picker interface.
181 |
182 | h3: a(id='hide', href='#hide') p.hide()
183 | p Removes the picker interface from display.
184 |
185 | h3: a(id='toggle', href='#toggle') p.toggle()
186 | p Shows the picker if it is currently hidden, hides it if currently displayed.
187 |
188 | h3: a(id='update', href='#update') p.update(value or fn)
189 | p Update the internal value of the picker. This will also update the related input (if there is one).
190 | p There are two ways to update the value:
191 |
192 | h4: a(id='update-value', href='#update-value') Pass a value
193 | pre.code-sample: code.
194 | p.update(new Date(2015, 4, 0)) // JS Date object
195 | p.update('2015-06-10T12:16:47.997Z') // String
196 | p.update(null) // Clear the value
197 |
198 | h4: a(id='update-fn', href='#update-fn') Use a function to manipulate the internal moment object
199 | p The return value is used to set the new date so you must return the moment object!
200 | pre.code-sample: code.
201 | picker.update(function (m) {
202 | return m.add(1, 'day') // increment the day
203 | })
204 |
205 | h3: a(id='change', href='#change') p.on('change', fn)
206 | p When a value is selected (or cleared) with the picker, the change event will emit with the new value.
207 |
208 | pre.code-sample: code.
209 | p.on('change', function (d) {
210 | // When set, d will be a date
211 | // When cleared d will be null
212 | console.log('The new date/time is…', d)
213 | })
214 |
215 | h3: a(id='destroy', href='#destroy') p.destroy()
216 | p Removes all event listeners and removes the picker interface element from the DOM.
217 |
218 | h2: a(id='examples', href='#examples') Examples
219 |
220 | h3: a(id='i18n', href='#i18n') i18n
221 | p.
222 | To use Anytime in a language other than the default (English) you need to load in your desired locale
223 | to moment and pass it in as an option like so:
224 |
225 | pre.code-sample: code.
226 | var moment = require('moment')
227 | require('moment/locale/fr')
228 | moment.locale('fr')
229 | var picker = new anytime({ moment: moment })
230 |
231 | p If you want timezone support, you must pass in a moment-timezone instance and set the timezone option:
232 |
233 | pre.code-sample: code.
234 | var moment = require('moment-timezone')
235 | require('moment/locale/fr')
236 | moment.locale('fr')
237 | var picker = new anytime({ moment: moment, timezone: 'Europe/Paris' })
238 |
239 | footer.main-footer
240 | p Made by Ben Gourley at Clock.
241 |
242 | script(src='index.js')
243 |
--------------------------------------------------------------------------------
/src/anytime.js:
--------------------------------------------------------------------------------
1 | module.exports = AnytimePicker
2 |
3 | var Emitter = require('events').EventEmitter
4 | , extend = require('lodash.assign')
5 | , throttle = require('lodash.throttle')
6 | , pad = require('pad-number')
7 | , moment = require('moment')
8 | , getYearList = require('./lib/get-year-list')
9 | , getTimeSeparator = require('./lib/get-time-separator')
10 | , createButton = require('./lib/create-button')
11 | , createSlider = require('./lib/create-slider')
12 | , getMonthDetails = require('./lib/get-month-details')
13 | , createMoment = require('./lib/create-moment')
14 | , defaults =
15 | { minYear: 1960
16 | , maxYear: 2030
17 | , offset: 5
18 | , initialValue: null
19 | , initialView: new Date()
20 | , format: 'h:mma on dddd D MMMM YYYY'
21 | , moment: moment
22 | , minuteIncrement: 1
23 | , showTime: true
24 | , timeSliders: false
25 | , shortMonthNames: true
26 | , doneText: 'Done'
27 | , clearText: 'Clear'
28 | , timeSlidersText: 'Time:'
29 | , timeSlidersHourText: 'Hour:'
30 | , timeSlidersMinuteText: 'Minute:'
31 | }
32 |
33 | function AnytimePicker(options) {
34 | this.options = extend({}, defaults, options)
35 |
36 | Emitter.call(this)
37 |
38 | // A place to store references to event callback functions so they can be specifically unbound later on
39 | this.__events = {}
40 |
41 | this.el = document.createElement('div')
42 | this.el.className = 'js-anytime-picker anytime-picker'
43 |
44 | this.options.initialValue = this.getInitialValue()
45 |
46 | var initialView = this.createMoment(this.options.initialValue || this.options.initialView)
47 | this.currentView = { month: initialView.month(), year: initialView.year() }
48 |
49 | this.value = this.options.initialValue ? this.createMoment(this.options.initialValue).seconds(0).milliseconds(0) : null
50 |
51 | if (this.value && !this.options.showTime) {
52 | this.value = this.value.hour(0).minute(0)
53 | }
54 |
55 | this.monthNames = this.getMonthNames()
56 |
57 | this.el.addEventListener('click', function (e) {
58 | if (e.target.classList.contains('js-anytime-picker-day')) {
59 | e.stopPropagation()
60 | this.update(function (value) {
61 | return value
62 | .date(parseInt(e.target.getAttribute('data-date'), 10))
63 | .month(parseInt(e.target.getAttribute('data-month'), 10))
64 | .year(parseInt(e.target.getAttribute('data-year'), 10))
65 | })
66 | }
67 | }.bind(this))
68 |
69 | // If the target element is within a form element this stops button clicks from submitting it
70 | this.el.addEventListener('click', function (e) { e.preventDefault() })
71 |
72 | this.__events['misc toggle'] = this.toggle.bind(this)
73 | if (this.options.button) this.options.button.addEventListener('click', this.__events['misc toggle'])
74 | this.options.input.addEventListener('click', this.__events['misc toggle'])
75 |
76 | this.root = this.options.anchor ? this.options.anchor : this.options.input
77 |
78 | if (this.options.input) {
79 | this.updateInput(this)
80 | this.on('change', this.updateInput.bind(this))
81 | }
82 | }
83 |
84 | AnytimePicker.prototype = Object.create(Emitter.prototype)
85 |
86 | AnytimePicker.prototype.createMoment = createMoment
87 |
88 | AnytimePicker.prototype.updateInput = function () {
89 | this.options.input.value = this.value ? this.value.format(this.options.format) : ''
90 | }
91 |
92 | AnytimePicker.prototype.getInitialValue = function () {
93 | if (this.options.initialValue) return this.options.initialValue
94 | if (this.options.input && this.options.input.value) return this.options.input.value
95 | return null
96 | }
97 |
98 | AnytimePicker.prototype.getMonthNames = function () {
99 | return this.options.moment[this.options.shortMonthNames ? 'monthsShort' : 'months']()
100 | }
101 |
102 | AnytimePicker.prototype.update = function (update) {
103 | if (update === null || update === undefined) {
104 | this.value = null
105 | this.updateDisplay()
106 | this.emit('change', null)
107 | return
108 | }
109 |
110 | if (typeof update !== 'function') {
111 | var newVal = update
112 | update = function () { return this.createMoment(newVal) }.bind(this)
113 | }
114 |
115 | var updated = update(this.value || this.createMoment())
116 | this.value = updated
117 |
118 | if (!this.options.showTime) {
119 | this.value = this.value.hour(0).minute(0)
120 | }
121 |
122 | this.currentView = { month: this.value.month(), year: this.value.year() }
123 | this.updateDisplay()
124 | this.emit('change', this.value.toDate())
125 | }
126 |
127 | AnytimePicker.prototype.render = function () {
128 | // Header
129 | var header = document.createElement('div')
130 | header.classList.add('anytime-picker__header')
131 | this.renderHeader(header)
132 |
133 | // Dates
134 | var dates = document.createElement('div')
135 | dates.classList.add('anytime-picker__dates')
136 | dates.classList.add('js-anytime-picker-dates')
137 |
138 | // Time
139 | var time
140 | if (this.options.showTime) {
141 | time = document.createElement('div')
142 | time.classList.add('anytime-picker__time')
143 | time.classList.add('js-anytime-picker-time')
144 | this.renderTimeInput(time)
145 | }
146 |
147 | // Footer
148 | var footer = document.createElement('div')
149 | footer.classList.add('anytime-picker__footer')
150 | this.renderFooter(footer)
151 |
152 | this.el.appendChild(header)
153 | this.el.appendChild(dates)
154 | if (this.options.showTime) this.el.appendChild(time)
155 | this.el.appendChild(footer)
156 |
157 | this.dateContainer = dates
158 |
159 | this.updateDisplay()
160 |
161 | return this
162 | }
163 |
164 | AnytimePicker.prototype.renderHeader = function (headerEl) {
165 | // Previous month button
166 | var prevBtn = createButton('<', [ 'anytime-picker__button', 'anytime-picker__button--prev' ])
167 | headerEl.appendChild(prevBtn)
168 | prevBtn.addEventListener('click', this.showPrevMonth.bind(this))
169 |
170 | // Months
171 | var monthSelect = document.createElement('select')
172 | monthSelect.classList.add('js-anytime-picker-month')
173 | monthSelect.classList.add('anytime-picker__dropdown')
174 | this.monthNames.forEach(function (month, i) {
175 | var monthOption = document.createElement('option')
176 | monthOption.textContent = month
177 | if (i === this.currentView.month) monthOption.selected = true
178 | monthSelect.appendChild(monthOption)
179 | }.bind(this))
180 | headerEl.appendChild(monthSelect)
181 | this.monthSelect = monthSelect
182 |
183 | monthSelect.addEventListener('change', function (e) {
184 | this.currentView.month = this.monthNames.indexOf(e.target.value)
185 | this.updateDisplay()
186 | }.bind(this))
187 |
188 | // Years
189 | var yearSelect = document.createElement('select')
190 | yearSelect.classList.add('js-anytime-picker-year')
191 | yearSelect.classList.add('anytime-picker__dropdown')
192 | getYearList(this.options.minYear, this.options.maxYear).forEach(function (year) {
193 | var yearOption = document.createElement('option')
194 | yearOption.textContent = year
195 | if (year === this.currentView.year) yearOption.selected = true
196 | yearSelect.appendChild(yearOption)
197 | }.bind(this))
198 | headerEl.appendChild(yearSelect)
199 | this.yearSelect = yearSelect
200 |
201 | yearSelect.addEventListener('change', function (e) {
202 | this.currentView.year = e.target.value
203 | this.updateDisplay()
204 | }.bind(this))
205 |
206 | // Next month button
207 | var nextBtn = createButton('>', [ 'anytime-picker__button', 'anytime-picker__button--next' ])
208 | headerEl.appendChild(nextBtn)
209 | nextBtn.addEventListener('click', this.showNextMonth.bind(this))
210 | }
211 |
212 | AnytimePicker.prototype.renderFooter = function (footerEl) {
213 | // "Done" button
214 | var doneBtn = createButton(this.options.doneText, [ 'anytime-picker__button', 'anytime-picker__button--done' ])
215 | footerEl.appendChild(doneBtn)
216 | doneBtn.addEventListener('click', this.hide.bind(this))
217 |
218 | // "Clear" button
219 | var clearBtn = createButton(this.options.clearText, [ 'anytime-picker__button', 'anytime-picker__button--clear' ])
220 | footerEl.appendChild(clearBtn)
221 | clearBtn.addEventListener('click', function () {
222 | this.update(null)
223 | this.hide()
224 | }.bind(this))
225 | }
226 |
227 | AnytimePicker.prototype.updateDisplay = function () {
228 | this.monthSelect.children[this.currentView.month].selected = true
229 | Array.prototype.slice.call(this.yearSelect.children).some(function (yearEl) {
230 | if (yearEl.textContent !== '' + this.currentView.year) return false
231 | yearEl.selected = true
232 | return true
233 | }.bind(this))
234 |
235 | var daysEl = document.createElement('div')
236 | , monthDetails = getMonthDetails(this.currentView.month, this.currentView.year)
237 |
238 | /*
239 | * Create the day column headers
240 | */
241 | function renderDayNames() {
242 | var names = this.options.moment.weekdaysMin()
243 | // Moment gives Sunday as the first item, but uses Monday as the first day of the week.
244 | // This is due to getMonthDetails returning the value of ISO weekday, which has Monday
245 | // as index 1 and Sunday at index 7. For this reason, Sunday is shifted from the from
246 | // of the array and pushed to the back.
247 | names.push(names.shift())
248 | names.forEach(function (d) {
249 | var dayName = document.createElement('span')
250 | dayName.textContent = d
251 | dayName.classList.add('anytime-picker__day-name')
252 | daysEl.appendChild(dayName)
253 | })
254 | }
255 |
256 | /*
257 | * Create the blank days ahead of the first day of the current month so that
258 | * the days appear in the corresponding columns of the days of the week
259 | */
260 | function padDays() {
261 | for (var x = 1; x < monthDetails.startDay; x++) {
262 | var blank = document.createElement('span')
263 | blank.textContent = ''
264 | daysEl.appendChild(blank)
265 | }
266 | }
267 |
268 | /*
269 | * Create a day element for each day of the current month
270 | */
271 | function populateDays() {
272 | var now = this.createMoment()
273 | , currentDayOfMonth = parseInt(now.format('D'), 10)
274 | , isCurrentMonth = parseInt(now.month(), 10) === this.currentView.month
275 | , isCurrentYear = parseInt(now.year(), 10) === this.currentView.year
276 | , selectedDayOfMonth = null
277 | , isSelectedCurrentMonth = false
278 | , isSelectedCurrentYear = false
279 |
280 | if (this.value) {
281 | selectedDayOfMonth = parseInt(this.value.format('D'), 10)
282 | isSelectedCurrentMonth = parseInt(this.value.month(), 10) === this.currentView.month
283 | isSelectedCurrentYear = parseInt(this.value.year(), 10) === this.currentView.year
284 | }
285 |
286 | for (var y = 1; y <= monthDetails.length; y++) {
287 | var date = createButton(y, [ 'anytime-picker__date', 'js-anytime-picker-day' ])
288 |
289 | if (y === currentDayOfMonth && isCurrentMonth && isCurrentYear) {
290 | date.classList.add('anytime-picker__date--current')
291 | }
292 |
293 | // Needs to add or remove because the current selected day can change
294 | // within the current month and need to be cleared from others
295 | var current = y === selectedDayOfMonth && isSelectedCurrentMonth && isSelectedCurrentYear
296 | date.classList.toggle('anytime-picker__date--selected', current)
297 |
298 | date.setAttribute('data-date', y)
299 | date.setAttribute('data-month', this.currentView.month)
300 | date.setAttribute('data-year', this.currentView.year)
301 | daysEl.appendChild(date)
302 | }
303 | }
304 |
305 | renderDayNames.call(this)
306 | padDays.call(this)
307 | populateDays.call(this)
308 |
309 | // Remove all of the old days
310 | Array.prototype.slice.call(this.dateContainer.children).forEach(function (child) {
311 | if (child.parentNode) child.parentNode.removeChild(child)
312 | })
313 |
314 | // Add all the new days
315 | Array.prototype.slice.call(daysEl.children).forEach(function (child) {
316 | this.dateContainer.appendChild(child)
317 | }.bind(this))
318 |
319 | if (this.value && this.timeEls) {
320 | this.timeEls.hours.value = this.value.hour() + ''
321 | this.timeEls.minutes.value = this.value.minute() + ''
322 | if (this.timeEls.hourLabel) this.timeEls.hourLabel.textContent = pad(this.value.hour(), 2)
323 | if (this.timeEls.minuteLabel) this.timeEls.minuteLabel.textContent = pad(this.value.minute(), 2)
324 | }
325 | }
326 |
327 | AnytimePicker.prototype.show = function () {
328 | this.root.offsetParent.appendChild(this.el)
329 |
330 | this.el.classList.add('anytime-picker--is-visible')
331 |
332 | this.updatePosition()
333 |
334 | this.__events['doc escape hide'] = function (e) {
335 | // Hide if escape is pressed
336 | if (e.keyCode === 27) this.hide()
337 | }.bind(this)
338 |
339 | this.__events['doc click hide'] = function (e) {
340 | // Hide if document outside of anytime is clicked
341 | if (e.target === this.el) return
342 | if (this.el.contains(e.target)) return
343 | this.hide()
344 | }.bind(this)
345 |
346 | this.__events['other anytime open'] = function (e) {
347 | // Hide if another instance is opened
348 | if (e.detail.instance !== this) this.hide()
349 | }.bind(this)
350 |
351 | this.__events['window resize position'] = throttle(function () {
352 | // Update position when window is resized
353 | this.updatePosition()
354 | }.bind(this), 100)
355 |
356 | process.nextTick(function () {
357 | document.addEventListener('keyup', this.__events['doc escape hide'])
358 | document.addEventListener('click', this.__events['doc click hide'])
359 | document.addEventListener('anytime::open', this.__events['other anytime open'])
360 | window.addEventListener('resize', this.__events['window resize position'])
361 | document.dispatchEvent(new CustomEvent('anytime::open', { detail: { instance: this } }))
362 | }.bind(this))
363 | }
364 |
365 | AnytimePicker.prototype.hide = function () {
366 | this.el.classList.remove('anytime-picker--is-visible')
367 |
368 | document.removeEventListener('keyup', this.__events['doc escape hide'])
369 | delete this.__events['doc escape hide']
370 |
371 | document.removeEventListener('click', this.__events['doc click hide'])
372 | delete this.__events['doc click hide']
373 |
374 | document.removeEventListener('anytime::open', this.__events['other anytime open'])
375 | delete this.__events['keyup other anytime open']
376 |
377 | window.removeEventListener('resize', this.__events['window resize position'])
378 | delete this.__events['window resize position']
379 |
380 | if (this.el.parentNode) this.el.parentNode.removeChild(this.el)
381 | }
382 |
383 | AnytimePicker.prototype.updatePosition = function () {
384 | var position = { top: this.root.offsetTop, left: this.root.offsetLeft }
385 | var topOffset = (position.top + this.root.offsetHeight + this.options.offset)
386 | var leftOffset = (position.left + this.root.offsetWidth - this.el.offsetWidth)
387 |
388 | if (leftOffset < 0) {
389 | leftOffset = 0
390 | }
391 |
392 | var transformValue = 'translate(' + leftOffset + 'px, ' + topOffset + 'px)'
393 |
394 | this.el.style.webkitTransform = transformValue
395 | this.el.style.transform = transformValue
396 | this.el.style.top = 0
397 | this.el.style.left = 0
398 | }
399 |
400 | AnytimePicker.prototype.toggle = function () {
401 | if (this.el.classList.contains('anytime-picker--is-visible')) {
402 | this.hide()
403 | } else {
404 | this.show()
405 | }
406 | }
407 |
408 | AnytimePicker.prototype.showPrevMonth = function () {
409 | if (this.currentView.month > 0) {
410 | this.currentView.month--
411 | this.updateDisplay()
412 | return
413 | }
414 | if (this.currentView.year - 1 > this.options.minYear) {
415 | this.currentView.month = 11
416 | this.currentView.year--
417 | this.updateDisplay()
418 | }
419 | }
420 |
421 | AnytimePicker.prototype.showNextMonth = function () {
422 | if (this.currentView.month < 11) {
423 | this.currentView.month++
424 | this.updateDisplay()
425 | return
426 | }
427 | if (this.currentView.year + 1 < this.options.maxYear) {
428 | this.currentView.month = 0
429 | this.currentView.year++
430 | this.updateDisplay()
431 | }
432 | }
433 |
434 | AnytimePicker.prototype.renderTimeSelect = function (timeEl) {
435 | var hourSelect = document.createElement('select')
436 | hourSelect.classList.add('anytime-picker__dropdown')
437 | hourSelect.classList.add('anytime-picker__dropdown--hours')
438 | for (var i = 0; i < 24; i++) {
439 | var hour = document.createElement('option')
440 | hour.value = i
441 | hour.textContent = pad(i, 2)
442 | if (this.createMoment(this.options.initialValue).hours() === i) hour.selected = true
443 | hourSelect.appendChild(hour)
444 | }
445 |
446 | hourSelect.addEventListener('change', function (e) {
447 | this.update(function (value) {
448 | return value.hours(e.target.value)
449 | })
450 | }.bind(this))
451 |
452 | timeEl.appendChild(hourSelect)
453 |
454 | var colonEl = getTimeSeparator()
455 | timeEl.appendChild(colonEl)
456 |
457 | var minuteSelect = document.createElement('select')
458 | minuteSelect.classList.add('anytime-picker__dropdown')
459 | minuteSelect.classList.add('anytime-picker__dropdown--minutes')
460 | for (var j = 0; j < 60; j += this.options.minuteIncrement) {
461 | var minute = document.createElement('option')
462 | minute.value = j
463 | minute.textContent = pad(j, 2)
464 | if (this.createMoment(this.options.initialValue).minutes() === j) minute.selected = true
465 | minuteSelect.appendChild(minute)
466 | }
467 |
468 | minuteSelect.addEventListener('change', function (e) {
469 | this.update(function (value) {
470 | return value.minutes(e.target.value)
471 | })
472 | }.bind(this))
473 |
474 | timeEl.appendChild(minuteSelect)
475 |
476 | this.timeEls = { hours: hourSelect, minutes: minuteSelect }
477 | }
478 |
479 | AnytimePicker.prototype.renderTimeSliders = function (timeEl) {
480 | /* jshint maxstatements: 28 */
481 | var timeLabelEl = document.createElement('p')
482 | timeLabelEl.classList.add('anytime-picker__time-label')
483 |
484 | var timeLabelTitleEl = document.createElement('span')
485 | timeLabelTitleEl.classList.add('anytime-picker__time-label--title')
486 | timeLabelEl.appendChild(timeLabelTitleEl)
487 | timeLabelTitleEl.textContent = this.options.timeSlidersText
488 |
489 | var timeLabelHourEl = document.createElement('span')
490 | timeLabelHourEl.classList.add('anytime-picker__time-label--hour')
491 | timeLabelEl.appendChild(timeLabelHourEl)
492 | timeLabelHourEl.textContent = pad(this.createMoment(this.options.initialValue).hours(), 2)
493 |
494 | var colonEl = getTimeSeparator()
495 | timeLabelEl.appendChild(colonEl)
496 |
497 | var timeLabelMinuteEl = document.createElement('span')
498 | timeLabelMinuteEl.classList.add('anytime-picker__time-label--minute')
499 | timeLabelEl.appendChild(timeLabelMinuteEl)
500 | timeLabelMinuteEl.textContent = pad(this.createMoment(this.options.initialValue).minutes(), 2)
501 |
502 | timeEl.appendChild(timeLabelEl)
503 |
504 | var hourSlider = createSlider(
505 | { className: 'anytime-picker__slider--hours'
506 | , min: 0
507 | , max: 23
508 | , value: this.createMoment(this.options.initialValue).hours()
509 | , title: this.options.timeSlidersHourText
510 | })
511 |
512 | function updateHour(e) {
513 | this.update(function (value) {
514 | return value.hours(e.target.value)
515 | })
516 | timeLabelHourEl.textContent = pad(e.target.value, 2)
517 | }
518 |
519 | hourSlider.addEventListener('change', updateHour.bind(this))
520 | hourSlider.addEventListener('input', updateHour.bind(this))
521 |
522 | timeEl.appendChild(hourSlider)
523 |
524 | var minuteSlider = createSlider(
525 | { className: 'anytime-picker__slider--minutes'
526 | , min: 0
527 | , max: 59
528 | , value: this.createMoment(this.options.initialValue).minutes()
529 | , title: this.options.timeSlidersMinuteText
530 | })
531 |
532 | function updateMinute(e) {
533 | this.update(function (value) {
534 | return value.minutes(e.target.value)
535 | })
536 | timeLabelMinuteEl.textContent = pad(e.target.value, 2)
537 | }
538 |
539 | minuteSlider.addEventListener('change', updateMinute.bind(this))
540 | minuteSlider.addEventListener('input', updateMinute.bind(this))
541 |
542 | timeEl.appendChild(minuteSlider)
543 |
544 | this.timeEls =
545 | { hours: hourSlider
546 | , minutes: minuteSlider
547 | , hourLabel: timeLabelHourEl
548 | , minuteLabel: timeLabelMinuteEl
549 | }
550 | }
551 |
552 | AnytimePicker.prototype.renderTimeInput = function (timeEl) {
553 | if (this.options.showTime) {
554 | if (this.options.timeSliders) {
555 | this.renderTimeSliders(timeEl)
556 | } else {
557 | this.renderTimeSelect(timeEl)
558 | }
559 | }
560 | }
561 |
562 | AnytimePicker.prototype.destroy = function () {
563 | if (this.el) {
564 | this.hide()
565 | this.emit('destroy')
566 | this.removeAllListeners()
567 | if (this.options.button) this.options.button.removeEventListener('click', this.__events['misc toggle'])
568 | this.options.input.removeEventListener('click', this.__events['misc toggle'])
569 | delete this.__events['misc toggle']
570 | this.el = null
571 | }
572 | }
573 |
--------------------------------------------------------------------------------
/dist/anytime.js:
--------------------------------------------------------------------------------
1 | /**
2 | * anytime - A date/time picker
3 | * @version 1.4.2
4 | * @link http://bengourley.github.io/anytime/
5 | * @license ISC
6 | */
7 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("moment")):"function"==typeof define&&define.amd?define(["moment"],e):"object"==typeof exports?exports.anytime=e(require("moment")):t.anytime=e(t.moment)}(this,function(t){return function(t){function e(i){if(n[i])return n[i].exports;var r=n[i]={exports:{},id:i,loaded:!1};return t[i].call(r.exports,r,r.exports,e),r.loaded=!0,r.exports}var n={};return e.m=t,e.c=n,e.p="",e(0)}([function(t,e,n){(function(e){function i(t){this.options=s({},f,t),r.call(this),this.__events={},this.el=document.createElement("div"),this.el.className="js-anytime-picker anytime-picker",this.options.initialValue=this.getInitialValue();var e=this.createMoment(this.options.initialValue||this.options.initialView);this.currentView={month:e.month(),year:e.year()},this.value=this.options.initialValue?this.createMoment(this.options.initialValue).seconds(0).milliseconds(0):null,this.value&&!this.options.showTime&&(this.value=this.value.hour(0).minute(0)),this.monthNames=this.getMonthNames(),this.el.addEventListener("click",function(t){t.target.classList.contains("js-anytime-picker-day")&&(t.stopPropagation(),this.update(function(e){return e.date(parseInt(t.target.getAttribute("data-date"),10)).month(parseInt(t.target.getAttribute("data-month"),10)).year(parseInt(t.target.getAttribute("data-year"),10))}))}.bind(this)),this.el.addEventListener("click",function(t){t.preventDefault()}),this.__events["misc toggle"]=this.toggle.bind(this),this.options.button&&this.options.button.addEventListener("click",this.__events["misc toggle"]),this.options.input.addEventListener("click",this.__events["misc toggle"]),this.root=this.options.anchor?this.options.anchor:this.options.input,this.options.input&&(this.updateInput(this),this.on("change",this.updateInput.bind(this)))}t.exports=i;var r=n(8).EventEmitter,s=n(2),o=n(5),a=n(7),u=n(1),c=n(15),h=n(14),l=n(10),p=n(12),d=n(13),m=n(11),f={minYear:1960,maxYear:2030,offset:5,initialValue:null,initialView:new Date,format:"h:mma on dddd D MMMM YYYY",moment:u,minuteIncrement:1,showTime:!0,timeSliders:!1,shortMonthNames:!0,doneText:"Done",clearText:"Clear",timeSlidersText:"Time:",timeSlidersHourText:"Hour:",timeSlidersMinuteText:"Minute:"};i.prototype=Object.create(r.prototype),i.prototype.createMoment=m,i.prototype.updateInput=function(){this.options.input.value=this.value?this.value.format(this.options.format):""},i.prototype.getInitialValue=function(){return this.options.initialValue?this.options.initialValue:this.options.input&&this.options.input.value?this.options.input.value:null},i.prototype.getMonthNames=function(){return this.options.moment[this.options.shortMonthNames?"monthsShort":"months"]()},i.prototype.update=function(t){if(null===t||void 0===t)return this.value=null,this.updateDisplay(),void this.emit("change",null);if("function"!=typeof t){var e=t;t=function(){return this.createMoment(e)}.bind(this)}var n=t(this.value||this.createMoment());this.value=n,this.options.showTime||(this.value=this.value.hour(0).minute(0)),this.currentView={month:this.value.month(),year:this.value.year()},this.updateDisplay(),this.emit("change",this.value.toDate())},i.prototype.render=function(){var t=document.createElement("div");t.classList.add("anytime-picker__header"),this.renderHeader(t);var e=document.createElement("div");e.classList.add("anytime-picker__dates"),e.classList.add("js-anytime-picker-dates");var n;this.options.showTime&&(n=document.createElement("div"),n.classList.add("anytime-picker__time"),n.classList.add("js-anytime-picker-time"),this.renderTimeInput(n));var i=document.createElement("div");return i.classList.add("anytime-picker__footer"),this.renderFooter(i),this.el.appendChild(t),this.el.appendChild(e),this.options.showTime&&this.el.appendChild(n),this.el.appendChild(i),this.dateContainer=e,this.updateDisplay(),this},i.prototype.renderHeader=function(t){var e=l("<",["anytime-picker__button","anytime-picker__button--prev"]);t.appendChild(e),e.addEventListener("click",this.showPrevMonth.bind(this));var n=document.createElement("select");n.classList.add("js-anytime-picker-month"),n.classList.add("anytime-picker__dropdown"),this.monthNames.forEach(function(t,e){var i=document.createElement("option");i.textContent=t,e===this.currentView.month&&(i.selected=!0),n.appendChild(i)}.bind(this)),t.appendChild(n),this.monthSelect=n,n.addEventListener("change",function(t){this.currentView.month=this.monthNames.indexOf(t.target.value),this.updateDisplay()}.bind(this));var i=document.createElement("select");i.classList.add("js-anytime-picker-year"),i.classList.add("anytime-picker__dropdown"),c(this.options.minYear,this.options.maxYear).forEach(function(t){var e=document.createElement("option");e.textContent=t,t===this.currentView.year&&(e.selected=!0),i.appendChild(e)}.bind(this)),t.appendChild(i),this.yearSelect=i,i.addEventListener("change",function(t){this.currentView.year=t.target.value,this.updateDisplay()}.bind(this));var r=l(">",["anytime-picker__button","anytime-picker__button--next"]);t.appendChild(r),r.addEventListener("click",this.showNextMonth.bind(this))},i.prototype.renderFooter=function(t){var e=l(this.options.doneText,["anytime-picker__button","anytime-picker__button--done"]);t.appendChild(e),e.addEventListener("click",this.hide.bind(this));var n=l(this.options.clearText,["anytime-picker__button","anytime-picker__button--clear"]);t.appendChild(n),n.addEventListener("click",function(){this.update(null),this.hide()}.bind(this))},i.prototype.updateDisplay=function(){function t(){var t=this.options.moment.weekdaysMin();t.push(t.shift()),t.forEach(function(t){var e=document.createElement("span");e.textContent=t,e.classList.add("anytime-picker__day-name"),i.appendChild(e)})}function e(){for(var t=1;tn&&(n=0);var i="translate("+n+"px, "+e+"px)";this.el.style.webkitTransform=i,this.el.style.transform=i,this.el.style.top=0,this.el.style.left=0},i.prototype.toggle=function(){this.el.classList.contains("anytime-picker--is-visible")?this.hide():this.show()},i.prototype.showPrevMonth=function(){return this.currentView.month>0?(this.currentView.month--,void this.updateDisplay()):void(this.currentView.year-1>this.options.minYear&&(this.currentView.month=11,this.currentView.year--,this.updateDisplay()))},i.prototype.showNextMonth=function(){return this.currentView.month<11?(this.currentView.month++,void this.updateDisplay()):void(this.currentView.year+1n;n++){var i=document.createElement("option");i.value=n,i.textContent=a(n,2),this.createMoment(this.options.initialValue).hours()===n&&(i.selected=!0),e.appendChild(i)}e.addEventListener("change",function(t){this.update(function(e){return e.hours(t.target.value)})}.bind(this)),t.appendChild(e);var r=h();t.appendChild(r);var s=document.createElement("select");s.classList.add("anytime-picker__dropdown"),s.classList.add("anytime-picker__dropdown--minutes");for(var o=0;60>o;o+=this.options.minuteIncrement){var u=document.createElement("option");u.value=o,u.textContent=a(o,2),this.createMoment(this.options.initialValue).minutes()===o&&(u.selected=!0),s.appendChild(u)}s.addEventListener("change",function(t){this.update(function(e){return e.minutes(t.target.value)})}.bind(this)),t.appendChild(s),this.timeEls={hours:e,minutes:s}},i.prototype.renderTimeSliders=function(t){function e(t){this.update(function(e){return e.hours(t.target.value)}),s.textContent=a(t.target.value,2)}function n(t){this.update(function(e){return e.minutes(t.target.value)}),u.textContent=a(t.target.value,2)}var i=document.createElement("p");i.classList.add("anytime-picker__time-label");var r=document.createElement("span");r.classList.add("anytime-picker__time-label--title"),i.appendChild(r),r.textContent=this.options.timeSlidersText;var s=document.createElement("span");s.classList.add("anytime-picker__time-label--hour"),i.appendChild(s),s.textContent=a(this.createMoment(this.options.initialValue).hours(),2);var o=h();i.appendChild(o);var u=document.createElement("span");u.classList.add("anytime-picker__time-label--minute"),i.appendChild(u),u.textContent=a(this.createMoment(this.options.initialValue).minutes(),2),t.appendChild(i);var c=p({className:"anytime-picker__slider--hours",min:0,max:23,value:this.createMoment(this.options.initialValue).hours(),title:this.options.timeSlidersHourText});c.addEventListener("change",e.bind(this)),c.addEventListener("input",e.bind(this)),t.appendChild(c);var l=p({className:"anytime-picker__slider--minutes",min:0,max:59,value:this.createMoment(this.options.initialValue).minutes(),title:this.options.timeSlidersMinuteText});l.addEventListener("change",n.bind(this)),l.addEventListener("input",n.bind(this)),t.appendChild(l),this.timeEls={hours:c,minutes:l,hourLabel:s,minuteLabel:u}},i.prototype.renderTimeInput=function(t){this.options.showTime&&(this.options.timeSliders?this.renderTimeSliders(t):this.renderTimeSelect(t))},i.prototype.destroy=function(){this.el&&(this.hide(),this.emit("destroy"),this.removeAllListeners(),this.options.button&&this.options.button.removeEventListener("click",this.__events["misc toggle"]),this.options.input.removeEventListener("click",this.__events["misc toggle"]),delete this.__events["misc toggle"],this.el=null)}}).call(e,n(9))},function(e,n){e.exports=t},function(t,e,n){function i(t,e){return t="number"==typeof t||b.test(t)?+t:-1,e=null==e?y:e,t>-1&&t%1==0&&e>t}function r(t,e,n){var i=t[e];L.call(t,e)&&h(i,n)&&(void 0!==n||e in t)||(t[e]=n)}function s(t){return function(e){return null==e?void 0:e[t]}}function o(t,e,n){return a(t,e,n)}function a(t,e,n,i){n||(n={});for(var s=-1,o=e.length;++s1?n[r-1]:void 0,o=r>2?n[2]:void 0;for(s="function"==typeof s?(r--,s):void 0,o&&c(n[0],n[1],o)&&(s=3>r?void 0:s,r=1),e=Object(e);++i-1&&t%1==0&&y>=t}function m(t){var e=typeof t;return!!t&&("object"==e||"function"==e)}var f=n(3),v=n(4),y=9007199254740991,_="[object Function]",g="[object GeneratorFunction]",b=/^(?:0|[1-9]\d*)$/,w=Object.prototype,L=w.hasOwnProperty,E=w.toString,x=s("length"),k=u(function(t,e){o(e,f(e),t)});t.exports=k},function(t,e){function n(t,e){for(var n=-1,i=Array(t);++n-1&&t%1==0&&e>t}function r(t,e){return k.call(t,e)||"object"==typeof t&&e in t&&null===T(t)}function s(t){return j(Object(t))}function o(t){return function(e){return null==e?void 0:e[t]}}function a(t){var e=t?t.length:void 0;return d(e)&&(A(t)||v(t)||c(t))?n(e,String):null}function u(t){var e=t&&t.constructor,n=p(e)&&e.prototype||x;return t===n}function c(t){return l(t)&&k.call(t,"callee")&&(!V.call(t,"callee")||C.call(t)==g)}function h(t){return null!=t&&!("function"==typeof t&&p(t))&&d(M(t))}function l(t){return f(t)&&h(t)}function p(t){var e=m(t)?C.call(t):"";return e==b||e==w}function d(t){return"number"==typeof t&&t>-1&&t%1==0&&_>=t}function m(t){var e=typeof t;return!!t&&("object"==e||"function"==e)}function f(t){return!!t&&"object"==typeof t}function v(t){return"string"==typeof t||!A(t)&&f(t)&&C.call(t)==L}function y(t){var e=u(t);if(!e&&!h(t))return s(t);var n=a(t),o=!!n,c=n||[],l=c.length;for(var p in t)!r(t,p)||o&&("length"==p||i(p,l))||e&&"constructor"==p||c.push(p);return c}var _=9007199254740991,g="[object Arguments]",b="[object Function]",w="[object GeneratorFunction]",L="[object String]",E=/^(?:0|[1-9]\d*)$/,x=Object.prototype,k=x.hasOwnProperty,C=x.toString,T=Object.getPrototypeOf,V=x.propertyIsEnumerable,j=Object.keys,M=o("length"),A=Array.isArray;t.exports=y},function(t,e){function n(t,e,n){var i=n.length;switch(i){case 0:return t.call(e);case 1:return t.call(e,n[0]);case 2:return t.call(e,n[0],n[1]);case 3:return t.call(e,n[0],n[1],n[2])}return t.apply(e,n)}function i(t,e){if("function"!=typeof t)throw new TypeError(u);return e=w(void 0===e?t.length-1:o(e),0),function(){for(var i=arguments,r=-1,s=w(i.length-e,0),o=Array(s);++rt?-1:1;return e*h}var n=t%1;return t===t?n?t-n:t:0}function a(t){if(s(t)){var e=r(t.valueOf)?t.valueOf():t;t=s(e)?e+"":e}if("string"!=typeof t)return 0===t?t:+t;t=t.replace(m,"");var n=v.test(t);return n||y.test(t)?_(t.slice(2),n?2:8):f.test(t)?l:+t}var u="Expected a function",c=1/0,h=1.7976931348623157e308,l=NaN,p="[object Function]",d="[object GeneratorFunction]",m=/^\s+|\s+$/g,f=/^[-+]0x[0-9a-f]+$/i,v=/^0b[01]+$/i,y=/^0o[0-7]+$/i,_=parseInt,g=Object.prototype,b=g.toString,w=Math.max;t.exports=i},function(t,e,n){function i(t,e,n){var i=!0,a=!0;if("function"!=typeof t)throw new TypeError(o);return r(n)&&(i="leading"in n?!!n.leading:i,a="trailing"in n?!!n.trailing:a),s(t,e,{leading:i,maxWait:e,trailing:a})}function r(t){var e=typeof t;return!!t&&("object"==e||"function"==e)}var s=n(6),o="Expected a function";t.exports=i},function(t,e){function n(t,e,n){function i(){g&&clearTimeout(g),d&&clearTimeout(d),w=0,p=d=v=g=b=void 0}function a(e,n){n&&clearTimeout(n),d=g=b=void 0,e&&(w=_(),m=t.apply(v,p),g||d||(p=v=void 0))}function u(){var t=e-(_()-f);0>=t||t>e?a(b,d):g=setTimeout(u,t)}function c(){return(g&&b||d&&x)&&(m=t.apply(v,p)),i(),m}function h(){a(x,g)}function l(){if(p=arguments,f=_(),v=this,b=x&&(g||!L),E===!1)var n=L&&!g;else{w||d||L||(w=f);var i=E-(f-w),r=(0>=i||i>E)&&(L||d);r?(d&&(d=clearTimeout(d)),w=f,m=t.apply(v,p)):d||(d=setTimeout(h,i))}return r&&g?g=clearTimeout(g):g||e===E||(g=setTimeout(u,e)),n&&(r=!0,m=t.apply(v,p)),!r||g||d||(p=v=void 0),m}var p,d,m,f,v,g,b,w=0,L=!1,E=!1,x=!0;if("function"!=typeof t)throw new TypeError(o);return e=s(e)||0,r(n)&&(L=!!n.leading,E="maxWait"in n&&y(s(n.maxWait)||0,e),x="trailing"in n?!!n.trailing:x),l.cancel=i,l.flush=c,l}function i(t){var e=r(t)?v.call(t):"";return e==u||e==c}function r(t){var e=typeof t;return!!t&&("object"==e||"function"==e)}function s(t){if(r(t)){var e=i(t.valueOf)?t.valueOf():t;t=r(e)?e+"":e}if("string"!=typeof t)return 0===t?t:+t;t=t.replace(h,"");var n=p.test(t);return n||d.test(t)?m(t.slice(2),n?2:8):l.test(t)?a:+t}var o="Expected a function",a=NaN,u="[object Function]",c="[object GeneratorFunction]",h=/^\s+|\s+$/g,l=/^[-+]0x[0-9a-f]+$/i,p=/^0b[01]+$/i,d=/^0o[0-7]+$/i,m=parseInt,f=Object.prototype,v=f.toString,y=Math.max,_=Date.now;t.exports=n},function(t,e){"use strict";function n(t,e,n){var i=t.toString();if(!e||i.length>=e)return i;var r=new Array(e-i.length+1).join(n||"0");return r+i}t.exports=n},function(t,e){function n(){this._events=this._events||{},this._maxListeners=this._maxListeners||void 0}function i(t){return"function"==typeof t}function r(t){return"number"==typeof t}function s(t){return"object"==typeof t&&null!==t}function o(t){return void 0===t}t.exports=n,n.EventEmitter=n,n.prototype._events=void 0,n.prototype._maxListeners=void 0,n.defaultMaxListeners=10,n.prototype.setMaxListeners=function(t){if(!r(t)||0>t||isNaN(t))throw TypeError("n must be a positive number");return this._maxListeners=t,this},n.prototype.emit=function(t){var e,n,r,a,u,c;if(this._events||(this._events={}),"error"===t&&(!this._events.error||s(this._events.error)&&!this._events.error.length)){if(e=arguments[1],e instanceof Error)throw e;throw TypeError('Uncaught, unspecified "error" event.')}if(n=this._events[t],o(n))return!1;if(i(n))switch(arguments.length){case 1:n.call(this);break;case 2:n.call(this,arguments[1]);break;case 3:n.call(this,arguments[1],arguments[2]);break;default:a=Array.prototype.slice.call(arguments,1),n.apply(this,a)}else if(s(n))for(a=Array.prototype.slice.call(arguments,1),c=n.slice(),r=c.length,u=0;r>u;u++)c[u].apply(this,a);return!0},n.prototype.addListener=function(t,e){var r;if(!i(e))throw TypeError("listener must be a function");return this._events||(this._events={}),this._events.newListener&&this.emit("newListener",t,i(e.listener)?e.listener:e),this._events[t]?s(this._events[t])?this._events[t].push(e):this._events[t]=[this._events[t],e]:this._events[t]=e,s(this._events[t])&&!this._events[t].warned&&(r=o(this._maxListeners)?n.defaultMaxListeners:this._maxListeners,r&&r>0&&this._events[t].length>r&&(this._events[t].warned=!0,console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.",this._events[t].length),"function"==typeof console.trace&&console.trace())),this},n.prototype.on=n.prototype.addListener,n.prototype.once=function(t,e){function n(){this.removeListener(t,n),r||(r=!0,e.apply(this,arguments))}if(!i(e))throw TypeError("listener must be a function");var r=!1;return n.listener=e,this.on(t,n),this},n.prototype.removeListener=function(t,e){var n,r,o,a;if(!i(e))throw TypeError("listener must be a function");if(!this._events||!this._events[t])return this;if(n=this._events[t],o=n.length,r=-1,n===e||i(n.listener)&&n.listener===e)delete this._events[t],this._events.removeListener&&this.emit("removeListener",t,e);else if(s(n)){for(a=o;a-- >0;)if(n[a]===e||n[a].listener&&n[a].listener===e){r=a;break}if(0>r)return this;1===n.length?(n.length=0,delete this._events[t]):n.splice(r,1),this._events.removeListener&&this.emit("removeListener",t,e)}return this},n.prototype.removeAllListeners=function(t){var e,n;if(!this._events)return this;if(!this._events.removeListener)return 0===arguments.length?this._events={}:this._events[t]&&delete this._events[t],this;if(0===arguments.length){for(e in this._events)"removeListener"!==e&&this.removeAllListeners(e);return this.removeAllListeners("removeListener"),this._events={},this}if(n=this._events[t],i(n))this.removeListener(t,n);else if(n)for(;n.length;)this.removeListener(t,n[n.length-1]);return delete this._events[t],this},n.prototype.listeners=function(t){var e;return e=this._events&&this._events[t]?i(this._events[t])?[this._events[t]]:this._events[t].slice():[]},n.prototype.listenerCount=function(t){if(this._events){var e=this._events[t];if(i(e))return 1;if(e)return e.length}return 0},n.listenerCount=function(t,e){return t.listenerCount(e)}},function(t,e){function n(){c=!1,o.length?u=o.concat(u):h=-1,u.length&&i()}function i(){if(!c){var t=setTimeout(n);c=!0;for(var e=u.length;e;){for(o=u,u=[];++h1)for(var n=1;ne)throw new Error("min year must be before max year");for(var n=[],i=t;e>=i;i++)n.push(i);return n}t.exports=n}])});
--------------------------------------------------------------------------------