rivets icon indicating copy to clipboard operation
rivets copied to clipboard

rv-if does not update children when model changes

Open diachedelic opened this issue 11 years ago • 19 comments

Any binding deeper than one level within an rv-if statement is not updated when the object representing the deep binding is replaced, ala:

<div rv-if="foo">
    { foo.bar }
</div>
var model = {
    foo: {
        bar: 'original'
    }
}

model.foo = {
    bar: 'updated' // not refreshed in view
}

http://jsfiddle.net/v001txq3/

diachedelic avatar Feb 22 '15 07:02 diachedelic

It seems that rivets can't handle when a bound object's property is totally replaced with a new one (the target property you are binding in the template) in this case model.foo completely replaced with a new object {bar: 'updated'}

so, if you changed only a property of the target object, this will work just fine: model.foo.bar = 'updated'

http://jsfiddle.net/h53tfahn/

AMTourky avatar Feb 23 '15 08:02 AMTourky

Yes I realise that, but I want to replace the whole object - you can do that anywhere except inside an 'rv-if' statement!

diachedelic avatar Feb 23 '15 08:02 diachedelic

This seems to be the case with arbitrary bindings (e.g. rv-text and rv-value: http://jsfiddle.net/v001txq3/4/)

diachedelic avatar Feb 23 '15 22:02 diachedelic

And the sub-elements of the rv-if element are updated otherwise: http://jsfiddle.net/v001txq3/6/

diachedelic avatar Feb 23 '15 22:02 diachedelic

Ah...the rv-if binding does not update its subelements at all: http://jsfiddle.net/v001txq3/8/

Is this how rv-if is supposed to work? Is there an alternative to rv-if that updates? I know there is rv-show but you can't do this

<div rv-show="foo">
    { foo.bar }
</div>
var model = {}

model.foo = {
    bar: 'hello'
}

because accessing model.foo.bar is an error when model.foo is undefined

diachedelic avatar Feb 23 '15 23:02 diachedelic

@diachedelic rivets' default adapter is a defineProperty based adapter so it listens the object it is provided at first binding time (at the rivets.bind phase). If you want to override the current object with new one you need to inform the view about model change with view.update (view object is returned from rivets.bind).

You can see the result here : http://jsfiddle.net/opdow1sb/ please double check and be sure about my finding.

Best Regards.

tayfunyugruk avatar May 08 '15 07:05 tayfunyugruk

Oh wow, yeah that works. But why is this only an issue for rv-if?

On 8 May 2015, at 5:25 pm, Tayfun YÜĞRÜK [email protected] wrote:

@diachedelic https://github.com/diachedelic rivets's default adapter is a defineProperty based adapter so it listens the object it is provided at first binding time (rivets.bind phaes). If you want to override the current object with new one you need to inform the view about model change with view.update (view object is returned from rivets.bind).

You can see the result here : http://jsfiddle.net/opdow1sb/ http://jsfiddle.net/opdow1sb/ please double check and be sure about my finding.

Best Regards.

— Reply to this email directly or view it on GitHub https://github.com/mikeric/rivets/issues/452#issuecomment-100131921.

diachedelic avatar May 08 '15 10:05 diachedelic

i think this is because each binders gets the reference of the sub object and listens that reference so completely replacing sub object affects the root view but not rv-if because rv-if has old reference of sub object.

var root = {
   sub : {
     key : value
   }
};

in this object sub object has a reference to key-value and rv-if listens that reference not whole object. and if you set a new object instance you change the reference.

root.sub = {
   key2 : value2
}

if i did not miss anything this is how default binding of objects work.

tayfunyugruk avatar May 08 '15 11:05 tayfunyugruk

#512 seems to be related

Namek avatar Feb 27 '17 15:02 Namek

It seems like rv-if just does not rebind, am I right?

http://jsfiddle.net/h53tfahn/

diachedelic avatar Mar 30 '17 10:03 diachedelic

@diachedelic what can you see in output? I see both "updated" which seems OK.

Namek avatar Apr 10 '17 12:04 Namek

Oops I forgot to fork that fiddle.

I have made a new one which shows the exact problem I'm having: rv-src is never rebound if there is an rv-if on the same element

http://jsfiddle.net/bLwdbr2o/

diachedelic avatar Apr 10 '17 12:04 diachedelic

@diachedelic by trying this, you can see that rv-if alone works but doesn't trigger update of other directives (in this case rv-src): http://jsfiddle.net/qobb51ed/

rivets.bind(document, model);
setTimeout(() => {model.foo = null;}) // image for a while disappears
setTimeout(() => {model.foo = friesURL;},1000) // image reappear but still with old URL

Namek avatar Apr 10 '17 13:04 Namek

Look at this weirdness - neither rv-text, rv-src nor rv-title are being rebound

http://jsfiddle.net/ryL2zqt7/

diachedelic avatar Apr 10 '17 13:04 diachedelic

I have a simple fix for this problem by not copying model in rv-if's routine in src/binders.coffee

-        models = {}
-        models[key] = model for key, model of @view.models
+        models = @view.models

But don't know if it may cause other problems?

luikore avatar Jun 04 '17 04:06 luikore

@luikore I'm still note sure what was the purpose of creating a new object because I didn't look into that too deeply. However, you might want to look here: https://github.com/blikblum/rivets/commit/7203c105c194d4d554c4f567c78c437dea8726eb#diff-72d0612bafa6e737e475d5bce8cbab4cL169 which commits to a branch https://github.com/blikblum/rivets/tree/svelte

Namek avatar Jun 04 '17 12:06 Namek

In the svelte fork i changed how scope is created, basically by not shallow copying the models in the nested scopes. Instead, it uses prototype like inheritance. It searches the property to bind in the current scope, if not find search in parent scope until find the root scope.

Each scope has a $parent property pointing to parent scope.

This is similar how vue works.

It has some advantages. Fixes #486 #512 #417 and this bug

Also allows to bind to properties in parent scopes.

See https://codepen.io/blikblum/pen/eBQzJO . Try to edit one of the input fields that are bound to a property in the parent model

Now do the same with https://codepen.io/blikblum/pen/MKXXOX?editors=1010#0

svelte fork it behaves similar to vue: https://codepen.io/blikblum/pen/ZQRRmm

blikblum avatar Jun 04 '17 15:06 blikblum

I don't know for sure if this is related or not. In our code, we have:

  • An object (gTplData), with a key (userscripts), which is an object with two keys (active, inactive), each of which is an array of objects.
  • A UI action can remove one of those leaf objects, so we .splice() its index out of the array containing it, to remove it from the display.

This works fine, unless it's the last item in the array. Because we also have, in the html, some rv-unless="..." which take these arrays as their inputs. If I remove all the rv-unless'es that point at these arrays, everything works.

For now instead I'm working around the issue, by not calling splice() if the length is 1.

arantius avatar May 31 '18 18:05 arantius

@arantius You can try tinybind, a fork of rivets with many bug fixes, like this one, and other improvements

https://github.com/blikblum/tinybind

blikblum avatar May 31 '18 23:05 blikblum