lively 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 193f180461ac23de0fd1401f41e1c3b5c09b5d770326b7c9bab0958e7a3ad1f8
4
- data.tar.gz: 716af747f77e13785c2753a0710282828dbfc4d436583fe9b4d53747eb5f2f19
3
+ metadata.gz: 2eb71ca34e43090f3b06b4577cc5c29ed82d3282df6fb26c69adb02cc5eff5fe
4
+ data.tar.gz: 7df1288ae0defc42656da59f5a12e92f3619adc9993e54a4b5e55ac7d10cd795
5
5
  SHA512:
6
- metadata.gz: 38e46574582d3439c82f7d6241103ebaea644f8fd2df4e97e63b970952a8a1d7b5aea49556b9635d6dbb17890bf971ab869cabec023671d083c94f28a6cd0b62
7
- data.tar.gz: 9178f2ceeae957d72d85d73590ed2a40edec2de72ba5545048a460bb37596adada9c81ef6844f6e443222e67d2ddc992abfda4fcbdfebfdae36e7fdd5d1535fb
6
+ metadata.gz: dafdbe14c26702f71de2bc519ac398d141ffc2d1b7f2d1b94f32c44602ea9efc6c083282268f48c7d08798269947322d13bc7005d664b02863fb7bf4f041f031
7
+ data.tar.gz: d65fd9f4c15cd6992c67bf2ffb1ee21d4d86d15643b71e092f38f55386f438bea86e1aa1b0a535a1c0fbd48836e6ec99e77575e751eec513f7c74f77d89064f5
checksums.yaml.gz.sig CHANGED
Binary file
@@ -4,5 +4,5 @@
4
4
  # Copyright, 2021-2024, by Samuel Williams.
5
5
 
6
6
  module Lively
7
- VERSION = "0.3.0"
7
+ VERSION = "0.4.0"
8
8
  end
@@ -1,4 +1,3 @@
1
-
2
1
  import morphdom from 'morphdom';
3
2
 
4
3
  export class Live {
@@ -25,6 +24,39 @@ export class Live {
25
24
  // Track visibility state and connect if required:
26
25
  this.document.addEventListener("visibilitychange", () => this.handleVisibilityChange());
27
26
  this.handleVisibilityChange();
27
+
28
+ // Create a MutationObserver to watch for removed nodes
29
+ this.observer = new this.window.MutationObserver((mutationsList, observer) => {
30
+ for (let mutation of mutationsList) {
31
+ if (mutation.type === 'childList') {
32
+ for (let node of mutation.removedNodes) {
33
+ if (node.classList?.contains('live')) {
34
+ this.unbind(node);
35
+ }
36
+
37
+ // Unbind any child nodes:
38
+ for (let child of node.getElementsByClassName('live')) {
39
+ this.unbind(child);
40
+ }
41
+ }
42
+
43
+ for (let node of mutation.addedNodes) {
44
+ if (node.classList?.contains('live')) {
45
+ this.bind(node);
46
+ }
47
+
48
+ // Bind any child nodes:
49
+ for (let child of node.getElementsByClassName('live')) {
50
+ this.bind(child);
51
+ }
52
+ }
53
+ }
54
+ }
55
+ });
56
+
57
+ this.observer.observe(this.document.body, {childList: true, subtree: true});
58
+
59
+ this.attach();
28
60
  }
29
61
 
30
62
  // -- Connection Handling --
@@ -36,7 +68,7 @@ export class Live {
36
68
 
37
69
  server.onopen = () => {
38
70
  this.failures = 0;
39
- this.attach();
71
+ this.flush();
40
72
  };
41
73
 
42
74
  server.onmessage = (message) => {
@@ -73,11 +105,15 @@ export class Live {
73
105
  }
74
106
 
75
107
  send(message) {
76
- try {
77
- this.server.send(message);
78
- } catch (error) {
79
- this.events.push(message);
108
+ if (this.server) {
109
+ try {
110
+ return this.server.send(message);
111
+ } catch (error) {
112
+ // Ignore.
113
+ }
80
114
  }
115
+
116
+ this.events.push(message);
81
117
  }
82
118
 
83
119
  flush() {
@@ -91,20 +127,6 @@ export class Live {
91
127
  }
92
128
  }
93
129
 
94
- bind(elements) {
95
- for (var element of elements) {
96
- this.send(JSON.stringify({bind: element.id, data: element.dataset}));
97
- }
98
- }
99
-
100
- bindElementsByClassName(selector = 'live') {
101
- this.bind(
102
- this.document.getElementsByClassName(selector)
103
- );
104
-
105
- this.flush();
106
- }
107
-
108
130
  handleVisibilityChange() {
109
131
  if (this.document.hidden) {
110
132
  this.disconnect();
@@ -113,11 +135,21 @@ export class Live {
113
135
  }
114
136
  }
115
137
 
138
+ bind(element) {
139
+ console.log("bind", element.id, element.dataset);
140
+
141
+ this.send(JSON.stringify(['bind', element.id, element.dataset]));
142
+ }
143
+
144
+ unbind(element) {
145
+ console.log("unbind", element.id, element.dataset);
146
+
147
+ this.send(JSON.stringify(['unbind', element.id]));
148
+ }
149
+
116
150
  attach() {
117
- if (this.document.readyState === 'loading') {
118
- this.document.addEventListener('DOMContentLoaded', () => this.bindElementsByClassName());
119
- } else {
120
- this.bindElementsByClassName();
151
+ for (let node of this.document.getElementsByClassName('live')) {
152
+ this.bind(node);
121
153
  }
122
154
  }
123
155
 
@@ -126,8 +158,8 @@ export class Live {
126
158
  }
127
159
 
128
160
  reply(options) {
129
- if (options && options.reply) {
130
- this.send(JSON.stringify({reply: options.reply}));
161
+ if (options?.reply) {
162
+ this.send(JSON.stringify(['reply', options.reply]));
131
163
  }
132
164
  }
133
165
 
@@ -193,7 +225,7 @@ export class Live {
193
225
  this.connect();
194
226
 
195
227
  this.send(
196
- JSON.stringify({id: id, event: event})
228
+ JSON.stringify(['event', id, event])
197
229
  );
198
230
  }
199
231
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@socketry/live",
3
3
  "type": "module",
4
- "version": "0.7.0",
4
+ "version": "0.10.0",
5
5
  "description": "Live HTML tags for Ruby.",
6
6
  "main": "Live.js",
7
7
  "repository": {
@@ -1,5 +1,5 @@
1
1
  import {describe, before, after, it} from 'node:test';
2
- import {ok, strict, strictEqual} from 'node:assert';
2
+ import {ok, strict, strictEqual, deepStrictEqual} from 'node:assert';
3
3
 
4
4
  import {WebSocket} from 'ws';
5
5
  import {JSDOM} from 'jsdom';
@@ -13,8 +13,9 @@ describe('Live', function () {
13
13
  const webSocketServerURL = `ws://localhost:${webSocketServerConfig.port}/live`;
14
14
 
15
15
  before(async function () {
16
- const listening = new Promise(resolve => {
16
+ const listening = new Promise((resolve, reject) => {
17
17
  webSocketServer = new WebSocket.Server(webSocketServerConfig, resolve);
18
+ webSocketServer.on('error', reject);
18
19
  });
19
20
 
20
21
  dom = new JSDOM('<!DOCTYPE html><html><body><div id="my"><p>Hello World</p></div></body></html>');
@@ -81,7 +82,7 @@ describe('Live', function () {
81
82
  const reply = new Promise((resolve, reject) => {
82
83
  socket.on('message', message => {
83
84
  let payload = JSON.parse(message);
84
- if (payload.reply) resolve(payload);
85
+ if (payload[0] == 'reply') resolve(payload);
85
86
  });
86
87
  });
87
88
 
@@ -96,7 +97,72 @@ describe('Live', function () {
96
97
  live.disconnect();
97
98
  });
98
99
 
100
+ it('should handle updates with child live elements', async function () {
101
+ const live = new Live(dom.window, webSocketServerURL);
102
+
103
+ live.connect();
104
+
105
+ const connected = new Promise(resolve => {
106
+ webSocketServer.on('connection', resolve);
107
+ });
108
+
109
+ let socket = await connected;
110
+
111
+ const reply = new Promise((resolve, reject) => {
112
+ socket.on('message', message => {
113
+ let payload = JSON.parse(message);
114
+ console.log("message", payload);
115
+ if (payload[0] == 'bind') resolve(payload);
116
+ else console.log("ignoring", payload);
117
+ });
118
+ });
119
+
120
+ socket.send(
121
+ JSON.stringify(['update', 'my', '<div id="my"><div id="mychild" class="live"></div></div>'])
122
+ );
123
+
124
+ let payload = await reply;
125
+
126
+ deepStrictEqual(payload, ['bind', 'mychild', {}]);
127
+
128
+ live.disconnect();
129
+ });
130
+
131
+ it('can unbind removed elements', async function () {
132
+ dom.window.document.body.innerHTML = '<div id="my" class="live"><p>Hello World</p></div>';
133
+
134
+ const live = new Live(dom.window, webSocketServerURL);
135
+
136
+ live.connect();
137
+
138
+ const connected = new Promise(resolve => {
139
+ webSocketServer.on('connection', resolve);
140
+ });
141
+
142
+ let socket = await connected;
143
+
144
+ const reply = new Promise((resolve, reject) => {
145
+ socket.on('message', message => {
146
+ let payload = JSON.parse(message);
147
+ if (payload[0] == 'unbind') resolve(payload);
148
+ else console.log("ignoring", payload);
149
+ });
150
+ });
151
+
152
+ live.attach();
153
+
154
+ dom.window.document.getElementById('my').remove();
155
+
156
+ let payload = await reply;
157
+
158
+ deepStrictEqual(payload, ['unbind', 'my']);
159
+
160
+ live.disconnect();
161
+ });
162
+
99
163
  it('should handle replacements', async function () {
164
+ dom.window.document.body.innerHTML = '<div id="my"><p>Hello World</p></div>';
165
+
100
166
  const live = new Live(dom.window, webSocketServerURL);
101
167
 
102
168
  live.connect();
@@ -110,7 +176,8 @@ describe('Live', function () {
110
176
  const reply = new Promise((resolve, reject) => {
111
177
  socket.on('message', message => {
112
178
  let payload = JSON.parse(message);
113
- if (payload.reply) resolve(payload);
179
+ if (payload[0] == 'reply') resolve(payload);
180
+ else console.log("ignoring", payload);
114
181
  });
115
182
  });
116
183
 
@@ -143,7 +210,8 @@ describe('Live', function () {
143
210
  const reply = new Promise((resolve, reject) => {
144
211
  socket.on('message', message => {
145
212
  let payload = JSON.parse(message);
146
- if (payload.reply) resolve(payload);
213
+ if (payload[0] == 'reply') resolve(payload);
214
+ else console.log("ignoring", payload);
147
215
  });
148
216
  });
149
217
 
@@ -176,7 +244,8 @@ describe('Live', function () {
176
244
  const reply = new Promise((resolve, reject) => {
177
245
  socket.on('message', message => {
178
246
  let payload = JSON.parse(message);
179
- if (payload.reply) resolve(payload);
247
+ if (payload[0] == 'reply') resolve(payload);
248
+ else console.log("ignoring", payload);
180
249
  });
181
250
  });
182
251
 
@@ -209,7 +278,7 @@ describe('Live', function () {
209
278
  const reply = new Promise((resolve, reject) => {
210
279
  socket.on('message', message => {
211
280
  let payload = JSON.parse(message);
212
- if (payload.reply) resolve(payload);
281
+ if (payload[0] == 'reply') resolve(payload);
213
282
  });
214
283
  });
215
284
 
@@ -238,7 +307,7 @@ describe('Live', function () {
238
307
  const reply = new Promise((resolve, reject) => {
239
308
  socket.on('message', message => {
240
309
  let payload = JSON.parse(message);
241
- if (payload.reply) resolve(payload);
310
+ if (payload[0] == 'reply') resolve(payload);
242
311
  });
243
312
  });
244
313
 
@@ -265,7 +334,7 @@ describe('Live', function () {
265
334
  const reply = new Promise((resolve, reject) => {
266
335
  socket.on('message', message => {
267
336
  let payload = JSON.parse(message);
268
- if (payload.event) resolve(payload);
337
+ if (payload[0] == 'event') resolve(payload);
269
338
  });
270
339
  });
271
340
 
@@ -277,8 +346,8 @@ describe('Live', function () {
277
346
 
278
347
  let payload = await reply;
279
348
 
280
- strictEqual(payload.id, 'my');
281
- strictEqual(payload.event.type, 'click');
349
+ strictEqual(payload[1], 'my');
350
+ strictEqual(payload[2].type, 'click');
282
351
 
283
352
  live.disconnect();
284
353
  });
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lively
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -43,30 +43,30 @@ dependencies:
43
43
  name: falcon
44
44
  requirement: !ruby/object:Gem::Requirement
45
45
  requirements:
46
- - - ">="
46
+ - - "~>"
47
47
  - !ruby/object:Gem::Version
48
- version: '0'
48
+ version: '0.47'
49
49
  type: :runtime
50
50
  prerelease: false
51
51
  version_requirements: !ruby/object:Gem::Requirement
52
52
  requirements:
53
- - - ">="
53
+ - - "~>"
54
54
  - !ruby/object:Gem::Version
55
- version: '0'
55
+ version: '0.47'
56
56
  - !ruby/object:Gem::Dependency
57
57
  name: live
58
58
  requirement: !ruby/object:Gem::Requirement
59
59
  requirements:
60
60
  - - "~>"
61
61
  - !ruby/object:Gem::Version
62
- version: 0.7.0
62
+ version: '0.8'
63
63
  type: :runtime
64
64
  prerelease: false
65
65
  version_requirements: !ruby/object:Gem::Requirement
66
66
  requirements:
67
67
  - - "~>"
68
68
  - !ruby/object:Gem::Version
69
- version: 0.7.0
69
+ version: '0.8'
70
70
  - !ruby/object:Gem::Dependency
71
71
  name: xrb
72
72
  requirement: !ruby/object:Gem::Requirement
metadata.gz.sig CHANGED
Binary file