diff --git a/docs/api.md b/docs/api.md index b52e5d0..fe85f19 100644 --- a/docs/api.md +++ b/docs/api.md @@ -245,6 +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. 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. @@ -330,7 +332,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). diff --git a/src/components/ChangePasswordForm.js b/src/components/ChangePasswordForm.js index 8cbd301..0e8aa83 100644 --- a/src/components/ChangePasswordForm.js +++ b/src/components/ChangePasswordForm.js @@ -142,15 +142,7 @@ export default class ChangePasswordForm extends React.Component { } }; - if (typeof element.type === 'function' && utils.containsWord(element.type.name, ['input', 'field', 'text'])) { - 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 b66ea3d..6f27fbf 100644 --- a/src/components/LoginForm.js +++ b/src/components/LoginForm.js @@ -244,15 +244,7 @@ export default class LoginForm extends React.Component { } }; - if (typeof element.type === 'function' && utils.containsWord(element.type.name, ['input', 'field', 'text'])) { - 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 70a78d8..a18a28d 100644 --- a/src/components/RegistrationForm.js +++ b/src/components/RegistrationForm.js @@ -325,15 +325,7 @@ export default class RegistrationForm extends React.Component { } }; - if (typeof element.type === 'function' && utils.containsWord(element.type.name, ['input', 'field', 'text'])) { - 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 dc98c6d..ab4aa61 100644 --- a/src/components/ResetPasswordForm.js +++ b/src/components/ResetPasswordForm.js @@ -106,15 +106,7 @@ export default class ResetPasswordForm extends React.Component { } }; - if (typeof element.type === 'function' && utils.containsWord(element.type.name, ['input', 'field', 'text'])) { - 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 4cb2822..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 (typeof element.type === 'function' && utils.containsWord(element.type.name, ['input', 'field', 'text'])) { - 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 fb7d543..f6ae114 100644 --- a/src/utils.js +++ b/src/utils.js @@ -12,7 +12,29 @@ 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; + } + testWord = testWord.toLowerCase(); for (let i = 0; i < words.length; i++) { @@ -25,6 +47,15 @@ class Utils { return false; } + isInputLikeComponent(element, inputNames = ['input', 'field', 'text']) { + 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) { for (let i = 0; i < fields.length; i++) { let fieldName = fields[i]; @@ -116,6 +147,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 = {}; @@ -128,7 +171,7 @@ class Utils { name = elements.props.fieldName; } - if (!('name' in fields)) { + if (!(name in fields)) { fields[name] = { element: field, defaultValue: defaultValue @@ -150,7 +193,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' ? this.functionName(element.type) : element.type; if (!(elementType in inverseMap)) { inverseMap[elementType] = {};