One of these issues has a straightforward answer; unfortunately the other one doesn't.
I'll start with the easy one - the fact that
ractive.data.messages[ "stream_id1" ].stream[1].message
ractive.data.messages[ "stream_id2" ].stream[1].message
belong to the same DOM element. You're correct in that Ractive updates the existing elements rather than removing them and creating new ones - this is a core part of its design. In this case that's undesirable behaviour, but you can work around it like so:
// instead of immediately switching to a new stream ID like this...
ractive.set( 'current_stream_id', 'stream_id2' );
// you can set it to a non-existent ID. That will cause the existing DOM
// to be removed. When you set it to an ID that *does* exist, new DOM
// will be created:
ractive.set( 'current_stream_id', null );
ractive.set( 'current_stream_id', 'stream_id2' );
// or, if you'd like the initial transitions to complete first...
ractive.set( 'current_stream_id', null ).then(function () {
ractive.set( 'current_stream_id', 'stream_id2' );
});
The other issue - that merge()
isn't merging, but is instead behaving as though you were doing ractive.set('messages.stream_id1.stream', new_message_stream)
- is tougher. The problem is that while you and I know that {{#messages[ current_stream_id ]}}
equates to messages.stream_id1
when current_stream_id === 'stream_id1
, Ractive doesn't.
What it does know is that we have an expression whose value is determined by messages
and current_stream_id
. When the value of either of those references changes, the expression is re-evaluated, and if that value changes, the DOM gets updated - but using a standard set()
. When you do ractive.merge('messages.stream_id1.stream', ...)
, Ractive updates all the things that depend on keypaths that are 'upstream' or 'downstream' of messages.stream_id1.stream
- which includes messages
. So that's how the expression knows that it needs to re-evaluate.
It's possible that a future version of Ractive will be able to handle this case in a smarter fashion. Perhaps it could make a note of arrays that are subject to merge operations, and check evaluator results to see if they're identical to one of those arrays, and if so use merge()
rather than set()
. Perhaps it could analyse the function in some way to see if the {{#messages[ current_stream_id ]}}
section should register itself as a dependant of messages.stream_id1
for as long as current_stream_id === 'stream_id1'
, rather than the internally-generated ${messages-current_stream_id-}
keypath.
None of that helps you in the meantime though. The only way to use merge()
in your current situation is to have a separate reference that doesn't use an expression, and a bit of magic with pattern observers:
main_template:
{{#current_messages}} <!-- rather than `messages[ current_stream_id ]` -->
{{>render_message_stream}}
{{/current_messages}}
render_message_stream:
{{#current_message_stream}} <!-- rather than `stream` -->
<div class="stream">
{{>render_message}}
</div>
{{/current_message_stream}}
code:
ractive.observe( 'current_stream_id', function ( id ) {
var current_messages = this.get( 'messages.' + id );
this.set( 'current_messages', current_messages );
// hide existing stream, then show new stream
this.set( 'current_message_stream', null ).then(function () {
this.set( 'current_message_stream', current_messages.stream );
});
});
// when ANY message stream changes, we see if it's the current one - if so, we
// perform a merge on the top-level `current_message_stream` array
ractive.observe( 'messages.*.stream', function ( new_stream, old_stream, keypath, id ) {
// the value of any * characters are passed in as extra arguments, hence `id`
if ( id === this.get( 'current_stream_id' ) ) {
this.merge( 'current_message_stream', new_stream, {
compare: function ( item ) {
return item.id;
}
});
}
});
I've set up a JSFiddle demonstrating this. I hope it makes sense, let me know if not - and sorry I didn't get round to answering this question much sooner.