I have 2 Bacon.jQuery Models
(but the problem can be illustrated with any Bacon Observable
).
I have 3 combo boxes: foo
, bar
and quux
. bar
depends on foo
and quux
depends on a combination of bar
and quux
. There is a findQuux
function which finds a value for quux
combobox from foo
and bar
.
If a user changes foo
combobox, the values in bar
and quux
boxes are selected. Here is a working implementation of quux
selection:
// WORKS
var quuxBus = new Bacon.Bus()
Bacon.onValues(fooModel, barModel, function (foo, bar)
{
quuxBus.push(findQuux(foo, bar))
})
quuxModel.addSource(quuxBus.toProperty('quux4'))
There is a little problem with this solution as I need 'quux4'
hack to set quux
correctly on page load. But the following simpler solution doesn't work at all:
// DOESN'T WORK
var quuxCombo = Bacon.combineWith(findQuux, fooModel, barModel)
quuxModel.addSource(quuxCombo)
The problem is that findQuux
expects a valid combination of foo
and bar
and crashes when fed with an impossible combination for which a quux
cannot be found.
The onValues/push
solution works because findQuux
is called only once when a user changes foo
combobox. The bacon.combineWith
solution doesn't work because findQuux
is called twice.
What is the recommended way to implement the data source for quuxModel
? Coding findQuux
defensively is not an option.
The full code can be found at http://jsfiddle.net/5zp4D/8/
Update: There are already sensible values for foo
and bar
from page load. As can be seen from the fiddle link fooModel
is initialized with an explicit value, and barModel
is recalculated from that initial value:
var fooModel = Bacon.$.selectValue(fooDom, 'foo2-value')
var barModel = Bacon.$.selectValue(barDom)
barModel.addSource(fooModel.map(function (x)
{
return json[x][1].val
}))
foo
and bar
never take invalid values. Moreover, bar
is repopulated once a user changes foo
, so only valid combinations are seen in the UI.
The problem with combineWith
approach is that when a user switches foo
then findQuux
function is called twice with intermediate values, and one of the intermediate combinations is incorrect, while the combination constituents are separately correct. I changed the fiddle to better illustrate the problem: http://jsfiddle.net/5zp4D/12/
As can be seen from var json
the valid combinations are 1-1, 1-2, 2-3, 2-4 and 2-5.
I uncommented the broken version, added logging. When you select foo1
and then select back foo2
in the first combobox, you get 4 messages in the log instead of 3:
On page load you see that quux
value is correctly initialized (2-4):
"valid combination of foo2-value and bar4-value"
When you select foo1
instead of foo2
you see that findQuuxDef
is called with 1-4 (foo
is new, bar
is old) and then with 2-4 (foo
is new, bar
is new):
"invalid combination of foo1-value and bar4-value"
"valid combination of foo1-value and bar2-value"
My problem is that invalid intermediate combinations don't happen with onValues/push
approach, and I'd like to know what is an idiomatic recommended approach to UI with linked elements.
I'd start with having
quuxCombo
only output valid values forfindQuux
. I'd prefer having sensible values for inputsfoo
andbar
from page load, but if that's not possible (why not?), I'd usefilter
on the inputs. For instance,I guess though, that you want to start with some initial value before valid input exists. For this, I added a new method
Property.startWith
into Bacon.js 0.6.15. So, now you can