├── jquery-stub.js ├── server ├── index.js ├── views │ ├── footer.ejs │ ├── 404.ejs │ ├── error_header.ejs │ ├── 500.ejs │ └── index.ejs ├── api │ ├── dummyFormData.json │ ├── formData.js │ ├── routes.js │ └── form.js └── server.js ├── .eslintignore ├── examples ├── custom │ ├── jquery-stub.js │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 │ ├── .babelrc │ ├── requests.js │ ├── index.html │ ├── webpack.config.js │ ├── package.json │ ├── app.js │ └── demobar.js ├── demo │ ├── jquery-stub.js │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 │ ├── .babelrc │ ├── app.js │ ├── index.html │ ├── webpack.config.js │ ├── package.json │ └── demobar.js ├── next │ ├── server │ │ ├── index.js │ │ ├── api │ │ │ ├── dummyFormData.json │ │ │ ├── formData.js │ │ │ ├── routes.js │ │ │ └── form.js │ │ └── server.js │ ├── .gitignore │ ├── readme.md │ ├── .nodemon.json │ ├── next.config.js │ ├── styles │ │ └── site.css │ ├── components │ │ ├── requests.js │ │ └── demobar.js │ ├── pages │ │ ├── index.js │ │ ├── _app.js │ │ └── form.js │ └── package.json ├── mongo │ ├── .gitignore │ ├── server │ │ ├── index.js │ │ ├── api │ │ │ ├── dummyFormData.json │ │ │ ├── routes.js │ │ │ ├── formData.js │ │ │ └── form.js │ │ └── server.js │ ├── readme.md │ ├── .nodemon.json │ ├── next.config.js │ ├── styles │ │ └── site.css │ ├── pages │ │ ├── index.js │ │ ├── _app.js │ │ └── form.js │ ├── components │ │ ├── requests.js │ │ └── demobar.js │ └── package.json └── umd │ ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ └── fontawesome-webfont.woff2 │ ├── form-builder.js │ ├── package.json │ ├── index.html │ └── form-generator.js ├── src ├── ItemTypes.js ├── stores │ ├── requests.js │ └── store.js ├── form-element.jsx ├── form-place-holder.jsx ├── header-bar.jsx ├── toolbar-draggable-item.jsx ├── sortable-form-elements.jsx ├── form-validator.jsx ├── UUID.js ├── index.jsx ├── sortable-element.jsx ├── preview.jsx ├── dynamic-option-list.jsx ├── star-rating.jsx ├── toolbar.jsx ├── form.jsx └── form-elements-edit.jsx ├── screenshot.png ├── screenshot2.png ├── screenshot3.png ├── scss ├── variables.scss ├── react-bootstrap-slider.scss ├── application.scss ├── react-star-rating.scss ├── form-builder-form.scss ├── react-select.scss └── form-builder.scss ├── fonts ├── FontAwesome.otf ├── fontawesome-webfont.eot ├── fontawesome-webfont.ttf ├── fontawesome-webfont.woff └── fontawesome-webfont.woff2 ├── public ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ └── fontawesome-webfont.woff2 └── index.html ├── .babelrc ├── .editorconfig ├── .gitignore ├── index.html ├── .eslintrc.json ├── LICENSE ├── webpack.production.config.js ├── webpack.config.babel.js ├── docs ├── form-builder.js ├── form-generator.js └── index.html ├── package.json ├── app.js ├── README.md └── demobar.js /jquery-stub.js: -------------------------------------------------------------------------------- 1 | module.exports = null; -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | require('./server.js'); 2 | -------------------------------------------------------------------------------- /server/views/footer.ejs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/variables.js 2 | **/webpack.*.js -------------------------------------------------------------------------------- /examples/custom/jquery-stub.js: -------------------------------------------------------------------------------- 1 | module.exports = null; -------------------------------------------------------------------------------- /examples/demo/jquery-stub.js: -------------------------------------------------------------------------------- 1 | module.exports = null; -------------------------------------------------------------------------------- /server/api/dummyFormData.json: -------------------------------------------------------------------------------- 1 | { "task_data": "[]" } -------------------------------------------------------------------------------- /src/ItemTypes.js: -------------------------------------------------------------------------------- 1 | export default { CARD: 'card' }; 2 | -------------------------------------------------------------------------------- /examples/next/server/index.js: -------------------------------------------------------------------------------- 1 | require('./server.js'); 2 | -------------------------------------------------------------------------------- /examples/mongo/.gitignore: -------------------------------------------------------------------------------- 1 | .next 2 | node_modules 3 | *.log 4 | -------------------------------------------------------------------------------- /examples/mongo/server/index.js: -------------------------------------------------------------------------------- 1 | require('./server.js'); 2 | -------------------------------------------------------------------------------- /examples/next/.gitignore: -------------------------------------------------------------------------------- 1 | .next 2 | node_modules 3 | *.log 4 | -------------------------------------------------------------------------------- /examples/mongo/server/api/dummyFormData.json: -------------------------------------------------------------------------------- 1 | { "task_data": "[]" } -------------------------------------------------------------------------------- /examples/next/server/api/dummyFormData.json: -------------------------------------------------------------------------------- 1 | { "task_data": "[]" } -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amithit/react-form-builder/HEAD/screenshot.png -------------------------------------------------------------------------------- /screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amithit/react-form-builder/HEAD/screenshot2.png -------------------------------------------------------------------------------- /screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amithit/react-form-builder/HEAD/screenshot3.png -------------------------------------------------------------------------------- /scss/variables.scss: -------------------------------------------------------------------------------- 1 | $screen-sm-min: 768px; 2 | $screen-md-min: 992px; 3 | $screen-lg-min: 1200px; -------------------------------------------------------------------------------- /fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amithit/react-form-builder/HEAD/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /server/views/404.ejs: -------------------------------------------------------------------------------- 1 | <% include error_header %> 2 |

Cannot find <%= url %>

3 | <% include footer %> -------------------------------------------------------------------------------- /examples/mongo/readme.md: -------------------------------------------------------------------------------- 1 | ## Develop 2 | ```bash 3 | $ npm install 4 | $ npm run build 5 | $ npm run dev 6 | ``` -------------------------------------------------------------------------------- /examples/next/readme.md: -------------------------------------------------------------------------------- 1 | ## Develop 2 | ```bash 3 | $ npm install 4 | $ npm run build 5 | $ npm run dev 6 | ``` -------------------------------------------------------------------------------- /public/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amithit/react-form-builder/HEAD/public/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amithit/react-form-builder/HEAD/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amithit/react-form-builder/HEAD/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amithit/react-form-builder/HEAD/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amithit/react-form-builder/HEAD/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /examples/demo/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amithit/react-form-builder/HEAD/examples/demo/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /examples/umd/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amithit/react-form-builder/HEAD/examples/umd/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /examples/custom/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amithit/react-form-builder/HEAD/examples/custom/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amithit/react-form-builder/HEAD/public/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amithit/react-form-builder/HEAD/public/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amithit/react-form-builder/HEAD/public/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amithit/react-form-builder/HEAD/public/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /server/api/formData.js: -------------------------------------------------------------------------------- 1 | const formData = require('./dummyFormData.json'); 2 | 3 | module.exports = { data: formData, answers: {} }; 4 | -------------------------------------------------------------------------------- /examples/demo/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amithit/react-form-builder/HEAD/examples/demo/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /examples/demo/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amithit/react-form-builder/HEAD/examples/demo/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /examples/umd/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amithit/react-form-builder/HEAD/examples/umd/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /examples/umd/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amithit/react-form-builder/HEAD/examples/umd/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /examples/umd/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amithit/react-form-builder/HEAD/examples/umd/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /scss/react-bootstrap-slider.scss: -------------------------------------------------------------------------------- 1 | $slider-horizontal-width: 100%; 2 | 3 | @import "../node_modules/bootstrap-slider/src/sass/bootstrap-slider"; 4 | -------------------------------------------------------------------------------- /examples/custom/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amithit/react-form-builder/HEAD/examples/custom/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /examples/custom/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amithit/react-form-builder/HEAD/examples/custom/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /examples/demo/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amithit/react-form-builder/HEAD/examples/demo/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /examples/demo/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amithit/react-form-builder/HEAD/examples/demo/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /examples/next/server/api/formData.js: -------------------------------------------------------------------------------- 1 | const formData = require('./dummyFormData.json'); 2 | 3 | module.exports = { data: formData, answers: {} }; 4 | -------------------------------------------------------------------------------- /examples/umd/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amithit/react-form-builder/HEAD/examples/umd/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /server/views/error_header.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Error 5 | 6 | 7 | 8 |

An error occurred!

-------------------------------------------------------------------------------- /examples/custom/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amithit/react-form-builder/HEAD/examples/custom/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /examples/custom/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amithit/react-form-builder/HEAD/examples/custom/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /examples/mongo/.nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "verbose": true, 3 | "ignore": ["node_modules", ".next"], 4 | "watch": ["server/**/*"], 5 | "ext": "js json" 6 | } -------------------------------------------------------------------------------- /examples/next/.nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "verbose": true, 3 | "ignore": ["node_modules", ".next"], 4 | "watch": ["server/**/*"], 5 | "ext": "js json" 6 | } -------------------------------------------------------------------------------- /examples/next/next.config.js: -------------------------------------------------------------------------------- 1 | const withSass = require('@zeit/next-sass') 2 | const withCSS = require('@zeit/next-css') 3 | 4 | module.exports = withCSS(withSass()); -------------------------------------------------------------------------------- /examples/mongo/next.config.js: -------------------------------------------------------------------------------- 1 | const withSass = require('@zeit/next-sass'); 2 | const withCSS = require('@zeit/next-css'); 3 | 4 | module.exports = withCSS(withSass()); 5 | -------------------------------------------------------------------------------- /server/views/500.ejs: -------------------------------------------------------------------------------- 1 | <% include error_header %> 2 |

Error: <%= error.message %>

3 | <% if (settings['verbose errors']) { %> 4 |
<%= error.stack %>
5 | <% } else { %> 6 |

An error occurred!

7 | <% } %> 8 | <% include footer %> -------------------------------------------------------------------------------- /examples/mongo/styles/site.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-size: 14px; 3 | font-weight: 300; 4 | color: #404d5b; 5 | width: 900px; 6 | margin: 0 auto; 7 | } 8 | .modal { background: rgba(0,0,0, 0.3);} 9 | .modal-content { padding: 30px; max-height: 800px; overflow-y: auto } -------------------------------------------------------------------------------- /examples/next/styles/site.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-size: 14px; 3 | font-weight: 300; 4 | color: #404d5b; 5 | width: 900px; 6 | margin: 0 auto; 7 | } 8 | .modal { background: rgba(0,0,0, 0.3);} 9 | .modal-content { padding: 30px; max-height: 500px; overflow-y: auto } -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react", 5 | ], 6 | "plugins": [ 7 | "@babel/plugin-proposal-class-properties", 8 | "@babel/plugin-proposal-json-strings", 9 | "@babel/plugin-syntax-dynamic-import", 10 | "@babel/plugin-syntax-import-meta" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /examples/umd/form-builder.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | // const e = React.createElement; 3 | const FormBuilder = ReactFormBuilder.default.ReactFormBuilder; 4 | const domContainer = document.querySelector('#form-builder'); 5 | 6 | ReactDOM.render(e(FormBuilder, { url: '/api/formdata', saveUrl: '/api/formdata' }), domContainer); 7 | -------------------------------------------------------------------------------- /examples/custom/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react", 5 | ], 6 | "plugins": [ 7 | "@babel/plugin-proposal-class-properties", 8 | "@babel/plugin-proposal-json-strings", 9 | "@babel/plugin-syntax-dynamic-import", 10 | "@babel/plugin-syntax-import-meta" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /examples/demo/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react", 5 | ], 6 | "plugins": [ 7 | "@babel/plugin-proposal-class-properties", 8 | "@babel/plugin-proposal-json-strings", 9 | "@babel/plugin-syntax-dynamic-import", 10 | "@babel/plugin-syntax-import-meta" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /scss/application.scss: -------------------------------------------------------------------------------- 1 | // Variables 2 | @import "variables"; 3 | 4 | // Component syles 5 | @import "react-star-rating"; 6 | @import "react-select"; 7 | @import "react-bootstrap-slider"; 8 | @import "react-date-picker"; 9 | 10 | @import "react-draft"; 11 | 12 | // Form builder styles 13 | @import "form-builder"; 14 | @import "form-builder-form"; -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | max_line_length = 80 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | max_line_length = 0 14 | trim_trailing_whitespace = false 15 | 16 | [COMMIT_EDITMSG] 17 | max_line_length = 0 18 | -------------------------------------------------------------------------------- /examples/custom/requests.js: -------------------------------------------------------------------------------- 1 | import fetch from 'isomorphic-fetch'; 2 | 3 | const headers = { 4 | Accept: 'application/json', 5 | 'Content-Type': 'application/json; charset=utf-8', 6 | OPTIONS: '', 7 | }; 8 | 9 | export function post(url, data) { 10 | return fetch(url, { 11 | method: 'POST', 12 | headers, 13 | body: JSON.stringify(data), 14 | }).then(response => response); 15 | } 16 | 17 | export function get(url) { 18 | return fetch(url, { 19 | method: 'GET', 20 | headers, 21 | }).then(response => response.json()); 22 | } 23 | -------------------------------------------------------------------------------- /src/stores/requests.js: -------------------------------------------------------------------------------- 1 | import fetch from 'isomorphic-fetch'; 2 | 3 | const headers = { 4 | Accept: 'application/json', 5 | 'Content-Type': 'application/json; charset=utf-8', 6 | OPTIONS: '', 7 | }; 8 | 9 | export function post(url, data) { 10 | return fetch(url, { 11 | method: 'POST', 12 | headers, 13 | body: JSON.stringify(data), 14 | }).then(response => response); 15 | } 16 | 17 | export function get(url) { 18 | return fetch(url, { 19 | method: 'GET', 20 | headers, 21 | }).then(response => response.json()); 22 | } 23 | -------------------------------------------------------------------------------- /examples/next/components/requests.js: -------------------------------------------------------------------------------- 1 | import fetch from 'isomorphic-fetch'; 2 | 3 | const headers = { 4 | Accept: 'application/json', 5 | 'Content-Type': 'application/json; charset=utf-8', 6 | OPTIONS: '', 7 | }; 8 | 9 | export function post(url, data) { 10 | return fetch(url, { 11 | method: 'POST', 12 | headers, 13 | body: JSON.stringify(data), 14 | }).then(response => response); 15 | } 16 | 17 | export function get(url) { 18 | return fetch(url, { 19 | method: 'GET', 20 | headers, 21 | }).then(response => response.json()); 22 | } 23 | -------------------------------------------------------------------------------- /src/form-element.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default class extends React.Component { 4 | static defaultProps = { 5 | className: 'rfb-item', 6 | }; 7 | 8 | state = { 9 | changedValue: this.props.data.value, 10 | data: this.props.data, 11 | }; 12 | 13 | render() { 14 | return ( 15 |
16 | 17 | {this.props.children} 18 |
19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/mongo/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import FormBuilder from 'react-form-builder2'; 3 | import DemoBar from '../components/demobar'; 4 | 5 | // Form Data 6 | const url = '/api/formdata'; 7 | const saveUrl = '/api/formdata'; 8 | const postUrl = '/api/form'; 9 | 10 | class Index extends React.Component { 11 | render() { 12 | return ( 13 |
14 | 15 | 16 | 19 |
20 | ); 21 | } 22 | } 23 | 24 | export default Index; 25 | -------------------------------------------------------------------------------- /examples/next/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import FormBuilder from 'react-form-builder2'; 3 | import DemoBar from '../components/demobar'; 4 | 5 | // Form Data 6 | const url = '/api/formdata'; 7 | const saveUrl = '/api/formdata'; 8 | const postUrl = '/api/form'; 9 | 10 | class Index extends React.Component { 11 | render() { 12 | return ( 13 |
14 | 15 | 16 | 19 |
20 | ); 21 | } 22 | } 23 | 24 | export default Index; 25 | -------------------------------------------------------------------------------- /examples/demo/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { ReactFormBuilder } from 'react-form-builder2'; 4 | import DemoBar from './demobar'; 5 | import * as variables from './variables'; 6 | 7 | import 'react-form-builder2/dist/app.css'; 8 | 9 | ReactDOM.render( 10 | , 14 | document.getElementById('form-builder'), 15 | ); 16 | 17 | ReactDOM.render( 18 | , 19 | document.getElementById('demo-bar'), 20 | ); 21 | -------------------------------------------------------------------------------- /examples/umd/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rfb2-umd", 3 | "version": "0.0.7", 4 | "description": "A complete form builder for react.", 5 | "license": "MIT", 6 | "keywords": [ 7 | "react", 8 | "react-component", 9 | "form", 10 | "builder", 11 | "ui", 12 | "drag", 13 | "drop" 14 | ], 15 | "author": "Kiho Chang", 16 | "devDependencies": { 17 | "react-form-builder2": "^0.1.2", 18 | "live-server": "^1.2.0" 19 | }, 20 | "scripts": { 21 | "start": "live-server --cors --port=8007 --proxy=/api:http://127.0.0.1:5005/api --watch=./bundle.js,./index.html ." 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/form-place-holder.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const PLACE_HOLDER = 'form-place-holder'; 5 | 6 | export default class PlaceHolder extends React.Component { 7 | render() { 8 | return ( 9 | this.props.show && 10 |
11 |
{this.props.text}
12 |
13 | ); 14 | } 15 | } 16 | 17 | PlaceHolder.propTypes = { 18 | text: PropTypes.string, 19 | show: PropTypes.bool, 20 | }; 21 | 22 | PlaceHolder.defaultProps = { 23 | text: 'Drop a item here....', 24 | show: false, 25 | }; 26 | -------------------------------------------------------------------------------- /examples/mongo/components/requests.js: -------------------------------------------------------------------------------- 1 | import fetch from 'isomorphic-fetch'; 2 | 3 | const headers = { 4 | Accept: 'application/json', 5 | 'Content-Type': 'application/json; charset=utf-8', 6 | OPTIONS: '', 7 | }; 8 | 9 | export function post(url, data) { 10 | return fetch(url, { 11 | method: 'POST', 12 | headers, 13 | body: JSON.stringify(data), 14 | }).then(response => response); 15 | } 16 | 17 | export function get(url) { 18 | return fetch(url, { 19 | method: 'GET', 20 | headers, 21 | }).then(response => { 22 | const r = response.json(); 23 | return r; 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /server/api/routes.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | /* eslint-disable no-var */ 3 | // eslint-disable-next-line import/no-extraneous-dependencies 4 | var express = require('express'); 5 | // var handleForm = require('./form'); 6 | var formData = require('./formData'); 7 | 8 | var app = express(); 9 | 10 | app.route('/formdata/') 11 | .get((req, res) => { 12 | // console.log('get formdata: ', formData.data); 13 | res.send(formData.data.task_data); 14 | }) 15 | .post((req, res) => { 16 | formData.data = req.body; 17 | // console.log('post formdata: ', formData.data); 18 | res.status(200).send(); 19 | }); 20 | 21 | module.exports = app; 22 | -------------------------------------------------------------------------------- /examples/next/server/api/routes.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | /* eslint-disable no-var */ 3 | // eslint-disable-next-line import/no-extraneous-dependencies 4 | var express = require('express'); 5 | // var handleForm = require('./form'); 6 | var formData = require('./formData'); 7 | 8 | var app = express(); 9 | 10 | app.route('/formdata/') 11 | .get((req, res) => { 12 | // console.log('get formdata: ', formData.data); 13 | res.send(formData.data.task_data); 14 | }) 15 | .post((req, res) => { 16 | formData.data = req.body; 17 | // console.log('post formdata: ', formData.data); 18 | res.status(200).send(); 19 | }); 20 | 21 | module.exports = app; 22 | -------------------------------------------------------------------------------- /examples/demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | React Form Builder Example 4 | 5 | 6 | 17 | 18 | 19 | 23 |
24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | .idea/ 6 | .DS_Store 7 | 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # node-waf configuration 27 | .lock-wscript 28 | 29 | # Compiled binary addons (http://nodejs.org/api/addons.html) 30 | build/Release 31 | 32 | # Dependency directories 33 | node_modules 34 | jspm_packages 35 | 36 | # Optional npm cache directory 37 | .npm 38 | 39 | # Optional REPL history 40 | .node_repl_history 41 | 42 | 43 | # bundles 44 | bundle.js 45 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | React Form Builder Example 4 | 5 | 6 | 17 | 18 | 19 | 23 |
24 |
25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/header-bar.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | 5 | import React from 'react'; 6 | 7 | export default class HeaderBar extends React.Component { 8 | render() { 9 | return ( 10 |
11 | {this.props.data.text} 12 |
13 | { this.props.data.element !== 'LineBreak' && 14 |
15 | } 16 |
17 |
18 |
19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | React Form Builder Example 4 | 5 | 6 | 7 | 18 | 19 | 20 | 24 |
25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/custom/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | React Form Builder Example 4 | 5 | 6 | 7 | 18 | 19 | 20 | 24 |
25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /src/toolbar-draggable-item.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | 5 | import React from 'react'; 6 | import { DragSource } from 'react-dnd'; 7 | import ItemTypes from './ItemTypes'; 8 | import ID from './UUID'; 9 | 10 | const cardSource = { 11 | beginDrag(props) { 12 | return { 13 | id: ID.uuid(), 14 | index: -1, 15 | data: props.data, 16 | onCreate: props.onCreate, 17 | }; 18 | }, 19 | }; 20 | 21 | class ToolbarItem extends React.Component { 22 | render() { 23 | const { connectDragSource, data, onClick } = this.props; 24 | if (!connectDragSource) return null; 25 | return ( 26 | connectDragSource( 27 |
  • {data.name}
  • , 28 | ) 29 | ); 30 | } 31 | } 32 | 33 | export default DragSource(ItemTypes.CARD, cardSource, connect => ({ 34 | connectDragSource: connect.dragSource(), 35 | }))(ToolbarItem); 36 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": ["airbnb-base"], 4 | "plugins": [ 5 | "react" 6 | ], 7 | "globals": { 8 | "window": true, 9 | "document": true, 10 | "navigator": true 11 | }, 12 | "rules": { 13 | "max-len": [1, 180, 2, {"ignoreComments": true}], 14 | "semi": [2, "always"], 15 | "comma-dangle": "warn", 16 | "camelcase": "warn", 17 | "prefer-destructuring": "warn", 18 | "no-param-reassign": "warn", 19 | "linebreak-style": 0, 20 | "operator-linebreak": "off", 21 | "no-underscore-dangle": "off", 22 | "class-methods-use-this": "off", 23 | "arrow-parens": "off", 24 | "no-plusplus": "off", 25 | "no-return-assign": "off", 26 | "no-prototype-builtins": "warn", 27 | "react/jsx-uses-vars": [2], 28 | "react/jsx-uses-react": 1 29 | }, 30 | "settings": { 31 | "import/resolver": { 32 | "node": { 33 | "extensions": [".js", ".jsx"] 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /examples/next/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-form-next", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "dev": "nodemon server/index.js", 9 | "build": "next build", 10 | "start": "node server/index.js" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "@zeit/next-css": "^1.0.1", 17 | "@zeit/next-sass": "^1.0.1", 18 | "react-form-builder2": "^0.1.29", 19 | "body-parser": "^1.18.3", 20 | "classnames": "^2.2.5", 21 | "jquery": "^3.3.1", 22 | "es6-promise": "^4.2.4", 23 | "express": "^4.16.4", 24 | "multer": "^1.4.1", 25 | "isomorphic-fetch": "^2.2.1", 26 | "next": "^8.0.3", 27 | "next-routes": "^1.4.2", 28 | "node-sass": "^4.11.0", 29 | "react": "^16.8.4", 30 | "react-dom": "^16.8.4" 31 | }, 32 | "devDependencies": { 33 | "nodemon": "^1.18.10" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/mongo/server/api/routes.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | /* eslint-disable no-var */ 3 | // eslint-disable-next-line import/no-extraneous-dependencies 4 | var express = require('express'); 5 | var formData = require('./formData'); 6 | 7 | var app = express(); 8 | 9 | function fixFormData(data) { 10 | return (!data || data === '[]' || data.length === 0) ? [] : data; 11 | } 12 | 13 | app.route('/formdata/') 14 | .get(async (req, res) => { 15 | let data = fixFormData(formData.data.task_data); 16 | // console.log('get formdata: ', data); 17 | if (!data.length) { 18 | data = await formData.load(req.db); 19 | formData.task_data = data; 20 | } 21 | res.send(data); 22 | }) 23 | .post(async (req, res) => { 24 | formData.data = req.body; 25 | const data = fixFormData(formData.data.task_data); 26 | await formData.save(req.db, data); 27 | res.status(200).send(); 28 | }); 29 | 30 | module.exports = app; 31 | -------------------------------------------------------------------------------- /examples/custom/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | module.exports = { 4 | entry: './app.js', 5 | 6 | output: { 7 | path: path.resolve('.'), 8 | filename: 'bundle.js', 9 | library: 'ReactFormBuilder', 10 | libraryTarget: 'umd' 11 | }, 12 | 13 | externals: { 14 | 'bootstrap': 'bootstrap' 15 | }, 16 | 17 | resolve: { 18 | extensions: ['.js', '.jsx', '.scss', '.css', '.json'], 19 | alias: { 20 | "jquery": path.join(__dirname, "./jquery-stub.js") 21 | } 22 | }, 23 | 24 | module: { 25 | rules: [ 26 | { 27 | exclude: /node_modules/, 28 | test: /\.js|.jsx?$/, 29 | use: [ 30 | { loader: 'babel-loader' } 31 | ] 32 | }, { 33 | test: /\.css$/, 34 | use: [ 35 | { 36 | loader: 'style-loader' 37 | }, 38 | { 39 | loader: 'css-loader' 40 | }, 41 | ] 42 | } 43 | ] 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /examples/demo/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | module.exports = { 4 | entry: './app.js', 5 | 6 | output: { 7 | path: path.resolve('.'), 8 | filename: 'bundle.js', 9 | library: 'ReactFormBuilder', 10 | libraryTarget: 'umd' 11 | }, 12 | 13 | externals: { 14 | 'bootstrap': 'bootstrap' 15 | }, 16 | 17 | resolve: { 18 | extensions: ['.js', '.jsx', '.scss', '.css', '.json'], 19 | alias: { 20 | "jquery": path.join(__dirname, "./jquery-stub.js") 21 | } 22 | }, 23 | 24 | module: { 25 | rules: [ 26 | { 27 | exclude: /node_modules/, 28 | test: /\.js|.jsx?$/, 29 | use: [ 30 | { loader: 'babel-loader' } 31 | ] 32 | }, { 33 | test: /\.css$/, 34 | use: [ 35 | { 36 | loader: 'style-loader' 37 | }, 38 | { 39 | loader: 'css-loader' 40 | }, 41 | ] 42 | } 43 | ] 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /scss/react-star-rating.scss: -------------------------------------------------------------------------------- 1 | .rating-container, .react-star-rating__root{ 2 | vertical-align:middle;display:inline-block 3 | } 4 | .rating-container .rating-stars:before,.rating-container:before{content:attr(data-content)}.react-star-rating__star{width:25px}.react-star-rating__star #star-flat{fill:#C6C6C6}.react-star-rating__star #star-flat:hover{fill:#FFA91B}.react-star-rating__root{font-size:2em}.react-star-rating__root.rating-editing:hover{cursor:pointer}.rating-container{position:relative;color:#e3e3e3;overflow:hidden}.rating-container .rating-stars{position:absolute;left:0;top:0;white-space:nowrap;overflow:hidden;color:#F5A71B;-webkit-transition:all .01s;-moz-transition:all .01s;transition:all .01s;-webkit-mask-image:-webkit-gradient(linear,left top,left bottom,from(#FDBD47),to(#F5A71B))}.react-rating-caption{font-size:1.25em;vertical-align:middle;margin-right:.5em}.rating-disabled .rating-container:hover{cursor:not-allowed}.react-star-rating__size--sm{font-size:1em}.react-star-rating__size--md{font-size:2em}.react-star-rating__size--lg{font-size:2.5em} -------------------------------------------------------------------------------- /examples/mongo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-form-next", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "dev": "nodemon server/index.js", 9 | "build": "next build", 10 | "start": "node server/index.js" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "@zeit/next-css": "^1.0.1", 17 | "@zeit/next-sass": "^1.0.1", 18 | "body-parser": "^1.18.3", 19 | "classnames": "^2.2.5", 20 | "co": "^4.6.0", 21 | "es6-promise": "^4.2.4", 22 | "express": "^4.16.4", 23 | "isomorphic-fetch": "^2.2.1", 24 | "jquery": "^3.3.1", 25 | "mongodb": "^3.2.3", 26 | "multer": "^1.4.1", 27 | "next": "^8.0.3", 28 | "next-routes": "^1.4.2", 29 | "node-sass": "^4.11.0", 30 | "react": "^16.8.4", 31 | "react-dom": "^16.8.4", 32 | "react-form-builder2": "^0.1.30" 33 | }, 34 | "devDependencies": { 35 | "nodemon": "^1.18.10" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/mongo/pages/_app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import App, { Container } from 'next/app'; 3 | import Head from 'next/head'; 4 | 5 | // stylings 6 | import 'react-form-builder2/dist/app.css'; 7 | import '../styles/site.css'; 8 | 9 | class MyApp extends App { 10 | static async getInitialProps({ Component, ctx }) { 11 | let pageProps = { }; 12 | 13 | if (Component.getInitialProps) { 14 | pageProps = await Component.getInitialProps(ctx); 15 | } 16 | 17 | return { pageProps }; 18 | } 19 | 20 | render() { 21 | const { Component, pageProps } = this.props; 22 | 23 | return ( 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | ); 32 | } 33 | } 34 | 35 | export default MyApp; 36 | -------------------------------------------------------------------------------- /examples/next/pages/_app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import App, { Container } from 'next/app'; 3 | import Head from 'next/head'; 4 | 5 | // stylings 6 | import 'react-form-builder2/dist/app.css'; 7 | import '../styles/site.css'; 8 | 9 | class MyApp extends App { 10 | static async getInitialProps({ Component, ctx }) { 11 | let pageProps = { }; 12 | 13 | if (Component.getInitialProps) { 14 | pageProps = await Component.getInitialProps(ctx); 15 | } 16 | 17 | return { pageProps }; 18 | } 19 | 20 | render() { 21 | const { Component, pageProps } = this.props; 22 | 23 | return ( 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | ); 32 | } 33 | } 34 | 35 | export default MyApp; 36 | -------------------------------------------------------------------------------- /examples/demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rfb2-example", 3 | "version": "0.1.0", 4 | "description": "A complete form builder for react.", 5 | "license": "MIT", 6 | "keywords": [ 7 | "react", 8 | "react-component", 9 | "form", 10 | "builder", 11 | "ui", 12 | "drag", 13 | "drop" 14 | ], 15 | "author": "Jason Kadrmas", 16 | "dependencies": { 17 | "react": "~16.4.1", 18 | "react-dom": "~16.4.1", 19 | "react-form-builder2": "^0.1.3" 20 | }, 21 | "devDependencies": { 22 | "@babel/cli": "^7.0.0", 23 | "@babel/core": "^7.0.0", 24 | "@babel/plugin-proposal-class-properties": "^7.0.0", 25 | "@babel/plugin-proposal-json-strings": "^7.0.0", 26 | "@babel/plugin-syntax-dynamic-import": "^7.0.0", 27 | "@babel/plugin-syntax-import-meta": "^7.0.0", 28 | "@babel/preset-env": "^7.0.0", 29 | "@babel/preset-react": "^7.0.0", 30 | "@babel/runtime-corejs2": "^7.0.0", 31 | "babel-eslint": "^10.0.1", 32 | "babel-loader": "^8.0.0", 33 | "css-loader": "^0.28.10", 34 | "live-server": "^1.2.0", 35 | "style-loader": "^0.20.2", 36 | "uglifyjs-webpack-plugin": "^1.3.0", 37 | "webpack": "^4.0.0", 38 | "webpack-cli": "^3.1.0", 39 | "webpack-dev-server": "^3.1.5" 40 | }, 41 | "scripts": { 42 | "build": "webpack", 43 | "start": "live-server --cors --port=8007 --proxy=/api:http://127.0.0.1:5005/api --watch=./bundle.js,./index.html ." 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /examples/next/server/server.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable object-shorthand */ 2 | /* eslint-disable no-unused-vars */ 3 | /* eslint-disable func-names */ 4 | /* eslint-disable prefer-arrow-callback */ 5 | /* eslint-disable import/no-extraneous-dependencies */ 6 | /* eslint-disable no-var */ 7 | const express = require('express'); 8 | const next = require('next'); 9 | const bodyParser = require('body-parser'); 10 | const api = require('./api/routes'); 11 | const handleForm = require('./api/form'); 12 | const formData = require('./api/formData'); 13 | 14 | const PORT = process.env.PORT || 3000; 15 | const dev = process.env.NODE_DEV !== 'production'; // true false 16 | const nextApp = next({ dev }); 17 | const handle = nextApp.getRequestHandler(); // part of next config 18 | 19 | global.navigator = { 20 | userAgent: 'node.js', 21 | }; 22 | 23 | nextApp.prepare().then(() => { 24 | // express code here 25 | const app = express(); 26 | 27 | app.use(bodyParser.json()); 28 | app.use(bodyParser.urlencoded({ extended: true })); 29 | app.use('/api/', api); 30 | 31 | app.route('/api/form/') 32 | .get((req, res) => { 33 | res.send(formData.answers); 34 | }) 35 | .post(handleForm); 36 | 37 | app.get('*', (req, res) => { 38 | return handle(req, res); // for all the react stuff 39 | }); 40 | 41 | app.listen(PORT, err => { 42 | if (err) throw err; 43 | console.log(`ready at http://localhost:${PORT}`); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /examples/custom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rfb2-example-custom", 3 | "version": "0.1.0", 4 | "description": "A complete form builder for react.", 5 | "license": "MIT", 6 | "keywords": [ 7 | "react", 8 | "react-component", 9 | "form", 10 | "builder", 11 | "ui", 12 | "drag", 13 | "drop" 14 | ], 15 | "author": "Kiho Chang", 16 | "dependencies": { 17 | "isomorphic-fetch": "^2.2.1", 18 | "react": "~16.4.1", 19 | "react-dom": "~16.4.1", 20 | "react-form-builder2": "^0.1.3" 21 | }, 22 | "devDependencies": { 23 | "@babel/cli": "^7.0.0", 24 | "@babel/core": "^7.0.0", 25 | "@babel/plugin-proposal-class-properties": "^7.0.0", 26 | "@babel/plugin-proposal-json-strings": "^7.0.0", 27 | "@babel/plugin-syntax-dynamic-import": "^7.0.0", 28 | "@babel/plugin-syntax-import-meta": "^7.0.0", 29 | "@babel/preset-env": "^7.0.0", 30 | "@babel/preset-react": "^7.0.0", 31 | "@babel/runtime-corejs2": "^7.0.0", 32 | "babel-eslint": "^10.0.1", 33 | "babel-loader": "^8.0.0", 34 | "css-loader": "^0.28.10", 35 | "live-server": "^1.2.0", 36 | "style-loader": "^0.20.2", 37 | "uglifyjs-webpack-plugin": "^1.3.0", 38 | "webpack": "^4.0.0", 39 | "webpack-cli": "^3.1.0", 40 | "webpack-dev-server": "^3.1.5" 41 | }, 42 | "scripts": { 43 | "build": "webpack", 44 | "build:dev": "webpack --mode development", 45 | "start": "live-server --cors --port=8007 --proxy=/api:http://127.0.0.1:5005/api --watch=./bundle.js,./index.html ." 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /examples/mongo/server/api/formData.js: -------------------------------------------------------------------------------- 1 | const formData = require('./dummyFormData.json'); 2 | 3 | async function saveFormData(db, doc) { 4 | const o = await db.collection('form').findOne({ id: doc.id }); 5 | if (o != null) { 6 | // console.log('update: ', doc); 7 | // eslint-disable-next-line no-param-reassign 8 | delete doc._id; 9 | db.collection('form').findOneAndUpdate({ _id: o._id }, 10 | { $set: doc }, 11 | { upsert: false }, 12 | (err) => { 13 | if (err) console.log('findOneAndUpdate error: ', err); 14 | }); 15 | } else { 16 | // console.log('insert: ', doc); 17 | db.collection('form').insertOne(doc); 18 | } 19 | } 20 | 21 | async function loadFormData(db) { 22 | return db.collection('form').find().toArray(); 23 | } 24 | 25 | async function deleteFormData(db, doc) { 26 | db.collection('form').deleteOne({ id: doc.id }); 27 | } 28 | 29 | function findRemovedItem(currentArray, previousArray) { 30 | return previousArray.filter(x => { 31 | const z = currentArray.find(y => y.id === x.id); 32 | return !currentArray.includes(z); 33 | }); 34 | } 35 | 36 | async function save(db, taskData) { 37 | const oldData = await loadFormData(db); 38 | const missing = findRemovedItem(taskData, oldData); 39 | if (missing.length) { 40 | missing.forEach(doc => deleteFormData(db, doc)); 41 | } else { 42 | taskData.forEach(doc => saveFormData(db, doc)); 43 | } 44 | } 45 | 46 | module.exports = { 47 | data: formData, 48 | answers: {}, 49 | save, 50 | load: loadFormData, 51 | }; 52 | -------------------------------------------------------------------------------- /src/sortable-form-elements.jsx: -------------------------------------------------------------------------------- 1 | import SortableElement from './sortable-element'; 2 | import PlaceHolder from './form-place-holder'; 3 | import BaseFormElements from './form-elements'; 4 | 5 | const { 6 | Header, Paragraph, Label, LineBreak, TextInput, NumberInput, TextArea, Dropdown, Checkboxes, 7 | DatePicker, RadioButtons, Image, Rating, Tags, Signature, HyperLink, Download, Camera, Range, 8 | } = BaseFormElements; 9 | 10 | const FormElements = {}; 11 | 12 | FormElements.Header = SortableElement(Header); 13 | FormElements.Paragraph = SortableElement(Paragraph); 14 | FormElements.Label = SortableElement(Label); 15 | FormElements.LineBreak = SortableElement(LineBreak); 16 | FormElements.TextInput = SortableElement(TextInput); 17 | FormElements.NumberInput = SortableElement(NumberInput); 18 | FormElements.TextArea = SortableElement(TextArea); 19 | FormElements.Dropdown = SortableElement(Dropdown); 20 | FormElements.Signature = SortableElement(Signature); 21 | FormElements.Checkboxes = SortableElement(Checkboxes); 22 | FormElements.DatePicker = SortableElement(DatePicker); 23 | FormElements.RadioButtons = SortableElement(RadioButtons); 24 | FormElements.Image = SortableElement(Image); 25 | FormElements.Rating = SortableElement(Rating); 26 | FormElements.Tags = SortableElement(Tags); 27 | FormElements.HyperLink = SortableElement(HyperLink); 28 | FormElements.Download = SortableElement(Download); 29 | FormElements.Camera = SortableElement(Camera); 30 | FormElements.Range = SortableElement(Range); 31 | FormElements.PlaceHolder = SortableElement(PlaceHolder); 32 | 33 | export default FormElements; 34 | -------------------------------------------------------------------------------- /webpack.production.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | 4 | module.exports = { 5 | entry: './src/index.jsx', 6 | 7 | output: { 8 | path: path.resolve('./dist'), 9 | filename: 'app.js', 10 | library: 'ReactFormBuilder', 11 | libraryTarget: 'umd' 12 | }, 13 | 14 | externals: { 15 | //don't bundle the 'react' npm package with our bundle.js 16 | //but get it from a global 'React' variable 17 | // 'react': 'react', 18 | // 'react-dom': 'react-dom', 19 | // 'react-datepicker': 'react-datepicker', 20 | // 'classnames': 'classnames', 21 | // 'jquery': 'jquery', 22 | 'bootstrap': 'bootstrap' 23 | }, 24 | 25 | resolve: { 26 | extensions: ['.js', '.jsx', '.scss', '.css', '.json'], 27 | alias: { 28 | "jquery": path.join(__dirname, "./jquery-stub.js") 29 | } 30 | }, 31 | 32 | module: { 33 | rules: [ 34 | { 35 | exclude: /node_modules/, 36 | test: /\.js|.jsx?$/, 37 | use: [ 38 | { loader: 'babel-loader' } 39 | ] 40 | }, 41 | { 42 | test: /\.scss$/, 43 | use: [ 44 | { 45 | loader: 'style-loader' 46 | }, 47 | { 48 | loader: 'css-loader' 49 | }, 50 | { 51 | loader: 'sass-loader', options: { 52 | includePaths: ['./node_modules'] 53 | } 54 | } 55 | ] 56 | }, 57 | ] 58 | }, 59 | performance: { 60 | hints: false 61 | } 62 | }; -------------------------------------------------------------------------------- /webpack.config.babel.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | 4 | module.exports = { 5 | entry: './app.js', 6 | devtool: 'source-map', 7 | output: { 8 | path: path.resolve('./public'), 9 | filename: 'app.js' 10 | }, 11 | resolve: { 12 | extensions: ['.js', '.jsx', '.scss', '.css', '.json'], 13 | alias: { 14 | "jquery": path.join(__dirname, "./jquery-stub.js") 15 | } 16 | }, 17 | plugins: [ 18 | // 19 | ], 20 | module: { 21 | rules: [ 22 | { 23 | exclude: /node_modules/, 24 | test: /\.js|.jsx?$/, 25 | use: [ 26 | { loader: 'babel-loader' } 27 | ] 28 | }, 29 | { 30 | test: /\.scss$/, 31 | use: [ 32 | { 33 | loader: 'style-loader' 34 | }, 35 | { 36 | loader: 'css-loader' 37 | }, 38 | { 39 | loader: 'sass-loader', options: { 40 | includePaths: ['./node_modules'] 41 | } 42 | } 43 | ] 44 | }, 45 | ] 46 | }, 47 | devServer: { 48 | port: 8080, 49 | host: "localhost", 50 | historyApiFallback: true, 51 | headers: { 52 | "Access-Control-Allow-Origin": "*", 53 | "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS", 54 | "Access-Control-Allow-Headers": "X-Requested-With, content-type, Authorization" 55 | }, 56 | watchOptions: {aggregateTimeout: 300, poll: 1000}, 57 | contentBase: './public', 58 | open: true, 59 | proxy: { 60 | "/api/*": "http://127.0.0.1:5005" 61 | } 62 | } 63 | }; 64 | -------------------------------------------------------------------------------- /scss/form-builder-form.scss: -------------------------------------------------------------------------------- 1 | input[type="date"]:before { 2 | content: attr(placeholder); 3 | color: #aaa; 4 | margin-right: 0.5em; 5 | } 6 | 7 | input[type="date"] { 8 | width: 200px; 9 | } 10 | .validation-error { 11 | position: fixed; 12 | width: 100%; 13 | top: 0; 14 | left: 0; 15 | margin: 0; 16 | z-index: 99999999; 17 | ul { 18 | width: auto; 19 | } 20 | .dismiss-modal-button { 21 | margin-top: 10px; 22 | } 23 | } 24 | .react-form-builder-form { 25 | position: relative; 26 | 27 | .rfb-item.alwaysbreak { page-break-before: always; } 28 | .rfb-item.nobreak:before { clear:both; } 29 | .rfb-item.nobreak { page-break-inside: avoid; } 30 | 31 | .rfb-item { 32 | padding: 10px 0; 33 | position: relative; 34 | 35 | label { 36 | font-weight: normal; 37 | } 38 | 39 | .bold { 40 | font-weight: bold; 41 | } 42 | .italic { 43 | font-style: italic; 44 | } 45 | .form-label { 46 | display: block !important; 47 | } 48 | 49 | .form-group { 50 | .option-inline { 51 | display: inline-block !important; 52 | margin-right: 10px; 53 | } 54 | 55 | a { 56 | cursor: pointer; 57 | } 58 | input[type='date'] { 59 | height: 42px; 60 | } 61 | .m-signature-pad { 62 | position: relative; 63 | width: auto; 64 | } 65 | .react-star-rating { 66 | display: block; 67 | } 68 | .checkbox-label, .radio-label { 69 | font-weight: normal; 70 | display: block; 71 | } 72 | .label-required { 73 | margin-left: 5px; 74 | } 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /src/form-validator.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | 5 | import React from 'react'; 6 | import xss from 'xss'; 7 | 8 | const myxss = new xss.FilterXSS({ 9 | whiteList: { 10 | u: [], 11 | br: [], 12 | b: [], 13 | i: [], 14 | ol: ['style'], 15 | ul: ['style'], 16 | li: [], 17 | p: ['style'], 18 | sub: [], 19 | sup: [], 20 | div: ['style'], 21 | em: [], 22 | strong: [], 23 | span: ['style'], 24 | }, 25 | }); 26 | 27 | export default class FormValidator extends React.Component { 28 | constructor(props) { 29 | super(props); 30 | this.state = { 31 | errors: [], 32 | }; 33 | } 34 | 35 | componentWillMount() { 36 | this.subscription = this.props.emitter.addListener('formValidation', (errors) => { 37 | this.setState({ errors }); 38 | }); 39 | } 40 | 41 | componentWillUnmount() { 42 | this.subscription.remove(); 43 | } 44 | 45 | dismissModal(e) { 46 | e.preventDefault(); 47 | this.setState({ errors: [] }); 48 | } 49 | 50 | render() { 51 | const errors = this.state.errors.map((error, index) =>
  • ); 52 | 53 | return ( 54 |
    55 | { this.state.errors.length > 0 && 56 |
    57 |
    58 | 59 |
      60 | {errors} 61 |
    62 |
    63 |
    64 | Dismiss 65 |
    66 |
    67 | } 68 |
    69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/UUID.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // Private array of chars to use 3 | var CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(''); 4 | var ID = {}; 5 | ID.uuid = function (len, radix) { 6 | var chars = CHARS, uuid = [], i; 7 | radix = radix || chars.length; 8 | 9 | if (len) { 10 | // Compact form 11 | for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random()*radix]; 12 | } else { 13 | // rfc4122, version 4 form 14 | var r; 15 | 16 | // rfc4122 requires these characters 17 | uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'; 18 | uuid[14] = '4'; 19 | 20 | // Fill in random data. At i==19 set the high bits of clock sequence as 21 | // per rfc4122, sec. 4.1.5 22 | for (i = 0; i < 36; i++) { 23 | if (!uuid[i]) { 24 | r = 0 | Math.random()*16; 25 | uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]; 26 | } 27 | } 28 | } 29 | 30 | return uuid.join(''); 31 | }; 32 | 33 | // A more performant, but slightly bulkier, RFC4122v4 solution. We boost performance 34 | // by minimizing calls to random() 35 | ID.uuidFast = function() { 36 | var chars = CHARS, uuid = new Array(36), rnd=0, r; 37 | for (var i = 0; i < 36; i++) { 38 | if (i==8 || i==13 || i==18 || i==23) { 39 | uuid[i] = '-'; 40 | } else if (i==14) { 41 | uuid[i] = '4'; 42 | } else { 43 | if (rnd <= 0x02) rnd = 0x2000000 + (Math.random()*0x1000000)|0; 44 | r = rnd & 0xf; 45 | rnd = rnd >> 4; 46 | uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]; 47 | } 48 | } 49 | return uuid.join(''); 50 | }; 51 | 52 | // A more compact, but less performant, RFC4122v4 solution: 53 | ID.uuidCompact = function() { 54 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { 55 | var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); 56 | return v.toString(16); 57 | }); 58 | }; 59 | module.exports = ID; -------------------------------------------------------------------------------- /src/stores/store.js: -------------------------------------------------------------------------------- 1 | import Store from 'beedle'; 2 | import { get, post } from './requests'; 3 | 4 | let _saveUrl; 5 | let _onPost; 6 | let _onLoad; 7 | 8 | const store = new Store({ 9 | actions: { 10 | setData(context, data, saveData) { 11 | context.commit('setData', data); 12 | if (saveData) this.save(data); 13 | }, 14 | 15 | load(context, { loadUrl, saveUrl, data }) { 16 | _saveUrl = saveUrl; 17 | if (_onLoad) { 18 | _onLoad().then(x => this.setData(context, x)); 19 | } else if (loadUrl) { 20 | get(loadUrl).then(x => { 21 | if (data && data.length > 0 && x.length === 0) { 22 | data.forEach(y => x.push(y)); 23 | } 24 | this.setData(context, x); 25 | }); 26 | } else { 27 | this.setData(context, data); 28 | } 29 | }, 30 | 31 | create(context, element) { 32 | const { data } = context.state; 33 | data.push(element); 34 | this.setData(context, data, true); 35 | }, 36 | 37 | delete(context, element) { 38 | const { data } = context.state; 39 | data.splice(data.indexOf(element), 1); 40 | this.setData(context, data, true); 41 | }, 42 | 43 | updateOrder(context, elements) { 44 | this.setData(context, elements, true); 45 | }, 46 | 47 | save(data) { 48 | if (_onPost) { 49 | _onPost({ task_data: data }); 50 | } else if (_saveUrl) { 51 | post(_saveUrl, { task_data: data }); 52 | } 53 | }, 54 | }, 55 | 56 | mutations: { 57 | setData(state, payload) { 58 | // eslint-disable-next-line no-param-reassign 59 | state.data = payload; 60 | return state; 61 | }, 62 | }, 63 | 64 | initialState: { 65 | data: [], 66 | }, 67 | }); 68 | 69 | store.setExternalHandler = (onLoad, onPost) => { 70 | _onLoad = onLoad; 71 | _onPost = onPost; 72 | }; 73 | 74 | export default store; 75 | -------------------------------------------------------------------------------- /examples/custom/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { ReactFormBuilder, ElementStore } from 'react-form-builder2'; 4 | import DemoBar from './demobar'; 5 | import * as variables from './variables'; 6 | import { get, post } from './requests'; 7 | 8 | const getUrl = (cid) => `https://safe-springs-35306.herokuapp.com/api/formdata?cid=${cid}`; 9 | 10 | class App extends React.Component { 11 | constructor(props) { 12 | super(props); 13 | this.state = { formId: '1' }; 14 | this.formId = this.state.formId; 15 | this.handleChange = this.handleChange.bind(this); 16 | } 17 | 18 | formId; 19 | 20 | handleChange(event) { 21 | this.formId = event.target.value; 22 | const url = getUrl(this.formId); 23 | console.log('handleChange', url); 24 | ElementStore.dispatch('load', { loadUrl: url }); 25 | this.setState({ formId: this.formId }); 26 | } 27 | 28 | onLoad = () => { 29 | const url = getUrl(this.formId); 30 | console.log('onLoad', url); 31 | return get(url); 32 | }; 33 | 34 | onPost = (data) => { 35 | const saveUrl = getUrl(this.formId); 36 | console.log('onPost', saveUrl, data); 37 | post(saveUrl, data); 38 | }; 39 | 40 | render() { 41 | return ( 42 |
    43 | 46 | 52 |
    53 | , 57 |
    58 | ); 59 | } 60 | } 61 | 62 | ReactDOM.render( 63 | , 64 | document.getElementById('form-builder'), 65 | ); 66 | 67 | ReactDOM.render( 68 | , 69 | document.getElementById('demo-bar'), 70 | ); 71 | -------------------------------------------------------------------------------- /docs/form-builder.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | // const e = React.createElement; 3 | const FormBuilder = ReactFormBuilder.default.ReactFormBuilder; 4 | const domContainer = document.querySelector('#form-builder'); 5 | function guid() { 6 | function _p8(s) { 7 | const p = (`${Math.random().toString(16)}000000000`).substr(2, 8); 8 | return s ? `_${p.substr(0, 4)}_${p.substr(4, 4)}` : p; 9 | } 10 | return _p8() + _p8(true) + _p8(true) + _p8(); 11 | } 12 | 13 | let cid = window.localStorage.getItem('cid'); 14 | if (!cid) { 15 | cid = guid(); 16 | window.localStorage.setItem('cid', cid); 17 | } 18 | 19 | const url = `https://safe-springs-35306.herokuapp.com/api/formdata?cid=${cid}`; 20 | const saveUrl = `https://safe-springs-35306.herokuapp.com/api/formdata?cid=${cid}`; 21 | 22 | ReactDOM.render(e(FormBuilder, { url, saveUrl }), domContainer); 23 | 24 | let backdropElement; 25 | const classBackdrop = 'modal-backdrop'; 26 | 27 | function showBackdrop() { 28 | if (!backdropElement) { 29 | backdropElement = document.createElement('div'); 30 | backdropElement.className = `${classBackdrop} show`; 31 | document.body.appendChild(backdropElement); 32 | } 33 | } 34 | 35 | function destroyBackdrop() { 36 | if (backdropElement) { 37 | backdropElement.parentNode.removeChild(backdropElement); 38 | } 39 | } 40 | 41 | let show = false; 42 | 43 | function clearMessage() { 44 | destroyBackdrop(); 45 | toastr.clear(); 46 | show = false; 47 | } 48 | 49 | const headers = { 50 | Accept: 'application/json', 51 | 'Content-Type': 'application/json; charset=utf-8', 52 | OPTIONS: '', 53 | }; 54 | 55 | function checkBackEnd() { 56 | show = true; 57 | setTimeout(() => { 58 | if (show) showBackdrop(); 59 | }, 300); 60 | setTimeout(() => { 61 | if (show) toastr.warning('Loading.... Please Wait.'); 62 | }, 1000); 63 | fetch(url, { 64 | method: 'GET', 65 | headers, 66 | }).then(clearMessage); 67 | } 68 | 69 | checkBackEnd(); 70 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable object-shorthand */ 2 | /* eslint-disable no-unused-vars */ 3 | /* eslint-disable func-names */ 4 | /* eslint-disable prefer-arrow-callback */ 5 | /* eslint-disable import/no-extraneous-dependencies */ 6 | /* eslint-disable no-var */ 7 | var express = require('express'); 8 | var bodyParser = require('body-parser'); 9 | var path = require('path'); 10 | var api = require('./api/routes'); 11 | var handleForm = require('./api/form'); 12 | var formData = require('./api/formData'); 13 | 14 | var mode = process.env.NODE_ENV || 'development'; 15 | const isProduction = mode === 'production'; 16 | 17 | var app = express(); 18 | 19 | // set the view engine to ejs 20 | app.set('views', path.join(__dirname, '/views')); 21 | app.set('view engine', 'ejs'); 22 | // app.engine('ejs', require('ejs').renderFile); 23 | 24 | app.set('port', (process.env.PORT || isProduction ? 8080 : 5005)); 25 | 26 | app.use(bodyParser.json()); 27 | app.use(bodyParser.urlencoded({ 28 | extended: true, 29 | })); 30 | 31 | if (isProduction) { 32 | app.use(express.static(`${__dirname}/../dist`)); 33 | } 34 | 35 | app.use('/api/', api); 36 | 37 | app.route('/api/form/') 38 | .get((req, res) => { 39 | // console.log('get form: ', formData.data); 40 | // console.log('get form answers: ', formData.answers); 41 | res.render('index', { 42 | data: JSON.stringify(formData.data), 43 | answers: JSON.stringify(formData.answers), 44 | }); 45 | }) 46 | .post(handleForm); 47 | 48 | // console.log('NODE_ENV', process.env.NODE_ENV, `${__dirname}/../dist`); 49 | 50 | // 404 catch-all handler (middleware) 51 | app.use(function (req, res) { 52 | res.status(404); 53 | res.render('404'); 54 | }); 55 | 56 | // 500 error handler (middleware) 57 | app.use(function (err, req, res, next) { 58 | res.status(500); 59 | res.render('500', { error: err }); 60 | }); 61 | 62 | app.listen(app.get('port'), function () { 63 | console.log( 64 | `Express started on http://localhost:${app.get( 65 | 'port', 66 | )}; press Ctrl-C to terminate.`, 67 | ); 68 | }); 69 | -------------------------------------------------------------------------------- /examples/mongo/server/server.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-shadow */ 2 | /* eslint-disable arrow-body-style */ 3 | /* eslint-disable object-shorthand */ 4 | /* eslint-disable no-unused-vars */ 5 | /* eslint-disable func-names */ 6 | /* eslint-disable prefer-arrow-callback */ 7 | /* eslint-disable import/no-extraneous-dependencies */ 8 | /* eslint-disable no-var */ 9 | const express = require('express'); 10 | const next = require('next'); 11 | const { MongoClient } = require('mongodb'); 12 | 13 | const bodyParser = require('body-parser'); 14 | const api = require('./api/routes'); 15 | const handleForm = require('./api/form'); 16 | const formData = require('./api/formData'); 17 | 18 | const PORT = process.env.PORT || 3000; 19 | const dev = process.env.NODE_DEV !== 'production'; // true false 20 | const nextApp = next({ dev }); 21 | const handle = nextApp.getRequestHandler(); // part of next config 22 | 23 | const MONGO_URL = 'mongodb://localhost:27017'; 24 | const dbName = 'test'; 25 | 26 | // Fix crash with react-datepicker 27 | global.navigator = { 28 | userAgent: 'node.js', 29 | }; 30 | 31 | MongoClient.connect(MONGO_URL, (err, client) => { 32 | console.log('Connected successfully to server', MONGO_URL); 33 | const db = client.db(dbName); 34 | nextApp.prepare().then(() => { 35 | // express code here 36 | const app = express(); 37 | app.use(bodyParser.json()); 38 | app.use(bodyParser.urlencoded({ extended: true })); 39 | 40 | app.use((req, res, next) => { 41 | // Also expose the MongoDB database handle so Next.js can access it. 42 | req.db = db; 43 | next(); 44 | }); 45 | 46 | app.use('/api/', api); 47 | 48 | app.route('/api/form/') 49 | .get((req, res) => { 50 | res.send(formData.answers); 51 | }) 52 | .post(handleForm); 53 | 54 | app.get('*', (req, res) => 55 | // for all the react stuff 56 | // eslint-disable-next-line implicit-arrow-linebreak 57 | handle(req, res)); 58 | 59 | app.listen(PORT, err1 => { 60 | if (err1) throw err1; 61 | console.log(`ready at http://localhost:${PORT}`); 62 | }); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /examples/umd/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React Form Builder - UMD 6 | 7 | 8 | 9 | 20 | 21 | 22 | 26 | 27 |
    28 |

    Preview

    29 | 30 | 42 |
    43 |
    44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /server/api/form.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-use-before-define */ 2 | /* eslint-disable consistent-return */ 3 | /* eslint-disable import/no-extraneous-dependencies */ 4 | /* eslint-disable no-var */ 5 | var multer = require('multer'); 6 | var path = require('path'); 7 | var fs = require('fs'); 8 | var formData = require('./formData'); 9 | 10 | var extensions = ['.png', '.gif', '.jpg', '.jpeg']; 11 | var handleError = (err, res) => { 12 | res 13 | .status(500) 14 | .contentType('text/plain') 15 | .end('Oops! Something went wrong!'); 16 | }; 17 | var tempPath = path.join(__dirname, '../../public/temp'); 18 | var upload = multer({ dest: tempPath }).any(); 19 | 20 | var handleUpload = (req, res) => { 21 | upload(req, res, (error) => { 22 | if (error instanceof multer.MulterError) { 23 | // A Multer error occurred when uploading. 24 | console.log('multer.MulterError', error); 25 | } else if (error) { 26 | // An unknown error occurred when uploading. 27 | console.log('multer.MulterError', error); 28 | } else { 29 | formData.answers = req.body; 30 | const file = req.files[0]; 31 | if (!file) { 32 | res.redirect('/api/form'); 33 | return; 34 | } 35 | 36 | // TODO - handle multiple files 37 | const tempFilePath = file.path; 38 | const fieldname = file.fieldname; 39 | const targetPath = path.join(__dirname, '../../public/uploads'); 40 | const extn = path.extname(file.originalname).toLowerCase(); 41 | if (extensions.indexOf(extn) > -1) { 42 | const targetFilePath = path.join(targetPath, `${fieldname}${extn}`); 43 | fs.rename(tempFilePath, targetFilePath, err => { 44 | if (err) return handleError(err, res); 45 | formData.answers[fieldname] = `/uploads/${fieldname}${extn}`; 46 | res.redirect('/api/form'); 47 | }); 48 | } else { 49 | fs.unlink(tempPath, err => { 50 | if (err) return handleError(err, res); 51 | res 52 | .status(403) 53 | .contentType('text/plain') 54 | .end('File type is not allowed!'); 55 | }); 56 | } 57 | } 58 | }); 59 | }; 60 | 61 | module.exports = handleUpload; 62 | -------------------------------------------------------------------------------- /examples/next/server/api/form.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-use-before-define */ 2 | /* eslint-disable consistent-return */ 3 | /* eslint-disable import/no-extraneous-dependencies */ 4 | /* eslint-disable no-var */ 5 | var multer = require('multer'); 6 | var path = require('path'); 7 | var fs = require('fs'); 8 | var formData = require('./formData'); 9 | 10 | var extensions = ['.png', '.gif', '.jpg', '.jpeg']; 11 | var handleError = (err, res) => { 12 | res 13 | .status(500) 14 | .contentType('text/plain') 15 | .end('Oops! Something went wrong!'); 16 | }; 17 | var tempPath = path.join(__dirname, '../../public/temp'); 18 | var upload = multer({ dest: tempPath }).any(); 19 | 20 | var handleUpload = (req, res) => { 21 | upload(req, res, (error) => { 22 | if (error instanceof multer.MulterError) { 23 | // A Multer error occurred when uploading. 24 | console.log('multer.MulterError', error); 25 | } else if (error) { 26 | // An unknown error occurred when uploading. 27 | console.log('multer.MulterError', error); 28 | } else { 29 | formData.answers = req.body; 30 | const file = req.files && req.files.length ? req.files[0] : null; 31 | if (!file) { 32 | res.status(201).send(); 33 | return; 34 | } 35 | 36 | // TODO - handle multiple files 37 | const tempFilePath = file.path; 38 | const fieldname = file.fieldname; 39 | const targetPath = path.join(__dirname, '../../public/uploads'); 40 | const extn = path.extname(file.originalname).toLowerCase(); 41 | if (extensions.indexOf(extn) > -1) { 42 | const targetFilePath = path.join(targetPath, `${fieldname}${extn}`); 43 | fs.rename(tempFilePath, targetFilePath, err => { 44 | if (err) return handleError(err, res); 45 | formData.answers[fieldname] = `/uploads/${fieldname}${extn}`; 46 | res.status(201).send(); 47 | }); 48 | } else { 49 | fs.unlink(tempPath, err => { 50 | if (err) return handleError(err, res); 51 | res 52 | .status(403) 53 | .contentType('text/plain') 54 | .end('File type is not allowed!'); 55 | }); 56 | } 57 | } 58 | }); 59 | }; 60 | 61 | module.exports = handleUpload; 62 | -------------------------------------------------------------------------------- /examples/mongo/server/api/form.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-use-before-define */ 2 | /* eslint-disable consistent-return */ 3 | /* eslint-disable import/no-extraneous-dependencies */ 4 | /* eslint-disable no-var */ 5 | var multer = require('multer'); 6 | var path = require('path'); 7 | var fs = require('fs'); 8 | var formData = require('./formData'); 9 | 10 | var extensions = ['.png', '.gif', '.jpg', '.jpeg']; 11 | 12 | var handleError = (err, res) => { 13 | res 14 | .status(500) 15 | .contentType('text/plain') 16 | .end('Oops! Something went wrong!'); 17 | }; 18 | 19 | var tempPath = path.join(__dirname, '../../public/temp'); 20 | var upload = multer({ dest: tempPath }).any(); 21 | 22 | module.exports = (req, res) => { 23 | upload(req, res, (error) => { 24 | if (error instanceof multer.MulterError) { 25 | // A Multer error occurred when uploading. 26 | console.log('multer.MulterError', error); 27 | } else if (error) { 28 | // An unknown error occurred when uploading. 29 | console.log('multer.MulterError', error); 30 | } else { 31 | formData.answers = req.body; 32 | saveAnswers(req.db, req.body); 33 | const file = req.files && req.files.length ? req.files[0] : null; 34 | if (!file) { 35 | res.status(201).send(); 36 | return; 37 | } 38 | 39 | // TODO - handle multiple files 40 | const tempFilePath = file.path; 41 | const fieldname = file.fieldname; 42 | const targetPath = path.join(__dirname, '../../public/uploads'); 43 | const extn = path.extname(file.originalname).toLowerCase(); 44 | if (extensions.indexOf(extn) > -1) { 45 | const targetFilePath = path.join(targetPath, `${fieldname}${extn}`); 46 | fs.rename(tempFilePath, targetFilePath, err => { 47 | if (err) return handleError(err, res); 48 | formData.answers[fieldname] = `/uploads/${fieldname}${extn}`; 49 | res.status(201).send(); 50 | }); 51 | } else { 52 | fs.unlink(tempPath, err => { 53 | if (err) return handleError(err, res); 54 | res 55 | .status(403) 56 | .contentType('text/plain') 57 | .end('File type is not allowed!'); 58 | }); 59 | } 60 | } 61 | }); 62 | }; 63 | 64 | function saveAnswers(db, answers) { 65 | if (answers && answers.length) { 66 | db.collection('answers').insertMany(answers); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /examples/umd/form-generator.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-param-reassign */ 2 | /* eslint-disable prefer-destructuring */ 3 | /* eslint-disable no-undef */ 4 | const e = React.createElement; 5 | 6 | const ReactFormGenerator = ReactFormBuilder.default.ReactFormGenerator; 7 | const ElementStore = ReactFormBuilder.default.ElementStore; 8 | const formContainer = document.querySelector('#form-generator'); 9 | 10 | function setClass(element, name, remove) { 11 | if (typeof element === 'string') { 12 | element = document.querySelector(element); 13 | } 14 | if (remove) { 15 | element.classList.remove(name); 16 | } else { 17 | element.classList.add(name); 18 | } 19 | } 20 | 21 | class FormGenerator extends React.Component { 22 | constructor(props) { 23 | super(props); 24 | this.state = { 25 | data: [], 26 | previewVisible: false, 27 | }; 28 | 29 | this.showPreview = this.showPreview.bind(this); 30 | this.closePreview = this.closePreview.bind(this); 31 | const update = this._onChange.bind(this); 32 | ElementStore.subscribe(state => update(state.data)); 33 | } 34 | 35 | componentDidMount() { 36 | document.querySelector('#button-preview') 37 | .addEventListener('click', this.showPreview); 38 | document.querySelector('#button-close') 39 | .addEventListener('click', this.closePreview); 40 | } 41 | 42 | showPreview() { 43 | this.setState({ 44 | previewVisible: true, 45 | }); 46 | setClass('#preview-dialog', 'show', false); 47 | } 48 | 49 | closePreview() { 50 | this.setState({ 51 | previewVisible: false, 52 | }); 53 | setClass('#preview-dialog', 'show', true); 54 | } 55 | 56 | _onChange(data) { 57 | this.setState({ 58 | data, 59 | }); 60 | } 61 | 62 | render() { 63 | const previewVisible = this.state.previewVisible; 64 | if (!previewVisible) { 65 | return null; 66 | } 67 | return e( 68 | ReactFormGenerator, { 69 | download_path: '', 70 | back_action: '/', 71 | back_name: 'Back', 72 | answer_data: {}, 73 | action_name: 'Save', 74 | form_action: '/', 75 | form_method: 'POST', 76 | variables: this.props.variables, 77 | data: this.state.data, 78 | }, 79 | ); 80 | } 81 | } 82 | 83 | ReactDOM.render(e(FormGenerator), formContainer); 84 | -------------------------------------------------------------------------------- /docs/form-generator.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-param-reassign */ 2 | /* eslint-disable prefer-destructuring */ 3 | /* eslint-disable no-undef */ 4 | const e = React.createElement; 5 | 6 | const ReactFormGenerator = ReactFormBuilder.default.ReactFormGenerator; 7 | const ElementStore = ReactFormBuilder.default.ElementStore; 8 | const formContainer = document.querySelector('#form-generator'); 9 | 10 | function setClass(element, name, remove) { 11 | if (typeof element === 'string') { 12 | element = document.querySelector(element); 13 | } 14 | if (remove) { 15 | element.classList.remove(name); 16 | } else { 17 | element.classList.add(name); 18 | } 19 | } 20 | 21 | class FormGenerator extends React.Component { 22 | constructor(props) { 23 | super(props); 24 | this.state = { 25 | data: [], 26 | previewVisible: false, 27 | }; 28 | 29 | this.showPreview = this.showPreview.bind(this); 30 | this.closePreview = this.closePreview.bind(this); 31 | const update = this._onChange.bind(this); 32 | ElementStore.subscribe(state => update(state.data)); 33 | } 34 | 35 | componentDidMount() { 36 | document.querySelector('#button-preview') 37 | .addEventListener('click', this.showPreview); 38 | document.querySelector('#button-close') 39 | .addEventListener('click', this.closePreview); 40 | } 41 | 42 | showPreview() { 43 | this.setState({ 44 | previewVisible: true, 45 | }); 46 | setClass('#preview-dialog', 'show', false); 47 | } 48 | 49 | closePreview() { 50 | this.setState({ 51 | previewVisible: false, 52 | }); 53 | setClass('#preview-dialog', 'show', true); 54 | } 55 | 56 | _onChange(data) { 57 | this.setState({ 58 | data, 59 | }); 60 | } 61 | 62 | render() { 63 | const previewVisible = this.state.previewVisible; 64 | if (!previewVisible) { 65 | return null; 66 | } 67 | const cid = window.localStorage.getItem('cid'); 68 | const query = cid ? `?cid=${cid}` : ''; 69 | const postUrl = `https://safe-springs-35306.herokuapp.com/api/form${query}`; 70 | return e( 71 | ReactFormGenerator, { 72 | download_path: '', 73 | back_action: '/react-form-builder/index.html', 74 | back_name: 'Back', 75 | answer_data: {}, 76 | action_name: 'Save', 77 | form_action: postUrl, 78 | form_method: 'POST', 79 | variables: this.props.variables, 80 | data: this.state.data, 81 | }, 82 | ); 83 | } 84 | } 85 | 86 | ReactDOM.render(e(FormGenerator), formContainer); 87 | -------------------------------------------------------------------------------- /examples/mongo/pages/form.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ReactFormGenerator } from 'react-form-builder2'; 3 | import { get } from '../components/requests'; 4 | 5 | export default class Demobar extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | // console.log(`Demobar: `, props); 9 | this.state = { 10 | data: props.data, 11 | answers: props.answers, 12 | roPreviewVisible: props.roPreviewVisible, 13 | }; 14 | } 15 | 16 | showRoPreview() { 17 | this.setState({ 18 | roPreviewVisible: true, 19 | }); 20 | } 21 | 22 | closePreview() { 23 | this.setState({ 24 | roPreviewVisible: false, 25 | }); 26 | } 27 | 28 | render() { 29 | const { answers, data } = this.state; 30 | 31 | let roModalClass = 'modal ro-modal'; 32 | if (this.state.roPreviewVisible) { 33 | roModalClass += ' show'; 34 | } 35 | 36 | return ( 37 |
    38 |

    Preview

    39 | 40 | { this.state.roPreviewVisible && 41 |
    42 |
    43 |
    44 |
    45 | 57 |
    58 |
    59 | 60 |
    61 |
    62 |
    63 |
    } 64 |
    65 | ); 66 | } 67 | } 68 | 69 | // eslint-disable-next-line func-names 70 | Demobar.getInitialProps = async function ({ req }) { 71 | const protocol = req.headers.referer.split('://')[0]; 72 | const hostUrl = `${protocol}://${req.headers.host}`; 73 | const url = `${hostUrl}/api/formdata`; 74 | const getUrl = `${hostUrl}/api/form`; 75 | const answers = await get(getUrl); 76 | const data = await get(url); 77 | return { 78 | data, 79 | answers, 80 | roPreviewVisible: true, 81 | }; 82 | }; 83 | -------------------------------------------------------------------------------- /examples/next/pages/form.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ReactFormGenerator } from 'react-form-builder2'; 3 | import { get } from '../components/requests'; 4 | 5 | export default class Demobar extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | // console.log(`Demobar: `, props); 9 | this.state = { 10 | data: props.data, 11 | answers: props.answers, 12 | roPreviewVisible: props.roPreviewVisible, 13 | }; 14 | } 15 | 16 | showRoPreview() { 17 | this.setState({ 18 | roPreviewVisible: true, 19 | }); 20 | } 21 | 22 | closePreview() { 23 | this.setState({ 24 | roPreviewVisible: false, 25 | }); 26 | } 27 | 28 | render() { 29 | const { answers, data } = this.state; 30 | 31 | let roModalClass = 'modal ro-modal'; 32 | if (this.state.roPreviewVisible) { 33 | roModalClass += ' show'; 34 | } 35 | 36 | return ( 37 |
    38 |

    Preview

    39 | 40 | { this.state.roPreviewVisible && 41 |
    42 |
    43 |
    44 |
    45 | 57 |
    58 |
    59 | 60 |
    61 |
    62 |
    63 |
    } 64 |
    65 | ); 66 | } 67 | } 68 | 69 | // eslint-disable-next-line func-names 70 | Demobar.getInitialProps = async function ({ req }) { 71 | const protocol = req.headers.referer.split('://')[0]; 72 | const hostUrl = `${protocol}://${req.headers.host}`; 73 | const url = `${hostUrl}/api/formdata`; 74 | const getUrl = `${hostUrl}/api/form`; 75 | const answers = await get(getUrl); 76 | const data = await get(url); 77 | return { 78 | data, 79 | answers, 80 | roPreviewVisible: true, 81 | }; 82 | }; 83 | -------------------------------------------------------------------------------- /src/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | 5 | import React from 'react'; 6 | import { DragDropContext } from 'react-dnd'; 7 | import HTML5Backend from 'react-dnd-html5-backend'; 8 | import Preview from './preview'; 9 | import Toolbar from './toolbar'; 10 | import ReactFormGenerator from './form'; 11 | import store from './stores/store'; 12 | 13 | class ReactFormBuilder extends React.Component { 14 | constructor(props) { 15 | super(props); 16 | 17 | this.state = { 18 | editMode: false, 19 | editElement: null, 20 | }; 21 | } 22 | 23 | editModeOn(data, e) { 24 | e.preventDefault(); 25 | e.stopPropagation(); 26 | if (this.state.editMode) { 27 | this.setState({ editMode: !this.state.editMode, editElement: null }); 28 | } else { 29 | this.setState({ editMode: !this.state.editMode, editElement: data }); 30 | } 31 | } 32 | 33 | manualEditModeOff() { 34 | if (this.state.editMode) { 35 | this.setState({ 36 | editMode: false, 37 | editElement: null, 38 | }); 39 | } 40 | } 41 | 42 | render() { 43 | const toolbarProps = {}; 44 | if (this.props.toolbarItems) { toolbarProps.items = this.props.toolbarItems; } 45 | return ( 46 |
    47 | {/*
    48 |

    49 | It is easy to implement a sortable interface with React DnD. Just make 50 | the same component both a drag source and a drop target, and reorder 51 | the data in the hover handler. 52 |

    53 | 54 |
    */} 55 |
    56 |
    57 | 70 | 71 |
    72 |
    73 |
    74 | ); 75 | } 76 | } 77 | 78 | const FormBuilders = {}; 79 | const FormBuilder = DragDropContext(HTML5Backend)(ReactFormBuilder); 80 | 81 | FormBuilders.ReactFormBuilder = FormBuilder; 82 | FormBuilders.ReactFormGenerator = ReactFormGenerator; 83 | FormBuilders.ElementStore = store; 84 | 85 | export default FormBuilders; 86 | 87 | export { FormBuilder as ReactFormBuilder, ReactFormGenerator, store as ElementStore }; 88 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React Form Builder - UMD 6 | 7 | 8 | 9 | 10 | 31 | 32 | 33 | 37 | 38 |
    39 |

    React Form Builder 2

    40 | 41 | 53 |
    54 |
    55 | 56 | 57 | 58 | 59 | 60 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-form-builder2", 3 | "version": "0.3.2", 4 | "description": "A complete form builder for react.", 5 | "main": "lib/index.js", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/kiho/react-form-builder.git" 10 | }, 11 | "files": [ 12 | "lib", 13 | "dist" 14 | ], 15 | "keywords": [ 16 | "react", 17 | "react-component", 18 | "form", 19 | "builder", 20 | "ui", 21 | "drag", 22 | "drop" 23 | ], 24 | "author": "Kiho Chang", 25 | "dependencies": { 26 | "beedle": "^0.6.8", 27 | "classnames": "^2.2.5", 28 | "date-fns": "^2.0.0-alpha.27", 29 | "draft-js": "^0.10.0", 30 | "draftjs-to-html": "^0.6.1", 31 | "es6-promise": "^4.2.4", 32 | "fbemitter": "^2.1.1", 33 | "immutability-helper": "^2.6.6", 34 | "isomorphic-fetch": "^2.2.1", 35 | "prop-types": "^15.6.1", 36 | "react-bootstrap-slider": "^2.2.2", 37 | "react-datepicker": "^2.4.0", 38 | "react-dnd": "^2.5.4", 39 | "react-dnd-html5-backend": "^2.5.4", 40 | "react-draft-wysiwyg": "^1.12.13", 41 | "react-select": "^2.0.0", 42 | "react-signature-canvas": "^1.0.1", 43 | "react-star-rating": "^1.4.2", 44 | "react-textarea-autosize": "^6.0.0", 45 | "xss": "^1.0.3" 46 | }, 47 | "peerDependencies": { 48 | "react": ">=16.3.0", 49 | "react-dom": ">=16.3.0" 50 | }, 51 | "devDependencies": { 52 | "@babel/cli": "^7.0.0", 53 | "@babel/core": "^7.0.0", 54 | "@babel/plugin-proposal-class-properties": "^7.0.0", 55 | "@babel/plugin-proposal-json-strings": "^7.0.0", 56 | "@babel/plugin-syntax-dynamic-import": "^7.0.0", 57 | "@babel/plugin-syntax-import-meta": "^7.0.0", 58 | "@babel/preset-env": "^7.0.0", 59 | "@babel/preset-react": "^7.0.0", 60 | "@babel/runtime-corejs2": "^7.0.0", 61 | "add": "^2.0.6", 62 | "babel-eslint": "^10.0.1", 63 | "babel-loader": "^8.0.0", 64 | "copyfiles": "^2.1.0", 65 | "css-loader": "^0.28.10", 66 | "ejs": "^2.6.1", 67 | "eslint": "^6.1.0", 68 | "eslint-config-airbnb": "^17.1.0", 69 | "eslint-plugin-import": "^2.14.0", 70 | "eslint-plugin-jsx-a11y": "^6.1.2", 71 | "eslint-plugin-react": "^7.11.1", 72 | "express": "^4.16.3", 73 | "multer": "^1.4.1", 74 | "node-sass": "^4.7.2", 75 | "react": "^16.3.0", 76 | "react-dom": "^16.3.0", 77 | "rimraf": "^2.6.2", 78 | "sass-loader": "^6.0.7", 79 | "style-loader": "^0.20.2", 80 | "webpack": "^4.40.0", 81 | "webpack-cli": "^3.1.0", 82 | "webpack-dev-server": "^3.1.5" 83 | }, 84 | "scripts": { 85 | "build": "webpack -p --config webpack.production.config.js", 86 | "build:dev": "webpack --mode development", 87 | "build:style": "node-sass ./scss/application.scss dist/app.css --output-style compressed", 88 | "build:lib": "npm run transpile && npm run build:style", 89 | "build:dist": "npm run build && npm run copy:dist", 90 | "copy:dist": "copyfiles -f \"./dist/*\" \"./public/dist\"", 91 | "prepublish": "npm run build:lib && npm run build", 92 | "watch": "webpack --watch", 93 | "start": "webpack-dev-server --hot --inline --mode development", 94 | "serve:api": "node server/index.js", 95 | "pretranspile": "rimraf lib", 96 | "transpile": "babel --out-dir lib src --copy-files" 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/sortable-element.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { findDOMNode } from 'react-dom'; 4 | import { DragSource, DropTarget } from 'react-dnd'; 5 | import ItemTypes from './ItemTypes'; 6 | 7 | const style = { 8 | border: '1px dashed gray', 9 | padding: '0.5rem 1rem', 10 | marginBottom: '.5rem', 11 | backgroundColor: 'white', 12 | cursor: 'move', 13 | }; 14 | 15 | const cardSource = { 16 | beginDrag(props) { 17 | return { 18 | id: props.id, 19 | index: props.index, 20 | }; 21 | }, 22 | }; 23 | 24 | const cardTarget = { 25 | hover(props, monitor, component) { 26 | const item = monitor.getItem(); 27 | const dragIndex = item.index; 28 | const hoverIndex = props.index; 29 | 30 | // Don't replace items with themselves 31 | if (dragIndex === hoverIndex) { 32 | return; 33 | } if (dragIndex === -1) { 34 | item.index = hoverIndex; 35 | props.insertCard(item.onCreate(item.data), hoverIndex); 36 | } 37 | 38 | // Determine rectangle on screen 39 | const hoverBoundingRect = findDOMNode(component).getBoundingClientRect(); 40 | 41 | // Get vertical middle 42 | const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2; 43 | 44 | // Determine mouse position 45 | const clientOffset = monitor.getClientOffset(); 46 | 47 | // Get pixels to the top 48 | const hoverClientY = clientOffset.y - hoverBoundingRect.top; 49 | 50 | // Only perform the move when the mouse has crossed half of the items height 51 | // When dragging downwards, only move when the cursor is below 50% 52 | // When dragging upwards, only move when the cursor is above 50% 53 | 54 | // Dragging downwards 55 | if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) { 56 | return; 57 | } 58 | 59 | // Dragging upwards 60 | if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) { 61 | return; 62 | } 63 | 64 | // Time to actually perform the action 65 | props.moveCard(dragIndex, hoverIndex); 66 | 67 | // Note: we're mutating the monitor item here! 68 | // Generally it's better to avoid mutations, 69 | // but it's good here for the sake of performance 70 | // to avoid expensive index searches. 71 | item.index = hoverIndex; 72 | }, 73 | }; 74 | 75 | // eslint-disable-next-line no-unused-vars 76 | export default function (ComposedComponent) { 77 | class Card extends Component { 78 | static propTypes = { 79 | connectDragSource: PropTypes.func, 80 | connectDropTarget: PropTypes.func, 81 | index: PropTypes.number.isRequired, 82 | isDragging: PropTypes.bool, 83 | id: PropTypes.any.isRequired, 84 | // text: PropTypes.string.isRequired, 85 | moveCard: PropTypes.func.isRequired, 86 | } 87 | 88 | render() { 89 | const { 90 | isDragging, 91 | connectDragSource, 92 | connectDropTarget, 93 | } = this.props; 94 | const opacity = isDragging ? 0 : 1; 95 | 96 | return connectDragSource( 97 | connectDropTarget(
    ), 98 | ); 99 | } 100 | } 101 | 102 | const x = DropTarget(ItemTypes.CARD, cardTarget, connect => ({ 103 | connectDropTarget: connect.dropTarget(), 104 | }))(Card); 105 | return DragSource(ItemTypes.CARD, cardSource, (connect, monitor) => ({ 106 | connectDragSource: connect.dragSource(), 107 | isDragging: monitor.isDragging(), 108 | }))(x); 109 | } 110 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import DemoBar from './demobar'; 4 | import FormBuilder from './src/index'; 5 | import * as variables from './variables'; 6 | // import { get, post} from './src/stores/requests'; 7 | // Add our stylesheets for the demo. 8 | require('./scss/application.scss'); 9 | 10 | const url = '/api/formdata'; 11 | const saveUrl = '/api/formdata'; 12 | 13 | // const content = [ 14 | // { 15 | // id: '3A06600E-D7E1-44FD-AA0C-BFB8AB61B9F1', 16 | // element: 'Dropdown', 17 | // text: 'Dropdown', 18 | // required: false, 19 | // canHaveAnswer: true, 20 | // field_name: 'dropdown_38716F53-51AA-4A53-9A9B-367603D82548', 21 | // label: '
    Dropdown
    \n', 22 | // options: [ 23 | // { 24 | // value: 'd1', 25 | // text: '1', 26 | // key: 'dropdown_option_39F17D90-322B-4E23-8CD6-4D7AD58C4DD0', 27 | // }, 28 | // { 29 | // value: 'd2', 30 | // text: '2', 31 | // key: 'dropdown_option_C3BB35B7-6335-4704-BD03-1228D7C33EAE', 32 | // }, 33 | // { 34 | // value: 'd3', 35 | // text: '3', 36 | // key: 'dropdown_option_31C5C3A9-59B3-4CD5-B997-3754C6B05353', 37 | // }, 38 | // ], 39 | // }, 40 | // { 41 | // id: '7C8F465D-C09C-42CF-8563-EEF26635382F', 42 | // element: 'Checkboxes', 43 | // text: 'Checkboxes', 44 | // required: false, 45 | // canHaveAnswer: true, 46 | // field_name: 'checkboxes_8D6BDC45-76A3-4157-9D62-94B6B24BB833', 47 | // label: '
    check box
    \n', 48 | // options: [ 49 | // { 50 | // value: 'c1', 51 | // text: '1', 52 | // key: 'checkboxes_option_8657F4A6-AA5A-41E2-A44A-3E4F43BFC4A6', 53 | // }, 54 | // { 55 | // value: 'c2', 56 | // text: '2', 57 | // key: 'checkboxes_option_1D674F07-9E9F-4143-9D9C-D002B29BA9E4', 58 | // }, 59 | // { 60 | // value: 'c3', 61 | // text: '3', 62 | // key: 'checkboxes_option_6D097591-E445-4BB4-8474-03BFDAA06BFC', 63 | // }, 64 | // ], 65 | // }, 66 | // { 67 | // id: '850B1CE9-E8D8-47CA-A770-25496EECC000', 68 | // element: 'RadioButtons', 69 | // text: 'Multiple Choice', 70 | // required: false, 71 | // canHaveAnswer: true, 72 | // field_name: 'radio_buttons_F79ACC6B-7EBA-429E-870C-124F4F0DA90B', 73 | // label: '
    radio
    \n', 74 | // options: [ 75 | // { 76 | // value: 'r1', 77 | // text: '1', 78 | // key: 'radiobuttons_option_D3277CC8-FDB2-4CEB-AE83-C126492062B6', 79 | // }, 80 | // { 81 | // value: 'r2', 82 | // text: '2', 83 | // key: 'radiobuttons_option_553B2710-AD7C-46B4-9F47-B2BD5942E0C7', 84 | // }, 85 | // { 86 | // value: 'r3', 87 | // text: '3', 88 | // key: 'radiobuttons_option_08175D04-FF32-4FFB-9210-630AA155573E', 89 | // }, 90 | // ], 91 | // }, 92 | // { 93 | // id: '34611241-27CF-4D0A-9B8D-6F84024D6876', 94 | // element: 'Rating', 95 | // text: 'Rating', 96 | // required: false, 97 | // canHaveAnswer: true, 98 | // field_name: 'rating_3B3491B3-71AC-4A68-AB8C-A2B5009346CB', 99 | // label: '
    star
    \n', 100 | // }, 101 | // ]; 102 | 103 | // const onLoad = function() { 104 | // console.log('onLoad'); 105 | // return get(url); 106 | // } 107 | 108 | // const onPost = function(data) { 109 | // console.log('onPost', data); 110 | // post(saveUrl, data); 111 | // } 112 | 113 | // const items = [ 114 | // { 115 | // key: 'TextInput', 116 | // canHaveAnswer: true, 117 | // canHaveAlternateForm: false, 118 | // name: 'Text Input', 119 | // label: 'Placeholder Label', 120 | // icon: 'fa fa-font', 121 | // field_name: 'text_input_', 122 | // }, 123 | // { 124 | // key: 'Dropdown', 125 | // canHaveAnswer: true, 126 | // name: 'Dropdown', 127 | // icon: 'fa fa-caret-square-o-down', 128 | // label: 'Placeholder Label', 129 | // field_name: 'dropdown_', 130 | // options: [], 131 | // }, 132 | // { 133 | // key: 'RadioButtons', 134 | // canHaveOptionValue: false, 135 | // name: 'Multiple Choice', 136 | // icon: 'fa fa-dot-circle-o', 137 | // label: 'Placeholder Label', 138 | // field_name: 'radiobuttons_', 139 | // options: [], 140 | // }, 141 | // ]; 142 | 143 | ReactDOM.render( 144 | , 148 | document.getElementById('form-builder'), 149 | ); 150 | 151 | // ReactDOM.render( 152 | // , 156 | // document.getElementById('form-builder') 157 | // ) 158 | 159 | ReactDOM.render( 160 | , 161 | document.getElementById('demo-bar'), 162 | ); 163 | -------------------------------------------------------------------------------- /src/preview.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | 5 | import React from 'react'; 6 | import update from 'immutability-helper'; 7 | import store from './stores/store'; 8 | import FormElementsEdit from './form-elements-edit'; 9 | import SortableFormElements from './sortable-form-elements'; 10 | 11 | const { PlaceHolder } = SortableFormElements; 12 | 13 | export default class Preview extends React.Component { 14 | constructor(props) { 15 | super(props); 16 | 17 | const { onLoad, onPost } = props; 18 | store.setExternalHandler(onLoad, onPost); 19 | 20 | this.editForm = React.createRef(); 21 | this.state = { 22 | data: [], 23 | answer_data: {}, 24 | }; 25 | 26 | const onUpdate = this._onChange.bind(this); 27 | store.subscribe(state => onUpdate(state.data)); 28 | 29 | this.moveCard = this.moveCard.bind(this); 30 | this.insertCard = this.insertCard.bind(this); 31 | } 32 | 33 | componentWillReceiveProps(nextProps) { 34 | if (this.props.data !== nextProps.data) { 35 | store.dispatch('updateOrder', nextProps.data); 36 | } 37 | } 38 | 39 | componentDidMount() { 40 | const { data, url, saveUrl } = this.props; 41 | store.dispatch('load', { loadUrl: url, saveUrl, data: data || [] }); 42 | document.addEventListener('mousedown', this.editModeOff); 43 | } 44 | 45 | componentWillUnmount() { 46 | document.removeEventListener('mousedown', this.editModeOff); 47 | } 48 | 49 | editModeOff = (e) => { 50 | if (this.editForm.current && !this.editForm.current.contains(e.target)) { 51 | this.manualEditModeOff(); 52 | } 53 | } 54 | 55 | manualEditModeOff = () => { 56 | const { editElement } = this.props; 57 | if (editElement && editElement.dirty) { 58 | editElement.dirty = false; 59 | this.updateElement(editElement); 60 | } 61 | this.props.manualEditModeOff(); 62 | } 63 | 64 | _setValue(text) { 65 | return text.replace(/[^A-Z0-9]+/ig, '_').toLowerCase(); 66 | } 67 | 68 | updateElement(element) { 69 | const { data } = this.state; 70 | let found = false; 71 | 72 | for (let i = 0, len = data.length; i < len; i++) { 73 | if (element.id === data[i].id) { 74 | data[i] = element; 75 | found = true; 76 | break; 77 | } 78 | } 79 | 80 | if (found) { 81 | store.dispatch('updateOrder', data); 82 | } 83 | } 84 | 85 | _onChange(data) { 86 | const answer_data = {}; 87 | 88 | data.forEach((item) => { 89 | if (item && item.readOnly && this.props.variables[item.variableKey]) { 90 | answer_data[item.field_name] = this.props.variables[item.variableKey]; 91 | } 92 | }); 93 | 94 | this.setState({ 95 | data, 96 | answer_data, 97 | }); 98 | } 99 | 100 | _onDestroy(item) { 101 | store.dispatch('delete', item); 102 | } 103 | 104 | insertCard(item, hoverIndex) { 105 | const { data } = this.state; 106 | data.splice(hoverIndex, 0, item); 107 | this.saveData(item, hoverIndex, hoverIndex); 108 | } 109 | 110 | moveCard(dragIndex, hoverIndex) { 111 | const { data } = this.state; 112 | const dragCard = data[dragIndex]; 113 | this.saveData(dragCard, dragIndex, hoverIndex); 114 | } 115 | 116 | // eslint-disable-next-line no-unused-vars 117 | cardPlaceHolder(dragIndex, hoverIndex) { 118 | // Dummy 119 | } 120 | 121 | saveData(dragCard, dragIndex, hoverIndex) { 122 | const newData = update(this.state, { 123 | data: { 124 | $splice: [[dragIndex, 1], [hoverIndex, 0, dragCard]], 125 | }, 126 | }); 127 | this.setState(newData); 128 | store.dispatch('updateOrder', newData.data); 129 | } 130 | 131 | getElement(item, index) { 132 | const SortableFormElement = SortableFormElements[item.element]; 133 | return ; 134 | } 135 | 136 | render() { 137 | let classes = this.props.className; 138 | if (this.props.editMode) { classes += ' is-editing'; } 139 | const data = this.state.data.filter(x => !!x); 140 | const items = data.map((item, index) => this.getElement(item, index)); 141 | return ( 142 |
    143 |
    144 | { this.props.editElement !== null && 145 | 146 | } 147 |
    148 |
    {items}
    149 | 150 |
    151 | ); 152 | } 153 | } 154 | Preview.defaultProps = { 155 | showCorrectColumn: false, files: [], editMode: false, editElement: null, className: 'react-form-builder-preview pull-left', 156 | }; 157 | -------------------------------------------------------------------------------- /examples/demo/demobar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ReactFormGenerator, ElementStore } from 'react-form-builder2'; 3 | 4 | export default class Demobar extends React.Component { 5 | constructor(props) { 6 | super(props); 7 | this.state = { 8 | data: [], 9 | previewVisible: false, 10 | shortPreviewVisible: false, 11 | roPreviewVisible: false, 12 | }; 13 | 14 | const update = this._onChange.bind(this); 15 | ElementStore.subscribe(state => update(state.data)); 16 | } 17 | 18 | showPreview() { 19 | this.setState({ 20 | previewVisible: true, 21 | }); 22 | } 23 | 24 | showShortPreview() { 25 | this.setState({ 26 | shortPreviewVisible: true, 27 | }); 28 | } 29 | 30 | showRoPreview() { 31 | this.setState({ 32 | roPreviewVisible: true, 33 | }); 34 | } 35 | 36 | closePreview() { 37 | this.setState({ 38 | previewVisible: false, 39 | shortPreviewVisible: false, 40 | roPreviewVisible: false, 41 | }); 42 | } 43 | 44 | _onChange(data) { 45 | this.setState({ 46 | data, 47 | }); 48 | } 49 | 50 | render() { 51 | let modalClass = 'modal'; 52 | if (this.state.previewVisible) { 53 | modalClass += ' show'; 54 | } 55 | 56 | let shortModalClass = 'modal short-modal'; 57 | if (this.state.shortPreviewVisible) { 58 | shortModalClass += ' show'; 59 | } 60 | 61 | let roModalClass = 'modal ro-modal'; 62 | if (this.state.roPreviewVisible) { 63 | roModalClass += ' show'; 64 | } 65 | 66 | return ( 67 |
    68 |

    Preview

    69 | 70 | 71 | 72 | 73 | { this.state.previewVisible && 74 |
    75 |
    76 |
    77 | 87 | 88 |
    89 | 90 |
    91 |
    92 |
    93 |
    94 | } 95 | 96 | { this.state.roPreviewVisible && 97 |
    98 |
    99 |
    100 | 111 | 112 |
    113 | 114 |
    115 |
    116 |
    117 |
    118 | } 119 | 120 | { this.state.shortPreviewVisible && 121 |
    122 |
    123 |
    124 | 134 | 135 |
    136 | 137 |
    138 |
    139 |
    140 |
    141 | } 142 |
    143 | ); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /examples/custom/demobar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ReactFormGenerator, ElementStore } from 'react-form-builder2'; 3 | 4 | export default class Demobar extends React.Component { 5 | constructor(props) { 6 | super(props); 7 | this.state = { 8 | data: [], 9 | previewVisible: false, 10 | shortPreviewVisible: false, 11 | roPreviewVisible: false, 12 | }; 13 | 14 | const update = this._onChange.bind(this); 15 | ElementStore.subscribe(state => update(state.data)); 16 | } 17 | 18 | showPreview() { 19 | this.setState({ 20 | previewVisible: true, 21 | }); 22 | } 23 | 24 | showShortPreview() { 25 | this.setState({ 26 | shortPreviewVisible: true, 27 | }); 28 | } 29 | 30 | showRoPreview() { 31 | this.setState({ 32 | roPreviewVisible: true, 33 | }); 34 | } 35 | 36 | closePreview() { 37 | this.setState({ 38 | previewVisible: false, 39 | shortPreviewVisible: false, 40 | roPreviewVisible: false, 41 | }); 42 | } 43 | 44 | _onChange(data) { 45 | this.setState({ 46 | data, 47 | }); 48 | } 49 | 50 | _onSubmit(data) { 51 | console.log('onSubmit', data); 52 | // Place code to post json data to server here 53 | } 54 | 55 | render() { 56 | let modalClass = 'modal'; 57 | if (this.state.previewVisible) { 58 | modalClass += ' show'; 59 | } 60 | 61 | let shortModalClass = 'modal short-modal'; 62 | if (this.state.shortPreviewVisible) { 63 | shortModalClass += ' show'; 64 | } 65 | 66 | let roModalClass = 'modal ro-modal'; 67 | if (this.state.roPreviewVisible) { 68 | roModalClass += ' show'; 69 | } 70 | 71 | return ( 72 |
    73 |

    Preview

    74 | 75 | 76 | 77 | 78 | { this.state.previewVisible && 79 |
    80 |
    81 |
    82 | 93 | 94 |
    95 | 96 |
    97 |
    98 |
    99 |
    100 | } 101 | 102 | { this.state.roPreviewVisible && 103 |
    104 |
    105 |
    106 | 117 | 118 |
    119 | 120 |
    121 |
    122 |
    123 |
    124 | } 125 | 126 | { this.state.shortPreviewVisible && 127 |
    128 |
    129 |
    130 | 140 | 141 |
    142 | 143 |
    144 |
    145 |
    146 |
    147 | } 148 |
    149 | ); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/dynamic-option-list.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | 5 | import React from 'react'; 6 | import ID from './UUID'; 7 | 8 | export default class DynamicOptionList extends React.Component { 9 | constructor(props) { 10 | super(props); 11 | this.state = { 12 | element: this.props.element, 13 | data: this.props.data, 14 | dirty: false, 15 | }; 16 | } 17 | 18 | _setValue(text) { 19 | return text.replace(/[^A-Z0-9]+/ig, '_').toLowerCase(); 20 | } 21 | 22 | editOption(option_index, e) { 23 | const this_element = this.state.element; 24 | const val = (this_element.options[option_index].value !== this._setValue(this_element.options[option_index].text)) ? this_element.options[option_index].value : this._setValue(e.target.value); 25 | 26 | this_element.options[option_index].text = e.target.value; 27 | this_element.options[option_index].value = val; 28 | this.setState({ 29 | element: this_element, 30 | dirty: true, 31 | }); 32 | } 33 | 34 | editValue(option_index, e) { 35 | const this_element = this.state.element; 36 | const val = (e.target.value === '') ? this._setValue(this_element.options[option_index].text) : e.target.value; 37 | this_element.options[option_index].value = val; 38 | this.setState({ 39 | element: this_element, 40 | dirty: true, 41 | }); 42 | } 43 | 44 | // eslint-disable-next-line no-unused-vars 45 | editOptionCorrect(option_index, e) { 46 | const this_element = this.state.element; 47 | if (this_element.options[option_index].hasOwnProperty('correct')) { 48 | delete (this_element.options[option_index].correct); 49 | } else { 50 | this_element.options[option_index].correct = true; 51 | } 52 | this.setState({ element: this_element }); 53 | this.props.updateElement.call(this.props.preview, this_element); 54 | } 55 | 56 | updateOption() { 57 | const this_element = this.state.element; 58 | // to prevent ajax calls with no change 59 | if (this.state.dirty) { 60 | this.props.updateElement.call(this.props.preview, this_element); 61 | this.setState({ dirty: false }); 62 | } 63 | } 64 | 65 | addOption(index) { 66 | const this_element = this.state.element; 67 | this_element.options.splice(index + 1, 0, { value: '', text: '', key: ID.uuid() }); 68 | this.props.updateElement.call(this.props.preview, this_element); 69 | } 70 | 71 | removeOption(index) { 72 | const this_element = this.state.element; 73 | this_element.options.splice(index, 1); 74 | this.props.updateElement.call(this.props.preview, this_element); 75 | } 76 | 77 | render() { 78 | if (this.state.dirty) { 79 | this.state.element.dirty = true; 80 | } 81 | return ( 82 |
    83 |
      84 |
    • 85 |
      86 |
      Options
      87 | { this.props.canHaveOptionValue && 88 |
      Value
      } 89 | { this.props.canHaveOptionValue && this.props.canHaveOptionCorrect && 90 |
      Correct
      } 91 |
      92 |
    • 93 | { 94 | this.props.element.options.map((option, index) => { 95 | const this_key = `edit_${option.key}`; 96 | const val = (option.value !== this._setValue(option.text)) ? option.value : ''; 97 | return ( 98 |
    • 99 |
      100 |
      101 | 102 |
      103 | { this.props.canHaveOptionValue && 104 |
      105 | 106 |
      } 107 | { this.props.canHaveOptionValue && this.props.canHaveOptionCorrect && 108 |
      109 | 110 |
      } 111 |
      112 |
      113 | 114 | { index > 0 115 | && 116 | } 117 |
      118 |
      119 |
      120 |
    • 121 | ); 122 | }) 123 | } 124 |
    125 |
    126 | ); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /server/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React Form Builder 6 | 7 | 8 | 9 | 20 | 21 | 22 | 26 | 27 |
    28 |

    Preview

    29 | 30 | 42 |
    43 |
    44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 147 | 148 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![npm version](https://badge.fury.io/js/react-form-builder2.svg)](//npmjs.com/package/react-form-builder2) 2 | # React Form Builder 2 3 | A complete react form builder that interfaces with a json endpoint to load and save generated forms. 4 | - Upgraded to React 16.4.1 5 | - Use react-dnd for Drag & Drop 6 | - Save form data with dummy api server 7 | - Show posted data on readonly form 8 | 9 | [DEMO](https://kiho.github.io/react-form-builder/) Slow Loading.... back-end is running at FREE Heroku. 10 | 11 | ![](screenshot.png) 12 | 13 | ### Editing Items 14 | ![](screenshot2.png) 15 | 16 | # Basic Usage 17 | 18 | ```javascript 19 | var React = require('react'); 20 | var FormBuilder = require('react-form-builder2'); 21 | 22 | React.render( 23 | , 24 | document.body 25 | ) 26 | ``` 27 | 28 | # Props 29 | 30 | ```javascript 31 | var items = [{ 32 | key: 'Header', 33 | name: 'Header Text', 34 | icon: 'fa fa-header', 35 | static: true, 36 | content: 'Placeholder Text...' 37 | }, 38 | { 39 | key: 'Paragraph', 40 | name: 'Paragraph', 41 | static: true, 42 | icon: 'fa fa-paragraph', 43 | content: 'Placeholder Text...' 44 | }]; 45 | 46 | 50 | ``` 51 | 52 | # React Form Generator 53 | Now that a form is built and saved, let's generate it from the saved json. 54 | 55 | ```javascript 56 | var React = require('react'); 57 | var FormBuilder = require('react-form-builder2'); 58 | 59 | React.render( 60 |