Skip to content

Commit 6067e65

Browse files
major upgrade to v3.0.0. fixes #6 and major code refactor
1 parent 660fb2e commit 6067e65

File tree

9 files changed

+95
-56
lines changed

9 files changed

+95
-56
lines changed

README.md

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,19 @@ const steps =
3737
- pass in following options as well if you want to customise it further
3838

3939
```
40-
// hide or show Next and Previous Buttons
40+
// hide or show Next and Previous Buttons at the bottom
4141
showNavigation: true | false
4242
4343
// disable or enable the steps UI navigation on top
4444
showSteps: true | false
4545
46-
// disable or enable onClick step jumping from the UI navigation summary on top
46+
// disable or enable onClick step jumping from the UI navigation on top
4747
stepsNavigation: true | false
4848
49-
// show or hide the previews button in the last step (maybe the last step is a thank you message and you don't want them to go back)
49+
// show or hide the previous button in the last step (maybe the last step is a thank you message and you don't want them to go back)
5050
prevBtnOnLastStep: true | false
5151
52-
// dev control to disable calling and Child form component validation
52+
// dev control to disable validation rules called in step components **
5353
dontValidate: true | false
5454
5555
// by default if you hit the Enter key on any element it validates the form and moves to next step if validation passes. Use this to prevent this behaviour
@@ -71,7 +71,7 @@ example options usage:
7171
```
7272

7373

74-
- if one of your components is a form that requires validation before moving to the next component, then that component needs to implement a `isValidated()` public method which validates the form and returns true/false if the data is valid. For an e.g. on this have a look at the `src/examples/Step2` component.
74+
- ** if one of your components is a form that requires validation before moving to the next component, then that component needs to implement a `isValidated()` public method which validates the form and returns true/false if the data is valid. For an e.g. on this have a look at the `src/examples/Step2` component.
7575

7676
- also if you want some default style, copy the source from `src/css/main.css` code into your project (the above look in the picture also requires bootstrap)
7777

@@ -101,19 +101,21 @@ A full example is found in the `src/examples` directory.
101101
- all code is run against coverage, not just the unit tested modules
102102
- test coverage improvement is currently a work in progress
103103

104-
Current coverage sitting at v1.9.3:
104+
Current coverage sitting at v3.0.0:
105105
```
106-
Statements : 67.01% ( 65/97 ), 4 ignored
107-
Branches : 50.6% ( 42/83 ), 13 ignored
108-
Functions : 95.24% ( 20/21 ), 6 ignored
109-
Lines : 43.86% ( 25/57 )
106+
Statements : 53.17% ( 67/126 ), 4 ignored
107+
Branches : 41.75% ( 43/103 ), 13 ignored
108+
Functions : 76.67% ( 23/30 ), 7 ignored
109+
Lines : 31.4% ( 27/86 )
110110
```
111111

112112
### todo
113113
- ~~write the tests~~
114114
- improve code coverage
115115

116116
#### change log
117+
- 3.0.0
118+
- major revamp of logic to deal with validation bugs from stepsNavigation (fixes [#6](/newbreedofgeek/react-stepzilla/issues/6)). Also complete revamp of example app to be a fully working sample
117119
- 2.0.1
118120
- bug with handleKeyDown, as we capture enter if preventEnterSubmission=true, then onEnter the page refreshes
119121
- 2.0.0

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-stepzilla",
3-
"version": "2.0.1",
3+
"version": "3.0.0",
44
"description": "A react multi-step, wizard component for managing data collection via forms and sub components",
55
"main": "./dist/main.js",
66
"scripts": {

src/examples/Example.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ export default class Example extends Component {
2929

3030
updateStore(update) {
3131
this.sampleStore = {
32-
...update
32+
...this.sampleStore,
33+
...update,
3334
}
3435
}
3536

src/examples/Step1.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import React, { Component, PropTypes } from 'react';
44

55
export default class Step1 extends Component {
6-
76
constructor(props) {
87
super(props);
98

@@ -25,7 +24,20 @@ export default class Step1 extends Component {
2524
<div className="form-group">
2625
<label className="md-col-12 control-label">
2726
<h1>Step 1: Welcome to the StepZilla Example</h1>
27+
<h2>This example uses this config:</h2>
2828
</label>
29+
<br />
30+
<code>
31+
stepsNavigation=true
32+
<br />
33+
prevBtnOnLastStep=true
34+
<br />
35+
dontValidate=false
36+
<br />
37+
preventEnterSubmission=true
38+
<br />
39+
nextTextOnFinalActionStep="Save"
40+
</code>
2941
</div>
3042
</form>
3143
</div>

src/examples/Step2.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,12 @@ export default class Step2 extends Component {
2828

2929
// if full validation passes then save to store and pass as valid
3030
if (Object.keys(validateNewInput).every((k) => { return validateNewInput[k] === true })) {
31-
this.props.updateStore(userInput); // Update store here (this is just an example, in reality you will do it via redux or flux)
31+
if (this.props.getStore().email != userInput.email || this.props.getStore().gender != userInput.gender) { // only update store of something changed
32+
this.props.updateStore({
33+
...userInput,
34+
savedToCloud: false // use this to notify step4 that some changes took place and prompt the user to save again
35+
}); // Update store here (this is just an example, in reality you will do it via redux or flux)
36+
}
3237

3338
isDataValid = true;
3439
}

src/examples/Step4.js

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,23 @@ export default class Step4 extends Component {
1919
// _isValidated() {}
2020

2121
render() {
22-
const showSavedCls = (this.state.savedToCloud) ? 'show' : 'hide';
23-
2422
return (
2523
<div className="step step4">
2624
<div className="row">
2725
<form id="Form" className="form-horizontal">
2826
<div className="form-group">
29-
<label className="md-col-12 control-label">
30-
<h1>Thanks!</h1>
31-
<h2 className={showSavedCls}>Data was successfully saved to cloud...</h2>
32-
</label>
27+
<label className="md-col-12 control-label">
28+
{
29+
(this.state.savedToCloud)
30+
?
31+
<div>
32+
<h1>Thanks!</h1>
33+
<h2>Data was successfully saved to cloud...</h2>
34+
</div>
35+
:
36+
<h1>You have updated data, go <a onClick={() => {this.props.jumpToStep(2)}}>back</a> and Save again!</h1>
37+
}
38+
</label>
3339
</div>
3440
</form>
3541
</div>

src/examples/index.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@
7373
width: 90%;
7474
padding: 5px;
7575
}
76+
77+
code {
78+
position: relative;
79+
left: 12px;
80+
line-height: 25px;
81+
}
7682
</style>
7783
</head>
7884
<body>

src/main.js

Lines changed: 39 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@ export default class StepZilla extends Component {
1616
display: 'none'
1717
};
1818

19-
this.applyValidationState();
19+
this.applyValidationFlagsToSteps();
2020
}
2121

22-
applyValidationState() {
22+
// extend the "steps" array with flags to indicate if they have been validated
23+
applyValidationFlagsToSteps() {
2324
this.props.steps.map((i) => {
2425
if (this.props.dontValidate) {
2526
i.validated = true;
@@ -32,6 +33,7 @@ export default class StepZilla extends Component {
3233
});
3334
}
3435

36+
// update the header nav states via classes so they can be styled via css
3537
getNavStates(indx, length) {
3638
let styles = [];
3739

@@ -50,6 +52,7 @@ export default class StepZilla extends Component {
5052
return { current: indx, styles }
5153
}
5254

55+
// which step are we in?
5356
checkNavState(currentStep){
5457
if (currentStep > 0 && currentStep !== this.props.steps.length - 1) {
5558
let correctNextText = 'Next';
@@ -78,6 +81,7 @@ export default class StepZilla extends Component {
7881
}
7982
}
8083

84+
// set the nav state
8185
setNavState(next) {
8286
this.setState({navState: this.getNavStates(next, this.props.steps.length)});
8387

@@ -106,34 +110,35 @@ export default class StepZilla extends Component {
106110
// a child step wants to invoke a jump between steps
107111
this.setNavState(evt);
108112
}
109-
else {
110-
// the main navigation step ui is invoking a jump between steps
111-
if (!this.props.stepsNavigation) {
113+
else { // the main navigation step ui is invoking a jump between steps
114+
if (!this.props.stepsNavigation || evt.target.value == this.state.compState) { // if stepsNavigation is turned off or user clicked on existing step again (on step 2 and clicked on 2 again) then ignore
112115
evt.preventDefault();
113116
evt.stopPropagation();
114117

115118
return;
116119
}
117120

118-
// debugger;
119-
if (this.stepMoveAllowed(true)) {
120-
121-
let passThroughStepsValidated = true;
122-
123-
if (evt.target.value > this.state.compState) {
124-
console.log('moving ahead so validate in between steps');
125-
passThroughStepsValidated = false;
126-
// console.log(this.props.steps);
127-
128-
const go = this.props.steps.reduce((r, i) => {
129-
r.push(i.validated);
130-
return r;
131-
}, []);
132-
133-
console.log(go);
121+
// are we trying to move back or front?
122+
const movingBack = evt.target.value < this.state.compState;
123+
124+
if (this.stepMoveAllowed(movingBack)) {
125+
let passThroughStepsNotValid = false; // if we are jumping forward, only allow that if inbetween steps are all validated. This flag informs the logic...
126+
127+
if (!movingBack) {
128+
// looks like we are moving forward, 'reduce' a new array of step>validated values we need to check and 'some' that to get a decision on if we should allow moving forward
129+
passThroughStepsNotValid = this.props.steps
130+
.reduce((a, c, i) => {
131+
if (i >= this.state.compState && i < evt.target.value) {
132+
a.push(c.validated);
133+
}
134+
return a;
135+
}, [])
136+
.some((c) => {
137+
return c === false
138+
})
134139
}
135140

136-
if (passThroughStepsValidated) {
141+
if (!passThroughStepsNotValid) {
137142
if (evt.target.value === (this.props.steps.length - 1) &&
138143
this.state.compState === (this.props.steps.length - 1)) {
139144
this.setNavState(this.props.steps.length);
@@ -143,31 +148,24 @@ export default class StepZilla extends Component {
143148
}
144149
}
145150
}
146-
147-
// if (this.props.dontValidate || (passThroughStepsValidated && typeof this.refs.activeComponent.isValidated == 'undefined' || this.refs.activeComponent.isValidated()) ) {
148-
// if (evt.target.value === (this.props.steps.length - 1) &&
149-
// this.state.compState === (this.props.steps.length - 1)) {
150-
// this.setNavState(this.props.steps.length);
151-
// }
152-
// else {
153-
// this.setNavState(evt.target.value);
154-
// }
155-
// }
156151
}
157152
}
158153

154+
// move next via next button
159155
next() {
160156
if (this.stepMoveAllowed()) {
161157
this.setNavState(this.state.compState + 1);
162158
}
163159
}
164160

161+
// move behind via previous button
165162
previous() {
166163
if (this.state.compState > 0) {
167164
this.setNavState(this.state.compState - 1);
168165
}
169166
}
170167

168+
// are we allowed to move forward? via the next button or via jumpToStep?
171169
stepMoveAllowed(skipValidationExecution = false) {
172170
let proceed = false;
173171

@@ -179,7 +177,12 @@ export default class StepZilla extends Component {
179177
if (typeof this.refs.activeComponent.isValidated == 'undefined') {
180178
proceed = true;
181179
}
182-
else if (!skipValidationExecution) {
180+
else if (skipValidationExecution) {
181+
// we are moving backwards in steps, in this case dont validate as it means the user is not commiting to "save"
182+
proceed = true;
183+
}
184+
else {
185+
// user is moving forward in steps, invoke validation as its available
183186
proceed = this.refs.activeComponent.isValidated();
184187
this.props.steps[this.state.compState].validated = (typeof proceed == 'undefined') ? true : proceed; // if a step component returns 'underfined' then trate as "true" as it's an aync call (i.e. ajax call)
185188
}
@@ -188,6 +191,7 @@ export default class StepZilla extends Component {
188191
return proceed;
189192
}
190193

194+
// get the classmame of steps
191195
getClassName(className, i){
192196
let liClassName = className + "-" + this.state.navState.styles[i];
193197

@@ -198,6 +202,7 @@ export default class StepZilla extends Component {
198202
return liClassName;
199203
}
200204

205+
// render the steps as stepsNavigation
201206
renderSteps() {
202207
return this.props.steps.map((s, i)=> (
203208
<li className={this.getClassName("progtrckr", i)} onClick={(evt) => {this.jumpToStep(evt)}} key={i} value={i}>
@@ -207,6 +212,7 @@ export default class StepZilla extends Component {
207212
));
208213
}
209214

215+
// main render of stepzilla container
210216
render() {
211217
// clone the step component dynamically and tag it as activeComponent so we can validate it on next. also bind the jumpToStep piping method
212218
const compToRender = React.cloneElement(this.props.steps[this.state.compState].component, {

tests/main.spec.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ function setup(stepCount = 1, config = {}) {
3737

3838
describe('StepZilla', () => {
3939
describe('base component render', () => {
40-
const { enzymeWrapper } = setup(2);
40+
const { enzymeWrapper } = setup(2, {dontValidate: true});
4141

4242
it('should render self and primary css classes', () => {
4343
expect(enzymeWrapper).to.have.length(1);
@@ -50,7 +50,7 @@ describe('StepZilla', () => {
5050
});
5151

5252
describe('default props based ui render', () => {
53-
const { enzymeWrapper } = setup(2);
53+
const { enzymeWrapper } = setup(2, {dontValidate: true});
5454

5555
it('should render showSteps based header', () => {
5656
expect(enzymeWrapper.find('.progtrckr')).to.have.length(1);
@@ -64,7 +64,8 @@ describe('StepZilla', () => {
6464
describe('custom props based ui render', () => {
6565
const { enzymeWrapper } = setup(2, {
6666
showSteps: false,
67-
showNavigation: false
67+
showNavigation: false,
68+
dontValidate: true
6869
});
6970

7071
it('should NOT render showSteps based header as "showSteps: false"', () => {

0 commit comments

Comments
 (0)