Skip to content

Commit 99b9013

Browse files
committed
Unfinished exploration toward vuejspython components
1 parent e02b58b commit 99b9013

File tree

4 files changed

+219
-16
lines changed

4 files changed

+219
-16
lines changed

example-8.html

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Vue-Python sample 3</title>
6+
<script src="lib/vue.js"></script>
7+
<link rel="stylesheet" href="lib/picnic.min.css" />
8+
<style>
9+
</style>
10+
</head>
11+
<body>
12+
13+
<div id="main">
14+
<p>
15+
This example explores having custom components.
16+
</p>
17+
i: {{ i }} <button @click="incr(1)">+</button><button @click="incr(-1)">-</button><br/>
18+
<input v-model.number="i"/>
19+
<progress style="width: 500px" max="100" :value="i"></progress>
20+
<my-show :val="i" pre="my-show 1:"></my-show>
21+
<my-show :val="i" pre="my-show 2:"></my-show>
22+
<my-dummy></my-dummy>
23+
<my-dummy></my-dummy>
24+
<!--my-square :val="i" pre="my-square 2:"></my-square-->
25+
</div>
26+
27+
<script src="vuejspython.js"></script>
28+
<script>
29+
/* plain vuejs component */
30+
Vue.component('my-show', {
31+
props: ['val', 'pre'],
32+
template: '<div> {{pre}} {{val}}</div>'
33+
})
34+
/* vuejspython component */
35+
vuejspython.component('Dummy', 'my-dummy', {
36+
template: '<div> {{i}} <button @click="incr(1)">+</button></div>'
37+
})
38+
vuejspython.component('Square', 'my-square', {
39+
props: ['val', 'pre'],
40+
template: '<div> {{pre}} {{val}} -> {{square}}</div>'
41+
})
42+
vuejspython.start('localhost')
43+
</script>
44+
</body>
45+
</html>

example-8.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
2+
import asyncio
3+
4+
from vuejspython import model, start
5+
6+
7+
@model
8+
class Comp:
9+
# define properties to forward to vue (or not)
10+
i = 42
11+
12+
def __init__(self):
13+
self.i = 1
14+
15+
def incr(self, d):
16+
self.i += d
17+
18+
@model
19+
class Dummy:
20+
i = 12
21+
def incr(self, d):
22+
self.i += d
23+
24+
@model
25+
class Square:
26+
def computed_square(self):
27+
return self.val ** 2
28+
29+
start(Comp())

vuejspython.js

Lines changed: 100 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11

22
var vuejspython = {}
33

4-
vuejspython.defaultPort =
4+
vuejspython.defaultPort = 4259
5+
vuejspython.wsurl = null
56

67
vuejspython.start = function(wsurl, opt={}) {
78
if (opt.data === undefined) opt.data = ()=>({})
89
if (! wsurl.startsWith('ws')) {
9-
wsurl = 'ws://'+wsurl
10+
wsurl = 'ws://' + wsurl
1011
}
1112
if (wsurl.substr(4).indexOf(':') == -1) {
12-
wsurl = wsurl+':4259'
13+
wsurl = wsurl + ':' + vuejspython.defaultPort
1314
}
14-
var wsInit = new WebSocket(wsurl+'/init');
15+
vuejspython.wsurl = wsurl
16+
var wsInit = new WebSocket(wsurl+'/init')
1517
wsInit.addEventListener('message', function(a) {
1618
a = JSON.parse(a.data)
1719
var ws = new WebSocket(wsurl+'/');
@@ -55,18 +57,109 @@ vuejspython.start = function(wsurl, opt={}) {
5557
methods,
5658
watch,
5759
...opt,
58-
});
60+
})
5961
ws.addEventListener('message', function(a) {
6062
a = a.data
6163
if (a.startsWith('UPDATE ')) {
6264
let parts = a.split(' ', 2)
6365
let k = parts[1]
64-
let v = a.substr(parts.join(" ").length)
66+
let v = a.substr(parts.join(' ').length)
6567
v = JSON.parse(v)
6668
valuesWhere[k] = v
6769
vm.$set(vm, k, v)
6870
}
6971
})
7072
window.vuejspython_vm = vm // for console-based introspection
71-
});
73+
})
74+
}
75+
76+
let instanceId = 1
77+
78+
vuejspython.component = function(pyClass, name, opt={}) {
79+
// later, consider refactoring if the two are really similar
80+
if (opt.data === undefined) opt.data = ()=>({})
81+
if (opt.props === undefined) opt.props = []
82+
// async component
83+
Vue.component(name, function (resolve, reject) {
84+
let id = instanceId
85+
instanceId++
86+
let wsurl = vuejspython.wsurl
87+
// TODO watch properties below to send notif
88+
var wsInit = new WebSocket(wsurl+'/init:'+pyClass+':'+id)
89+
90+
91+
92+
93+
94+
95+
96+
/// TODO WIP, it seems the promise is called only once
97+
/// the async is more for loading the component, not the instance
98+
/// should consider how to do the creation of instances
99+
/// maybe everything is too single-instance in the code...
100+
/// so above we cannot just connect to init:Truc:1
101+
/// it might well be easy still, as below we don't really access the js object
102+
/// maybe beforeCreate .... .__id = instanceId++ .... etc
103+
wsInit.addEventListener('message', function(a) {
104+
a = JSON.parse(a.data)
105+
var ws = new WebSocket(wsurl+'/');
106+
let props = {...opt.props}
107+
let computed = {...opt.computed}
108+
let methods = {...opt.methods}
109+
let watch = {...opt.watch}
110+
let optdata = opt.data
111+
for (let k of ['props', 'data', 'watch', 'methods', 'computed']) delete opt[k]
112+
let valuesWhere = {}
113+
114+
for (let k in a.state) {
115+
let watchk = function (v, old) {
116+
if (valuesWhere[k] == v) return
117+
delete valuesWhere[k]
118+
ws.send('UPDATE:'+id)
119+
ws.send(k)
120+
ws.send(JSON.stringify(v))
121+
}
122+
if (watch[k] === undefined) {
123+
watch[k] = watchk
124+
} else {
125+
let owk = watch[k]
126+
watch[k] = function (...args) { watchk(...args); owk.bind(this)(...args) }
127+
}
128+
129+
}
130+
for (let k of a.methods) {
131+
methods[k] = function(...args) {
132+
console.log('CALL:'+id, ...args)
133+
ws.send('CALL:'+id)
134+
ws.send(k)
135+
ws.send(JSON.stringify(args))
136+
}
137+
}
138+
resolve({
139+
data: () => ({
140+
...a.state,
141+
...optdata()
142+
}),
143+
props,
144+
computed,
145+
methods,
146+
watch,
147+
...opt,
148+
})
149+
ws.addEventListener('message', function(a) {
150+
a = a.data
151+
if (a.startsWith('UPDATE:'+id+' ')) {
152+
let parts = a.split(' ', 2)
153+
let k = parts[1]
154+
let v = a.substr(parts.join(' ').length)
155+
v = JSON.parse(v)
156+
valuesWhere[k] = v
157+
vm.$set(vm, k, v)
158+
}
159+
})
160+
})
161+
wsInit.addEventListener('open', function(a) {
162+
wsInit.send(JSON.stringify(opt.props))
163+
})
164+
})
72165
}

vuejspython.py

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
import json
55
from observablecollections.observablelist import ObservableList
66

7+
g_components = {}
8+
g_instances = {}
9+
710
PREFIX = {
811
'IN': '🢀║ ',
912
'OUT': ' ║🢂',
@@ -103,6 +106,7 @@ def field_should_be_synced(cls):
103106

104107
# class annotation
105108
def model(cls):
109+
g_components[cls.__name__] = cls
106110
prefix = 'computed_'
107111
novue = cls._v_novue if hasattr(cls, '_v_novue') else []
108112
cls._v_nobroadcast = cls._v_nobroadcast if hasattr(cls, '_v_nobroadcast') else []
@@ -143,10 +147,28 @@ async def broadcast_update(k, v):
143147
except:
144148
pass
145149

146-
def handleClient(o):
150+
def handleClient():
147151
async def handleClient(websocket, path):
148-
if path == '/init':
149-
clss = type(o)
152+
print("PATH", path)
153+
if path == '/init' or path.startswith('/init:'):
154+
if path == '/init':
155+
o = g_instances['ROOT']
156+
clss = type(o)
157+
else:
158+
parts = path.split(':')
159+
clss = g_components[parts[1]]
160+
id = int(parts[2])
161+
o = clss()
162+
setattr(o, '__id', id)
163+
print("BLKC RCV")
164+
data = await websocket.recv()
165+
info('IN', 'PROPS', data)
166+
data = json.loads(data)
167+
for k in data:
168+
setattr(o, k, '')
169+
setup_model_object_infra(o)
170+
g_instances[id] = o
171+
print(g_instances)
150172
state = {}
151173
methods = []
152174
for k in filter(field_should_be_synced(clss), dir(clss)):
@@ -159,23 +181,32 @@ async def handleClient(websocket, path):
159181
'state': state,
160182
'methods': methods
161183
}
162-
info('OUT', 'INIT', list(state.keys()))
184+
info('OUT', 'INIT', clss.__name__, list(state.keys()))
163185
await websocket.send(json.dumps(to_send))
164186
else:
165187
all.append(websocket)
166188
try:
167189
while True:
168190
comm = await websocket.recv()
169-
if comm == 'CALL':
191+
if comm == 'CALL' or comm.startswith('CALL:'):
192+
if comm == 'CALL':
193+
o = g_instances['ROOT']
194+
else:
195+
o = g_instances[comm.split(':')[1]]
170196
meth = await websocket.recv()
171197
info('IN', 'METH', meth)
172198
data = await websocket.recv()
173199
info('IN', 'DATA', data)
174200
try:
201+
###############
175202
res = getattr(o, meth)(*json.loads(data))
176203
except Exception as inst:
177204
info('ERR', '... exception while calling method:', inst)
178-
elif comm == 'UPDATE':
205+
if comm == 'UPDATE' or comm.startswith('UPDATE:'):
206+
if comm == 'UPDATE':
207+
o = g_instances['ROOT']
208+
else:
209+
o = g_instances[comm.split(':')[1]]
179210
k = await websocket.recv()
180211
v = await websocket.recv()
181212
info('IN', 'UPDATE', k, v)
@@ -200,8 +231,7 @@ def _decorator(self, *args, **kwargs):
200231
recompute_scheduled_computed(self)
201232
return _decorator
202233

203-
204-
def start(o, port=4259, host='localhost'):
234+
def setup_model_object_infra(o):
205235
cls = o.__class__
206236
o._v_cache = {}
207237
o._v_currently_computing = []
@@ -216,7 +246,13 @@ def start(o, port=4259, host='localhost'):
216246
o._v_deps[k] = []
217247
for k in o._v_computed.keys():
218248
recompute_computed(o, k)
249+
250+
251+
def start(o, port=4259, host='localhost'):
252+
g_instances['ROOT'] = o
253+
setattr(o, '__id', 'ROOT')
254+
setup_model_object_infra(o)
219255
#inreader = asyncio.StreamReader(sys.stdin)
220-
ws_server = websockets.serve(handleClient(o), host, port)
256+
ws_server = websockets.serve(handleClient(), host, port)
221257
asyncio.ensure_future(ws_server)
222258
asyncio.get_event_loop().run_forever()

0 commit comments

Comments
 (0)