Self-resetting circular flow in wolkenkit - wolkenkit

I have a question about stateful flows, I'll try to break it down to a really simple example:
I am using different states, lets say pristine, add, and calculate.
My state consists of an int that I want to add to, and after I added to it twice, I want to calculate the doubled amount, then set it to pristine again.
So now I'll start off with a pristine flow, int = 0. Inside my transitions I react to a addNumber event, take an int out of the event data and add it to my state, then I transitionTo('add').
I repeat this, add another number and transitionTo('calculate').
Inside my reactions I now react to the transition between add and calculating and set the new State to double the amount of the int.
What i want to do now is work with that number, for example send a command containing it, and then reset the state and transitionTo('pristine').
The problem is: I can't set the state neither can I transition.
How do you tackle this?
Here you have the actual code so that it becomes a bit more understandable:
'use strict';
const identity = {
'usermgmt.user.presentSwitched': event => event.user.id,
'usermgmt.user.pauseSwitched': event => event.user.id
};
const initialState = {
is: 'pristine',
present: false,
pause: false,
presentSince: null,
presentUntil: null,
pauses: [],
};
const transitions = {
pristine: {
'usermgmt.user.presentSwitched' (flow, event) {
flow.setState({
present: true,
presentSince: event.data.timestamp
});
flow.transitionTo('present');
}
},
present: {
'usermgmt.user.presentSwitched' (flow, event) {
flow.setState({
present: false,
presentUntil: event.data.timestamp
});
flow.transitionTo('calculating');
},
'usermgmt.user.pauseSwitched' (flow, event) {
const newPause = {pauseSince: event.data.timestamp};
flow.setState({
pause: true,
pauses: [...flow.state.pauses, newPause]
});
flow.transitionTo('pause');
}
},
pause: {
'usermgmt.user.pauseSwitched' (flow, event) {
const pauses = Object.create(flow.state.pauses);
pauses[flow.state.pauses.length - 1].pauseUntil = event.data.timestamp;
flow.setState({
pause: false,
pauseSince: event.data.timestamp,
pauses
});
flow.transitionTo('present');
}
}
};
const reactions = {
present: {
'calculating' (flow, event, services) {
const {app, logger} = services;
const workDayId = 'kek';
const from = flow.state.presentSince;
const to = flow.state.presentUntil;
const pauses = flow.state.pauses;
logger.info(JSON.stringify(flow));
app.keksing.recording().createRecording({workDayId, from, to, type: 'working'});
flow.setState({
present: false,
pause: false,
presentSince: null,
presentUntil: null,
pauses: [],
});
flow.transitionTo('pristine');
}
}
};
module.exports = { identity, initialState, transitions, reactions };
https://gist.github.com/DrFelder/122a72ffed3eb239a1a3ae33c99ea00d

Basically, there are two thoughts on this:
The first option is to question whether you should use a flow at all here. Maybe you could model this as an aggregate, with commands such as startWork, endWork, pause and resume, with the desired state, and then you could have the logic you want to have inside of that aggregate. You could either use a single aggregate per person, or a single aggregate per presence block. Either way, this should work. So, to put this in different words: Is there a special reason why you want to implement this as a flow, and not as an aggregate?
The second option is not to differ between pristine and calculating. Because, what you effectively want to have is some kind of a circle, but your flow isn't modelled as a circle. So if instead of finally transitioning to calculating you transitioned back to pristine, you would be in the desired state, and inside of the reaction of present->pristine, you could do the calculation you want to and send a command with the calculated data (which, actually, is the reaction to the transition).
Does this help to clarify things a little bit?
PS: Maybe it would also help to be more explicit in naming, e.g. to have paused and resumed instead of pauseSwitched. This would require less logic to figure out the intent of what has happened, and that's basically one of the strengths of DDD, to have the ability to be explicit in wording. Because, as it is named right now, it is more some kind of an updated event (which should be avoided).

Related

How to prevent initial onFocus trigger on TextField during first composition

Is there any way I can prevent onFocus trigger during the initial laying out of a composition?,
.onFocusChanged {
// initial pass triggers un-necessary onFocus callback with just `false` values
Log.e("TextFieldFocusChanged", "${it.isFocused} : ${it.hasFocus}")
}
I have found this Google issue tracker that seems related to such behavior, though the issue is not exactly related to an initial pass of a composition.
This thing can be solved by specifying some boolean flag depending on your needs, but handling it this way slowly introduces complex boolean evaluations depending on your use-case. Is there any way I can configure a TextField to prevent onFocus callback on initial pass?
Thank you in advance.
There must be a more efficient way to handle this but the most optimal approach I can do for now is to rely on SideEffect.
#Composable
fun ScreenTextFields() {
//..TextField ... onFocusChanged { onTextFieldFocus(it) }...
//..TextField ... onFocusChanged { onTextFieldFocus(it) }...
//..TextField ... onFocusChanged { onTextFieldFocus(it) }...
//..TextField ... onFocusChanged { onTextFieldFocus(it) }...
SideEffect {
// notify and update a boolean state (e.g onFieldsPostComposition)
}
}
and in a state class.
fun onTextFieldFocus(focusState: FocusState) {
if (onFieldsPostComposition) {
// do intended use-case on actual focus changes
}
}
This way, any initial onFocus callback can be ignored/skip for your intended logic, the only part I'm not sure if onFocusChanged { ... } callback is part of the composition/re-composition, not sure if there's a chance that SideEffect will occur first before onFocus callback, but so far, this approach works on my case..

How does this function keep track of clicks?

On the "Thinking in Compose" page I don't get this code, how does $clicks keep track of number of clicks?
#Composable
fun ClickCounter(clicks: Int, onClick: () -> Unit) {
Button(onClick = onClick) {
Text("I've been clicked $clicks times")
}
}
I'm learning Kotlin at the same time as Compose, so I get puzzled all the time.
It's omitted in that example but it should store the click-count in a MutableState<T> wrapped with remember
var clickCount by remember { mutableStateOf(0)}
ClickCounter(clicks = clickCount, onClick = {clickCount += it})
For real real real beginner like me, let me add my comment and code.
When the ClickCounter composable is called we need to pass two parameters.
To 'clicks', we pass initial value 0 which will be changed everytime we click the button. Also the initial value need to be 'remembered' and tracked for further changes. So we create new variable using 'mutableStateOf' function inside 'remember' function.
To 'onClick', we need to pass function which adds 1 everytime the button is clicked.
As a caller of the ClickCounter composable, I made a simple preview composable as below.
#Preview(showBackground = true)
#Composable
fun ClickCounterPreview(){
var clicks by remember { mutableStateOf(0) }
// We call ClickCounter composable passing two parameters.
ClickCounter(clicks = clicks, onClick = { clicks += 1 })
}

How to add a click event on webix gantt task

I use the first time webix gantt. Before, is used dhtmlx gantt (developed from the same company),
On dhtmlx gantt, there was onTaskClick event. But on webix gantt, this event is missing?
Does someone knows how to add a onTaskClick event?
There is no such event in Webix Gantt, but that's not a problem.
If you want to track selection of tasks, use $observe on the reactive state for the selected property:
$$("gtt").getState().$observe("selected", id => {
console.log(id);
});
If you need any clicks, you need to do a bit more, but it's possible. There's no private code in Gantt, each part of the UI is defined as an ES6 class. So to change something in the UI or its behavior, you need to redefine some classes.
Gantt chart is reachable as gantt.views["chart/bars"]. Let's redefine it and add a global app event:
class bars extends gantt.views["chart/bars"] {
/**
* Sets action of bar item click
* #param {(string|number)} id - ID of the clicked item
*/
ItemClickHandler(id) {
super.ItemClickHandler(id);
// the check is optional, leave it if
// you do not want to track
// clicks on group items in
// display:"resources"
if (!this.Bars.getItem(id).$group) {
this.app.callEvent("onTaskClick", [id]);
}
}
}
Next, apply the override:
webix.ui({
view: "gantt",
id:"gtt",
url: "https://docs.webix.com/gantt-backend/",
override: new Map([
[gantt.views["chart/bars"], bars]
])
});
Now you can track the event like:
gantt1.$app.attachEvent("onTaskClick", id => {
webix.message("Clicked " + id);
});
If you would rather have this event on gantt obj itself, not on its $app property, you can "channel" the event:
webix.protoUI({
name:"my-gantt",
$init: function(){
this.$app.attachEvent("app:task:click", id => {
this.callEvent("onTaskClick", [id]);
});
}
}, webix.ui.gantt);
webix.ui({
view: "my-gantt",
id:"gtt",
url: "https://docs.webix.com/gantt-backend/",
override: new Map([
[gantt.views["chart/bars"], bars]
])
});
...and later listen to it like a usual Webix widget event:
gantt1.attachEvent("onTaskClick", id => {
webix.message("Clicked " + id);
});
The overall example is https://snippet.webix.com/p0sardik
If you also want to track Tree clicks, you will need to override gantt.views.tree in a similar way (redefine the ItemClickHandler method).

OpenLayers 3: Sync/Unsync Different Maps Events

Let's say I have N different maps on the screen, I want to be able based on a condition, let s say a value coming from a checkbox or a different control to be able to sync all the map events (zoom/pan/rotation) and unsync them accordingly.
Can this be achieved in OpenLayers 3 using native code?
You can sync maps by listen to their events and setting their changes to the other maps. I wrote a working example, using this as the listener for each map's view's change:center, change:resolution and change:rotation event:
function(evt){
var type = evt.type.split(':');
if (type[0] === 'change' && type.length === 2){
var attribute = type[1];
maps.forEach(function(map){
var value = evt.target.get(attribute);
if (map.getView().get(attribute) !== value){
map.getView().set(attribute, value);
}
});
}
}
I'm working on a project with fairly heavy WMS layers and hit a problem with Alvin's solution. For the first map OL waits for any interaction to end before requesting an updated WMS layer. But for the second map it appears to treat each step triggered by the function as a separate interaction and therefore generates a separate WMS layer request at each step. In my case this was seriously affecting performance.
I've come up with a compromise solution that waits for interaction to end and then animates the second map to match the first. This is a first attempt and could probably be improved:
// attributes to synchronise between maps
var synchedAttributes = ['resolution', 'center']
// call this on moveend
var syncPanZoom = function(maps, evt){
maps.forEach(function(map){
synchedAttributes.forEach(function(attribute) {
var value = evt.target.getView().get(attribute);
if (map.olmap.getView().get(attribute) !== value){
var animateObject = {};
animateObject[attribute] = value;
animateObject.duration = 500;
map.olmap.getView().animate(animateObject)
}
});
});
};

Drag and Drop with Angular JS and JQuery

Couple of days ago I found this interesting post at http://www.smartjava.org/content/drag-and-drop-angularjs-using-jquery-ui and applied it into my website. However when I progressively using it there is a bug I identified, basically you can not move an item directly from one div to another's bottom, it has to go through the parts above and progress to the bottom. Anyone can suggest where does it goes wrong? The example is at http://www.smartjava.org/examples/dnd/double.html
Troubling me for days already.....
I did this a bit differently. Instead of attaching a jquery ui element inside the directive's controller, I instead did it inside the directive's link function. I came up with my solution, based on a blog post by Ben Farrell.
Note, that this is a Rails app, and I am using the acts_as_list gem to calculate positioning.
app.directive('sortable', function() {
return {
restrict: 'A',
link: function(scope, elt, attrs) {
// the card that will be moved
scope.movedCard = {};
return elt.sortable({
connectWith: ".deck",
revert: true,
items: '.card',
stop: function(evt, ui) {
return scope.$apply(function() {
// the deck the card is being moved to
// deck-id is an element attribute I defined
scope.movedCard.toDeck = parseInt(ui.item[0].parentElement.attributes['deck-id'].value);
// the id of the card being moved
// the card id is an attribute I definied
scope.movedCard.id = parseInt(ui.item[0].attributes['card-id'].value);
// edge case that handles a card being added to the end of the list
if (ui.item[0].nextElementSibling !== null) {
scope.movedCard.pos = parseInt(ui.item[0].nextElementSibling.attributes['card-pos'].value - 1);
} else {
// the card is being added to the very end of the list
scope.movedCard.pos = parseInt(ui.item[0].previousElementSibling.attributes['card-pos'].value + 1);
}
// broadcast to child scopes the movedCard event
return scope.$broadcast('movedCardEvent', scope.movedCard);
});
}
});
}
};
});
Important points
I utilize card attributes to store a card's id, deck, and position, in order to allow the jQuery sortable widget to grab onto.
After the stop event is called, I immediately execute a scope.$apply function to get back into, what Misko Hevery call,s the angular execution context.
I have a working example of this in action, up in a GitHub Repo of mine.

Resources