├── .babelrc ├── circle.yml ├── test ├── tests.bundle.js ├── karma.conf.js └── core.test.jsx ├── documentation-images └── date-picker-screencast.gif ├── example ├── webpack.example.config.js ├── README.md ├── index.html └── app.jsx ├── .gitignore ├── LICENSE ├── package.json ├── README.md ├── src └── index.jsx └── lib └── index.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["stage-1", "es2015", "react"] 3 | } 4 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | general: 2 | branches: 3 | only: 4 | - master -------------------------------------------------------------------------------- /test/tests.bundle.js: -------------------------------------------------------------------------------- 1 | var ctx = require.context('.', true, /.+\.test\.jsx?$/); 2 | require.context('.', true, /.+\.test\.jsx?$/).keys().forEach(ctx); 3 | module.exports = ctx; -------------------------------------------------------------------------------- /documentation-images/date-picker-screencast.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outlandishideas/react-bootstrap-date-picker/2.0.1/documentation-images/date-picker-screencast.gif -------------------------------------------------------------------------------- /example/webpack.example.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require("webpack"); 2 | var path = require("path"); 3 | 4 | module.exports = { 5 | entry: { 6 | index: path.resolve(__dirname, './app.jsx') 7 | }, 8 | output: { 9 | path: __dirname, 10 | filename: 'bundle.js' 11 | }, 12 | resolve: { 13 | extensions: ['', '.js', '.jsx'] 14 | }, 15 | module: { 16 | loaders: [ 17 | { 18 | exclude: /node_modules/, 19 | loader: 'babel-loader', 20 | test: /\.jsx?$/ 21 | } 22 | ] 23 | }, 24 | devtool: 'inline-source-map' 25 | }; -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Table of Contents

4 | 5 | - [Running this example](#running-this-example) 6 | 7 | 8 | 9 | ### Running this example 10 | 11 | 1. Clone the [react-bootstrap-date-picker repository](https://github.com/pushtell/react-bootstrap-date-picker) 12 | 2. Run `npm install` to install dependencies 13 | 3. Run `npm run example` 14 | 4. Open [http://localhost:8080](http://localhost:8080) in your browser -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | run-browserstack-tests.sh 30 | 31 | bundle.js -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React-Bootstrap Date Picker Example 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010-2015, John Wehr 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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-bootstrap-date-picker-outlandish", 3 | "keywords": [ 4 | "react", 5 | "react-component", 6 | "react-bootstrap", 7 | "bootstrap", 8 | "date picker", 9 | "calendar", 10 | "date", 11 | "picker" 12 | ], 13 | "main": "lib/index.js", 14 | "version": "2.0.2", 15 | "description": "React-Bootstrap based date picker.", 16 | "directories": { 17 | "test": "test" 18 | }, 19 | "peerDependencies": { 20 | "react": ">=0.14.0", 21 | "react-bootstrap": "^0.28.1" 22 | }, 23 | "devDependencies": { 24 | "assert": "*", 25 | "babel": "^6.1.18", 26 | "babel-cli": "^6.7.5", 27 | "babel-loader": "^6.2.0", 28 | "babel-preset-es2015": "*", 29 | "babel-preset-react": "*", 30 | "babel-preset-stage-1": "*", 31 | "co": "*", 32 | "envify": "*", 33 | "es6-promise": "*", 34 | "expose-loader": "*", 35 | "isparta-loader": "*", 36 | "karma": "*", 37 | "karma-browserstack-launcher": "*", 38 | "karma-chrome-launcher": "*", 39 | "karma-coverage": "*", 40 | "karma-coveralls": "*", 41 | "karma-firefox-launcher": "*", 42 | "karma-mocha": "*", 43 | "karma-sourcemap-loader": "*", 44 | "karma-webpack": "*", 45 | "mocha": "*", 46 | "mocha-babel": "*", 47 | "node-uuid": "*", 48 | "react": "^15.0.1", 49 | "react-bootstrap": "^0.28.1", 50 | "react-dom": "^15.0.1", 51 | "regenerator": "*", 52 | "regenerator-loader": "*", 53 | "transform-loader": "*", 54 | "webpack": "*", 55 | "webpack-dev-server": "*" 56 | }, 57 | "scripts": { 58 | "example": "./node_modules/webpack-dev-server/bin/webpack-dev-server.js --content-base example/ --config ./example/webpack.example.config.js", 59 | "gh-pages": "./node_modules/webpack/bin/webpack.js --content-base example/ --config ./example/webpack.example.config.js; cp ./example/index.html /tmp/index.html; cp ./example/bundle.js /tmp/bundle.js; git checkout gh-pages; cp /tmp/index.html ./index.html; cp /tmp/bundle.js ./bundle.js; git commit -a -m 'Automatic Example Update'; git push origin gh-pages; git checkout master;", 60 | "test": "./node_modules/karma/bin/karma start test/karma.conf.js", 61 | "build": "doctoc . --github --title '

Table of Contents

'; ./node_modules/babel-cli/bin/babel.js ./src --out-dir ./lib;" 62 | }, 63 | "repository": { 64 | "type": "git", 65 | "url": "git+https://github.com/outlandishideas/react-bootstrap-date-picker.git" 66 | }, 67 | "author": "", 68 | "license": "MIT", 69 | "bugs": { 70 | "url": "https://github.com/pushtell/react-bootstrap-date-picker/issues" 71 | }, 72 | "homepage": "https://github.com/pushtell/react-bootstrap-date-picker#readme" 73 | } 74 | -------------------------------------------------------------------------------- /test/karma.conf.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | 3 | module.exports = function (karma) { 4 | var options = { 5 | files: [ 6 | 'tests.bundle.js' 7 | ], 8 | frameworks: ['mocha'], 9 | plugins: [ 10 | 'karma-firefox-launcher', 11 | 'karma-chrome-launcher', 12 | 'karma-mocha', 13 | 'karma-coveralls', 14 | 'karma-coverage', 15 | 'karma-sourcemap-loader', 16 | 'karma-webpack', 17 | 'karma-browserstack-launcher' 18 | ], 19 | preprocessors: { 20 | 'tests.bundle.js': ['webpack', 'sourcemap'] 21 | }, 22 | customLaunchers: { 23 | Chrome_without_localstorage: { 24 | base: 'Chrome', 25 | flags: ['--disable-local-storage'] 26 | }, 27 | bs_windows_7_ie_9: { 28 | base: 'BrowserStack', 29 | os: 'Windows', 30 | os_version: '7', 31 | browser: 'ie', 32 | browser_version : '9.0' 33 | }, 34 | bs_windows_7_ie_10: { 35 | base: 'BrowserStack', 36 | os: 'Windows', 37 | os_version: '7', 38 | browser: 'ie', 39 | browser_version : '10.0' 40 | }, 41 | bs_windows_7_ie_11: { 42 | base: 'BrowserStack', 43 | os: 'Windows', 44 | os_version: '7', 45 | browser: 'ie', 46 | browser_version : '11.0' 47 | }, 48 | bs_windows_7_opera_latest: { 49 | base: 'BrowserStack', 50 | os: 'Windows', 51 | os_version: '7', 52 | browser: 'opera', 53 | browser_version : 'latest' 54 | }, 55 | bs_windows_7_firefox_latest: { 56 | base: 'BrowserStack', 57 | os: 'Windows', 58 | os_version: '7', 59 | browser: 'firefox', 60 | browser_version : 'latest' 61 | }, 62 | bs_windows_7_chrome_latest: { 63 | base: 'BrowserStack', 64 | os: 'Windows', 65 | os_version: '7', 66 | browser: 'chrome', 67 | browser_version : 'latest' 68 | }, 69 | bs_osx_yosemite_safari: { 70 | base: 'BrowserStack', 71 | os: 'OS X', 72 | os_version: 'Yosemite', 73 | browser: 'safari', 74 | browser_version : 'latest' 75 | }, 76 | bs_ios_8_default: { 77 | base: 'BrowserStack', 78 | os_version: "8.3", 79 | device: "iPhone 6", 80 | browser_version: null, 81 | os: "ios", 82 | browser: "iphone" 83 | } 84 | }, 85 | reporters: ['dots', 'coverage'], 86 | coverageReporter: { 87 | type: 'lcov', 88 | dir: 'coverage/' 89 | }, 90 | singleRun: true, 91 | webpack: { 92 | resolve: { 93 | extensions: ['', '.js', '.jsx'] 94 | }, 95 | devtool: 'inline-source-map', 96 | module: { 97 | loaders: [ 98 | { 99 | exclude: /(node_modules|lib|example)/, 100 | loader: 'babel-loader', 101 | test: /\.jsx?$/ 102 | }, { 103 | exclude: /(node_modules|lib|example)/, 104 | loader: 'regenerator-loader', 105 | test: /\.jsx?$/ 106 | }, { 107 | include: path.resolve('src'), 108 | loader: 'isparta', 109 | test: /\.jsx?$/ 110 | } 111 | ] 112 | } 113 | }, 114 | browserNoActivityTimeout: 30000, 115 | browserDisconnectTolerance: 2, 116 | webpackMiddleware: { 117 | noInfo: true, 118 | } 119 | }; 120 | if(process.env.BROWSERSTACK_USERNAME && process.env.BROWSERSTACK_ACCESS_KEY) { 121 | options.browserStack = { 122 | username: process.env.BROWSERSTACK_USERNAME, 123 | accessKey: process.env.BROWSERSTACK_ACCESS_KEY, 124 | pollingTimeout: 30000 125 | }; 126 | options.browsers = Object.keys(options.customLaunchers).filter(function(key){ 127 | return key.indexOf("bs_") !== -1; 128 | }); 129 | } else { 130 | options.browsers = ['Chrome']; 131 | } 132 | if(process.env.COVERALLS_REPO_TOKEN) { 133 | options.reporters.push('coveralls'); 134 | } 135 | karma.set(options); 136 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React-Bootstrap based date picker. 2 | 3 | Forked from v2.0.0 of [React-Bootstrap date picker](https://www.npmjs.com/package/react-bootstrap-date-picker) 4 | 5 | [![NPM Version](https://badge.fury.io/js/react-bootstrap-date-picker.svg)](https://www.npmjs.com/package/react-bootstrap-date-picker) 6 | [![Circle CI](https://circleci.com/gh/pushtell/react-bootstrap-date-picker.svg?style=shield)](https://circleci.com/gh/pushtell/react-bootstrap-date-picker) 7 | [![Coverage Status](https://coveralls.io/repos/pushtell/react-bootstrap-date-picker/badge.svg?branch=master&service=github)](https://coveralls.io/github/pushtell/react-bootstrap-date-picker?branch=master) 8 | [![Dependency Status](https://david-dm.org/pushtell/react-bootstrap-date-picker.svg)](https://david-dm.org/pushtell/react-bootstrap-date-picker) 9 | [![NPM Downloads](https://img.shields.io/npm/dm/react-bootstrap-date-picker.svg?style=flat)](https://www.npmjs.com/package/react-bootstrap-date-picker) 10 | 11 | See the demo at [pushtell.github.io/react-bootstrap-date-picker](http://pushtell.github.io/react-bootstrap-date-picker/). 12 | 13 | [![Demo](https://cdn.rawgit.com/pushtell/react-bootstrap-date-picker/master/documentation-images/date-picker-screencast.gif)](http://pushtell.github.io/react-bootstrap-date-picker/) 14 | 15 | Please [★ on GitHub](https://github.com/pushtell/react-bootstrap-date-picker)! 16 | 17 | 18 | 19 |

Table of Contents

20 | 21 | - [Installation](#installation) 22 | - [Usage](#usage) 23 | - [API Reference](#api-reference) 24 | - [``](#datepicker-) 25 | - [Tests](#tests) 26 | - [Browser Coverage](#browser-coverage) 27 | - [Running Tests](#running-tests) 28 | 29 | 30 | 31 | ## Installation 32 | 33 | `react-bootstrap-date-picker` is compatible with React 0.14.x and 0.15.x. 34 | 35 | ```bash 36 | npm install react-bootstrap-date-picker 37 | ``` 38 | 39 | ## Usage 40 | 41 | ```js 42 | 43 | var DatePicker = require("react-bootstrap-date-picker"); 44 | 45 | var App = React.createClass({ 46 | getInitialState: function(){ 47 | var value = new Date().getISOString(); 48 | return { 49 | value: value 50 | } 51 | }, 52 | handleChange: function(value) { 53 | // value is an ISO String. 54 | this.setState({ 55 | value: value 56 | }); 57 | }, 58 | render: function(){ 59 | return
60 | 61 |
; 62 | } 63 | }); 64 | 65 | ``` 66 | 67 | ## API Reference 68 | 69 | ### `` 70 | 71 | DatePicker component. Renders as a [react-bootstrap input element](https://react-bootstrap.github.io/components.html#forms). 72 | 73 | [Input element](https://react-bootstrap.github.io/components.html#forms) properties are passed through to the input element. 74 | 75 | * **Properties:** 76 | * `dateFormat` - Date format. `"MM/DD/YYYY"` or `"DD/MM/YYYY"` or `"YYYY/MM/DD"` 77 | * **Optional** 78 | * **Type:** `string` 79 | * **Example:** `"MM/DD/YYYY"` 80 | * `clearButtonElement` - Character or component to use for the clear button. 81 | * **Optional** 82 | * **Type:** `string` or `ReactClass` 83 | * **Example:** `"×"` 84 | * `previousButtonElement` - Character or component to use for the calendar's previous button. 85 | * **Optional** 86 | * **Type:** `string` or `ReactClass` 87 | * **Example:** `"<"` 88 | * `nextButtonElement` - Character or component to use for the calendar's next button. 89 | * **Optional** 90 | * **Type:** `string` or `ReactClass` 91 | * **Example:** `">"` 92 | * `cellPadding` - CSS padding value for calendar date cells. 93 | * **Optional** 94 | * **Type:** `string` 95 | * **Example:** `"2px"` 96 | * `dayLabels` - Array of day names to use in the calendar. Starting on Sunday. 97 | * **Optional** 98 | * **Type:** `array` 99 | * **Example:** `['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']` 100 | * `monthLabels` - Array of month names to use in the calendar. 101 | * **Optional** 102 | * **Type:** `array` 103 | * **Example:** `['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']` 104 | * `calendarPlacement` - Overlay placement for the popover calendar. 105 | * **Optional** 106 | * **Type:** `string` 107 | * **Example:** `"top"` 108 | 109 | ## Tests 110 | 111 | ### Browser Coverage 112 | 113 | [Karma](http://karma-runner.github.io/0.13/index.html) tests are performed on [Browserstack](https://www.browserstack.com/) in the following browsers: 114 | 115 | * IE 9, Windows 7 116 | * IE 10, Windows 7 117 | * IE 11, Windows 7 118 | * Opera (latest version), Windows 7 119 | * Firefox (latest version), Windows 7 120 | * Chrome (latest version), Windows 7 121 | * Safari (latest version), OSX Yosemite 122 | * Mobile Safari (latest version), iPhone 6, iOS 8.3 123 | 124 | Please [let us know](https://github.com/pushtell/react-bootstrap-date-picker/issues/new) if a different configuration should be included here. 125 | 126 | ### Running Tests 127 | 128 | Locally: 129 | 130 | ```bash 131 | 132 | npm test 133 | 134 | ``` 135 | 136 | On [Browserstack](https://www.browserstack.com/): 137 | 138 | ```bash 139 | 140 | BROWSERSTACK_USERNAME=YOUR_USERNAME BROWSERSTACK_ACCESS_KEY=YOUR_ACCESS_KEY npm test 141 | 142 | ``` 143 | -------------------------------------------------------------------------------- /example/app.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Grid from "react-bootstrap/lib/Grid"; 4 | import Row from "react-bootstrap/lib/Row"; 5 | import Col from "react-bootstrap/lib/Col"; 6 | import NavBar from "react-bootstrap/lib/NavBar"; 7 | import Nav from "react-bootstrap/lib/Nav"; 8 | import NavItem from "react-bootstrap/lib/NavItem"; 9 | import DatePicker from "../src/index.jsx"; 10 | import Glyphicon from 'react-bootstrap/lib/Glyphicon'; 11 | 12 | const spanishDayLabels = ['Dom', 'Lu', 'Ma', 'Mx', 'Ju', 'Vi', 'Sab']; 13 | const spanishMonthLabels = ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre']; 14 | 15 | const App = React.createClass({ 16 | getInitialState() { 17 | return { 18 | date: new Date().toISOString(), 19 | previousDate: null 20 | }; 21 | }, 22 | handleChange(value) { 23 | this.setState({ 24 | date: value 25 | }); 26 | }, 27 | render() { 28 | const LabelISOString = new Date().toISOString(); 29 | return 30 | 31 | 32 |

React-Bootstrap Date Picker

33 | 34 |
35 | 36 | 37 | 38 | 43 | 44 | 45 | 46 | 47 | 48 |

Change Handler

49 | 50 |
51 | 52 | 53 |
54 | 55 | 56 | 57 |
58 | 59 | 60 |

Blur and Focus Events

61 | 62 |
63 | 64 | 65 |
66 | {console.log("Focus")}} onBlur={() => {console.log("Blur")}}/> 67 | 68 | 69 |
70 | 71 | 72 |

Styles

73 | 74 |
75 | 76 | 77 |
78 | 79 | 80 | 81 | 82 |
83 | 84 | 85 | 86 | 87 |
88 | 89 | 90 | 91 |
92 | 93 | 94 |

Date Format

95 | 96 |
97 | 98 | 99 |
100 | 101 | 102 | 103 | 104 |
105 | 106 | 107 | 108 | 109 |
110 | 111 | 112 | 113 |
114 | 115 | 116 |

Custom

117 | 118 |
119 | 120 | 121 |
122 | } /> 123 | 124 | 125 | 126 |
127 | } nextButtonElement={} /> 128 | 129 | 130 | 131 |
132 | 133 | 134 | 135 | 136 |
137 | 138 | 139 | 140 |
141 | 142 | 143 |

Placement

144 | 145 |
146 | 147 | 148 |
149 | 150 | 151 | 152 | 153 |
154 | 155 | 156 | 157 | 158 |
159 | 160 | 161 | 162 | 163 |
164 | 165 | 166 | 167 |
168 | 169 | 170 |

Sizes

171 | 172 |
173 | 174 | 175 |
176 | 177 | 178 | 179 | 180 |
181 | 182 | 183 | 184 |
185 | 186 | 187 |
188 | 189 | 190 | 191 | 192 |
193 | 194 | 195 | 196 |
197 | 198 | 199 |
200 | 201 | 202 | 203 | 204 |
205 | 206 | 207 | 208 |
209 |
; 210 | } 211 | }); 212 | 213 | ReactDOM.render(, document.getElementById('react')); 214 | 215 | 216 | -------------------------------------------------------------------------------- /src/index.jsx: -------------------------------------------------------------------------------- 1 | // See http://jszen.blogspot.com/2007/03/how-to-build-simple-calendar-with.html for calendar logic. 2 | 3 | import React from 'react'; 4 | import Input from 'react-bootstrap/lib/Input'; 5 | import Popover from 'react-bootstrap/lib/Popover'; 6 | import Button from 'react-bootstrap/lib/Button'; 7 | import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'; 8 | 9 | const CalendarHeader = React.createClass({ 10 | displayName: "DatePickerHeader", 11 | propTypes: { 12 | displayDate: React.PropTypes.object.isRequired, 13 | onChange: React.PropTypes.func.isRequired, 14 | monthLabels: React.PropTypes.array.isRequired, 15 | previousButtonElement: React.PropTypes.oneOfType([ 16 | React.PropTypes.string, 17 | React.PropTypes.object 18 | ]).isRequired, 19 | nextButtonElement: React.PropTypes.oneOfType([ 20 | React.PropTypes.string, 21 | React.PropTypes.object 22 | ]).isRequired 23 | }, 24 | handleClickPrevious(){ 25 | const newDisplayDate = new Date(this.props.displayDate); 26 | newDisplayDate.setMonth(newDisplayDate.getMonth() - 1); 27 | this.props.onChange(newDisplayDate); 28 | }, 29 | handleClickNext(){ 30 | const newDisplayDate = new Date(this.props.displayDate); 31 | newDisplayDate.setMonth(newDisplayDate.getMonth() + 1); 32 | this.props.onChange(newDisplayDate); 33 | }, 34 | render() { 35 | return
36 |
{this.props.previousButtonElement}
37 | {this.props.monthLabels[this.props.displayDate.getMonth()]} {this.props.displayDate.getFullYear()} 38 |
{this.props.nextButtonElement}
39 |
; 40 | } 41 | }); 42 | 43 | const daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; 44 | 45 | const Calendar = React.createClass({ 46 | displayName: "DatePickerCalendar", 47 | propTypes: { 48 | selectedDate: React.PropTypes.object, 49 | displayDate: React.PropTypes.object.isRequired, 50 | onChange: React.PropTypes.func.isRequired, 51 | dayLabels: React.PropTypes.array.isRequired, 52 | cellPadding: React.PropTypes.string.isRequired, 53 | onUnmount: React.PropTypes.func.isRequired 54 | }, 55 | componentWillUnmount(){ 56 | this.props.onUnmount(); 57 | }, 58 | handleClick(day) { 59 | const newSelectedDate = new Date(this.props.displayDate); 60 | newSelectedDate.setDate(day); 61 | this.props.onChange(newSelectedDate); 62 | }, 63 | render() { 64 | const currentDate = new Date(); 65 | const currentDay = currentDate.getDate(); 66 | const currentMonth = currentDate.getMonth(); 67 | const currentYear = currentDate.getFullYear(); 68 | const selectedDay = this.props.selectedDate ? this.props.selectedDate.getDate() : null; 69 | const selectedMonth = this.props.selectedDate ? this.props.selectedDate.getMonth() : null; 70 | const selectedYear = this.props.selectedDate ? this.props.selectedDate.getFullYear() : null; 71 | const year = this.props.displayDate.getFullYear(); 72 | const month = this.props.displayDate.getMonth(); 73 | const firstDay = new Date(year, month, 1); 74 | const startingDay = firstDay.getDay(); 75 | let monthLength = daysInMonth[month]; 76 | if (month == 1) { 77 | if((year % 4 == 0 && year % 100 != 0) || year % 400 == 0){ 78 | monthLength = 29; 79 | } 80 | } 81 | const weeks = []; 82 | let day = 1; 83 | for (let i = 0; i < 9; i++) { 84 | let week = []; 85 | for (let j = 0; j <= 6; j++) { 86 | if (day <= monthLength && (i > 0 || j >= startingDay)) { 87 | const selected = day === selectedDay && month == selectedMonth && year === selectedYear; 88 | const current = day === currentDay && month == currentMonth && year === currentYear; 89 | week.push({day}); 90 | day++; 91 | } else { 92 | week.push(); 93 | } 94 | } 95 | weeks.push({week}); 96 | if (day > monthLength) { 97 | break; 98 | } 99 | } 100 | return 101 | 102 | 103 | {this.props.dayLabels.map((label, index)=>{ 104 | return 107 | })} 108 | 109 | 110 | 111 | {weeks} 112 | 113 |
105 | {label} 106 |
; 114 | } 115 | }); 116 | 117 | export default React.createClass({ 118 | displayName: "DatePicker", 119 | propTypes: { 120 | value: React.PropTypes.string, 121 | cellPadding: React.PropTypes.string, 122 | placeholder: React.PropTypes.string, 123 | dayLabels: React.PropTypes.array, 124 | monthLabels: React.PropTypes.array, 125 | onChange: React.PropTypes.func, 126 | clearButtonElement: React.PropTypes.oneOfType([ 127 | React.PropTypes.string, 128 | React.PropTypes.object 129 | ]), 130 | previousButtonElement: React.PropTypes.oneOfType([ 131 | React.PropTypes.string, 132 | React.PropTypes.object 133 | ]), 134 | nextButtonElement: React.PropTypes.oneOfType([ 135 | React.PropTypes.string, 136 | React.PropTypes.object 137 | ]), 138 | calendarPlacement: React.PropTypes.string, 139 | dateFormat: React.PropTypes.oneOf(['MM/DD/YYYY', 'DD/MM/YYYY', 'YYYY/MM/DD']) 140 | }, 141 | getDefaultProps() { 142 | const language = (window.navigator.userLanguage || window.navigator.language || '').toLowerCase(); 143 | const dateFormat = !language || language === "en-us" ? 'MM/DD/YYYY' : 'DD/MM/YYYY'; 144 | return { 145 | cellPadding: "5px", 146 | dayLabels: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], 147 | monthLabels: ['January', 'February', 'March', 'April', 148 | 'May', 'June', 'July', 'August', 'September', 149 | 'October', 'November', 'December'], 150 | clearButtonElement: "×", 151 | previousButtonElement: "<", 152 | nextButtonElement: ">", 153 | calendarPlacement: "bottom", 154 | dateFormat: dateFormat 155 | } 156 | }, 157 | getInitialState() { 158 | var state = this.makeDateValues(this.props.value); 159 | state.focused = false; 160 | state.placeholder = this.props.placeholder || this.props.dateFormat; 161 | return state; 162 | }, 163 | makeDateValues(isoString) { 164 | let displayDate; 165 | const selectedDate = isoString ? new Date(isoString) : null; 166 | const inputValue = isoString ? this.makeInputValueString(selectedDate) : null; 167 | if(selectedDate) { 168 | displayDate = new Date(selectedDate); 169 | } else { 170 | displayDate = new Date(); 171 | displayDate.setHours(12); 172 | displayDate.setMinutes(0); 173 | displayDate.setSeconds(0); 174 | displayDate.setMilliseconds(0); 175 | } 176 | return { 177 | value: selectedDate ? selectedDate.toISOString() : null, 178 | displayDate: displayDate, 179 | selectedDate: selectedDate, 180 | inputValue: inputValue 181 | } 182 | }, 183 | clear() { 184 | this.setState(this.makeDateValues(null)); 185 | if(this.props.onChange) { 186 | this.props.onChange(null); 187 | } 188 | }, 189 | handleHide(e){ 190 | if(document.activeElement === this.refs.input.getInputDOMNode()) { 191 | return; 192 | } 193 | this.setState({ 194 | focused: false 195 | }); 196 | if(this.props.onBlur) { 197 | this.props.onBlur(e); 198 | } 199 | }, 200 | handleFocus(e){ 201 | if(this.refs.overlay.state.isOverlayShown === true) { 202 | return; 203 | } 204 | this.setState({ 205 | focused: true 206 | }); 207 | if(this.props.onFocus) { 208 | this.props.onFocus(e); 209 | } 210 | }, 211 | handleBlur(e){ 212 | if(this.refs.overlay.state.isOverlayShown === true) { 213 | return; 214 | } 215 | this.setState({ 216 | focused: false 217 | }); 218 | if(this.props.onBlur) { 219 | this.props.onBlur(e); 220 | } 221 | }, 222 | getValue(){ 223 | return this.state.selectedDate ? this.state.selectedDate.toISOString() : null; 224 | }, 225 | makeInputValueString(date) { 226 | const month = date.getMonth() + 1; 227 | const day = date.getDate(); 228 | if(this.props.dateFormat === "MM/DD/YYYY") { 229 | return (month > 9 ? month : "0" + month) + "/" + (day > 9 ? day : "0" + day) + "/" + date.getFullYear(); 230 | } else if(this.props.dateFormat === "DD/MM/YYYY") { 231 | return (day > 9 ? day : "0" + day) + "/" + (month > 9 ? month : "0" + month) + "/" + date.getFullYear(); 232 | } else { 233 | return date.getFullYear() + "/" + (month > 9 ? month : "0" + month) + "/" + (day > 9 ? day : "0" + day); 234 | } 235 | }, 236 | handleInputChange(e){ 237 | let inputValue = this.refs.input.getValue(); 238 | inputValue = inputValue.replace(/(-|\/\/)/g, '/'); 239 | let month, day, year; 240 | if(this.props.dateFormat === "MM/DD/YYYY") { 241 | month = inputValue.slice(0,2).replace(/[^0-9]/g, ''); 242 | day = inputValue.slice(3,5).replace(/[^0-9]/g, ''); 243 | year = inputValue.slice(6,10).replace(/[^0-9]/g, ''); 244 | } else if(this.props.dateFormat === "DD/MM/YYYY") { 245 | day = inputValue.slice(0,2).replace(/[^0-9]/g, ''); 246 | month = inputValue.slice(3,5).replace(/[^0-9]/g, ''); 247 | year = inputValue.slice(6,10).replace(/[^0-9]/g, ''); 248 | } else { 249 | year = inputValue.slice(0,4).replace(/[^0-9]/g, ''); 250 | month = inputValue.slice(5,7).replace(/[^0-9]/g, ''); 251 | day = inputValue.slice(8,10).replace(/[^0-9]/g, ''); 252 | } 253 | 254 | const monthInteger = parseInt(month, 10); 255 | const dayInteger = parseInt(day, 10); 256 | const yearInteger = parseInt(year, 10); 257 | if(!isNaN(monthInteger) && !isNaN(dayInteger) && !isNaN(yearInteger) && monthInteger <= 12 && dayInteger <= 31 && yearInteger > 999) { 258 | const selectedDate = new Date(); 259 | selectedDate.setHours(12); 260 | selectedDate.setMinutes(0); 261 | selectedDate.setSeconds(0); 262 | selectedDate.setMilliseconds(0); 263 | selectedDate.setYear(yearInteger); 264 | selectedDate.setMonth(monthInteger - 1); 265 | selectedDate.setDate(dayInteger); 266 | this.setState({ 267 | selectedDate: selectedDate, 268 | displayDate: selectedDate, 269 | value: selectedDate.toISOString() 270 | }); 271 | if(this.props.onChange) { 272 | this.props.onChange(selectedDate.toISOString()); 273 | } 274 | } 275 | if(this.props.dateFormat === "MM/DD/YYYY") { 276 | inputValue = month + inputValue.slice(2,3).replace(/[^\/]/g, '') + day + inputValue.slice(5,6).replace(/[^\/]/g, '') + year; 277 | } else if(this.props.dateFormat === "DD/MM/YYYY") { 278 | inputValue = day + inputValue.slice(2,3).replace(/[^\/]/g, '') + month + inputValue.slice(5,6).replace(/[^\/]/g, '') + year; 279 | } else { 280 | inputValue = year + inputValue.slice(4,5).replace(/[^\/]/g, '') + month + inputValue.slice(7,8).replace(/[^\/]/g, ''); 281 | } 282 | if(this.props.dateFormat === "YYYY/MM/DD") { 283 | if(this.state.inputValue && inputValue.length > this.state.inputValue.length) { 284 | if(inputValue.length == 4) { 285 | inputValue += "/"; 286 | } 287 | if(inputValue.length == 7) { 288 | inputValue += "/"; 289 | } 290 | inputValue = inputValue.slice(0, 10); 291 | } 292 | } else { 293 | if(this.state.inputValue && inputValue.length > this.state.inputValue.length) { 294 | if(inputValue.length == 2) { 295 | inputValue += "/"; 296 | } 297 | if(inputValue.length == 5) { 298 | inputValue += "/"; 299 | } 300 | inputValue = inputValue.slice(0, 10); 301 | } 302 | } 303 | this.setState({ 304 | inputValue: inputValue 305 | }); 306 | }, 307 | onChangeMonth(newDisplayDate) { 308 | this.setState({ 309 | displayDate: newDisplayDate 310 | }); 311 | }, 312 | onChangeDate(newSelectedDate) { 313 | this.setState({ 314 | inputValue: this.makeInputValueString(newSelectedDate), 315 | selectedDate: newSelectedDate, 316 | displayDate: newSelectedDate, 317 | value: newSelectedDate.toISOString() 318 | }); 319 | this.refs.overlay.handleDelayedHide(); 320 | if(this.props.onChange) { 321 | this.props.onChange(newSelectedDate.toISOString()); 322 | } 323 | }, 324 | componentWillReceiveProps(newProps) { 325 | const value = newProps.value; 326 | if(this.getValue() !== value) { 327 | this.setState(this.makeDateValues(value)); 328 | } 329 | }, 330 | render() { 331 | const calendarHeader = ; 338 | const popOver = 339 | 340 | ; 341 | const buttonStyle = this.props.bsStyle === "error" ? "danger" : this.props.bsStyle; 342 | const clearButton = ; 343 | return
344 | 345 | 358 | 359 | 360 |
; 361 | } 362 | }); 363 | -------------------------------------------------------------------------------- /test/core.test.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import DatePicker from "../src/index.jsx"; 4 | import assert from "assert"; 5 | import co from "co"; 6 | import ES6Promise from 'es6-promise'; 7 | import UUID from "node-uuid"; 8 | import TestUtils from 'react/lib/ReactTestUtils'; 9 | 10 | ES6Promise.polyfill(); 11 | 12 | const spanishDayLabels = ['Dom', 'Lu', 'Ma', 'Mx', 'Ju', 'Vi', 'Sab']; 13 | const spanishMonthLabels = ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre']; 14 | 15 | describe("Date Picker", function() { 16 | this.timeout(30000); 17 | let container; 18 | before(function(){ 19 | container = document.createElement("div"); 20 | container.id = "react"; 21 | document.getElementsByTagName('body')[0].appendChild(container); 22 | }); 23 | after(function(){ 24 | document.getElementsByTagName('body')[0].removeChild(container); 25 | }); 26 | it("should render an empty date picker.", co.wrap(function *(){ 27 | const id = UUID.v4(); 28 | const App = React.createClass({ 29 | render: function(){ 30 | return
31 | 32 |
; 33 | } 34 | }); 35 | yield new Promise(function(resolve, reject){ 36 | ReactDOM.render(, container, resolve); 37 | }); 38 | const hiddenInputElement = document.getElementById(id); 39 | assert.equal(hiddenInputElement.value, ""); 40 | ReactDOM.unmountComponentAtNode(container); 41 | })); 42 | it("should render a date picker with a value.", co.wrap(function *(){ 43 | const id = UUID.v4(); 44 | const value = new Date().toISOString(); 45 | const App = React.createClass({ 46 | render: function(){ 47 | return
48 | 49 |
; 50 | } 51 | }); 52 | yield new Promise(function(resolve, reject){ 53 | ReactDOM.render(, container, resolve); 54 | }); 55 | const hiddenInputElement = document.getElementById(id); 56 | assert.equal(hiddenInputElement.value, value); 57 | ReactDOM.unmountComponentAtNode(container); 58 | })); 59 | it("should open the calendar and select a date.", co.wrap(function *(){ 60 | const id = UUID.v4(); 61 | const App = React.createClass({ 62 | render: function(){ 63 | return
64 | 65 |
; 66 | } 67 | }); 68 | yield new Promise(function(resolve, reject){ 69 | ReactDOM.render(, container, resolve); 70 | }); 71 | const hiddenInputElement = document.getElementById(id); 72 | const inputElement = document.querySelector("input.form-control"); 73 | TestUtils.Simulate.click(inputElement); 74 | const dayElement = document.querySelector("table tbody tr:nth-child(2) td"); 75 | assert.equal(hiddenInputElement.value, ''); 76 | TestUtils.Simulate.click(dayElement); 77 | assert.notEqual(hiddenInputElement.value, ''); 78 | ReactDOM.unmountComponentAtNode(container); 79 | })); 80 | it("should open the calendar, select a date, and trigger a change event.", co.wrap(function *(){ 81 | const id = UUID.v4(); 82 | let value = null; 83 | const App = React.createClass({ 84 | handleChange: function(newValue){ 85 | value = newValue; 86 | }, 87 | render: function(){ 88 | return
89 | 90 |
; 91 | } 92 | }); 93 | yield new Promise(function(resolve, reject){ 94 | ReactDOM.render(, container, resolve); 95 | }); 96 | const inputElement = document.querySelector("input.form-control"); 97 | TestUtils.Simulate.click(inputElement); 98 | const dayElement = document.querySelector("table tbody tr:nth-child(2) td"); 99 | assert.equal(value, null); 100 | TestUtils.Simulate.click(dayElement); 101 | assert(typeof value === "string"); 102 | ReactDOM.unmountComponentAtNode(container); 103 | })); 104 | it("should open the calendar and render 29 days on a leap year.", co.wrap(function *(){ 105 | const id = UUID.v4(); 106 | let value = "2016-02-15T00:00:00.000Z"; 107 | const App = React.createClass({ 108 | render: function(){ 109 | return
110 | 111 |
; 112 | } 113 | }); 114 | yield new Promise(function(resolve, reject){ 115 | ReactDOM.render(, container, resolve); 116 | }); 117 | const inputElement = document.querySelector("input.form-control"); 118 | TestUtils.Simulate.click(inputElement); 119 | const dayElement = document.querySelector("table tbody tr:nth-child(5) td:nth-of-type(2)"); 120 | assert.equal(dayElement.innerHTML, '29'); 121 | TestUtils.Simulate.click(dayElement); 122 | ReactDOM.unmountComponentAtNode(container); 123 | })); 124 | it("should update via a change handler when the input is changed.", co.wrap(function *(){ 125 | const id = UUID.v4(); 126 | let value = null; 127 | const App = React.createClass({ 128 | handleChange: function(newValue){ 129 | value = newValue; 130 | }, 131 | render: function(){ 132 | return
133 | 134 |
; 135 | } 136 | }); 137 | yield new Promise(function(resolve, reject){ 138 | ReactDOM.render(, container, resolve); 139 | }); 140 | const inputElement = document.querySelector("input.form-control"); 141 | inputElement.value = "05/31/1980"; 142 | TestUtils.Simulate.change(inputElement); 143 | const date = new Date(value); 144 | console.log(window.navigator.userLanguage); 145 | assert.equal(date.getMonth(), 4); 146 | assert.equal(date.getDate(), 31); 147 | assert.equal(date.getFullYear(), 1980); 148 | ReactDOM.unmountComponentAtNode(container); 149 | })); 150 | it("should render with custom elements", co.wrap(function *(){ 151 | const id = UUID.v4(); 152 | const clearButtonElement =
; 153 | const previousButtonElement =
; 154 | const nextButtonElement =
; 155 | const App = React.createClass({ 156 | render: function(){ 157 | return
158 | 165 |
; 166 | } 167 | }); 168 | var spanishMonthLabel = spanishMonthLabels[new Date().getMonth()]; 169 | yield new Promise(function(resolve, reject){ 170 | ReactDOM.render(, container, resolve); 171 | }); 172 | const inputElement = document.querySelector("input.form-control"); 173 | TestUtils.Simulate.click(inputElement); 174 | const dayElement = document.querySelector("table tbody tr:nth-child(2) td"); 175 | TestUtils.Simulate.click(dayElement); 176 | const popover = document.querySelector(".popover"); 177 | assert.notEqual(document.getElementById("clear-button-element"), null); 178 | assert.notEqual(document.getElementById("previous-button-element"), null); 179 | assert.notEqual(document.getElementById("next-button-element"), null); 180 | assert.notEqual(popover.innerHTML.indexOf(spanishMonthLabel), -1); 181 | assert.notEqual("Mx", -1); 182 | ReactDOM.unmountComponentAtNode(container); 183 | })); 184 | it("should go to the previous and next month.", co.wrap(function *(){ 185 | const id = UUID.v4(); 186 | const App = React.createClass({ 187 | render: function(){ 188 | return
189 | 190 |
; 191 | } 192 | }); 193 | yield new Promise(function(resolve, reject){ 194 | ReactDOM.render(, container, resolve); 195 | }); 196 | const hiddenInputElement = document.getElementById(id); 197 | const inputElement = document.querySelector("input.form-control"); 198 | TestUtils.Simulate.click(inputElement); 199 | const previousButtonElement = document.querySelector(".pull-left"); 200 | const nextButtonElement = document.querySelector(".pull-right"); 201 | const dayElement = document.querySelector("table tbody tr:nth-child(2) td"); 202 | TestUtils.Simulate.click(previousButtonElement); 203 | TestUtils.Simulate.click(dayElement); 204 | var previousMonthISOString = hiddenInputElement.value; 205 | TestUtils.Simulate.click(inputElement); 206 | TestUtils.Simulate.click(nextButtonElement); 207 | TestUtils.Simulate.click(dayElement); 208 | var currentMonthISOString = hiddenInputElement.value; 209 | assert(previousMonthISOString < currentMonthISOString); 210 | ReactDOM.unmountComponentAtNode(container); 211 | })); 212 | it("should cycle through every month in the year.", co.wrap(function *(){ 213 | const id = UUID.v4(); 214 | const App = React.createClass({ 215 | render: function(){ 216 | return
217 | 218 |
; 219 | } 220 | }); 221 | yield new Promise(function(resolve, reject){ 222 | ReactDOM.render(, container, resolve); 223 | }); 224 | const inputElement = document.querySelector("input.form-control"); 225 | TestUtils.Simulate.click(inputElement); 226 | const nextButtonElement = document.querySelector(".pull-right"); 227 | for(let i = 0; i < 12; i++) { 228 | TestUtils.Simulate.click(nextButtonElement); 229 | } 230 | ReactDOM.unmountComponentAtNode(container); 231 | })); 232 | it("should updated a change handler when cleared.", co.wrap(function *(){ 233 | const id = UUID.v4(); 234 | let value = null; 235 | const App = React.createClass({ 236 | handleChange: function(newValue){ 237 | value = newValue; 238 | }, 239 | render: function(){ 240 | return
241 | 242 |
; 243 | } 244 | }); 245 | yield new Promise(function(resolve, reject){ 246 | ReactDOM.render(, container, resolve); 247 | }); 248 | const inputElement = document.querySelector("input.form-control"); 249 | TestUtils.Simulate.click(inputElement); 250 | const clearButtonElement = document.querySelector(".form-group button"); 251 | const dayElement = document.querySelector("table tbody tr:nth-child(2) td"); 252 | TestUtils.Simulate.click(dayElement); 253 | assert.notEqual(value, null); 254 | TestUtils.Simulate.click(clearButtonElement); 255 | assert.equal(value, null); 256 | ReactDOM.unmountComponentAtNode(container); 257 | })); 258 | it("should call focus and blur handlers.", co.wrap(function *(){ 259 | const id = UUID.v4(); 260 | var results = {}; 261 | const App = React.createClass({ 262 | getInitialState: function() { 263 | return { 264 | blurred: false, 265 | focused: false 266 | } 267 | }, 268 | focusHandler: function() { 269 | this.setState({ 270 | focused: true 271 | }); 272 | }, 273 | blurHandler: function() { 274 | this.setState({ 275 | blurred: true 276 | }); 277 | }, 278 | render: function(){ 279 | return
280 | {this.state.focused ?
: null} 281 | {this.state.blurred ?
: null} 282 | 283 |
; 284 | } 285 | }); 286 | yield new Promise(function(resolve, reject){ 287 | ReactDOM.render(, container, resolve); 288 | }); 289 | const inputElement = document.querySelector("input.form-control"); 290 | inputElement.focus(); 291 | yield new Promise(function(resolve, reject){ 292 | setTimeout(resolve, 1000); 293 | }); 294 | inputElement.blur(); 295 | yield new Promise(function(resolve, reject){ 296 | setTimeout(resolve, 2000); 297 | }); 298 | assert.notEqual(document.getElementById("focused"), null); 299 | assert.notEqual(document.getElementById("blurred"), null); 300 | ReactDOM.unmountComponentAtNode(container); 301 | })); 302 | it("should automatically insert slashes.", co.wrap(function *(){ 303 | const id = UUID.v4(); 304 | const App = React.createClass({ 305 | render: function(){ 306 | return
307 | 308 |
; 309 | } 310 | }); 311 | yield new Promise(function(resolve, reject){ 312 | ReactDOM.render(, container, resolve); 313 | }); 314 | const inputElement = document.querySelector("input.form-control"); 315 | inputElement.value = "0"; 316 | TestUtils.Simulate.change(inputElement); 317 | inputElement.value = "05"; 318 | TestUtils.Simulate.change(inputElement); 319 | assert.equal(inputElement.value, "05/"); 320 | inputElement.value = "05/31"; 321 | TestUtils.Simulate.change(inputElement) 322 | assert.equal(inputElement.value, "05/31/"); 323 | ReactDOM.unmountComponentAtNode(container); 324 | })); 325 | it("should automatically insert in YYYY/MM/DD format.", co.wrap(function *(){ 326 | const id = UUID.v4(); 327 | const App = React.createClass({ 328 | render: function(){ 329 | return
330 | 331 |
; 332 | } 333 | }); 334 | yield new Promise(function(resolve, reject){ 335 | ReactDOM.render(, container, resolve); 336 | }); 337 | const inputElement = document.querySelector("input.form-control"); 338 | inputElement.value = "0"; 339 | TestUtils.Simulate.change(inputElement); 340 | inputElement.value = "1980"; 341 | TestUtils.Simulate.change(inputElement); 342 | assert.equal(inputElement.value, "1980/"); 343 | inputElement.value = "1980/05"; 344 | TestUtils.Simulate.change(inputElement) 345 | assert.equal(inputElement.value, "1980/05/"); 346 | ReactDOM.unmountComponentAtNode(container); 347 | })); 348 | it("should render dates in different formats.", co.wrap(function *(){ 349 | const mm_dd_yyyy_id = "_" + UUID.v4(); 350 | const dd_mm_yyyy_id = "_" + UUID.v4(); 351 | const yyyy_mm_dd_id = "_" + UUID.v4(); 352 | const App = React.createClass({ 353 | getInitialState: function(){ 354 | return { 355 | value: null 356 | } 357 | }, 358 | handleChange(value){ 359 | this.setState({value:value}); 360 | }, 361 | render: function(){ 362 | return
363 | 364 | 365 | 366 |
; 367 | } 368 | }); 369 | yield new Promise(function(resolve, reject){ 370 | ReactDOM.render(, container, resolve); 371 | }); 372 | const mm_dd_yyyy_inputElement = document.querySelector("#" + mm_dd_yyyy_id + "_container input.form-control"); 373 | const dd_mm_yyyy_inputElement = document.querySelector("#" + dd_mm_yyyy_id + "_container input.form-control"); 374 | const yyyy_mm_dd_inputElement = document.querySelector("#" + yyyy_mm_dd_id + "_container input.form-control"); 375 | mm_dd_yyyy_inputElement.value = "05/31/1980"; 376 | TestUtils.Simulate.change(mm_dd_yyyy_inputElement); 377 | assert.equal(mm_dd_yyyy_inputElement.value, "05/31/1980"); 378 | assert.equal(dd_mm_yyyy_inputElement.value, "31/05/1980"); 379 | assert.equal(yyyy_mm_dd_inputElement.value, "1980/05/31"); 380 | dd_mm_yyyy_inputElement.value = "15/04/2015"; 381 | TestUtils.Simulate.change(dd_mm_yyyy_inputElement); 382 | assert.equal(mm_dd_yyyy_inputElement.value, "04/15/2015"); 383 | assert.equal(dd_mm_yyyy_inputElement.value, "15/04/2015"); 384 | assert.equal(yyyy_mm_dd_inputElement.value, "2015/04/15"); 385 | yyyy_mm_dd_inputElement.value = "1999/12/31"; 386 | TestUtils.Simulate.change(yyyy_mm_dd_inputElement); 387 | assert.equal(mm_dd_yyyy_inputElement.value, "12/31/1999"); 388 | assert.equal(dd_mm_yyyy_inputElement.value, "31/12/1999"); 389 | assert.equal(yyyy_mm_dd_inputElement.value, "1999/12/31"); 390 | ReactDOM.unmountComponentAtNode(container); 391 | })); 392 | }); 393 | 394 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; // See http://jszen.blogspot.com/2007/03/how-to-build-simple-calendar-with.html for calendar logic. 8 | 9 | var _react = require('react'); 10 | 11 | var _react2 = _interopRequireDefault(_react); 12 | 13 | var _Input = require('react-bootstrap/lib/Input'); 14 | 15 | var _Input2 = _interopRequireDefault(_Input); 16 | 17 | var _Popover = require('react-bootstrap/lib/Popover'); 18 | 19 | var _Popover2 = _interopRequireDefault(_Popover); 20 | 21 | var _Button = require('react-bootstrap/lib/Button'); 22 | 23 | var _Button2 = _interopRequireDefault(_Button); 24 | 25 | var _OverlayTrigger = require('react-bootstrap/lib/OverlayTrigger'); 26 | 27 | var _OverlayTrigger2 = _interopRequireDefault(_OverlayTrigger); 28 | 29 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 30 | 31 | var CalendarHeader = _react2.default.createClass({ 32 | displayName: "DatePickerHeader", 33 | propTypes: { 34 | displayDate: _react2.default.PropTypes.object.isRequired, 35 | onChange: _react2.default.PropTypes.func.isRequired, 36 | monthLabels: _react2.default.PropTypes.array.isRequired, 37 | previousButtonElement: _react2.default.PropTypes.oneOfType([_react2.default.PropTypes.string, _react2.default.PropTypes.object]).isRequired, 38 | nextButtonElement: _react2.default.PropTypes.oneOfType([_react2.default.PropTypes.string, _react2.default.PropTypes.object]).isRequired 39 | }, 40 | handleClickPrevious: function handleClickPrevious() { 41 | var newDisplayDate = new Date(this.props.displayDate); 42 | newDisplayDate.setMonth(newDisplayDate.getMonth() - 1); 43 | this.props.onChange(newDisplayDate); 44 | }, 45 | handleClickNext: function handleClickNext() { 46 | var newDisplayDate = new Date(this.props.displayDate); 47 | newDisplayDate.setMonth(newDisplayDate.getMonth() + 1); 48 | this.props.onChange(newDisplayDate); 49 | }, 50 | render: function render() { 51 | return _react2.default.createElement( 52 | 'div', 53 | { className: 'text-center' }, 54 | _react2.default.createElement( 55 | 'div', 56 | { className: 'text-muted pull-left', onClick: this.handleClickPrevious, style: { cursor: "pointer" } }, 57 | this.props.previousButtonElement 58 | ), 59 | _react2.default.createElement( 60 | 'span', 61 | null, 62 | this.props.monthLabels[this.props.displayDate.getMonth()], 63 | ' ', 64 | this.props.displayDate.getFullYear() 65 | ), 66 | _react2.default.createElement( 67 | 'div', 68 | { className: 'text-muted pull-right', onClick: this.handleClickNext, style: { cursor: "pointer" } }, 69 | this.props.nextButtonElement 70 | ) 71 | ); 72 | } 73 | }); 74 | 75 | var daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; 76 | 77 | var Calendar = _react2.default.createClass({ 78 | displayName: "DatePickerCalendar", 79 | propTypes: { 80 | selectedDate: _react2.default.PropTypes.object, 81 | displayDate: _react2.default.PropTypes.object.isRequired, 82 | onChange: _react2.default.PropTypes.func.isRequired, 83 | dayLabels: _react2.default.PropTypes.array.isRequired, 84 | cellPadding: _react2.default.PropTypes.string.isRequired, 85 | onUnmount: _react2.default.PropTypes.func.isRequired 86 | }, 87 | componentWillUnmount: function componentWillUnmount() { 88 | this.props.onUnmount(); 89 | }, 90 | handleClick: function handleClick(day) { 91 | var newSelectedDate = new Date(this.props.displayDate); 92 | newSelectedDate.setDate(day); 93 | this.props.onChange(newSelectedDate); 94 | }, 95 | render: function render() { 96 | var _this = this; 97 | 98 | var currentDate = new Date(); 99 | var currentDay = currentDate.getDate(); 100 | var currentMonth = currentDate.getMonth(); 101 | var currentYear = currentDate.getFullYear(); 102 | var selectedDay = this.props.selectedDate ? this.props.selectedDate.getDate() : null; 103 | var selectedMonth = this.props.selectedDate ? this.props.selectedDate.getMonth() : null; 104 | var selectedYear = this.props.selectedDate ? this.props.selectedDate.getFullYear() : null; 105 | var year = this.props.displayDate.getFullYear(); 106 | var month = this.props.displayDate.getMonth(); 107 | var firstDay = new Date(year, month, 1); 108 | var startingDay = firstDay.getDay(); 109 | var monthLength = daysInMonth[month]; 110 | if (month == 1) { 111 | if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) { 112 | monthLength = 29; 113 | } 114 | } 115 | var weeks = []; 116 | var day = 1; 117 | for (var i = 0; i < 9; i++) { 118 | var week = []; 119 | for (var j = 0; j <= 6; j++) { 120 | if (day <= monthLength && (i > 0 || j >= startingDay)) { 121 | var selected = day === selectedDay && month == selectedMonth && year === selectedYear; 122 | var current = day === currentDay && month == currentMonth && year === currentYear; 123 | week.push(_react2.default.createElement( 124 | 'td', 125 | { key: j, onClick: this.handleClick.bind(this, day), style: { cursor: "pointer", padding: this.props.cellPadding }, className: selected ? "bg-primary" : current ? "text-muted" : null }, 126 | day 127 | )); 128 | day++; 129 | } else { 130 | week.push(_react2.default.createElement('td', { key: j })); 131 | } 132 | } 133 | weeks.push(_react2.default.createElement( 134 | 'tr', 135 | { key: i }, 136 | week 137 | )); 138 | if (day > monthLength) { 139 | break; 140 | } 141 | } 142 | return _react2.default.createElement( 143 | 'table', 144 | { className: 'text-center' }, 145 | _react2.default.createElement( 146 | 'thead', 147 | null, 148 | _react2.default.createElement( 149 | 'tr', 150 | null, 151 | this.props.dayLabels.map(function (label, index) { 152 | return _react2.default.createElement( 153 | 'td', 154 | { key: index, className: 'text-muted', style: { padding: _this.props.cellPadding } }, 155 | _react2.default.createElement( 156 | 'small', 157 | null, 158 | label 159 | ) 160 | ); 161 | }) 162 | ) 163 | ), 164 | _react2.default.createElement( 165 | 'tbody', 166 | null, 167 | weeks 168 | ) 169 | ); 170 | } 171 | }); 172 | 173 | exports.default = _react2.default.createClass({ 174 | displayName: "DatePicker", 175 | propTypes: { 176 | value: _react2.default.PropTypes.string, 177 | cellPadding: _react2.default.PropTypes.string, 178 | placeholder: _react2.default.PropTypes.string, 179 | dayLabels: _react2.default.PropTypes.array, 180 | monthLabels: _react2.default.PropTypes.array, 181 | onChange: _react2.default.PropTypes.func, 182 | clearButtonElement: _react2.default.PropTypes.oneOfType([_react2.default.PropTypes.string, _react2.default.PropTypes.object]), 183 | previousButtonElement: _react2.default.PropTypes.oneOfType([_react2.default.PropTypes.string, _react2.default.PropTypes.object]), 184 | nextButtonElement: _react2.default.PropTypes.oneOfType([_react2.default.PropTypes.string, _react2.default.PropTypes.object]), 185 | calendarPlacement: _react2.default.PropTypes.string, 186 | dateFormat: _react2.default.PropTypes.oneOf(['MM/DD/YYYY', 'DD/MM/YYYY', 'YYYY/MM/DD']) 187 | }, 188 | getDefaultProps: function getDefaultProps() { 189 | var language = (typeof window !== 'undefined' && (window.navigator.userLanguage || window.navigator.language) || '').toLowerCase(); 190 | var dateFormat = !language || language === "en-us" ? 'MM/DD/YYYY' : 'DD/MM/YYYY'; 191 | return { 192 | cellPadding: "5px", 193 | dayLabels: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], 194 | monthLabels: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], 195 | clearButtonElement: "×", 196 | previousButtonElement: "<", 197 | nextButtonElement: ">", 198 | calendarPlacement: "bottom", 199 | dateFormat: dateFormat 200 | }; 201 | }, 202 | getInitialState: function getInitialState() { 203 | var state = this.makeDateValues(this.props.value); 204 | state.focused = false; 205 | state.placeholder = this.props.placeholder || this.props.dateFormat; 206 | return state; 207 | }, 208 | makeDateValues: function makeDateValues(isoString) { 209 | var displayDate = void 0; 210 | var selectedDate = isoString ? new Date(isoString) : null; 211 | var inputValue = isoString ? this.makeInputValueString(selectedDate) : null; 212 | if (selectedDate) { 213 | displayDate = new Date(selectedDate); 214 | } else { 215 | displayDate = new Date(); 216 | displayDate.setHours(12); 217 | displayDate.setMinutes(0); 218 | displayDate.setSeconds(0); 219 | displayDate.setMilliseconds(0); 220 | } 221 | return { 222 | value: selectedDate ? selectedDate.toISOString() : null, 223 | displayDate: displayDate, 224 | selectedDate: selectedDate, 225 | inputValue: inputValue 226 | }; 227 | }, 228 | clear: function clear() { 229 | this.setState(this.makeDateValues(null)); 230 | if (this.props.onChange) { 231 | this.props.onChange(null); 232 | } 233 | }, 234 | handleHide: function handleHide(e) { 235 | if (document.activeElement === this.refs.input.getInputDOMNode()) { 236 | return; 237 | } 238 | this.setState({ 239 | focused: false 240 | }); 241 | if (this.props.onBlur) { 242 | this.props.onBlur(e); 243 | } 244 | }, 245 | handleFocus: function handleFocus(e) { 246 | if (this.refs.overlay.state.isOverlayShown === true) { 247 | return; 248 | } 249 | this.setState({ 250 | focused: true 251 | }); 252 | if (this.props.onFocus) { 253 | this.props.onFocus(e); 254 | } 255 | }, 256 | handleBlur: function handleBlur(e) { 257 | if (this.refs.overlay.state.isOverlayShown === true) { 258 | return; 259 | } 260 | this.setState({ 261 | focused: false 262 | }); 263 | if (this.props.onBlur) { 264 | this.props.onBlur(e); 265 | } 266 | }, 267 | getValue: function getValue() { 268 | return this.state.selectedDate ? this.state.selectedDate.toISOString() : null; 269 | }, 270 | makeInputValueString: function makeInputValueString(date) { 271 | var month = date.getMonth() + 1; 272 | var day = date.getDate(); 273 | if (this.props.dateFormat === "MM/DD/YYYY") { 274 | return (month > 9 ? month : "0" + month) + "/" + (day > 9 ? day : "0" + day) + "/" + date.getFullYear(); 275 | } else if (this.props.dateFormat === "DD/MM/YYYY") { 276 | return (day > 9 ? day : "0" + day) + "/" + (month > 9 ? month : "0" + month) + "/" + date.getFullYear(); 277 | } else { 278 | return date.getFullYear() + "/" + (month > 9 ? month : "0" + month) + "/" + (day > 9 ? day : "0" + day); 279 | } 280 | }, 281 | handleInputChange: function handleInputChange(e) { 282 | var inputValue = this.refs.input.getValue(); 283 | inputValue = inputValue.replace(/(-|\/\/)/g, '/'); 284 | var month = void 0, 285 | day = void 0, 286 | year = void 0; 287 | if (this.props.dateFormat === "MM/DD/YYYY") { 288 | month = inputValue.slice(0, 2).replace(/[^0-9]/g, ''); 289 | day = inputValue.slice(3, 5).replace(/[^0-9]/g, ''); 290 | year = inputValue.slice(6, 10).replace(/[^0-9]/g, ''); 291 | } else if (this.props.dateFormat === "DD/MM/YYYY") { 292 | day = inputValue.slice(0, 2).replace(/[^0-9]/g, ''); 293 | month = inputValue.slice(3, 5).replace(/[^0-9]/g, ''); 294 | year = inputValue.slice(6, 10).replace(/[^0-9]/g, ''); 295 | } else { 296 | year = inputValue.slice(0, 4).replace(/[^0-9]/g, ''); 297 | month = inputValue.slice(5, 7).replace(/[^0-9]/g, ''); 298 | day = inputValue.slice(8, 10).replace(/[^0-9]/g, ''); 299 | } 300 | 301 | var monthInteger = parseInt(month, 10); 302 | var dayInteger = parseInt(day, 10); 303 | var yearInteger = parseInt(year, 10); 304 | if (!isNaN(monthInteger) && !isNaN(dayInteger) && !isNaN(yearInteger) && monthInteger <= 12 && dayInteger <= 31 && yearInteger > 999) { 305 | var selectedDate = new Date(); 306 | selectedDate.setHours(12); 307 | selectedDate.setMinutes(0); 308 | selectedDate.setSeconds(0); 309 | selectedDate.setMilliseconds(0); 310 | selectedDate.setYear(yearInteger); 311 | selectedDate.setMonth(monthInteger - 1); 312 | selectedDate.setDate(dayInteger); 313 | this.setState({ 314 | selectedDate: selectedDate, 315 | displayDate: selectedDate, 316 | value: selectedDate.toISOString() 317 | }); 318 | if (this.props.onChange) { 319 | this.props.onChange(selectedDate.toISOString()); 320 | } 321 | } 322 | if (this.props.dateFormat === "MM/DD/YYYY") { 323 | inputValue = month + inputValue.slice(2, 3).replace(/[^\/]/g, '') + day + inputValue.slice(5, 6).replace(/[^\/]/g, '') + year; 324 | } else if (this.props.dateFormat === "DD/MM/YYYY") { 325 | inputValue = day + inputValue.slice(2, 3).replace(/[^\/]/g, '') + month + inputValue.slice(5, 6).replace(/[^\/]/g, '') + year; 326 | } else { 327 | inputValue = year + inputValue.slice(4, 5).replace(/[^\/]/g, '') + month + inputValue.slice(7, 8).replace(/[^\/]/g, ''); 328 | } 329 | if (this.props.dateFormat === "YYYY/MM/DD") { 330 | if (this.state.inputValue && inputValue.length > this.state.inputValue.length) { 331 | if (inputValue.length == 4) { 332 | inputValue += "/"; 333 | } 334 | if (inputValue.length == 7) { 335 | inputValue += "/"; 336 | } 337 | inputValue = inputValue.slice(0, 10); 338 | } 339 | } else { 340 | if (this.state.inputValue && inputValue.length > this.state.inputValue.length) { 341 | if (inputValue.length == 2) { 342 | inputValue += "/"; 343 | } 344 | if (inputValue.length == 5) { 345 | inputValue += "/"; 346 | } 347 | inputValue = inputValue.slice(0, 10); 348 | } 349 | } 350 | this.setState({ 351 | inputValue: inputValue 352 | }); 353 | }, 354 | onChangeMonth: function onChangeMonth(newDisplayDate) { 355 | this.setState({ 356 | displayDate: newDisplayDate 357 | }); 358 | }, 359 | onChangeDate: function onChangeDate(newSelectedDate) { 360 | this.setState({ 361 | inputValue: this.makeInputValueString(newSelectedDate), 362 | selectedDate: newSelectedDate, 363 | displayDate: newSelectedDate, 364 | value: newSelectedDate.toISOString() 365 | }); 366 | this.refs.overlay.handleDelayedHide(); 367 | if (this.props.onChange) { 368 | this.props.onChange(newSelectedDate.toISOString()); 369 | } 370 | }, 371 | componentWillReceiveProps: function componentWillReceiveProps(newProps) { 372 | var value = newProps.value; 373 | if (this.getValue() !== value) { 374 | this.setState(this.makeDateValues(value)); 375 | } 376 | }, 377 | render: function render() { 378 | var calendarHeader = _react2.default.createElement(CalendarHeader, { 379 | previousButtonElement: this.props.previousButtonElement, 380 | nextButtonElement: this.props.nextButtonElement, 381 | displayDate: this.state.displayDate, 382 | onChange: this.onChangeMonth, 383 | monthLabels: this.props.monthLabels, 384 | dateFormat: this.props.dateFormat }); 385 | var popOver = _react2.default.createElement( 386 | _Popover2.default, 387 | { id: 'calendar', title: calendarHeader }, 388 | _react2.default.createElement(Calendar, { cellPadding: this.props.cellPadding, selectedDate: this.state.selectedDate, displayDate: this.state.displayDate, onChange: this.onChangeDate, dayLabels: this.props.dayLabels, onUnmount: this.handleHide }) 389 | ); 390 | var buttonStyle = this.props.bsStyle === "error" ? "danger" : this.props.bsStyle; 391 | var clearButton = _react2.default.createElement( 392 | _Button2.default, 393 | { onClick: this.clear, bsStyle: buttonStyle || "default", disabled: !this.state.inputValue }, 394 | this.props.clearButtonElement 395 | ); 396 | return _react2.default.createElement( 397 | 'div', 398 | { id: this.props.id ? this.props.id + "_container" : null }, 399 | _react2.default.createElement( 400 | _OverlayTrigger2.default, 401 | { ref: 'overlay', trigger: 'click', rootClose: true, placement: this.props.calendarPlacement, overlay: popOver, delayHide: 100 }, 402 | _react2.default.createElement(_Input2.default, _extends({}, this.props, { 403 | value: this.state.inputValue || '', 404 | ref: 'input', 405 | type: 'text', 406 | placeholder: this.state.focused ? this.props.dateFormat : this.state.placeholder, 407 | onFocus: this.handleFocus, 408 | onBlur: this.handleBlur, 409 | onChange: this.handleInputChange, 410 | buttonAfter: clearButton, 411 | name: null, 412 | id: null 413 | })) 414 | ), 415 | _react2.default.createElement('input', { type: 'hidden', id: this.props.id, name: this.props.name, value: this.state.value || '' }) 416 | ); 417 | } 418 | }); --------------------------------------------------------------------------------