Distinct between possible streams, looking for a clean solution
I'm trying to use RxJS in some GUI scenarios. I came across an interesting case. I have a widget where one can view, edit and create entities.
When you click on the "AddNewEntity" Button. The editwidget creates an empty entity, loads it and changes into edit mode. However, if you are already in edit mode, it kindly askes if you like to revert changes first and once you clicked "yes" the same happens as described earlier.
So I thought Rx might help me with that. Here is the code.
Rx.Observable.Merge([
editWidget.getObservable('AddNewEntityButtonClicked')
.Where(isNotInEditMode),
editWidget.getObservable('AddNewEntityButtonClicked')
.Where(isInEditMode)
.Select(function (id) {
return dialogWidget.question("Reject Changes?", "You are in edit mode. Reject Changes?")
.Where(function (answer) { return answer === true; });
})
.Switch()
])
.Subscribe(self, function () {
var entity = createNewEntity();
editWidget.loadEntity(currentEntity);
editWidget.setEditMode();
});
Basically I'm merging two streams. One stream of clicks to the button which is filtered by the state of the widget where the state is "NotInEditMode". And another stream of clicks to the button that is filtered to the opposite state plus is projected into the return value stream of the dialog. Notice that the return value of the dialog is an AsyncSubject of bool which represents the given answer.
Now the tricky part! It doesnt work this way! Why? Because when the state is "NotInEditMode", the first stream matches, it sets the widget into edit mode and now the second stream (which runs afterwards because of the order inside the merge) will also match which basically results into a completly inconsitent state (unlocked edit mode plus open dialog).
I found two ways to fix it. The first one, change the order inside the merge so that it looks like this:
Rx.Observable.Merge([
editWidget.getObservable('AddNewEntityButtonClicked')
.Where(isInEditMode)
.Select(function (id) {
return dialogWidget.question("Reject Changes?", "You are in edit mode. Reject Changes?")
.Where(functio开发者_运维百科n (answer) { return answer === true; });
})
.Switch(),
editWidget.getObservable('AddNewEntityButtonClicked')
.Where(isNotInEditMode)
])
.Subscribe(self, function () {
var entity = createNewEntity();
editWidget.loadEntity(currentEntity);
editWidget.setEditMode();
});
However, I dislike this solution. It's not obvious to the reader.
Fortunatly, I found another solution:
Rx.Observable.Merge([
editWidget.getObservable('AddNewEntityButtonClicked')
.Where(isNotInEditMode),
editWidget.getObservable('AddNewEntityButtonClicked')
.Where(isInEditMode)
.Select(function (id) {
return dialogWidget.question("Reject Changes?", "You are in edit mode. Reject Changes?")
.Where(function (answer) { return answer === true; });
})
.Switch()
])
.Take(1)
.Repeat()
.Subscribe(self, function () {
var entity = createNewEntity();
editWidget.loadEntity(currentEntity);
editWidget.setEditMode();
});
The idea behind is that there can only be one way to go so the first matching scenario should abort all others.
However, I wonder if there might be a cleaner solution or if I'm trying to use Rx for things it wasnt designed for ;-)
I think I found a clean solution:
editWidget.getObservable('NewButtonClicked')
.Select(function () {
return isInEditMode() ? dialogWidget.question("Reject Changes", "You are in edit mode. Reject Changes?)
.Where(function (answer) { return answer === true; }) : Rx.Observable.Return(true);
})
.Switch()
.Subscribe(function(){
currentEntity = options.createNewEntity();
editWidgetLoadEntity(currentEntity);
editWidget.enable();
});
精彩评论