30 | Features
31 | You get full control over all tags and styles
32 |
33 | Do you prefer {''}
s over {''}s?
34 | Do you prefer inline styles over class names? It's all up to you! You remain control over how your forms are rendered.
35 |
36 |
Define your custom behaviour for validation
37 |
38 | Do you want to indicate validation errors while the user is typing?
39 | Do you want to treat erros sent from the server differently?
40 | React-Reform allows you to consistently define such behaviours accross all your forms.
41 |
42 |
Low level
43 |
44 | Take a look at the recipes to see what kind of stuff React Reform allows you to do. Sure, being low level means that you've got quite some stuff to write to get production-ready themes. But you'll be rewarded with a very concise way of creating powerful and consistent forms.
45 |
46 |
Small
47 |
48 | React Reform comes with 0 dependencies.
49 | It also leaves out all optional things like default inputs and validation rules out of the core.
50 |
51 |
52 |
53 | )
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/doc-engine/src/pages/Recipes.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {Scaffold, H1, H2, H3, SubH1, Code, P, Section, AppliedCode} from 'comps/layouts'
3 |
4 | export default class Examples extends React.Component {
5 |
6 | render() {
7 |
8 | return (
9 |
10 | Recipes
11 | See some typical patterns in action
12 |
13 | Add a * to all required fields
14 | When rendering a field, see if there's a validation with name === 'required':
15 | {require('raw!comps/recipes/required-with-stars')}
16 |
17 |
18 |
19 | Custom button text
20 | Look at the directProps passed to the Form and filter out a buttonLabel prop:
21 | {require('raw!comps/recipes/custom-button-text')}
22 |
23 |
24 |
25 | Multiple submit buttons
26 | Approach 1: Create a theme
27 |
28 | If it's a recurring feature within your application it may be worth creating a explicit theme for this.
29 | renderForm offers a submitForm callback which allows you to pass additional arguments to the onSubmit handler:
30 |
31 | {require('raw!comps/recipes/multiple-submit-theme')}
32 |
33 | Approach 2: Create a custom handler in your form
34 |
35 | Here we're creating a theme that allows to have no button by default. So instead we're passing our own buttons to the form.
36 | The second button is our submit button and will execute the default onSubmit handler.
37 | The first button gets an onClick handler which calls handleSubmit on the form's ref.
38 | This function expects an event as an argument. Instead of this, we just pass a string signaling to the submit handler that this is a special case.
39 |
40 | Calling handleSubmit will perform all validation checks, so your onSubmit won't be called if there's still invalid data.
41 | {require('raw!comps/recipes/multiple-submit-inline')}
42 |
43 |
44 |
45 | Submit on blur
46 | submitForm is also available in the renderField function. This can be called when the input is blurred.
47 | There's a special case here. When validation fails, React Reform focusses the first invalid field by default. In our submit-on-blur case however, this would result in focussing the field immediately blurring if there's an validation error. This behaviour can be skipped via setting dontFocusAfterFail on the field.
48 | {require('raw!comps/recipes/submit-on-blur')}
49 |
50 |
51 |
52 | Dynamic fields
53 | When using a controlled form you can render your form using different fields or validation rules:
54 | {require('raw!comps/recipes/dynamic-fields')}
55 |
56 |
57 |
58 | )
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/doc-engine/src/pages/docs/Form.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {Scaffold, H1, H2, H3, P, Code, Section, Link, AppliedCode} from 'comps/layouts'
3 |
4 | export default class Form extends React.Component {
5 |
6 | render() {
7 | return (
8 |
9 | Form
10 |
11 | Let's have a look of what using your themes and writing some actual forms looks like.
12 |
13 |
14 | Controlled vs uncontrolled forms
15 | When writing your form you need to decide whether your form should be controlled or uncontrolled. I.e do you need to keep track of all the form changes via onFieldChange and inform the form via a changed model? Or is it sufficient to maybe provide an initialModel and only get to know the field values in the onSubmithandler?
16 | In most cases the less complex uncontrolled forms will be fine. But sometimes you need to be aware of the field values to change the render output accordingly. This is when controlled forms come in handy.
17 | Uncontrolled form example
18 | {require('raw!comps/recipes/form-uncontrolled')}
19 |
20 | Controlled form example
21 | {require('raw!comps/recipes/form-controlled')}
22 |
23 |
24 |
25 | Form props
26 | theme: (string|themeObj)
27 | Defaults to 'default'. You may either pass a string referencing a theme name that you defined via ReformContext or you may pass a theme object created via createTheme.
28 | onSubmit: fn(data, event) => (Promise?)
29 | The onSubmit handler will be called once the form was submitted and all fields' validations returned true.
30 | The data is an object with the fields' names as keys and their values as the value.
31 | You may return a promise to indicate to the theme that there's no result yet. The status within the renderFormFn will be 'pending'.
32 | If the promise gets resolved the form gets reset and a 'success' status will be set.
33 | You may also reject the promise. You have two options. If you know the field(s) that led to the error you may reject like this: {'reject({fieldName: \'is not unique\'})'}. If the error is more generic, reject with a string or react element: {'reject(the server responded with a {code} message)'}
34 | initialModel (optional) uncontrolled forms only.
35 | You may pass in initial data in the form of an object mapping field name to values. These values will be used when the form is mounted or reset.
36 | model controlled forms only.
37 | Passing a model prop signals to the form that it's an controlled form. It expects an object mapping field name to values. Field values won't be changed unless you explicitely change the model.
38 | onFieldChange: fn(fieldName, value) controlled forms only.
39 | This handler allows you to react to fieldChange request. Most typically it looks like the one form the controlled form example above.
40 | You may return false to indicate that no change has happened. This will prevent the input from becoming isDirty.
41 | children
42 | Pass your inputs here. It's perfectly fine to pass in non-inputs here as well. So a form like this will work:
43 | {`
44 |
56 | `}
57 |
58 |
59 | Form instance methods
60 | You may attach a ref prop to the form. This ref officially supports some methods you might want to take advantage of.
61 | reset()
62 | Sets the form inputs to an untouched state.
63 | {'checkForErrors() => ({hasErrors, firstInvalidFieldName})'}
64 | In case you'd like to validate the form without submitting it. Call this method. You can use the firstInvalidFieldName to focus the relevant input (see below).
65 | focusField(fieldName)
66 | Calls focuses the input by executing the focusFn(node) method according to it's definition on the WrapInput component.
67 |
68 |
69 | )
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/doc-engine/src/pages/docs/Optional.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {Scaffold, H1, H2, H3, P, Code, Section, Link} from 'comps/layouts'
3 |
4 | export default class Optional extends React.Component {
5 |
6 | render() {
7 | return (
8 |
9 | Optional validations and inputs
10 |
11 | React Reform comes with some validations and wrapped inputs out of the box. They are not included in the main bundle. I.e. if you don't explicitely require them they won't be added to your build.
12 | Also there's no magic in here. Have a look at the sources to learn what's going on here.
13 |
14 |
15 | Inputs
16 | You can require them via react-reform/opt/inputs. Take a look below at all the inputs in use:
17 | {`
18 | import {Form, ReformContext} from 'react-reform'
19 | import {Text, Textarea, Password, Select, Checkbox} from 'react-reform/opt/inputs'
20 |
21 | ...
22 |
23 |
33 | `}
34 | Note: there's no default Radio input. This is because there's no unopionated way of handling those in react. It's recommended wrapping a package like react-radio-group.
35 |
36 |
37 | Validations
38 | React Reform also offers some typical validations. Here's a list of them
39 | required
40 | {`
41 |
42 | `}
43 | email
44 | {`
45 |
46 | `}
47 | minlength
48 | {`
49 |
50 | `}
51 | maxlength
52 | {`
53 |
54 | `}
55 | pattern
56 | {`
57 |
58 | `}
59 |
60 |
61 | )
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/doc-engine/src/pages/docs/ReformContext.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {Scaffold, H1, H2, P, Code, Section, Link} from 'comps/layouts'
3 |
4 | export default class ReformContext extends React.Component {
5 |
6 | render() {
7 | return (
8 |
9 | ReformContext
10 |
11 | Your forms need to know which themes and validations are available within your app. ReformContext uses React's context feature to propage this information.
12 | For this to work, you need to put the ReformContext component at the root of your app.
13 | Have a look here for how to use it:
14 | {`
15 | import React, {Component} from 'react'
16 | import {render} from 'react-dom'
17 | import {ReformContext, createTheme} from 'react-reform'
18 | import defaultValidations from 'react-reform/opt/validations'
19 |
20 | const defaultTheme = createTheme(...)
21 | const darkTheme = createTheme(...)
22 | const singleInputTheme = createTheme(...)
23 |
24 | const themes = {
25 | default: defaultTheme,
26 | dark: darkTheme,
27 | singleInput: singleInputTheme
28 | };
29 |
30 | const validations = {
31 | ...defaultValidations,
32 | validTag: {...}
33 | }
34 |
35 | class App extends Component {
36 |
37 | render() {
38 | return (
39 |
40 |
41 | ...Your App Code...
42 |
43 |
44 | )
45 | }
46 | }
47 |
48 | render(, document.getElementById('root'))
49 | `}
50 |
51 |
52 | themes prop
53 | The themes prop expect an object mapping keys to themes.
54 | The key names allow to set a form's theme like this:
55 | {`
56 |
59 | `}
60 | If a form sets no explicit theme, the default theme will be chosen.
61 |
62 |
63 | validations prop
64 | To setup validations, use the validations prop. Pass an object that maps validation names to an validation object.
65 | This validation names can then be used by inputs by prefixing is or has:
66 | {`
67 |
70 | `}
71 |
72 |
73 | )
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/doc-engine/src/pages/docs/Validations.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {Scaffold, H1, H2, H3, P, Code, Section, Link} from 'comps/layouts'
3 |
4 | export default class Validations extends React.Component {
5 |
6 | render() {
7 | return (
8 |
9 | Validations
10 |
11 | Validations consist of 4 parts. A name, an isValid function and an error and a hint message.
12 | You may omit defining a hint message in which case the error message will be passed as hintMessage to the renderField function.
13 |
14 |
15 | Define a validation
16 | Validations are passed to the ReformContext component. This is where you assign a name to a validation. Have a look at this example to know how it works:
17 | {`
18 | import React, {Component} from 'react'
19 | import {render} from 'react-dom'
20 | import {ReformContext, createTheme} from 'react-reform'
21 |
22 | const defaultTheme = createTheme(...)
23 |
24 | const validations = {
25 | validUserName: {
26 | isValid: val => /#\\W+(-\\W+)*/.test(val),
27 | errorMessage: () => 'is not a valid username',
28 | hintMessage: () => 'may contain letters, digits, underscores and dashes'
29 | },
30 | maxLength: {
31 | isValid: (val, {arg}) => (val || '').toString().length <= arg,
32 | errorMessage: (val, {arg}) => {
33 | const currLength = (val || '').toString().length
34 | return \`maximal length: \${currLength}/\${arg}\`
35 | },
36 | hintMessage: (val, {arg}) => 'may contain letters at most \${arg} chars'
37 | }
38 | }
39 |
40 | class App extends Component {
41 |
42 | render() {
43 | return (
44 |
45 |
46 | ...Your App Code...
47 |
48 |
49 | )
50 | }
51 | }
52 |
53 | render(, document.getElementById('root'))
54 | `}
55 | The two validations validUserName and maxLength may be used within inputs when prefixing either is or has:
56 | {`
57 |
61 | `}
62 |
63 |
64 | isValid: (val, data, revalidateFn) => (true|false|string)
65 | This function determines whether a given input is valid or not. You may also return something else than true or false in case you have more states like e.g. "pending". But be aware that submitting a form will only suceed if all inputs' validations return true.
66 | val
67 | Contains the current value of the input.
68 | data.arg
69 | You may add options to a validation like {''} or even {''}. You can access the passed prop value via this field.
70 | data.getValue(fieldName)
71 | In case you want to access other field's values use this function. Imagine an input like {''}, a useful implementation would be:
72 | {`
73 | {
74 | isValid: (val, {arg, getValue}) => val === getValue(arg),
75 | ...
76 | }
77 | `}
78 | revalidateFn
79 | When dealing with asynchronous validatoins, you need to tell React Reform to revalidate the input once a result gets back to you. Use this function to do exactly this. See the section below for a full example.
80 |
81 |
82 | hintMessage/errorMessage: (val, data) => (string|React element)
83 | Use this to define two types of messages for your validation. The hintMessage is optional and the errorMessage's output will be used in the renderField function
84 | val
85 | Contains the current value of the input.
86 | data.arg
87 | Same as isValid's data.arg described above.
88 | data.name
89 | Contains the name of the field.
90 |
91 |
92 | Asynchronous validation
93 | It's also possible to define asynchronous validations. The API is rather similar. But rather than defining a validation description like above we're using a function returning a validation description:
94 | {`
95 | const validations = {
96 | unique: () => {
97 | const cachedResults = {}
98 | return {
99 | isValid: (val, data, askAgain) => {
100 | if (!val) return true
101 | if (cachedResults[val] === undefined) {
102 | cachedResults[val] = 'pending'
103 | setTimeout(() => {
104 | cachedResults[val] = Math.random() > 0.5
105 | askAgain()
106 | }, 1000)
107 | }
108 | return cachedResults[val]
109 | },
110 | errorMessage: (val, {arg}) => \`has to be unique. But '\${val}' isn't\`
111 | }
112 | }
113 | }
114 | `}
115 | This function gets invoked once the input is mounted. This means that the cachedResults variable is unique for each input using this validation.
116 | As you can see we're doing any real request but simulate this via setTimeout. We fill the cache with a value and return this value on any subsequent validation request. Once we receive the proper value, we're calling the third argument of the isValid function, indicating that we'd like to be asked again, whether the input is valid.
117 |
118 |
119 | )
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/doc-engine/src/pages/docs/WrapInput.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {Scaffold, H1, H2, H3, P, Code, Section, Link, List, AppliedCode} from 'comps/layouts'
3 |
4 | export default class WrapInput extends React.Component {
5 |
6 | render() {
7 | return (
8 |
9 | WrapInput
10 |
11 | To make your inputs ready for React Reform you need to wrap them first. The WrapInput component should provide all functionality to interface with all kinds of components.
12 | Let's see a simple example to get an idea of what it looks like
13 | {`
14 | import {WrapInput} from 'react-reform'
15 |
16 | const Checkbox = props => (
17 | {
18 | ({value, themeProps: {onChange, ...restThemeProps}}) => (
19 | onChange(e.target.checked)}
21 | {...restThemeProps}
22 | />
23 | )
24 | }
25 | )
26 |
27 | `}
28 | Okay, so what is going on here?
29 | Checkbox is a functional component that returns a WrapInput component with a child-as-function. Here you put your raw input component and tell it how to interface with React Reform's primitives.
30 | Most importantly you tell how the value is set on your component and how to extract the value from its onChange callback.
31 |
32 |
33 | WrapInput props
34 | Let's take a more detailed look at how to use the WrapInput component.
35 | directProps
36 | This should always be the same: whatever props the user puts on the wrapped component should be passed as directProps. The theme's renderField function will then decide which of these props will be propagated to the real component via themeProps.
37 | focusFn (optional)
38 | Defines how the component's reference can be used to focus the input when there's a validation error (and we don't skip this behaviour via setting dontFocusAfterFail). The default definition looks like this: {'function() {if (!this.shouldPreventFocusAfterFail || !this.shouldPreventFocusAfterFail()) node.focus()}'}.
39 | children: (value, themeProps) => (React Element)
40 | As seen in the example above, WrapInput' child needs to be a function. This function provides two arguments, value and themeProps to allow adjusting to the raw input component's needs.
41 |
42 | value describes the value of the form's model. This value might need to be translated such that the input component can understand it.
43 | themeProps contains all the props that are passed to the input compoent via the theme. In addition it'll contain onChange, onFocus and onBlur listeners that you might have to adapt in order to get the correct behaviour. It also contains a ref callback to get a handle on the rendered instance to allow focusing it when validation fails.
44 |
45 |
46 |
47 | Examples
48 | Let's take a look at various DatePickers to see how we can wrap them.
49 | react-datepicker
50 | {require('raw!comps/recipes/wrapinput-react-datepicker')}
51 |
52 | react-date-picker
53 | {require('raw!comps/recipes/wrapinput-react-date-picker')}
54 |
55 | Belle's date picker
56 | {require('raw!comps/recipes/wrapinput-react-belle-date-picker')}
57 |
58 | react-bootstrap-daterangepicker
59 | {require('raw!comps/recipes/wrapinput-react-bootstrap-daterangepicker')}
60 |
61 | Custom components
62 | The recommended way would be to create one component first and wrap it rather than wrapping 3 inputs. But to give you a idea of what a very incomplete approach could look like:
63 | {require('raw!comps/recipes/wrapinput-custom-datepicker')}
64 |
65 |
66 |
67 | )
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/doc-engine/src/reta.css:
--------------------------------------------------------------------------------
1 | @import '~reta/loader!babel!comps/style-rules';
--------------------------------------------------------------------------------
/doc-engine/src/routes.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {Router, Route, browserHistory, applyRouterMiddleware} from 'react-router'
3 | import App from './App'
4 | import P404 from 'pages/404'
5 | import {useScroll} from 'react-router-scroll'
6 |
7 | export default function(isServerRender, routeList) {
8 | const AppWithRoutes = (props) =>
9 | return (
10 |
(
12 | prevRouterProps && location.pathname !== prevRouterProps.location.pathname
13 | )))
14 | }>
15 |
16 | {routeList.map(({path, comp: Comp, isAsync}) => {
17 | const props = {path, key: path}
18 | if (isAsync) {
19 | props.getComponent = (location, cb) => Comp((RealComp) => cb(null, isServerRender ? (props) => : RealComp))
20 | } else {
21 | props.component = isServerRender ? (props) => : Comp
22 | }
23 | return
24 | })}
25 |
26 |
27 |
28 | )
29 | }
30 |
--------------------------------------------------------------------------------
/doc-engine/web_loaders/md-loader.js:
--------------------------------------------------------------------------------
1 | const frontMatter = require('front-matter')
2 | const commonmark = require('commonmark')
3 | const loaderUtils = require('loader-utils')
4 |
5 |
6 | const reader = new commonmark.Parser()
7 | const writer = new commonmark.HtmlRenderer({softbreak: '
'})
8 |
9 | writer.heading = function(node, entering) {
10 | var tagname = 'h' + node.level, attrs = this.attrs(node)
11 | attrs.push(['id', (node.firstChild.literal || '').replace(/[^a-zA-Z0-9]+/g, '-').toLowerCase()])
12 | if (entering) {
13 | this.cr()
14 | this.tag(tagname, attrs)
15 | } else {
16 | this.tag('/' + tagname)
17 | this.cr()
18 | }
19 | }
20 |
21 |
22 | function randomIdent() {
23 | return 'xxxMDLINKxxx' + Math.random() + Math.random() + 'xxx'
24 | }
25 |
26 | module.exports = function(source) {
27 | this.cacheable()
28 |
29 | const data = frontMatter(source)
30 |
31 | if (!data.attributes.layout) throw new Error(`No layout given for ${this.resourcePath}`)
32 |
33 | const links = {}
34 |
35 | const parsed = reader.parse(data.body)
36 | const walker = parsed.walker()
37 | let event
38 | while ((event = walker.next())) {
39 | let node = event.node
40 | if (event.entering && node.type === 'image') {
41 | if (loaderUtils.isUrlRequest(node.destination, root)) {
42 | const ident = randomIdent()
43 | links[ident] = node.destination
44 | node.destination = ident
45 | loaderUtils.isUrlRequest(node.destination)
46 | }
47 | }
48 | }
49 | const html = JSON.stringify(writer.render(parsed)).replace(/xxxMDLINKxxx[0-9\.]+xxx/g, function(match) {
50 | if(!links[match]) return match
51 | return '" + require(' + JSON.stringify(loaderUtils.urlToRequest(links[match])) + ') + "'
52 | })
53 |
54 | return `
55 | var Comp = require("comps/${data.attributes.layout}").default;
56 | var React = require("react");
57 | module.exports = function ${data.attributes.layout}Renderer(props) {return React.createElement(Comp, Object.assign({}, props, ${JSON.stringify(data.attributes)}), ${html});}
58 | `
59 | }
60 |
--------------------------------------------------------------------------------
/doc-engine/web_loaders/routes-loader.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | /*
4 | use like this: `import routeList from "routes!./pages/Home"`;
5 |
6 | This will treat `Home.js` as your homepage and all other js files as routes defined by the file names.
7 |
8 | `routeList` will return an array like this:
9 |
10 | ```
11 | [
12 | {path: "/", comp: function Home() {...}},
13 | {path: "/about/", comp: function About() {...}}
14 | ]
15 | ```
16 | */
17 |
18 | const glob = require('glob')
19 | const path = require('path')
20 | const loaderUtils = require('loader-utils')
21 | const kebabCase = require('lodash/kebabCase')
22 | const fs = require('fs')
23 | const frontMatter = require('front-matter')
24 | const commonmark = require('commonmark')
25 |
26 | const reader = new commonmark.Parser()
27 |
28 | const shrink = (text, maxChars) => {
29 | if (text.length <= maxChars) return text
30 | const lastPartLength = Math.round(maxChars / 4)
31 | let lastPart = text.slice(maxChars - lastPartLength, maxChars)
32 | const m = lastPart.match(/\s+\S*$/)
33 | if (m) lastPart = lastPart.slice(0, lastPart.length - m[0].length + 1)
34 | lastPart = `${lastPart.slice(0, -1)}…`
35 | return text.slice(0, maxChars - lastPartLength) + lastPart
36 | }
37 |
38 |
39 | const extractExcerpt = (md) => {
40 | const parsed = reader.parse(md)
41 | const walker = parsed.walker()
42 | let event
43 | let processed = 0
44 | let parts = []
45 | const add = (part) => {
46 | processed += part.length
47 | parts.push(part)
48 | }
49 | while (processed < 300 && (event = walker.next())) {
50 | let node = event.node
51 | if (event.entering) {
52 | if (node.type === 'text') {
53 | add(node.literal)
54 | } else if (node.type === 'softbreak' || node.type === 'hardbreak' || node.type === 'paragraph') {
55 | add(' ')
56 | }
57 | }
58 | }
59 | return shrink(parts.join('').trim(), 300)
60 | }
61 |
62 | const getRoutes = (homePath, cb) => {
63 | const homeFile = path.basename(homePath)
64 | const baseDir = path.dirname(homePath)
65 |
66 | const fileToUrl = (f) => {
67 | if (f === homeFile) return '/'
68 | const extLength = path.extname(f).length
69 | const parts = f.slice(0, -extLength).split(path.sep)
70 | if (parts.length > 1 && parts[parts.length - 1] === parts[parts.length - 2]) parts.pop()
71 | return '/' + parts.map(p => kebabCase(p)).join('/') + '/'
72 | }
73 |
74 | const transformFile = (f) => new Promise((res, rej) => {
75 | const extension = path.extname(f)
76 | const data = {
77 | location: f,
78 | path: fileToUrl(f),
79 | extension,
80 | isAsync: true
81 | }
82 | if (extension === '.md') {
83 | fs.readFile(path.join(baseDir, f), 'utf8', (err, content) => {
84 | if (err) return rej(err)
85 | const fm = frontMatter(content)
86 | data.info = fm.attributes
87 | data.info.excerpt = data.info.excerpt || extractExcerpt(fm.body)
88 | res(data)
89 | })
90 | } else {
91 | res(data)
92 | }
93 | })
94 |
95 | const options = {cwd: baseDir}
96 |
97 | if (cb) {
98 | glob('**/*.{js,md}', options, (err, files) => {
99 | if (err) return cb(err)
100 | Promise.all(files.map(f => transformFile(f))).then(r => cb(null, r), e => cb(e))
101 | })
102 | } else {
103 | return glob.sync('**/*.{js,md}', options).map(f => fileToUrl(f))
104 | }
105 | }
106 |
107 | const loader = function(source) {
108 | this.cacheable()
109 | const webpackCallback = this.async()
110 |
111 | var query = loaderUtils.parseQuery(this.query)
112 |
113 | let callbackCalled = false
114 | const callback = (err, result) => {
115 | if (callbackCalled) return
116 | webpackCallback(err, result)
117 | callbackCalled = true
118 | }
119 |
120 | const result = []
121 | let done = 0
122 |
123 | getRoutes(this.resourcePath, (err, fileData) => {
124 | if (err) return callback(err)
125 | fileData.forEach(data => {
126 | this.resolve(this.context, `.${path.sep}${data.location}`, (err, resolved) => {
127 | if (err) return callback(err)
128 | const request = `${loaderUtils.stringifyRequest(this, `${data.extension === '.md' ? 'cdx-md!' : ''}${resolved}`)}`
129 | const req = `require(${request})${data.extension === '.md' ? '' : '.default'}`
130 | const source = data.isAsync && !query.onlySync ? (
131 | `function load${data.location.replace(/\W+/g, '')}Async(cb) {
132 | require.ensure([${request}], function (err) {
133 | cb(${req})
134 | }, ${JSON.stringify(data.location)})
135 | }`
136 | ) : req
137 | result.push({
138 | path: data.path,
139 | info: data.info || null,
140 | isAsync: (data.isAsync && !query.onlySync) || false,
141 | comp: source
142 | })
143 | done += 1
144 |
145 | if (done === fileData.length) {
146 | const stringified = result.map((o) => (
147 | `{path: ${JSON.stringify(o.path)}, info: ${JSON.stringify(o.info)}, isAsync: ${JSON.stringify(o.isAsync)}, comp: ${o.comp}}`
148 | )).join(',\n')
149 | callback(null, `module.exports = [${stringified}]`)
150 | }
151 | })
152 | })
153 | })
154 | }
155 |
156 | loader.getRoutes = getRoutes
157 |
158 | module.exports = loader
159 |
--------------------------------------------------------------------------------
/doc-engine/webpack.config.base.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack')
2 | const StaticSiteGeneratorPlugin = require('static-site-generator-webpack-plugin')
3 | const path = require('path')
4 | const HtmlWebpackPlugin = require('html-webpack-plugin')
5 | const routeLoader = require('./web_loaders/routes-loader')
6 | const ExtractTextPlugin = require('extract-text-webpack-plugin')
7 | const purify = require('purify-css')
8 | const DirectoryNameAsMain = require('webpack-directory-name-as-main')
9 | const AssetsPlugin = require('assets-webpack-plugin')
10 |
11 | const REACT_REFORM_SRC = path.join(__dirname, '..', 'src')
12 |
13 | module.exports = function(env) {
14 | const isDev = env === 'dev'
15 | const isProdServer = env === 'prod-server' // render html
16 | const isProdClient = env === 'prod-client' // bundle js and css
17 | const isProd = isProdClient || isProdServer
18 |
19 | process.env.NODE_ENV = isProd ? 'production' : 'development'
20 |
21 | const envVars = Object.assign({
22 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
23 | })
24 |
25 | return {
26 | bail: isProd,
27 |
28 | contentBase: path.join(__dirname, 'src'),
29 |
30 | entry: Object.assign(isProdServer ? {
31 | 'generate-html': './src/generate-html.js'
32 | } : {
33 | 'main': [
34 | ...(isDev ? [require.resolve('react-dev-utils/webpackHotDevClient')] : []),
35 | './src/client.js'
36 | ]
37 | }),
38 |
39 | output: Object.assign({
40 | filename: `[name]-[${isDev ? 'hash' : 'chunkhash'}].js`,
41 | chunkFilename: '[id]-[chunkhash].chunk.js',
42 | path: isProd ? '../docs' : '/',
43 | publicPath: '/',
44 | libraryTarget: 'umd', // required by StaticSiteGeneratorPlugin,
45 | hashDigestLength: 5
46 | }, isDev ? {
47 | pathinfo: true
48 | } : {}),
49 |
50 | devtool: isProdServer ? false : isProdClient ? 'source-map' : 'eval',
51 |
52 | module: {
53 | loaders: [
54 | ...(isProdServer ? [
55 | {
56 | test: /\.html$/,
57 | loader: 'html',
58 | query: {
59 | attrs: ['img:src', 'link:href'],
60 | conservativeCollapse: false
61 | }
62 | }
63 | ] : []),
64 | {
65 | test: /\.js?$/,
66 | loader: path.join(__dirname, 'node_modules', 'babel-loader'),
67 | exclude: /node_modules|comps\/recipes/,
68 | query: {
69 | cacheDirectory: true
70 | }
71 | },
72 | {
73 | test: /\.css$/,
74 | loader: isProd ? ExtractTextPlugin.extract('style', 'css?sourceMap') : 'style!css'
75 | },
76 | {
77 | test: /\.svg\?inline$/,
78 | loader: 'svg-inline'
79 | },
80 | {
81 | test: /\.(ico|jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2)$/,
82 | exclude: /\/favicon.ico$/,
83 | loader: 'file',
84 | query: {
85 | name: 'static/media/[name].[hash:5].[ext]'
86 | }
87 | },
88 | {
89 | test: /\/favicon.ico$/,
90 | loader: 'file',
91 | query: {
92 | name: 'favicon.ico?[hash:8]'
93 | }
94 | },
95 | ]
96 | },
97 |
98 | resolve: {
99 | modulesDirectories: ['./src', 'node_modules'],
100 | alias: {
101 | 'react-reform': REACT_REFORM_SRC,
102 | 'react': path.join(__dirname, 'node_modules', 'react')
103 | }
104 | },
105 |
106 | plugins: [
107 | new webpack.ResolverPlugin([
108 | new DirectoryNameAsMain()
109 | ]),
110 | new webpack.ContextReplacementPlugin(/moment[\\\/]lang$/, /^\.\/(en-gb)$/),
111 | new webpack.DefinePlugin(Object.assign(envVars, {
112 | __DEV__: isDev,
113 | __SERVERRENDER__: isProdServer,
114 | 'define.amd': 'false' // because of bootstrap-daterangepicker
115 | })),
116 | ...(isProd ? [
117 | new ExtractTextPlugin('[name]-[contenthash].css')
118 | ] : []),
119 | ...(isProdServer ? [
120 | new StaticSiteGeneratorPlugin(
121 | 'generate-html',
122 | routeLoader.getRoutes('src/pages/Home.js'),
123 | {
124 | purify: purify, clientAssets: require('../docs/webpack-assets.json'),
125 | pathLib: path, fs: require('fs'), pathToDocs: path.join(__dirname, '..', 'docs')
126 | },
127 | {}
128 | ),
129 | ] : []),
130 | ...(isProdClient ? [
131 | new webpack.optimize.OccurrenceOrderPlugin(),
132 | new webpack.optimize.DedupePlugin(),
133 | new AssetsPlugin({path: path.join(__dirname, '..', 'docs')}),
134 | new webpack.optimize.UglifyJsPlugin({
135 | compress: {screw_ie8: true, warnings: false},
136 | mangle: {screw_ie8: true},
137 | output: {comments: false, screw_ie8: true}
138 | }),
139 | ] : []),
140 | ...(isDev ? [
141 | new HtmlWebpackPlugin({inject: true, template: './src/index.html'})
142 | ] : [])
143 | ]
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/doc-engine/webpack.config.dev.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./webpack.config.base')('dev')
2 |
--------------------------------------------------------------------------------
/doc-engine/webpack.config.prod-client.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./webpack.config.base')('prod-client')
2 |
--------------------------------------------------------------------------------
/doc-engine/webpack.config.prod-server.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./webpack.config.base')('prod-server')
2 |
--------------------------------------------------------------------------------
/docs/10-1087e.chunk.js:
--------------------------------------------------------------------------------
1 | webpackJsonp([10],{139:function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function l(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var i=function(){function e(e,t){for(var n=0;n
(\n \n {children}
\n \n \n ),\n\n renderField: (Field, {directProps, name, isFocused, validations, id}) => {\n const errors = validations\n .filter(({isValid}) => isValid === false)\n .map(({errorMessage, name}) => {errorMessage}, )\n return (\n \n \n \n
\n )\n\n })\n ")),u.default.createElement(c.Section,null,u.default.createElement(c.H2,{mb3:!0},"Create custom validations"),u.default.createElement(c.P,null,"Let's add a ",u.default.createElement(c.Code.Inline,null,"validTag")," validation to the default validations like e.g. ",u.default.createElement(c.Code.Inline,null,"required")," or ",u.default.createElement(c.Code.Inline,null,"maxlength"),"."),u.default.createElement(c.Code,null,"\n import defaultValidations from 'react-reform/opt/validations'\n\n const validations = {\n ...defaultValidations,\n\n validTag: {\n isValid: val => /#\\W+/.test(val),\n errorMessage: val => `'${val}' is not a valid tag!`\n }\n }\n\n ")),u.default.createElement(c.Section,null,u.default.createElement(c.H2,{mb3:!0},"Make your app aware of your themes and validations"),u.default.createElement(c.P,null,"React Reform uses react's context to propagate what themes and validations you have enabled for your app."),u.default.createElement(c.P,null,"Therefore you need to wrap your root component with the ",u.default.createElement(c.Code.Inline,null,"")," Component like so."),u.default.createElement(c.Code,null,"\n import React, {Component} from 'react'\n import {render} from 'react-dom'\n import {ReformContext} from 'react-reform'\n\n const themes = {default: defaultTheme};\n\n class App extends Component {\n\n render() {\n return (\n \n \n ...Your App Code...\n
\n \n )\n }\n }\n\n render(, document.getElementById('root'))\n ")),u.default.createElement(c.Section,null,u.default.createElement(c.H2,{mb3:!0},"Let's write our first form"),u.default.createElement(c.P,null,"Now that the basics are set up, enjoy writing your forms with very little boilerplate!"),u.default.createElement(c.Code,null,"\n import React, {Component} from 'react'\n import {Form} from 'react-reform'\n import {Text, Textarea} from 'react-reform/opt/inputs'\n\n class TagForm extends Component {\n\n handleSubmit = ({tag, description}, e) => {\n ...\n }\n\n render() {\n return (\n \n
Enter your tag information here
\n \n \n )\n }\n }\n ")))}}]),t}(u.default.Component);t.default=m}});
2 | //# sourceMappingURL=10-1087e.chunk.js.map
--------------------------------------------------------------------------------
/docs/11-773bf.chunk.js:
--------------------------------------------------------------------------------
1 | webpackJsonp([11],{138:function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{default:e}}function l(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function a(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function r(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var i=function(){function e(e,t){for(var n=0;n\n Api Docs
\n A detailed look at all the moving parts of React Reform\n \n ReformContext
\n Add this component to the root of your app to inform all forms within which themes and validations are available.
\n \n \n Themes
\n Define what your form look like withcreateTheme. You can define how the form body and each field is rendered, as well as allowing you to overwrite validation messages.
\n \n \n Validations
\n Learn how to write custom sync or async validations.
\n \n \n WrapInput
\n The WrapInput component allows to wrap any input component to make the ready for React Reform.
\n \n \n Form
\n Let's apply all the techniques above to see how forms are written.
\n \n \n Optional validations and inputs
\n To get you off the ground, see which validations and inputs come with React Reform
\n \n \n )\n }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./src/pages/Docs.js"],"sourceRoot":""}
--------------------------------------------------------------------------------
/docs/404/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | React Reform
8 |
9 |
10 | 404
Page not found :(
React Reform is brought to you by
Codecks. Suggest edits for these pages on
GitHub
11 |
12 |
13 |
--------------------------------------------------------------------------------
/docs/5-e67b0.chunk.js:
--------------------------------------------------------------------------------
1 | webpackJsonp([5],{146:function(e,t,n){"use strict";function l(e){return e&&e.__esModule?e:{default:e}}function a(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function u(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e,t){for(var n=0;n /#\\W+(-\\W+)*/.test(val),\n errorMessage: () => 'is not a valid username',\n hintMessage: () => 'may contain letters, digits, underscores and dashes'\n },\n maxLength: {\n isValid: (val, {arg}) => (val || '').toString().length <= arg,\n errorMessage: (val, {arg}) => {\n const currLength = (val || '').toString().length\n return `maximal length: ${currLength}/${arg}`\n },\n hintMessage: (val, {arg}) => 'may contain letters at most ${arg} chars'\n }\n }\n\n class App extends Component {\n\n render() {\n return (\n \n \n ...Your App Code...\n
\n \n )\n }\n }\n\n render(, document.getElementById('root'))\n "),d.default.createElement(s.P,null,"The two validations ",d.default.createElement(s.Code.Inline,null,"validUserName")," and ",d.default.createElement(s.Code.Inline,null,"maxLength")," may be used within inputs when prefixing either ",d.default.createElement(s.Code.Inline,null,"is")," or ",d.default.createElement(s.Code.Inline,null,"has"),":"),d.default.createElement(s.Code,null,'\n \n ')),d.default.createElement(s.Section,null,d.default.createElement(s.H2,null,"isValid: ",d.default.createElement(s.Code.Inline,null,"(val, data, revalidateFn) => (true|false|string)")),d.default.createElement(s.P,null,"This function determines whether a given input is valid or not. You may also return something else than ",d.default.createElement(s.Code.Inline,null,"true")," or ",d.default.createElement(s.Code.Inline,null,"false")," in case you have more states like e.g. ",d.default.createElement(s.Code.Inline,null,'"pending"'),". But be aware that submitting a form will only suceed if all inputs' validations return ",d.default.createElement(s.Code.Inline,null,"true"),"."),d.default.createElement(s.H3,null,d.default.createElement(s.Code.Inline,null,"val")),d.default.createElement(s.P,null,"Contains the current value of the input."),d.default.createElement(s.H3,null,d.default.createElement(s.Code.Inline,null,"data.arg")),d.default.createElement(s.P,null,"You may add options to a validation like ",d.default.createElement(s.Code.Inline,null,'')," or even ",d.default.createElement(s.Code.Inline,null,""),". You can access the passed prop value via this field."),d.default.createElement(s.H3,null,d.default.createElement(s.Code.Inline,null,"data.getValue(fieldName)")),d.default.createElement(s.P,null,"In case you want to access other field's values use this function. Imagine an input like ",d.default.createElement(s.Code.Inline,null,''),", a useful implementation would be:"),d.default.createElement(s.Code,null,"\n {\n isValid: (val, {arg, getValue}) => val === getValue(arg),\n ...\n }\n "),d.default.createElement(s.H3,null,d.default.createElement(s.Code.Inline,null,"revalidateFn")),d.default.createElement(s.P,null,"When dealing with asynchronous validatoins, you need to tell React Reform to revalidate the input once a result gets back to you. Use this function to do exactly this. See the section below for a full example.")),d.default.createElement(s.Section,null,d.default.createElement(s.H2,null,"hintMessage/errorMessage: ",d.default.createElement(s.Code.Inline,null,"(val, data) => (string|React element)")),d.default.createElement(s.P,null,"Use this to define two types of messages for your validation. The ",d.default.createElement(s.Code.Inline,null,"hintMessage")," is optional and the ",d.default.createElement(s.Code.Inline,null,"errorMessage"),"'s output will be used in the ",d.default.createElement(s.Code.Inline,null,"renderField")," function"),d.default.createElement(s.H3,null,d.default.createElement(s.Code.Inline,null,"val")),d.default.createElement(s.P,null,"Contains the current value of the input."),d.default.createElement(s.H3,null,d.default.createElement(s.Code.Inline,null,"data.arg")),d.default.createElement(s.P,null,"Same as ",d.default.createElement(s.Code.Inline,null,"isValid"),"'s ",d.default.createElement(s.Code.Inline,null,"data.arg")," described above."),d.default.createElement(s.H3,null,d.default.createElement(s.Code.Inline,null,"data.name")),d.default.createElement(s.P,null,"Contains the name of the field.")),d.default.createElement(s.Section,null,d.default.createElement(s.H2,null,"Asynchronous validation"),d.default.createElement(s.P,null,"It's also possible to define asynchronous validations. The API is rather similar. But rather than defining a validation description like above we're using a ",d.default.createElement("i",null,"function")," returning a validation description:"),d.default.createElement(s.Code,null,"\n const validations = {\n unique: () => {\n const cachedResults = {}\n return {\n isValid: (val, data, askAgain) => {\n if (!val) return true\n if (cachedResults[val] === undefined) {\n cachedResults[val] = 'pending'\n setTimeout(() => {\n cachedResults[val] = Math.random() > 0.5\n askAgain()\n }, 1000)\n }\n return cachedResults[val]\n },\n errorMessage: (val, {arg}) => `has to be unique. But '${val}' isn't`\n }\n }\n }\n "),d.default.createElement(s.P,null,"This function gets invoked once the input is mounted. This means that the ",d.default.createElement(s.Code.Inline,null,"cachedResults")," variable is unique for each input using this validation."),d.default.createElement(s.P,null,"As you can see we're doing any real request but simulate this via ",d.default.createElement(s.Code.Inline,null,"setTimeout"),". We fill the cache with a value and return this value on any subsequent validation request. Once we receive the proper value, we're calling the third argument of the ",d.default.createElement(s.Code.Inline,null,"isValid")," function, indicating that we'd like to be asked again, whether the input is valid.")))}}]),t}(d.default.Component);t.default=c}});
2 | //# sourceMappingURL=5-e67b0.chunk.js.map
--------------------------------------------------------------------------------
/docs/7-7e1c9.chunk.js:
--------------------------------------------------------------------------------
1 | webpackJsonp([7],{144:function(e,t,n){"use strict";function l(e){return e&&e.__esModule?e:{default:e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function a(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function r(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var u=function(){function e(e,t){for(var n=0;n\n \n ...Your App Code...\n
\n \n )\n }\n }\n\n render(, document.getElementById('root'))\n ")),c.default.createElement(m.Section,null,c.default.createElement(m.H2,null,c.default.createElement(m.Code.Inline,null,"themes")," prop"),c.default.createElement(m.P,null,"The ",c.default.createElement(m.Code.Inline,null,"themes")," prop expect an object mapping keys to ",c.default.createElement(m.Link,{to:"/docs/themes/"},"themes"),"."),c.default.createElement(m.P,null,"The key names allow to set a form's theme like this:"),c.default.createElement(m.Code,null,'\n \n '),c.default.createElement(m.P,null,"If a form sets no explicit theme, the ",c.default.createElement(m.Code.Inline,null,"default")," theme will be chosen.")),c.default.createElement(m.Section,null,c.default.createElement(m.H2,null,c.default.createElement(m.Code.Inline,null,"validations")," prop"),c.default.createElement(m.P,null,"To setup ",c.default.createElement(m.Link,{to:"/docs/validations/"},"validations"),", use the ",c.default.createElement(m.Code.Inline,null,"validations")," prop. Pass an object that maps validation names to an validation object."),c.default.createElement(m.P,null,"This validation names can then be used by inputs by prefixing ",c.default.createElement(m.Code.Inline,null,"is")," or ",c.default.createElement(m.Code.Inline,null,"has"),":"),c.default.createElement(m.Code,null,'\n \n ')))}}]),t}(c.default.Component);t.default=d}});
2 | //# sourceMappingURL=7-7e1c9.chunk.js.map
--------------------------------------------------------------------------------
/docs/8-efa67.chunk.js:
--------------------------------------------------------------------------------
1 | webpackJsonp([8],{143:function(e,t,n){"use strict";function l(e){return e&&e.__esModule?e:{default:e}}function a(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function r(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var u=function(){function e(e,t){for(var n=0;n\n \n