├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── common
└── config
│ └── rush
│ └── npm-shrinkwrap.json
├── docs
└── 2017-04
│ ├── 2017-04-08.md
│ ├── 2017-04-14.md
│ ├── 2017-04-26.md
│ └── 2017-04-27.md
├── package.json
├── packages
├── react-forms-dom
│ ├── .gitignore
│ ├── README.md
│ ├── __tests__
│ │ ├── components
│ │ │ ├── clear.test.tsx
│ │ │ ├── form.test.tsx
│ │ │ ├── reset.test.tsx
│ │ │ └── text.test.tsx
│ │ └── tsconfig.json
│ ├── package.json
│ ├── src
│ │ ├── abstractions.ts
│ │ ├── abstractions
│ │ │ ├── base-dom-field.tsx
│ │ │ ├── base-errors-container.ts
│ │ │ ├── base-form-button.tsx
│ │ │ └── index.ts
│ │ ├── components
│ │ │ ├── checkbox.tsx
│ │ │ ├── clear.tsx
│ │ │ ├── email.tsx
│ │ │ ├── errors-container.ts
│ │ │ ├── fields-array.tsx
│ │ │ ├── fields-group.tsx
│ │ │ ├── form.tsx
│ │ │ ├── hidden.tsx
│ │ │ ├── number.tsx
│ │ │ ├── password.tsx
│ │ │ ├── radio-group.tsx
│ │ │ ├── radio.tsx
│ │ │ ├── reset.tsx
│ │ │ ├── search.tsx
│ │ │ ├── select.tsx
│ │ │ ├── submit.tsx
│ │ │ ├── text.tsx
│ │ │ └── textarea.tsx
│ │ ├── contracts.ts
│ │ ├── contracts
│ │ │ ├── field.ts
│ │ │ └── form.ts
│ │ └── index.ts
│ ├── tools
│ │ ├── gulpfile.ts
│ │ ├── tsconfig.gulp.json
│ │ └── webpack.config.ts
│ ├── tsconfig.json
│ └── tslint.json
├── react-forms-validation
│ ├── .gitignore
│ ├── README.md
│ ├── __tests__
│ │ ├── subscribers
│ │ │ ├── form-store-subscriber.test.tsx
│ │ │ └── form-stores-handler-subscriber.test.ts
│ │ ├── tsconfig.json
│ │ └── validation.test.tsx
│ ├── package.json
│ ├── src
│ │ ├── abstractions.ts
│ │ ├── abstractions
│ │ │ ├── base-field-validator.ts
│ │ │ ├── base-form-validator.ts
│ │ │ ├── base-validator.ts
│ │ │ └── index.ts
│ │ ├── contracts.ts
│ │ ├── index.ts
│ │ ├── subscribers
│ │ │ ├── form-store-subscriber.ts
│ │ │ ├── form-stores-handler-subscriber.ts
│ │ │ ├── index.ts
│ │ │ └── subscriber.ts
│ │ ├── utils.ts
│ │ ├── utils
│ │ │ ├── index.ts
│ │ │ └── validation.ts
│ │ └── validators
│ │ │ ├── alpha.ts
│ │ │ ├── alphanumeric.ts
│ │ │ ├── ascii.ts
│ │ │ ├── base64.ts
│ │ │ ├── boolean.ts
│ │ │ ├── byte-length.ts
│ │ │ ├── contains.ts
│ │ │ ├── credit-card.ts
│ │ │ ├── currency.ts
│ │ │ ├── equals.ts
│ │ │ ├── index.ts
│ │ │ └── required.ts
│ ├── tools
│ │ ├── gulpfile.ts
│ │ ├── tsconfig.gulp.json
│ │ └── webpack.config.ts
│ ├── tsconfig.json
│ └── tslint.json
└── react-forms
│ ├── .gitignore
│ ├── README.md
│ ├── __tests__
│ ├── abstractions
│ │ ├── base-field.test.tsx
│ │ ├── base-fields-group.test.tsx
│ │ └── base-form.test.tsx
│ ├── stores
│ │ ├── form-store.test.ts
│ │ └── form-stores-handler.test.ts
│ ├── test-components
│ │ ├── test-field.tsx
│ │ ├── test-fields-group.tsx
│ │ └── test-form.tsx
│ └── tsconfig.json
│ ├── package.json
│ ├── src
│ ├── abstractions
│ │ ├── base-container.ts
│ │ ├── base-field.ts
│ │ ├── base-fields-array.ts
│ │ ├── base-fields-group.ts
│ │ ├── base-form.ts
│ │ ├── core-field.ts
│ │ └── index.ts
│ ├── actions.ts
│ ├── actions
│ │ ├── form-store.ts
│ │ ├── form-stores-handler.ts
│ │ └── index.ts
│ ├── contracts.ts
│ ├── contracts
│ │ ├── error.ts
│ │ ├── field.ts
│ │ ├── fields-array.ts
│ │ ├── fields-group.ts
│ │ ├── form-store.ts
│ │ ├── form.ts
│ │ ├── index.ts
│ │ ├── validation.ts
│ │ └── value.ts
│ ├── index.ts
│ ├── modifiers.ts
│ ├── modifiers
│ │ ├── base-modifier.ts
│ │ ├── index.ts
│ │ └── string-to-decimal.ts
│ ├── normalizers.ts
│ ├── normalizers
│ │ ├── alphanumeric.ts
│ │ ├── base-normalizer.ts
│ │ ├── index.ts
│ │ ├── lower-case.ts
│ │ └── upper-case.ts
│ ├── stores.ts
│ ├── stores
│ │ ├── form-store-helpers.ts
│ │ ├── form-store.ts
│ │ ├── form-stores-handler.ts
│ │ └── index.ts
│ ├── utils.ts
│ └── utils
│ │ ├── form-error-helpers.ts
│ │ ├── index.ts
│ │ └── value-helpers.ts
│ ├── tools
│ ├── gulpfile.ts
│ ├── tsconfig.gulp.json
│ └── webpack.config.ts
│ ├── tsconfig.json
│ └── tslint.json
├── rush.json
├── tools
├── mvdir
│ ├── .gitignore
│ ├── dist
│ │ ├── arguments.d.ts
│ │ ├── arguments.js
│ │ ├── cli.d.ts
│ │ ├── cli.js
│ │ ├── contracts.d.ts
│ │ ├── contracts.js
│ │ ├── index.d.ts
│ │ └── index.js
│ ├── package.json
│ ├── src
│ │ ├── arguments.ts
│ │ ├── cli.ts
│ │ ├── contracts.ts
│ │ └── index.ts
│ ├── tsconfig.json
│ └── tslint.json
└── rush-tools.ts
└── tslint.json
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | notifications:
3 | email:
4 | on_success: never
5 | on_failure: change
6 | node_js:
7 | - stable
8 | script:
9 | - npm run generate
10 | - npm run tools-build
11 | - npm run source-build
12 | - npm test
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
6 | @simplr/react-forms
7 | Declarative forms for React. Includes declarative modifiers, normalizers and validators.
8 |
9 | ### Packages
10 |
11 | | Package | Version | Docs | Description |
12 | |---------|---------|------|-------------|
13 | | [`@simplr/react-forms`](/packages/react-forms) | [](https://www.npmjs.com/package/@simplr/react-forms) | TBA | The core |
14 | | [`@simplr/react-forms-dom`](/packages/react-forms-dom) | [](https://www.npmjs.com/package/@simplr/react-forms-dom) | TBA | DOM components |
15 | | [`@simplr/react-forms-validation`](/packages/react-forms-validation) | [](https://www.npmjs.com/package/@simplr/react-forms-dom) | TBA | Validation |
16 |
17 |
18 | ### Development
19 |
20 | Latest published version should be in the [master](https://github.com/SimplrJS/react-forms/tree/master) branch.
21 | Development is happening in the [dev](https://github.com/SimplrJS/react-forms/tree/dev) and feature branches.
22 |
23 | Core Team
24 |
25 |
59 |
60 | ### Notes
61 |
62 | Notes can be found in this repo and in Medium:
63 |
64 | * [Part 1: Why are we doing this?](https://medium.com/@DovydasNavickas/part-1-simplr-forms-declarative-forms-for-react-why-are-we-doing-this-293b9a9c45bd)
65 | * [Part 2: Core, validation, testing.](https://medium.com/@DovydasNavickas/part-2-simplr-forms-declarative-forms-for-react-core-validation-testing-fd9494304305)
66 | * [Part 3: First e2e flow, FormStore.ToObject()](https://medium.com/@DovydasNavickas/part-3-simplr-forms-declarative-forms-for-react-first-e2e-flow-formstore-toobject-ed81d930e14c)
67 | * [Part 4: Normalizers and Modifiers](https://medium.com/@DovydasNavickas/part-4-simplr-forms-declarative-forms-for-react-normalizers-and-modifiers-6cefbd0269c4)
68 |
--------------------------------------------------------------------------------
/docs/2017-04/2017-04-14.md:
--------------------------------------------------------------------------------
1 | ## April 14 ([discuss](https://github.com/SimplrJS/simplr-forms/pull/4))
2 |
3 | ### Attendees
4 |
5 | * [Dovydas](https://twitter.com/dovydasnav) (QuatroDev)
6 | * [Martynas](https://twitter.com/MartinZilinskas) (QuatroDev)
7 | * [Giedrius](https://twitter.com/giedrucis) (QuatroDev)
8 | * [Aurimas](https://twitter.com/waikys) (QuatroDev)
9 |
10 | ### simplr-forms-core is done for pre-alpha
11 |
12 | During the week, we worked on `simplr-forms-core` to have a solid foundation for `simplr-forms-dom` and `simplr-forms-native`.
13 |
14 | Most of the coding was done by Dovydas and Martynas, with valuable and fast reviews by Giedrius.
15 | Feels good to work with such an amazing team.
16 |
17 | Also, Aurimas joined our efforts by preparing react-native app for development of `simplr-forms-native`. Thanks, Aurimas!
18 |
19 | We will start working on both `dom` and `native` packages soon.
20 |
21 | ### So... What happened during the week?
22 |
23 | We can confidently say that we have a solid first appearances of `core`, which consists of:
24 | * [`form-stores-handler`][1] - a general manager for all forms data stores
25 | * [`form-store`][2] - a data store for one particular form
26 | * [`base-form`][3] - a base component for a form to register to store and provide context for children, register with `form-stores-handler`, etc.
27 | * [`base-field`][4] - a base component for field that is aware of the parent form, registers and registers to and from form store, incorporates React lifecycle events, etc.
28 |
29 | These 4 pretty much cover the base of the whole library.
30 |
31 | Of course, there is more to it.
32 |
33 | [`form-stores-handler`][1] and [`base-form`][3] extend [`ActionEmitter`][5] for you to be able to `addListener`
34 | for actions and know when the store is created, updates it's state or even more specific [actions][6],
35 | e.g. [value][7] or [props][8] are updated.
36 |
37 | This enables another amazing thing: now we can externalize validation!
38 |
39 | > Shoutout to Giedrius for publishing [`ActionEmitter`][5] right on time.
40 | > And for us all to debugging it in a single day :smile:
41 |
42 | ### Externalized validation
43 |
44 | We understand that most developers use whatever they get out-of-the-box,
45 | especially if what they get is an easy-to-consume and also an optimal solution.
46 |
47 | But sometimes usage of project-specific validation library is a must.
48 |
49 | For example, there are quite a few industries with very specific data structures and their validations.
50 | Medicine, aviation, insurance, finance... And that's just a few on the tip of the tongue.
51 |
52 | Therefore, externalizing validation is an amazing step forward to using `simplr-forms`
53 | no matter what data validation requirements you have.
54 |
55 | ### Testing
56 |
57 | A library without tests these days is a scary and unstable bomb with a timer that shows gibberish on it's timer,
58 | i.e. you don't know when it is gonna hit you.
59 |
60 | Therefore, we already have 44 tests in 4 test suites.
61 |
62 | And we will write as much more as we need to ensure everything will progress forward
63 | and this library becomes de-facto a standard, a no brainer when anyone thinks about React and forms.
64 |
65 | ### What's next?
66 |
67 | Releasing pre-alpha of `simplr-forms-core` and starting development of `simplr-forms-dom` and (maybe) `simplr-forms-native`.
68 |
69 | Also, `simplr-validation` is coming into the light as we evaluate all of the peaces for it.
70 |
71 | And now... It's coding time again! :tada:
72 |
73 | ------------
74 |
75 | The feedback and questions are more than welcome!
76 |
77 | ------------
78 |
79 | Please feel free to discuss these notes in the [corresponding pull request](https://github.com/SimplrJS/simplr-forms/pull/4).
80 |
81 | [1]: https://github.com/SimplrJS/simplr-forms/blob/dev/packages/simplr-forms-core/src/stores/form-stores-handler.ts
82 | [2]: https://github.com/SimplrJS/simplr-forms/blob/dev/packages/simplr-forms-core/src/stores/form-store.ts
83 | [3]: https://github.com/SimplrJS/simplr-forms/blob/dev/packages/simplr-forms-core/src/abstractions/base-form.ts
84 | [4]: https://github.com/SimplrJS/simplr-forms/blob/dev/packages/simplr-forms-core/src/abstractions/base-field.ts
85 | [5]: https://github.com/SimplrJS/action-emitter
86 | [6]: https://github.com/SimplrJS/simplr-forms/tree/dev/packages/simplr-forms-core/src/actions
87 | [7]: https://github.com/SimplrJS/simplr-forms/blob/dev/packages/simplr-forms-core/src/actions/form-store-actions.ts#L3-L9
88 | [8]: https://github.com/SimplrJS/simplr-forms/blob/dev/packages/simplr-forms-core/src/actions/form-store-actions.ts#L11-L17
89 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@simplr/react-forms-repo",
3 | "private": true,
4 | "scripts": {
5 | "generate": "rush generate",
6 | "tools-build": "npm run rush-tools -- run gulp-build -e @simplr/mvdir",
7 | "source-build": "npm run rush-tools -- run build -e @simplr/mvdir",
8 | "test": "npm run rush-tools -- run test -e @simplr/mvdir",
9 | "rush-tools": "ts-node ./tools/rush-tools.ts",
10 | "publish": "npm run rush-tools -- publish --access public"
11 | },
12 | "devDependencies": {
13 | "@microsoft/rush": "^3.0.9",
14 | "@microsoft/rush-lib": "^3.0.9",
15 | "@types/shelljs": "^0.7.2",
16 | "@types/yargs": "^6.6.0",
17 | "mz": "^2.6.0",
18 | "shelljs": "^0.7.8",
19 | "ts-node": "^3.0.6",
20 | "typescript": "^2.3.4",
21 | "yargs": "^8.0.2"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/react-forms-dom/.gitignore:
--------------------------------------------------------------------------------
1 | *.js
2 | **/*d.ts
3 |
--------------------------------------------------------------------------------
/packages/react-forms-dom/README.md:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | @simplr/react-forms-dom
16 |
17 | DOM implementation for `@simplr/react-forms`.
18 |
--------------------------------------------------------------------------------
/packages/react-forms-dom/__tests__/components/clear.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { mount } from "enzyme";
3 | import * as Sinon from "sinon";
4 |
5 | import { FormStore } from "@simplr/react-forms/stores";
6 |
7 | import { Form } from "../../src/components/form";
8 | import { Text } from "../../src/components/text";
9 | import { Clear } from "../../src/components/clear";
10 |
11 | describe("Clear button", () => {
12 | let sandbox: Sinon.SinonSandbox;
13 |
14 | beforeEach(() => {
15 | sandbox = Sinon.sandbox.create();
16 | });
17 |
18 | afterEach(() => {
19 | sandbox.restore();
20 | });
21 |
22 | it("clears all fields", () => {
23 | const callback = sandbox.stub(FormStore.prototype, "ClearFields");
24 |
25 | const wrapper = mount();
29 |
30 | wrapper.find("button").simulate("click");
31 |
32 | expect(callback.called).toBe(true);
33 | });
34 |
35 | it("clears fields by fieldsIds", () => {
36 | const callback = sandbox.stub(FormStore.prototype, "ClearFields");
37 | const fieldsIds: string[] = [];
38 | for (let i = 0; i < 5; i++) {
39 | fieldsIds.push(`text-${i}`);
40 | }
41 | const fields: JSX.Element[] = [];
42 | fieldsIds.forEach(value => {
43 | fields.push( );
44 | });
45 |
46 | const wrapper = mount();
50 |
51 | wrapper.find("button").simulate("click");
52 |
53 | expect(callback.called).toBe(true);
54 | // first click, first argument(fieldIds)
55 | expect(callback.args[0][0]).toBe(fieldsIds);
56 | });
57 | });
58 |
--------------------------------------------------------------------------------
/packages/react-forms-dom/__tests__/components/form.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { mount } from "enzyme";
3 | import * as Sinon from "sinon";
4 |
5 | import { FSHContainer, FormStoresHandler } from "@simplr/react-forms/stores";
6 |
7 | import { Form } from "../../src/components/form";
8 |
9 | describe("Form", () => {
10 | beforeEach(() => {
11 | FSHContainer.SetFormStoresHandler(new FormStoresHandler(), true);
12 | });
13 |
14 | it("calls submit callback when submit button is clicked", () => {
15 | const submitCallback = Sinon.stub();
16 |
17 | const wrapper = mount();
20 |
21 | wrapper.find("button").simulate("submit");
22 |
23 | expect(submitCallback.called).toBe(true);
24 | });
25 |
26 | it("calls submit callback when submit called from FormStore", () => {
27 | const formId = "form-id";
28 | const submitCallback = Sinon.stub();
29 |
30 | mount();
31 |
32 | const formStore = FSHContainer.FormStoresHandler.GetStore(formId);
33 | formStore.InitiateFormSubmit();
34 |
35 | expect(submitCallback.called).toBe(true);
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/packages/react-forms-dom/__tests__/components/reset.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { mount } from "enzyme";
3 | import * as Sinon from "sinon";
4 |
5 | import { FormStore } from "@simplr/react-forms/stores";
6 |
7 | import { Form } from "../../src/components/form";
8 | import { Text } from "../../src/components/text";
9 | import { Reset } from "../../src/components/reset";
10 |
11 | describe("Reset button", () => {
12 | let sandbox: Sinon.SinonSandbox;
13 |
14 | beforeEach(() => {
15 | sandbox = Sinon.sandbox.create();
16 | });
17 |
18 | afterEach(() => {
19 | sandbox.restore();
20 | });
21 |
22 | it("reset all fields", () => {
23 | const callback = sandbox.spy(FormStore.prototype, "ResetFields");
24 |
25 | const wrapper = mount();
29 |
30 | wrapper.find("button").simulate("click");
31 |
32 | expect(callback.called).toBe(true);
33 | });
34 |
35 | it("reset fields by fieldsIds", () => {
36 | const callback = sandbox.spy(FormStore.prototype, "ResetFields");
37 | const fieldsIds: string[] = [];
38 | for (let i = 0; i < 5; i++) {
39 | fieldsIds.push(`text-${i}`);
40 | }
41 | const fields: JSX.Element[] = [];
42 | fieldsIds.forEach(value => {
43 | fields.push( );
44 | });
45 |
46 | const wrapper = mount();
50 |
51 | wrapper.find("button").simulate("click");
52 |
53 | expect(callback.called).toBe(true);
54 | // first click, first argument(fieldIds)
55 | expect(callback.args[0][0]).toBe(fieldsIds);
56 | });
57 | });
58 |
--------------------------------------------------------------------------------
/packages/react-forms-dom/__tests__/components/text.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { mount } from "enzyme";
3 | import * as Sinon from "sinon";
4 |
5 | import { FSHContainer, FormStoresHandler } from "@simplr/react-forms/stores";
6 | import { Form } from "../../src/components/form";
7 | import { Text } from "../../src/components/text";
8 |
9 | describe("Text field", () => {
10 | let sandbox: Sinon.SinonSandbox;
11 | beforeEach(() => {
12 | FSHContainer.SetFormStoresHandler(new FormStoresHandler(), true);
13 | sandbox = Sinon.sandbox.create();
14 | });
15 |
16 | afterEach(() => {
17 | sandbox.restore();
18 | });
19 |
20 | it("change value from input", () => {
21 | const formId = "formId";
22 | const fieldName = "field name";
23 | const nextValue = "next value";
24 | const wrapper = mount();
27 |
28 | const formStore = FSHContainer.FormStoresHandler.GetStore(formId);
29 |
30 | wrapper.find("input").simulate("change", { target: { value: nextValue } });
31 |
32 | expect(wrapper.find("input").props().value).toBe(nextValue);
33 | expect(formStore.GetField(fieldName).Value).toBe(nextValue);
34 | });
35 |
36 | it("change value triggers onChange callback", () => {
37 | const fieldName = "field name";
38 | const nextValue = "next value";
39 | const callback = Sinon.stub();
40 | const wrapper = mount();
43 |
44 | wrapper.find("input").simulate("change", { target: { value: nextValue } });
45 |
46 | expect(callback.called).toBe(true);
47 | });
48 |
49 | it("render is defined", () => {
50 | const wrapper = mount();
53 |
54 | expect(wrapper.find(Text).getDOMNode()).toBeDefined();
55 | });
56 | });
57 |
--------------------------------------------------------------------------------
/packages/react-forms-dom/__tests__/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es5",
5 | "removeComments": false,
6 | "jsx": "react",
7 | "sourceMap": false,
8 | "skipDefaultLibCheck": true,
9 | "declaration": true,
10 | "declarationDir": "@types",
11 | "pretty": true,
12 | "strict": true,
13 | "noEmit": true,
14 | "noUnusedLocals": true,
15 | "importHelpers": true,
16 | "lib": [
17 | "dom",
18 | "dom.iterable",
19 | "es6"
20 | ],
21 | "typeRoots": [
22 | "../node_modules/@types/"
23 | ]
24 | },
25 | "exclude": [
26 | "node_modules",
27 | "dist",
28 | "@types",
29 | "__tests__"
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/packages/react-forms-dom/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@simplr/react-forms-dom",
3 | "version": "4.1.0",
4 | "description": "DOM components for @simplr/react-forms.",
5 | "repository": "SimplrJS/react-forms",
6 | "homepage": "https://github.com/SimplrJS/react-forms",
7 | "main": "index.js",
8 | "types": "index.d.ts",
9 | "author": "simplrjs (https://github.com/simplrjs)",
10 | "scripts": {
11 | "build": "gulp",
12 | "watch": "webpack -w",
13 | "uglifyjs": "uglifyjs ./dist/react-forms-dom.js -o ./dist/react-forms-dom.min.js --compress --mangle",
14 | "release": "npm run build && npm run uglifyjs",
15 | "test": "jest && npm run test-tsc && npm run test-tslint",
16 | "test-tslint": "tslint --config ./tslint.json --project tsconfig.json && echo TsLint test successfully passed.",
17 | "test-tsc": "tsc -p . --noEmit",
18 | "test-watch": "jest --watchAll",
19 | "test-coverage": "npm test -- --coverage",
20 | "prepublishOnly": "npm run build",
21 | "gulp-build": "tsc -p ./tools/tsconfig.gulp.json",
22 | "gulp-watch": "npm run gulp-build -- -w"
23 | },
24 | "license": "AGPL-3.0",
25 | "files": [
26 | "**/*.md",
27 | "*.js",
28 | "**/*.d.ts",
29 | "!*.config.js",
30 | "!gulpfile.js",
31 | "!node_modules/**"
32 | ],
33 | "devDependencies": {
34 | "@types/chokidar": "^1.7.0",
35 | "@types/enzyme": "^2.8.0",
36 | "@types/gulp": "^4.0.4",
37 | "@types/jest": "^20.0.1",
38 | "@types/sinon": "^2.3.1",
39 | "@types/undertaker": "^1.1.2",
40 | "@types/vinyl-fs": "^2.4.5",
41 | "@types/webpack": "^2.2.16",
42 | "enzyme": "^2.8.2",
43 | "gulp": "github:gulpjs/gulp#4.0",
44 | "jest": "^20.0.4",
45 | "jest-enzyme": "^3.2.0",
46 | "react-test-renderer": "^15.6.1",
47 | "@simplr/mvdir": "0.0.2",
48 | "sinon": "^2.3.5",
49 | "source-map-loader": "^0.2.1",
50 | "ts-jest": "^20.0.6",
51 | "ts-loader": "^2.1.0",
52 | "tslint": "^5.4.3",
53 | "typescript": "^2.3.4",
54 | "uglify-js": "^3.0.18",
55 | "webpack": "^3.0.0"
56 | },
57 | "dependencies": {
58 | "@types/prop-types": "^15.5.1",
59 | "@types/react": "^15.0.29",
60 | "immutable": "^3.8.1",
61 | "prop-types": "^15.5.10",
62 | "react": "^15.6.1",
63 | "react-dom": "^15.6.1",
64 | "@simplr/react-forms": "^4.0.0",
65 | "tslib": "^1.7.1",
66 | "typed-immutable-record": "^0.0.6"
67 | },
68 | "jest": {
69 | "setupTestFrameworkScriptFile": "./node_modules/jest-enzyme/lib/index.js",
70 | "transform": {
71 | ".(ts|tsx)": "/node_modules/ts-jest/preprocessor.js"
72 | },
73 | "testRegex": "/__tests__/.*\\.(test|spec).(ts|tsx|js)$",
74 | "moduleFileExtensions": [
75 | "ts",
76 | "tsx",
77 | "js"
78 | ]
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/packages/react-forms-dom/src/abstractions.ts:
--------------------------------------------------------------------------------
1 | export * from "./abstractions/index";
2 |
--------------------------------------------------------------------------------
/packages/react-forms-dom/src/abstractions/base-dom-field.tsx:
--------------------------------------------------------------------------------
1 | import { BaseField, BaseFieldState } from "@simplr/react-forms";
2 | import {
3 | DomFieldProps,
4 | DomFieldTemplateCallback,
5 | DomComponentData,
6 | DomFieldDetails
7 | } from "../contracts/field";
8 | import { FormProps } from "../contracts/form";
9 |
10 | export type BaseDomFieldState = BaseFieldState;
11 |
12 | export abstract class BaseDomField
13 | extends BaseField {
14 | public Element: TUnderlyingElement | null;
15 |
16 | protected OnFocus = (event: React.FocusEvent): void => {
17 | const props = this.props as DomFieldProps;
18 | if (props.onFocus != null) {
19 | props.onFocus(event);
20 | }
21 | this.Focus();
22 | }
23 |
24 | protected OnBlur = (event: React.FocusEvent): void => {
25 | const props = this.props as DomFieldProps;
26 | if (props.onBlur != null) {
27 | props.onBlur(event);
28 | }
29 |
30 | this.Blur();
31 | }
32 |
33 | protected get FieldTemplate(): DomFieldTemplateCallback | undefined {
34 | const formProps = this.FormStore.GetState().Form.Props as FormProps;
35 | if (this.props.template != null) {
36 | return this.props.template;
37 | }
38 |
39 | if (formProps.template) {
40 | return formProps.template;
41 | }
42 | }
43 |
44 | protected GetHTMLProps(props: DomFieldProps): {} {
45 | const {
46 | defaultValue,
47 | destroyOnUnmount,
48 | disabled,
49 | formatValue,
50 | initialValue,
51 | name,
52 | normalizeValue,
53 | onBlur,
54 | onFocus,
55 | parseValue,
56 | template,
57 | validationType,
58 | value,
59 | children,
60 | ...restProps
61 | } = props;
62 |
63 | return restProps;
64 | }
65 |
66 | protected SetElementRef = (element: TUnderlyingElement | null): void => {
67 | this.Element = element;
68 | }
69 |
70 | public abstract renderField(): JSX.Element | null;
71 |
72 | public render(): JSX.Element | null {
73 | if (this.FieldTemplate == null) {
74 | return this.renderField();
75 | }
76 | return this.FieldTemplate(
77 | this.renderField.bind(this),
78 | {
79 | name: this.Name,
80 | fieldGroupId: this.FieldsGroupId,
81 | id: this.FieldId
82 | } as DomFieldDetails,
83 | this.FormStore,
84 | {
85 | props: this.props,
86 | state: this.FieldState
87 | } as DomComponentData);
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/packages/react-forms-dom/src/abstractions/base-errors-container.ts:
--------------------------------------------------------------------------------
1 | import * as Immutable from "immutable";
2 | import { BaseContainer, BaseContainerParentContext } from "@simplr/react-forms";
3 | import { FormStore } from "@simplr/react-forms/stores";
4 | import { FormError, FieldValidationStatus } from "@simplr/react-forms/contracts";
5 |
6 | export type BaseErrorsContainerProps = {};
7 |
8 | export type FieldErrors = Immutable.Map;
9 |
10 | export interface BaseErrorsContainerState {
11 | FieldErrors: FieldErrors;
12 | FormError?: FormError;
13 | }
14 |
15 | export abstract class BaseErrorsContainer
16 | extends BaseContainer {
17 | public state: TState = {
18 | FieldErrors: Immutable.Map()
19 | } as TState;
20 |
21 | protected OnStoreUpdated(): void {
22 | const storeState = this.FormStore.GetState();
23 |
24 | if (!storeState.HasError) {
25 | this.setState(state => {
26 | state.FieldErrors = Immutable.Map();
27 | state.FormError = undefined;
28 | return state;
29 | });
30 | return;
31 | }
32 | const formError = storeState.Form.Error;
33 | const fieldsWithError = storeState
34 | .FieldsValidationStatuses
35 | .filter(x => x != null && x === FieldValidationStatus.HasError)
36 | .keySeq()
37 | .toArray();
38 |
39 | const fieldsErrors: { [id: string]: FormError } = {};
40 |
41 | fieldsWithError.forEach(fieldId => {
42 | const fieldError = storeState.Fields.get(fieldId).Error;
43 | if (fieldError != null) {
44 | fieldsErrors[fieldId] = fieldError;
45 | }
46 | });
47 |
48 | this.setState(state => {
49 | state.FormError = formError;
50 | state.FieldErrors = Immutable.Map(fieldsErrors);
51 |
52 | return state;
53 | });
54 | }
55 |
56 | public abstract render(): JSX.Element | null;
57 | }
58 |
--------------------------------------------------------------------------------
/packages/react-forms-dom/src/abstractions/base-form-button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { recordify, TypedRecord } from "typed-immutable-record";
3 | import { Iterable } from "immutable";
4 |
5 | import { BaseContainer, BaseContainerProps } from "@simplr/react-forms";
6 |
7 | export interface BaseFormButtonProps extends BaseContainerProps {
8 | disableOnError?: boolean;
9 | disableOnBusy?: boolean;
10 | disableOnPristine?: boolean;
11 | busy?: boolean;
12 | disabled?: boolean;
13 | busyClassName?: string;
14 | style?: React.CSSProperties;
15 | className?: string;
16 | }
17 |
18 | export interface BaseFormButtonState {
19 | HasError: boolean;
20 | Validating: boolean;
21 | Submitting: boolean;
22 | Pristine: boolean;
23 | Disabled: boolean;
24 | }
25 |
26 | export interface BaseFormButtonStateRecord extends TypedRecord, BaseFormButtonState { }
27 |
28 | export abstract class BaseFormButton
29 | extends BaseContainer {
30 | public static defaultProps: BaseFormButtonProps = {
31 | disableOnBusy: true,
32 | disableOnError: false,
33 | disableOnPristine: false,
34 | disabled: undefined,
35 | busyClassName: "busy"
36 | };
37 |
38 | protected OnStoreUpdated(): void {
39 | const formStore = this.FormStore.GetState();
40 | const newState: BaseFormButtonState = {
41 | HasError: formStore.HasError,
42 | Validating: formStore.Validating,
43 | Submitting: formStore.Submitting,
44 | Pristine: formStore.Pristine,
45 | Disabled: formStore.Form.Disabled
46 | };
47 |
48 | const newStateRecord = recordify(newState);
49 |
50 | // Type 'Readonly' cannot be converted to type 'Iterable'.
51 | const stateIterable = this.state as any as Iterable;
52 | if (!newStateRecord.equals(stateIterable)) {
53 | // newStateRecord becomes an empty object after setState
54 | // This happens because of an underlying Immutable.Record
55 | // not enumerating properties in for..in
56 | this.setState(() => newState);
57 | }
58 | }
59 |
60 | protected get Disabled(): boolean {
61 | if (this.state == null) {
62 | return false;
63 | }
64 |
65 | if (this.state.Disabled === true) {
66 | return true;
67 | }
68 |
69 | const props = this.props as TProps;
70 | if (props.disabled != null) {
71 | return props.disabled;
72 | }
73 |
74 | if (props.disableOnError === true &&
75 | this.state.HasError) {
76 | return true;
77 | }
78 |
79 | if (props.disableOnBusy === true &&
80 | this.Busy) {
81 | return true;
82 | }
83 |
84 | if (props.disableOnPristine === true &&
85 | this.state.Pristine === true) {
86 | return true;
87 | }
88 | return false;
89 | }
90 |
91 | protected get Busy(): boolean {
92 | return this.props.busy === true ||
93 | this.state != null &&
94 | (this.state.Validating || this.state.Submitting);
95 | }
96 |
97 | protected get InlineStyles(): React.CSSProperties {
98 | let inlineStyles: React.CSSProperties = {};
99 | const props = this.props as TProps;
100 | if (props.style != null) {
101 | inlineStyles = props.style;
102 | }
103 |
104 | if (this.Busy && !props.disabled) {
105 | inlineStyles.cursor = "wait";
106 | }
107 |
108 | return inlineStyles;
109 | }
110 |
111 | protected get ClassName(): string | undefined {
112 | let className = "";
113 | if (this.props.className != null) {
114 | className += `${this.props.className} `;
115 | }
116 | if (this.Busy) {
117 | className += this.props.busyClassName;
118 | }
119 | return className.length > 0 ? className : undefined;
120 | }
121 |
122 | protected GetHTMLProps(props: BaseFormButtonProps): {} {
123 | const {
124 | disableOnError,
125 | disableOnBusy,
126 | disableOnPristine,
127 | busy,
128 | busyClassName,
129 | formId,
130 | ...restProps
131 | } = props;
132 |
133 | return restProps;
134 | }
135 |
136 | public abstract render(): JSX.Element | null;
137 | }
138 |
--------------------------------------------------------------------------------
/packages/react-forms-dom/src/abstractions/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./base-dom-field";
2 | export * from "./base-errors-container";
3 | export * from "./base-form-button";
4 |
--------------------------------------------------------------------------------
/packages/react-forms-dom/src/components/checkbox.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { FieldValue } from "@simplr/react-forms/contracts";
3 | import { DomFieldProps } from "../contracts/field";
4 |
5 | import { BaseDomField, BaseDomFieldState } from "../abstractions/base-dom-field";
6 | import { FieldOnChangeCallback } from "../contracts/field";
7 | import {
8 | HTMLElementProps
9 | } from "../contracts/field";
10 | import {
11 | FormProps
12 | } from "../contracts/form";
13 |
14 | export type CheckBoxOnChangeCallback = FieldOnChangeCallback;
15 |
16 | /**
17 | * Override the differences between extended interfaces.
18 | */
19 | export interface CheckBoxProps extends DomFieldProps, HTMLElementProps {
20 | name: string;
21 | onFocus?: React.FocusEventHandler;
22 | onBlur?: React.FocusEventHandler;
23 | onChange?: CheckBoxOnChangeCallback;
24 |
25 | defaultValue?: boolean;
26 | initialValue?: boolean;
27 | value?: boolean;
28 | ref?: React.Ref;
29 | }
30 |
31 | export class CheckBox extends BaseDomField {
32 | protected GetValueFromEvent(event: React.ChangeEvent): FieldValue {
33 | return event.currentTarget.checked;
34 | }
35 |
36 | protected OnChangeHandler: React.ChangeEventHandler = event => {
37 | this.OnValueChange(this.GetValueFromEvent(event));
38 |
39 | const newValue = this.FormStore.GetField(this.FieldId).Value;
40 |
41 | if (this.props.onChange != null) {
42 | event.persist();
43 | this.props.onChange(event, newValue, this.FieldId, this.FormStore);
44 | }
45 |
46 | const formStoreState = this.FormStore.GetState();
47 | const formProps = formStoreState.Form.Props as FormProps;
48 | if (formProps.onChange != null) {
49 | event.persist();
50 | formProps.onChange(event, newValue, this.FieldId, this.FormStore);
51 | }
52 | }
53 |
54 | protected get RawDefaultValue(): boolean {
55 | if (this.props.defaultValue != null) {
56 | return this.props.defaultValue;
57 | }
58 | return false;
59 | }
60 |
61 | public renderField(): JSX.Element {
62 | return ;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/packages/react-forms-dom/src/components/clear.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import {
4 | BaseFormButton,
5 | BaseFormButtonProps,
6 | BaseFormButtonStateRecord
7 | } from "../abstractions/base-form-button";
8 | import { HTMLElementProps } from "../contracts/field";
9 |
10 | export interface ClearProps extends BaseFormButtonProps, HTMLElementProps {
11 | fieldIds?: string[];
12 |
13 | ref?: React.Ref;
14 | }
15 |
16 | export class Clear extends BaseFormButton {
17 | protected OnButtonClick: React.MouseEventHandler = (event): void => {
18 | this.FormStore.ClearFields(this.props.fieldIds);
19 |
20 | if (this.props.onClick != null) {
21 | event.persist();
22 | this.props.onClick(event);
23 | }
24 | }
25 |
26 | protected GetHTMLProps(props: ClearProps): {} {
27 | const filteredProps = super.GetHTMLProps(props) as ClearProps;
28 | const {
29 | fieldIds,
30 | ...restProps
31 | } = filteredProps;
32 |
33 | return restProps;
34 | }
35 |
36 | public render(): JSX.Element {
37 | return
45 | {this.props.children}
46 | ;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/packages/react-forms-dom/src/components/email.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { FieldValue } from "@simplr/react-forms/contracts";
3 | import { DomFieldProps } from "../contracts/field";
4 |
5 | import { BaseDomField, BaseDomFieldState } from "../abstractions/base-dom-field";
6 | import { FieldOnChangeCallback } from "../contracts/field";
7 | import {
8 | HTMLElementProps
9 | } from "../contracts/field";
10 | import {
11 | FormProps
12 | } from "../contracts/form";
13 |
14 | export type EmailOnChangeCallback = FieldOnChangeCallback;
15 |
16 | /**
17 | * Override the differences between extended interfaces.
18 | */
19 | export interface EmailProps extends DomFieldProps, HTMLElementProps {
20 | name: string;
21 | onFocus?: React.FocusEventHandler;
22 | onBlur?: React.FocusEventHandler;
23 | onChange?: EmailOnChangeCallback;
24 |
25 | defaultValue?: FieldValue;
26 | value?: FieldValue;
27 | ref?: React.Ref;
28 | }
29 |
30 | export class Email extends BaseDomField {
31 | protected GetValueFromEvent(event: React.ChangeEvent): FieldValue {
32 | return event.currentTarget.value;
33 | }
34 |
35 | protected OnChangeHandler: React.ChangeEventHandler = event => {
36 | this.OnValueChange(this.GetValueFromEvent(event));
37 |
38 | const newValue = this.FormStore.GetField(this.FieldId).Value;
39 |
40 | if (this.props.onChange != null) {
41 | event.persist();
42 | this.props.onChange(event, newValue, this.FieldId, this.FormStore);
43 | }
44 |
45 | const formStoreState = this.FormStore.GetState();
46 | const formProps = formStoreState.Form.Props as FormProps;
47 | if (formProps.onChange != null) {
48 | event.persist();
49 | formProps.onChange(event, newValue, this.FieldId, this.FormStore);
50 | }
51 | }
52 |
53 | protected get RawDefaultValue(): string {
54 | if (this.props.defaultValue != null) {
55 | return this.props.defaultValue;
56 | }
57 | return "";
58 | }
59 |
60 | public renderField(): JSX.Element {
61 | return ;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/packages/react-forms-dom/src/components/errors-container.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { FormStore } from "@simplr/react-forms/stores";
3 | import { FormError, FieldValidationStatus } from "@simplr/react-forms/contracts";
4 |
5 | import {
6 | BaseErrorsContainer,
7 | BaseErrorsContainerState,
8 | FieldErrors
9 | } from "../abstractions/base-errors-container";
10 |
11 | export type ErrorsTemplate = (fieldErrors: FieldErrors, formError: FormError | undefined, formStore: FormStore) => JSX.Element | null;
12 |
13 | export interface ErrorsContainerProps {
14 | template: ErrorsTemplate;
15 | }
16 |
17 | export type ErrorsContainerState = BaseErrorsContainerState;
18 |
19 | export class ErrorsContainer extends BaseErrorsContainer {
20 | public render(): JSX.Element | null {
21 | return this.props.template(this.state.FieldErrors, this.state.FormError, this.FormStore);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/react-forms-dom/src/components/fields-array.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import {
3 | FieldsArrayProps as CoreProps,
4 | FieldsArrayState as CoreState
5 | } from "@simplr/react-forms/contracts";
6 | import { BaseFieldsArray } from "@simplr/react-forms";
7 | import { HTMLElementProps } from "../contracts/field";
8 |
9 | export interface FieldsArrayProps extends CoreProps, HTMLElementProps {
10 | name: string;
11 |
12 | ref?: React.Ref;
13 | }
14 |
15 | export type FieldsArrayState = CoreState;
16 |
17 | interface Dictionary {
18 | [key: string]: any;
19 | }
20 |
21 | export class FieldsArray extends BaseFieldsArray {
22 | public Element: HTMLDivElement | null;
23 |
24 | protected SetElementRef = (element: HTMLDivElement | null): void => {
25 | this.Element = element;
26 | }
27 |
28 | protected GetHTMLProps(props: FieldsArrayProps): {} {
29 | const {
30 | name,
31 | index,
32 | destroyOnUnmount,
33 | children,
34 | ...restProps } = this.props;
35 | return restProps;
36 | }
37 |
38 | public render(): JSX.Element {
39 | return
43 | {this.props.children}
44 |
;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/packages/react-forms-dom/src/components/fields-group.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import {
3 | FieldsGroupProps as CoreProps,
4 | FieldsGroupState as CoreState
5 | } from "@simplr/react-forms/contracts";
6 | import { BaseFieldsGroup } from "@simplr/react-forms";
7 | import { HTMLElementProps } from "../contracts/field";
8 |
9 | export interface FieldsGroupProps extends CoreProps, HTMLElementProps {
10 | name: string;
11 |
12 | ref?: React.Ref;
13 | }
14 |
15 | export type FieldsGroupState = CoreState;
16 |
17 | interface Dictionary {
18 | [key: string]: any;
19 | }
20 |
21 | export class FieldsGroup extends BaseFieldsGroup {
22 | public Element: HTMLDivElement | null;
23 |
24 | protected SetElementRef = (element: HTMLDivElement | null): void => {
25 | this.Element = element;
26 | }
27 |
28 | protected GetHTMLProps(props: FieldsGroupProps): {} {
29 | const {
30 | name,
31 | destroyOnUnmount,
32 | children,
33 | ...restProps } = this.props;
34 | return restProps;
35 | }
36 |
37 | public render(): JSX.Element {
38 | return
42 | {this.props.children}
43 |
;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/packages/react-forms-dom/src/components/form.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { BaseForm } from "@simplr/react-forms";
3 |
4 | import { FormProps } from "../contracts/form";
5 |
6 | export class Form extends BaseForm {
7 | public Element: HTMLFormElement | null;
8 |
9 | protected SetElementRef = (element: HTMLFormElement | null) => {
10 | this.Element = element;
11 | if (this.FormStore != null && element != null) {
12 | this.FormStore.SetFormSubmitCallback(() => {
13 | element.dispatchEvent(new Event("submit"));
14 | });
15 | }
16 | }
17 |
18 | public static defaultProps: FormProps = {
19 | ...BaseForm.defaultProps,
20 | preventSubmitDefaultAndPropagation: true
21 | };
22 |
23 | protected FormSubmitHandler = (event: React.FormEvent): void => {
24 | if (this.props.preventSubmitDefaultAndPropagation) {
25 | event.preventDefault();
26 | event.stopPropagation();
27 | }
28 | if (!this.ShouldFormSubmit()) {
29 | return;
30 | }
31 |
32 | this.FormStore.TouchFields();
33 |
34 | if (this.props.onSubmit == null) {
35 | return;
36 | }
37 |
38 | // Persist synthetic event, because it's passed into another method.
39 | event.persist();
40 |
41 | // Pass onSubmit result to FormStore for further processing.
42 | const result = this.props.onSubmit(event, this.FormStore);
43 | this.FormStore.SubmitForm(result);
44 | }
45 |
46 | public render(): JSX.Element {
47 | return ;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/packages/react-forms-dom/src/components/hidden.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { BaseField, BaseFieldState } from "@simplr/react-forms";
3 | import { FieldProps, FieldValue } from "@simplr/react-forms/contracts";
4 |
5 | export interface HiddenProps extends FieldProps {
6 | defaultValue: FieldValue;
7 | value: FieldValue;
8 | }
9 |
10 | export class Hidden extends BaseField {
11 | protected get IsControlled(): boolean {
12 | return true;
13 | }
14 |
15 | protected get RawDefaultValue(): FieldValue {
16 | return this.props.defaultValue;
17 | }
18 |
19 | public render(): null {
20 | return null;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/react-forms-dom/src/components/number.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { FieldValue } from "@simplr/react-forms/contracts";
3 | import { DomFieldProps } from "../contracts/field";
4 |
5 | import { BaseDomField, BaseDomFieldState } from "../abstractions/base-dom-field";
6 | import { FieldOnChangeCallback } from "../contracts/field";
7 | import {
8 | HTMLElementProps
9 | } from "../contracts/field";
10 | import {
11 | FormProps
12 | } from "../contracts/form";
13 | import { StringToDecimalModifier } from "@simplr/react-forms/modifiers";
14 |
15 | export type NumberOnChangeCallback = FieldOnChangeCallback;
16 |
17 | /**
18 | * Override the differences between extended interfaces.
19 | */
20 | export interface NumberProps extends DomFieldProps, HTMLElementProps {
21 | name: string;
22 | onFocus?: React.EventHandler>;
23 | onBlur?: React.EventHandler>;
24 | onChange?: NumberOnChangeCallback;
25 |
26 | defaultValue?: FieldValue;
27 | value?: FieldValue;
28 | ref?: React.Ref;
29 | }
30 |
31 | export class Number extends BaseDomField {
32 | protected get DefaultModifiers(): JSX.Element[] {
33 | return [ ];
34 | }
35 |
36 | protected GetValueFromEvent(event: React.ChangeEvent): FieldValue {
37 | return event.currentTarget.value;
38 | }
39 |
40 | protected OnChangeHandler: React.ChangeEventHandler = event => {
41 | this.OnValueChange(this.GetValueFromEvent(event));
42 |
43 | const newValue = this.FormStore.GetField(this.FieldId).Value;
44 |
45 | if (this.props.onChange != null) {
46 | event.persist();
47 | this.props.onChange(event, newValue, this.FieldId, this.FormStore);
48 | }
49 |
50 | const formStoreState = this.FormStore.GetState();
51 | const formProps = formStoreState.Form.Props as FormProps;
52 | if (formProps.onChange != null) {
53 | event.persist();
54 | formProps.onChange(event, newValue, this.FieldId, this.FormStore);
55 | }
56 | }
57 |
58 | protected get RawDefaultValue(): string {
59 | if (this.props.defaultValue != null) {
60 | return this.props.defaultValue;
61 | }
62 | return "";
63 | }
64 |
65 | public renderField(): JSX.Element | null {
66 | return ;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/packages/react-forms-dom/src/components/password.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { FieldValue } from "@simplr/react-forms/contracts";
3 | import { DomFieldProps } from "../contracts/field";
4 |
5 | import { BaseDomField, BaseDomFieldState } from "../abstractions/base-dom-field";
6 | import { FieldOnChangeCallback } from "../contracts/field";
7 | import {
8 | HTMLElementProps
9 | } from "../contracts/field";
10 | import {
11 | FormProps
12 | } from "../contracts/form";
13 |
14 | export type PasswordOnChangeCallback = FieldOnChangeCallback;
15 |
16 | /**
17 | * Override the differences between extended interfaces.
18 | */
19 | export interface PasswordProps extends DomFieldProps, HTMLElementProps {
20 | name: string;
21 | onFocus?: React.FocusEventHandler;
22 | onBlur?: React.FocusEventHandler;
23 | onChange?: PasswordOnChangeCallback;
24 |
25 | defaultValue?: FieldValue;
26 | value?: FieldValue;
27 | ref?: React.Ref;
28 | }
29 |
30 | export class Password extends BaseDomField {
31 | protected GetValueFromEvent(event: React.ChangeEvent): FieldValue {
32 | return event.currentTarget.value;
33 | }
34 |
35 | protected OnChangeHandler: React.ChangeEventHandler = event => {
36 | this.OnValueChange(this.GetValueFromEvent(event));
37 |
38 | const newValue = this.FormStore.GetField(this.FieldId).Value;
39 |
40 | if (this.props.onChange != null) {
41 | event.persist();
42 | this.props.onChange(event, newValue, this.FieldId, this.FormStore);
43 | }
44 |
45 | const formStoreState = this.FormStore.GetState();
46 | const formProps = formStoreState.Form.Props as FormProps;
47 | if (formProps.onChange != null) {
48 | event.persist();
49 | formProps.onChange(event, newValue, this.FieldId, this.FormStore);
50 | }
51 | }
52 |
53 | protected get RawDefaultValue(): string {
54 | if (this.props.defaultValue != null) {
55 | return this.props.defaultValue;
56 | }
57 | return "";
58 | }
59 |
60 | public renderField(): JSX.Element {
61 | return ;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/packages/react-forms-dom/src/components/radio-group.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import * as PropTypes from "prop-types";
3 | import { BaseField, BaseFieldState } from "@simplr/react-forms";
4 | import { FieldProps, FieldChildContext, FieldValue } from "@simplr/react-forms/contracts";
5 | import { HTMLElementProps, DomFieldTemplateCallback } from "../contracts/field";
6 | import { FormProps } from "../contracts/form";
7 | import { BaseDomField } from "../abstractions";
8 | import { TypedRecord } from "typed-immutable-record";
9 |
10 | export type RadioValue = string | number;
11 |
12 | export interface RadioGroupProps extends FieldProps, HTMLElementProps {
13 | name: string;
14 | radioTemplate?: DomFieldTemplateCallback;
15 |
16 | defaultValue?: RadioValue;
17 | initialValue?: RadioValue;
18 | value?: RadioValue;
19 | }
20 |
21 | export interface RadioGroupChildContext extends FieldChildContext {
22 | RadioGroupOnChangeHandler: RadioOnChangeHandler;
23 | RadioGroupOnFocus: React.FocusEventHandler;
24 | RadioGroupOnBlur: React.FocusEventHandler;
25 | }
26 |
27 | export type RadioOnChangeHandler = (event: React.ChangeEvent, value: string) => void;
28 |
29 | export class RadioGroup extends BaseDomField {
30 | public static childContextTypes: PropTypes.ValidationMap = {
31 | ...BaseDomField.childContextTypes,
32 | RadioGroupOnChangeHandler: PropTypes.func.isRequired,
33 | RadioGroupOnBlur: PropTypes.func.isRequired,
34 | RadioGroupOnFocus: PropTypes.func.isRequired
35 | };
36 |
37 | public getChildContext(): RadioGroupChildContext {
38 | return {
39 | ...super.getChildContext(),
40 | FieldId: this.FieldId,
41 | RadioGroupOnChangeHandler: this.OnChangeHandler,
42 | RadioGroupOnBlur: this.OnBlur,
43 | RadioGroupOnFocus: this.OnFocus
44 | };
45 | }
46 |
47 | protected get RawDefaultValue(): React.ReactText {
48 | if (this.props.defaultValue != null) {
49 | return this.props.defaultValue;
50 | }
51 |
52 | return "";
53 | }
54 |
55 | protected OnChangeHandler: RadioOnChangeHandler = (event, value) => {
56 | this.OnValueChange(value);
57 |
58 | const newValue = this.FormStore.GetField(this.FieldId).Value;
59 |
60 | if (this.props.onChange != null) {
61 | event.persist();
62 | this.props.onChange(event, newValue, this.FieldId, this.FormId);
63 | }
64 |
65 | const formStoreState = this.FormStore.GetState();
66 | const formProps = formStoreState.Form.Props as FormProps;
67 | if (formProps.onChange != null) {
68 | event.persist();
69 | formProps.onChange(event, newValue, this.FieldId, this.FormStore);
70 | }
71 | }
72 |
73 | protected GetHTMLProps(props: RadioGroupProps): {} {
74 | const cleanedProps = super.GetHTMLProps(props) as RadioGroupProps;
75 | const { radioTemplate, ...restProps } = cleanedProps;
76 |
77 | return restProps;
78 | }
79 |
80 | public renderField(): JSX.Element {
81 | return
85 | {this.props.children}
86 |
;
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/packages/react-forms-dom/src/components/reset.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import {
4 | BaseFormButton,
5 | BaseFormButtonProps,
6 | BaseFormButtonStateRecord
7 | } from "../abstractions/base-form-button";
8 | import { HTMLElementProps } from "../contracts/field";
9 |
10 | export interface ResetProps extends BaseFormButtonProps, HTMLElementProps {
11 | fieldIds?: string[];
12 |
13 | ref?: React.Ref;
14 | }
15 |
16 | export class Reset extends BaseFormButton {
17 | protected OnButtonClick: React.MouseEventHandler = (event): void => {
18 | this.FormStore.ResetFields(this.props.fieldIds);
19 |
20 | if (this.props.onClick != null) {
21 | event.persist();
22 | this.props.onClick(event);
23 | }
24 | }
25 |
26 | protected GetHTMLProps(props: ResetProps): {} {
27 | const filteredProps = super.GetHTMLProps(props) as ResetProps;
28 | const {
29 | fieldIds,
30 | ...restProps
31 | } = filteredProps;
32 |
33 | return restProps;
34 | }
35 |
36 | public render(): JSX.Element {
37 | return
45 | {this.props.children}
46 | ;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/packages/react-forms-dom/src/components/search.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { FieldValue } from "@simplr/react-forms/contracts";
3 | import { DomFieldProps } from "../contracts/field";
4 |
5 | import { BaseDomField, BaseDomFieldState } from "../abstractions/base-dom-field";
6 | import { FieldOnChangeCallback } from "../contracts/field";
7 | import {
8 | HTMLElementProps
9 | } from "../contracts/field";
10 | import {
11 | FormProps
12 | } from "../contracts/form";
13 |
14 | export type SearchOnChangeCallback = FieldOnChangeCallback;
15 |
16 | /**
17 | * Override the differences between extended interfaces.
18 | */
19 | export interface SearchProps extends DomFieldProps, HTMLElementProps {
20 | name: string;
21 | onFocus?: React.FocusEventHandler;
22 | onBlur?: React.FocusEventHandler;
23 | onChange?: SearchOnChangeCallback;
24 |
25 | defaultValue?: string;
26 | initialValue?: string;
27 | value?: string;
28 | ref?: React.Ref;
29 | }
30 |
31 | export class Search extends BaseDomField {
32 | protected GetValueFromEvent(event: React.ChangeEvent): string {
33 | return event.currentTarget.value;
34 | }
35 |
36 | protected OnChangeHandler: React.ChangeEventHandler = event => {
37 | this.OnValueChange(this.GetValueFromEvent(event));
38 |
39 | const newValue = this.FormStore.GetField(this.FieldId).Value;
40 |
41 | if (this.props.onChange != null) {
42 | event.persist();
43 | this.props.onChange(event, newValue, this.FieldId, this.FormStore);
44 | }
45 |
46 | const formStoreState = this.FormStore.GetState();
47 | const formProps = formStoreState.Form.Props as FormProps;
48 | if (formProps.onChange != null) {
49 | event.persist();
50 | formProps.onChange(event, newValue, this.FieldId, this.FormStore);
51 | }
52 | }
53 |
54 | protected get RawDefaultValue(): string {
55 | if (this.props.defaultValue != null) {
56 | return this.props.defaultValue;
57 | }
58 | return "";
59 | }
60 |
61 | public renderField(): JSX.Element {
62 | return ;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/packages/react-forms-dom/src/components/select.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { FieldValue } from "@simplr/react-forms/contracts";
3 | import { DomFieldProps } from "../contracts/field";
4 |
5 | import { BaseDomField, BaseDomFieldState } from "../abstractions/base-dom-field";
6 | import { FieldOnChangeCallback } from "../contracts/field";
7 | import {
8 | HTMLElementProps
9 | } from "../contracts/field";
10 | import {
11 | FormProps
12 | } from "../contracts/form";
13 |
14 | export type SelectValue = string | string[];
15 | export type SelectOnChangeCallback = FieldOnChangeCallback;
16 |
17 | /**
18 | * Override the differences between extended interfaces.
19 | */
20 | export interface SelectProps extends DomFieldProps, HTMLElementProps {
21 | name: string;
22 | onFocus?: React.FocusEventHandler;
23 | onBlur?: React.FocusEventHandler;
24 | onChange?: SelectOnChangeCallback;
25 |
26 | defaultValue?: SelectValue;
27 | initialValue?: SelectValue;
28 | value?: SelectValue;
29 | ref?: React.Ref;
30 | }
31 |
32 | export interface SelectState extends BaseDomFieldState {
33 | RenderValue: SelectValue;
34 | }
35 |
36 | export class Select extends BaseDomField {
37 | protected get RawInitialValue(): SelectValue | undefined {
38 | if (this.props.multiple ||
39 | this.props.initialValue != null) {
40 | return this.props.initialValue;
41 | }
42 | // If select does not have multiple options, then we need to get the first option value.
43 | const options = React
44 | .Children
45 | .toArray(this.props.children)
46 | .filter((x: JSX.Element) => x.type != null && x.type === "option");
47 |
48 | if (options.length === 0) {
49 | throw new Error("@simplr/react-forms-dom: Select MUST have at least one option!");
50 | }
51 |
52 | const firstOption = options[0] as JSX.Element;
53 | if (firstOption.props.value != null) {
54 | return firstOption.props.value;
55 | }
56 |
57 | return firstOption.props.children;
58 | }
59 |
60 | protected GetValueFromEvent(event: React.ChangeEvent): SelectValue {
61 | if (this.props.multiple) {
62 | const newValue: string[] = [];
63 |
64 | for (let i = 0; i < event.currentTarget.options.length; i++) {
65 | const option = event.currentTarget.options[i];
66 | if (option.selected) {
67 | newValue.push(option.value);
68 | }
69 | }
70 |
71 | return newValue;
72 | }
73 |
74 | return event.currentTarget.value;
75 | }
76 |
77 | protected OnChangeHandler: React.ChangeEventHandler = event => {
78 | this.OnValueChange(this.GetValueFromEvent(event));
79 | let newValue = this.FormStore.GetField(this.FieldId).Value;
80 |
81 | // Check if it's immutable.
82 | // TODO: When using Immutable v4 use isImmutable instead.
83 | if (newValue.toArray != null) {
84 | newValue = newValue.toArray();
85 | }
86 |
87 | this.setState(state => {
88 | state.RenderValue = newValue;
89 | return state;
90 | });
91 |
92 | if (this.props.onChange != null) {
93 | event.persist();
94 | this.props.onChange(event, newValue, this.FieldId, this.FormStore);
95 | }
96 |
97 | const formStoreState = this.FormStore.GetState();
98 | const formProps = formStoreState.Form.Props as FormProps;
99 | if (formProps.onChange != null) {
100 | event.persist();
101 | formProps.onChange(event, newValue, this.FieldId, this.FormStore);
102 | }
103 | }
104 |
105 | protected get RawDefaultValue(): SelectValue {
106 | if (this.props.defaultValue != null) {
107 | return this.props.defaultValue;
108 | }
109 | return (this.props.multiple) ? [] : "";
110 | }
111 |
112 | protected get Value(): SelectValue {
113 | if (this.state != null && this.state.RenderValue != null) {
114 | return this.state.RenderValue;
115 | }
116 |
117 | return this.RawDefaultValue;
118 | }
119 |
120 | public renderField(): JSX.Element {
121 | return
131 | {this.props.children}
132 | ;
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/packages/react-forms-dom/src/components/submit.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { recordify, TypedRecord } from "typed-immutable-record";
3 |
4 | import { BaseContainer, BaseContainerProps } from "@simplr/react-forms";
5 | import { FormError } from "@simplr/react-forms/contracts";
6 |
7 | import {
8 | BaseFormButton,
9 | BaseFormButtonProps,
10 | BaseFormButtonStateRecord
11 | } from "../abstractions/base-form-button";
12 |
13 | export type SubmitProps = BaseFormButtonProps;
14 |
15 | export class Submit extends BaseFormButton {
16 | public static defaultProps: BaseFormButtonProps = {
17 | ...BaseFormButton.defaultProps,
18 | disableOnError: true
19 | };
20 |
21 | public render(): JSX.Element {
22 | return
28 | {this.props.children}
29 | ;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/packages/react-forms-dom/src/components/text.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { DomFieldProps } from "../contracts/field";
3 |
4 | import {
5 | BaseDomField,
6 | BaseDomFieldState
7 | } from "../abstractions/base-dom-field";
8 | import {
9 | FieldOnChangeCallback,
10 | HTMLElementProps
11 | } from "../contracts/field";
12 | import {
13 | FormProps
14 | } from "../contracts/form";
15 |
16 | export type TextOnChangeCallback = FieldOnChangeCallback;
17 |
18 | /**
19 | * Override the differences between extended interfaces.
20 | */
21 | export interface TextProps extends DomFieldProps, HTMLElementProps {
22 | name: string;
23 | onFocus?: React.EventHandler>;
24 | onBlur?: React.EventHandler>;
25 | onChange?: TextOnChangeCallback;
26 |
27 | defaultValue?: string;
28 | initialValue?: string;
29 | value?: string;
30 | ref?: React.Ref;
31 | }
32 |
33 | export class Text extends BaseDomField {
34 | protected GetValueFromEvent(event: React.ChangeEvent): string {
35 | return event.target.value;
36 | }
37 |
38 | protected OnChangeHandler: React.ChangeEventHandler = event => {
39 | let newValue: string | undefined;
40 | if (!this.IsControlled) {
41 | this.OnValueChange(this.GetValueFromEvent(event));
42 | newValue = this.FormStore.GetField(this.FieldId).Value;
43 | } else {
44 | newValue = this.GetValueFromEvent(event);
45 | }
46 |
47 | if (this.props.onChange != null) {
48 | event.persist();
49 | this.props.onChange(event, newValue, this.FieldId, this.FormStore);
50 | }
51 |
52 | const formStoreState = this.FormStore.GetState();
53 | const formProps = formStoreState.Form.Props as FormProps;
54 | if (formProps.onChange != null) {
55 | event.persist();
56 | formProps.onChange(event, newValue, this.FieldId, this.FormStore);
57 | }
58 | }
59 |
60 | protected get RawDefaultValue(): string {
61 | if (this.props.defaultValue != null) {
62 | return this.props.defaultValue;
63 | }
64 |
65 | return "";
66 | }
67 |
68 | public renderField(): JSX.Element {
69 | return ;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/packages/react-forms-dom/src/components/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { FieldValue } from "@simplr/react-forms/contracts";
3 | import { DomFieldProps } from "../contracts/field";
4 |
5 | import {
6 | BaseDomField,
7 | BaseDomFieldState
8 | } from "../abstractions/base-dom-field";
9 | import {
10 | FieldOnChangeCallback,
11 | HTMLElementProps
12 | } from "../contracts/field";
13 | import {
14 | FormProps
15 | } from "../contracts/form";
16 |
17 | export type TextAreaOnChangeCallback = FieldOnChangeCallback;
18 |
19 | /**
20 | * Override the differences between extended interfaces.
21 | */
22 | export interface TextAreaProps extends DomFieldProps, HTMLElementProps {
23 | name: string;
24 | onFocus?: React.EventHandler>;
25 | onBlur?: React.EventHandler>;
26 | onChange?: TextAreaOnChangeCallback;
27 |
28 | defaultValue?: string;
29 | initialValue?: string;
30 | value?: string;
31 | ref?: React.Ref