Search code examples
bacon.js

Use combineAsArray on Changing Set of Streams in BaconJS


Already found questions- how should I keep a running total of several values with bacon.js?

I've already found the above question, which deals with a static set of properties. However, I'm interested in a variation, wherein the set of properties is dynamic.

My specific case is that I have a set of things which have editable costs, forming an observable Property. From these, I combine the properties with combineWith to get the total cost for ALL events. (Code at end)

Right now, I've got the properties hooked up properly for a static set of events using jQuery to get the right values, but the idea is that the user could add (or delete) an event arbitrarily and the calculated total will be updated accordingly.

I have a vague inkling that the tool for the job is combineAsArray and some clever usage of event listening at the common parent for all of my streams, or possibly some sort of Bacon.Bus (dynamically plugging streams into it?), but I'm really spinning my wheels now...

The (Pseudo)Code

<table>
  <tr>
    <td><input id="thing1" value="40"></input></td>
  </tr>
  <tr>
    <td><input id="thing2" value="40"></input></td>
  </tr>
</table>


$(function() {
  stream1 = $("#input1").asEventStream("change blur keyup").toProperty();
  stream2 = $("#input2").asEventStream("change blur keyup").toProperty();
  totalMonthlyCost = Bacon.combineWith(
    sumAll,
    [stream1, stream2]
  );
});

Solution

  • The key to combining a changing set of Properties is flatMapLatest. You can start by representing the current set of active inputs as

    inputsP = ...
    

    From that, you can map to the current set of Properties to combine like

    propertiesP = inputP.map(i => i.asEventStream("change").map(() => i.value()).toProperty())
    

    This is a Property whose values are Arrays of Properties, or in Haskellish, you can call it a Property (Array Property). You can flatten that into a Property Array, or a Property containing arrays of values, by simply

    valuesP = propertiesP.flatMapLatest(Bacon.combineAsArray).toProperty()
    

    And finally, apply your sumAll function as in

    valuesP.map(sumAll)
    

    ... assuming that sumAll consumes an Array of values as its sole argument.

    One might, of course, argue that there are simpler ways to achieve the same result. Like listening to all interesting input events on the whole container element and grepping the input values from the container using JQuery, feeding that stuff to sumAll. Like

    $container.asEventStream("change blur keyup", "input")
      .map(() => $container.find("input").toArray().map(() => $(this).value()))
      .map(sumAll)
    

    Or something along those lines. See also: http://baconjs.blogspot.fi/2013/02/chicken-egg-and-baconjs.html