From 38e5a96da82d9305c5a8c709d34b9f553fa5e1d3 Mon Sep 17 00:00:00 2001 From: Niv Sardi Date: Mon, 20 Aug 2018 16:30:31 -0300 Subject: [PATCH 01/11] indent all the things (and transform to PureComponents because it's free) --- src/index.js | 146 ++++++++++++++++++++++++--------------------------- 1 file changed, 70 insertions(+), 76 deletions(-) diff --git a/src/index.js b/src/index.js index 8b2be33..89b4303 100644 --- a/src/index.js +++ b/src/index.js @@ -37,7 +37,7 @@ const randomID = () => { return id; }; -class EurekaForm extends React.Component { +class EurekaForm extends React.PureComponent { constructor(props) { super(props); @@ -133,70 +133,52 @@ class EurekaForm extends React.Component { this.ctrlNext.classList.add('show'); }; - // show the next question control first time the input gets focused + // show the next question control first time the input gets focused firstElInput.addEventListener('focus', onFocusStartFn); if (this.props.autoFocus) { firstElInput.focus(); } - // show next question - this.ctrlNext.addEventListener('click', ev => { + // show next question + this.ctrlNext.addEventListener('click', ev => { ev.preventDefault(); - this._nextQuestion(); - }); + this._nextQuestion(); + }); - // pressing enter will jump to next question - this.formRef.addEventListener('keydown', ev => { + // pressing enter will jump to next question + this.formRef.addEventListener('keydown', ev => { const keyCode = ev.keyCode || ev.which; - // enter - if(keyCode === 13) { + // enter + if(keyCode === 13) { ev.preventDefault(); this._nextQuestion(); - } - }); + } + }); } - _nextQuestion() { - if(!this._validate()) { - return false; - } - // checks HTML5 validation - if (this.supportsHTML5Forms) { - const input = this.state.questions[this.state.current].querySelector('input, textarea, select'); - // clear any previous error messages - input.setCustomValidity(''); + if (this.supportsHTML5Forms) { + const input = this.state.questions[this.state.current].querySelector('input, textarea, select'); + // clear any previous error messages + input.setCustomValidity(''); - // checks input against the validation constraint - if (!input.checkValidity()) { - // Optionally, set a custom HTML5 valiation message - // comment or remove this line to use the browser default message + // checks input against the validation constraint + if (!input.checkValidity()) { + // Optionally, set a custom HTML5 valiation message + // comment or remove this line to use the browser default message //input.setCustomValidity('Whoops, that\'s not an email address!'); - - // display the HTML5 error message - this._showError(input.validationMessage); - - // prevent the question from changing - return false; - } - } - - // check if form is filled - if (this.state.current === this.state.questionsCount - 1) { - this.isFilled = true; - } - // clear any previous error messages - this._clearError(); + // display the HTML5 error message + this._showError(input.validationMessage); - // current question - const currentQuestion = this.state.questions[this.state.current]; - currentQuestion.querySelector('input, textarea, select').blur(); - this._setValue(currentQuestion) + // prevent the question from changing + return false; + } + } this.setState({ ...this.state, @@ -253,14 +235,26 @@ class EurekaForm extends React.Component { onEndTransitionFn(); } }); + _nextQuestion() { + if(!this._validate()) { + return false; + } + + // checks HTML5 validation + // check if form is filled + if (this.state.current === this.state.questionsCount - 1) { + this.isFilled = true; } + // clear any previous error messages + this._clearError(); + // updates the progress bar by setting its width - _progress() { - const currentProgress = this.state.current * ( 100 / this.state.questionsCount ); + _progress() { + const currentProgress = this.state.current * ( 100 / this.state.questionsCount ); this.progress.style.width = currentProgress + '%'; - - // update the progressbar's aria-valuenow attribute + + // update the progressbar's aria-valuenow attribute this.progress.setAttribute('aria-valuenow', currentProgress); } @@ -269,48 +263,48 @@ class EurekaForm extends React.Component { return false; } - // current question´s input - const input = this.state.questions[this.state.current].querySelector('input, textarea, select').value; - if (input === '') { + // current question´s input + const input = this.state.questions[this.state.current].querySelector('input, textarea, select').value; + if (input === '') { this._showError('EMPTYSTR'); - - return false; - } - return true; + return false; + } + + return true; } _updateQuestionNumber() { - // first, create next question number placeholder - this.nextQuestionNum = document.createElement('span'); - this.nextQuestionNum.className = 'number-next'; + // first, create next question number placeholder + this.nextQuestionNum = document.createElement('span'); + this.nextQuestionNum.className = 'number-next'; this.nextQuestionNum.innerHTML = Number(this.state.current + 1); - // insert it in the DOM - this.questionStatus.appendChild(this.nextQuestionNum); - } + // insert it in the DOM + this.questionStatus.appendChild(this.nextQuestionNum); + } _showError(err) { - let message = ''; - switch(err) { - case 'EMPTYSTR': - message = 'Please fill the field before continuing'; - break; - case 'INVALIDEMAIL': - message = 'Please fill a valid email address'; - break; - // ... - default: - message = err; + let message = ''; + switch(err) { + case 'EMPTYSTR': + message = 'Please fill the field before continuing'; + break; + case 'INVALIDEMAIL': + message = 'Please fill a valid email address'; + break; + // ... + default: + message = err; }; this.error.innerHTML = message; - this.error.classList.add('show'); + this.error.classList.add('show'); } _clearError() { - this.error.classList.remove('show'); + this.error.classList.remove('show'); } _setValue(question) { @@ -341,11 +335,11 @@ class EurekaForm extends React.Component { this.props.onSubmit(this.formRef, this.state.values); }); } - } + } render() { let customClass = ""; - + if (this.props.className) { customClass = this.props.className + " "; } From 1f49d286b2e9575a1a327fe8593fb5c4cd0ea933 Mon Sep 17 00:00:00 2001 From: Niv Sardi Date: Mon, 20 Aug 2018 16:31:24 -0300 Subject: [PATCH 02/11] /!\ API change: introduce Question component and port questions API /!\ /!\ this breaks the children API /!\ instead of passing just the label and constructing all the component in a loop, we create a Question component that you can use instead of the if you only want the previous API, or you can implement your own that just needs to implement an onChange callback whenever the data is ready to be added to values. this allows to implement maps, typeahead, or any other kind of custom components. we should probably ship some of those so that we have pretty and useable stuff right away. --- src/index.js | 188 +++++++++++++++++++++++++++++---------------------- 1 file changed, 107 insertions(+), 81 deletions(-) diff --git a/src/index.js b/src/index.js index 89b4303..ec0fb6f 100644 --- a/src/index.js +++ b/src/index.js @@ -37,6 +37,29 @@ const randomID = () => { return id; }; +class Question extends React.PureComponent { + render () { + const { + children = [], + onChange = function () {console.error('warning you didnt ass an onChange handler')}, + type = "text", + key + } = this.props + + return ( +
  • + + + + + +
  • + ) + } +} + class EurekaForm extends React.PureComponent { constructor(props) { super(props); @@ -160,7 +183,7 @@ class EurekaForm extends React.PureComponent { }); } - + _validateHTML5() { if (this.supportsHTML5Forms) { const input = this.state.questions[this.state.current].querySelector('input, textarea, select'); // clear any previous error messages @@ -179,14 +202,14 @@ class EurekaForm extends React.PureComponent { return false; } } + return true + } - this.setState({ - ...this.state, - // increment current question iterator - current: ++this.state.current - }, () => { + _nextQuestionFinish(currentQuestion) { + { // update progress bar this._progress(); + this.props.onUpdate(this.state) let nextQuestion; @@ -234,13 +257,19 @@ class EurekaForm extends React.PureComponent { else { onEndTransitionFn(); } - }); + } + } + _nextQuestion() { if(!this._validate()) { return false; } // checks HTML5 validation + if (! this._validateHTML5()) { + return false + } + // check if form is filled if (this.state.current === this.state.questionsCount - 1) { this.isFilled = true; @@ -249,6 +278,19 @@ class EurekaForm extends React.PureComponent { // clear any previous error messages this._clearError(); + // current question + const currentQuestion = this.state.questions[this.state.current]; + const currentInput = currentQuestion.querySelector('input, textarea, select') + currentInput.blur() + currentInput.setAttribute("disabled", true); + + this.setState(state => ({ + ...state, + // increment current question iterator + current: ++state.current + }), () => this._nextQuestionFinish(currentQuestion)); + } + // updates the progress bar by setting its width _progress() { const currentProgress = this.state.current * ( 100 / this.state.questionsCount ); @@ -257,7 +299,7 @@ class EurekaForm extends React.PureComponent { // update the progressbar's aria-valuenow attribute this.progress.setAttribute('aria-valuenow', currentProgress); } - + _validate() { if (!this.state.questions[this.state.current]) { return false; @@ -302,24 +344,26 @@ class EurekaForm extends React.PureComponent { this.error.classList.add('show'); } - + _clearError() { this.error.classList.remove('show'); } - - _setValue(question) { - const questionInput = question.querySelector('input, textarea, select'); - questionInput.setAttribute("disabled", true); - const key = questionInput.getAttribute("id") - const newState = { - ...this.state, - values: { - ...this.state.values, - [key]: questionInput.value + + _change(key) { + return function (e) { + const { questions, current } = this.state + + const questionInput = questions[current].querySelector('input, textarea, select'); + const newState = { + ...this.state, + values: { + ...this.state.values, + [key]: questionInput.value + } } + this.setState(newState) + console.error('this', this.state) } - this.setState(newState) - this.props.onUpdate(newState) } _submit() { @@ -338,67 +382,49 @@ class EurekaForm extends React.PureComponent { } render() { - let customClass = ""; - - if (this.props.className) { - customClass = this.props.className + " "; - } - - return ( -
    this.formRef = formRef}> -
    -
      - {this.props.questions && this.props.questions.map((question, i) => { - const key = question.key || `eureka-question-${i}` - return ( -
    1. - - - - - -
    2. - ) - })} - {this.props.children && React.Children.map(this.props.children, (child, i) => { - const key = child.props.type || `eureka-question-${i}` - return ( -
    3. - - - - - -
    4. - ) - })} -
    - - - -
    - - -
    - - - - - - - + const { className = "", children = [], questions } = this.props + let customClass = className + " "; + + questions && children.concat(questions.map((question, i) => ( + + {question.title} + + ))) + + return ( + this.formRef = formRef}> +
    +
      + {children && React.Children.map(children, (child, i) => { + const key = child.props.type || `eureka-question-${i}` + return React.cloneElement(child, { + onChange: this._change(key).bind(this), + key + }) + })} +
    + + + +
    + + +
    + + + + + + + +
    -
    - - - ) + + + ) } } @@ -406,4 +432,4 @@ EurekaForm.defaultProps = { onUpdate: function () {} } -module.exports = { EurekaForm }; +module.exports = { EurekaForm, Question }; From 02b8afdde41e11bafcd38b9a53153f65cddc5f68 Mon Sep 17 00:00:00 2001 From: Niv Sardi Date: Tue, 21 Aug 2018 00:30:17 -0300 Subject: [PATCH 03/11] doc for Component API --- README.md | 82 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 67 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 8ad77ef..3febcf0 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ npm install --save react-eureka ```jsx import React, { Component } from 'react'; -import { EurekaForm } from 'react-eureka'; +import { EurekaForm, Question } from 'react-eureka'; class EurekaDemo extends Component { constructor(props) { @@ -27,7 +27,7 @@ class EurekaDemo extends Component { formSubmitted: false }; } - + _onSubmit = () => this.setState({...this.state, formSubmitted: true}) render() { @@ -43,13 +43,13 @@ class EurekaDemo extends Component { What's your name - + Hello {values.name}, and your email? - + - + Phone Number? - + } @@ -84,7 +84,7 @@ there are 2 APIs you can use (you can actually use both, but we don't recomend i in the questions API you pass your questions as a JSON object. in the React children API you pass the components you want to display as your questions. -## questions API +## questions array API ```js const questions = [{ @@ -102,22 +102,74 @@ const questions = [{ ``` -## React children API -*Note:* **The type prop sets both the HTML form type and the key in the values object** +## React Component API +*Note:* **The type prop sets both the HTML form type and the key in the +values object** +each child you give to EurekaForm will be treated as a `question`, the +easiest way is to use the `Question` helper we provide. + +### Question Helper ```jsx - + What's your name - - + + Hello {values.name}, and your email? - - + + Phone Number? - + ``` +### Full API +In order to implement your own Question Helper you just need to call +`onChange` when your value changes, if you are going to use an `` +tag, please pass down the `type` property to make sure HTML5 validation +still works. You should also make sure to gracefully handle the `children` +prop, as it'll be usual to pass down the question component there. + +```jsx +class InputQuestion extends React.PureComponent { + render () { + const { onChange, type, children } = this.props + return ( +
    + {children} + +
    + ) + } +} + + +class ListQuestion extends React.PureComponent { + render () { + const { options, onChange, children } = this.props + return ( +
      +

      {children}

      + { options.map(opt, i => ( +
    • onChange(opt)}>{opt}
    • + )} +
    + ) + } +} + +const MyForm = ({values = {}}) => ( + + + What's your name ? + + + Hello {values.name}, and your country ? + + +) +``` + ## Credits The implementation of the component is based on the work of [Mary Lou from Tympanus](https://tympanus.net/Development/MinimalForm/) From 204dcd90fb2e97d5d7d9514843d8cc1ec1d01363 Mon Sep 17 00:00:00 2001 From: Niv Sardi Date: Tue, 21 Aug 2018 10:39:59 -0300 Subject: [PATCH 04/11] README: component example is simpler with functional components Signed-off-by: Niv Sardi --- README.md | 39 ++++++++++++++------------------------- 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 3febcf0..4305aed 100644 --- a/README.md +++ b/README.md @@ -131,32 +131,21 @@ still works. You should also make sure to gracefully handle the `children` prop, as it'll be usual to pass down the question component there. ```jsx -class InputQuestion extends React.PureComponent { - render () { - const { onChange, type, children } = this.props - return ( -
    - {children} - -
    - ) - } -} - +const InputQuestion = ({ onChange, type, children }) => ( +
    + {children} + +
    +) -class ListQuestion extends React.PureComponent { - render () { - const { options, onChange, children } = this.props - return ( -
      -

      {children}

      - { options.map(opt, i => ( -
    • onChange(opt)}>{opt}
    • - )} -
    - ) - } -} +const ListQuestion = ({ options, onChange, children }) => ( +
      +

      {children}

      + { options.map(opt, i => ( +
    • onChange(opt)}>{opt}
    • + )} +
    +) const MyForm = ({values = {}}) => ( From 6b2ddb83be3b4d1a2ad30f866398c02d2e3a52e3 Mon Sep 17 00:00:00 2001 From: Niv Sardi Date: Tue, 21 Aug 2018 10:40:15 -0300 Subject: [PATCH 05/11] README: fixup: correctly close tags Signed-off-by: Niv Sardi --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4305aed..17e90a8 100644 --- a/README.md +++ b/README.md @@ -151,10 +151,10 @@ const MyForm = ({values = {}}) => ( What's your name ? - + Hello {values.name}, and your country ? - + ) ``` From 7e23d005834fbaf6ac70838952c094651fd587f8 Mon Sep 17 00:00:00 2001 From: Niv Sardi Date: Thu, 23 Aug 2018 11:45:24 -0300 Subject: [PATCH 06/11] stop accessing DOM directly do things the react way. - create a questionsRef object to hold a ref to all Questions DOM nodes - extract values from questionsRef - process count, total number and error from state and questionsRef - process current/next/previous classNames from state - drop all use of innerHTML - implement focus() method in Question - implement focus prop on question to set disabled attribute (may need to move to a method --- src/index.js | 129 ++++++++++++--------------------------------------- 1 file changed, 29 insertions(+), 100 deletions(-) diff --git a/src/index.js b/src/index.js index ec0fb6f..09b152c 100644 --- a/src/index.js +++ b/src/index.js @@ -63,10 +63,17 @@ class Question extends React.PureComponent { class EurekaForm extends React.PureComponent { constructor(props) { super(props); + const { questions = [], children } = props + const stateQuestions = questions.map((question, i) => ( + + {question.title} + + )).concat(children) + this.questionRefs = {} this.state = { + questions: stateQuestions, current: 0, - questions: [], values: {}, wasSubmitted: false }; @@ -76,7 +83,6 @@ class EurekaForm extends React.PureComponent { const childQuestions = React.Children.map(this.props.children, (child, i) => ({ type: child.props.type || `eureka-question-${i}` })) - const questions = [].slice.call(this.formRef.querySelectorAll( 'ol.questions > li' )); const values = (this.props.questions || []) .concat(childQuestions) .reduce((acc, cur) => Object.assign({}, acc, { @@ -84,11 +90,8 @@ class EurekaForm extends React.PureComponent { }), {}) this.setState({ ...this.state, - questions, values, - // XXX: this actually doesn't catch duplicate keys, - // i.e. it actually counts the *awswers* you can get. - questionsCount: Object.keys(values).length, + questionsCount: this.state.questions.length }, callback); } @@ -96,8 +99,6 @@ class EurekaForm extends React.PureComponent { this._updateQuestions(() => { // show first question this.props.onUpdate(this.state) - const firstQuestion = this.state.questions[0]; - firstQuestion.classList.add('current'); // next question control this.ctrlNext = this.formRef.querySelector('button.next'); @@ -115,26 +116,9 @@ class EurekaForm extends React.PureComponent { // // question number status this.questionStatus = this.formRef.querySelector('span.number'); - + // // give the questions status an id this.questionStatus.id = this.questionStatus.id || randomID(); - - // associate "x / y" with the input via aria-describedby - for (var i = this.state.questions.length - 1; i >= 0; i--) { - const formElement = this.state.questions[i].querySelector('input, textarea, select'); - formElement.setAttribute('aria-describedby', this.questionStatus.id); - }; - - // // current question placeholder - this.currentNum = this.questionStatus.querySelector('span.number-current'); - this.currentNum.innerHTML = Number(this.state.current + 1); - - // // total questions placeholder - this.totalQuestionNum = this.questionStatus.querySelector('span.number-total'); - this.totalQuestionNum.innerHTML = this.state.questionsCount; - - // // error message - this.error = this.formRef.querySelector('span.error-message'); // checks for HTML5 Form Validation support // a cleaner solution might be to add form validation to the custom Modernizr script @@ -142,27 +126,11 @@ class EurekaForm extends React.PureComponent { // init events this._initEvents(); + this.questionRefs[this.state.current].focus() }); } _initEvents() { - // first input - const firstElInput = this.state.questions[this.state.current].querySelector('input, textarea, select'); - - // focus - const onFocusStartFn = () => { - firstElInput.removeEventListener('focus', onFocusStartFn); - - this.ctrlNext.classList.add('show'); - }; - - // show the next question control first time the input gets focused - firstElInput.addEventListener('focus', onFocusStartFn); - - if (this.props.autoFocus) { - firstElInput.focus(); - } - // show next question this.ctrlNext.addEventListener('click', ev => { ev.preventDefault(); @@ -211,20 +179,9 @@ class EurekaForm extends React.PureComponent { this._progress(); this.props.onUpdate(this.state) - let nextQuestion; - if(!this.isFilled) { - // change the current question number/status - this._updateQuestionNumber(); - // add class "show-next" to form element (start animations) this.formRef.classList.add('show-next'); - - // remove class "current" from current question and add it to the next one - // current question - nextQuestion = this.state.questions[this.state.current]; - currentQuestion.classList.remove('current'); - nextQuestion.classList.add('current'); } // after animation ends, remove class "show-next" from form element and change current question placeholder @@ -236,17 +193,10 @@ class EurekaForm extends React.PureComponent { if(self.isFilled) { this._submit(); - } - - else { + } else { this.formRef.classList.remove('show-next'); - - this.currentNum.innerHTML = this.nextQuestionNum.innerHTML; - this.questionStatus.removeChild(this.nextQuestionNum); - - // force the focus on the next input - nextQuestion.querySelector('input, textarea, select').focus(); } + this.questionRefs[this.state.current].focus() }; // onEndTransitionFn(); @@ -278,17 +228,11 @@ class EurekaForm extends React.PureComponent { // clear any previous error messages this._clearError(); - // current question - const currentQuestion = this.state.questions[this.state.current]; - const currentInput = currentQuestion.querySelector('input, textarea, select') - currentInput.blur() - currentInput.setAttribute("disabled", true); - this.setState(state => ({ ...state, // increment current question iterator current: ++state.current - }), () => this._nextQuestionFinish(currentQuestion)); + }), () => this._nextQuestionFinish()); } // updates the progress bar by setting its width @@ -316,16 +260,6 @@ class EurekaForm extends React.PureComponent { return true; } - _updateQuestionNumber() { - // first, create next question number placeholder - this.nextQuestionNum = document.createElement('span'); - this.nextQuestionNum.className = 'number-next'; - this.nextQuestionNum.innerHTML = Number(this.state.current + 1); - - // insert it in the DOM - this.questionStatus.appendChild(this.nextQuestionNum); - } - _showError(err) { let message = ''; switch(err) { @@ -339,26 +273,27 @@ class EurekaForm extends React.PureComponent { default: message = err; }; - - this.error.innerHTML = message; - - this.error.classList.add('show'); + + this.setState({ + error: message + }) } _clearError() { - this.error.classList.remove('show'); + this.setState({ + error: null + }) } _change(key) { - return function (e) { + return function (value) { const { questions, current } = this.state - const questionInput = questions[current].querySelector('input, textarea, select'); const newState = { ...this.state, values: { ...this.state.values, - [key]: questionInput.value + [key]: value } } this.setState(newState) @@ -382,20 +317,16 @@ class EurekaForm extends React.PureComponent { } render() { - const { className = "", children = [], questions } = this.props + const { current, error, questions, questionsCount } = this.state + const { className = "" } = this.props let customClass = className + " "; - questions && children.concat(questions.map((question, i) => ( - - {question.title} - - ))) return (
    this.formRef = formRef}>
      - {children && React.Children.map(children, (child, i) => { + {questions && React.Children.map(questions, (child, i) => { const key = child.props.type || `eureka-question-${i}` return React.cloneElement(child, { onChange: this._change(key).bind(this), @@ -414,15 +345,13 @@ class EurekaForm extends React.PureComponent {
      - - + + {questionsCount} - + {error && {error}}
    - - ) } From e167ed0a2b65f83c44e16fa9e5aed1a34ee13851 Mon Sep 17 00:00:00 2001 From: Niv Sardi Date: Thu, 23 Aug 2018 11:49:23 -0300 Subject: [PATCH 07/11] Move validation to Question Component we introduce a `validate()` method that defaults to using `_validateHTML5()` (note: this breaks html5 detection, but there was no validation happening when HTML5 was not available anyway. ) you are now *required* to provide a `this.inputRef` in the Question Component for this to work, or re-implement your own `validate()` method (you can still use the `_validateHTML5()` method if you wish by passing it an `inputRef`). --- src/index.js | 118 ++++++++++++++++++++++++++++----------------------- 1 file changed, 65 insertions(+), 53 deletions(-) diff --git a/src/index.js b/src/index.js index 09b152c..ad8491a 100644 --- a/src/index.js +++ b/src/index.js @@ -38,24 +38,71 @@ const randomID = () => { }; class Question extends React.PureComponent { + constructor(props) { + super(props) + + this.inputRef = null + } + + _validateHTML5(input) { + if (input.setCustomValidity && input.checkValidity) { + // clear any previous error messages + input.setCustomValidity(''); + + // checks input against the validation constraint + if (!input.checkValidity()) { + // Optionally, set a custom HTML5 valiation message + // comment or remove this line to use the browser default message + //input.setCustomValidity('Whoops, that\'s not an email address!'); + + // prevent the question from changing + return input.validationMessage; + } + } + return true + } + + validate() { + // XXX(xaiki): here the correct way would be to use ReactDOM to find + // `this` so we don't have to pass the node around, but that would + // introduce a new dependency, so we have this weird API where we let + // the user shoot his foot by providing us the (wrong?) ref + return this._validateHTML5(this.inputRef) + } + + focus() { + this.inputRef && this.inputRef.focus() + } + render () { const { children = [], - onChange = function () {console.error('warning you didnt ass an onChange handler')}, type = "text", - key + key, + focus, } = this.props + let { onChange } = this.props + + if (! onChange) { + console.error('warning you didnt pass an `onChange` handler') + onChange = function () {} + } + return ( -
  • + - -
  • + this.inputRef = e} + id={key} name={key} + onChange={onChange} + type={type} + disabled={!focus}/> + ) } } @@ -141,39 +188,18 @@ class EurekaForm extends React.PureComponent { // pressing enter will jump to next question this.formRef.addEventListener('keydown', ev => { const keyCode = ev.keyCode || ev.which; - + console.error('keydown', keyCode, ev) + // enter if(keyCode === 13) { ev.preventDefault(); - + this._nextQuestion(); } }); } - _validateHTML5() { - if (this.supportsHTML5Forms) { - const input = this.state.questions[this.state.current].querySelector('input, textarea, select'); - // clear any previous error messages - input.setCustomValidity(''); - - // checks input against the validation constraint - if (!input.checkValidity()) { - // Optionally, set a custom HTML5 valiation message - // comment or remove this line to use the browser default message - //input.setCustomValidity('Whoops, that\'s not an email address!'); - - // display the HTML5 error message - this._showError(input.validationMessage); - - // prevent the question from changing - return false; - } - } - return true - } - - _nextQuestionFinish(currentQuestion) { + _nextQuestionFinish() { { // update progress bar this._progress(); @@ -211,13 +237,15 @@ class EurekaForm extends React.PureComponent { } _nextQuestion() { - if(!this._validate()) { - return false; - } - - // checks HTML5 validation - if (! this._validateHTML5()) { - return false + const component = this.questionRefs[this.state.current] + if (! component.validate) { + console.error("Warning, component", component, "doesn't support validation, implement your validate() method") + } else { + const validationResult = component.validate() + console.error("validate", validationResult) + if (validationResult !== true) { + return this._showError(validationResult); + } } // check if form is filled @@ -244,22 +272,6 @@ class EurekaForm extends React.PureComponent { this.progress.setAttribute('aria-valuenow', currentProgress); } - _validate() { - if (!this.state.questions[this.state.current]) { - return false; - } - - // current question´s input - const input = this.state.questions[this.state.current].querySelector('input, textarea, select').value; - if (input === '') { - this._showError('EMPTYSTR'); - - return false; - } - - return true; - } - _showError(err) { let message = ''; switch(err) { From 9e2fbef22d375d3e2c59e62e465b6498038bd744 Mon Sep 17 00:00:00 2001 From: Niv Sardi Date: Thu, 23 Aug 2018 11:53:09 -0300 Subject: [PATCH 08/11] Reimplement Animations (still needs fixing) we completly rework how animation work, introducing a `.current`, `.next` and `.prev` system of classNames, we also introduce a `Number` component (that we export so you can re-use it in other parts of your UI) that uses the same CSS as the questions roll to handle the counter. we will animate any `