├── .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 | [](https://www.npmjs.com/package/react-bootstrap-date-picker)
6 | [](https://circleci.com/gh/pushtell/react-bootstrap-date-picker)
7 | [](https://coveralls.io/github/pushtell/react-bootstrap-date-picker?branch=master)
8 | [](https://david-dm.org/pushtell/react-bootstrap-date-picker)
9 | [](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 | [](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 |
56 |
57 |
58 |
59 |
60 | Blur and Focus Events
61 |
62 |
63 |
64 |
65 |
68 |
69 |
70 |
71 |
72 | Styles
73 |
74 |
75 |
76 |
77 |
80 |
81 |
82 |
85 |
86 |
87 |
90 |
91 |
92 |
93 |
94 | Date Format
95 |
96 |
97 |
98 |
99 |
102 |
103 |
104 |
107 |
108 |
109 |
112 |
113 |
114 |
115 |
116 | Custom
117 |
118 |
119 |
120 |
121 |
124 |
125 |
126 |
129 |
130 |
131 |
134 |
135 |
136 |
139 |
140 |
141 |
142 |
143 | Placement
144 |
145 |
146 |
147 |
148 |
151 |
152 |
153 |
156 |
157 |
158 |
161 |
162 |
163 |
166 |
167 |
168 |
169 |
170 | Sizes
171 |
172 |
173 |
174 |
175 |
178 |
179 |
180 |
183 |
184 |
185 |
186 |
187 |
190 |
191 |
192 |
195 |
196 |
197 |
198 |
199 |
202 |
203 |
204 |
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 |
105 | {label}
106 | |
107 | })}
108 |
109 |
110 |
111 | {weeks}
112 |
113 |
;
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 | });
--------------------------------------------------------------------------------