├── .babelrc
├── .gitignore
├── .npmrc
├── LICENSE
├── README.md
├── aToB.PNG
├── composition.PNG
├── generateForm.PNG
├── index.html
├── package.json
├── processInputs.PNG
├── processLists.PNG
├── src
├── Example.jsx
├── data
│ └── cities.json
├── generator
│ ├── Lookup_Component.jsx
│ └── helpers.js
├── index.js
├── routes.jsx
├── schema.json
├── styles.css
└── transformers.js
├── webpack.config.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-env",
4 | "@babel/preset-react"
5 | ],
6 | "plugins": []
7 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | save-exact=true
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Dipen Bagia
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # declarative-form-generator
2 | A simple react form generator using functional programming concepts.
3 | An explanation of how it works is being drafted!
4 |
5 | ## How to run
6 |
7 | 1. `git clone https://github.com/dbagia/declarative-form-generator.git`
8 | 2. `yarn`
9 | 3. `yarn start`
10 | 4. `Go to http://localhost:8080`
11 |
12 | You should see a form rendered in the browser with some fields. Keying in any text or changing the drop-down value should log the entered data in the console.
13 |
14 | ## How does it work?
15 | The core idea behind the form generator is to use a schema to render form in the UI.
16 |
17 | The form schema is a JSON structure, an array of objects, each representing an input field on the form.
18 |
19 | The generator takes this JSON structure as an input and returns a list of React Components, each corresponding to the object in the JSON structure at that position.
20 |
21 | ## The Concept
22 | The generator has been built using a simple concept from Category Theory in order to render the form from an array of input fields.
23 |
24 | Below is an example of form schema:
25 |
26 | ```
27 | [
28 | {
29 | "type": "text",
30 | "label": "First Name",
31 | "required": true,
32 | "placeholder": "first name",
33 | "readOnly": false,
34 | "name": "fname"
35 | },
36 | {
37 | "type": "list",
38 | "lookupId": "id",
39 | "lookupDisplay": "name",
40 | "lookupUrl": "http://localhost:8080/cities",
41 | "defaultValue": 2,
42 | "readOnly": false,
43 | "required": true,
44 | "label": "City",
45 | "name": "city",
46 | "placeholder": "city"
47 | },
48 | {
49 | "type": "text",
50 | "label": "Last Name",
51 | "required": true,
52 | "placeholder": "last name",
53 | "defaultValue": "x",
54 | "readOnly": false,
55 | "name": "lname"
56 | }
57 | ]
58 | ```
59 |
60 | The responsibility of the generator is to transform the above form schema into an array of ```React/JSX``` elements like so:
61 |
62 | ```
63 | [
64 |
65 |
66 |
73 | />
74 |
,
75 | ...
76 | ]
77 | ```
78 | The process of this transformation has been developed using ```composition```, ```currying``` and ```Monads``` by using [Crocks Library](https://github.com/evilsoft/crocks).
79 |
80 | ## A bit of Category Theory
81 |
82 | This section explains the above transformation process using diagrams.
83 |
84 | Let's first draw the final diagram and then we will zoom into the details.
85 |
86 | 
87 |
88 | This diagram is depicting two sets A and B and a map *```f```* which performs the transformation from A to B. Set A is the list of JSON objects, the schema of the form and Set B is the list of React Elements.
89 |
90 | The map *```f```* is however a composition and we can expand this diagram to describe the actual design of this transformation.
91 |
92 | Starting from the form schema, as stated, it is a list of input fields. We can define transformations for individual field types (text, select, radio, checkbox etc) and compose all of them together to get to our final *```f```*.
93 |
94 | Let's take the input text example.
95 |
96 | ### Transforming text types
97 |
98 | We want to transform all the fields in the form schema which are of type ```text```. So after the transformation, we should have a set consisting of all the fields in form schema with fields with type ```text``` transformed into it's JSX equivalents. Let's call this set M.
99 |
100 | 
101 |
102 | In the above diagram, we have two sets ```A``` and ```M``` and a map *```textInputs```* which performs the trainsition from set ```A``` to set ```M```.
103 |
104 | Notice that the map *```textInputs```* is only transforming those elements in set ```A``` whose type is ```text```. It is not transorming any other types. This has been indicated by black arrows (performing transformation) and red arrows (bypassing without transforming) in the diagram.
105 |
106 | ### Transforming list types
107 |
108 | Next, we want to transform all the fields in the form schema which are of type ```list``` to JSX ```select``` elements. We have a map called *```lists```* to do this.
109 |
110 | 
111 |
112 | As you might have noticed, our map *```lists```* does not perform any transformations on ```input``` JSX elements.
113 |
114 | ### Transforming other types
115 |
116 | We can continue creating additional maps for other types like radio buttons, checkbox, file upload etc and continue to perform transformations on our set.
117 |
118 | ### Putting it all together
119 |
120 | Below is how a final transformation will look like starting from Set A to Set B.
121 |
122 | 
123 |
124 | The laws of Category Theory allow us to combine more than one maps through ```composition```. Hence, if we use composition on the above diagram, we can combine the maps *```textInputs```* and *```lists```* into a single map like so:
125 |
126 | 
127 |
128 | The above diagram is the same as we started with: (map *```f```* ).
129 |
130 | ## Thinking Declaratively
131 |
132 | I have spent a lot of time trying to figure this out. The most useful resource I have found so far is Kevlin Henney's [talk](https://www.youtube.com/watch?v=NSzsYWckGd4) on declarative thinking.
133 |
134 | In a declarative/functional paradigm, you tell the computer what you need rather than how you want the computer to get it. The "how" part is left for the computer to decide.
135 |
136 | But how can we actually describe the "what" part in programming?
137 |
138 | **Describe the properties of the domain/problem**
139 |
140 | For instance, consider the problem of [balanced brackets](https://www.hackerrank.com/challenges/balanced-brackets/problem). One of the imperative ways of solving this problem is using Stack Data Structure.
141 |
142 | Using the declarative approach, the properties of this problem are as below:
143 |
144 | 1. The input is a string of variable length and only allows {, [, (, }, ] and )
145 |
146 | 2. If the input length is odd, it is unbalanced (it is just not possible to have balanced brackets with odd number of characters)
147 |
148 | 3. If the input length is even, then for every opening brace of type (, { or [ there is an equivalent closing brace at a distance double the length of other opening braces after the current opening brace
149 |
150 | You can have a look at the declarative solution [here](https://github.com/dbagia/declarative-demos/tree/master/demos/balanced-brackets)
151 |
152 | ## Container Style Programming
153 | ### ...
154 |
155 | ## References
156 |
157 | * [Prof Frisby's Mostly Adequate Guide to Functional Programming](https://drboolean.gitbooks.io/mostly-adequate-guide/)
158 | * [Declarative Thinking](https://www.youtube.com/watch?v=NSzsYWckGd4)
159 | * [Crocks Library](https://evilsoft.github.io/crocks/docs/crocks/)
160 |
--------------------------------------------------------------------------------
/aToB.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dbagia/declarative-form-generator/7eaf35d246596f9e10e206dbd4dec848b30424f0/aToB.PNG
--------------------------------------------------------------------------------
/composition.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dbagia/declarative-form-generator/7eaf35d246596f9e10e206dbd4dec848b30424f0/composition.PNG
--------------------------------------------------------------------------------
/generateForm.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dbagia/declarative-form-generator/7eaf35d246596f9e10e206dbd4dec848b30424f0/generateForm.PNG
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 | Declarative Form Generator
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "declarative-form-generator",
3 | "version": "1.0.0",
4 | "description": "A simple react form generator using functional programming concepts",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "webpack-dev-server --hot --colors",
8 | "build": "webpack",
9 | "test": "standard && jest"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/dbagia/declarative-form-generator.git"
14 | },
15 | "keywords": [
16 | "declarative",
17 | "react"
18 | ],
19 | "author": "Dipen Bagia (dipen.bagia@outlook.com)",
20 | "license": "Apache-2.0",
21 | "bugs": {
22 | "url": "https://github.com/dbagia/declarative-form-generator/issues"
23 | },
24 | "homepage": "https://github.com/dbagia/declarative-form-generator#readme",
25 | "dependencies": {
26 | "axios": "0.21.1",
27 | "basscss": "8.1.0",
28 | "crocks": "0.12.4",
29 | "ramda": "0.27.0",
30 | "react": "16.13.0",
31 | "react-dom": "16.13.0",
32 | "react-router-dom": "5.1.2"
33 | },
34 | "devDependencies": {
35 | "@babel/preset-env": "7.8.7",
36 | "@babel/preset-react": "7.8.3",
37 | "babel-jest": "25.1.0",
38 | "babel-loader": "8.0.6",
39 | "css-loader": "3.4.2",
40 | "jest": "25.1.0",
41 | "react-test-renderer": "16.13.0",
42 | "standard": "14.3.3",
43 | "style-loader": "1.1.3",
44 | "webpack": "4.42.0",
45 | "webpack-cli": "3.3.11",
46 | "webpack-dev-server": "3.10.3"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/processInputs.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dbagia/declarative-form-generator/7eaf35d246596f9e10e206dbd4dec848b30424f0/processInputs.PNG
--------------------------------------------------------------------------------
/processLists.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dbagia/declarative-form-generator/7eaf35d246596f9e10e206dbd4dec848b30424f0/processLists.PNG
--------------------------------------------------------------------------------
/src/Example.jsx:
--------------------------------------------------------------------------------
1 | import schema from './schema.json'
2 | import List from 'crocks/List'
3 | import when from 'crocks/logic/when'
4 | import compose from 'crocks/helpers/compose'
5 | import {isSchemaItemOfType} from './generator/helpers'
6 | import { listToReact, textToReact, addChangeListener } from './transformers'
7 |
8 | // textInputs:: (Pred -> ())
9 | const textInputs =
10 | when(isSchemaItemOfType('text'), textToReact)
11 |
12 | // lists:: (Pred -> ())
13 | const lists =
14 | when(isSchemaItemOfType('list'), listToReact)
15 |
16 | // transform:: a -> JSX
17 | const transform =
18 | compose(lists, textInputs, addChangeListener({}))
19 |
20 | const Example =
21 | List
22 | .fromArray(schema)
23 | .map(transform)
24 |
25 | export default Example
26 |
--------------------------------------------------------------------------------
/src/data/cities.json:
--------------------------------------------------------------------------------
1 | [{"id":"CTY:1","name":"Mumbai"},{"id":"CTY:2","name":"Hyderabad"},{"id":"CTY:3","name":"Chennai"},{"id":"CTY:4","name":"Delhi"},{"id":"CTY:5","name":"Bangaluru"},{"id":"CTY:7","name":"Kolkatta"}]
--------------------------------------------------------------------------------
/src/generator/Lookup_Component.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react'
2 | import { getData, toOptions } from './helpers'
3 |
4 | export default function LookupComponent(props) {
5 | const [options, setOptions] = useState([])
6 |
7 | useEffect(() => {
8 | getData(props.field.lookupUrl)
9 | .map(r => r.data)
10 | .map(toOptions)
11 | .fork(
12 | err => console.log('err', err), setOptions
13 | )
14 | })
15 |
16 | return (
17 |