From b57b19f60c64aabb37cbbad70a9e9c8cfcf25c85 Mon Sep 17 00:00:00 2001 From: Luka Skukan Date: Wed, 25 Jan 2017 19:55:11 +0100 Subject: [PATCH 1/9] Prevent containsWord on non-strings --- src/utils.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/utils.js b/src/utils.js index fb7d543..8f2954f 100644 --- a/src/utils.js +++ b/src/utils.js @@ -13,6 +13,10 @@ class Utils { } containsWord(testWord, words) { + if (typeof testWord !== 'string') { + return false; + } + testWord = testWord.toLowerCase(); for (let i = 0; i < words.length; i++) { From 071c40ce1a919f747dc128039d86a8c680bc7560 Mon Sep 17 00:00:00 2001 From: Luka Skukan Date: Thu, 26 Jan 2017 12:41:47 +0100 Subject: [PATCH 2/9] Refactor form binding and add IE fallback --- src/components/ChangePasswordForm.js | 2 +- src/components/LoginForm.js | 2 +- src/components/RegistrationForm.js | 2 +- src/components/ResetPasswordForm.js | 2 +- src/components/UserProfileForm.js | 2 +- src/utils.js | 23 +++++++++++++++++++++++ 6 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/components/ChangePasswordForm.js b/src/components/ChangePasswordForm.js index 8cbd301..fa2c22e 100644 --- a/src/components/ChangePasswordForm.js +++ b/src/components/ChangePasswordForm.js @@ -142,7 +142,7 @@ export default class ChangePasswordForm extends React.Component { } }; - if (typeof element.type === 'function' && utils.containsWord(element.type.name, ['input', 'field', 'text'])) { + if (utils.isInputLikeComponent(element)) { if (element.props && element.props.name) { tryMapFormField(element.props.name); } diff --git a/src/components/LoginForm.js b/src/components/LoginForm.js index b66ea3d..788dbe2 100644 --- a/src/components/LoginForm.js +++ b/src/components/LoginForm.js @@ -244,7 +244,7 @@ export default class LoginForm extends React.Component { } }; - if (typeof element.type === 'function' && utils.containsWord(element.type.name, ['input', 'field', 'text'])) { + if (utils.isInputLikeComponent(element)) { if (element.props && element.props.name) { tryMapFormField(element.props.name); } diff --git a/src/components/RegistrationForm.js b/src/components/RegistrationForm.js index 70a78d8..658fd4e 100644 --- a/src/components/RegistrationForm.js +++ b/src/components/RegistrationForm.js @@ -325,7 +325,7 @@ export default class RegistrationForm extends React.Component { } }; - if (typeof element.type === 'function' && utils.containsWord(element.type.name, ['input', 'field', 'text'])) { + if (utils.isInputLikeComponent(element)) { if (element.props && element.props.name) { tryMapFormField(element.props.name); } diff --git a/src/components/ResetPasswordForm.js b/src/components/ResetPasswordForm.js index dc98c6d..c8a82a2 100644 --- a/src/components/ResetPasswordForm.js +++ b/src/components/ResetPasswordForm.js @@ -106,7 +106,7 @@ export default class ResetPasswordForm extends React.Component { } }; - if (typeof element.type === 'function' && utils.containsWord(element.type.name, ['input', 'field', 'text'])) { + if (utils.isInputLikeComponent(element)) { if (element.props && element.props.name) { tryMapFormField(element.props.name); } diff --git a/src/components/UserProfileForm.js b/src/components/UserProfileForm.js index 4cb2822..c459712 100644 --- a/src/components/UserProfileForm.js +++ b/src/components/UserProfileForm.js @@ -168,7 +168,7 @@ export default class UserProfileForm extends React.Component { utils.getFieldValue(this.state.defaultFields, element.props.name) : undefined; - if (typeof element.type === 'function' && utils.containsWord(element.type.name, ['input', 'field', 'text'])) { + if (utils.isInputLikeComponent(element)) { if (element.props && element.props.name) { tryMapField(element.props.name, defaultValue); } diff --git a/src/utils.js b/src/utils.js index 8f2954f..4e91907 100644 --- a/src/utils.js +++ b/src/utils.js @@ -12,6 +12,24 @@ class Utils { s4() + '-' + s4() + s4() + s4(); } + functionName(f) { + if (typeof f !== 'function') { + return ''; + } + + if (f.name) { + return f.name; + } + + const parts = f.toString().match(/^function\s*([^\s(]+)/); + + if (parts) { + return parts[1]; + } + + return ''; + } + containsWord(testWord, words) { if (typeof testWord !== 'string') { return false; @@ -29,6 +47,11 @@ class Utils { return false; } + isInputLikeComponent(element, inputNames = ['input', 'field', 'text']) { + return typeof element.type === 'function' + && this.containsWord(this.functionName(element.type), inputNames); + } + takeProp(source, ...fields) { for (let i = 0; i < fields.length; i++) { let fieldName = fields[i]; From 1ab627a9e8d4dc212aba219bbbeff591cedbcef5 Mon Sep 17 00:00:00 2001 From: Luka Skukan Date: Thu, 26 Jan 2017 13:06:05 +0100 Subject: [PATCH 3/9] DRY out field mapping logic --- src/components/ChangePasswordForm.js | 10 +--------- src/components/LoginForm.js | 10 +--------- src/components/RegistrationForm.js | 10 +--------- src/components/ResetPasswordForm.js | 10 +--------- src/components/UserProfileForm.js | 12 +----------- src/utils.js | 12 ++++++++++++ 6 files changed, 17 insertions(+), 47 deletions(-) diff --git a/src/components/ChangePasswordForm.js b/src/components/ChangePasswordForm.js index fa2c22e..0e8aa83 100644 --- a/src/components/ChangePasswordForm.js +++ b/src/components/ChangePasswordForm.js @@ -142,15 +142,7 @@ export default class ChangePasswordForm extends React.Component { } }; - if (utils.isInputLikeComponent(element)) { - if (element.props && element.props.name) { - tryMapFormField(element.props.name); - } - } else if (element.type === 'input' || element.type === 'textarea') { - if (element.props.type !== 'submit') { - tryMapFormField(element.props.name); - } - } + utils.mapFormField(element, tryMapFormField); } _spIfHandler(action, element) { diff --git a/src/components/LoginForm.js b/src/components/LoginForm.js index 788dbe2..6f27fbf 100644 --- a/src/components/LoginForm.js +++ b/src/components/LoginForm.js @@ -244,15 +244,7 @@ export default class LoginForm extends React.Component { } }; - if (utils.isInputLikeComponent(element)) { - if (element.props && element.props.name) { - tryMapFormField(element.props.name); - } - } else if (['input', 'textarea'].indexOf(element.type) > -1) { - if (element.props.type !== 'submit') { - tryMapFormField(element.props.name); - } - } + utils.mapFormField(element, tryMapFormField); } _spIfHandler(action, element) { diff --git a/src/components/RegistrationForm.js b/src/components/RegistrationForm.js index 658fd4e..a18a28d 100644 --- a/src/components/RegistrationForm.js +++ b/src/components/RegistrationForm.js @@ -325,15 +325,7 @@ export default class RegistrationForm extends React.Component { } }; - if (utils.isInputLikeComponent(element)) { - if (element.props && element.props.name) { - tryMapFormField(element.props.name); - } - } else if (['input', 'textarea'].indexOf(element.type) > -1) { - if (element.props.type !== 'submit') { - tryMapFormField(element.props.name); - } - } + utils.mapFormField(element, tryMapFormField); } _spIfHandler(action, element) { diff --git a/src/components/ResetPasswordForm.js b/src/components/ResetPasswordForm.js index c8a82a2..ab4aa61 100644 --- a/src/components/ResetPasswordForm.js +++ b/src/components/ResetPasswordForm.js @@ -106,15 +106,7 @@ export default class ResetPasswordForm extends React.Component { } }; - if (utils.isInputLikeComponent(element)) { - if (element.props && element.props.name) { - tryMapFormField(element.props.name); - } - } else if (['input', 'textarea'].indexOf(element.type) > -1) { - if (element.props.type !== 'submit') { - tryMapFormField(element.props.name); - } - } + utils.mapFormField(element, tryMapFormField); } _spIfHandler(action, element) { diff --git a/src/components/UserProfileForm.js b/src/components/UserProfileForm.js index c459712..1ad1f98 100644 --- a/src/components/UserProfileForm.js +++ b/src/components/UserProfileForm.js @@ -168,17 +168,7 @@ export default class UserProfileForm extends React.Component { utils.getFieldValue(this.state.defaultFields, element.props.name) : undefined; - if (utils.isInputLikeComponent(element)) { - if (element.props && element.props.name) { - tryMapField(element.props.name, defaultValue); - } - } else if (element.type === 'input') { - if (element.props.type === 'submit') { - return; - } - - tryMapField(element.props.name, defaultValue); - } + utils.mapFormField(element, tryMapField, defaultValue); } _spIfHandler(action, element) { diff --git a/src/utils.js b/src/utils.js index 4e91907..c130beb 100644 --- a/src/utils.js +++ b/src/utils.js @@ -143,6 +143,18 @@ class Utils { return React.cloneElement(newElement, newOptions, newChildren); } + mapFormField(element, mappingFn, defaultValue) { + if (this.isInputLikeComponent(element)) { + if (element.props && element.props.name) { + mappingFn(element.props.name, defaultValue); + } + } else if (['input', 'textarea'].indexOf(element.type) > -1) { + if (element.props.type !== 'submit') { + mappingFn(element.props.name, defaultValue); + } + } + } + getFormFieldMap(root, handler) { var fields = {}; From 25d88c0f056b54434d77bc2344a8863bce3a755b Mon Sep 17 00:00:00 2001 From: Luka Skukan Date: Fri, 3 Feb 2017 12:58:13 +0100 Subject: [PATCH 4/9] Fix bug in form handling of faux inputs --- src/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.js b/src/utils.js index c130beb..c0fa9c2 100644 --- a/src/utils.js +++ b/src/utils.js @@ -167,7 +167,7 @@ class Utils { name = elements.props.fieldName; } - if (!('name' in fields)) { + if (!(name in fields)) { fields[name] = { element: field, defaultValue: defaultValue From 55f3fa167ff81bc6cb466167eb657828d96f7294 Mon Sep 17 00:00:00 2001 From: Luka Skukan Date: Fri, 3 Feb 2017 13:00:03 +0100 Subject: [PATCH 5/9] Fix another potential IE issue --- src/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.js b/src/utils.js index c0fa9c2..925d45c 100644 --- a/src/utils.js +++ b/src/utils.js @@ -189,7 +189,7 @@ class Utils { for (var key in fields) { var field = fields[key]; var element = field.element; - var elementType = typeof element.type === 'function' ? element.type.name : element.type; + var elementType = typeof element.type === 'function' ? functionName(element.type) : element.type; if (!(elementType in inverseMap)) { inverseMap[elementType] = {}; From 82d97af97e640510d24cff5d15546a3b65006919 Mon Sep 17 00:00:00 2001 From: Luka Skukan Date: Fri, 3 Feb 2017 13:17:47 +0100 Subject: [PATCH 6/9] Add support for spInputLike --- src/utils.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/utils.js b/src/utils.js index 925d45c..8f3047c 100644 --- a/src/utils.js +++ b/src/utils.js @@ -48,8 +48,12 @@ class Utils { } isInputLikeComponent(element, inputNames = ['input', 'field', 'text']) { - return typeof element.type === 'function' - && this.containsWord(this.functionName(element.type), inputNames); + if (typeof element.type === 'function') { + const hasInputLikeName = this.containsWord(this.functionName(element.type), inputNames); + const spInputLike = this.takeProp(element.props, 'spInputLike', 'data-spInputLike'); + + return spInputLike || hasInputLikeName; + } } takeProp(source, ...fields) { From 0fb831ee49a1c821b97013920b331241a54a2dfe Mon Sep 17 00:00:00 2001 From: Luka Skukan Date: Fri, 3 Feb 2017 13:50:42 +0100 Subject: [PATCH 7/9] Fix wrong context issue --- src/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.js b/src/utils.js index 8f3047c..f6ae114 100644 --- a/src/utils.js +++ b/src/utils.js @@ -193,7 +193,7 @@ class Utils { for (var key in fields) { var field = fields[key]; var element = field.element; - var elementType = typeof element.type === 'function' ? functionName(element.type) : element.type; + var elementType = typeof element.type === 'function' ? this.functionName(element.type) : element.type; if (!(elementType in inverseMap)) { inverseMap[elementType] = {}; From 2992541b03016d04ec5555590859881209d23deb Mon Sep 17 00:00:00 2001 From: Luka Skukan Date: Fri, 3 Feb 2017 13:53:19 +0100 Subject: [PATCH 8/9] Add note about using new spInputLike to docs --- docs/api.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/api.md b/docs/api.md index b52e5d0..a075c0b 100644 --- a/docs/api.md +++ b/docs/api.md @@ -245,6 +245,7 @@ Customize the form by providing your own markup. ``` **Important:** To use a custom field component the name of the component must contain one of these three words: `text`, `input` or `field`. +Alternatively, the component can have arbitrary name if it has the `spInputLike` (or `data-spInputLike`) property set. The component must also support the properties `name` and `onChange`. The property `name` should represent the name of the field, and the `onChange` property a handler for the field's `onChange` event. @@ -330,7 +331,7 @@ Specify `hideSocial` to hide the ability to register with a social provider. ``` -Customize the form by providing your own markup. +Customize the form by providing your own markup. By default, the registration form will render these four fields, and they will be required by the user: `givenName`, `surname`, `email`, and `password`. Express.js users who want to make `givenName` and/or `surname` optional, or to add new required fields (like `username`), can refer to [Stormpath Express Library Guide](https://docs.stormpath.com/nodejs/express/latest/registration.html). From cddbd933440bd5aaef201f65b788781aa3d9865f Mon Sep 17 00:00:00 2001 From: Luka Skukan Date: Fri, 3 Feb 2017 17:32:22 +0100 Subject: [PATCH 9/9] Add note about minification --- docs/api.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/api.md b/docs/api.md index a075c0b..fe85f19 100644 --- a/docs/api.md +++ b/docs/api.md @@ -245,7 +245,8 @@ Customize the form by providing your own markup. ``` **Important:** To use a custom field component the name of the component must contain one of these three words: `text`, `input` or `field`. -Alternatively, the component can have arbitrary name if it has the `spInputLike` (or `data-spInputLike`) property set. +Alternatively, the component can have arbitrary name if it has the `spInputLike` (or `data-spInputLike`) property set. If you minify your code, +prefer using this property to relying on the component name, as the latter may be mangled by the minifier. The component must also support the properties `name` and `onChange`. The property `name` should represent the name of the field, and the `onChange` property a handler for the field's `onChange` event.