Skip to content

Commit 2ba1e8c

Browse files
FormStore helpers extracted and fixed. Source maps incorporated into webpack. (#58)
* Controlled mechanism implemented and onChange types for Field and Form updated. * FormStore helpers extracted and fixed. Source maps incorporated into webpack. FormStore helpers moved into a separate file and arrow function definition checking fixed in PropsEqual method. SourceMaps integrated into webpack pipeline.
1 parent fdbbf06 commit 2ba1e8c

File tree

11 files changed

+275
-224
lines changed

11 files changed

+275
-224
lines changed

packages/simplr-forms-dom/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"mz": "^2.6.0",
4848
"simplr-mvdir": "0.0.1-beta.7",
4949
"sinon": "^2.2.0",
50+
"source-map-loader": "^0.2.1",
5051
"ts-jest": "^20.0.4",
5152
"ts-loader": "^2.1.0",
5253
"tslint": "^5.2.0",

packages/simplr-forms-dom/tools/webpack.config.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,16 @@ module.exports = {
6262
},
6363
module: {
6464
rules: [
65+
{
66+
enforce: "pre",
67+
test: /\.js$/,
68+
loader: "source-map-loader"
69+
},
70+
{
71+
enforce: "pre",
72+
test: /\.tsx?$/,
73+
use: "source-map-loader"
74+
},
6575
{
6676
test: /\.tsx?$/,
6777
loader: "ts-loader",
@@ -73,5 +83,6 @@ module.exports = {
7383
resolve: {
7484
extensions: [".ts", ".tsx"]
7585
},
76-
externals: externalsResolver
86+
externals: externalsResolver,
87+
devtool: "inline-source-map"
7788
};

packages/simplr-forms-dom/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"outDir": "dist",
77
"rootDir": "src",
88
"jsx": "react",
9-
"sourceMap": false,
9+
"sourceMap": true,
1010
"skipDefaultLibCheck": true,
1111
"declaration": true,
1212
"pretty": true,

packages/simplr-forms-dom/webpack.config.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,16 @@ module.exports = {
5252
},
5353
module: {
5454
rules: [
55+
{
56+
enforce: "pre",
57+
test: /\.js$/,
58+
loader: "source-map-loader"
59+
},
60+
{
61+
enforce: "pre",
62+
test: /\.tsx?$/,
63+
use: "source-map-loader"
64+
},
5565
{
5666
test: /\.tsx?$/,
5767
loader: "ts-loader",
@@ -62,5 +72,6 @@ module.exports = {
6272
resolve: {
6373
extensions: [".ts", ".tsx"]
6474
},
65-
externals: externalsResolver
75+
externals: externalsResolver,
76+
devtool: "inline-source-map"
6677
};

packages/simplr-forms/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"react-test-renderer": "^15.5.4",
4646
"simplr-mvdir": "0.0.1-beta.7",
4747
"sinon": "^2.2.0",
48+
"source-map-loader": "^0.2.1",
4849
"ts-jest": "^20.0.4",
4950
"ts-loader": "^2.1.0",
5051
"tslint": "^5.2.0",
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
import * as React from "react";
2+
import { FieldStorePropsRecord } from "../contracts/field";
3+
4+
export class FormStoreHelpers {
5+
protected static ArrayUnique<T>(array: T[], concat: boolean = true): T[] {
6+
let result = concat ? array.concat() : array;
7+
for (var i = 0; i < result.length; ++i) {
8+
for (var j = i + 1; j < result.length; ++j) {
9+
if (result[i] === result[j]) {
10+
result.splice(j--, 1);
11+
}
12+
}
13+
}
14+
return result;
15+
}
16+
17+
protected static RemoveValues<T>(array: T[], valuesToRemove: T[], concat: boolean = true): T[] {
18+
let result = concat ? array.concat() : array;
19+
for (const value of valuesToRemove) {
20+
let index;
21+
while ((index = result.indexOf(value)) !== -1) {
22+
result.splice(index, 1);
23+
}
24+
}
25+
return result;
26+
}
27+
28+
public static PropsEqual(newProps: FieldStorePropsRecord, oldProps: FieldStorePropsRecord): boolean {
29+
const newKeys = newProps.keySeq().toArray();
30+
const oldKeys = oldProps.keySeq().toArray();
31+
32+
if (newKeys.length !== oldKeys.length) {
33+
return false;
34+
}
35+
const childrenKey = "children";
36+
let allKeys = this.ArrayUnique(newKeys.concat(oldKeys), false);
37+
allKeys = this.RemoveValues(allKeys, [childrenKey], false);
38+
39+
// Custom props diff, to have most efficient diffing
40+
41+
// First, check top level properties
42+
for (const key of allKeys) {
43+
const newValue = newProps.get(key);
44+
const oldValue = oldProps.get(key);
45+
46+
const newValueType = typeof newValue;
47+
const oldValueType = typeof oldValue;
48+
49+
if (newValueType !== oldValueType) {
50+
return false;
51+
}
52+
53+
if (newValueType === "object" || newValueType === "function") {
54+
if (!this.DeepCompare(newValue, oldValue)) {
55+
return false;
56+
}
57+
} else if (newValue !== oldValue) {
58+
return false;
59+
}
60+
}
61+
62+
const newChildrenValue = newProps.get(childrenKey) as React.ReactNode | undefined;
63+
const oldChildrenValue = oldProps.get(childrenKey) as React.ReactNode | undefined;
64+
65+
const newChildren = React.Children.toArray(newChildrenValue);
66+
const oldChildren = React.Children.toArray(oldChildrenValue);
67+
68+
if (newChildren.length !== oldChildren.length) {
69+
return false;
70+
}
71+
72+
// For each newChildren
73+
for (const child of newChildren) {
74+
// If a child is a text component and no old child is equal to it
75+
if (typeof child === "string" && !oldChildren.some(x => x === child)) {
76+
// Props have changed
77+
return false;
78+
}
79+
80+
const newChildElement = child as React.ReactElement<any>;
81+
82+
// Try to find best a match for an old child in the newChildren array
83+
const oldChildElement = oldChildren.find(oldChild => {
84+
// String case has been checked before
85+
if (typeof oldChild !== "string") {
86+
const element = oldChild as React.ReactElement<any>;
87+
// If type and key properties match
88+
// Children should be the same
89+
if (element.type === newChildElement.type &&
90+
element.key === newChildElement.key) {
91+
return true;
92+
}
93+
}
94+
// Return false explicitly by default
95+
return false;
96+
}) as React.ReactElement<any> | undefined;
97+
98+
// If oldChildElement was found and its props are different
99+
if (oldChildElement != null &&
100+
!this.DeepCompare(newChildElement.props, oldChildElement.props)) {
101+
// Props are not the same
102+
return false;
103+
}
104+
}
105+
// Props are equal
106+
return true;
107+
}
108+
109+
protected static DeepCompare(...args: any[]): boolean {
110+
var i, l, leftChain: any, rightChain: any;
111+
function Compare2Objects(x: any, y: any) {
112+
var p;
113+
114+
// remember that NaN === NaN returns false
115+
// and isNaN(undefined) returns true
116+
if (isNaN(x) && isNaN(y) && typeof x === "number" && typeof y === "number") {
117+
return true;
118+
}
119+
120+
// Compare primitives and functions.
121+
// Check if both arguments link to the same object.
122+
// Especially useful on the step where we compare prototypes
123+
if (x === y) {
124+
return true;
125+
}
126+
127+
// Works in case when functions are created in constructor.
128+
// Comparing dates is a common scenario. Another built-ins?
129+
// We can even handle functions passed across iframes
130+
if ((typeof x === "function" && typeof y === "function") ||
131+
(x instanceof Date && y instanceof Date) ||
132+
(x instanceof RegExp && y instanceof RegExp) ||
133+
(x instanceof String && y instanceof String) ||
134+
(x instanceof Number && y instanceof Number)) {
135+
return x.toString() === y.toString();
136+
}
137+
138+
// At last checking prototypes as good as we can
139+
if (!(x instanceof Object && y instanceof Object)) {
140+
return false;
141+
}
142+
143+
if (x.isPrototypeOf(y) || y.isPrototypeOf(x)) {
144+
return false;
145+
}
146+
147+
if (x.constructor !== y.constructor) {
148+
return false;
149+
}
150+
151+
if (x.prototype !== y.prototype) {
152+
return false;
153+
}
154+
155+
// Check for infinitive linking loops
156+
if (leftChain.indexOf(x) > -1 || rightChain.indexOf(y) > -1) {
157+
return false;
158+
}
159+
160+
// Quick checking of one object being a subset of another.
161+
// todo: cache the structure of arguments[0] for performance
162+
for (p in y) {
163+
if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
164+
return false;
165+
} else if (typeof y[p] !== typeof x[p]) {
166+
return false;
167+
}
168+
}
169+
170+
// tslint:disable-next-line:forin
171+
for (p in x) {
172+
if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
173+
return false;
174+
} else if (typeof y[p] !== typeof x[p]) {
175+
return false;
176+
}
177+
178+
switch (typeof (x[p])) {
179+
case "object":
180+
case "function":
181+
leftChain.push(x);
182+
rightChain.push(y);
183+
184+
if (!Compare2Objects(x[p], y[p])) {
185+
return false;
186+
}
187+
188+
leftChain.pop();
189+
rightChain.pop();
190+
break;
191+
192+
default:
193+
if (x[p] !== y[p]) {
194+
return false;
195+
}
196+
break;
197+
}
198+
}
199+
return true;
200+
}
201+
202+
if (args.length < 1) {
203+
//Die silently? Don't know how to handle such case, please help...
204+
return true;
205+
// throw "Need two or more arguments to compare";
206+
}
207+
208+
for (i = 1, l = args.length; i < l; i++) {
209+
//Todo: this can be cached
210+
leftChain = [];
211+
rightChain = [];
212+
213+
if (!Compare2Objects(args[0], args[i])) {
214+
return false;
215+
}
216+
}
217+
return true;
218+
}
219+
}

0 commit comments

Comments
 (0)