Skip to content

Commit d307dbd

Browse files
authored
fix: watch didn't work for Vue-only vars (#86)
* fix: `watch` didn't work for Vue-only vars
1 parent 459ae46 commit d307dbd

File tree

2 files changed

+116
-19
lines changed

2 files changed

+116
-19
lines changed

js/src/VueTemplateRenderer.js

+22-19
Original file line numberDiff line numberDiff line change
@@ -163,27 +163,30 @@ function addModelListeners(model, vueModel) {
163163
}
164164

165165
function createWatches(model, parentView, templateWatchers) {
166-
return model.keys()
167-
.filter(prop => !prop.startsWith('_')
168-
&& !['events', 'template', 'components', 'layout', 'css', 'data', 'methods'].includes(prop))
169-
.reduce((result, prop) => ({
170-
...result,
171-
[prop]: {
172-
handler(value) {
173-
if (templateWatchers && templateWatchers[prop]) {
174-
templateWatchers[prop].bind(this)(value);
175-
}
176-
/* Don't send changes received from backend back */
177-
if (_.isEqual(value, model.get(prop))) {
178-
return;
179-
}
166+
const modelWatchers = model.keys().filter(prop => !prop.startsWith('_')
167+
&& !['events', 'template', 'components', 'layout', 'css', 'data', 'methods'].includes(prop))
168+
.reduce((result, prop) => ({
169+
...result,
170+
[prop]: {
171+
handler(value) {
172+
if (templateWatchers && templateWatchers[prop]) {
173+
templateWatchers[prop].bind(this)(value);
174+
}
175+
/* Don't send changes received from backend back */
176+
if (_.isEqual(value, model.get(prop))) {
177+
return;
178+
}
180179

181-
model.set(prop, value === undefined ? null : _.cloneDeep(value));
182-
model.save_changes(model.callbacks(parentView));
183-
},
184-
deep: true,
180+
model.set(prop, value === undefined ? null : _.cloneDeep(value));
181+
model.save_changes(model.callbacks(parentView));
185182
},
186-
}), {});
183+
deep: true,
184+
},
185+
}), {})
186+
/* Overwritten keys from templateWatchers are handled in modelWatchers
187+
so that we eventually call all handlers from templateWatchers.
188+
*/
189+
return {...templateWatchers, ...modelWatchers};
187190
}
188191

189192
function createMethods(model, parentView) {

tests/ui/test_watchers.py

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import pytest
2+
import sys
3+
from playwright.sync_api import Page
4+
from traitlets import Callable, Int, Unicode, default
5+
from IPython.display import display
6+
7+
if sys.version_info < (3, 7):
8+
pytest.skip("requires python3.7 or higher", allow_module_level=True)
9+
10+
import ipyvue as vue
11+
12+
13+
class WatcherTemplateTraitlet(vue.VueTemplate):
14+
number = Int(0).tag(sync=True)
15+
callback = Callable()
16+
text = Unicode("Click Me ").tag(sync=True)
17+
18+
@default("template")
19+
def _default_vue_template(self):
20+
return """
21+
<template>
22+
<div @click="number += 1">{{text + number}}</div>
23+
</template>
24+
<script>
25+
export default {
26+
watch: {
27+
number: function(value) {
28+
callback();
29+
}
30+
}
31+
}
32+
</script>
33+
"""
34+
35+
def vue_callback(self):
36+
self.callback()
37+
38+
39+
# We test that the watcher is activated when a var from python is changed
40+
def test_watcher_traitlet(solara_test, page_session: Page):
41+
def callback():
42+
widget.text = "Clicked "
43+
44+
widget = WatcherTemplateTraitlet(callback=callback)
45+
46+
display(widget)
47+
48+
widget = page_session.locator("text=Click Me 0")
49+
widget.click()
50+
widget = page_session.locator("text=Clicked 1")
51+
52+
53+
class WatcherTemplateVue(vue.VueTemplate):
54+
callback = Callable()
55+
text = Unicode("Click Me ").tag(sync=True)
56+
57+
@default("template")
58+
def _default_vue_template(self):
59+
return """
60+
<template>
61+
<div @click="number += 1">{{text + number}}</div>
62+
</template>
63+
<script>
64+
export default {
65+
watch: {
66+
number: function(value) {
67+
callback();
68+
}
69+
},
70+
data(){
71+
return {
72+
number: 0
73+
}
74+
}
75+
}
76+
</script>
77+
"""
78+
79+
def vue_callback(self):
80+
self.callback()
81+
82+
83+
# We test that watch works for a purely Vue variable
84+
def test_watcher_vue(solara_test, page_session: Page):
85+
def callback():
86+
widget.text = "Clicked "
87+
88+
widget = WatcherTemplateVue(callback=callback)
89+
90+
display(widget)
91+
92+
widget = page_session.locator("text=Click Me 0")
93+
widget.click()
94+
widget = page_session.locator("text=Clicked 1")

0 commit comments

Comments
 (0)