├── .babelrc
├── .gitignore
├── .npmignore
├── .scripts
├── build-api-docs.sh
├── build-docs.sh
├── build.sh
├── bump.sh
├── release-docs.sh
└── template.hbs
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── bower.json
├── docs
├── README.md
├── _coverpage.md
├── _demo.md
├── _sidebar.md
├── _stylesheets.md
├── demo
│ ├── .babelrc
│ ├── .gitignore
│ ├── config
│ │ └── webpack.config.js
│ ├── package.json
│ ├── public
│ │ └── index.html
│ └── src
│ │ ├── app.js
│ │ ├── dpicker.cycle.js
│ │ ├── index.js
│ │ └── style.scss
├── index.html
└── logo.svg
├── examples
├── angular2.directive.ts
└── index.html
├── jsdoc.conf.json
├── module.test.js
├── package-lock.json
├── package.json
├── src
├── adapters
│ └── moment.js
├── all.js
├── datetime.js
├── dpicker.js
├── dpicker.moment.js
├── plugins
│ ├── arrow-navigation.js
│ ├── modifiers.js
│ ├── monthAndYear.js
│ ├── navigation.js
│ └── time.js
└── polyfills.js
└── test
├── adapters
└── moment.spec.js
├── dpicker.spec.js
├── mocha.global.js
├── mocha.opts
└── plugins
├── arrow-navigation.spec.js
├── modifiers.spec.js
└── time.spec.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | "yo-yoify"
4 | ],
5 | "presets": [
6 | "env"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | coverage
4 | docs/_api.md
5 | docs/CHANGELOG.md
6 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | demo
2 | !dist/*.js
3 | test.js
4 | gulpfile.js
5 | screen.png
6 | bower_components
7 | bump.sh
8 | src/*.spec.js
9 |
--------------------------------------------------------------------------------
/.scripts/build-api-docs.sh:
--------------------------------------------------------------------------------
1 | #!env bash
2 | $(npm bin)/jsdoc2md --template .scripts/template.hbs -f src/dpicker.js src/plugins/*.js src/adapters/*.js > docs/_api.md
3 |
--------------------------------------------------------------------------------
/.scripts/build-docs.sh:
--------------------------------------------------------------------------------
1 | #!env bash
2 | bash .scripts/build.sh docs
3 | cp CHANGELOG.md docs/CHANGELOG.md
4 | cd docs/demo
5 | npm install
6 | NODE_ENV=production $(npm bin)/webpack --config config/webpack.config.js
7 | cd ../../
8 | bash .scripts/build-api-docs.sh
9 |
--------------------------------------------------------------------------------
/.scripts/build.sh:
--------------------------------------------------------------------------------
1 | #!env bash
2 |
3 | browserify="$(npm bin)/browserify"
4 | uglifyjs="$(npm bin)/uglifyjs"
5 |
6 | GLOBAL_ARGS="-g unassertify -t babelify -x moment"
7 | MIN_ARGS="$GLOBAL_ARGS -g uglifyify"
8 |
9 | RELEASE_BUILD=0
10 | if [[ $1 != '' ]]; then
11 | RELEASE_BUILD=1
12 | fi
13 |
14 | build() {
15 | args=$GLOBAL_ARGS
16 |
17 | if [[ $3 == 1 ]]; then
18 | args="-s dpicker $args"
19 | else
20 | args="-s ${2/./_} $args"
21 | fi
22 |
23 | echo "Browserifying src/$1 => $2"
24 | sh -c "$browserify $args src/$1 -o dist/$2.js"
25 |
26 | if [[ $RELEASE_BUILD == 1 ]]; then
27 | echo "Browserifying src/$1 => $2.min.js"
28 | sh -c "$browserify -g uglifyify $args src/$1 | $uglifyjs -c > dist/$2.min.js"
29 | fi
30 | }
31 |
32 | rm dist/* &> /dev/null
33 | mkdir dist &> /dev/null
34 |
35 | echo
36 | echo "Build"
37 |
38 | build "dpicker.moment" "dpicker" 1 &
39 | build "dpicker" "dpicker.core" 1 &
40 | build "plugins/time" "dpicker.time" &
41 | build "plugins/modifiers" "dpicker.modifiers" &
42 | build "plugins/arrow-navigation" "dpicker.arrow-navigation" &
43 | build "plugins/navigation" "dpicker.navigation" &
44 | build "plugins/monthAndYear" "dpicker.monthAndYear" &
45 |
46 | wait
47 |
48 | if [[ $RELEASE_BUILD == 1 ]]; then
49 | echo
50 | echo "Build release files"
51 |
52 | # Build date + time
53 | echo "Browserifying datetime"
54 | sh -c "$browserify $GLOBAL_ARGS -s dpicker src/datetime.js -o dist/dpicker.datetime.js"
55 | sh -c "$browserify $MIN_ARGS -s dpicker src/datetime.js -o dist/dpicker.datetime.min.js"
56 | # Build all
57 | echo "Browserifying all"
58 | sh -c "$browserify $GLOBAL_ARGS -s dpicker src/all.js -o dist/dpicker.all.js"
59 | sh -c "$browserify $MIN_ARGS -s dpicker src/all -o dist/dpicker.all.min.js"
60 |
61 | echo "Browserifying polyfills"
62 | $browserify -g uglifyify src/polyfills.js -o dist/polyfills.min.js
63 |
64 | echo
65 | echo "Sizes"
66 |
67 | for f in dist/*.min.js; do
68 | echo "$f.gz: " $(node -pe "$(gzip -c $f | wc -c) * 0.001") kb
69 | done
70 | fi
71 |
72 | exit 0
73 |
--------------------------------------------------------------------------------
/.scripts/bump.sh:
--------------------------------------------------------------------------------
1 | #!env bash
2 |
3 | [[ '' == $1 ]] && echo "Please provide patch, minor, major argument" && exit 1
4 |
5 | tag='next'
6 |
7 | if [[ $1 == 'patch' || $1 == 'minor' || $1 == 'major' ]]; then
8 | tag='latest'
9 | fi
10 |
11 | bash .scripts/build.sh forpublish
12 | npm test
13 | newver=$(npm --no-git-tag-version version $1)
14 | git add -f dist package.json package-lock.json
15 | git commit -m $newver
16 | git tag $newver
17 | npm publish --tag $tag
18 | git reset --hard HEAD~1
19 | newver=$(npm --no-git-tag-version version $1)
20 | git add package.json package-lock.json
21 | git commit -m $newver
22 | git push --tags
23 | git push
24 |
--------------------------------------------------------------------------------
/.scripts/release-docs.sh:
--------------------------------------------------------------------------------
1 | #!env bash
2 | DPICKER_VERSION=$(jq -r .version package.json)
3 |
4 | git checkout gh-pages
5 | git reset --hard origin/master
6 | bash .scripts/build-docs.sh
7 | mv -f docs/* .
8 | sed -i.bak "s/DPICKER_VERSION/$DPICKER_VERSION/g" _coverpage.md
9 | sed -i.bak "s/DPICKER_VERSION/$DPICKER_VERSION/g" README.md
10 | rm *.bak
11 | touch .nojekyll
12 | git add -f *.md
13 | git add .nojekyll index.html logo.svg demo
14 | git add -f demo/public/dist
15 | git commit -m 'bump doc'
16 | git push -fu origin gh-pages
17 | rm -r demo
18 | rm index.html logo.svg
19 | git checkout master
20 | git reset --hard origin/master
21 |
--------------------------------------------------------------------------------
/.scripts/template.hbs:
--------------------------------------------------------------------------------
1 | {{#function name="DPicker"}}
2 | {{>docs}}
3 | {{/function}}
4 | {{#function name="onChange"}}
5 | {{>docs}}
6 | {{/function}}
7 | {{#module name="MomentAdapter"}}
8 | {{>docs}}
9 | {{/module}}
10 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | node_js:
4 | - "8"
5 |
6 | install:
7 | - npm install
8 | - npm run build
9 |
10 | before_script:
11 | - npm install -g codecov
12 |
13 | after_success:
14 | - npm run coverage
15 | - codecov coverage/lcov.info
16 |
17 | matrix:
18 | fast_finish: true
19 |
20 | # container-base
21 | sudo: false
22 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## 5.0.2
4 |
5 | - Fix sibling months day click gave incorrect date
6 |
7 | ## 5.0.1
8 |
9 | - Globals are evil. By trying to make things easy to use, we declared a global `window.DPicker` variable. Since `5.0.1` it's not the case anymore and it will be declared as `window.dpicker`.
10 | It's recommended that you use a bundle system though (webpack, rollup, browserify...).
11 |
12 | - Plugins are exporting a `function`. Makes things easier to pre-build packages. This means that this won't work anymore:
13 |
14 | ```javascript
15 |
16 |
17 | ```
18 |
19 | To get date + time, you should use:
20 |
21 | ```javascript
22 |
23 | ```
24 |
25 | Or the package with every module:
26 |
27 | ```javascript
28 |
29 | ```
30 |
31 | ## 5.0.0
32 |
33 | - Use browserify instead of gulp
34 | - Use real dom, real dates ([nanomorph](https://github.com/yoshuawuyts/nanomorph) for diffing and [bel](https://github.com/shama/bel)
35 | - Use [standard](https://github.com/feross/standard)
36 | - Get rid of momentjs hard dependency (still required for now)
37 | - Implement a Date Adapter for future libraries support
38 | - Simplify plugins api
39 | - uses immutable `Date` instances internally! [BC]
40 |
41 | No more `_modules` stuff. Just simple objects creation:
42 |
43 | ```javascript
44 | DPicker.renders.closeButton = function renderCloseButton(events, data) {
45 | const button = document.createElement('button')
46 | button.innerText = 'Confirm'
47 | button.type = 'button'
48 | button.classList.add('dpicker-close-button')
49 | button.addEventListener('click', events.hidePicker)
50 | return button
51 | }
52 |
53 | DPicker.events.hidePicker = function hidePicker() {
54 | this.display = false
55 | }
56 | ```
57 |
58 | - Incremental builds
59 |
60 | You can use `dpicker.datetime.js` directly. Coming soon builds with `date-fns`. Available builds:
61 |
62 | ```
63 | dist/dpicker.all.min.js.gz: 7.698 kb
64 | dist/dpicker.arrow-navigation.min.js.gz: 0.9410000000000001 kb
65 | dist/dpicker.core.min.js.gz: 4.892 kb
66 | dist/dpicker.datetime.min.js.gz: 6.947 kb
67 | dist/dpicker.min.js.gz: 5.699 kb
68 | dist/dpicker.modifiers.min.js.gz: 0.58 kb
69 | dist/dpicker.navigation.min.js.gz: 0.6900000000000001 kb
70 | dist/dpicker.time.min.js.gz: 2.08 kb
71 | dist/polyfills.min.js.gz: 4.5200000000000005 kb
72 | ```
73 |
74 | - Improve docs (A LOT) https://soyuka.github.io/dpicker thanks to [jsdoc2md](https://github.com/jsdoc2md/jsdoc-to-markdown) and [docsify](https://github.com/QingWei-Li/docsify/)
75 | - Build a decent demo using cycle.js
76 | - naming public stuff #33
77 |
78 | `_events` => `events`
79 | `_data` => `data`
80 |
81 | - new getter for `empty`
82 |
83 | ## 4.2.0
84 |
85 | `onChange` now has a second argument giving informations about the recent change, for example:
86 |
87 | ```javascript
88 | dpicker.onChange = function(data, event) {
89 | console.log('Model changed? %s', event.modelChanged ? 'yes' : 'no')
90 | console.log('DPicker event: %s', event.name)
91 | console.log('DPicker original event: %s', event.event)
92 | }
93 | ```
94 |
95 | ## 4.0.9
96 |
97 | - Adds an option to enable sibling month days
98 |
99 | ## 4.0.8
100 |
101 | - Adds an option `concatHoursAndMinutes` to merge hours and minutes in one `select`
102 |
103 | ## 4.0.0
104 |
105 | - Drop hyperscript library, it's not needed anymore and it's not possible to add a custom one
106 | - An invalid Date doesn't reset the value to the closest correct one, it leaves the choice to the user instead
107 | - A class `.invalid` is added to the input
108 | - `aria-*` attributes are now working
109 |
110 | ## 3.1.1
111 |
112 | Add touch friendly behavior #20
113 |
114 | ## 3.1.0
115 |
116 | Drop maquettejs hard dependency (see #1). You can now use your own hyperscript library, for example with mithriljs:
117 |
118 | ```
119 | let dpicker = DPicker(label, {
120 | h: mithril,
121 | mount: function(element, toRender) {
122 | mithril.render(element, toRender())
123 | },
124 | redraw: mithril.redraw
125 | })
126 | ```
127 |
128 | ## 3.0.0
129 |
130 | `time-format` renamed to `meridiem`
131 |
132 | ## 2.0.0
133 |
134 | `isEmpty` renamed `empty`
135 |
136 | ## 1.3.0
137 |
138 | Drop `previousYear` and `futureYear`. Those are replaced by `min` and `max`:
139 | - `input[type="date"]` polyfill (#2)
140 | - allows to change the months selection on max/min dates
141 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017 Antoine Bluchet
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DPicker
2 |
3 |
4 |
5 | [](https://travis-ci.org/soyuka/dpicker)
6 | [](https://codecov.io/gh/soyuka/dpicker)
7 |
8 | A framework-agnostic minimal date picker.
9 |
10 | ## Quick start
11 |
12 | ```html
13 |
16 |
17 |
18 |
21 | ```
22 |
23 | ## Read the docs
24 |
25 | - [Full Documentation (web)](https://soyuka.github.io/dpicker/)
26 | - [/docs](https://github.com/soyuka/dpicker/tree/master/docs)
27 |
28 | ## License
29 |
30 | MIT
31 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dpicker",
3 | "description": "Minimalist date picker",
4 | "main": [
5 | "dist/dpicker.js"
6 | ],
7 | "authors": [
8 | "soyuka"
9 | ],
10 | "license": "MIT",
11 | "keywords": [
12 | "date",
13 | "picker"
14 | ],
15 | "homepage": "https://github.com/soyuka/dpicker",
16 | "ignore": [
17 | "**/.*",
18 | "node_modules",
19 | "bower_components",
20 | "test.js",
21 | "src/*.spec.js",
22 | "!dist/*.js",
23 | "screen.png",
24 | "gulpfile.js",
25 | "demo",
26 | "bump.sh",
27 | "doc",
28 | "coverage"
29 | ],
30 | "dependencies": {
31 | "moment": "^2.13.0"
32 | },
33 | "devDependencies": {
34 | "maquette": "^2.3.3",
35 | "foundation-sites": "^6.2.3"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/soyuka/dpicker)
2 | [](https://codecov.io/gh/soyuka/dpicker)
3 |
4 | A framework-agnostic minimal date picker.
5 |
6 | ## Installation
7 |
8 | ### npm
9 |
10 | ```
11 | npm install dpicker --save
12 | ```
13 |
14 | ### bower
15 |
16 | ```
17 | bower install dpicker --save
18 | ```
19 |
20 | !> Package managers are only referencing `dpicker.core.js`! This is useful to build your own package! See below for pre-built packages.
21 |
22 | To keep `dpicker` small, every new feature is added through a module. You can either use those separatly, or use one of the pre-built package.
23 |
24 | We've built-in the following for an easy installation (suffix with `.min.js` for the minified version):
25 |
26 | - `dpicker.all` contains every module (~7.7kb)
27 | - `dpicker.datetime` contains only core and time (~6.9kb)
28 | - `dpicker` contains core with momentjs adapter (~5.6kb)
29 | - `dpicker.core` contains only core (~4.89kb)
30 |
31 | For example with the [unpkg cdn](https://unpkg.com):
32 |
33 | ```html
34 |
35 | ```
36 |
37 | Modules alone:
38 |
39 | - `dpicker.arrow-navigation` enable keyboard arrows to navigate between days (~0.1kb)
40 | - `dpicker.modifiers` enable modifiers, for example `+` to get current day, `+100` to get the date in 100 days. (~0.5kb)
41 | - `dpicker.time` enable time (~2kb)
42 |
43 | When using modules, the best is to bundle them with your favorite tool, for example:
44 |
45 | ```javascript
46 | var DPicker = require('dpicker') //dpicker.core
47 | DPicker.dateAdapter = require('dpicker/src/adapters/moment')
48 | require('dpicker/dist/dpicker.time')(DPicker)
49 | ```
50 |
51 | For old browser support, you will need the `polyfill` file:
52 |
53 | ```html
54 |
55 | ```
56 |
57 | ?> Those polyfills are [array.prototype.fill](https://www.npmjs.com/package/array.prototype.fill) and [dom4](https://www.npmjs.com/package/dom4)
58 |
59 | The default library to handle dates is `momentjs`, just add it to your script:
60 |
61 | ```html
62 |
63 | ```
64 |
65 | ?> I'm working on getting `date-fns` as alternative, please let me know if you wish to see other date adapters. Want to build your own take a look at the [Date adapter section](#date-adapter)
66 |
67 | ## Usage
68 |
69 | !> Global variables are not safe, but if you're not using a bundle system (wepback, rollup, browserify), you may use the global `window.dpicker` variable.
70 |
71 | To create a date picker, just init the DPicker module within a container:
72 |
73 | ```html
74 |
75 |
78 | ```
79 |
80 | If you have an input already, you can init the datepicker with it, the date picker container will be the input parent node:
81 |
82 | ```html
83 |
87 |
90 | ```
91 |
92 | ## Examples
93 |
94 | ### Html initalization
95 |
96 | Let's initialize every `date` and `datetime` inputs with DPicker by using this ugly one-liner:
97 |
98 | ```javascript
99 | [].slice.call(document.querySelectorAll('input[type="date"],input[type="datetime"]')).forEach(function(e){new dpicker(e);});
100 | ```
101 |
102 | HTML can now take multiple formats, the simplest would be:
103 |
104 | ```html
105 |
108 | ```
109 |
110 | With a custom format and value:
111 |
112 | ```html
113 |
116 | ```
117 |
118 | Or with every available options:
119 |
120 | ```html
121 |
124 | ```
125 |
126 | Note that every specified attribute have to be strings, and if it's a date it should be in the given format.
127 |
128 | Now with time (24h format):
129 |
130 | ```html
131 |
134 | ```
135 |
136 | Or with AM/PM time format (specify the time format, ie: 12, 12h)
137 |
138 | ```html
139 |
142 | ```
143 |
144 | ### Javascript
145 |
146 | Let's do the oposite by declaring a simple html container:
147 |
148 | ```html
149 |
150 | ```
151 |
152 | Basic initalization:
153 |
154 | ```javascript
155 | var container = document.getElementById('my-dpicker')
156 | var dp = new dpicker(container)
157 | ```
158 |
159 | Setup almost every option:
160 |
161 | ```javascript
162 | var container = document.getElementById('my-dpicker')
163 | var dp = new dpicker(container, {
164 | model: moment(), //today
165 | min: moment('1986-01-01'),
166 | max: moment().add(1, 'year').month(11), //today + 1 year
167 | format: 'DD/MM/YYYY hh:mm A',
168 | time: true, //add time
169 | meridiem: true, //12h format
170 | months: moment.monthsShort(),
171 | days: moment.weekdaysMin()
172 | })
173 | ```
174 |
175 | More options are available, for a complete list [check the api documentation](_api#dpickerelement-options).
176 |
177 | Change values during the date picker life cycle:
178 |
179 | ```javascript
180 | var container = document.getElementById('my-dpicker')
181 | var dp = new dpicker(container)
182 | // do things
183 |
184 | dp.format = 'DD/MM/YYYY'
185 | dp.min = moment('01/01/1991')
186 | dp.max = moment('01/01/2020')
187 | dp.time = true
188 | ```
189 |
190 | Available properties are listed in the [api documentation](_api#dpickerelement-options), where [some properties](_api#dpickerproperties) belong to modules.
191 |
192 | Let's get further with Javascript with the [`onChange` method](_api#onchangedata-dpickerevent). This is a hook to listen to any change that comes from DPicker. It helps integrating the date picker in any framework.
193 |
194 | ```javascript
195 | var container = document.getElementById('my-dpicker')
196 | var dp = new dpicker(container)
197 |
198 | dp.onChange = function(data, DPickerEvent) {
199 | // has the model changed?
200 | console.log(DPickerEvent.modelChanged)
201 | // the name of the internal event
202 | console.log(DPickerEvent.name)
203 | // the origin DOM event
204 | console.dir(DPickerEvent.event)
205 | }
206 |
207 | ```
208 |
209 | ## CSS
210 |
211 | No css is included by default, here's the minimal style:
212 |
213 | ```css
214 | td.dpicker-inactive {
215 | color: grey;
216 | }
217 |
218 | button.dpicker-active {
219 | background: coral;
220 | }
221 |
222 | .dpicker-invisible {
223 | display: none;
224 | }
225 | .dpicker-visible {
226 | display: block;
227 | }
228 | ```
229 |
230 | [You can find alternatives stylesheets here.](_stylesheets)
231 |
232 | ## Modules
233 |
234 | ### Concept
235 |
236 | As stated above, DPicker has a *built-in module system*. Actually, as DPicker is built with two core concepts, it's nothing more than:
237 |
238 | 1. render methods
239 | 2. event listeners
240 |
241 | A render method is a context-less function, which must return one DOM element.
242 | It always has two parameters:
243 |
244 | - `events` an Object with every event listener available in DPicker
245 | - `data` an Object with the DPicker data
246 |
247 | Example:
248 |
249 | ```javascript
250 | DPicker.renders.myRenderFn = function (data, events) {
251 | var el = document.createElement('div')
252 | el.onclick = events.doSomeStuff
253 |
254 | return el
255 | }
256 | ```
257 |
258 | An event listener is a function that will be executed in DPicker context. This means that `this.data` will be the `data` object of our date picker.
259 |
260 | Example:
261 |
262 | ```javascript
263 | DPicker.events.doSomeStuff = function (evt) {
264 | evt.preventDefault()
265 | this.data.foo = 'foobar'
266 | this.redraw() // Call the public redraw method, those are documented in the API docs
267 | }
268 | ```
269 |
270 | ### Example
271 |
272 | #### Confirm button
273 |
274 | Straightforward, plain javascript module that adds a close button:
275 |
276 | ```javascript
277 | //add 'closeButton' to the `order` array
278 | DPicker.renders.closeButton = function renderCloseButton(events, data) {
279 | const button = document.createElement('button')
280 | button.innerText = 'Confirm'
281 | button.type = 'button'
282 | button.classList.add('dpicker-close-button')
283 | button.addEventListener('click', events.hidePicker)
284 | return button
285 | }
286 |
287 | DPicker.events.hidePicker = function hidePicker() {
288 | this.display = false
289 | }
290 | ```
291 |
292 | Then, initialize your DPicker with `new DPicker(container, {order: ['months', 'years', 'time', 'days', 'closeButton']})`.
293 |
294 | #### Previous/Next month buttons
295 |
296 | Let's build an other module that will add two arrows to navigate between previous and next months.
297 |
298 | To make this more readable, we will use the [`bel`](https://github.com/shama/bel) module. This module can then be [`yo-yoified`](https://github.com/shama/yo-yoify) or [`babel-yo-yoified`](https://github.com/goto-bus-stop/babel-plugin-yo-yoify) to transform our html text to real DOM elements.
299 |
300 | First, let's add two rendering methods for our buttons:
301 |
302 | ```javascript
303 | const html = require('bel')
304 |
305 | DPicker.renders.previousMonth = function renderPreviousMonth(events, data) {
306 | return html``
307 | }
308 |
309 | DPicker.renders.nextMonth = function renderNextMonth(events, data) {
310 | return html``
311 | }
312 | ```
313 |
314 | Now let's add two events to make this work. Note that to play with dates you should use the [`DateAdapter`](_api#momentadapter):
315 |
316 | ```javascript
317 | DPicker.events.previousMonth = function previousMonth(evt) {
318 | // go one month back
319 | this.model = DPicker.dateAdapter.subMonths(this.data.model, 1)
320 | // redraw the DPicker
321 | this.redraw()
322 | // This is not mandatory but is good if you want your changes to reach DPicker.onChange
323 | this.onChange({modelChanged: true, name: 'previousMonth', event: evt})
324 | }
325 |
326 | DPicker.events.nextMonth = function nextMonth(evt) {
327 | this.model = DPicker.dateAdapter.addMonths(this.data.model, 1)
328 | this.redraw()
329 | this.onChange({modelChanged: true, name: 'nextMonth', event: evt})
330 | }
331 | ```
332 |
333 | We're done. To enable your render functions, you have to specify their keys in the `order` option:
334 |
335 | ```javascript
336 | new dpicker(element, {order: ['previousMonth', 'months', 'years', 'nextMonth', 'days', 'time']})
337 | ```
338 |
339 | This module is actually available [here](https://github.com/soyuka/dpicker/blob/development/src/plugins/navigation.js).
340 |
341 | ### Go further
342 |
343 | Sometimes, you just want to do more work when one of the [available events or methods](_api) from DPicker are called. For this to work we can `decorate` public methods or events.
344 |
345 | For example, let's add a call when `dpicker#initialize` is called:
346 |
347 | ```javascript
348 | DPicker.prototype.initialize = DPicker.decorate(DPicker.prototype.initialize, function myPluginInit () {
349 | //do some stuff
350 | })
351 | ```
352 |
353 | Or with an event:
354 |
355 | ```javascript
356 | DPicker.events.dayKeyDown = DPicker.decorate(DPicker.events.dayKeyDown, function DayKeyDown (evt) {
357 | // a keydown event was called on a day
358 | })
359 | ```
360 |
361 | !> Note that if a decoration returns `false`, it'll stop the call chain.
362 |
363 | Last but not least, you can add options to your plugin. DPicker will automatically try to instantiate the given properties via:
364 |
365 | 1. attributes (the DOM attributes of the given `input`)
366 | 2. options (the options given to `DPicker` constructor)
367 | 3. a default value
368 |
369 | Properties should be added like this:
370 |
371 | ```javascript
372 | DPicker.properties.myOption = false
373 | ```
374 |
375 | This sets up `this.data.myOption`, and has a default `false` value.
376 |
377 | If you want to customize the behavior on the `attributes` parsing, you can use a `function`:
378 |
379 | ```javascript
380 | DPicker.properties.step = function getStepAttribute (attributes) {
381 | return attributes.step ? parseInt(attributes.step, 10) : 1
382 | }
383 | ```
384 |
385 | ### Share the module
386 |
387 | The best to share a module is to embed it in a function:
388 |
389 | ```javascript
390 | module.exports = function(DPicker) {
391 |
392 | }
393 | ```
394 |
395 | Then you can bundle your own DPicker:
396 |
397 | ```javascript
398 | const DPicker = require('dpicker')
399 | const MomentDateAdapter = require('dpicker/src/adapters/moment')
400 |
401 | DPicker.dateAdapter = MomentDateAdapter
402 |
403 | // Require some modules here
404 | require('dpicker/src/plugins/time')(DPicker)
405 | require('./my-awesome-dpicker-module')(DPicker)
406 |
407 | module.exports = DPicker
408 | ```
409 |
410 | ## Framework agnostic
411 |
412 | !> Those are only base examples, please adapt them to your needs!
413 |
414 | ### Angular 1
415 |
416 | ?> Simple Angular 1 example that leverages ngModelCtrl. Some more bits are needed for validation.
417 |
418 | ```javascript
419 | angular.module('DPicker', [])
420 |
421 | angular.module('DPicker')
422 | .directive('dpDpicker', function() {
423 | return {
424 | restrict: 'A',
425 | scope: {
426 | ngModel: '='
427 | },
428 | require: 'ngModel',
429 | link: function(scope, element, attrs, ngModelCtrl) {
430 | scope.dpicker = new dpicker(element[0])
431 | scope.dpicker.onChange = function(data, DPickerEvent) {
432 | if (DPickerEvent.modelChanged === true) {
433 | ngModelCtrl.$setViewValue(scope.dpicker.model)
434 | }
435 | }
436 |
437 | if (scope.ngModel && scope.ngModel instanceof Date) {
438 | scope.dpicker.model = scope.ngModel
439 | }
440 |
441 | ngModelCtrl.$setViewValue(scope.dpicker.empty ? null : scope.dpicker.model)
442 |
443 | attrs.$observe('ngModel', function(value) {
444 | if (value instanceof Date) {
445 | scope.dpicker.model = value
446 | }
447 | })
448 | }
449 | }
450 | })
451 | ```
452 |
453 | ### Angular 2
454 |
455 | ?> This Angular 2 example assumes that the ngModel is a Date. This example also attach an angular 2 validator.
456 |
457 | ```javascript
458 | import { forwardRef, ElementRef, Directive, Input, OnInit } from '@angular/core'
459 | import { NG_VALUE_ACCESSOR, ControlValueAccessor, NG_VALIDATORS, AbstractControl } from '@angular/forms'
460 |
461 | import * as DPicker from 'dpicker'
462 |
463 | @Directive({
464 | selector: '[prefixDpicker]',
465 | providers: [{
466 | provide: NG_VALUE_ACCESSOR,
467 | useExisting: forwardRef(() => PrefixDpickerDirective),
468 | multi: true
469 | },
470 | {
471 | provide: NG_VALIDATORS,
472 | useExisting: forwardRef(() => PrefixDpickerDirective),
473 | multi: true
474 | }
475 | ],
476 | })
477 | export class PrefixDpickerDirective implements ControlValueAccessor, OnInit {
478 | dpicker: any
479 | @Input() max: Date
480 | @Input() min: Date
481 |
482 | private onChangeCallback: (_: any) => void = () => {}
483 | private onTouchedCallback: () => void = () => {}
484 |
485 | constructor(public elementRef: ElementRef) {}
486 |
487 | ngOnInit() {
488 | try {
489 | this.dpicker = new dpicker(this.elementRef.nativeElement, {min: this.min, max: this.max})
490 | } catch (e) {
491 | console.error(e.message)
492 | }
493 |
494 | this.dpicker.onChange = (data, DPickerEvent) => {
495 | if (DPickerEvent.modelChanged === true) {
496 | this.onChangeCallback(this.dpicker.model)
497 | }
498 |
499 | this.onTouchedCallback()
500 | }
501 | }
502 |
503 | registerOnChange(fn: any) {
504 | this.onChangeCallback = fn
505 | }
506 |
507 | registerOnTouched(fn: any) {
508 | this.onTouchedCallback = fn
509 | }
510 |
511 | writeValue(value: any) {
512 | this.dpicker.model = value
513 | }
514 |
515 | validate(c: AbstractControl): { [key: string]: any } {
516 | this.dpicker.isValid(c.value)
517 |
518 | if (this.dpicker.valid === true) {
519 | return null
520 | }
521 |
522 | return {validDate: false}
523 | }
524 | }
525 | ```
526 |
527 | Html:
528 |
529 | ```html
530 |
536 | ```
537 |
538 | ### Cycle.js
539 |
540 | To bundle `DPicker` in the cyclejs DOM, let's create a [Component](https://cycle.js.org/components.html#components).
541 |
542 | ?> We are setting a [`snabbdom` hook](https://github.com/snabbdom/snabbdom#hooks) on `insert`. This allows us to set up DPicker on the real `Element`, once appended to the DOM.
543 |
544 | ```javascript
545 | import {div} from '@cycle/dom'
546 | import xs from 'xstream'
547 | import DPicker from 'dpicker/dist/dpicker.all'
548 |
549 | export function CycleDPicker (selector, sources) {
550 |
551 | const value$ = sources.DOM.select(selector)
552 | .events('dpicker:change')
553 | .map((ev) => {
554 | return ev.detail
555 | })
556 |
557 | const state$ = sources.props
558 | .map((props) => {
559 | return value$
560 | .startWith(props)
561 | })
562 | .flatten()
563 | .remember()
564 |
565 | const vdom$ = state$.map((state) => {
566 | return div(selector, {
567 | hook: {
568 | insert: (vnode) => {
569 | const dpicker = new DPicker(vnode.elm, state)
570 | dpicker.onChange = function(data, modelChanged) {
571 | if (modelChanged === false) {
572 | return
573 | }
574 |
575 | const evt = new CustomEvent('dpicker:change', {bubbles: true, detail: dpicker.data})
576 | vnode.elm.dispatchEvent(evt)
577 | }
578 | }
579 | }
580 | })
581 | })
582 |
583 | return {
584 | DOM: vdom$,
585 | state: state$
586 | }
587 | }
588 | ```
589 |
590 | Usage:
591 |
592 | ```javascript
593 | const myDpicker = CycleDPicker('.cycle-dpicker-max', {
594 | DOM: sources.DOM,
595 | props: xs.of({name: 'max', model: new Date()}) //dpicker options
596 | })
597 |
598 | // Streams:
599 |
600 | const vdom$ = myDpicker.DOM
601 | const state$ = myDpicker.state
602 | ```
603 |
604 | ?> **TODO** add more examples
605 |
606 | ## Date Adapter
607 |
608 | Because framework agnostic also means that we don't want to force you to use one or another Date library, DPicker uses a `DateAdapter`. It's a simple bridge module that exposes needed functions. If you want to implement your own date adapter, implement the [DateAdapter as documented in the API](_api#momentadapter). Dates MUST be immutable!
609 |
610 | Referencing the `dateAdapter` of your choice is done through the static property:
611 |
612 | ```javascript
613 | DPicker.dateAdapter = MyDateAdapter
614 | ```
615 |
616 | For this to work nicely, I'd recommend to use `dpicker.core.js` and to build with `browserify`. The end file will look like this:
617 |
618 | ```javascript
619 | const DPicker = require('dpicker')
620 | const MyDateAdapter = require('./my.date.adapter')
621 |
622 | DPicker.dateAdapter = MyDateAdapter
623 |
624 | // Require some modules here
625 | require('dpicker/src/plugins/time')(DPicker)
626 |
627 | module.exports = DPicker
628 | ```
629 |
630 | Use our 100% coverage test case instead of building your own tests!
631 |
632 | ## Why?
633 |
634 | I was searching for a simple date picker, with only basic features and an ability to work with any framework, or event plain javascript (VanillaJS).
635 | If you know one that does have less than 1000 SLOC, please let me know.
636 |
637 | This date picker:
638 |
639 | - is light and easy to use, especially easy to maintain (core has ~500 SLOC), uses `Date` and `DOM` objects.
640 | - is compliant and can be extended to suit your needs
641 | - no default css so that it fits well with foundation/bootstrap
642 | - is framework agnostic
643 | - has HTML attributes compatibility with `input[type="date"]` (adds a `format` attribute) and `input[type="datetime"]` (adds a `meridiem` attribute on top of the `format` one if you need 12 hours time range). Define minutes step through the `step` attribute.
644 | - has in mind to work with any Date module (momentjs, date-fns)
645 | - extensible through modules, use the core and implement your specific needs easily
646 |
647 | What I think is good, and isn't straightforward in other date pickers is that your input's `Date` instance is separated from the input real value:
648 |
649 | ```javascript
650 | const dpicker = new dpicker(input)
651 |
652 | console.log(dpicker.model) //the Date instance
653 | console.log(dpicker.input) //the input value, a formatted date
654 | ```
655 |
656 | ## Credits
657 |
658 | DPicker refactor (from `4` to `5`) motivation:
659 |
660 | - [date-fns](https://github.com/date-fns/date-fns) - because I think this is a good alternative to momentjs, especially since it's immutable
661 | - [nanomorph](https://github.com/yoshuawuyts/nanomorph), in fact [@yoshuawuyts](https://github.com/yoshuawuyts) writings in general
662 | - [bel](https://github.com/shama/bel), why did I discover this in 2017?
663 | - [babel-plugin-yo-yoify](https://github.com/goto-bus-stop/babel-plugin-yo-yoify) for the reactivity and because this works great
664 | - [@florianpircher](https://github.com/florianpircher), [@stereonom](https://github.com/stereonom), [@thcolin](https://github.com/thcolin) for the motivation, the really good bug reports, and the design ideas/talks
665 |
666 | DPicker docs:
667 |
668 | - [jsdoc2md](https://github.com/jsdoc2md/jsdoc-to-markdown)
669 | - [docsify](https://github.com/QingWei-Li/docsify/)
670 |
671 | ## License
672 |
673 | ```
674 | The MIT License (MIT)
675 |
676 | Copyright (c) 2016 Antoine Bluchet
677 |
678 | Permission is hereby granted, free of charge, to any person obtaining a copy
679 | of this software and associated documentation files (the "Software"), to deal
680 | in the Software without restriction, including without limitation the rights
681 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
682 | copies of the Software, and to permit persons to whom the Software is
683 | furnished to do so, subject to the following conditions:
684 |
685 | The above copyright notice and this permission notice shall be included in
686 | all copies or substantial portions of the Software.
687 |
688 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
689 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
690 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
691 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
692 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
693 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
694 | THE SOFTWARE.
695 | ```
696 |
--------------------------------------------------------------------------------
/docs/_coverpage.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # DPicker DPICKER_VERSION
4 |
5 | > A framework-agnostic minimal date picker.
6 |
7 | - Easy to use
8 | - Lightweight (core is 4.89kb)
9 | - Fully featured
10 | - Extendable - build you own modules
11 |
12 | [Demo](_demo)
13 | [Get Started](#installation)
14 |
15 | 
16 |
--------------------------------------------------------------------------------
/docs/_demo.md:
--------------------------------------------------------------------------------
1 |
39 |
--------------------------------------------------------------------------------
/docs/_sidebar.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | - [Home](/)
4 | - [Demo](_demo.md)
5 | - [StyleSheets](_stylesheets.md)
6 | - [Api](_api.md)
7 | - [Changelog](CHANGELOG.md)
8 |
9 |
--------------------------------------------------------------------------------
/docs/_stylesheets.md:
--------------------------------------------------------------------------------
1 | # Stylesheets
2 |
3 | ## Minimal
4 |
5 | ```css
6 | td.dpicker-inactive {
7 | color: grey;
8 | }
9 |
10 | button.dpicker-active {
11 | background: coral;
12 | }
13 |
14 | .dpicker-invisible {
15 | display: none;
16 | }
17 |
18 | .dpicker-visible {
19 | display: block;
20 | }
21 | ```
22 |
23 | ## Foundation
24 |
25 | ```css
26 | @import 'foundation';
27 |
28 | $black: #2f3439 !default;
29 | $white: #fefefe !default;
30 | $alt-white: #f0f0f0 !default;
31 |
32 | label.dpicker .dpicker-container { top: inherit; }
33 |
34 | .dpicker {
35 | position: relative;
36 |
37 | .dpicker-container {
38 | background: $white;
39 | border: $black;
40 | flex-wrap: wrap;
41 | justify-content: space-between;
42 | left: 0;
43 | padding: 15px;
44 | position: absolute;
45 | top: ($input-font-size + ($form-spacing * 1.5) - rem-calc(1));
46 | width: 300px;
47 | z-index: 2;
48 |
49 | &.dpicker-invisible { display: none; }
50 | &.dpicker-visible { display: flex; }
51 |
52 | select {
53 | flex: 0 0 49%;
54 | }
55 |
56 | .dpicker-time {
57 | display: flex;
58 |
59 | select {
60 | flex: 0 0 30%;
61 | }
62 | }
63 |
64 | table {
65 | color: $black;
66 | text-align: center;
67 | $dpicker-hover: scale-color($alt-white, $lightness: -20%);
68 |
69 | td {
70 | border: 1px solid $black;
71 | border-collapse: collapse;
72 | height: 40px;
73 | width: 40px;
74 | }
75 |
76 | .dpicker-inactive {
77 | color: scale-color($black, $lightness: 50%);
78 | font-size: $small-font-size;
79 | }
80 |
81 | .dpicker-active button {
82 |
83 | @include button($expand: true, $background: $alt-white, $background-hover: $dpicker-hover)
84 |
85 | border: 0;
86 | color: inherit;
87 | height: 100%;
88 | margin: 0;
89 | padding: 0;
90 |
91 | &.dpicker-active {
92 | background-color: $dpicker-hover;
93 | }
94 | }
95 | }
96 | }
97 | }
98 | ```
99 |
100 | ## Bootstrap
101 |
102 | From the [demo](_demo) (scss)
103 |
104 | ```css
105 | @import 'bootstrap';
106 |
107 | @mixin form-control {
108 | display: block;
109 | width: 100%;
110 | height: $input-height-base; // Make inputs at least the height of their button counterpart (base line-height + padding + border)
111 | padding: $padding-base-vertical $padding-base-horizontal;
112 | font-size: $font-size-base;
113 | line-height: $line-height-base;
114 | color: $input-color;
115 | background-color: $input-bg;
116 | background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214
117 | border: 1px solid $input-border;
118 | border-radius: $input-border-radius; // Note: This has no effect on