Skip to content

Commit c668bfc

Browse files
committed
Fixes rjsf-team#150: Using native date widgets by default. (rjsf-team#158)
1 parent 432ec20 commit c668bfc

File tree

11 files changed

+468
-164
lines changed

11 files changed

+468
-164
lines changed

README.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ A [live playground](https://mozilla-services.github.io/react-jsonschema-form/) i
2323
- [Alternative widgets](#alternative-widgets)
2424
- [For boolean fields](#for-boolean-fields)
2525
- [For string fields](#for-string-fields)
26+
- [String formats](#string-formats)
2627
- [For number and integer fields](#for-number-and-integer-fields)
2728
- [Hidden widgets](#hidden-widgets)
2829
- [Object fields ordering](#object-fields-ordering)
@@ -215,13 +216,22 @@ Here's a list of supported alternative widgets for different JSONSchema data typ
215216
* `password`: an `input[type=password]` element is used;
216217
* by default, a regular `input[type=text]` element is used.
217218

218-
The built-in string field also supports the JSONSchema `format` property, and will render an appropriate widget by default for the following formats:
219+
##### String formats
220+
221+
The built-in string field also supports the JSONSchema `format` property, and will render an appropriate widget by default for the following string formats:
219222

220-
- `date-time`: Six `select` elements are used to select the year, the month, the day, the hour, the minute and the second;
221-
* If you don't want to deal with time, a `"ui:widget": "date"` uiSchema widget is alternatively available, exposing three selects for year, month and day only;
222223
- `email`: An `input[type=email]` element is used;
223224
- `uri`: An `input[type=url]` element is used;
224-
- More formats could be supported in a near future, feel free to help us going faster!
225+
- `date-time`: By default, an `input[type=datetime-local]` element is used; if you solely want to rely on a date, a `date` uiSchema alternative widget is available:
226+
227+
![](http://i.imgur.com/xqu6Lcp.png)
228+
229+
Please note that while standardized, `datetime-local` and `date` input elements are not yet supported by Firefox and IE. If you plan on targetting these platforms, two alternative widgets are available:
230+
231+
- `alt-datetime`: Six `select` elements are used to select the year, the month, the day, the hour, the minute and the second;
232+
- `alt-date`: Three `select` elements are used to select the year, month and the day.
233+
234+
![](http://i.imgur.com/VF5tY60.png)
225235

226236
#### For `number` and `integer` fields
227237

playground/samples/date.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
module.exports = {
2+
schema: {
3+
title: "Date and time widgets",
4+
type: "object",
5+
properties: {
6+
native: {
7+
title: "Native",
8+
description: "May not work on some browsers, notably Firefox Desktop and IE.",
9+
type: "object",
10+
properties: {
11+
"datetime": {
12+
type: "string",
13+
format: "date-time"
14+
},
15+
"date": {
16+
type: "string",
17+
format: "date-time"
18+
}
19+
}
20+
},
21+
alternative: {
22+
title: "Alternative",
23+
description: "These work on every platform.",
24+
type: "object",
25+
properties: {
26+
"alt-datetime": {
27+
type: "string",
28+
format: "date-time"
29+
},
30+
"alt-date": {
31+
type: "string",
32+
format: "date-time"
33+
}
34+
}
35+
}
36+
}
37+
},
38+
uiSchema: {
39+
native: {
40+
date: {
41+
"ui:widget": "date"
42+
}
43+
},
44+
alternative: {
45+
"alt-datetime": {
46+
"ui:widget": "alt-datetime"
47+
},
48+
"alt-date": {
49+
"ui:widget": "alt-date"
50+
}
51+
}
52+
},
53+
formData: {}
54+
};

playground/samples/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import references from "./references";
88
import custom from "./custom";
99
import errors from "./errors";
1010
import large from "./large";
11+
import date from "./date";
1112

1213
export const samples = {
1314
Simple: simple,
@@ -20,4 +21,5 @@ export const samples = {
2021
Custom: custom,
2122
Errors: errors,
2223
Large: large,
24+
"Date & time": date,
2325
};

playground/samples/simple.js

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,6 @@ module.exports = {
2424
type: "string",
2525
title: "Password",
2626
minLength: 3
27-
},
28-
date: {
29-
type: "string",
30-
format: "date-time",
31-
title: "Subscription date"
3227
}
3328
}
3429
},
@@ -42,14 +37,16 @@ module.exports = {
4237
password: {
4338
"ui:widget": "password",
4439
"ui:help": "Hint: Make it strong!"
40+
},
41+
date: {
42+
"ui:widget": "alt-datetime"
4543
}
4644
},
4745
formData: {
4846
firstName: "Chuck",
4947
lastName: "Norris",
5048
age: 75,
5149
bio: "Roundhouse kicking asses since 1940",
52-
password: "noneed",
53-
date: new Date().toJSON()
50+
password: "noneed"
5451
}
5552
};

playground/samples/widgets.js

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,6 @@ module.exports = {
1414
uri: {
1515
type: "string",
1616
format: "uri"
17-
},
18-
datetime: {
19-
type: "string",
20-
format: "date-time"
21-
},
22-
date: {
23-
type: "string",
24-
format: "date-time"
2517
}
2618
}
2719
},
@@ -77,11 +69,6 @@ module.exports = {
7769
"ui:widget": "textarea"
7870
}
7971
},
80-
stringFormats: {
81-
date: {
82-
"ui:widget": "date"
83-
}
84-
},
8572
secret: {
8673
"ui:widget": "hidden"
8774
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import React, { PropTypes } from "react";
2+
3+
import AltDateWidget from "./AltDateWidget";
4+
5+
6+
function AltDateTimeWidget(props) {
7+
return <AltDateWidget time {...props} />;
8+
}
9+
10+
if (process.env.NODE_ENV !== "production") {
11+
AltDateTimeWidget.propTypes = {
12+
schema: PropTypes.object.isRequired,
13+
id: PropTypes.string.isRequired,
14+
placeholder: PropTypes.string,
15+
value: React.PropTypes.string,
16+
required: PropTypes.bool,
17+
onChange: PropTypes.func,
18+
};
19+
}
20+
21+
export default AltDateTimeWidget;
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import React, { Component, PropTypes } from "react";
2+
3+
import { shouldRender, parseDateString, toDateString, pad } from "../../utils";
4+
import SelectWidget from "../widgets/SelectWidget";
5+
6+
7+
function rangeOptions(type, start, stop) {
8+
let options = [{value: -1, label: type}];
9+
for (let i=start; i<= stop; i++) {
10+
options.push({value: i, label: pad(i, 2)});
11+
}
12+
return options;
13+
}
14+
15+
function valid(state) {
16+
return Object.keys(state).every(key => state[key] !== -1);
17+
}
18+
19+
function DateElement({type, range, value, select, rootId}) {
20+
const id = rootId + "_" + type;
21+
return (
22+
<SelectWidget
23+
schema={{type: "integer"}}
24+
id={id}
25+
className="form-control"
26+
options={rangeOptions(type, range[0], range[1])}
27+
value={value}
28+
onChange={(value) => select(type, value)} />
29+
);
30+
}
31+
32+
class AltDateWidget extends Component {
33+
static defaultProps = {
34+
time: false
35+
};
36+
37+
constructor(props) {
38+
super(props);
39+
this.state = parseDateString(props.value, props.time);
40+
}
41+
42+
componentWillReceiveProps(nextProps) {
43+
this.setState(parseDateString(nextProps.value, nextProps.time));
44+
}
45+
46+
shouldComponentUpdate(nextProps, nextState) {
47+
return shouldRender(this, nextProps, nextState);
48+
}
49+
50+
onChange = (property, value) => {
51+
this.setState({[property]: value}, () => {
52+
// Only propagate to parent state if we have a complete date{time}
53+
if (valid(this.state)) {
54+
this.props.onChange(toDateString(this.state));
55+
}
56+
});
57+
};
58+
59+
setNow = (event) => {
60+
event.preventDefault();
61+
const {time, onChange} = this.props;
62+
const nowDateObj = parseDateString(new Date().toJSON(), time);
63+
this.setState(nowDateObj, () => onChange(toDateString(this.state)));
64+
};
65+
66+
clear = (event) => {
67+
event.preventDefault();
68+
const {time, onChange} = this.props;
69+
this.setState(parseDateString("", time), () => onChange(undefined));
70+
};
71+
72+
get dateElementProps() {
73+
const {time} = this.props;
74+
const {year, month, day, hour, minute, second} = this.state;
75+
const data = [
76+
{type: "year", range: [1900, 2020], value: year},
77+
{type: "month", range: [1, 12], value: month},
78+
{type: "day", range: [1, 31], value: day},
79+
];
80+
if (time) {
81+
data.push(
82+
{type: "hour", range: [0, 23], value: hour},
83+
{type: "minute", range: [0, 59], value: minute},
84+
{type: "second", range: [0, 59], value: second}
85+
);
86+
}
87+
return data;
88+
}
89+
90+
render() {
91+
const {id} = this.props;
92+
return (
93+
<ul className="list-inline">{
94+
this.dateElementProps.map((props, i) => (
95+
<li key={i}>
96+
<DateElement rootId={id} select={this.onChange} {...props} />
97+
</li>
98+
))
99+
}
100+
<li>
101+
<a href="#" className="btn btn-info btn-now"
102+
onClick={this.setNow}>Now</a>
103+
</li>
104+
<li>
105+
<a href="#" className="btn btn-warning btn-clear"
106+
onClick={this.clear}>Clear</a>
107+
</li>
108+
</ul>
109+
);
110+
}
111+
}
112+
113+
if (process.env.NODE_ENV !== "production") {
114+
AltDateWidget.propTypes = {
115+
schema: PropTypes.object.isRequired,
116+
id: PropTypes.string.isRequired,
117+
placeholder: PropTypes.string,
118+
value: React.PropTypes.string,
119+
required: PropTypes.bool,
120+
onChange: PropTypes.func,
121+
time: PropTypes.bool,
122+
};
123+
}
124+
125+
export default AltDateWidget;

src/components/widgets/DateTimeWidget.js

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,39 @@
11
import React, { PropTypes } from "react";
22

3-
import DateWidget from "./DateWidget";
43

4+
function fromJSONDate(jsonDate) {
5+
return jsonDate ? jsonDate.slice(0, 19) : "";
6+
}
7+
8+
function toJSONDate(dateString) {
9+
if (dateString) {
10+
return new Date(dateString).toJSON();
11+
}
12+
}
513

6-
function DateTimeWidget(props) {
7-
return <DateWidget time {...props} />;
14+
function DateTimeWidget({
15+
schema,
16+
id,
17+
value,
18+
required,
19+
onChange
20+
}) {
21+
return (
22+
<input type="datetime-local"
23+
id={id}
24+
className="form-control"
25+
value={fromJSONDate(value)}
26+
required={required}
27+
onChange={(event) => onChange(toJSONDate(event.target.value))} />
28+
);
829
}
930

1031
if (process.env.NODE_ENV !== "production") {
1132
DateTimeWidget.propTypes = {
1233
schema: PropTypes.object.isRequired,
1334
id: PropTypes.string.isRequired,
1435
placeholder: PropTypes.string,
15-
value: React.PropTypes.string,
36+
value: PropTypes.string,
1637
required: PropTypes.bool,
1738
onChange: PropTypes.func,
1839
};

0 commit comments

Comments
 (0)