How to implement a stepper decision flow in JavaScript? - javascript

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...

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());

Related

Rxjs/lodash throttle - how to use it on condition and in case that the condition may change while app runing?

I'm listening to the observable that may return true or false value - the only thing that I want to do is to set throttleTime for function call when it's true and don't have it when it's false. So I did some kind of workaround for that but I don't like this solution. I have tried a different approach where I tried to do it in the actions' effect but without success..
So this is the observable:
this.store$
.pipe(
takeUntil(this.componentDestroyed$),
select(selectGlobalsFiltered([
GlobalPreferencesKeys.liveAircraftMovement])),
distinctUntilChanged()
)
.subscribe((globals) => {
if (globals && globals[GlobalPreferencesKeys.liveAircraftMovement] !== undefined) {
this.isLiveMovementEnabled = (globals[GlobalPreferencesKeys.liveAircraftMovement] === 'true');
}
if (!this.isLiveMovementEnabled) {
this.processPlaneData = throttle(this.processPlaneData, 4000);
} else {
this.processPlaneData = this.notThrottledFunction;
}
});
And as you can see I've created excat the same method that is 'pure' - notThrottledFunction and I'm assigning it when it's needed.
processPlaneData(data: Airplane[]): void {
this.store$.dispatch(addAllAirplanes({ airplanes: data }));
}
notThrottledFunction(data: Airplane[]): void {
this.store$.dispatch(addAllAirplanes({ airplanes: data }));
}
So basically this is working solution, but I'm pretty sure there is a better approach for doing such a things.
*throttle(this.processPlaneData, isLiveMovementEnabled ? 0 : 4000) doesn't work
So the second approch where I tried to do this inside of effect, I added a new argument for addAllAirplanes action - isLiveMovementEnabled: this.isLiveMovementEnabled
addAllAirplanes$ = createEffect(() =>
this.actions$.pipe(
ofType(ActionTypes.ADD_ALL_AIRPLANES),
map((data) => {
if (data.isLiveMovementEnabled) {
return addAllAirplanesSuccessWithThrottle(data);
} else {
return addAllAirplanesSuccess(data);
}
}
)
);
And then I added another effect for addAllAirplanesSuccessWithThrottle
addAllAirplanesThrottle$ = createEffect(() =>
this.actions$.pipe(
ofType(ActionTypes.ADD_ALL_AIRPLANES_THROTTLE),
throttleTime(4000),
map((data) => addAllAirplanesSuccess(data))
)
);
But it doesn't work. Can someone help me?
(It's not clear how the data arrives in your example code, but I'll assume an Observable)
throttle and throttleTime are similar to your use case, but I think it makes sense to build your own custom implementation without them. I'd suggest managing the timing yourself, using Date to determine time deltas.
You've already cached the live filtering boolean in the component state, so we can just handle all of the data stream from your position update observable and filter them out manually (which is all throttle does, but it expects you to be able to feed it an interval at subscription time, and yours needs to be dynamic).
Setup component scoped variables to contain previous timestamps, as
private prevTime: number;
private intervalLimit: number = 4000;
Supposing data$ is your input plane position data stream:
data$.pipe(filter(data => {
const now: number = Date.now();
const diff = now - this.prevTime;
if (this.isLiveMovementEnabled) {
// no throttle - pass every update, but prepare for disabling too
// record when we last allowed an update & allow the update
this.prevTime = now;
return true;
} else if (diff > intervalLimit) {
// we are throttling results, but this one gets through!
this.prevTime = now;
return true;
} else {
// we're throttling, and we're in the throttle period. eat the result!
return false;
}
}
Something like that gives you full control over the logic used whenever data comes in. You can add other operations like takeUntil and distinctUntilChanges into the pipe and trust that when you subscribe you'll be getting updated when you want them.
You can even adjust the intervalLimit to dynamically adjust the throttle period on the fly.

Angular 2 Rendering Performance in a Select

We have 2 lists, a short one and a large one. The large one loads the date based on the selection in the short one.
In the example, most of the elements in the large list are selected (380 out of 400) initially. After a new selection is made in the short list, data in the large list should be cleared and loaded again.
Now the difference lies in the
// await this.delayExecution(1);
line in the parent component. Uncommenting await (even with 1 ms) changes the execution flow in a way that the second list reacts immediately.
The transpiled code JavaScript:
ParentComponent.prototype.selectionChanged = function (data) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
console.log('waiting');
this.dataSelectedLarge = [];
// await this.delayExecution(1);
this.dataToSetLarge = [];
console.log('changed');
return [2 /*return*/];
});
});
};
And uncommented:
ParentComponent.prototype.selectionChanged = function (data) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
console.log('waiting');
this.dataSelectedLarge = [];
return [4 /*yield*/, this.delayExecution(1)];
case 1:
_a.sent();
this.dataToSetLarge = [];
console.log('changed');
return [2 /*return*/];
}
});
});
};
So clearing the selection
this.dataSelectedLarge = [];
and the data
this.dataToSetLarge = [];
without some kind of delay forces new rendering of the list which takes long time, while with await rendering happens in an instant.
The example is here: Angular 2 Performance Select
The questions are:
why causes this behavior
what would be the proper implementation
In the real app, we use ChangeDetectionStrategy.OnPush in the child component.
The performance problem seems to be the removal of all data AND the selection at the same time, meaning in the same Change Detection Cycle.
After running some experiments with zones.jsan NgZone I figured out, that the only difference is an extra Change Detection between this.dataSelectedLarge = []; and this.dataToSetLarge = [];.
By removing the Selection, forcing the Change Detection using injected ChangeDetectorRef.detectChanges(); and removing the Data after that, view gets update immediately and there is not need for async/wait.
Finally, the method is as simple as that
selectionChanged(data: any) {
this.dataSelectedLarge = [];
this.chdRef.detectChanges();
this.dataToSetLarge = [];
}

More than one state property change in redux

I was using redux to check how it scales with my application. There are few things, that I found as roadblock when I was using it.
There are high possibility that I am not thinking the redux way / not using the way it was supposed to be used or have not read the doc properly.
I have read the basic section this docs.
The problem statement is fairly simple.
I have two property in store
{
x: 10,
y: 20
}
lets say x is the x-position of the point and y is the y-position of the point. There is one condition if the x goes above 50, y value becomes equal to x.
So I have this
let x = (state = 10, action ) => {
if (action.type === 'cx') {
return action.value;
}
else {
return state;
}
}
let y = (state = 20, action ) => {
if (action.type === 'cy') {
return action.value;
}
else {
return state;
}
}
let model = redux.combineReducers({x: x, y: y});
let store = redux.createStore(model);
let actions = {
changeX: (val) => {
if ( val > 50) {
store.dispatch(actions.changeY(val));
}
return {type: 'cx', value: val }
},
changeY: (val) => ({type: 'cy', value: val })
}
console.log('INITIAL STATE', '---', store.getState());
store.subscribe(() => {
console.log('SUB2', '---', store.getState());
// Paint the dom with new state
});
So the moment
store.dispatch(actions.changeX(60));
is called the subscriber's function gets called twice hence the two times dom painting happen.
Is there a redux-way / workaround to solve this?
You are trying to relate to x and y as part of the same sub model equation - when one is updated, the other maybe updated also.
Using combineReducer you can update related state in the same reducer.
According to Redux guide, if you want that states to be separated, sometimes combineReducer is not enough, and you can breach that pattern into more openly reducer.
The combineReducers utility included with Redux is very useful, but is
deliberately limited to handle a single common use case: updating a
state tree that is a plain Javascript object, by delegating the work
of updating each slice of state to a specific slice reducer. It does
not handle other use cases, such as a state tree made up of
Immutable.js Maps, trying to pass other portions of the state tree as
an additional argument to a slice reducer, or performing "ordering" of
slice reducer calls. It also does not care how a given slice reducer
does its work.
The common question, then, is "How can I use combineReducers to handle
these other use cases?". The answer to that is simply: "you don't -
you probably need to use something else". Once you go past the core
use case for combineReducers, it's time to use more "custom" reducer
logic, whether it be specific logic for a one-off use case, or a
reusable function that could be widely shared. Here's some suggestions
for dealing with a couple of these typical use cases, but feel free to
come up with your own approaches.
An example that is given related for this case:
function combinedReducer(state, action) {
switch(action.type) {
case "A_TYPICAL_ACTION" : {
return {
a : sliceReducerA(state.a, action),
b : sliceReducerB(state.b, action)
};
}
case "SOME_SPECIAL_ACTION" : {
return {
// specifically pass state.b as an additional argument
a : sliceReducerA(state.a, action, state.b),
b : sliceReducerB(state.b, action)
}
}
case "ANOTHER_SPECIAL_ACTION" : {
return {
a : sliceReducerA(state.a, action),
// specifically pass the entire state as an additional argument
b : sliceReducerB(state.b, action, state)
}
}
default: return state;
}
}

How to handle multiple events at once, where one rules over the other

I'm trying to handle multiple states of a Counter-Strike events. I basically need to overrule some of the rules. Here are some examples of functions that could be triggered:
If I shoot someone
If I shoot someone, but I win the round, the round won should overrule my shot someone
If I shoot someone in the head, it should overrule shot someone, but round won should overrule headshot
Other than that, I need to be able to cancel some events that are occuring at the moment. Basically say STOP EVERYTHING AND DO THIS INSTEAD. Let's imagine the code is hosted on my Raspberry Pi and I can control LEDs using my NodeJS code (basically what I am doing). I need to be able to stop current events, so the LED doesn't keep blinking a color, if something else happens instead. Here is a mockup of what I am trying to accomplish:
var t_won = false;
var ct_won = false;
var bomb_planted = false;
var kills = 0;
server.on("update", function(data) {
if(data.round.win_team == "T" && t_won == false) {
t_won = true;
blinkLED("red", 5);
} else if(data.round.win_team == "CT" && ct_won == false) {
ct_won = true;
blinkLED("blue", 5);
} else if(data.round.bomb == "planted" && bomb_planted == false) {
bomb_planted = true;
blinkLED("red", 40);
} else if(data.round.bomb == "defused" && bomb_planted == true) {
LED.stop();
//ct_won(); or whatever
}
});
function blinkLED(color, time) {
//time = time in seconds
LED.blink(color, time);
}
Does that make sense? It's basically pseudo code, but it works the same way. If the LED is blinking for 40 seconds (because the bomb has been planted), I need to be able to stop it, so I can trigger ct_won instead. I don't want to make all these variables hardcoded into the app, because that seems very bad.
Many of these can appear at the same time. The data.round.bomb == "defused" and data.round.win_team == "CT" are basically both true at the same time, so a big if-else-if chunk of code doesn't seem very wise now that I think about it.
Is something like this possible? It would be fine to have some kind of order system, so I can give the most important one order: 1, the second most important one order: 2 and so on.
It sounds like you are thinking about implementing a rules engine like this one https://www.npmjs.com/package/json-rules-engine. This would be fun to play with, but from your example I do not believe it would be beneficial. Best would be to just try to simplify your code a bit if possible and stick with plain JavaScript code.
If you notice I have done a few things to clean up the ifs. One is to separate detecting new state, another using the same property names, also breaking winner and bomb indications into their own function, also getting led color from team object, and return instead of else if. This is is equivalent to the ifs but more readable.
let state = { won: null, bomb: null };
let team = { terrorist: 'red', counterTerrorist: 'blue' }
function detectNewState(data) {
let newState = {};
for (key in state)
if (state[key] != data[key]) {
newState[key] = data[key];
state[key] = data[key];
}
return newState;
}
function indicateWinner(won) {
blinkLED(team[won], 5);
}
function indicateBomb(bomb) {
blinkLed(team[bomb], 40);
}
function update(data) {
const {won, bomb} = detectNewState(data);
if (won) return indicateWinner(won);
if (bomb) return indicateBomb(bomb);
});
server.on('update', update);

RxJS, how to poll an API to continuously check for updated records using a dynamic timestamp

I am new to RxJS and I am trying to write an app that will accomplish the following things:
On load, make an AJAX request (faked as fetchItems() for simplicity) to fetch a list of items.
Every second after that, make an AJAX request to get the items.
When checking for new items, ONLY items changed after the most recent timestamp should be returned.
There shouldn't be any state external to the observables.
My first attempt was very straight forward and met goals 1, 2 and 4.
var data$ = Rx.Observable.interval(1000)
.startWith('run right away')
.map(function() {
// `fetchItems(modifiedSince)` returns an array of items modified after `modifiedSince`, but
// I am not yet tracking the `modifiedSince` timestamp yet so all items will always be returned
return fetchItems();
});
Now I'm excited, that was easy, it can't be that much harder to meet goal 3...several hours later this is where I am at:
var modifiedSince = null;
var data$ = Rx.Observable.interval(1000)
.startWith('run right away')
.flatMap(function() {
// `fetchItems(modifiedSince)` returns an array of items modified after `modifiedSince`
return fetchItems(modifiedSince);
})
.do(function(item) {
if(item.updatedAt > modifiedSince) {
modifiedSince = item.updatedAt;
}
})
.scan(function(previous, current) {
previous.push(current);
return previous;
}, []);
This solves goal 3, but regresses on goal 4. I am now storing state outside of the observable.
I'm assuming that global modifiedSince and the .do() block aren't the best way of accomplishing this. Any guidance would be greatly appreciated.
EDIT: hopefully clarified what I am looking for with this question.
Here is another solution which does not use closure or 'external state'.
I made the following hypothesis :
fetchItems returns a Rx.Observable of items, i.e. not an array of items
It makes use of the expand operator which allows to emit values which follow a recursive relationship of the type x_n+1 = f(x_n). You pass x_n+1 by returning an observable which emits that value, for instance Rx.Observable.return(x_n+1) and you can finish the recursion by returning Rx.Observable.empty(). Here it seems that you don't have an ending condition so this will run forever.
scan also allows to emit values following a recursive relationship (x_n+1 = f(x_n, y_n)). The difference is that scan forces you to use a syncronous function (so x_n+1 is synchronized with y_n), while with expand you can use an asynchronous function in the form of an observable.
Code is not tested, so keep me updated if this works or not.
Relevant documentation : expand, combineLatest
var modifiedSinceInitValue = // put your date here
var polling_frequency = // put your value here
var initial_state = {modifiedSince: modifiedSinceInitValue, itemArray : []}
function max(property) {
return function (acc, current) {
acc = current[property] > acc ? current[property] : acc;
}
}
var data$ = Rx.Observable.return(initial_state)
.expand (function(state){
return fetchItem(state.modifiedSince)
.toArray()
.combineLatest(Rx.Observable.interval(polling_frequency).take(1),
function (itemArray, _) {
return {
modifiedSince : itemArray.reduce(max('updatedAt'), modifiedSinceInitValue),
itemArray : itemArray
}
}
})
You seem to mean that modifiedSince is part of the state you carry, so it should appear in the scan. Why don-t you move the action in do into the scan too?. Your seed would then be {modifiedSince: null, itemArray: []}.
Errr, I just thought that this might not work, as you need to feed modifiedSince back to the fetchItem function which is upstream. Don't you have a cycle here? That means you would have to use a subject to break that cycle. Alternatively you can try to keep modifiedSince encapsulated in a closure. Something like
function pollItems (fetchItems, polling_frequency) {
var modifiedSince = null;
var data$ = Rx.Observable.interval(polling_frequency)
.startWith('run right away')
.flatMap(function() {
// `fetchItems(modifiedSince)` returns an array of items modified after `modifiedSince`
return fetchItems(modifiedSince);
})
.do(function(item) {
if(item.updatedAt > modifiedSince) {
modifiedSince = item.updatedAt;
}
})
.scan(function(previous, current) {
previous.push(current);
return previous;
}, []);
return data$;
}
I have to run out to celebrate the new year, if that does not work, I can give another try later (maybe using the expand operator, the other version of scan).
How about this:
var interval = 1000;
function fetchItems() {
return items;
}
var data$ = Rx.Observable.interval(interval)
.map(function() { return fetchItems(); })
.filter(function(x) {return x.lastModified > Date.now() - interval}
.skip(1)
.startWith(fetchItems());
That should filter the source only for new items, plus start you off with the full collection. Just write the filter function to be appropriate for your data source.
Or by passing an argument to fetchItems:
var interval = 1000;
function fetchItems(modifiedSince) {
var retVal = modifiedSince ? items.filter( function(x) {return x.lastModified > modifiedSince}) : items
return retVal;
}
var data$ = Rx.Observable.interval(interval)
.map(function() { return fetchItems(Date.now() - interval); })
.skip(1)
.startWith(fetchItems());

Categories