1

I've been trying to teach myself FRP (and bacon.js specifically) by diving in head first on a new project. I've gotten pretty far on my own but recently ran into a problem that I can't seem to fight my way through:

I have an interface with a set of clickable objects. When an object is clicked, detailed information for that object is loaded in a panel to the right.

What I need is the ability to select multiple, to accumulate those objects into an array and show a "bulk actions" panel when more than one is selected.

So far I have:

  • a SelectMultiple boolean property that represents the current UI mode
  • a CurrentObject stream that holds the currently selected object

I've gotten somewhat close with this:

var SelectedObjects = CurrentObject.filter(SelectMultiple).skipDuplicates().scan([], function(a,b){
    return a.concat([b]);
};

There are a few problems:

  1. The value of SelectedObjects represents the objects selected over all time, it doesn't reset when SelectMultiple state changes.
  2. The value of SelectObjects does not include the original CurrentObject (of course because the scan accumulator seed is an empty array, not the current value of CurrentObject).

The fact that I'm looking to use the current value of a property directly seems to be a hint that there's a fundamental issue here. I have a notion that the answer involves flapMapLatest and spawning a new stream every time SelectMultiple changes, funneling selected orders into this new stream and accumulating, but I can't quite work out what that should look like.

Of course there is an additional problem that skipDuplicates only skips consecutive duplicates. I can probably work this one out on my own but a solution that addresses that issue would be ideal.

Any suggestions would be greatly appreciated!

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
disantlor
  • 43
  • 4

2 Answers2

2

This might work (coffeescript):

var selectMultiple # Property[Boolean] - whether in multiselect mode
var selectedObject # Property[Object] - latest selected object
var selectedObjects = selectMultiple.flatMapLatest((multiple) ->
  if !multiple
    selectedObject.map((obj) -> [obj])
  else
    selectedObject.scan([], (xs, x) ->
      xs.concat(x)
    )
).toProperty()

On each value of selectMultiple flag we start a new stream that'll either just track the current single selection or start accumulating from the single selection, adding items as they're selected. It doesn't support de-selection by toggling, but that's straightforward to add into the scan part.

raimohanska
  • 3,265
  • 17
  • 28
0

Ok I figured out a solution. I realized that I could use a dynamically-sized slidingWindow combinator. I found the basis for the answer in the Implementing Snake in Bacon.js tutorial.

I got an error when I tried adding directly to the Bacon prototype (as described in the tutorial) so I just made a function that takes the stream to observe and a boolean that determines if it should capture values:

slidingWindowWhile = function(sourceStream, toTakeOrNotToTake) {
    return new Bacon.EventStream(function(sink){
        var buf = [];
        var take = false;

        sourceStream.onValue(function(x){
            if (! take) {
                buf = [];
            }
            buf.push(x);
            sink(new Bacon.Next(buf));
        });

        toTakeOrNotToTake.onValue(function(v){
            take = v;
        });
    });
};

It still seems like there should be a way to do this without using local variables to track state but at least this solution is pretty well encapsulated.

disantlor
  • 43
  • 4