4

I'm not actually sure of the best name for what I'm trying to describe. Decision flow, decision matrix, flow chart...

What is the best way to implement a flow that is basically a "Choose Your Own Adventure" stepper, or a flowchart turned into a stepper?

For example, you have a UI with steps, and the next and previous step that will appear depend on data from the previous step. Right now, I have a switch statement to determine which step to appear, and very ugly logic to determine which step should be next or previous.

(I'm using React to render components for each step, but I don't think that matters much with regards to the question).

renderStep = () => {
    switch (currentStep) {
      case 1:
        return <FirstStep/>
      case 2:
        return <SecondStep />
      case 2.5:
        return <SecondStepA />
      case 3:
        return <ThirdStep />
      case 3.25:
        return <ThirdStepA />
      case 3.5:
        return <ThirdStepB />
      case 3.75:
        return <ThirdStepC />
      case 4:
        return <FourthStep />
      default:
        return null
    }
  }

Now clicking "Next" or "Previous" will send the data, and determine for each step which step to go to. If the flow were linear, it would be very easy - update the data, increment or decrement by one. But with the conditional flow, it gets more complicated.

  goToPreviousStep = () => {
    const { currentStep } = this.state

    this.setState(prevState => {
      if (currentStep === 4 && prevState.someDataHappened) {
        return { currentStep: prevState.currentStep - 0.5 }
      } else if (currentStep === 3.5) {
        return { currentStep: prevState.currentStep - 0.5 }
      } else {
        return { currentStep: prevState.currentStep - 1 }
      }
    })
  }

Potentially trying to support additional nesting beyond that would be even more complex. As this grows, I know it's going to become unmaintainable, but I can't find any good resources for storing this data in a table or something to make it more programmatic. Can someone point me in the right direction?

Not sure if I have to write some non-deterministic finite automata or something...

Tania Rascia
  • 1,563
  • 17
  • 35
  • Javascript is no different from any other language here, possibly more powerful. But as you say. Forget presentation layer. You need data (JSON) and a basic `rule` class. From there you can derive and create more complex rules. Most basic is a processing step. More complex is an IF rule. The list goes on. – Bibberty Dec 04 '19 at 17:51
  • @Bibberty assume each step is determined by a new API call to get data, and the next one is determined by that API data...I wouldn't be able to have the JSON built out in advance. Would this approach still work? – Tania Rascia Dec 04 '19 at 17:58
  • Tania, you build out a set of classes, starting with a base class, that class accepts data from a parent and executes the logic in the derived class. This in-turn based on the logic implemented affects the data and calls one or more of its children. – Bibberty Dec 04 '19 at 18:28

1 Answers1

1

Have you looked at something similar to how a linked list works? Each step is an object and each step has a reference to its previous step as well as potential next steps. Traversal can be done by following these links, and the current step can be stored as the object instead of some kind of number. If you want to number them, you can simply count all the steps and then traverse backwards to the first step to see what step number you're on.

You can put as much data as you need in the Step prototype so that each steps defines everything needed to render it, to take actions based on it, etc. Title, text, associated urls, etc.

function Step() {}

Step.prototype.previous = null;
Step.prototype.next = [];

Step.prototype.addNext = function(nextStep) {
  let step = Object.assign(new Step(), nextStep);
  step.previous = this;
  this.next.push(step);
}

Step.prototype.getNumber = function() {
  var number = 0;
  var step = this;
  
  while(step) {
    step = step.previous;
    number += 1;
  }
  
  return number;
}


let CurrentStep = null;
function createSteps() {

  const step1 = new Step();
  const step2 = new Step();
  const step2_1 = new Step();
  const step2_2 = new Step();
  const step3 = new Step();
  
  CurrentStep = step1;
  
  step1.addNext(step2);
  step1.addNext(step2_1);
  step1.addNext(step2_2);
  
  step2.addNext(step3);
  step2_1.addNext(step3);
  step2_2.addNext(step3);

}

createSteps();

console.log(CurrentStep.getNumber());
console.log(CurrentStep.next[0].getNumber());
console.log(CurrentStep.next[1].getNumber());
console.log(CurrentStep.next[2].getNumber());
flagoworld
  • 3,196
  • 2
  • 20
  • 16
  • This looks like a good approach. There would still be a lot of logic to figure out which step to move to, but figuring out which next or previous to chose would be better like this than subtracting and adding .5... – Tania Rascia Dec 04 '19 at 18:21
  • How would you "Go to (conditional) next" in this example? – Tania Rascia Dec 04 '19 at 18:45
  • What do you mean by (conditional) next? Can you be more specific? – flagoworld Dec 04 '19 at 21:58
  • Unfortunately, there's a problem with the "previous" with this approach. `step1.addNext(step2)` will make the previous of step2->step1, then `step1.addNext(step2_1)` will make the previous of step2_1->step1, then `step2.addNext(step3)` will make the previous of step3->step2, then `step2_1.addNext(step3)` will make the previous of step3->step2_1, which means if you got to step 3 from step 2, it will assume you got there from step 2_1. – Tania Rascia Dec 04 '19 at 23:03
  • You can solve that by shallow copying each step when it's added in the addNext. Previous can also be an array if you want that extra logic. I edited the example. – flagoworld Dec 05 '19 at 23:34
  • 1
    Thanks, @flagoworld, I ended up doing something similar and using an array for previous. This was very helpful. – Tania Rascia Dec 06 '19 at 16:39