├── .babelrc
├── .circleci
└── config.yml
├── .eslintrc
├── .gitignore
├── .vscode
└── settings.json
├── Changelog.md
├── LICENSE
├── README.md
├── circle.yml
├── formio.css
├── package-lock.json
├── package.json
├── src
├── components
│ ├── FormBuilder.js
│ ├── FormComponents
│ │ ├── button
│ │ │ └── Button.js
│ │ ├── columns
│ │ │ ├── Columns.js
│ │ │ └── styles.js
│ │ ├── componentUtils
│ │ │ ├── getDefaultValue.js
│ │ │ ├── safeSingleToMultiple.js
│ │ │ └── validators.js
│ │ ├── content
│ │ │ ├── Content.js
│ │ │ └── styles.js
│ │ ├── datetime
│ │ │ ├── Datetime.js
│ │ │ └── styles.js
│ │ ├── day
│ │ │ └── Day.js
│ │ ├── fieldset
│ │ │ ├── Fieldset.js
│ │ │ └── styles.js
│ │ ├── h2
│ │ │ ├── H2.js
│ │ │ └── styles.js
│ │ ├── h3
│ │ │ ├── H3.js
│ │ │ └── styles.js
│ │ ├── htmlelement
│ │ │ ├── HtmlElement.js
│ │ │ └── styles.js
│ │ ├── index.js
│ │ ├── panel
│ │ │ ├── Panel.js
│ │ │ └── styles.js
│ │ ├── radio
│ │ │ ├── Radio.js
│ │ │ └── styles.js
│ │ ├── select
│ │ │ ├── Select.js
│ │ │ └── styles.js
│ │ ├── selectboxes
│ │ │ └── SelectBoxes.js
│ │ ├── sharedComponents
│ │ │ ├── Base.js
│ │ │ ├── Input.js
│ │ │ ├── Multi.js
│ │ │ ├── Select.js
│ │ │ ├── Tooltip.js
│ │ │ └── Value.js
│ │ ├── signature
│ │ │ ├── Signature.js
│ │ │ └── styles.js
│ │ ├── switch
│ │ │ └── Switch.js
│ │ ├── tests
│ │ │ ├── Columns.spec.js
│ │ │ ├── Content.spec.js
│ │ │ ├── Fieldset.spec.js
│ │ │ ├── H2.spec.js
│ │ │ ├── H3.spec.js
│ │ │ ├── Panel.spec.js
│ │ │ ├── Radio.spec.js
│ │ │ ├── Select.spec.js
│ │ │ ├── SelectBoxes.spec.js
│ │ │ ├── Signature.spec.js
│ │ │ ├── Switch.spec.js
│ │ │ ├── TextField.spec.js
│ │ │ ├── Textarea.spec.js
│ │ │ └── __snapshots__
│ │ │ │ ├── Content.spec.js.snap
│ │ │ │ ├── H2.spec.js.snap
│ │ │ │ ├── H3.spec.js.snap
│ │ │ │ ├── Radio.spec.js.snap
│ │ │ │ ├── Select.spec.js.snap
│ │ │ │ ├── SelectBoxes.spec.js.snap
│ │ │ │ ├── Switch.spec.js.snap
│ │ │ │ ├── TextField.spec.js.snap
│ │ │ │ └── Textarea.spec.js.snap
│ │ ├── textarea
│ │ │ ├── Textarea.js
│ │ │ └── styles.js
│ │ └── textfield
│ │ │ └── TextField.js
│ ├── Formio.js
│ ├── FormioComponentsList.js
│ ├── FormioConfirm.js
│ ├── FormioGrid.js
│ └── index.js
├── defaultTheme
│ ├── colors.js
│ └── index.js
├── factories
│ ├── FormioComponents.js
│ └── index.js
├── formio
│ ├── index.js
│ ├── providers
│ │ ├── index.js
│ │ └── storage
│ │ │ ├── base64.js
│ │ │ ├── dropbox.js
│ │ │ ├── index.js
│ │ │ ├── s3.js
│ │ │ └── url.js
│ └── utils
│ │ ├── builder.js
│ │ ├── fixtures
│ │ ├── components.json
│ │ ├── components2.json
│ │ ├── components3.json
│ │ └── submission1.json
│ │ ├── index.js
│ │ ├── jsonlogic
│ │ ├── operators.js
│ │ └── operators.spec.js
│ │ ├── utils.js
│ │ └── utils.spec.js
├── index.js
└── util
│ ├── constants.js
│ └── index.js
├── test
├── .setup.js
├── __snapshots__
│ └── changes.spec.js.snap
├── changes.spec.js
├── forms
│ ├── componentSpec.js
│ ├── empty.json
│ └── tmp.json
├── submissions-wip.js
└── validations.spec.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["react-native"]
3 | }
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | # Javascript Node CircleCI 2.0 configuration file
2 | #
3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details
4 | #
5 | version: 2
6 | jobs:
7 | build:
8 | docker:
9 | # specify the version you desire here
10 | - image: circleci/node:7.10
11 |
12 | # Specify service dependencies here if necessary
13 | # CircleCI maintains a library of pre-built images
14 | # documented at https://circleci.com/docs/2.0/circleci-images/
15 | # - image: circleci/mongo:3.4.4
16 |
17 | working_directory: ~/repo
18 |
19 | steps:
20 | - checkout
21 |
22 | # Download and cache dependencies
23 | - restore_cache:
24 | keys:
25 | - v1-dependencies-{{ checksum "package.json" }}
26 | # fallback to using the latest cache if no exact match is found
27 | - v1-dependencies-
28 |
29 | - run: yarn install
30 |
31 | - save_cache:
32 | paths:
33 | - node_modules
34 | key: v1-dependencies-{{ checksum "package.json" }}
35 |
36 | # run tests!
37 | - run: yarn test
38 |
39 |
40 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "parser": "babel-eslint",
4 | "parserOptions": {
5 | "sourceType": "module",
6 | "ecmaVersion": 2017
7 | },
8 | "rules": {
9 | "max-len": 0,
10 | "max-depth": [1, 5],
11 | "max-statements": 0,
12 | "no-unused-vars": 0
13 | },
14 | "env": {
15 | "es6": true,
16 | "jest": true,
17 | "node": true
18 | },
19 | "plugins": [
20 | "react",
21 | "react-native"
22 | ],
23 | "globals": {
24 | "require": true,
25 | "module": true,
26 | "global": true,
27 | "fetch": true
28 | },
29 | "extends": [
30 | "eslint-config-formio",
31 | "plugin:react/recommended",
32 | "plugin:react-native/all"
33 | ]
34 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .bower.json
2 | *.log
3 | node_modules
4 | bower_components
5 | .DS_Store
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | }
--------------------------------------------------------------------------------
/Changelog.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 | All notable changes to this project will be documented in this file
3 |
4 | The format is based on [Keep a Changelog](http://keepachangelog.com/)
5 | and this project adheres to [Semantic Versioning](http://semver.org/).
6 |
7 | ## 1.5.4
8 | ### Changed
9 | - Editgrid rows close now removes new rows but just close existing rows.
10 | - Changed events when editing and saving rows.
11 |
12 | ## 1.5.3
13 | ### Fixed
14 | - Datagrid not always refreshing when a component is hidden.
15 |
16 | ## 1.5.2
17 | ### Fixed
18 | - Stop signature clearing on resize.
19 |
20 | ## 1.5.1
21 | ### Fixed
22 | - Allow select component to set headers.
23 |
24 | ## 1.5.0
25 | ### Added
26 | - Number and currency components can be limited to a certain number of decimals.
27 |
28 | ## 1.4.26
29 | ### Fixed
30 | - Crash in signature component when in read only mode.
31 |
32 | ## 1.4.25
33 | ### Fixed
34 | - Nesting datagrids passing parameters wrong causing remove rows to call on wrong datagrid.
35 |
36 | ## 1.4.24
37 | ### Fixed
38 | - Pass input variable to select custom values so that manual filtering can be done.
39 |
40 | ## 1.4.23
41 | ### Fixed
42 | - Add typecheck to date parsing.
43 |
44 | ## 1.4.22
45 | ### Fixed
46 | - Date picker on android only returns the date portion which makes the rendered date appear on the day before.
47 |
48 | ## 1.4.21
49 | ### Changed
50 | - Upgrade formiojs to 2.25.4
51 |
52 | ### Fixed
53 | - Fix skipInit preventing default values being set on conditionally visible fields.
54 |
55 | ## 1.4.20
56 | ### Fixed
57 | - Fix crash of editgrid when using the default template that contains a util function.
58 | - Fix crash of editgrid when not using skipInit that would attempt to change the array to an object.
59 |
60 | ## 1.4.19
61 | ### Fixed
62 | - Day component didn't properly implement custom validations.
63 |
64 | ## 1.4.18
65 | ### Fixed
66 | - Hiding items in editgrids didn't remove their values.
67 |
68 | ## 1.4.17
69 | ### Changed
70 | - Upgrade react-widgets version to 4.x.
71 |
72 | ## 1.4.16
73 | ### Changed
74 | - Decrease animations to 10ms
75 |
76 | ## 1.4.15
77 | ### Fixed
78 | - Crash in select resources if not json due to change in 1.4.14
79 |
80 | ## 1.4.14
81 | ### Fixed
82 | - Fix performance of rendering select json components with lots of items in the json array.
83 |
84 | ## 1.4.13
85 | ### Fixed
86 | - Select components with lots of json were slow to filter. Pre-rendering the data on load speeds it up.
87 |
88 | ## 1.4.12
89 | ### Fixed
90 | - Datagrids and containers embedded within other datagrids and containers not setting values properly.
91 |
92 | ## 1.4.11
93 | ### Added
94 | - Support for column definitions.
95 |
96 | ## 1.4.10
97 | ### Fixed
98 | - Crash when empty rows are deleted from datagrids.
99 |
100 | ## 1.4.9
101 | ### Fixed
102 | - Input masks not initializing validity properly.
103 |
104 | ## 1.4.8
105 | ### Fixed
106 | - Select JSON with a value property not finding item when loading in a submission.
107 |
108 | ## 1.4.7
109 | ### Fixed
110 | - Disable editgrid controls when form is read-only.
111 | - Edit grid was not fully calculating validity on form load.
112 |
113 | ## 1.4.6
114 | ### Fixed
115 | - Input mask not required for required fields.
116 |
117 | ## 1.4.5
118 | ### Fixed
119 | - Edit grid editing was showing all fields as invalid.
120 |
121 | ## 1.4.4
122 | ### Fixed
123 | - Fix issue where Select JSON fields couldn't set the value field.
124 |
125 | ## 1.4.3
126 | ### Fixed
127 | - Fix issue where components in a container that hide at the same time can't fail to clear data.
128 |
129 | ## 1.4.2
130 | ### Changed
131 | - Fire edit grid open event on componentDidMount instead of componentWillMount.
132 |
133 | ## 1.4.1
134 | ### Fixed
135 | - HTML output of editgrid header
136 |
137 | ### Added
138 | - Footer for editgrid
139 |
140 | ## 1.4.0
141 | ### Added
142 | - Time component
143 | - EditGrid component
144 |
145 | ## 1.3.14
146 | - Fix default formatting of empty custom error validation.
147 |
148 | ## 1.3.13
149 | ### Fixed
150 | - Disable datagrid buttons when form is read only.
151 | - Don't fire change events for readOnly forms.
152 |
153 | ## 1.3.12
154 | ### Added
155 | - Events that fire when select lists open or close.
156 | - Event that fires on add/remove from datagrid.
157 | - Event that fires on loadMore for selects.
158 |
159 | ## 1.3.11
160 | ### Reverted
161 | - Reverted revert of change to datagrids delete value.
162 |
163 | ### Fixed
164 | - Calculated Select values could return something other than an array which caused an error.
165 |
166 | ## 1.3.10
167 | ### Reverted
168 | - Reverted change to setting values that attempted to fix deleting rows in datagrids issue that had a lot of side effects.
169 |
170 | ### 1.3.9
171 | ### Fixed
172 | - Fix MinLength calculation for datagrids.
173 | - Fixed error about setState in select component.
174 | - Scenario where updating a form doesn't always set the values.
175 |
176 | ### Changed
177 | - Replace full lodash with individual functions.
178 |
179 | ## 1.3.8
180 | ### Fixed
181 | - Datagrids with select components dependent on external data weren't updating when the data updated.
182 |
183 | ## 1.3.7
184 | ### Changed
185 | - Datagrid headers won't render if there are no labels.
186 |
187 | ## 1.3.6
188 | ### Fixed
189 | - Deleting rows in datagrids didn't clear components properly.
190 |
191 | ## 1.3.5
192 | ### Fixed
193 | - Fix performance of datagrids with large data.
194 |
195 | ## 1.3.4
196 | ### Added
197 | - Onchange event will fire for input fields after 500ms of no typing instead of only on blur.
198 |
199 | ## 1.3.3
200 | ### Added
201 | - Expose mixins as exports to ease creation of custom components.
202 |
203 | ## 1.3.2
204 | Changed
205 | - Text inputs will fire change events on blur now instead of on change. Change events were too slow in redux.
206 |
207 | ## 1.3.1
208 | ### Fixed
209 | - Fixed tests dealing with input mask change and missing onChange events.
210 |
211 | ### Removed
212 | - Removing tests that don't work with current libraries.
213 |
214 | ## 1.3.0
215 | ### Changed
216 | - Swapped react-input-mask for react-text-mask for input masks.
217 | - Improved performance of input masks.
218 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Form.io
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 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## This repository is now considered legacy and no longer supported. Please take a look at our recent repositories and help documentation at the following links.
2 | - https://help.form.io
3 | - https://github.com/formio/formio.js
4 | - https://github.com/formio/formio
5 | - https://github.com/formio/react
6 | - https://github.com/formio/angular
7 | - https://github.com/formio/vue
8 |
9 | # React Native Formio
10 |
11 | A [React](http://facebook.github.io/react/) component for rendering out forms based on the [Form.io](https://www.form.io) platform.
12 |
13 | ## Install
14 |
15 | ### npm
16 |
17 | `React Formio` can be used on the server, or bundled for the client using an
18 | npm-compatible packaging system such as [Browserify](http://browserify.org/) or
19 | [webpack](http://webpack.github.io/).
20 |
21 | ```
22 | npm install react-formio --save
23 | ```
24 |
25 | ### Browser bundle
26 |
27 | The browser bundle exposes a global `Formio` variable and expects to find
28 | a global `React` variable to work with.
29 |
30 | You can find it in the [/dist directory](https://github.com/formio/react-formio/tree/master/dist/build).
31 |
32 | ## Usage
33 |
34 | Give `Formio` a `src` property and render:
35 |
36 | ** For es5 require() modules. **
37 | ```javascript
38 | var React = require('react');
39 | var ReactDOM = require('react-dom');
40 | var Formio = require('react-formio').Formio;
41 | ```
42 |
43 | ** For es6 import/export modules. **
44 | ```javascript
45 | import React from 'react';
46 | import ReactDOM from 'react-dom';
47 | import {Formio} from 'react-formio';
48 | ```
49 |
50 | ```javascript
51 | ReactDOM.render(
52 |
53 | , document.getElementById('example')
54 | );
55 | ```
56 |
57 | ## Props
58 |
59 | ### `src` : `string`
60 |
61 | The form API source from [form.io](https://www.form.io) or your custom formio server.
62 |
63 | See the [Creating a form](http://help.form.io/userguide/#new-form)
64 | for where to set the API Path for your form.
65 |
66 | You can also pass in the submission url as the `src` and the form will render with the data populated from the submission.
67 |
68 | ### `form` : `object`
69 |
70 | An object representing the form. Use this instead of src for custom forms.
71 |
72 | **Note:** `src` will override this property if used.
73 |
74 | ### `submission`: `Object`
75 |
76 | An object representing the default data for the form.
77 |
78 | **Note:** `src` will override this if a submission url is entered.
79 |
80 | ### `onChange` : `(submission: object, key: string, value: mixed)`
81 |
82 | A function callback that will be called when any field is changed. The full submission is passed as well as the field
83 | that is changing's key and value.
84 |
85 | ### `onFormSubmit` : `(submission: object)`
86 |
87 | A function callback that will be called when a submission is successful.
88 |
89 | ### `onFormError` : `(response: object)`
90 |
91 | A function callback that will be called when a submisison is unsuccessful.
92 |
93 | ### `onFormLoad` : `(form: object)`
94 |
95 | A function callback that will be called with a form is finished loading.
96 |
97 | ### `onSubmissionLoad` : `(submission: object)`
98 |
99 | A function callback that will be called after a submission is loaded.
100 |
101 | ### `onElementRender` : `(element: object)`
102 |
103 | A function callback that will be called each time a component is rendered.
104 |
105 | ### `options : object`
106 |
107 | A settings object to pass various options into the form. skipInit will stop the form from initialling setting values
108 | on the submission object which will result in data only changing when a user interacts with the form.
109 |
110 | ```javascript
111 | options={
112 | skipInit: true
113 | }
114 | ```
115 |
116 | ## License
117 | Released under the [MIT License](http://www.opensource.org/licenses/MIT).
118 |
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | machine:
2 | node:
3 | version: 6.9.1
4 |
--------------------------------------------------------------------------------
/formio.css:
--------------------------------------------------------------------------------
1 |
2 | /* FA-877 - Fix component widths in the data grid with prefix/sufix. */
3 | .input-group {
4 | width: 100%;
5 | }
6 | .form-field-type-datagrid .input-group {
7 | width: auto;
8 | }
9 |
10 | .form-field-type-datagrid .form-group {
11 | margin-bottom: 0;
12 | }
13 |
14 | .form-field-type-datagrid .datagrid-table {
15 | margin-bottom: 0px;
16 | }
17 |
18 | .form-field-type-datagrid .datagrid-add {
19 | margin-bottom: 20px;
20 | }
21 |
22 | .form-field-type-datetime .input-group,
23 | .form-field-type-datetime .input-group-btn {
24 | width: 100%;
25 | }
26 | .form-field-type-datetime .input-group-btn {
27 | width: auto;
28 | }
29 |
30 | .m-signature-pad {
31 | border: 1px solid #e8e8e8;
32 | background-color: #F5F5EB;
33 | border-radius: 4px;
34 | }
35 |
36 | .has-error .m-signature-pad--body {
37 | border-color: #b94a48;
38 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
39 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
40 | }
41 |
42 | .m-signature-pad--body {
43 | left: 20px;
44 | right: 20px;
45 | top: 20px;
46 | bottom: 60px;
47 | border: 1px solid #f4f4f4;
48 | }
49 |
50 | .m-signature-pad--body canvas {
51 | width: 100%;
52 | height: 100%;
53 | border-radius: 4px;
54 | box-shadow: 0 0 5px rgba(0, 0, 0, 0.02) inset;
55 | }
56 |
57 | @media screen and (max-width: 1024px) {
58 | .m-signature-pad {
59 | top: 0;
60 | left: 0;
61 | right: 0;
62 | bottom: 0;
63 | width: auto;
64 | height: auto;
65 | min-width: 250px;
66 | min-height: 140px;
67 | }
68 | }
69 |
70 | @media screen and (min-device-width: 768px) and (max-device-width: 1024px) {
71 | .m-signature-pad {
72 | margin: 10%;
73 | }
74 | }
75 |
76 | @media screen and (max-height: 320px) {
77 | .m-signature-pad--body {
78 | left: 0;
79 | right: 0;
80 | top: 0;
81 | bottom: 32px;
82 | }
83 | }
84 |
85 | .formio-signature-footer {
86 | color: #C3C3C3;
87 | margin: 0 10px;
88 | text-align: center;
89 | }
90 |
91 | .formio-content-centered {
92 | text-align: center;
93 | }
94 |
95 | .filepicker.dropzone {
96 | width: 100%;
97 | position: relative;
98 | height: auto;
99 | }
100 |
101 | .dz-default.dz-message {
102 | text-align: center;
103 | border-style: dashed;
104 | border-width: 2px;
105 | border-color: #666;
106 | border-radius: 5px;
107 | padding: 15px;
108 | margin: 10px 0px;
109 | }
110 |
111 | .formio-dropzone-default-content {
112 | width: 100%;
113 | height: auto;
114 | border-style: dashed;
115 | border-width: 2px;
116 | border-color: #666;
117 | border-radius: 5px;
118 | padding: 15px;
119 | margin-top: 10px;
120 | }
121 |
122 | .formio-dropzone-margin {
123 | margin: 10px 0;
124 | }
125 |
126 | .formio-dropzone-error-content {
127 | width: 100%;
128 | height: auto;
129 | background-color: #E99002;
130 | margin-bottom: 10px;
131 | padding: 15px;
132 | text-align: left;
133 | }
134 |
135 | .formio-dropzone-table {
136 | width: 1%;
137 | white-space: nowrap;
138 | }
139 |
140 | .progress-bar {
141 | float: left;
142 | width: 0;
143 | height: 20px;
144 | font-size: 12px;
145 | line-height: 20px;
146 | color: #fff;
147 | text-align: center;
148 | background-color: #337ab7;
149 | -webkit-box-shadow: inset 0 -1px 0 rgba(0,0,0,.15);
150 | box-shadow: inset 0 -1px 0 rgba(0,0,0,.15);
151 | -webkit-transition: width .6s ease;
152 | -o-transition: width .6s ease;
153 | transition: width .6s ease;
154 | margin-bottom: 10px;
155 | }
156 |
157 | .dz-drag-hover > .dz-default.dz-message {
158 | border-color: #127abe;
159 | }
160 |
161 | .rw-list-option {
162 | height: 1.8em;
163 | }
164 |
165 | .field-required:after {
166 | color: red;
167 | content: " *";
168 | }
169 |
170 | .field-required-inline {
171 | color:red;
172 | z-index: 5;
173 | }
174 |
175 | .quill {
176 | border: 1px solid #ccc;
177 | box-shadow: 0px 0px 5px #ddd;
178 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-formio",
3 | "version": "0.0.1",
4 | "description": "React native rendering library for form.io embedded forms.",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "test": "jest",
8 | "lint": "eslint src",
9 | "prepublish": "npm run lint"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/formio/react-native-formio.git"
14 | },
15 | "keywords": [
16 | "React",
17 | "component",
18 | "Formio",
19 | "Forms",
20 | "react--native-component"
21 | ],
22 | "author": "Randall Knutson ",
23 | "license": "MIT",
24 | "bugs": {
25 | "url": "https://github.com/formio/react-native-formio/issues"
26 | },
27 | "homepage": "https://github.com/formio/react-native-formio#readme",
28 | "dependencies": {
29 | "eventemitter2": "^5.0.1",
30 | "json-logic-js": "^1.2.2",
31 | "lodash": "^4.15.0",
32 | "moment": "^2.22.2",
33 | "prop-types": "^15.6.1",
34 | "react-native-device-info": "^0.22.3",
35 | "react-native-elements": "^0.19.1",
36 | "react-native-htmlview": "^0.13.0",
37 | "react-native-material-dropdown": "^0.11.1",
38 | "react-native-modal-datetime-picker": "^6.0.0",
39 | "react-native-multiple-select": "^0.4.4",
40 | "react-native-select-multiple": "^1.2.0",
41 | "react-native-signature-capture": "^0.4.9",
42 | "react-native-simple-radio-button": "^2.7.2",
43 | "react-native-vector-icons": "^4.6.0",
44 | "react-text-mask-hoc": "^0.10.6",
45 | "rn-tooltip": "^1.0.1"
46 | },
47 | "devDependencies": {
48 | "babel-eslint": "^8.2.6",
49 | "babel-jest": "23.4.0",
50 | "babel-plugin-module-resolver": "^3.1.1",
51 | "babel-preset-env": "^1.7.0",
52 | "babel-preset-jest": "^23.2.0",
53 | "babel-preset-react-native": "^4.0.0",
54 | "eslint": "^3.2.2",
55 | "eslint-config-airbnb": "^10.0.0",
56 | "eslint-config-formio": "^1.1.0",
57 | "eslint-loader": "^1.5.0",
58 | "eslint-plugin-jsx-a11y": "^2.0.1",
59 | "eslint-plugin-react": "^6.10.0",
60 | "eslint-plugin-react-native": "^2.3.1",
61 | "jest": "23.4.0",
62 | "react": "^16.4.2",
63 | "react-native": "^0.56.0",
64 | "react-native-create-bridge": "^2.0.1",
65 | "react-test-renderer": "^16.4.2",
66 | "written-number": "^0.8.1"
67 | },
68 | "peerDependencies": {
69 | "react": "^16.2.0",
70 | "react-native": "*"
71 | },
72 | "files": [
73 | "dist",
74 | "lib",
75 | "formio.css"
76 | ],
77 | "jest": {
78 | "preset": "react-native",
79 | "transform": {
80 | "^.+\\.(js|jsx)$": "babel-jest"
81 | },
82 | "setupFiles": [
83 | "/test/.setup.js"
84 | ],
85 | "transformIgnorePatterns": [
86 | "node_modules/(?!(react-native|react-native-clean-form|react-native-vector-icons|react-text-mask-hoc|react-native-elements|react-native-simple-radio-button)/)"
87 | ],
88 | "moduleFileExtensions": [
89 | "js",
90 | "jsx"
91 | ]
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/components/FormBuilder.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {View, StyleSheet} from 'react-native';
3 |
4 | const formBuilderStyles = StyleSheet.create({
5 | wrapper: {
6 | height: '500px'
7 | }
8 | });
9 |
10 | export default class FormBuilder extends React.Component {
11 | render() {
12 | return Form Builder;
13 | }
14 | }
15 |
16 |
--------------------------------------------------------------------------------
/src/components/FormComponents/button/Button.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {StyleSheet} from 'react-native';
3 | import BaseComponent from '../sharedComponents/Base';
4 | import {Button as ButtonElement} from 'react-native-elements';
5 | import DeviceInfo from 'react-native-device-info';
6 |
7 | export default class Button extends BaseComponent {
8 | constructor(props) {
9 | super(props);
10 | this.onClick = this.onClick.bind(this);
11 | this.getButtonType = this.getButtonType.bind(this);
12 | }
13 |
14 | getButtonType() {
15 | switch (this.props.component.action) {
16 | case 'submit':
17 | case 'saveState':
18 | return 'submit';
19 | case 'reset':
20 | return 'reset';
21 | case 'event':
22 | case 'oauth':
23 | default:
24 | return 'button';
25 | }
26 | }
27 |
28 | onClick(event) {
29 | if (this.props.readOnly) {
30 | event.preventDefault();
31 | this.props.resetForm();
32 | return;
33 | }
34 | switch (this.props.component.action) {
35 | case 'submit':
36 | this.props.onSubmit(event);
37 | break;
38 | case 'saveState':
39 | this.props.onSave(event);
40 | break;
41 | case 'event':
42 | this.props.onEvent(this.props.component.event);
43 | break;
44 | case 'oauth':
45 | /* eslint-disable no-console */
46 | console.warn('You must add the OAuth button to a form for it to function properly');
47 | /* eslint-enable no-console */
48 | break;
49 | case 'delete':
50 | this.props.onEvent('deleteSubmission');
51 | break;
52 | case 'reset':
53 | event.preventDefault();
54 | this.props.resetForm();
55 | break;
56 | }
57 | }
58 |
59 | render() {
60 | let buttonWidth;
61 | const {component} = this.props;
62 | if (component.block) {
63 | buttonWidth = '100%';
64 | }
65 | else {
66 | buttonWidth = DeviceInfo.isTablet() ? 250 : 150;
67 | }
68 |
69 | const styles = StyleSheet.flatten({
70 | button: {
71 | width: buttonWidth,
72 | alignSelf: 'center',
73 | marginHorizontal: 10,
74 | paddingHorizontal: component.block ? 20 : 0,
75 | marginTop: 20,
76 | marginBottom: 10
77 | },
78 | });
79 |
80 | const getIconName = (value) => value.split(' ')[1].split('-')[1];
81 | const disabled = this.props.readOnly || this.props.isSubmitting || (component.disableOnInvalid && !this.props.isFormValid);
82 | const submitting = this.props.isSubmitting && component.action === 'submit';
83 |
84 | const leftIcon = component.leftIcon ? {name: getIconName(component.leftIcon), type: 'font-awesome'} : null;
85 | const rightIcon = component.rightIcon ? {name: getIconName(component.rightIcon), type: 'font-awesome'} : null;
86 |
87 | return (
88 |
100 | );
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/components/FormComponents/columns/Columns.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {FormioComponentsList} from '../../../components';
3 | import PropTypes from 'prop-types';
4 | import {View} from 'react-native';
5 | import styles from './styles';
6 |
7 | const Columns = (props) => (
8 |
9 | {props.component.columns.map((column, index) => (
10 |
15 | ))}
16 |
17 | );
18 |
19 | Columns.propTypes = {
20 | component: PropTypes.object,
21 | theme: PropTypes.any,
22 | colors: PropTypes.object,
23 | };
24 |
25 | export default Columns;
26 |
--------------------------------------------------------------------------------
/src/components/FormComponents/columns/styles.js:
--------------------------------------------------------------------------------
1 |
2 | import {StyleSheet} from 'react-native';
3 |
4 | const styles = StyleSheet.flatten({
5 | columns: {
6 | flex: 1,
7 | flexDirection: 'row',
8 | borderBottomWidth: 1,
9 | paddingVertical: 20,
10 | },
11 | });
12 |
13 | export default styles;
14 |
--------------------------------------------------------------------------------
/src/components/FormComponents/componentUtils/getDefaultValue.js:
--------------------------------------------------------------------------------
1 | import {safeSingleToMultiple} from './safeSingleToMultiple';
2 |
3 | const getCustomDefault = (component) => {
4 | const value = component.customDefaultValue.toString();
5 | return value;
6 | };
7 |
8 | export const getDefaultValue = (value, component, getInitialValue, onChangeCustom) => {
9 | // Allow components to set different default values.
10 | if (value == null) {
11 | if (component.hasOwnProperty('customDefaultValue')) {
12 | try {
13 | value = getCustomDefault(component);
14 | }
15 | catch (e) {
16 | /* eslint-disable no-console */
17 | console.warn('An error occurrend in a custom default value in ' + component.key, e);
18 | /* eslint-enable no-console */
19 | value = '';
20 | }
21 | }
22 | else if (component.hasOwnProperty('defaultValue')) {
23 | value = component.defaultValue;
24 | if (typeof onChangeCustom === 'function') {
25 | value = onChangeCustom(value);
26 | }
27 | }
28 | else if (typeof getInitialValue === 'function') {
29 | value = getInitialValue();
30 | }
31 | else {
32 | value = '';
33 | }
34 | }
35 | value = safeSingleToMultiple(value, component);
36 | return value;
37 | };
38 |
--------------------------------------------------------------------------------
/src/components/FormComponents/componentUtils/safeSingleToMultiple.js:
--------------------------------------------------------------------------------
1 |
2 | export const safeSingleToMultiple = (value, component) => {
3 | // Don't do anything to datagrid or containers.
4 | if ((component.type === 'datagrid') || (component.type === 'container') || (component.type === 'editgrid')) {
5 | return value;
6 | }
7 | // If this was a single but is not a multivalue.
8 | if (component.multiple && !Array.isArray(value)) {
9 | if (component.type === 'select' && !value) {
10 | value = [];
11 | }
12 | else {
13 | value = [value];
14 | }
15 | }
16 | // If this was a multivalue but is now single value.
17 | // RE-60 :-Need to return the value as array of object instead of object while converting a multivalue to single value for datagrid component
18 | else if (!component.multiple && Array.isArray(value)) {
19 | value = value[0];
20 | }
21 | // Set dates to Date object.
22 | if (component.type === 'datetime' && value && !(value instanceof Date)) {
23 | value = new Date(value);
24 | }
25 | return value;
26 | };
27 |
--------------------------------------------------------------------------------
/src/components/FormComponents/componentUtils/validators.js:
--------------------------------------------------------------------------------
1 |
2 | export const validateItem = (item, component, data) => {
3 | let state = {
4 | isValid: true,
5 | errorType: '',
6 | errorMessage: '',
7 | item,
8 | };
9 | // Check for no validation criteria
10 | if (!component.validate) {
11 | return state;
12 | }
13 | // Required
14 | if (component.validate.required) {
15 | // Multivalue and selectboxes are exceptions since !![] === true and !!{} === true.
16 | if (component.type === 'selectboxes' && !Object.keys(item).reduce((prev, cur) => {
17 | return prev || item[cur];
18 | }, false)) {
19 | state.isValid = false;
20 | state.errorType = 'required';
21 | state.errorMessage = (component.label || component.key) + ' is required.';
22 | }
23 | else if (!item) {
24 | state.isValid = false;
25 | state.errorType = 'required';
26 | state.errorMessage = (component.label || component.key) + ' is required.';
27 | }
28 | }
29 | // Email
30 | if (state.isValid && component.type === 'email' && !item.match(/\S+@\S+/)) {
31 | state.isValid = false;
32 | state.errorType = 'email';
33 | state.errorMessage = (component.label || component.key) + ' must be a valid email.';
34 | }
35 | // MaxLength
36 | if (state.isValid && component.validate.maxLength && item.length > component.validate.maxLength) {
37 | state.isValid = false;
38 | state.errorType = 'maxlength';
39 | state.errorMessage = (component.label || component.key) + ' cannot be longer than ' + (component.validate.maxLength) + ' characters.';
40 | }
41 | // MinLength
42 | if (state.isValid && component.validate.minLength && item.length < component.validate.minLength) {
43 | state.isValid = false;
44 | state.errorType = 'minlength';
45 | state.errorMessage = (component.label || component.key) + ' cannot be shorter than ' + (component.validate.minLength) + ' characters.';
46 | }
47 | // MaxValue
48 | if (state.isValid && component.validate.max && item > component.validate.max) {
49 | state.isValid = false;
50 | state.errorType = 'max';
51 | state.errorMessage = (component.label || component.key) + ' cannot be greater than ' + component.validate.max;
52 | }
53 | // MinValue
54 | if (state.isValid && component.validate.min && item < component.validate.min) {
55 | state.isValid = false;
56 | state.errorType = 'min';
57 | state.errorMessage = (component.label || component.key) + ' cannot be less than ' + component.validate.min;
58 | }
59 | // Regex
60 | if (state.isValid && component.validate.pattern) {
61 | const re = new RegExp(component.validate.pattern, 'g');
62 | state.isValid = item.match(re);
63 | if (!state.isValid) {
64 | state.errorType = 'regex';
65 | state.errorMessage = (component.label || component.key) + ' must match the expression: ' + component.validate.pattern;
66 | }
67 | }
68 | // Input Mask
69 | if (component.inputMask && item.includes('_')) {
70 | state.isValid = false;
71 | state.errorType = 'mask';
72 | state.errorMessage = (component.label || component.key) + ' must use the format ' + component.inputMask;
73 | }
74 | // Custom
75 | if (state.isValid && component.validate.custom) {
76 | let custom = component.validate.custom;
77 | custom = custom.replace(/({{\s+(.*)\s+}})/, (match, $1, $2) => data[$2]);
78 | const input = item;
79 | let valid;
80 | try {
81 | valid = eval(custom);
82 | state.isValid = (valid === true);
83 | }
84 | catch (e) {
85 | /* eslint-disable no-console */
86 | console.warn('A syntax error occurred while computing custom values in ' + component.key, e);
87 | /* eslint-enable no-console */
88 | }
89 | if (!state.isValid) {
90 | state.errorType = 'custom';
91 | state.errorMessage = valid;
92 | }
93 | }
94 | return state;
95 | };
96 |
97 | export const validate = (value, component, data, validateCustom) => {
98 | let state = {
99 | isValid: true,
100 | errorType: '',
101 | errorMessage: ''
102 | };
103 |
104 | // Allow components to have custom validation.
105 | if (typeof validateCustom === 'function') {
106 | const customValidation = validateCustom(value);
107 | if (!customValidation.isValid) {
108 | state.isValid = false;
109 | state.errorType = 'day';
110 | state.errorMessage = customValidation.errorMessage;
111 | }
112 | }
113 | // Validate each item if multiple.
114 | if (component.multiple) {
115 | const items = [];
116 | value.forEach(item => {
117 | if (state.isValid) {
118 | state = validateItem(item, component, data);
119 | items.push(state.item);
120 | }
121 | state.item = items;
122 | });
123 |
124 | if (component.validate && component.validate.required && (!(value instanceof Array) || value.length === 0)) {
125 | state.isValid = false;
126 | state.errorType = 'required';
127 | state.errorMessage = (component.label || component.key) + ' is required.';
128 | }
129 | }
130 | else {
131 | state = validateItem(value, component, data);
132 | }
133 | return state;
134 | };
135 |
--------------------------------------------------------------------------------
/src/components/FormComponents/content/Content.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {View, Linking} from 'react-native';
3 | import {StyleSheet} from 'react-native';
4 | import HTMLView from 'react-native-htmlview';
5 | import BaseComponent from '../sharedComponents/Base';
6 | import styles from './styles';
7 |
8 | export default class Content extends BaseComponent {
9 |
10 | constructor(props) {
11 | super(props);
12 | this.onLinkPress = this.onLinkPress.bind(this);
13 | this.getHtmlStyles = this.getHtmlStyles.bind(this);
14 | }
15 |
16 | getHtmlStyles() {
17 | return {
18 | p: {
19 | ...StyleSheet.flatten(styles.p),
20 | color: this.props.colors.textColor,
21 | },
22 | };
23 | }
24 |
25 | onLinkPress(url) {
26 | Linking.openURL(url)
27 | .catch((e) => {
28 | return e;
29 | });
30 | }
31 |
32 | render() {
33 | return (
34 |
35 |
41 |
42 | );
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/components/FormComponents/content/styles.js:
--------------------------------------------------------------------------------
1 | import {StyleSheet} from 'react-native';
2 |
3 | const textColor = '#86939e';
4 | const styles = StyleSheet.create({
5 | content: {
6 | flex: 1,
7 | marginHorizontal: 20,
8 | marginTop: 10,
9 | marginBottom: 0,
10 | },
11 | p: {
12 | fontSize: 16,
13 | marginTop: 5,
14 | marginBottom: 0,
15 | color: textColor,
16 | lineHeight: 25,
17 | textAlign: 'justify',
18 | },
19 | });
20 |
21 | export default styles;
22 |
--------------------------------------------------------------------------------
/src/components/FormComponents/datetime/Datetime.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import DateTimePicker from 'react-native-modal-datetime-picker';
3 | import MultiComponent from '../sharedComponents/Multi';
4 | import {View, Text} from 'react-native';
5 | import {Button} from 'react-native-elements';
6 | import styles from './styles';
7 | import moment from 'moment';
8 |
9 | export default class Datetime extends MultiComponent {
10 | constructor(props) {
11 | super(props);
12 | this.getInitialValue = this.getInitialValue.bind(this);
13 | this.getMode = this.getMode.bind(this);
14 | this.getDisplayFormat = this.getDisplayFormat.bind(this);
15 | this.getResultFormat = this.getResultFormat.bind(this);
16 | this.onConfirm = this.onConfirm.bind(this);
17 | this.togglePicker = this.togglePicker.bind(this);
18 | this.getSingleElement = this.getSingleElement.bind(this);
19 | }
20 |
21 | getInitialValue(value) {
22 | if (!this.props) {
23 | return moment().toDate();
24 | }
25 |
26 | const dateFormat = this.props.component.dateFirst ? 'DD/MM/YYYY' : 'MM/DD/YYYY';
27 | if (value && value.item && moment(value.item, dateFormat).isValid()) {
28 | return moment(value.item, dateFormat).toDate();
29 | }
30 | else if (this.props.component.defaultDate) {
31 | return moment(this.props.component.defaultDate, dateFormat).toDate();
32 | }
33 | else {
34 | return moment().toDate();
35 | }
36 | }
37 |
38 | getMode() {
39 | switch (this.props.component.type) {
40 | case 'datetime':
41 | return 'datetime';
42 | case 'day':
43 | return 'date';
44 | case 'time':
45 | return 'time';
46 | default:
47 | return 'date';
48 | }
49 | }
50 |
51 | getDisplayFormat() {
52 | switch (this.props.component.type) {
53 | case 'datetime':
54 | return 'MMMM DD, YYYY hh:mm A';
55 | case 'day':
56 | return 'MMMM DD, YYYY';
57 | case 'time':
58 | return 'hh:mm A';
59 | default:
60 | return 'MMMM DD, YYYY';
61 | }
62 | }
63 |
64 | getResultFormat() {
65 | const dateFirst = this.props.component.dateFirst;
66 | switch (this.props.component.type) {
67 | case 'datetime':
68 | return dateFirst ? 'DD/MM/YYYY : hh:mm A' : 'MM/DD/YYYY : hh:mm A';
69 | case 'day':
70 | return dateFirst ? 'DD/MM/YYYY' : 'MM/DD/YYYY';
71 | case 'time':
72 | return 'hh:mm A';
73 | default:
74 | return dateFirst ? 'DD/MM/YYYY' : 'MM/DD/YYYY';
75 | }
76 | }
77 |
78 | onConfirm(value, index) {
79 | const selected = moment(value);
80 | const dateFormat = this.getResultFormat();
81 | if (selected.isValid()) {
82 | const date = selected.format(dateFormat).toString();
83 | this.setValue(date, index);
84 | }
85 | else {
86 | // this fixes date module returning invalid date
87 | //if confirm button was pressed without touching date picker.
88 | value = moment().format(dateFormat).toString();
89 | this.setValue(value.toISOString(), index);
90 | }
91 | this.togglePicker();
92 | }
93 |
94 | togglePicker() {
95 | this.setState({
96 | open: !this.state.open
97 | });
98 | }
99 |
100 | getSingleElement(value, index) {
101 | const {component, name, readOnly} = this.props;
102 | const dateFormat = this.props.component.dateFirst ? 'DD/MM/YYYY' : 'MM/DD/YYYY';
103 | return (
104 |
105 | {this.state.value && this.state.value.item &&
106 | {
107 | moment(this.state.value.item, dateFormat).format(this.getDisplayFormat())}
108 | }
109 |
118 | this.datepicker = ref}
125 | minuteInterval={this.props.component.timePicker ? this.props.component.timePicker.minuteStep : 5}
126 | mode={this.getMode()}
127 | date={this.getInitialValue(value)}
128 | onCancel={this.togglePicker}
129 | onConfirm={this.onConfirm}
130 | />
131 |
132 | );
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/src/components/FormComponents/datetime/styles.js:
--------------------------------------------------------------------------------
1 |
2 | import {StyleSheet} from 'react-native';
3 | import DeviceInfo from 'react-native-device-info';
4 |
5 | const styles = StyleSheet.flatten({
6 | button: {
7 | width: DeviceInfo.isTablet() ? 150 : 120,
8 | marginHorizontal: DeviceInfo.isTablet() ? 10 : 0,
9 | marginVertical: DeviceInfo.isTablet() ? 10 : 0,
10 | },
11 | date: {
12 | flex: 1,
13 | flexDirection: DeviceInfo.isTablet() ? 'row' : 'column'
14 | },
15 | dateText: {
16 | fontSize: DeviceInfo.isTablet() ? 18 : 12,
17 | marginLeft: 20,
18 | marginRight: 10,
19 | marginTop: 20,
20 | }
21 | });
22 |
23 | export default styles;
24 |
--------------------------------------------------------------------------------
/src/components/FormComponents/day/Day.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Datetime from '../datetime/Datetime';
3 | import moment from 'moment';
4 |
5 | export default class Day extends Datetime {
6 | constructor(props) {
7 | super(props);
8 | this.validateCustom = this.validateCustom.bind(this);
9 | this.customState = this.customState.bind(this);
10 | this.willReceiveProps = this.willReceiveProps.bind(this);
11 | }
12 |
13 | validateCustom(value) {
14 | let state = {
15 | isValid: true,
16 | errorType: '',
17 | errorMessage: '',
18 | item: value,
19 | };
20 | if (!this.props) {
21 | return state;
22 | }
23 | const required = this.props.component.fields.day.required || this.props.component.fields.month.required || this.props.component.fields.year.required;
24 | if (!required) {
25 | return state;
26 | }
27 | if (!value && required) {
28 | state = {
29 | isValid: false,
30 | errorMessage: (this.props.component.label || this.props.component.key) + ' must be a valid date.',
31 | item: value,
32 | };
33 | }
34 | const parts = value.split('/');
35 | if (this.props.component.fields.day.required) {
36 | if (parts[(this.props.component.dayFirst ? 0 : 1)] === '00') {
37 | state = {
38 | isValid: false,
39 | errorType: 'day',
40 | errorMessage: (this.props.component.label || this.props.component.key) + ' must be a valid date.',
41 | item: value,
42 | };
43 | }
44 | }
45 | if (this.props.component.fields.month.required) {
46 | if (parts[(this.props.component.dayFirst ? 1 : 0)] === '00') {
47 | state = {
48 | isValid: false,
49 | errorType: 'day',
50 | errorMessage: (this.props.component.label || this.props.component.key) + ' must be a valid date.',
51 | item: value,
52 | };
53 | }
54 | }
55 | if (this.props.component.fields.year.required) {
56 | if (parts[2] === '0000') {
57 | state = {
58 | isValid: false,
59 | errorType: 'day',
60 | errorMessage: (this.props.component.label || this.props.component.key) + ' must be a valid date.',
61 | item: value,
62 | };
63 | }
64 | }
65 | return state;
66 | }
67 |
68 | customState(state) {
69 | let dateItem;
70 | const dateFormat = this.props.component.dateFirst ? 'DD/MM/YYYY' : 'MM/DD/YYYY';
71 | state.date = {
72 | day: '',
73 | month: '',
74 | year: ''
75 | };
76 |
77 | if (state.value.item) {
78 | dateItem = state.value.item;
79 | }
80 | else {
81 | dateItem = moment(state.value).format(dateFormat).toString();
82 | }
83 |
84 | if (dateItem) {
85 | const parts = dateItem.split(/\//g);
86 | state.date.day = parseInt(parts[(this.props.component.dayFirst ? 0 : 1)]).toString();
87 | state.date.month = parseInt(parts[(this.props.component.dayFirst ? 1 : 0)]).toString();
88 | state.date.year = parseInt(parts[2]).toString();
89 | }
90 | return state;
91 | }
92 |
93 | willReceiveProps(nextProps) {
94 | if (this.state.value !== nextProps.value) {
95 | if (!nextProps.value) {
96 | return;
97 | }
98 | const parts = nextProps.value.item ? nextProps.value.item.split('/') : ['', '', ''];
99 | this.setState({
100 | date: {
101 | day: parts[(this.props.component.dayFirst ? 0 : 1)],
102 | month: parseInt(parts[(this.props.component.dayFirst ? 1 : 0)]).toString(),
103 | year: parts[2]
104 | }
105 | });
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/components/FormComponents/fieldset/Fieldset.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {View} from 'react-native';
3 | import PropTypes from 'prop-types';
4 | import {FormioComponentsList} from '../../';
5 | import H3 from '../h3/H3';
6 | import styles from './styles';
7 |
8 | const Fieldset = (props) => {
9 | const legend = (props.component.legend ? {props.component.legend}
: null);
10 | return (
11 |
12 | {legend}
13 |
17 |
18 | );
19 | };
20 |
21 | Fieldset.propTypes = {
22 | component: PropTypes.object,
23 | theme: PropTypes.object,
24 | colors: PropTypes.object
25 | };
26 |
27 | export default Fieldset;
28 |
29 |
--------------------------------------------------------------------------------
/src/components/FormComponents/fieldset/styles.js:
--------------------------------------------------------------------------------
1 | import {StyleSheet} from 'react-native';
2 |
3 | const styles = StyleSheet.create({
4 | filedset: {
5 | flex: 1,
6 | flexDirection: 'column',
7 | padding: 0,
8 | },
9 | });
10 |
11 | export default styles;
12 |
--------------------------------------------------------------------------------
/src/components/FormComponents/h2/H2.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Text} from 'react-native-elements';
3 | import PropTypes from 'prop-types';
4 | import styles from './styles';
5 |
6 | const H2 = (props) => (
7 |
8 | {props.children}
9 |
10 | );
11 |
12 | H2.propTypes = {
13 | children: PropTypes.node
14 | };
15 |
16 | export default H2;
17 |
--------------------------------------------------------------------------------
/src/components/FormComponents/h2/styles.js:
--------------------------------------------------------------------------------
1 | import {StyleSheet} from 'react-native';
2 |
3 | const styles = StyleSheet.create({
4 | header2: {
5 | fontSize: 28,
6 | margin: 10,
7 | lineHeight: 34,
8 | letterSpacing: 0.36,
9 | }
10 | });
11 |
12 | export default styles;
13 |
--------------------------------------------------------------------------------
/src/components/FormComponents/h3/H3.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Text} from 'react-native-elements';
3 | import PropTypes from 'prop-types';
4 | import styles from './styles';
5 |
6 | const H3 = (props) => (
7 |
8 | {props.children}
9 |
10 | );
11 |
12 | H3.propTypes = {
13 | children: PropTypes.node
14 | };
15 |
16 | export default H3;
17 |
18 |
--------------------------------------------------------------------------------
/src/components/FormComponents/h3/styles.js:
--------------------------------------------------------------------------------
1 | import {StyleSheet} from 'react-native';
2 |
3 | const styles = StyleSheet.create({
4 | header3: {
5 | fontSize: 22,
6 | margin: 10,
7 | lineHeight: 28,
8 | letterSpacing: 0.35,
9 | }
10 | });
11 |
12 | export default styles;
13 |
--------------------------------------------------------------------------------
/src/components/FormComponents/htmlelement/HtmlElement.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {View, Text} from 'react-native';
3 | import BaseComponent from '../sharedComponents/Base';
4 | import H3 from '../h3/H3';
5 | import H2 from '../h2/H2';
6 | import styles from './styles';
7 |
8 | const tags = {
9 | h2: H2,
10 | h3: H3,
11 | default: Text
12 | };
13 |
14 | export default class HTMLElement extends BaseComponent {
15 |
16 | constructor(props) {
17 | super(props);
18 | }
19 |
20 | renderContent() {
21 | let Tag = tags.default;
22 | if (tags.hasOwnProperty(this.props.component.tag.toLowerCase())) {
23 | Tag = tags[this.props.component.tag.toLowerCase()];
24 | }
25 | return ({this.props.component.content});
26 | }
27 |
28 | render() {
29 | return (
30 |
31 | {this.renderContent()}
32 |
33 | );
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/components/FormComponents/htmlelement/styles.js:
--------------------------------------------------------------------------------
1 | import {StyleSheet} from 'react-native';
2 |
3 | const styles = StyleSheet.create({
4 | content: {
5 | flex: 1,
6 | }
7 | });
8 |
9 | export default styles;
10 |
--------------------------------------------------------------------------------
/src/components/FormComponents/index.js:
--------------------------------------------------------------------------------
1 | import {FormioComponents} from '../../factories';
2 |
3 | // import address from './address';
4 | import Button from './button/Button';
5 | import Columns from './columns/Columns';
6 | // import container from './container';
7 | import Content from './content/Content';
8 | // import currency from './currency';
9 | // import custom from './custom';
10 | // import datagrid from './datagrid';
11 | // import editgrid from './editgrid';
12 | import Datetime from './datetime/Datetime';
13 | import Day from './day/Day';
14 | // import email from './email';
15 | import Fieldset from './fieldset/Fieldset';
16 | // import file from './file';
17 | // import hidden from './hidden';
18 | import HtmlElement from './htmlelement/HtmlElement';
19 | // import number from './number';
20 | import Panel from './panel/Panel';
21 | // import password from './password';
22 | // import phoneNumber from './phoneNumber';
23 | import Radio from './radio/Radio';
24 | // import resource from './resource';
25 | // import survey from './survey';
26 | import Select from './select/Select';
27 | import Selectboxes from './selectboxes/SelectBoxes';
28 | import Signature from './signature/Signature';
29 | import Switch from './switch/Switch';
30 | // import table from './table';
31 | import Textarea from './textarea/Textarea';
32 | import TextField from './textfield/TextField';
33 | // import time from './time';
34 | // import well from './well';
35 |
36 | // FormioComponents.register('address', address);
37 | FormioComponents.register('button', Button);
38 | FormioComponents.register('columns', Columns);
39 | // FormioComponents.register('container', container);
40 | FormioComponents.register('content', Content);
41 | // FormioComponents.register('currency', currency);
42 | // FormioComponents.register('custom', custom);
43 | // FormioComponents.register('datagrid', datagrid);
44 | // FormioComponents.register('editgrid', editgrid);
45 | FormioComponents.register('datetime', Datetime);
46 | FormioComponents.register('day', Day);
47 | // FormioComponents.register('email', email);
48 | FormioComponents.register('fieldset', Fieldset);
49 | // FormioComponents.register('file', file);
50 | // FormioComponents.register('hidden', hidden);
51 | FormioComponents.register('htmlelement', HtmlElement);
52 | // FormioComponents.register('number', number);
53 | FormioComponents.register('panel', Panel);
54 | // FormioComponents.register('password', password);
55 | // FormioComponents.register('phoneNumber', phoneNumber);
56 | FormioComponents.register('radio', Radio);
57 | // FormioComponents.register('resource', resource);
58 | // FormioComponents.register('survey', survey);
59 | FormioComponents.register('select', Select);
60 | FormioComponents.register('selectboxes', Selectboxes);
61 | FormioComponents.register('signature', Signature);
62 | FormioComponents.register('checkbox', Switch);
63 | // FormioComponents.register('table', table);
64 | FormioComponents.register('textarea', Textarea);
65 | FormioComponents.register('textfield', TextField);
66 | // FormioComponents.register('time', time);
67 | // FormioComponents.register('well', well);
68 |
--------------------------------------------------------------------------------
/src/components/FormComponents/panel/Panel.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {FormioComponentsList} from '../../';
3 | import {View} from 'react-native';
4 | import {Card} from 'react-native-elements';
5 | import PropTypes from 'prop-types';
6 | import {StyleSheet} from 'react-native';
7 | import styles from './styles';
8 |
9 | const Panel = (props) => {
10 | const title = (props.component.title && !props.component.hideLabel ? props.component.title : undefined);
11 | const titleStyle = {...StyleSheet.flatten(styles.title), color: props.colors.secondaryTextColor};
12 |
13 | return (
14 |
15 |
16 |
20 |
21 |
22 | );
23 | };
24 |
25 | Panel.propTypes = {
26 | component: PropTypes.object,
27 | theme: PropTypes.object,
28 | colors: PropTypes.object
29 | };
30 |
31 | export default Panel;
32 |
--------------------------------------------------------------------------------
/src/components/FormComponents/panel/styles.js:
--------------------------------------------------------------------------------
1 | import {StyleSheet} from 'react-native';
2 |
3 | const styles = StyleSheet.create({
4 | panel: {
5 | flex: 1,
6 | flexDirection: 'column',
7 | padding: 0,
8 | paddingBottom: 20,
9 | },
10 | title: {
11 | alignSelf: 'flex-start',
12 | margin: 10,
13 | },
14 | componentsWrapper: {
15 | flex: 1,
16 | },
17 | });
18 |
19 | export default styles;
20 |
--------------------------------------------------------------------------------
/src/components/FormComponents/radio/Radio.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import InputComponent from '../sharedComponents/Input';
3 | import {StyleSheet} from 'react-native';
4 | import RadioForm, {RadioButton, RadioButtonInput, RadioButtonLabel} from 'react-native-simple-radio-button';
5 | import styles from './styles';
6 |
7 | export default class Radio extends InputComponent {
8 |
9 | constructor(props) {
10 | super(props);
11 |
12 | this.onChangeRadio = this.onChangeRadio.bind(this);
13 | this.getRadioButton = this.getRadioButton.bind(this);
14 | this.getSingleElement = this.getSingleElement.bind(this);
15 | this.getValueDisplay = this.getValueDisplay.bind(this);
16 | }
17 |
18 | onChangeRadio(value, index) {
19 | this.setValue(value);
20 | }
21 |
22 | getRadioButton(v, id, key, index, horizontal) {
23 | return (
24 |
25 |
37 |
45 |
46 | );
47 | }
48 |
49 | getSingleElement(value, index) {
50 | index = index || 0;
51 | let key = this.props.component.key;
52 | if (this.props.hasOwnProperty('rowIndex')) {
53 | key += '-' + this.props.rowIndex;
54 | }
55 |
56 | const radioFormStyle = {...StyleSheet.flatten(styles.radioForm), marginLeft: this.props.component.inline ? 20 : 0};
57 | return (
58 |
63 | {this.props.component.values.map((v, id) => {
64 | return this.getRadioButton(v, id, key, index, this.props.component.inline);
65 | })}
66 |
67 | );
68 | }
69 |
70 | getValueDisplay(component, data) {
71 | for (let i in component.values) {
72 | if (component.values[i].value === data) {
73 | return component.values[i].label;
74 | }
75 | }
76 | return data;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/components/FormComponents/radio/styles.js:
--------------------------------------------------------------------------------
1 | import {StyleSheet} from 'react-native';
2 |
3 | const styles = StyleSheet.create({
4 | radioForm: {
5 | marginTop: 20,
6 | paddingRight: 10,
7 | },
8 | radioButton: {
9 | alignSelf: 'flex-start',
10 | justifyContent: 'flex-start',
11 | },
12 | label: {
13 | fontSize: 14,
14 | marginHorizontal: 10,
15 | },
16 | radioButtonWrap: {
17 | marginLeft: 10
18 | }
19 | });
20 |
21 | export default styles;
22 |
--------------------------------------------------------------------------------
/src/components/FormComponents/select/Select.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import SelectComponent from '../sharedComponents/Select';
3 | import formiojs from '../../../formio';
4 | import {Text} from 'react-native';
5 | import {interpolate, serialize, raw} from '../../../util';
6 | import get from 'lodash/get';
7 |
8 | export default class Select extends SelectComponent {
9 | constructor(props) {
10 | super(props);
11 | this.getValueField = this.getValueField.bind(this);
12 | this.refreshItems = this.refreshItems.bind(this);
13 | this.loadMoreItems = this.loadMoreItems.bind(this);
14 | this.setResult = this.setResult.bind(this);
15 | this.getValueDisplay = this.getValueDisplay.bind(this);
16 | }
17 |
18 | componentDidMount() {
19 | switch (this.props.component.dataSrc) {
20 | case 'values':
21 | this.internalFilter = true;
22 | this.setState({
23 | selectItems: this.props.component.data.values
24 | });
25 | break;
26 | case 'json':
27 | try {
28 | if (typeof this.props.component.data.json === 'string') {
29 | this.items = JSON.parse(this.props.component.data.json);
30 | }
31 | else if (typeof this.props.component.data.json === 'object') {
32 | this.items = this.props.component.data.json;
33 | }
34 | else {
35 | this.items = [];
36 | }
37 | }
38 | catch (error) {
39 | this.items = [];
40 | }
41 | this.options.params = {
42 | limit: parseInt(this.props.component.limit) || 20,
43 | skip: 0
44 | };
45 | this.refreshItems = (input, url, append) => {
46 | // If they typed in a search, reset skip.
47 | if ((this.lastInput || input) && this.lastInput !== input) {
48 | this.lastInput = input;
49 | this.options.params.skip = 0;
50 | }
51 | let items = this.items;
52 | if (input) {
53 | items = items.filter(item => {
54 | // Get the visible string from the interpolated item.
55 | const value = interpolate(this.props.component.template, {item}).replace(/<(?:.|\n)*?>/gm, '');
56 | switch (this.props.component.filter) {
57 | case 'startsWith':
58 | return value.toLowerCase().lastIndexOf(input.toLowerCase(), 0) === 0;
59 | case 'contains':
60 | default:
61 | return value.toLowerCase().indexOf(input.toLowerCase()) !== -1;
62 | }
63 | });
64 | }
65 | items = items.slice(this.options.params.skip, this.options.params.skip + this.options.params.limit);
66 | this.setResult(items, append);
67 | };
68 | this.refreshItems();
69 | break;
70 | case 'custom':
71 | this.refreshItems = () => {
72 | try {
73 | /* eslint-disable no-unused-vars */
74 | const {data, row} = this.props;
75 | /* eslint-enable no-unused-vars */
76 | let selectItems = eval('(function(data, row) { var values = [];' + this.props.component.data.custom.toString() + '; return values; })(data, row)');
77 | if (!Array.isArray(selectItems)) {
78 | throw 'Didn\'t return an array.';
79 | }
80 | this.setState({
81 | selectItems
82 | });
83 | }
84 | catch (error) {
85 | this.setState({
86 | selectItems: []
87 | });
88 | }
89 | };
90 | this.refreshItems();
91 | break;
92 | case 'resource':
93 | case 'url':
94 | if (this.props.component.dataSrc === 'url') {
95 | this.url = this.props.component.data.url;
96 | if (this.url.substr(0, 1) === '/') {
97 | this.url = formiojs.getBaseUrl() + this.props.component.data.url;
98 | }
99 |
100 | // Disable auth for outgoing requests.
101 | if (!this.props.component.authenticate && this.url.indexOf(formiojs.getBaseUrl()) === -1) {
102 | this.options = {
103 | disableJWT: true,
104 | headers: {
105 | Authorization: undefined,
106 | Pragma: undefined,
107 | 'Cache-Control': undefined
108 | }
109 | };
110 | }
111 | }
112 | else {
113 | this.url = formiojs.getBaseUrl();
114 | if (this.props.component.data.project) {
115 | this.url += '/project/' + this.props.component.data.project;
116 | }
117 | this.url += '/form/' + this.props.component.data.resource + '/submission';
118 | }
119 |
120 | this.options.params = {
121 | limit: this.props.component.limit || 100,
122 | skip: 0
123 | };
124 |
125 | this.refreshItems = (input, newUrl, append) => {
126 | let {data, row} = this.props;
127 | newUrl = newUrl || this.url;
128 | // Allow templating the url.
129 | newUrl = interpolate(newUrl, {
130 | data,
131 | row,
132 | formioBase: formiojs.getBaseUrl()
133 | });
134 | if (!newUrl) {
135 | return;
136 | }
137 |
138 | // If this is a search, then add that to the filter.
139 | if (this.props.component.searchField && input) {
140 | // If they typed in a search, reset skip.
141 | if (this.lastInput !== input) {
142 | this.lastInput = input;
143 | this.options.params.skip = 0;
144 | }
145 | newUrl += ((newUrl.indexOf('?') === -1) ? '?' : '&') +
146 | encodeURIComponent(this.props.component.searchField) +
147 | '=' +
148 | encodeURIComponent(input);
149 | }
150 |
151 | // Add the other filter.
152 | if (this.props.component.filter) {
153 | const filter = interpolate(this.props.component.filter, {data});
154 | newUrl += ((newUrl.indexOf('?') === -1) ? '?' : '&') + filter;
155 | }
156 |
157 | // If they wish to return only some fields.
158 | if (this.props.component.selectFields) {
159 | this.options.params.select = this.props.component.selectFields;
160 | }
161 |
162 | // If this is a search, then add that to the filter.
163 | newUrl += ((newUrl.indexOf('?') === -1) ? '?' : '&') + serialize(this.options.params);
164 | formiojs.request(newUrl).then(data => {
165 | // If the selectValue prop is defined, use it.
166 | if (this.props.component.selectValues) {
167 | this.setResult(get(data, this.props.component.selectValues, []), append);
168 | }
169 | // Attempt to default to the formio settings for a resource.
170 | else if (data.hasOwnProperty('data')) {
171 | this.setResult(data.data, append);
172 | }
173 | else if (data.hasOwnProperty('items')) {
174 | this.setResult(data.items, append);
175 | }
176 | // Use the data itself.
177 | else {
178 | this.setResult(data, append);
179 | }
180 | });
181 | };
182 |
183 | this.refreshItems();
184 |
185 | break;
186 | default:
187 | this.setState({
188 | selectItems: []
189 | });
190 | }
191 | }
192 |
193 | getValueField() {
194 | if (this.props.component.dataSrc === 'custom' || this.props.component.dataSrc === 'json') {
195 | return false;
196 | }
197 | if (this.props.component.dataSrc === 'resource' && this.props.component.valueProperty === '') {
198 | return '_id';
199 | }
200 | return this.props.component.valueProperty || 'value';
201 | }
202 |
203 | refreshItems() {}
204 |
205 | loadMoreItems(event) {
206 | event.stopPropagation();
207 | event.preventDefault();
208 | this.props.onEvent('loadMore', this.loadMoreItems);
209 | this.options.params.skip += this.options.params.limit;
210 | this.refreshItems(null, null, true);
211 | }
212 |
213 | setResult(data, append) {
214 | if (!Array.isArray(data)) {
215 | data = [data];
216 | }
217 | if (append) {
218 | this.setState({
219 | selectItems: [...this.state.selectItems, ...data],
220 | hasNextPage: this.state.selectItems.length >= (this.options.params.limit + this.options.params.skip),
221 | });
222 | }
223 | else {
224 | this.setState({
225 | selectItems: data,
226 | hasNextPage: this.state.selectItems.length >= (this.options.params.limit + this.options.params.skip),
227 | });
228 | }
229 | }
230 |
231 | getValueDisplay(component, data) {
232 | const getItem = (data) => {
233 | switch (component.dataSrc) {
234 | case 'values':
235 | component.data.values.forEach((item) => {
236 | if (item.value === data) {
237 | data = item;
238 | }
239 | });
240 | return data;
241 | case 'json':
242 | if (component.valueProperty) {
243 | let selectItems;
244 | try {
245 | selectItems = JSON.parse(component.data.json);
246 | }
247 | catch (error) {
248 | selectItems = [];
249 | }
250 | selectItems.forEach((item) => {
251 | if (item[component.valueProperty] === data) {
252 | data = item;
253 | }
254 | });
255 | }
256 | return data;
257 | // TODO: implement url and resource view.
258 | case 'url':
259 | case 'resource':
260 | default:
261 | return data;
262 | }
263 | };
264 | if (component.multiple && Array.isArray(data)) {
265 | return data.map(getItem).reduce(function(prev, item) {
266 | var value;
267 | if (typeof item === 'object') {
268 | value = ({raw(interpolate(component.template, {item}))});
269 | }
270 | else {
271 | value = item;
272 | }
273 | return (prev === '' ? '' : ', ') + value;
274 | }, '');
275 | }
276 | else {
277 | var item = getItem(data);
278 | var value;
279 | if (typeof item === 'object') {
280 | value = ({raw(interpolate(component.template, {item}))});
281 | }
282 | else {
283 | value = item;
284 | }
285 | return value;
286 | }
287 | }
288 | }
289 |
--------------------------------------------------------------------------------
/src/components/FormComponents/select/styles.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/formio/react-native-formio/56a3f3b801b56c8f84e39ed104b640ee1870bb98/src/components/FormComponents/select/styles.js
--------------------------------------------------------------------------------
/src/components/FormComponents/selectboxes/SelectBoxes.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {View, StyleSheet, Text} from 'react-native';
3 | import {CheckBox, FormLabel} from 'react-native-elements';
4 | import ValueComponent from '../sharedComponents/Value';
5 | import DeviceInfo from 'react-native-device-info';
6 | import Tooltip from '../sharedComponents/Tooltip';
7 |
8 | export default class SelectBox extends ValueComponent {
9 | constructor(props) {
10 | super(props);
11 |
12 | this.getInitialValue = this.getInitialValue.bind(this);
13 | this.selectBoxes = this.selectBoxes.bind(this);
14 | this.getElements = this.getElements.bind(this);
15 | this.getValueDisplay = this.getValueDisplay.bind(this);
16 | this.onChangeItems = this.onChangeItems.bind(this);
17 | }
18 |
19 | getInitialValue() {
20 | return {};
21 | }
22 |
23 | onChangeItems(item) {
24 | const selectedItems = this.state.value && this.state.value.item ? this.state.value.item : [];
25 | const isSelected = Object.keys(selectedItems).find((i) => i === item.value);
26 | if (isSelected && selectedItems[isSelected] === true) {
27 | selectedItems[item.value] = false;
28 | }
29 | else {
30 | selectedItems[item.value] = true;
31 | }
32 | this.setValue(selectedItems);
33 | }
34 |
35 | elementLayout(position) {
36 | switch (position) {
37 | case 'top':
38 | return {
39 | flexDirection: 'column',
40 | };
41 | case 'left-left':
42 | case 'left-right':
43 | return {
44 | flexDirection: 'row',
45 | alignItems: 'flex-start',
46 | };
47 | case 'right-left':
48 | case 'right-right':
49 | return {
50 | flexDirection: 'row-reverse',
51 | flex: 1,
52 | };
53 | case 'bottom':
54 | return {
55 | flexDirection: 'column-reverse',
56 | };
57 | default:
58 | return {
59 | flexDirection: 'column',
60 | };
61 | }
62 | }
63 |
64 | selectBoxes() {
65 | const {component} = this.props;
66 | const boxesStyles = StyleSheet.create({
67 | boxesWrapper: {
68 | flex: 1,
69 | flexDirection: component.inline ? 'row' : 'column',
70 | marginHorizontal: component.inline ? 20 : 0,
71 | borderBottomWidth: 0.5,
72 | borderTopWidth: 0.5,
73 | borderColor: this.props.colors.borderColor,
74 | },
75 | checkbox: { //eslint-disable-line react-native/no-color-literals
76 | backgroundColor: 'transparent',
77 | zIndex: 0,
78 | margin: 0,
79 | borderWidth: 0,
80 | }
81 | });
82 |
83 | return (
84 |
85 | {component.values.map(item => {
86 | const selectedItems = this.state.value && this.state.value.item ? this.state.value.item : [];
87 | const isSelected = Object.keys(selectedItems).find((i) => i === item.value);
88 | const isChecked = isSelected && selectedItems[isSelected] === true;
89 | const onSelect = () => this.onChangeItems(item);
90 |
91 | return (
92 | );
105 | })}
106 |
107 | );
108 | }
109 |
110 | getElements() {
111 | const {component} = this.props;
112 | const selectBoxStyle = StyleSheet.create({
113 | wrapper: {
114 | flex: 1,
115 | marginHorizontal: 20,
116 | marginTop: 20,
117 | },
118 | label: {
119 | flexWrap: 'wrap',
120 | color: this.props.theme.Label.color,
121 | fontSize: DeviceInfo.isTablet() ? this.props.theme.Label.fontSize : 12,
122 | },
123 | mainElement: this.elementLayout(this.props.component.labelPosition),
124 | labelWrapper: {
125 | flexDirection: 'row',
126 | marginBottom: this.props.component.labelPosition === 'top' ? 10 : 0,
127 | marginTop: this.props.component.labelPosition === 'bottom' ? 10 : 0,
128 | marginRight: this.props.component.labelPosition === 'left-left' || this.props.component.labelPosition === 'left-right' ? 10 : 0,
129 | marginLeft: this.props.component.labelPosition === 'right-left' || this.props.component.labelPosition === 'right-right' ? 10 : 0,
130 | },
131 | descriptionText: {
132 | fontSize: DeviceInfo.isTablet() ? 12 : 10,
133 | marginLeft: 20,
134 | marginTop: 10,
135 | },
136 | });
137 |
138 | const inputLabel = (
139 |
140 | {component.label && !component.hideLabel ? component.label : ''}
141 | );
142 |
143 | const requiredInline = (
144 | {!component.label && component.validate && component.validate.required ? '*': ''}
145 | );
146 |
147 | return (
148 |
149 |
150 |
151 | {inputLabel} {requiredInline}
152 | {component.tooltip && }
157 |
158 | {this.selectBoxes()}
159 |
160 | {component.description && {component.description}}
161 |
162 | );
163 | }
164 |
165 | getValueDisplay(component, data) {
166 | if (!data) return '';
167 |
168 | return Object.keys(data)
169 | .filter((key) => {
170 | return data[key];
171 | })
172 | .map((data) => {
173 | component.values.forEach((item) => {
174 | if (item.value === data) {
175 | data = item.label;
176 | }
177 | });
178 | return data;
179 | })
180 | .join(', ');
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/src/components/FormComponents/sharedComponents/Base.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {deepEqual} from '../../../util';
3 | import PropTypes from 'prop-types';
4 |
5 | export default class BaseComponent extends React.Component {
6 | shouldComponentUpdate(nextProps, nextState) {
7 | // If a new value is set within state, re-render.
8 | if (this.state && this.state.hasOwnProperty('value') && this.state.value !== nextState.value) {
9 | return true;
10 | }
11 |
12 | // If the pristineness changes without a value change, re-render.
13 | if (this.state && this.state.hasOwnProperty('isPristine') && this.state.isPristine !== nextState.isPristine) {
14 | return true;
15 | }
16 |
17 | // If a new value is passed in, re-render.
18 | if (this.props.value !== nextProps.value) {
19 | return true;
20 | }
21 |
22 | // If the component definition change, re-render.
23 | if (!deepEqual(this.props.component, nextProps.component)) {
24 | return true;
25 | }
26 |
27 | // If component has a custom data source, always recalculate
28 | if (this.props.component.hasOwnProperty('refreshOn') && this.props.component.refreshOn) {
29 | return true;
30 | }
31 |
32 | if (this.state && this.state.hasOwnProperty('searchTerm') && this.state.searchTerm !== nextState.searchTerm) {
33 | return true;
34 | }
35 |
36 | if (this.state && this.state.hasOwnProperty('selectItems') && !deepEqual(this.state.selectItems, nextState.selectItems)) {
37 | return true;
38 | }
39 |
40 | if (this.state && this.state.hasOwnProperty('open') && this.state.open !== nextState.open) {
41 | return true;
42 | }
43 |
44 | if (this.state && this.state.hasOwnProperty('showSignaturePad') && this.state.showSignaturePad !== nextState.showSignaturePad) {
45 | return true;
46 | }
47 |
48 | if (this.props.component.hasOwnProperty('disableOnInvalid') && this.props.isFormValid !== nextProps.isFormValid) {
49 | return true;
50 | }
51 |
52 | return false;
53 | }
54 | }
55 |
56 | BaseComponent.propTypes = {
57 | component: PropTypes.any,
58 | isFormValid: PropTypes.any,
59 | value: PropTypes.any
60 | };
61 |
--------------------------------------------------------------------------------
/src/components/FormComponents/sharedComponents/Input.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import MultiComponent from './Multi';
3 | import {StyleSheet} from 'react-native';
4 | import DeviceInfo from 'react-native-device-info';
5 | import {TextMask} from 'react-text-mask-hoc/ReactNative';
6 | import {clone} from 'lodash';
7 | import {FormInput} from 'react-native-elements';
8 | import PropTypes from 'prop-types';
9 |
10 | export default class InputComponent extends MultiComponent {
11 | constructor(props) {
12 | super(props);
13 | this.timeout = null;
14 |
15 | this.customState =this.customState.bind(this);
16 | this.triggerChange =this.triggerChange.bind(this);
17 | this.onChangeInput =this.onChangeInput.bind(this);
18 | this.onBlur = this.onBlur.bind(this);
19 | this.getInputMask =this.getInputMask.bind(this);
20 | this.getSingleElement =this.getSingleElement.bind(this);
21 | }
22 |
23 | customState(state) {
24 | state.hasChanges = false;
25 | return state;
26 | }
27 |
28 | triggerChange() {
29 | if (typeof this.props.onChange === 'function' && this.state.hasChanges) {
30 | this.props.onChange(this);
31 | this.setState({
32 | hasChanges: false,
33 | }, () => this.props.onChange(this));
34 | }
35 | }
36 |
37 | onChangeInput(value) {
38 | // Allow components to respond to onChange event.
39 | if (typeof this.onChangeCustom === 'function') {
40 | value = this.onChangeCustom(value);
41 | }
42 |
43 | clearTimeout(this.timeout);
44 | this.timeout = setTimeout(() => {
45 | this.triggerChange();
46 | }, 500);
47 |
48 | let newValue;
49 | if (Array.isArray(this.state.value)) {
50 | // Clone so we keep state immutable.
51 | newValue = clone(this.state.value);
52 | }
53 | else {
54 | newValue = value;
55 | }
56 | const validatedValue = this.validate(newValue);
57 |
58 | this.setState({
59 | isPristine: false,
60 | hasChanges: true,
61 | value: validatedValue,
62 | });
63 | }
64 |
65 | onBlur() {
66 | this.triggerChange();
67 | }
68 | /**
69 | * Returns an input mask that is compatible with the input mask library.
70 | * @param {string} mask - The Form.io input mask.
71 | * @returns {Array} - The input mask for the mask library.
72 | */
73 | getInputMask(mask) {
74 | if (typeof this.customMask === 'function') {
75 | return this.customMask();
76 | }
77 | if (!mask) {
78 | return false;
79 | }
80 | if (mask instanceof Array) {
81 | return mask;
82 | }
83 | let maskArray = [];
84 | for (let i=0; i < mask.length; i++) {
85 | switch (mask[i]) {
86 | case '9':
87 | maskArray.push(/\d/);
88 | break;
89 | case 'a':
90 | case 'A':
91 | maskArray.push(/[a-zA-Z]/);
92 | break;
93 | case '*':
94 | maskArray.push(/[a-zA-Z0-9]/);
95 | break;
96 | default:
97 | maskArray.push(mask[i]);
98 | break;
99 | }
100 | }
101 | return maskArray;
102 | }
103 |
104 | getSingleElement(value, index, error) {
105 | const themeStyle = this.props.theme.Input;
106 | const style = StyleSheet.create({
107 | container: {
108 | borderColor: error ? themeStyle.borderColorOnError : themeStyle.borderColor,
109 | },
110 | input: {
111 | color: themeStyle.color,
112 | fontSize: themeStyle.fontSize,
113 | lineHeight: themeStyle.lineHeight,
114 | flex: 1,
115 | maxWidth: DeviceInfo.isTablet() ? 580 : 210,
116 | }
117 | });
118 |
119 | index = index || 0;
120 | const item = typeof value === 'string' ? value : value.item;
121 | const {component, name, readOnly} = this.props;
122 | const mask = component.inputMask || '';
123 | const properties = {
124 | type: component.inputType !== 'number' ? component.inputType : 'text',
125 | key: index,
126 | id: component.key,
127 | 'data-index': index,
128 | name: name,
129 | shake: true,
130 | defaultValue: item,
131 | value: item,
132 | editable: !readOnly,
133 | placeholder: component.placeholder,
134 | placeholderTextColor: this.props.theme.Input.placeholderTextColor,
135 | onChangeText: this.onChangeInput,
136 | onBlur: this.onBlur,
137 | ref: input => this.element = input
138 | };
139 |
140 | if (mask || component.type === 'currency' || component.type === 'number') {
141 | properties.inputMode = 'number';
142 | properties.mask = this.getInputMask(mask);
143 | properties.placeholderChar = '_';
144 | properties.guide = true;
145 |
146 | return ();
147 | }
148 | else {
149 | return ();
150 | }
151 | }
152 | }
153 |
154 | InputComponent.propTypes = {
155 | component: PropTypes.any,
156 | name: PropTypes.string,
157 | theme: PropTypes.object,
158 | readOnly: PropTypes.bool,
159 | onChange: PropTypes.func
160 | };
161 |
--------------------------------------------------------------------------------
/src/components/FormComponents/sharedComponents/Multi.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import clone from 'lodash/clone';
3 | import PropTypes from 'prop-types';
4 | import {View, StyleSheet} from 'react-native';
5 | import Tooltip from './Tooltip';
6 | import ValueComponent from './Value';
7 | import DeviceInfo from 'react-native-device-info';
8 | import {
9 | Icon,
10 | Button,
11 | FormValidationMessage,
12 | Text,
13 | FormLabel
14 | } from 'react-native-elements';
15 |
16 | export default class MultiComponent extends ValueComponent {
17 | constructor(props) {
18 | super(props);
19 | this.addFieldValue = this.addFieldValue.bind(this);
20 | this.removeFieldValue = this.removeFieldValue.bind(this);
21 | this.getTableRows = this.getTableRows.bind(this);
22 | this.getElements = this.getElements.bind(this);
23 | }
24 |
25 | addFieldValue() {
26 | let value = clone(this.state.value);
27 | value.push(this.props.component.defaultValue);
28 | this.setState({
29 | isPristine: false,
30 | value: value,
31 | }, () => {
32 | if (typeof this.props.onChange === 'function') {
33 | this.props.onChange(this);
34 | }
35 | });
36 | }
37 |
38 | removeFieldValue(id) {
39 | let value = clone(this.state.value);
40 | value.splice(id, 1);
41 | this.setState({
42 | isPristine: false,
43 | value: value,
44 | }, () => {
45 | if (typeof this.props.onChange === 'function') {
46 | this.props.onChange(this);
47 | }
48 | });
49 | }
50 |
51 | getTableRows(value, id, style) {
52 | const error = this.state.isPristine || value.isValid ? false : true;
53 | const Element = this.getSingleElement(value, id, error);
54 | const errorText = error ? ( {value.errorMessage}) : null;
55 | return (
56 |
57 | {Element}
58 |
59 | {errorText}
60 |
61 | );
62 | }
63 |
64 | elementLayout(position) {
65 | switch (position) {
66 | case 'top':
67 | return {
68 | flexDirection: 'column',
69 | };
70 | case 'left-left':
71 | case 'left-right':
72 | return {
73 | flexDirection: 'row',
74 | alignItems: 'flex-start',
75 | };
76 | case 'right-left':
77 | case 'right-right':
78 | return {
79 | flexDirection: 'row-reverse',
80 | marginHorizontal: 20,
81 | };
82 | case 'bottom':
83 | return {
84 | flexDirection: 'column-reverse',
85 | };
86 | default:
87 | return {
88 | flexDirection: 'column',
89 | };
90 | }
91 | }
92 |
93 | getElements() {
94 | const multiStyles = StyleSheet.create({
95 | fieldWrapper: {
96 | flex: 1,
97 | },
98 | mainElement: this.elementLayout(this.props.component.labelPosition),
99 | labelWrapper: {
100 | flexDirection: 'row',
101 | marginTop: this.props.component.labelPosition === 'top' || this.props.component.labelPosition === 'bottom' ? 0 : 15,
102 | marginRight: this.props.component.labelPosition === 'left-left' || this.props.component.labelPosition === 'left-right' ? 10 : 0,
103 | },
104 | errorText: {
105 | alignSelf: 'flex-end',
106 | fontSize: 10,
107 | color: this.props.colors.errorColor
108 | },
109 | descriptionText: {
110 | fontSize: DeviceInfo.isTablet() ? 12 : 10,
111 | marginLeft: 20,
112 | marginTop: 10,
113 | },
114 | labelStyle: {
115 | flexWrap: 'wrap',
116 | maxWidth: DeviceInfo.isTablet() ? 580 : 210,
117 | color: this.props.theme.Label.color,
118 | fontSize: DeviceInfo.isTablet() ? this.props.theme.Label.fontSize : 12,
119 | }
120 | });
121 |
122 | const {component} = this.props;
123 | let Component;
124 | const requiredInline = ((component.hideLabel === true || component.label === '' ||
125 | !component.label) && component.validate && component.validate.required ? : {''});
126 |
127 | const prefix = ({component.prefix});
128 | const suffix = ({component.suffix});
129 | const inputLabel = (component.label && !component.hideLabel ?
130 | {requiredInline} {prefix} {component.label} {suffix} : null);
131 |
132 | const data = this.state.value || {};
133 | if (component.multiple) {
134 | const rows = data.map((value, id) => {
135 | this.getTableRows(value, id, multiStyles);
136 | });
137 | Component = (
138 |
139 | {component.label}
140 | {rows}
141 |
142 |
143 | );
144 | }
145 | else {
146 | const error = this.state.isPristine || data.isValid ? false : true;
147 | const Element = this.getSingleElement(data, 0, error);
148 | const errorText = error ? ({data.errorMessage}) : null;
149 |
150 | Component = (
151 |
152 |
153 |
154 | {inputLabel}
155 | {component.tooltip && }
160 |
161 | {Element}
162 |
163 | {errorText}
164 | {component.description && {component.description}}
165 |
166 | );
167 | }
168 | return Component;
169 | }
170 | }
171 |
172 | MultiComponent.propTypes = {
173 | component: PropTypes.any,
174 | onChange: PropTypes.func,
175 | theme: PropTypes.object,
176 | colors: PropTypes.object
177 | };
178 |
--------------------------------------------------------------------------------
/src/components/FormComponents/sharedComponents/Select.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {View, Text, StyleSheet} from 'react-native';
3 | import Icon from 'react-native-vector-icons/dist/FontAwesome';
4 | import {Dropdown} from 'react-native-material-dropdown';
5 | import MultiSelect from 'react-native-multiple-select';
6 | import {FormLabel} from 'react-native-elements';
7 | import get from 'lodash/get';
8 | import isEqual from 'lodash/isEqual';
9 | import PropTypes from 'prop-types';
10 | import DeviceInfo from 'react-native-device-info';
11 | import ValueComponent from './Value';
12 | import Tooltip from './Tooltip';
13 | import colors from '../../../defaultTheme/colors';
14 |
15 | export default class SelectComponent extends ValueComponent {
16 | constructor(props) {
17 | super(props);
18 | this.data = {...this.props.data};
19 | this.state = {
20 | selectItems: [],
21 | searchTerm: '',
22 | hasNextPage: false,
23 | open: false
24 | };
25 | this.willReceiveProps = this.willReceiveProps.bind(this);
26 | this.onChangeSelect = this.onChangeSelect.bind(this);
27 | this.onSearch = this.onSearch.bind(this);
28 | this.valueField = this.valueField.bind(this);
29 | this.getElements = this.getElements.bind(this);
30 | this.onToggle = this.onToggle.bind(this);
31 | }
32 |
33 | willReceiveProps(nextProps) {
34 | if (this.props.component.refreshOn && !nextProps.formPristine) {
35 | const refreshOn = this.props.component.refreshOn;
36 | this.refresh = false;
37 | if (refreshOn === 'data') {
38 | if (!isEqual(this.data, nextProps.data)) {
39 | this.refresh = true;
40 | }
41 | }
42 | else {
43 | if ((!this.data.hasOwnProperty(refreshOn) && nextProps.hasOwnProperty(refreshOn)) || this.data[refreshOn] !== nextProps.data[refreshOn]) {
44 | this.refresh = true;
45 | }
46 | else if (nextProps && nextProps.row && nextProps.row.hasOwnProperty(refreshOn) && this.props.row[refreshOn] !== nextProps.row[refreshOn]) {
47 | this.refresh = true;
48 | }
49 | }
50 | if (this.refresh && this.props.component.clearOnRefresh) {
51 | this.setValue(this.getDefaultValue());
52 | }
53 | }
54 | if (this.refresh) {
55 | this.refreshItems();
56 | this.refresh = false;
57 | }
58 | this.data = {...nextProps.data};
59 | }
60 |
61 | valueField() {
62 | let valueFieldItem = this.props.component.valueProperty || 'value';
63 | if (typeof this.getValueField === 'function') {
64 | valueFieldItem = this.getValueField();
65 | }
66 | return valueFieldItem;
67 | }
68 |
69 | onChangeSelect(selected) {
70 | let value;
71 | if (this.props.component.multiple) {
72 | value = this.state.selectItems.filter((i) => selected.includes(i.label))
73 | .map((i) => i.value);
74 | }
75 | else {
76 | value = selected;
77 | }
78 | if (Array.isArray(value) && this.valueField()) {
79 | value.forEach((val, index) => {
80 | value[index] = (typeof val === 'object' ? get(val, this.valueField()) : val);
81 | });
82 | }
83 | else if (typeof value === 'object' && this.valueField()) {
84 | value = get(value, this.valueField());
85 | }
86 | this.setValue(value);
87 | }
88 |
89 | onSearch(text) {
90 | this.setState({
91 | searchTerm: text
92 | });
93 | if (typeof this.refreshItems === 'function') {
94 | this.refreshItems(text);
95 | }
96 | }
97 |
98 | elementLayout(position) {
99 | switch (position) {
100 | case 'top':
101 | return {
102 | flexDirection: 'column',
103 | };
104 | case 'left-left':
105 | case 'left-right':
106 | return {
107 | flexDirection: 'row',
108 | alignItems: 'flex-start',
109 | };
110 | case 'right-left':
111 | case 'right-right':
112 | return {
113 | flexDirection: 'row-reverse',
114 | flex: 1,
115 | marginHorizontal: 20,
116 | };
117 | case 'bottom':
118 | return {
119 | flexDirection: 'column-reverse',
120 | };
121 | default:
122 | return {
123 | flexDirection: 'column',
124 | };
125 | }
126 | }
127 |
128 | onToggle(isOpen) {
129 | this.props.onEvent('selectToggle', this, isOpen);
130 | this.setState(prevState => {
131 | prevState.open = isOpen;
132 | return prevState;
133 | });
134 | }
135 |
136 | getElements() {
137 | const selectStyle = StyleSheet.create({
138 | wrapper: {
139 | flex: 1,
140 | marginHorizontal: 20,
141 | marginTop: 20
142 | },
143 | container: {
144 | zIndex: 1000,
145 | },
146 | containerSingle: {
147 | flex: 1,
148 | marginTop: 0,
149 | marginBottom: 15,
150 | },
151 | label: {
152 | maxWidth: DeviceInfo.isTablet() ? 580 : 210,
153 | color: this.props.theme.Label.color,
154 | fontSize: DeviceInfo.isTablet() ? this.props.theme.Label.fontSize : 12,
155 | },
156 | mainElement: this.elementLayout(this.props.component.labelPosition),
157 | labelWrapper: {
158 | flexDirection: 'row',
159 | marginTop: this.props.component.labelPosition === 'top' || this.props.component.labelPosition === 'bottom' ? 0 : 40,
160 | marginRight: this.props.component.labelPosition === 'left-left' || this.props.component.labelPosition === 'left-right' ? 10 : 0,
161 | },
162 | list: {
163 | backgroundColor: colors.mainBackground,
164 | },
165 | descriptionText: {
166 | fontSize: DeviceInfo.isTablet() ? 12 : 10,
167 | marginLeft: 20,
168 | marginTop: 10,
169 | },
170 | });
171 |
172 | const {component} = this.props;
173 | const labelText = component.label && !component.hideLabel ? component.label : '';
174 | const requiredInline = (!component.label && component.validate && component.validate.required ? : '');
175 | const multiMode = component.multiple;
176 | let values;
177 | let Element;
178 |
179 | const inputLabel = labelText ?
180 | {requiredInline} {component.label} : null;
181 |
182 | if (multiMode) {
183 | values = this.state.value && this.state.value.item ? this.state.selectItems.filter((i) => this.state.value.item.includes(i.value)) : [];
184 | Element = (
185 | v.label)}
197 | tagRemoveIconColor={this.props.colors.primary1Color}
198 | selectedItemTextColor={this.props.colors.primary1Color}
199 | selectedItemIconColor={this.props.colors.primary1Color}
200 | />
201 | );
202 | }
203 | else {
204 | values = this.state.value ? this.state.value.item : '';
205 | Element = (
206 |
218 | );
219 | }
220 | return (
221 |
222 |
223 |
224 | {inputLabel}
225 | {component.tooltip && }
236 |
237 | {Element}
238 |
239 | {component.description && {component.description}}
240 |
241 | );
242 | }
243 | }
244 |
245 | SelectComponent.propTypes = {
246 | data: PropTypes.any,
247 | component: PropTypes.any,
248 | row: PropTypes.object,
249 | readOnly: PropTypes.bool,
250 | value: PropTypes.any,
251 | onEvent: PropTypes.func,
252 | };
253 |
--------------------------------------------------------------------------------
/src/components/FormComponents/sharedComponents/Tooltip.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import RNTooltip from 'rn-tooltip';
4 | import {Text, StyleSheet} from 'react-native';
5 | import {Icon} from 'react-native-elements';
6 |
7 | const Tooltip = (props) => {
8 | const customStyles = props.styles || {};
9 | const styles = StyleSheet.create({
10 | tooltipText: {
11 | flexWrap: 'wrap',
12 | color: props.color,
13 | },
14 | tooltipIcon: {
15 | marginLeft: -20,
16 | marginTop: 13,
17 | ...customStyles.icon
18 | },
19 | });
20 | return (
21 | {props.text}}
23 | backgroundColor={props.backgroundColor}
24 | height={props.text.length < 20 ? 70 : props.text.length + 20}
25 | width={props.text.length < 20 ? 100 : props.text.length + 140}
26 | >
27 |
28 |
29 | );
30 | };
31 |
32 | Tooltip.propTypes = {
33 | color: PropTypes.string,
34 | backgroundColor: PropTypes.string,
35 | text: PropTypes.string,
36 | styles: PropTypes.any,
37 | };
38 |
39 | export default Tooltip;
40 |
--------------------------------------------------------------------------------
/src/components/FormComponents/sharedComponents/Value.js:
--------------------------------------------------------------------------------
1 | import {deepEqual} from '../../../util';
2 | import clone from 'lodash/clone';
3 | import BaseComponent from './Base';
4 | import PropTypes from 'prop-types';
5 | import {validate} from '../componentUtils/validators';
6 | import {safeSingleToMultiple} from '../componentUtils/safeSingleToMultiple';
7 | import {getDefaultValue} from '../componentUtils/getDefaultValue';
8 |
9 | export default class ValueComponent extends BaseComponent {
10 | constructor(props) {
11 | super(props);
12 | const value = getDefaultValue(this.props.value, this.props.component, this.getInitialValue, this.onChangeCustom);
13 | const valid = this.validate(value);
14 | this.state = {
15 | open: false,
16 | showSignaturePad: false,
17 | value: value,
18 | isValid: valid.isValid,
19 | errorType: valid.errorType,
20 | errorMessage: valid.errorMessage,
21 | isPristine: true
22 | };
23 | if (typeof this.customState === 'function') {
24 | this.state = this.customState(this.state);
25 | }
26 | this.data = {};
27 | this.validate = this.validate.bind(this);
28 | this.onChange = this.onChange.bind(this);
29 | this.setValue = this.setValue.bind(this);
30 | this.getDisplay = this.getDisplay.bind(this);
31 | this.getElements = this.getElements.bind(this);
32 | }
33 |
34 | componentDidMount() {
35 | this.unmounting = false;
36 | if (!this.props.options || !this.props.options.skipInit || !this.props.options.isInit) {
37 | this.setValue(this.state.value, null, true);
38 | }
39 | if (typeof this.props.attachToForm === 'function') {
40 | this.props.attachToForm(this);
41 | }
42 | }
43 |
44 | componentWillUnmount() {
45 | this.unmounting = true;
46 | if (typeof this.props.detachFromForm === 'function') {
47 | this.props.detachFromForm(this);
48 | }
49 | }
50 |
51 | componentDidUpdate(prevProps) {
52 | const {component} = prevProps;
53 | let value;
54 | if (component.hasOwnProperty('calculateValue') && component.calculateValue) {
55 | if (!deepEqual(this.data, this.props.data)) {
56 | this.data = clone(this.props.data);
57 | try {
58 | const result = eval('(function(data, row) { const value = [];' + component.calculateValue.toString() + '; return value; })(this.data, this.props.row)');
59 | if (this.state.value !== result) {
60 | this.setValue(result);
61 | }
62 | }
63 | catch (e) {
64 | /* eslint-disable no-console */
65 | console.warn('An error occurred calculating a value for ' + component.key, e);
66 | /* eslint-enable no-console */
67 | }
68 | }
69 | }
70 |
71 | if (this.props.value && (!prevProps.value || prevProps.value !== this.props.value)) {
72 | value = safeSingleToMultiple(this.props.value, this.props.component);
73 | }
74 |
75 | // This occurs when a datagrid row is deleted.
76 | let defaultValue = getDefaultValue(value, this.props.component, this.getInitialValue, this.onChangeCustom);
77 | if (value === null && this.state.value !== defaultValue) {
78 | value = defaultValue;
79 | this.setState({
80 | isPristine: true
81 | });
82 | }
83 | if (typeof value !== 'undefined' && value !== null) {
84 | const valid = this.validate(value);
85 | this.setState({
86 | value: valid,
87 | isValid: valid.isValid,
88 | errorType: valid.errorType,
89 | errorMessage: valid.errorMessage
90 | });
91 | }
92 | if (typeof this.willReceiveProps === 'function') {
93 | this.willReceiveProps(this.props);
94 | }
95 | }
96 |
97 | validate(value) {
98 | return validate(value, this.props.component, this.props.data, this.validateCustom);
99 | }
100 |
101 | onChange(event) {
102 | let value = event.nativeEvent.text;
103 | // Allow components to respond to onChange event.
104 | if (typeof this.props.onChangeCustom === 'function') {
105 | value = this.props.onChangeCustom(value);
106 | }
107 | const index = (this.props.component.multiple ? event.nativeEvent.target : null);
108 | this.setValue(value, index);
109 | }
110 |
111 | setValue(value, index, pristine) {
112 | if (index === undefined) {
113 | index = null;
114 | }
115 | let newValue;
116 | if (index !== null && Array.isArray(this.state.value)) {
117 | // Clone so we keep state immutable.
118 | newValue = clone(this.state.value);
119 | newValue[index] = value;
120 | }
121 | else {
122 | newValue = value;
123 | }
124 | const validatedValue = this.validate(newValue);
125 | this.setState({
126 | isPristine: !!pristine,
127 | value: validatedValue,
128 | }, () => {
129 | if (typeof this.props.onChange === 'function') {
130 | if (!this.state.isPristine || (this.props.value && this.props.value.item !== this.state.value.item)) {
131 | this.props.onChange(this);
132 | }
133 | }
134 | });
135 | }
136 |
137 | getDisplay(component, value) {
138 | if (typeof this.getValueDisplay === 'function') {
139 | if (Array.isArray(value) && component.multiple && component.type !== 'file') {
140 | return value.map(this.getValueDisplay.bind(null, component)).join(', ');
141 | }
142 | else {
143 | return this.getValueDisplay(component, value);
144 | }
145 | }
146 | if (Array.isArray(value)) {
147 | return value.join(', ');
148 | }
149 | // If this is still an object, something went wrong and we don't know what to do with it.
150 | if (typeof value === 'object') {
151 | return '[object Object]';
152 | }
153 | return value;
154 | }
155 |
156 | render() {
157 | let element;
158 | if (typeof this.props.onElementRender === 'function') {
159 | element = this.props.onElementRender(this, element);
160 | }
161 | element = this.getElements();
162 | return element;
163 | }
164 | }
165 |
166 | ValueComponent.propTypes = {
167 | data: PropTypes.any,
168 | options: PropTypes.object,
169 | component: PropTypes.any,
170 | value: PropTypes.any,
171 | row: PropTypes.any,
172 | onChange: PropTypes.func,
173 | onElementRender: PropTypes.func,
174 | attachToForm: PropTypes.func,
175 | detachFromForm: PropTypes.func,
176 | };
177 |
--------------------------------------------------------------------------------
/src/components/FormComponents/signature/Signature.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {View, Image, Text, Modal} from 'react-native';
3 | import {Button} from 'react-native-elements';
4 | import ValueComponent from '../sharedComponents/Value';
5 | import SignatureCapture from 'react-native-signature-capture';
6 | import DeviceInfo from 'react-native-device-info';
7 | import styles from './styles';
8 |
9 | const isTablet = DeviceInfo.isTablet();
10 |
11 | export default class Signature extends ValueComponent {
12 | constructor(props) {
13 | super(props);
14 | this.toggleSignaturePad = this.toggleSignaturePad.bind(this);
15 | this.willReceiveProps = this.willReceiveProps.bind(this);
16 | this.saveSignature = this.saveSignature.bind(this);
17 | this.clearSignature = this.clearSignature.bind(this);
18 | this.onEnd = this.onEnd.bind(this);
19 | this.getElements = this.getElements.bind(this);
20 | this.signature = null;
21 | }
22 |
23 | componentDidMount() {
24 | if (!this.signature) {
25 | return;
26 | }
27 | if (this.state.value) {
28 | this.signature.fromDataURL(this.state.value);
29 | }
30 | else {
31 | this.signature.resetImage();
32 | }
33 | }
34 |
35 | willReceiveProps(nextProps) {
36 | if (!this.signature) {
37 | return;
38 | }
39 | if (this.props.value !== nextProps.value) {
40 | this.signature.fromDataURL(nextProps.value);
41 | }
42 | }
43 |
44 | onEnd(image) {
45 | if (!image || !image.encoded) {
46 | return;
47 | }
48 | const signature = `data:image/png;base64,${image.encoded}`;
49 | this.setValue(signature);
50 | this.toggleSignaturePad();
51 | }
52 |
53 | toggleSignaturePad() {
54 | this.setState({
55 | showSignaturePad: !this.state.showSignaturePad
56 | });
57 | }
58 |
59 | saveSignature() {
60 | this.signature.saveImage();
61 | }
62 |
63 | clearSignature() {
64 | this.signature.resetImage();
65 | this.setValue(null);
66 | }
67 |
68 | getElements() {
69 | const {component} = this.props;
70 |
71 | if (this.props.readOnly) {
72 | const image = typeof this.state.value === 'object' ? this.state.value.item : this.state.value;
73 | return (
74 |
75 |
80 |
81 | );
82 | }
83 | return (
84 |
85 | {this.state.value && this.state.value.item &&
86 |
91 | }
92 |
99 |
106 |
107 | {isTablet &&
108 |
114 |
120 | }
121 | {
127 | this.signature = ref;
128 | }}
129 | onSaveEvent={this.onEnd}
130 | saveImageFileInExtStorage={false}
131 | showNativeButtons={!isTablet ? true : false}
132 | rotateClockwise={!isTablet}
133 | minStrokeWidth={2}
134 | maxStrokeWidth={8}
135 | viewMode={isTablet ? 'portrait' : 'landscape'}
136 | />
137 |
138 | {component.footer}
139 |
145 |
146 |
147 |
148 |
149 | );
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/src/components/FormComponents/signature/styles.js:
--------------------------------------------------------------------------------
1 | import {StyleSheet} from 'react-native';
2 | import DeviceInfo from 'react-native-device-info';
3 |
4 | const isTablet = DeviceInfo.isTablet();
5 |
6 | const border = '#000033';
7 |
8 | const styles = StyleSheet.create({
9 | signatureWrapper: {
10 | marginTop: 10,
11 | },
12 | imageWrapper: {
13 | marginTop: 10,
14 | },
15 | signatureButton: {
16 | width: 150,
17 | marginTop: isTablet ? 0 : 10,
18 | marginHorizontal: 0,
19 | paddingHorizontal: 0,
20 | },
21 | signature: {
22 | marginLeft: 10,
23 | width: 300,
24 | height: 150,
25 | },
26 | signaturePadWrapper: {
27 | flex: 1,
28 | },
29 | buttonWrapper: {
30 | flexDirection: 'row',
31 | justifyContent: 'space-between',
32 | },
33 | signaturePad: {
34 | flex: 1,
35 | borderColor: border,
36 | borderWidth: 1,
37 | },
38 | modalFooter: {
39 | flexDirection: 'row',
40 | alignItems: 'center',
41 | justifyContent: 'space-between',
42 | margin: 5,
43 | },
44 | modalFooterText: {
45 | fontSize: 18,
46 | marginLeft: 20,
47 | }
48 | });
49 |
50 | export default styles;
51 |
--------------------------------------------------------------------------------
/src/components/FormComponents/switch/Switch.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {View, Switch, Text, StyleSheet} from 'react-native';
3 | import ValueComponent from '../sharedComponents/Value';
4 |
5 | export default class SwitchComponent extends ValueComponent {
6 |
7 | constructor(props) {
8 | super(props);
9 |
10 | this.onChange = this.onChange.bind(this);
11 | this.getElements = this.getElements.bind(this);
12 | this.getValueDisplay = this.getValueDisplay.bind(this);
13 | }
14 |
15 | onChange(checked) {
16 | this.setValue(checked);
17 | }
18 |
19 | getElements() {
20 | const switchStyle = StyleSheet.create({
21 | wrapper: {
22 | flex: 1,
23 | marginTop: 10,
24 | marginLeft: 20,
25 | flexDirection: 'row'
26 | },
27 | label: {
28 | fontSize: 15,
29 | marginTop: 8,
30 | marginLeft: 10,
31 | color: this.props.colors.textColor,
32 | },
33 | });
34 |
35 | const {component} = this.props;
36 | const {value} = this.state;
37 |
38 | return (
39 |
40 |
46 |
47 | {!(component.hideLabel && component.datagridLabel === false) ? component.label : ''}
48 |
49 |
50 | );
51 | }
52 |
53 | getValueDisplay(component, data) {
54 | return data ? 'Yes' : 'No';
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/components/FormComponents/tests/Columns.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import renderer from 'react-test-renderer';
3 | import Columns from '../columns/Columns';
4 | import colors from '../../../defaultTheme/colors';
5 | import theme from '../../../defaultTheme';
6 |
7 | import components from '../../../../test/forms/componentSpec';
8 |
9 | describe('Columns', () => {
10 | describe.only(' Columns component', () => {
11 | var component= {
12 | 'lockKey': true,
13 | 'key': 'columns',
14 | 'conditional': {
15 | 'eq': '',
16 | 'when': null,
17 | 'show': ''
18 | },
19 | 'type': 'columns',
20 | 'columns': [
21 | {
22 | 'components': [
23 | components.textfeild,
24 | components.password
25 | ]
26 | },
27 | {
28 | 'components': [
29 | components.phoneNumber
30 | ]
31 | }
32 | ],
33 | 'input': false
34 | };
35 | const attachToForm = jest.fn();
36 |
37 | it.only('Renders a basic columns component', () => {
38 | const element = renderer.create();
44 |
45 | expect(element).toMatchSnapshot();
46 | // expect(element.find('.row').length).to.equal(1);
47 | // expect(element.nodes[0].props.children[1].props.className).to.equal('col-sm-6');
48 | });
49 |
50 | it('Check number of columns ', function(done) {
51 | const element = renderer.create();
55 |
56 | expect(element.nodes[0].props.children[1].props.children.props.component.columns.length).to.equal(component.columns.length);
57 | done();
58 | });
59 |
60 | it('Check the nested components of columns', function(done) {
61 | const element = renderer.create();
65 |
66 | //To test type of nested components of columns
67 | for (var i= 0; i {
7 | describe('Content field', () => {
8 | const component= {
9 | 'input': false,
10 | 'html': 'Test p tag andstrong tag
\n',
11 | 'type': 'content',
12 | 'conditional': {
13 | 'show': '',
14 | 'when': null,
15 | 'eq': ''
16 | },
17 | 'key': 'testContent',
18 | 'lockKey': true
19 | };
20 | const attachToForm = jest.fn();
21 | it('Renders a content component', () => {
22 | const element = renderer.create(
23 |
28 | );
29 | expect(element).toMatchSnapshot();
30 | });
31 |
32 | it.skip('Check the html for content component', () => {
33 | const element = renderer.create(
34 |
39 | ).find('div');
40 | expect(element.html()).to.equal(component.html);
41 | });
42 | });
43 | });
44 |
--------------------------------------------------------------------------------
/src/components/FormComponents/tests/Fieldset.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import renderer from 'react-test-renderer';
3 | import Fieldset from '../fieldset/Fieldset';
4 | import colors from '../../../defaultTheme/colors';
5 | import theme from '../../../defaultTheme';
6 |
7 | import components from '../../../../test/forms/componentSpec';
8 |
9 | describe('Fieldset', () => {
10 | describe(' Fieldset component', () => {
11 | var component= {
12 | 'input': false,
13 | 'tableView': true,
14 | 'legend': '',
15 | 'components': [
16 | components.textfeild,
17 | components.password,
18 | components.phoneNumber
19 | ],
20 | 'type': 'fieldset',
21 | 'conditional': {
22 | 'show': '',
23 | 'when': null,
24 | 'eq': ''
25 | }
26 | };
27 | const attachToForm = jest.fn();
28 |
29 | it.only('Renders a basic fieldset component', () => {
30 | const element = renderer.create();
36 | expect(element).toMatchSnapshot();
37 | // expect(element.find('fieldset').length).to.equal(1);
38 | });
39 |
40 | it('Check with legend for fieldset component', function(done) {
41 | component.legend = 'My fieldset';
42 | const element = renderer.create();
46 | expect(element.find('legend').length).to.equal(1);
47 | done();
48 | });
49 |
50 | it('Check without legend for fieldset component', function(done) {
51 | component.legend = '';
52 | const element = renderer.create();
56 | expect(element.find('legend').length).to.equal(0);
57 | done();
58 | });
59 |
60 | it('Check the nested components of fieldset', function(done) {
61 | const element = renderer.create();
65 |
66 | //To test type of nested components of fieldset
67 | for (var i= 0; i {
6 | describe(' H2 component', () => {
7 | it('Renders a basic H2 component', () => {
8 | const element = renderer.create(Content
);
9 |
10 | expect(element).toMatchSnapshot();
11 | });
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/src/components/FormComponents/tests/H3.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import renderer from 'react-test-renderer';
3 | import H3 from '../h3/H3';
4 |
5 | describe('H#', () => {
6 | describe(' H3 component', () => {
7 | it('Renders a basic H3 component', () => {
8 | const element = renderer.create(Header
);
9 |
10 | expect(element).toMatchSnapshot();
11 | });
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/src/components/FormComponents/tests/Panel.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import renderer from 'react-test-renderer';
3 | import Panel from '../panel/Panel';
4 | import colors from '../../../defaultTheme/colors';
5 | import theme from '../../../defaultTheme';
6 |
7 | import components from '../../../../test/forms/componentSpec';
8 |
9 | describe('Panel', () => {
10 | describe(' Panel component', () => {
11 | var component= {
12 | 'input': false,
13 | 'title': 'testpanel',
14 | 'theme': 'warning',
15 | 'components': [
16 | components.textfeild,
17 | components.password,
18 | components.phoneNumber
19 | ],
20 | 'type': 'panel',
21 | 'conditional': {
22 | 'show': '',
23 | 'when': null,
24 | 'eq': ''
25 | }
26 | };
27 | const attachToForm = jest.fn();
28 |
29 | it.only('Renders a basic panel component', () => {
30 | const element = renderer.create();
36 |
37 | expect(element).toMatchSnapshot();
38 | // expect(element.find('.panel').length).to.equal(1);
39 | });
40 |
41 | it('Test the theme of panel component', function(done) {
42 | const element = renderer.create().find('.panel');
46 | expect(element.nodes[0].props.className).to.equal('panel panel-' + component.theme + ' ');
47 | done();
48 | });
49 |
50 | it('Test the label of panel component', function(done) {
51 | const element = renderer.create().find('.panel');
55 | expect(element.nodes[0].props.children[0].props.className).to.equal('panel-heading');
56 | expect(element.nodes[0].props.children[0].props.children.props.className).to.equal('panel-title');
57 | done();
58 | });
59 |
60 | it('Check the nested components of Panel', function(done) {
61 | const element = renderer.create().find('.panel-body');
65 |
66 | //To test type of nested components of panel
67 | for (var i= 0; i {
8 | describe.only('Radio field', () => {
9 | const component= {
10 | 'input': true,
11 | 'tableView': true,
12 | 'inputType': 'radio',
13 | 'label': '',
14 | 'key': 'radio',
15 | 'values': [
16 | {
17 | 'value': 'test',
18 | 'label': 'test'
19 | },
20 | {
21 | 'value': 'test1',
22 | 'label': 'test1'
23 | }
24 | ],
25 | 'defaultValue': '',
26 | 'protected': false,
27 | 'persistent': true,
28 | 'validate': {
29 | 'required': false,
30 | 'custom': '',
31 | 'customPrivate': false
32 | },
33 | 'type': 'radio',
34 | 'conditional': {
35 | 'show': '',
36 | 'when': null,
37 | 'eq': ''
38 | },
39 | 'inline': false
40 | };
41 | const attachToForm = jest.fn();
42 |
43 | it.only('Renders a radio field', () => {
44 | const element = renderer.create(
45 | );
51 |
52 | expect(element).toMatchSnapshot();
53 | });
54 |
55 | it('Check the label value for each radio component', (done) => {
56 | const element = renderer.create(
57 |
61 | ).find('.radio-wrapper');
62 | expect(element.length).to.equal(1);
63 |
64 | //To test the label of each select boxes
65 | for (let i= 0; i {
73 | component.label = 'test Label';
74 | const element = renderer.create(
75 |
79 | ).find('label').eq(0);
80 | expect(element.attr('class')).to.equal('control-label');
81 | expect(element.length).to.equal(1);
82 | done();
83 | });
84 |
85 | it('Check the validations with required', (done) => {
86 | component.validate.required = true;
87 | const element = renderer.create(
88 |
92 | ).find('.formio-component-single');
93 | expect(element.find('.formio-component-single label').attr('class')).to.equal('control-label field-required');
94 | done();
95 | });
96 |
97 | it('Check the validations without required', (done) => {
98 | component.validate.required = false;
99 | const element = renderer.create(
100 |
104 | ).find('.formio-component-single');
105 | expect(element.find('.formio-component-single label').attr('class')).to.equal('control-label');
106 | done();
107 | });
108 |
109 | it('Sets the checked class when selected', (done) => {
110 | const element = renderer.create(
111 |
115 | );
116 | // There is a label in the header so indexes are off by 1.
117 | expect(element.find('label').at(1).hasClass('not-checked')).to.equal(true);
118 | element.find('input').at(0).simulate('change', {'target': {'id': element.find('input').at(0).prop('id')}});
119 | expect(element.find('label').at(1).hasClass('checked')).to.equal(true);
120 | done();
121 | });
122 |
123 | it('sets a custom class', (done) => {
124 | component.customClass = 'my-custom-class';
125 | const element = renderer.create(
126 |
130 | ).children().eq(0);
131 | expect(element.attr('class').split(' ')).to.contain('my-custom-class');
132 | done();
133 | });
134 |
135 | //To Do :- Write a test case to validate the inline layout support.
136 | });
137 | });
138 |
--------------------------------------------------------------------------------
/src/components/FormComponents/tests/Select.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import renderer from 'react-test-renderer';
3 | import colors from '../../../defaultTheme/colors';
4 | import theme from '../../../defaultTheme';
5 | import Select from '../select/Select';
6 |
7 | describe('Select Field', () => {
8 | describe('Select Component', () => {
9 | var component= {
10 | 'input': true,
11 | 'tableView': true,
12 | 'label': '',
13 | 'key': 'select',
14 | 'placeholder': '',
15 | 'data': {
16 | 'values': [
17 | {
18 | 'value': 'a',
19 | 'label': 'a'
20 | },
21 | {
22 | 'value': 'b',
23 | 'label': 'b'
24 | },
25 | {
26 | 'value': 'c',
27 | 'label': 'c'
28 | },
29 | {
30 | 'value': 'd',
31 | 'label': 'd'
32 | }
33 | ],
34 | 'json': '',
35 | 'url': '',
36 | 'resource': ''
37 | },
38 | 'dataSrc': 'values',
39 | 'valueProperty': '',
40 | 'defaultValue': '',
41 | 'refreshOn': '',
42 | 'filter': '',
43 | 'authenticate': false,
44 | 'template': '{{ item.label }}',
45 | 'multiple': false,
46 | 'protected': true,
47 | 'unique': false,
48 | 'persistent': true,
49 | 'validate': {
50 | 'required': false
51 | },
52 | 'type': 'select',
53 | 'conditional': {
54 | 'show': '',
55 | 'when': null,
56 | 'eq': ''
57 | }
58 | };
59 | var attachToForm = jest.fn();
60 |
61 | it.only('Render a basic select component', () => {
62 | const element = renderer.create(
63 |
69 | );
70 | expect(element).toMatchSnapshot();
71 | });
72 |
73 | it('Check the label of the select component', () => {
74 | component.label = 'Select component';
75 | const element = renderer.create(
76 |
80 | );
81 | expect(element.html()).to.equal(component.label);
82 | expect(element).to.have.length(1);
83 | });
84 |
85 | it('Check with out label for select component', () => {
86 | component.label = null;
87 | const element = renderer.create(
88 |
92 | );
93 | expect(element.html()).to.equal(component.label);
94 | expect(element).to.have.length(0);
95 | });
96 |
97 | it('Check the dropdown and check the number of options of select component', () => {
98 | const element = renderer.create(
99 |
103 | );
104 | expect(element.find('.rw-open')).to.have.length(0);
105 | element.find('.rw-i-caret-down').simulate('click');
106 | expect(element.find('.rw-open')).to.have.length(1);
107 | expect(element.find('.rw-popup')).to.have.length(1);
108 |
109 | //To check the number of options for select component
110 |
111 | expect(element.find('.rw-popup ul li').length).to.equal(component.data.values.length);
112 | });
113 |
114 | it('Render a multiple select component ', () => {
115 | component.multiple = true;
116 | const element = renderer.create(
117 |
121 | );
122 | expect(element).to.have.length(1);
123 | component.multiple = false;
124 | });
125 | });
126 | });
127 |
--------------------------------------------------------------------------------
/src/components/FormComponents/tests/SelectBoxes.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import renderer from 'react-test-renderer';
3 | import colors from '../../../defaultTheme/colors';
4 | import Selectboxes from '../selectboxes/SelectBoxes';
5 |
6 | describe('Selectboxes', () => {
7 | describe('Selectboxes field', () => {
8 | const component= {
9 | 'conditional': {
10 | 'eq': '',
11 | 'when': null,
12 | 'show': ''
13 | },
14 | 'type': 'selectboxes',
15 | 'validate': {
16 | 'required': false
17 | },
18 | 'persistent': true,
19 | 'protected': false,
20 | 'inline': false,
21 | 'values': [
22 | {
23 | 'label': 'firstSelectbox',
24 | 'value': 'firstSelectbox-value'
25 | },
26 | {
27 | 'label': 'secondSelectbox',
28 | 'value': 'secondSelectbox-value'
29 | },
30 | {
31 | 'label': 'thirdSelectbox',
32 | 'value': 'thirdSelectbox-value'
33 | }
34 | ],
35 | 'key': 'selectBox',
36 | 'label': 'selectBox',
37 | 'tableView': true,
38 | 'input': true
39 | };
40 | const attachToForm = jest.fn();
41 |
42 | it.only('Renders a selectboxes field', () => {
43 | const element = renderer.create(
44 |
49 | );
50 | expect(element).toMatchSnapshot();
51 | });
52 |
53 | it('Check the label value for each selectbox component', () => {
54 | const element = renderer.create(
55 |
60 | );
61 | expect(element.find('.checkbox').length).to.equal(component.values.length);
62 | expect(element.find('.checkbox label input').attr('type')).to.equal('checkbox');
63 |
64 | //To test the label of each select boxes
65 | for (let i= 0; i {
71 | component.validate.required = true;
72 | const element = renderer.create(
73 |
78 | );
79 | expect(element.attr('class')).to.equal('control-label field-required');
80 | });
81 |
82 | it('Check the validations with out required', () => {
83 | component.validate.required = false;
84 | const element = renderer.create(
85 |
90 | );
91 | expect(element.attr('class')).to.equal('control-label');
92 | });
93 |
94 | it('Check single Selectboxes with label', () => {
95 | component.label = 'test Label';
96 | const element = renderer.create(
97 |
102 | );
103 | expect(element.attr('class')).to.equal('control-label');
104 | expect(element.length).to.equal(1);
105 | });
106 |
107 | it('Sets the checked class when selected', () => {
108 | const element = renderer.create(
109 |
114 | );
115 | // There is a label in the header so indexes are off by 1.
116 | expect(element.find('label').at(1).hasClass('not-checked')).to.equal(true);
117 | element.find('input').at(0).simulate('change', {'target': {'checked': true}});
118 | expect(element.find('label').at(1).hasClass('checked')).to.equal(true);
119 | });
120 |
121 | it('sets a custom class', () => {
122 | component.customClass = 'my-custom-class';
123 | const element = renderer.create(
124 |
129 | );
130 | expect(element.attr('class').split(' ')).to.contain('my-custom-class');
131 | });
132 | });
133 | });
134 |
--------------------------------------------------------------------------------
/src/components/FormComponents/tests/Signature.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import renderer from 'react-test-renderer';
3 | import Signature from '../signature/Signature';
4 |
5 | describe.skip('Signature', () => {
6 | describe('Signature field', () => {
7 | var component= {
8 | 'input': true,
9 | 'tableView': true,
10 | 'label': 'Signature',
11 | 'key': 'signature',
12 | 'placeholder': '',
13 | 'footer': 'Sign above',
14 | 'width': '100%',
15 | 'height': '150px',
16 | 'penColor': 'black',
17 | 'backgroundColor': 'rgb(245,245,235)',
18 | 'minWidth': '0.5',
19 | 'maxWidth': '2.5',
20 | 'protected': false,
21 | 'persistent': true,
22 | 'validate': {
23 | 'required': false
24 | },
25 | 'type': 'signature',
26 | 'hideLabel': true,
27 | 'conditional': {
28 | 'show': '',
29 | 'when': null,
30 | 'eq': ''
31 | }
32 | };
33 | var attachToForm = jest.fn();
34 |
35 | it('Renders a basic Signature field', (done) => {
36 | const element = renderer.create(
37 |
41 | ).children().eq(0);
42 | expect(element).to.have.length(1);
43 | expect(element).to.have.length(1);
44 | expect(element.hasClass('form-group form-field-type-signature form-group-signature')).to.equal(true);
45 | expect(element.attr('id')).to.equal('form-group-signature');
46 | expect(element.find('.form-group-signature .m-signature-pad').length).to.equal(1);
47 | expect(element.find('.form-group-signature .m-signature-pad .m-signature-pad--body').length).to.equal(1);
48 | expect(element.find('.form-group-signature .m-signature-pad .m-signature-pad--body canvas').length).to.equal(1);
49 | done();
50 | });
51 |
52 | it('Check Footer for signature component', (done) => {
53 | const element = renderer.create(
54 |
58 | ).find('.formio-signature-footer');
59 | expect(element).to.have.length(1);
60 | expect(element.html()).to.equal(component.footer);
61 | done();
62 | });
63 |
64 | //Checked the clear button class is glyphicon-refresh.
65 | it('Check refresh button of signature component', (done) => {
66 | const element = renderer.create(
67 |
71 | ).find('.glyphicon-refresh');
72 | expect(element).to.have.length(1);
73 | done();
74 | });
75 |
76 | it('Sets a custom class', (done) => {
77 | component.customClass = 'my-custom-class';
78 | const element = renderer.create(
79 |
83 | ).children().eq(0);
84 | expect(element.attr('class').split(' ')).to.contain('my-custom-class');
85 | done();
86 | });
87 | });
88 | });
89 |
--------------------------------------------------------------------------------
/src/components/FormComponents/tests/Switch.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import renderer from 'react-test-renderer';
3 | import colors from '../../../defaultTheme/colors';
4 | import Switch from '../switch/Switch';
5 |
6 | describe('Switch', () => {
7 | describe('Switch field', () => {
8 | const component= {
9 | 'conditional': {
10 | 'eq': '',
11 | 'when': null,
12 | 'show': ''
13 | },
14 | 'type': 'checkbox',
15 | 'validate': {
16 | 'required': false
17 | },
18 | 'persistent': true,
19 | 'protected': false,
20 | 'defaultValue': false,
21 | 'key': 'checkbox',
22 | 'label': 'Checkbox',
23 | 'hideLabel': true,
24 | 'tableView': true,
25 | 'inputType': 'checkbox',
26 | 'input': true
27 | };
28 | var attachToForm = jest.fn();
29 | it.only('Renders a checkbox field', () => {
30 | const element = renderer.create(
31 |
36 | );
37 | expect(element).toMatchSnapshot();
38 | });
39 |
40 | it('Renders a checkbox field without a label', () => {
41 | component.hideLabel = true;
42 | component.datagridLabel = false;
43 | const element = renderer.create(
44 |
49 | );
50 | expect(element.find('.checkbox label').html()).to.equal('');
51 | delete component.datagridLabel;
52 | });
53 |
54 | it('Renders a checkbox field with a label when variables set', () => {
55 | component.hideLabel = false;
56 | component.datagridLabel = false;
57 | const element = renderer.create(
58 |
63 | );
64 | expect(element.find('.checkbox label').html()).to.equal('Checkbox');
65 | delete component.datagridLabel;
66 | component.hideLabel = true;
67 | });
68 |
69 | it('Renders a checkbox field with a label when variables set', () => {
70 | component.hideLabel = true;
71 | component.datagridLabel = true;
72 | const element = renderer.create(
73 |
78 | );
79 | expect(element.find('.checkbox label').html()).to.equal('Checkbox');
80 | delete component.datagridLabel;
81 | component.hideLabel = true;
82 | });
83 |
84 | it('Renders a checkbox field with a label when variables set', () => {
85 | component.hideLabel = false;
86 | component.datagridLabel = true;
87 | const element = renderer.create(
88 |
93 | );
94 | expect(element.find('.checkbox label').html()).to.equal('Checkbox');
95 | delete component.datagridLabel;
96 | component.hideLabel = true;
97 | });
98 |
99 | it('Sets a default value as true', () => {
100 | component.defaultValue = true;
101 | const element = renderer.create(
102 |
108 | );
109 | expect(element.attr('checked')).to.equal('checked');
110 | component.defaultValue = false;
111 | });
112 |
113 | it('Check the validations', () => {
114 | component.validate.required = true;
115 | const element = renderer.create(
116 |
121 | );
122 | expect(element.find('.checkbox label').attr('class')).to.equal('control-label field-required not-checked');
123 | });
124 |
125 | it('Sets the checked class when selected', () => {
126 | const element = renderer.create(
127 |
132 | );
133 | expect(element.find('label').hasClass('not-checked')).to.equal(true);
134 | element.find('input').simulate('change', {'target': {'checked': true}});
135 | expect(element.find('label').hasClass('checked')).to.equal(true);
136 | });
137 |
138 | it('sets a custom class', () => {
139 | component.customClass = 'my-custom-class';
140 | const element = renderer.create(
141 |
146 | );
147 | expect(element.attr('class').split(' ')).to.contain('my-custom-class');
148 | });
149 | });
150 | });
151 |
--------------------------------------------------------------------------------
/src/components/FormComponents/tests/__snapshots__/Content.spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Content Content field Renders a content component 1`] = `
4 |
14 | HTMLView
15 |
16 | `;
17 |
--------------------------------------------------------------------------------
/src/components/FormComponents/tests/__snapshots__/H2.spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`H# H2 component Renders a basic H2 component 1`] = `
4 |
26 | Content
27 |
28 | `;
29 |
--------------------------------------------------------------------------------
/src/components/FormComponents/tests/__snapshots__/H3.spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`H# H3 component Renders a basic H3 component 1`] = `
4 |
26 | Header
27 |
28 | `;
29 |
--------------------------------------------------------------------------------
/src/components/FormComponents/tests/__snapshots__/Radio.spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Radio Radio field Renders a radio field 1`] = `
4 |
11 |
24 |
25 |
45 |
52 |
75 |
87 |
88 |
89 |
108 |
128 | test
129 |
130 |
131 |
132 |
133 |
134 |
154 |
161 |
184 |
196 |
197 |
198 |
217 |
237 | test1
238 |
239 |
240 |
241 |
242 |
243 |
244 | `;
245 |
--------------------------------------------------------------------------------
/src/components/FormComponents/tests/__snapshots__/Select.spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Select Field Select Component Render a basic select component 1`] = `
4 |
13 | Dropdown
14 |
15 | `;
16 |
--------------------------------------------------------------------------------
/src/components/FormComponents/tests/__snapshots__/SelectBoxes.spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Selectboxes Selectboxes field Renders a selectboxes field 1`] = `
4 |
12 |
20 | selectBox
21 |
22 |
23 |
24 |
25 |
26 |
27 | SelectBoxes
28 |
29 |
30 | `;
31 |
--------------------------------------------------------------------------------
/src/components/FormComponents/tests/__snapshots__/Switch.spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Switch Switch field Renders a checkbox field 1`] = `
4 |
14 |
29 |
39 | Checkbox
40 |
41 |
42 | `;
43 |
--------------------------------------------------------------------------------
/src/components/FormComponents/tests/__snapshots__/TextField.spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Textfield @textfield Single Textfield renders a basic textfield 1`] = `
4 |
11 | `;
12 |
--------------------------------------------------------------------------------
/src/components/FormComponents/tests/__snapshots__/Textarea.spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Textarea Single Textarea Renders a basic textarea 1`] = `
4 |
11 |
19 |
48 |
65 |
66 |
67 |
68 |
85 |
86 |
87 |
88 | textarea
89 |
90 |
107 |
108 |
109 |
110 |
111 |
143 |
144 | `;
145 |
--------------------------------------------------------------------------------
/src/components/FormComponents/textarea/Textarea.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import {TextInput} from 'react-native';
4 | import InputComponent from '../sharedComponents/Input';
5 | import styles from './styles';
6 |
7 | export default class Textarea extends InputComponent {
8 |
9 | constructor(props) {
10 | super(props);
11 | this.onChangeText = this.onChangeText.bind(this);
12 | this.getSingleElement = this.getSingleElement.bind(this);
13 | }
14 |
15 | onChangeText(index, value) {
16 | this.setValue(value, index);
17 | }
18 |
19 | getSingleElement(value, index) {
20 | const {component, name, readOnly, colors, theme} = this.props;
21 | const fieldValue = typeof value === 'object' ? value.item : value;
22 | index = index || 0;
23 | return (
24 |
39 | );
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/FormComponents/textarea/styles.js:
--------------------------------------------------------------------------------
1 | import {StyleSheet} from 'react-native';
2 |
3 | const styles = StyleSheet.create({
4 | textarea: {
5 | flex: 1,
6 | marginTop: 20,
7 | paddingTop: 10,
8 | marginHorizontal: 10,
9 | paddingHorizontal: 10,
10 | borderWidth: 1,
11 | fontSize: 16,
12 | lineHeight: 16,
13 | paddingBottom: 20,
14 | }
15 | });
16 |
17 | export default styles;
18 |
--------------------------------------------------------------------------------
/src/components/FormComponents/textfield/TextField.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import InputComponent from '../sharedComponents/Input';
3 |
4 | export default class Textfield extends InputComponent {}
5 |
--------------------------------------------------------------------------------
/src/components/FormioComponentsList.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import {View, StyleSheet} from 'react-native';
4 | import {FormioComponents} from '../factories';
5 |
6 | const styles = StyleSheet.create({
7 | wrapper: {
8 | flex: 1,
9 | }
10 | });
11 |
12 | const FormioComponentsList = (props) => {
13 | return (
14 |
15 | {props.components.map((component, index) => {
16 | const key = component.key || component.type + index;
17 | const value = (props.values && props.values.hasOwnProperty(component.key) ? props.values[component.key] : null);
18 | const FormioElement = FormioComponents.getComponent(component.type);
19 | if (!FormioElement) return null;
20 | if (props.checkConditional(component, props.row)) {
21 | return (
22 |
30 | );
31 | }
32 | else {
33 | return null;
34 | }
35 | })}
36 |
37 | );
38 | };
39 |
40 | FormioComponentsList.propTypes = {
41 | components: PropTypes.array,
42 | values: PropTypes.any,
43 | row: PropTypes.any,
44 | isDisabled: PropTypes.func,
45 | checkConditional: PropTypes.func,
46 | };
47 |
48 | export default FormioComponentsList;
49 |
--------------------------------------------------------------------------------
/src/components/FormioConfirm.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Text, View} from 'react-native';
3 | import PropTypes from 'prop-types';
4 |
5 | const FormioConfirm = (props) => {
6 | const buttonElements = props.buttons.map((button, index) => {
7 | return (
8 | { button.text }
9 | );
10 | });
11 | return (
12 |
13 | { props.message}
14 |
15 | {buttonElements}
16 |
17 |
18 | );
19 | };
20 |
21 | FormioConfirm.propTypes = {
22 | message: PropTypes.string,
23 | buttons: PropTypes.array,
24 | };
25 |
26 | export default FormioConfirm;
27 |
--------------------------------------------------------------------------------
/src/components/FormioGrid.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {View, Button, Text} from 'react-native';
3 | import Icon from 'react-native-vector-icons/dist/FontAwesome';
4 | import PropTypes from 'prop-types';
5 | import Formiojs from '../formio';
6 | import FormioUtils from '../formio/utils';
7 | import {nested} from '../util';
8 | import {FormioComponents} from '../factories';
9 |
10 | export default class FormioGrid extends React.Component {
11 | constructor(props) {
12 | super(props);
13 |
14 | this.state = {
15 | columns: this.columnsFromForm(props.form),
16 | submissions: this.props.submissions || [],
17 | pagination: {...FormioGrid.defaultProps.pagination, ...this.props.pagination}
18 | };
19 | }
20 |
21 | formatCell(value, {column}) {
22 | return FormioComponents.getComponent(column.component.type).prototype.getDisplay(column.component, value);
23 | }
24 |
25 | columnsFromForm(form) {
26 | let columns = [];
27 | let buttons = this.props.buttons.map((button) => {
28 | return {
29 | property: '_id',
30 | header: {
31 | label: button.label
32 | },
33 | cell: {
34 | format: ({rowData}) => {
35 | return (
36 |
42 | );
43 | }
44 | },
45 | visible: true
46 | };
47 | });
48 | if (form && form.components) {
49 | FormioUtils.eachComponent(form.components, (component, path) => {
50 | if (component.input && component.tableView && component.key && path.indexOf('.') === -1) {
51 | columns.push({
52 | component: component,
53 | property: 'data.' + component.key,
54 | header: {
55 | label: component.label || component.key
56 | },
57 | cell: {
58 | format: this.formatCell
59 | },
60 | visible: true
61 | });
62 | }
63 | });
64 | }
65 | if (!buttons.length) {
66 | return columns;
67 | }
68 | if (this.props.buttonLocation === 'right') {
69 | return columns.concat(buttons);
70 | }
71 | else {
72 | return buttons.concat(columns);
73 | }
74 | }
75 |
76 | componentWillReceiveProps(nextProps) {
77 | if (nextProps.form !== this.props.form) {
78 | this.setState({
79 | columns: this.columnsFromForm(nextProps.form)
80 | });
81 | }
82 | if (nextProps.submissions !== this.state.submissions) {
83 | this.setState({
84 | submissions: nextProps.submissions
85 | });
86 | }
87 | if (nextProps.pagination.page !== this.state.pagination.page) {
88 | this.setState(curState => curState.pagination.page = nextProps.pagination.page);
89 | }
90 | if (nextProps.pagination.numPage !== this.state.pagination.numPage) {
91 | this.setState(curState => curState.pagination.numPage = nextProps.pagination.numPage);
92 | }
93 | if (nextProps.pagination.size !== this.state.pagination.size) {
94 | this.setState(curState => curState.pagination.size = nextProps.pagination.size);
95 | }
96 | }
97 |
98 | componentDidMount() {
99 | if (this.props.src) {
100 | this.formio = new Formiojs(this.props.src);
101 | this.loadForm();
102 | this.loadSubmissions();
103 | }
104 | }
105 |
106 | loadForm() {
107 | this.formio.loadForm().then(form => {
108 | this.setState({
109 | columns: this.columnsFromForm(form)
110 | });
111 | });
112 | }
113 |
114 | loadSubmissions() {
115 | this.formio.loadSubmissions({
116 | params: {
117 | ...this.props.query,
118 | limit: this.state.pagination.size,
119 | skip: (this.state.pagination.page - 1) * this.state.pagination.size
120 | }
121 | }).then(submissions => {
122 | this.setState(curState => {
123 | curState.submissions = submissions;
124 | curState.pagination.numPage = Math.ceil(submissions.serverCount / this.state.pagination.size);
125 | return curState;
126 | });
127 | });
128 | }
129 |
130 | onButtonClick(event, type, row) {
131 | event.preventDefault();
132 | event.stopPropagation();
133 | if (typeof this.props.onButtonClick === 'function') {
134 | this.props.onButtonClick(type, row._id);
135 | }
136 | }
137 |
138 | onRowClick(row) {
139 | return {
140 | onClick: () => {
141 | if (typeof this.props.onButtonClick === 'function') {
142 | this.props.onButtonClick('row', row._id);
143 | }
144 | }
145 | };
146 | }
147 |
148 | onPageChange(page) {
149 | if (typeof this.props.onPageChange === 'function') {
150 | this.props.onPageChange(page);
151 | }
152 | else {
153 | this.setState(curState => curState.pagination.page = page, this.loadSubmissions);
154 | }
155 | }
156 |
157 | render() {
158 | let rows = this.state.columns;
159 | return (
160 |
161 | {rows}
162 |
163 | );
164 | }
165 | }
166 |
167 | FormioGrid.defaultProps = {
168 | form: {},
169 | submissions: [],
170 | query: {
171 | sort: '-created'
172 | },
173 | pagination: {
174 | page: 1,
175 | numPage: 1,
176 | sizes: [25, 50, 75],
177 | size: 25
178 | },
179 | buttons: [],
180 | buttonLocation: 'right'
181 | };
182 |
183 | FormioGrid.propTypes = {
184 | src: PropTypes.string,
185 | form: PropTypes.object,
186 | submissions: PropTypes.object,
187 | buttons: PropTypes.array,
188 | pagination: PropTypes.shape({
189 | page: PropTypes.number,
190 | numPage: PropTypes.number,
191 | sizes: PropTypes.arrayOf(PropTypes.number),
192 | size: PropTypes.number
193 | }),
194 | query: PropTypes.shape({
195 | sort: PropTypes.string
196 | }),
197 | buttonLocation: PropTypes.string,
198 | onButtonClick: PropTypes.func,
199 | onPageChange: PropTypes.func,
200 | };
201 |
--------------------------------------------------------------------------------
/src/components/index.js:
--------------------------------------------------------------------------------
1 | export {default as Formio} from './Formio';
2 | export {default as FormBuilder} from './FormBuilder';
3 | export {default as FormioComponentsList} from './FormioComponentsList';
4 | export {default as FormioConfirm} from './FormioConfirm';
5 | export {default as FormioGrid} from './FormioGrid';
6 |
--------------------------------------------------------------------------------
/src/defaultTheme/colors.js:
--------------------------------------------------------------------------------
1 | export default {
2 | mainBackground: '#ffffff',
3 | primary1Color: '#03a9f4',
4 | primary2Color: '#1976d2',
5 | primary3Color: '#bdbdbd',
6 | accent1Color: '#ec407a',
7 | accent2Color: 'rgba(245, 245, 245, 0.99)',
8 | accent3Color: '#9e9e9e',
9 | textColor: 'rgba(0, 0, 0, 0.87)',
10 | secondaryTextColor: 'rgba(0, 0, 0, 0.54)',
11 | alternateTextColor: '#ffffff',
12 | borderColor: '#9e9e9e',
13 | disabledColor: '#000000',
14 | pickerColor: '#2196f3',
15 | shadowColor: 'rgba(0, 0, 0, 0.54)',
16 | errorColor: 'rgb(244, 67, 54)',
17 | successColor: '#4BB543',
18 | };
19 |
--------------------------------------------------------------------------------
/src/defaultTheme/index.js:
--------------------------------------------------------------------------------
1 | import colors from './colors';
2 |
3 | const Theme = {
4 | Main: {
5 | backgroundColor: colors.mainBackground
6 | },
7 | Button: {
8 | backgroundColor: colors.primary1Color,
9 | color: colors.alternateTextColor,
10 | fontSize: 12,
11 | fontWeight: 700,
12 | height: 45
13 | },
14 | ErrorMessage: {
15 | color: colors.errorColor,
16 | fontSize: 10,
17 | marginBottom: 15,
18 | textAlign: 'right'
19 | },
20 | Fieldset: {
21 | borderBottomWidth: 1,
22 | borderBottomColor: colors.borderColor,
23 | labelColor: colors.textColor,
24 | labelSize: 9,
25 | labelWeight: 700,
26 | labelHeight: 25,
27 | paddingTop: 12,
28 | paddingBottom: 12,
29 | paddingLeft: 8,
30 | paddingRight: 8,
31 | },
32 | Input: {
33 | borderColor: colors.borderColor,
34 | borderColorOnError: colors.errorColor,
35 | color: colors.textColor,
36 | placeholderTextColor: colors.primary3Color,
37 | fontSize: 14,
38 | lineHeight: 18
39 | },
40 | Label: {
41 | color: colors.textColor,
42 | fontSize: 12,
43 | stackedHeight: 25
44 | },
45 | Select: {
46 |
47 | }
48 | };
49 |
50 | export default Theme;
51 |
--------------------------------------------------------------------------------
/src/factories/FormioComponents.js:
--------------------------------------------------------------------------------
1 | const components = {};
2 | const groups = {
3 | __component: {
4 | title: 'Basic Components'
5 | },
6 | advanced: {
7 | title: 'Special Components'
8 | },
9 | layout: {
10 | title: 'Layout Components'
11 | }
12 | };
13 |
14 | export const FormioComponents = {
15 | addGroup: (name, group) => {
16 | groups[name] = group;
17 | },
18 | register: (type, component, group) => {
19 | if (!components[type]) {
20 | components[type] = component;
21 | }
22 | else {
23 | Object.assign(components[type], component);
24 | }
25 |
26 | // Set the type for this component.
27 | if (!components[type].group) {
28 | components[type].group = group || '__component';
29 | }
30 | },
31 | getComponent: (type) => {
32 | return components.hasOwnProperty(type) ? components[type] : components['custom'];
33 | },
34 | components,
35 | groups
36 | };
37 |
--------------------------------------------------------------------------------
/src/factories/index.js:
--------------------------------------------------------------------------------
1 | export * from './FormioComponents';
2 |
--------------------------------------------------------------------------------
/src/formio/providers/index.js:
--------------------------------------------------------------------------------
1 | import storage from './storage';
2 | export default storage;
3 |
--------------------------------------------------------------------------------
/src/formio/providers/storage/base64.js:
--------------------------------------------------------------------------------
1 | /** THIS FILE IS NOT USABLE, NEEDS TO BE RECREATED IN REACT NATIVE */
2 |
3 | const base64 = () => ({
4 | title: 'Base64',
5 | name: 'base64',
6 | uploadFile(file, fileName) {
7 | const reader = {};
8 |
9 | return new Promise((resolve, reject) => {
10 | reader.onload = (event) => {
11 | const url = event.target.result;
12 | resolve({
13 | storage: 'base64',
14 | name: fileName,
15 | url: url,
16 | size: file.size,
17 | type: file.type,
18 | data: url.replace(`data:${file.type};base64,`, '')
19 | });
20 | };
21 |
22 | reader.onerror = () => {
23 | return reject(this);
24 | };
25 |
26 | reader.readAsDataURL(file);
27 | });
28 | },
29 | downloadFile(file) {
30 | // Return the original as there is nothing to do.
31 | return Promise.resolve(file);
32 | }
33 | });
34 |
35 | base64.title = 'Base64';
36 | export default base64;
37 |
--------------------------------------------------------------------------------
/src/formio/providers/storage/dropbox.js:
--------------------------------------------------------------------------------
1 | /** THIS FILE IS NOT USABLE, NEEDS TO BE RECREATED IN REACT NATIVE */
2 |
3 | const dropbox = (formio) => ({
4 | uploadFile(file, fileName, dir, progressCallback) {
5 | return new Promise(((resolve, reject) => {
6 | // Send the file with data.
7 | const xhr = {};
8 |
9 | if (typeof progressCallback === 'function') {
10 | xhr.upload.onprogress = progressCallback;
11 | }
12 |
13 | const fd = {};
14 | fd.append('name', fileName);
15 | fd.append('dir', dir);
16 | fd.append('file', file);
17 |
18 | // Fire on network error.
19 | xhr.onerror = (err) => {
20 | err.networkError = true;
21 | reject(err);
22 | };
23 |
24 | xhr.onload = () => {
25 | if (xhr.status >= 200 && xhr.status < 300) {
26 | const response = JSON.parse(xhr.response);
27 | response.storage = 'dropbox';
28 | response.size = file.size;
29 | response.type = file.type;
30 | response.url = response.path_lower;
31 | resolve(response);
32 | }
33 | else {
34 | reject(xhr.response || 'Unable to upload file');
35 | }
36 | };
37 |
38 | xhr.onabort = reject;
39 |
40 | xhr.open('POST', `${formio.formUrl}/storage/dropbox`);
41 | const token = formio.getToken();
42 | if (token) {
43 | xhr.setRequestHeader('x-jwt-token', token);
44 | }
45 | xhr.send(fd);
46 | }));
47 | },
48 | downloadFile(file) {
49 | const token = formio.getToken();
50 | file.url =
51 | `${formio.formUrl}/storage/dropbox?path_lower=${file.path_lower}${token ? `&x-jwt-token=${token}` : ''}`;
52 | return Promise.resolve(file);
53 | }
54 | });
55 |
56 | dropbox.title = 'Dropbox';
57 | export default dropbox;
58 |
--------------------------------------------------------------------------------
/src/formio/providers/storage/index.js:
--------------------------------------------------------------------------------
1 | import base64 from './base64';
2 | import dropbox from './dropbox';
3 | import s3 from './s3';
4 | import url from './url';
5 | export default {
6 | base64: base64,
7 | dropbox: dropbox,
8 | s3: s3,
9 | url: url
10 | };
11 |
--------------------------------------------------------------------------------
/src/formio/providers/storage/s3.js:
--------------------------------------------------------------------------------
1 | /** THIS FILE IS NOT USABLE, NEEDS TO BE RECREATED IN REACT NATIVE */
2 |
3 | const s3 = (formio) => ({
4 | uploadFile(file, fileName, dir, progressCallback) {
5 | return new Promise(((resolve, reject) => {
6 | // Send the pre response to sign the upload.
7 | const pre = {};
8 |
9 | const prefd = {};
10 | prefd.append('name', fileName);
11 | prefd.append('size', file.size);
12 | prefd.append('type', file.type);
13 |
14 | // This only fires on a network error.
15 | pre.onerror = (err) => {
16 | err.networkError = true;
17 | reject(err);
18 | };
19 |
20 | pre.onabort = reject;
21 |
22 | pre.onload = () => {
23 | if (pre.status >= 200 && pre.status < 300) {
24 | const response = JSON.parse(pre.response);
25 |
26 | // Send the file with data.
27 | const xhr = {};
28 |
29 | if (typeof progressCallback === 'function') {
30 | xhr.upload.onprogress = progressCallback;
31 | }
32 |
33 | response.data.fileName = fileName;
34 | response.data.key += dir + fileName;
35 |
36 | const fd = {};
37 | for (const key in response.data) {
38 | fd.append(key, response.data[key]);
39 | }
40 | fd.append('file', file);
41 |
42 | // Fire on network error.
43 | xhr.onerror = (err) => {
44 | err.networkError = true;
45 | reject(err);
46 | };
47 |
48 | xhr.onload = () => {
49 | if (xhr.status >= 200 && xhr.status < 300) {
50 | resolve({
51 | storage: 's3',
52 | name: fileName,
53 | bucket: response.bucket,
54 | key: response.data.key,
55 | url: response.url + response.data.key,
56 | acl: response.data.acl,
57 | size: file.size,
58 | type: file.type
59 | });
60 | }
61 | else {
62 | reject(xhr.response || 'Unable to upload file');
63 | }
64 | };
65 |
66 | xhr.onabort = reject;
67 |
68 | xhr.open('POST', response.url);
69 |
70 | xhr.send(fd);
71 | }
72 | else {
73 | reject(pre.response || 'Unable to sign file');
74 | }
75 | };
76 |
77 | pre.open('POST', `${formio.formUrl}/storage/s3`);
78 |
79 | pre.setRequestHeader('Accept', 'application/json');
80 | pre.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
81 | const token = formio.getToken();
82 | if (token) {
83 | pre.setRequestHeader('x-jwt-token', token);
84 | }
85 |
86 | pre.send(JSON.stringify({
87 | name: fileName,
88 | size: file.size,
89 | type: file.type
90 | }));
91 | }));
92 | },
93 | downloadFile(file) {
94 | if (file.acl !== 'public-read') {
95 | return formio.makeRequest('file', `${formio.formUrl}/storage/s3?bucket=${file.bucket}&key=${file.key}`, 'GET');
96 | }
97 | else {
98 | return Promise.resolve(file);
99 | }
100 | }
101 | });
102 |
103 | s3.title = 'S3';
104 | export default s3;
105 |
--------------------------------------------------------------------------------
/src/formio/providers/storage/url.js:
--------------------------------------------------------------------------------
1 | /** THIS FILE IS NOT USABLE, NEEDS TO BE RECREATED IN REACT NATIVE */
2 |
3 | const url = (formio) => ({
4 | title: 'Url',
5 | name: 'url',
6 | uploadFile(file, fileName, dir, progressCallback, url) {
7 | return new Promise(((resolve, reject) => {
8 | const data = {
9 | dir,
10 | file,
11 | name: fileName
12 | };
13 |
14 | // Send the file with data.
15 | const xhr = {};
16 |
17 | if (typeof progressCallback === 'function') {
18 | xhr.upload.onprogress = progressCallback;
19 | }
20 |
21 | const fd = {};
22 | for (const key in data) {
23 | fd.append(key, data[key]);
24 | }
25 |
26 | xhr.onload = () => {
27 | if (xhr.status >= 200 && xhr.status < 300) {
28 | // Need to test if xhr.response is decoded or not.
29 | let respData = {};
30 | try {
31 | respData = (typeof xhr.response === 'string') ? JSON.parse(xhr.response) : {};
32 | respData = (respData && respData.data) ? respData.data : respData;
33 | }
34 | catch (err) {
35 | respData = {};
36 | }
37 |
38 | const url = respData.hasOwnProperty('url') ? respData.url : `${xhr.responseURL}/${fileName}`;
39 | resolve({
40 | storage: 'url',
41 | name: fileName,
42 | url,
43 | size: file.size,
44 | type: file.type,
45 | data: respData
46 | });
47 | }
48 | else {
49 | reject(xhr.response || 'Unable to upload file');
50 | }
51 | };
52 |
53 | // Fire on network error.
54 | xhr.onerror = () => reject(xhr);
55 |
56 | xhr.onabort = () => reject(xhr);
57 |
58 | xhr.open('POST', url);
59 | const token = formio.getToken();
60 | if (token) {
61 | xhr.setRequestHeader('x-jwt-token', token);
62 | }
63 | xhr.send(fd);
64 | }));
65 | },
66 | downloadFile(file) {
67 | // Return the original as there is nothing to do.
68 | return Promise.resolve(file);
69 | }
70 | });
71 |
72 | url.title = 'Url';
73 | export default url;
74 |
--------------------------------------------------------------------------------
/src/formio/utils/builder.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import {eachComponent} from './utils';
3 | export default {
4 | /**
5 | * Iterate the given key to make it unique.
6 | *
7 | * @param {String} key
8 | * Modify the component key to be unique.
9 | *
10 | * @returns {String}
11 | * The new component key.
12 | */
13 | iterateKey(key) {
14 | if (!key.match(/(\d+)$/)) {
15 | return `${key}2`;
16 | }
17 |
18 | return key.replace(/(\d+)$/, function(suffix) {
19 | return Number(suffix) + 1;
20 | });
21 | },
22 |
23 | /**
24 | * Appends a number to a component.key to keep it unique
25 | *
26 | * @param {Object} form
27 | * The components parent form.
28 | * @param {Object} component
29 | * The component to uniquify
30 | */
31 | uniquify(form, component) {
32 | let changed = false;
33 | const formKeys = {};
34 | eachComponent(form.components, function(comp) {
35 | formKeys[comp.key] = true;
36 | });
37 |
38 | // Recurse into all child components.
39 | eachComponent([component], (component) => {
40 | // Skip key uniquification if this component doesn't have a key.
41 | if (!component.key) {
42 | return;
43 | }
44 |
45 | while (formKeys.hasOwnProperty(component.key)) {
46 | component.key = this.iterateKey(component.key);
47 | changed = true;
48 | }
49 | }, true);
50 | return changed;
51 | },
52 |
53 | additionalShortcuts: {
54 | button: [
55 | 'Enter',
56 | 'Esc'
57 | ]
58 | },
59 |
60 | getAlphaShortcuts() {
61 | return _.range('A'.charCodeAt(), 'Z'.charCodeAt() + 1).map((charCode) => String.fromCharCode(charCode));
62 | },
63 |
64 | getAdditionalShortcuts(type) {
65 | return this.additionalShortcuts[type] || [];
66 | },
67 |
68 | getBindedShortcuts(components, input) {
69 | const result = [];
70 |
71 | eachComponent(components, function(component) {
72 | if (component === input) {
73 | return;
74 | }
75 |
76 | if (component.shortcut) {
77 | result.push(component.shortcut);
78 | }
79 | if (component.values) {
80 | component.values.forEach((value) => {
81 | if (value.shortcut) {
82 | result.push(value.shortcut);
83 | }
84 | });
85 | }
86 | }, true);
87 |
88 | return result;
89 | },
90 |
91 | getAvailableShortcuts(form, component) {
92 | if (!component) {
93 | return [];
94 | }
95 | return [''].concat(_.difference(
96 | this.getAlphaShortcuts().concat(this.getAdditionalShortcuts(component.type)),
97 | this.getBindedShortcuts(form.components, component))
98 | );
99 | }
100 | };
101 |
--------------------------------------------------------------------------------
/src/formio/utils/fixtures/components.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "type": "textfield",
4 | "key": "one",
5 | "order": 1
6 | },
7 | {
8 | "input": false,
9 | "key": "parent1",
10 | "components": [
11 | {
12 | "type": "textfield",
13 | "key": "two",
14 | "order": 2
15 | },
16 | {
17 | "input": false,
18 | "key": "parent2",
19 | "columns": [
20 | {
21 | "components": [
22 | {
23 | "type": "textfield",
24 | "key": "three",
25 | "order": 3
26 | }
27 | ]
28 | },
29 | {
30 | "components": [
31 | {
32 | "rows": [
33 | [
34 | {
35 | "components": [
36 | {
37 | "key": "four",
38 | "order": 4,
39 | "type": "textfield"
40 | }
41 | ]
42 | },
43 | {
44 | "components": [
45 | {
46 | "key": "five",
47 | "order": 5,
48 | "type": "textfield"
49 | }
50 | ]
51 | }
52 | ],
53 | [
54 | {
55 | "components": [
56 | {
57 | "key": "six",
58 | "order": 6,
59 | "type": "textfield"
60 | }
61 | ]
62 | },
63 | {
64 | "components": [
65 | {
66 | "key": "seven",
67 | "order": 7,
68 | "type": "textarea",
69 | "rows": 3
70 | }
71 | ]
72 | }
73 | ]
74 | ],
75 | "type": "table"
76 | }
77 | ]
78 | }
79 | ],
80 | "type": "columns"
81 | }
82 | ],
83 | "type": "well"
84 | },
85 | {
86 | "key": "eight",
87 | "order": 8,
88 | "type": "button"
89 | }
90 | ]
91 |
--------------------------------------------------------------------------------
/src/formio/utils/fixtures/submission1.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {
3 | "name": "John Smith",
4 | "age": "30",
5 | "colors": [
6 | {"value": "red", "label": "Red"},
7 | {"value": "blue", "label": "Blue"},
8 | {"value": "green", "label": "Green"}
9 | ],
10 | "selectboxes": {
11 | "car": false,
12 | "boat": true
13 | },
14 | "mycontainer": {
15 | "checked": true,
16 | "animalname": "Fluffy"
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/formio/utils/index.js:
--------------------------------------------------------------------------------
1 | import * as FormioUtils from './utils';
2 | if (typeof global === 'object') {
3 | global.FormioUtils = FormioUtils;
4 | }
5 | export default FormioUtils;
6 |
--------------------------------------------------------------------------------
/src/formio/utils/jsonlogic/operators.js:
--------------------------------------------------------------------------------
1 | // Use only immutable useful functions from Lodash.
2 | // Visit https://lodash.com/docs for more info.
3 | export const lodashOperators = [
4 | // Array
5 | 'chunk',
6 | 'compact',
7 | 'concat',
8 | 'difference',
9 | 'differenceBy',
10 | 'differenceWith',
11 | 'drop',
12 | 'dropRight',
13 | 'dropRightWhile',
14 | 'dropWhile',
15 | 'findIndex',
16 | 'findLastIndex',
17 | 'first',
18 | 'flatten',
19 | 'flattenDeep',
20 | 'flattenDepth',
21 | 'fromPairs',
22 | 'head',
23 | 'indexOf',
24 | 'initial',
25 | 'intersection',
26 | 'intersectionBy',
27 | 'intersectionWith',
28 | 'join',
29 | 'last',
30 | 'lastIndexOf',
31 | 'nth',
32 | 'slice',
33 | 'sortedIndex',
34 | 'sortedIndexBy',
35 | 'sortedIndexOf',
36 | 'sortedLastIndex',
37 | 'sortedLastIndexBy',
38 | 'sortedLastIndexOf',
39 | 'sortedUniq',
40 | 'sortedUniqBy',
41 | 'tail',
42 | 'take',
43 | 'takeRight',
44 | 'takeRightWhile',
45 | 'takeWhile',
46 | 'union',
47 | 'unionBy',
48 | 'unionWith',
49 | 'uniq',
50 | 'uniqBy',
51 | 'uniqWith',
52 | 'unzip',
53 | 'unzipWith',
54 | 'without',
55 | 'xor',
56 | 'xorBy',
57 | 'xorWith',
58 | 'zip',
59 | 'zipObject',
60 | 'zipObjectDeep',
61 | 'zipWith',
62 | // Collection
63 | 'countBy',
64 | 'every',
65 | 'filter',
66 | 'find',
67 | 'findLast',
68 | 'flatMap',
69 | 'flatMapDeep',
70 | 'flatMapDepth',
71 | 'groupBy',
72 | 'includes',
73 | 'invokeMap',
74 | 'keyBy',
75 | 'map',
76 | 'orderBy',
77 | 'partition',
78 | 'reduce',
79 | 'reduceRight',
80 | 'reject',
81 | 'sample',
82 | 'sampleSize',
83 | 'shuffle',
84 | 'size',
85 | 'some',
86 | 'sortBy',
87 | // Date
88 | 'now',
89 | // Function
90 | 'flip',
91 | 'negate',
92 | 'overArgs',
93 | 'partial',
94 | 'partialRight',
95 | 'rearg',
96 | 'rest',
97 | 'spread',
98 | // Lang
99 | 'castArray',
100 | 'clone',
101 | 'cloneDeep',
102 | 'cloneDeepWith',
103 | 'cloneDeep',
104 | 'conformsTo',
105 | 'eq',
106 | 'gt',
107 | 'gte',
108 | 'isArguments',
109 | 'isArray',
110 | 'isArrayBuffer',
111 | 'isArrayLike',
112 | 'isArrayLikeObject',
113 | 'isBoolean',
114 | 'isBuffer',
115 | 'isDate',
116 | 'isElement',
117 | 'isEmpty',
118 | 'isEqual',
119 | 'isEqualWith',
120 | 'isError',
121 | 'isFinite',
122 | 'isFunction',
123 | 'isInteger',
124 | 'isLength',
125 | 'isMap',
126 | 'isMatch',
127 | 'isMatchWith',
128 | 'isNaN',
129 | 'isNative',
130 | 'isNil',
131 | 'isNull',
132 | 'isNumber',
133 | 'isObject',
134 | 'isObjectLike',
135 | 'isPlainObject',
136 | 'isRegExp',
137 | 'isSafeInteger',
138 | 'isSet',
139 | 'isString',
140 | 'isSymbol',
141 | 'isTypedArray',
142 | 'isUndefined',
143 | 'isWeakMap',
144 | 'isWeakSet',
145 | 'lt',
146 | 'lte',
147 | 'toArray',
148 | 'toFinite',
149 | 'toInteger',
150 | 'toLength',
151 | 'toNumber',
152 | 'toPlainObject',
153 | 'toSafeInteger',
154 | 'toString',
155 | // Math
156 | 'add',
157 | 'ceil',
158 | 'divide',
159 | 'floor',
160 | 'max',
161 | 'maxBy',
162 | 'mean',
163 | 'meanBy',
164 | 'min',
165 | 'minBy',
166 | 'multiply',
167 | 'round',
168 | 'subtract',
169 | 'sum',
170 | 'sumBy',
171 | // Number
172 | 'clamp',
173 | 'inRange',
174 | 'random',
175 | // Object
176 | 'at',
177 | 'entries',
178 | 'entriesIn',
179 | 'findKey',
180 | 'findLastKey',
181 | 'functions',
182 | 'functionsIn',
183 | 'get',
184 | 'has',
185 | 'hasIn',
186 | 'invert',
187 | 'invertBy',
188 | 'invoke',
189 | 'keys',
190 | 'keysIn',
191 | 'mapKeys',
192 | 'mapValues',
193 | 'omit',
194 | 'omitBy',
195 | 'pick',
196 | 'pickBy',
197 | 'result',
198 | 'toPairs',
199 | 'toPairsIn',
200 | 'transform',
201 | 'values',
202 | 'valuesIn',
203 | // String
204 | 'camelCase',
205 | 'capitalize',
206 | 'deburr',
207 | 'endsWith',
208 | 'escape',
209 | 'escapeRegExp',
210 | 'kebabCase',
211 | 'lowerCase',
212 | 'lowerFirst',
213 | 'pad',
214 | 'padEnd',
215 | 'padStart',
216 | 'parseInt',
217 | 'repeat',
218 | 'replace',
219 | 'snakeCase',
220 | 'split',
221 | 'startCase',
222 | 'startsWith',
223 | 'toLower',
224 | 'toUpper',
225 | 'trim',
226 | 'trimEnd',
227 | 'trimStart',
228 | 'truncate',
229 | 'unescape',
230 | 'upperCase',
231 | 'upperFirst',
232 | 'words',
233 | // Util
234 | 'cond',
235 | 'conforms',
236 | 'constant',
237 | 'defaultTo',
238 | 'flow',
239 | 'flowRight',
240 | 'identity',
241 | 'iteratee',
242 | 'matches',
243 | 'matchesProperty',
244 | 'method',
245 | 'methodOf',
246 | 'nthArg',
247 | 'over',
248 | 'overEvery',
249 | 'overSome',
250 | 'property',
251 | 'propertyOf',
252 | 'range',
253 | 'rangeRight',
254 | 'stubArray',
255 | 'stubFalse',
256 | 'stubObject',
257 | 'stubString',
258 | 'stubTrue',
259 | 'times',
260 | 'toPath',
261 | 'uniqueId',
262 | ];
263 |
--------------------------------------------------------------------------------
/src/formio/utils/jsonlogic/operators.spec.js:
--------------------------------------------------------------------------------
1 | import {jsonLogic} from '../utils';
2 |
3 | describe.skip('Lodash operators', () => {
4 | describe.skip('Arrays', () => {
5 |
6 | });
7 | describe.skip('Collection', () => {
8 |
9 | });
10 | describe.skip('Date', () => {
11 |
12 | });
13 | describe.skip('Function', () => {
14 |
15 | });
16 | describe.skip('Lang', () => {
17 | it('isEqual', () => {
18 | const logicTrue = {_isEqual: [[2, 3], [2, 3]]};
19 | const logicFalse = {_isEqual: [[2, 3], [2, 4]]};
20 | expect(jsonLogic.apply(logicTrue)).to.be.equal(true);
21 | expect(jsonLogic.apply(logicFalse)).to.be.equal(false);
22 | });
23 | });
24 | describe.skip('Math', () => {
25 | it('add', () => {
26 | const logic = {_add: [2, 3]};
27 | expect(jsonLogic.apply(logic)).to.be.equal(5);
28 | });
29 | it('ceil', () => {
30 | const logic = {_ceil: [4.006]};
31 | expect(jsonLogic.apply(logic)).to.be.equal(5);
32 | });
33 | it('divide', () => {
34 | const logic = {_divide: [6, 3]};
35 | expect(jsonLogic.apply(logic)).to.be.equal(2);
36 | });
37 | it('floor', () => {
38 | const logic = {_floor: [4.906]};
39 | expect(jsonLogic.apply(logic)).to.be.equal(4);
40 | });
41 | it('max', () => {
42 | const logic = {_max: [[2, 5, 6, 3]]};
43 | expect(jsonLogic.apply(logic)).to.be.equal(6);
44 | });
45 | it('maxBy', () => {
46 | const data = [{n: 4}, {n: 2}, {n: 8}, {n: 6}];
47 | const logic1 = {_maxBy: [{'var': ''}, 'n']};
48 | const logic2 = {_maxBy: [{'var': ''}, {_property: 'n'}]};
49 | expect(jsonLogic.apply(logic1, data)).to.be.equal(data[2]);
50 | expect(jsonLogic.apply(logic2, data)).to.be.equal(data[2]);
51 | });
52 | it('mean', () => {
53 | const logic = {_mean: [[2, 5, 6, 3]]};
54 | expect(jsonLogic.apply(logic)).to.be.equal(4);
55 | });
56 | it('meanBy', () => {
57 | const data = [{n: 4}, {n: 2}, {n: 8}, {n: 6}];
58 | const logic1 = {_meanBy: [{'var': ''}, 'n']};
59 | const logic2 = {_meanBy: [{'var': ''}, {_property: 'n'}]};
60 | expect(jsonLogic.apply(logic1, data)).to.be.equal(5);
61 | expect(jsonLogic.apply(logic2, data)).to.be.equal(5);
62 | });
63 | it('min', () => {
64 | const logic = {_min: [[2, 5, 6, 3]]};
65 | expect(jsonLogic.apply(logic)).to.be.equal(2);
66 | });
67 | it('minBy', () => {
68 | const data = [{n: 4}, {n: 2}, {n: 8}, {n: 6}];
69 | const logic1 = {_minBy: [{'var': ''}, 'n']};
70 | const logic2 = {_minBy: [{'var': ''}, {_property: 'n'}]};
71 | expect(jsonLogic.apply(logic1, data)).to.be.equal(data[1]);
72 | expect(jsonLogic.apply(logic2, data)).to.be.equal(data[1]);
73 | });
74 | it('multiply', () => {
75 | const logic = {_multiply: [2, 3]};
76 | expect(jsonLogic.apply(logic)).to.be.equal(6);
77 | });
78 | it('round', () => {
79 | const logic1 = {_round: [4.006]};
80 | const logic2 = {_round: [4.906]};
81 | expect(jsonLogic.apply(logic1)).to.be.equal(4);
82 | expect(jsonLogic.apply(logic2)).to.be.equal(5);
83 | });
84 | it('multiply', () => {
85 | const logic = {_multiply: [2, 3]};
86 | expect(jsonLogic.apply(logic)).to.be.equal(6);
87 | });
88 | it('subtract', () => {
89 | const logic = {_subtract: [2, 3]};
90 | expect(jsonLogic.apply(logic)).to.be.equal(-1);
91 | });
92 | it('sum', () => {
93 | const logic = {_sum: [[2, 3]]};
94 | expect(jsonLogic.apply(logic)).to.be.equal(5);
95 | });
96 | it('sumBy', () => {
97 | const data = [{n: 4}, {n: 2}, {n: 8}, {n: 6}];
98 | const logic1 = {_sumBy: [{'var': ''}, 'n']};
99 | const logic2 = {_sumBy: [{'var': ''}, {_property: 'n'}]};
100 | expect(jsonLogic.apply(logic1, data)).to.be.equal(20);
101 | expect(jsonLogic.apply(logic2, data)).to.be.equal(20);
102 | });
103 | });
104 | describe.skip('Number', () => {
105 |
106 | });
107 | describe.skip('Object', () => {
108 |
109 | });
110 | describe.skip('String', () => {
111 |
112 | });
113 | describe.skip('Util', () => {
114 | it('property', () => {
115 | const data = [
116 | {'a': {'b': 2}},
117 | {'a': {'b': 1}}
118 | ];
119 | const logic = {_sumBy: [{'var': ''}, {_property: 'a.b'}]};
120 | expect(jsonLogic.apply(logic, data)).to.be.equal(3);
121 | });
122 | });
123 | });
124 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | // Master export file for multiple components and features.
2 | export * from './components';
3 | export * from './factories';
4 | export * from './util';
5 |
--------------------------------------------------------------------------------
/src/util/constants.js:
--------------------------------------------------------------------------------
1 | export const ErrorTypes = {
2 | FieldValueError: 'FieldValueError',
3 | FormFetchError: 'FormFetchError',
4 | FormLoadError: 'FormLoadError',
5 | SubmissionError: 'SubmissionError',
6 | VieldValidationError: 'VieldValidationError',
7 | };
8 |
--------------------------------------------------------------------------------
/src/util/index.js:
--------------------------------------------------------------------------------
1 | import get from 'lodash/get';
2 | import has from 'lodash/has';
3 | import isEqual from 'lodash/isEqual';
4 |
5 | function defineTransformerOutsideStrictMode() {
6 | var safeGlobalName = '____formioSelectMixinGetTransformer';
7 | var globalObject = typeof global !== 'undefined' ? global : {};
8 |
9 | /* We are essentially doing this, but because we're in strict mode by default in all babeled
10 | * modules, we need to escape it
11 | *
12 | * //string-replace callback, called for every match in the template.
13 | * function transform (_, expression) {
14 | * //bring the properties of 'props' into local scope so that the expression can reference them
15 | * with (props) {
16 | * return eval(expression); //evaluate the expression.
17 | * }
18 | * }
19 | */
20 |
21 | // This escapes strict mode.
22 | try {
23 | (1,eval)('function '+safeGlobalName+' (props) { return function (_, exp) { with(props) { return eval(exp); } } }');
24 | }
25 | catch (error) {
26 | return '';
27 | }
28 |
29 | var ret = eval(safeGlobalName);
30 |
31 | // Cleanup
32 | // This can throw errors in some versions of IE for unknown reasons at this time.
33 | if (globalObject[safeGlobalName]) {
34 | delete globalObject[safeGlobalName];
35 | }
36 |
37 | return ret;
38 | }
39 |
40 | var getTransformer = defineTransformerOutsideStrictMode();
41 |
42 | /**
43 | * This function is intended to mimic the Angular $interpolate function.
44 | *
45 | * Since template strings are created using Angular syntax, we need to mimic rendering them to strings. This function
46 | * will take the template and the associated variables and render it out. It currently does basic compiling but
47 | * is not full featured compatible with Angular.
48 | *
49 | * @param template
50 | * @param variables
51 | * @returns {string|XML|*|void}
52 | */
53 | export const interpolate = (template, variables) => {
54 | var transform = getTransformer(variables);
55 | //find all {{ }} expression blocks and then replace the blocks with their evaluation.
56 | try {
57 | return template.replace(/\{\s*\{([^\}]*)\}\s*\}/gm, transform);
58 | }
59 | catch (error) {
60 | return '';
61 | }
62 | };
63 |
64 | /**
65 | * This function serializes an object to a queryString.
66 | * @param obj
67 | * @returns {string}
68 | */
69 | export const serialize = obj => {
70 | var str = [];
71 | for (var p in obj) {
72 | if (obj.hasOwnProperty(p)) {
73 | str.push(encodeURIComponent(p) + '=' + encodeURIComponent(obj[p]));
74 | }
75 | }
76 | return str.join('&');
77 | };
78 |
79 | //helper function to render raw html under a react element.
80 | export const raw = function(html) {
81 | return {dangerouslySetInnerHTML: {__html: html}};
82 | };
83 |
84 | export const fileSize = function(a, b, c, d, e) {
85 | /* eslint-disable space-before-function-paren */
86 | return (b = Math, c = b.log, d = 1024, e = c(a) / c(d) | 0, a / b.pow(d, e)).toFixed(2) + ' ' + (e ? 'kMGTPEZY'[--e] + 'B' : 'Bytes');
87 | };
88 |
89 | // Resolve nested values within an array.
90 | export const nested = function({rowData, column}) {
91 | const {property} = column;
92 |
93 | if (!property) {
94 | return {};
95 | }
96 |
97 | if (!has(rowData, property)) {
98 | //console.warn( // eslint-disable-line no-console
99 | // `resolve.nested - Failed to find "${property}" property from`,
100 | // rowData
101 | //);
102 |
103 | return {};
104 | }
105 |
106 | return {
107 | ...rowData,
108 | [property]: get(rowData, property)
109 | };
110 | };
111 |
112 | export const deepEqual = isEqual;
113 |
--------------------------------------------------------------------------------
/test/.setup.js:
--------------------------------------------------------------------------------
1 |
2 | import { Response, Headers, Request } from 'node-fetch';
3 |
4 | global.Response = Response;
5 | global.Headers = Headers;
6 | global.Request = Request;
7 |
8 | jest.useFakeTimers()
9 |
10 | jest.mock('react-native-signature-capture', () => {
11 | return () => ('SignatureCapture')
12 | })
13 |
14 | jest.mock('react-native-material-dropdown', () => {
15 | return {
16 | Dropdown: ()=> ('Dropdown')
17 | }
18 | })
19 |
20 | jest.mock('react-native-multiple-select', () => {
21 | return () => ('MultiSelect')
22 | })
23 |
24 | jest.mock('react-native-modal-datetime-picker', () => {
25 | return () => ('DateTimePicker')
26 | })
27 |
28 | jest.mock('react-native-htmlview', () => {
29 | return () => ('HTMLView')
30 | })
31 |
32 | jest.mock('react-native-device-info', () => {
33 | return {
34 | isTablet: () => (true)
35 | }
36 | })
37 |
38 | jest.mock('react-native-select-multiple', () => {
39 | return () => ('SelectBoxes')
40 | })
--------------------------------------------------------------------------------
/test/__snapshots__/changes.spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Change Events @change fires change events on a form 1`] = `
4 |
17 |
18 |
25 |
32 |
40 |
69 |
86 |
87 |
88 |
89 |
106 |
107 |
108 |
109 | My Textfield
110 |
111 |
128 |
129 |
130 |
131 |
132 |
148 |
181 |
182 |
183 |
184 |
185 |
186 | `;
187 |
--------------------------------------------------------------------------------
/test/forms/componentSpec.js:
--------------------------------------------------------------------------------
1 |
2 | //Spec for textfeild component
3 | export var textfeild = {
4 | 'input': true,
5 | 'tableView': true,
6 | 'inputType': 'text',
7 | 'inputMask': '',
8 | 'label': 'My Textfield',
9 | 'key': 'myTextfield',
10 | 'placeholder': '',
11 | 'prefix': '',
12 | 'suffix': '',
13 | 'multiple': false,
14 | 'defaultValue': '',
15 | 'protected': false,
16 | 'unique': false,
17 | 'persistent': true,
18 | 'validate': {
19 | 'required': false,
20 | 'minLength': '',
21 | 'maxLength': '',
22 | 'pattern': '',
23 | 'custom': '',
24 | 'customPrivate': false
25 | },
26 | 'conditional': {
27 | 'show': null,
28 | 'when': null,
29 | 'eq': ''
30 | },
31 | 'type': 'textfield'
32 | },
33 | //Spec for password component
34 | password= {
35 | 'input': true,
36 | 'tableView': false,
37 | 'inputType': 'password',
38 | 'label': 'Password',
39 | 'key': 'password',
40 | 'placeholder': '',
41 | 'prefix': '',
42 | 'suffix': '',
43 | 'protected': true,
44 | 'persistent': true,
45 | 'inputMask': '',
46 | 'multiple': false,
47 | 'defaultValue': '',
48 | 'unique': false,
49 | 'validate': {
50 | 'required': false,
51 | 'minLength': '',
52 | 'maxLength': '',
53 | 'pattern': '',
54 | 'custom': '',
55 | 'customPrivate': false
56 | },
57 | 'conditional': {
58 | 'show': null,
59 | 'when': null,
60 | 'eq': ''
61 | },
62 | 'type': 'password'
63 | },
64 | //Spec for phoneNumber component
65 | phoneNumber= {
66 | 'conditional': {
67 | 'eq': '',
68 | 'when': null,
69 | 'show': ''
70 | },
71 | 'type': 'phoneNumber',
72 | 'validate': {
73 | 'required': false
74 | },
75 | 'defaultValue': '',
76 | 'persistent': true,
77 | 'unique': false,
78 | 'protected': false,
79 | 'multiple': false,
80 | 'suffix': '',
81 | 'prefix': '',
82 | 'placeholder': '',
83 | 'key': 'phoneNumber',
84 | 'label': 'Phone Number',
85 | 'inputMask': '(999) 999-9999',
86 | 'tableView': true,
87 | 'input': true
88 | };
89 |
--------------------------------------------------------------------------------
/test/forms/empty.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Textfield",
3 | "display": "form",
4 | "type": "form",
5 | "name": "textfield",
6 | "path": "textfield",
7 | "components": []
8 | }
--------------------------------------------------------------------------------
/test/forms/tmp.json:
--------------------------------------------------------------------------------
1 | {
2 | "_id": "58ae037c593e8c00967f124b",
3 | "modified": "2017-02-23T21:53:22.291Z",
4 | "title": "conditional",
5 | "display": "form",
6 | "type": "form",
7 | "name": "conditional",
8 | "path": "conditional",
9 | "project": "5811179a7fd506006b4a8327",
10 | "created": "2017-02-22T21:32:44.599Z",
11 | "components": [
12 | {
13 | "conditional": {
14 | "eq": "",
15 | "when": null,
16 | "show": ""
17 | },
18 | "tags": [],
19 | "type": "checkbox",
20 | "validate": {
21 | "required": false
22 | },
23 | "clearOnHide": true,
24 | "persistent": true,
25 | "protected": false,
26 | "defaultValue": false,
27 | "key": "visible",
28 | "datagridLabel": true,
29 | "label": "Visible",
30 | "hideLabel": true,
31 | "tableView": true,
32 | "inputType": "checkbox",
33 | "input": true,
34 | "lockKey": true
35 | },
36 | {
37 | "conditional": {
38 | "eq": "true",
39 | "when": "visible",
40 | "show": "true"
41 | },
42 | "tags": [],
43 | "type": "datagrid",
44 | "clearOnHide": true,
45 | "persistent": true,
46 | "protected": false,
47 | "key": "datagrid1",
48 | "label": "Datagrid",
49 | "tableView": true,
50 | "components": [
51 | {
52 | "hideLabel": true,
53 | "isNew": false,
54 | "tags": [],
55 | "type": "textfield",
56 | "conditional": {
57 | "eq": "",
58 | "when": null,
59 | "show": ""
60 | },
61 | "validate": {
62 | "customPrivate": false,
63 | "custom": "",
64 | "pattern": "",
65 | "maxLength": "",
66 | "minLength": "",
67 | "required": true
68 | },
69 | "clearOnHide": true,
70 | "persistent": true,
71 | "unique": false,
72 | "protected": false,
73 | "defaultValue": "",
74 | "multiple": false,
75 | "suffix": "",
76 | "prefix": "",
77 | "placeholder": "",
78 | "key": "textfield",
79 | "label": "Textfield",
80 | "inputMask": "",
81 | "inputType": "text",
82 | "tableView": true,
83 | "input": true
84 | }
85 | ],
86 | "tree": true,
87 | "input": true
88 | }
89 | ],
90 | "owner": "55673dc04f0405dd28205bb7",
91 | "submissionAccess": [],
92 | "access": [
93 | {
94 | "type": "read_all",
95 | "roles": [
96 | "5811179a7fd506006b4a8328",
97 | "5811179a7fd506006b4a8329",
98 | "5811179a7fd506006b4a832a"
99 | ]
100 | }
101 | ],
102 | "tags": []
103 | }/**/
--------------------------------------------------------------------------------
/test/submissions-wip.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Formio from '../src/Formio';
3 | import renderer from 'react-test-renderer';
4 | import {fetch, Headers} from 'whatwg-fetch';
5 | global.window.Headers = Headers;
6 | //global.window.fetch = fetch;
7 |
8 | describe.skip('Submission tests @submission', function() {
9 | beforeAll(function(done) {
10 | var form = {
11 | 'title': 'My Form',
12 | 'display': 'form',
13 | 'type': 'form',
14 | 'name': 'myform',
15 | 'path': 'myform',
16 | 'components': [
17 | {
18 | 'input': true,
19 | 'tableView': true,
20 | 'inputType': 'text',
21 | 'inputMask': '',
22 | 'label': 'Name',
23 | 'key': 'name',
24 | 'placeholder': '',
25 | 'prefix': '',
26 | 'suffix': '',
27 | 'multiple': false,
28 | 'defaultValue': '',
29 | 'protected': false,
30 | 'unique': false,
31 | 'persistent': true,
32 | 'validate': {
33 | 'required': false,
34 | 'minLength': '',
35 | 'maxLength': '',
36 | 'pattern': '',
37 | 'custom': '',
38 | 'customPrivate': false
39 | },
40 | 'type': 'textfield'
41 | },
42 | {
43 | 'input': true,
44 | 'tableView': true,
45 | 'label': 'Message',
46 | 'key': 'message',
47 | 'placeholder': '',
48 | 'prefix': '',
49 | 'suffix': '',
50 | 'rows': 3,
51 | 'multiple': false,
52 | 'defaultValue': '',
53 | 'protected': false,
54 | 'persistent': true,
55 | 'validate': {
56 | 'required': false,
57 | 'minLength': '',
58 | 'maxLength': '',
59 | 'pattern': '',
60 | 'custom': ''
61 | },
62 | 'type': 'textarea'
63 | },
64 | {
65 | 'input': true,
66 | 'label': 'Submit',
67 | 'tableView': false,
68 | 'key': 'submit',
69 | 'size': 'md',
70 | 'leftIcon': '',
71 | 'rightIcon': '',
72 | 'block': false,
73 | 'action': 'submit',
74 | 'disableOnInvalid': true,
75 | 'theme': 'primary',
76 | 'type': 'button'
77 | }
78 | ]
79 | };
80 |
81 | jest.mock('fetch')
82 | .withArgs('https://myproject.form.io/myform', jest.match.any).yields(null, {statusCode: 200, headers: {}}, Promise.resolve(form));
83 |
84 | done();
85 | });
86 |
87 | it('Submits the form to form.io', function(done) {
88 | var onFormLoad = function(form) {
89 | done();
90 | };
91 | const element = renderer.create(
92 |
96 | );
97 | });
98 |
99 | afterAll(function(done) {
100 | done();
101 | });
102 | });
103 |
--------------------------------------------------------------------------------