Skip to content

Commit

Permalink
Fixes #150: Using native date widgets by default.
Browse files Browse the repository at this point in the history
  • Loading branch information
n1k0 committed Apr 22, 2016
1 parent 432ec20 commit 2361e30
Show file tree
Hide file tree
Showing 10 changed files with 279 additions and 132 deletions.
54 changes: 54 additions & 0 deletions playground/samples/date.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
module.exports = {
schema: {
title: "Date and time widgets",
type: "object",
properties: {
native: {
title: "Native",
description: "Note that a few browsers support these.",
type: "object",
properties: {
"datetime": {
type: "string",
format: "date-time"
},
"date": {
type: "string",
format: "date-time"
}
}
},
alternative: {
title: "Alternative",
description: "These work on every platform.",
type: "object",
properties: {
"alt-datetime": {
type: "string",
format: "date-time"
},
"alt-date": {
type: "string",
format: "date-time"
}
}
}
}
},
uiSchema: {
native: {
date: {
"ui:widget": "date"
}
},
alternative: {
"alt-datetime": {
"ui:widget": "alt-datetime"
},
"alt-date": {
"ui:widget": "alt-date"
}
}
},
formData: {}
};
2 changes: 2 additions & 0 deletions playground/samples/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import references from "./references";
import custom from "./custom";
import errors from "./errors";
import large from "./large";
import date from "./date";

export const samples = {
Simple: simple,
Expand All @@ -20,4 +21,5 @@ export const samples = {
Custom: custom,
Errors: errors,
Large: large,
"Date & time": date,
};
11 changes: 4 additions & 7 deletions playground/samples/simple.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,6 @@ module.exports = {
type: "string",
title: "Password",
minLength: 3
},
date: {
type: "string",
format: "date-time",
title: "Subscription date"
}
}
},
Expand All @@ -42,14 +37,16 @@ module.exports = {
password: {
"ui:widget": "password",
"ui:help": "Hint: Make it strong!"
},
date: {
"ui:widget": "alt-datetime"
}
},
formData: {
firstName: "Chuck",
lastName: "Norris",
age: 75,
bio: "Roundhouse kicking asses since 1940",
password: "noneed",
date: new Date().toJSON()
password: "noneed"
}
};
11 changes: 7 additions & 4 deletions playground/samples/widgets.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ module.exports = {
type: "string",
format: "uri"
},
datetime: {
"alt-datetime": {
type: "string",
format: "date-time"
},
date: {
"alt-date": {
type: "string",
format: "date-time"
}
Expand Down Expand Up @@ -78,8 +78,11 @@ module.exports = {
}
},
stringFormats: {
date: {
"ui:widget": "date"
"alt-date": {
"ui:widget": "alt-date"
},
"alt-datetime": {
"ui:widget": "alt-datetime"
}
},
secret: {
Expand Down
21 changes: 21 additions & 0 deletions src/components/widgets/AltDateTimeWidget.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React, { PropTypes } from "react";

import AltDateWidget from "./AltDateWidget";


function AltDateTimeWidget(props) {
return <AltDateWidget time {...props} />;
}

if (process.env.NODE_ENV !== "production") {
AltDateTimeWidget.propTypes = {
schema: PropTypes.object.isRequired,
id: PropTypes.string.isRequired,
placeholder: PropTypes.string,
value: React.PropTypes.string,
required: PropTypes.bool,
onChange: PropTypes.func,
};
}

export default AltDateTimeWidget;
125 changes: 125 additions & 0 deletions src/components/widgets/AltDateWidget.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import React, { Component, PropTypes } from "react";

import { shouldRender, parseDateString, toDateString, pad } from "../../utils";
import SelectWidget from "../widgets/SelectWidget";


function rangeOptions(type, start, stop) {
let options = [{value: -1, label: type}];
for (let i=start; i<= stop; i++) {
options.push({value: i, label: pad(i, 2)});
}
return options;
}

function valid(state) {
return Object.keys(state).every(key => state[key] !== -1);
}

function DateElement({type, range, value, select, rootId}) {
const id = rootId + "_" + type;
return (
<SelectWidget
schema={{type: "integer"}}
id={id}
className="form-control"
options={rangeOptions(type, range[0], range[1])}
value={value}
onChange={(value) => select(type, value)} />
);
}

class AltDateWidget extends Component {
static defaultProps = {
time: false
};

constructor(props) {
super(props);
this.state = parseDateString(props.value, props.time);
}

componentWillReceiveProps(nextProps) {
this.setState(parseDateString(nextProps.value, nextProps.time));
}

shouldComponentUpdate(nextProps, nextState) {
return shouldRender(this, nextProps, nextState);
}

onChange = (property, value) => {
this.setState({[property]: value}, () => {
// Only propagate to parent state if we have a complete date{time}
if (valid(this.state)) {
this.props.onChange(toDateString(this.state));
}
});
};

setNow = (event) => {
event.preventDefault();
const {time, onChange} = this.props;
const nowDateObj = parseDateString(new Date().toJSON(), time);
this.setState(nowDateObj, () => onChange(toDateString(this.state)));
};

clear = (event) => {
event.preventDefault();
const {time, onChange} = this.props;
this.setState(parseDateString("", time), () => onChange(undefined));
};

get dateElementProps() {
const {time} = this.props;
const {year, month, day, hour, minute, second} = this.state;
const data = [
{type: "year", range: [1900, 2020], value: year},
{type: "month", range: [1, 12], value: month},
{type: "day", range: [1, 31], value: day},
];
if (time) {
data.push(
{type: "hour", range: [0, 23], value: hour},
{type: "minute", range: [0, 59], value: minute},
{type: "second", range: [0, 59], value: second}
);
}
return data;
}

render() {
const {id} = this.props;
return (
<ul className="list-inline">{
this.dateElementProps.map((props, i) => (
<li key={i}>
<DateElement rootId={id} select={this.onChange} {...props} />
</li>
))
}
<li>
<a href="#" className="btn btn-info btn-now"
onClick={this.setNow}>Now</a>
</li>
<li>
<a href="#" className="btn btn-warning btn-clear"
onClick={this.clear}>Clear</a>
</li>
</ul>
);
}
}

if (process.env.NODE_ENV !== "production") {
AltDateWidget.propTypes = {
schema: PropTypes.object.isRequired,
id: PropTypes.string.isRequired,
placeholder: PropTypes.string,
value: React.PropTypes.string,
required: PropTypes.bool,
onChange: PropTypes.func,
time: PropTypes.bool,
};
}

export default AltDateWidget;
29 changes: 25 additions & 4 deletions src/components/widgets/DateTimeWidget.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,39 @@
import React, { PropTypes } from "react";

import DateWidget from "./DateWidget";

function fromJSONDate(jsonDate) {
return jsonDate ? jsonDate.slice(0, 19) : "";
}

function toJSONDate(dateString) {
if (dateString) {
return new Date(dateString).toJSON();
}
}

function DateTimeWidget(props) {
return <DateWidget time {...props} />;
function DateTimeWidget({
schema,
id,
value,
required,
onChange
}) {
return (
<input type="datetime-local"
id={id}
className="form-control"
value={typeof value === "undefined" ? "" : fromJSONDate(value)}
required={required}
onChange={(event) => onChange(toJSONDate(event.target.value))} />
);
}

if (process.env.NODE_ENV !== "production") {
DateTimeWidget.propTypes = {
schema: PropTypes.object.isRequired,
id: PropTypes.string.isRequired,
placeholder: PropTypes.string,
value: React.PropTypes.string,
value: PropTypes.string,
required: PropTypes.bool,
onChange: PropTypes.func,
};
Expand Down
Loading

0 comments on commit 2361e30

Please sign in to comment.