lively 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 25c5a24a2c298b43882809b4ef1f42e59ad1ab64a9bd4873351506c435e6fd07
4
- data.tar.gz: c2defb6ca553cce597f0093f8b345f71d13f26b4d100b70593b8dab9aac9457d
3
+ metadata.gz: 193f180461ac23de0fd1401f41e1c3b5c09b5d770326b7c9bab0958e7a3ad1f8
4
+ data.tar.gz: 716af747f77e13785c2753a0710282828dbfc4d436583fe9b4d53747eb5f2f19
5
5
  SHA512:
6
- metadata.gz: a5b2e54d74e5a7101eba9c8a8e6a6c81c2d43396e493267d4d6aad0531b05a0911ec939e6a5c64db59886aef539c6fd02435f56fdc203946db793c5e6ca5797e
7
- data.tar.gz: 2ef972d3dd1154f6d69b3c31cf0ef8a520d85424fe1eb49716c5c35be28016eb0ecf7c1fd288cd4b26199d070bfdd8c1319b4cc2924f8b5113b43d7c26cde525
6
+ metadata.gz: 38e46574582d3439c82f7d6241103ebaea644f8fd2df4e97e63b970952a8a1d7b5aea49556b9635d6dbb17890bf971ab869cabec023671d083c94f28a6cd0b62
7
+ data.tar.gz: 9178f2ceeae957d72d85d73590ed2a40edec2de72ba5545048a460bb37596adada9c81ef6844f6e443222e67d2ddc992abfda4fcbdfebfdae36e7fdd5d1535fb
checksums.yaml.gz.sig ADDED
Binary file
@@ -1,24 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
- #
5
- # Permission is hereby granted, free of charge, to any person obtaining a copy
6
- # of this software and associated documentation files (the "Software"), to deal
7
- # in the Software without restriction, including without limitation the rights
8
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- # copies of the Software, and to permit persons to whom the Software is
10
- # furnished to do so, subject to the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be included in
13
- # all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- # THE SOFTWARE.
3
+ # Released under the MIT License.
4
+ # Copyright, 2021-2024, by Samuel Williams.
22
5
 
23
6
  require 'live'
24
7
  require 'async/websocket/adapters/http'
data/lib/lively/assets.rb CHANGED
@@ -1,24 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
- #
5
- # Permission is hereby granted, free of charge, to any person obtaining a copy
6
- # of this software and associated documentation files (the "Software"), to deal
7
- # in the Software without restriction, including without limitation the rights
8
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- # copies of the Software, and to permit persons to whom the Software is
10
- # furnished to do so, subject to the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be included in
13
- # all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- # THE SOFTWARE.
3
+ # Released under the MIT License.
4
+ # Copyright, 2021-2024, by Samuel Williams.
22
5
 
23
6
  module Lively
24
7
  class Assets < Protocol::HTTP::Middleware
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2021-2024, by Samuel Williams.
5
+
6
+ module Lively
7
+ module Environment
8
+ module Application
9
+ include Falcon::Environment::Server
10
+
11
+ def application
12
+ if Object.const_defined?(:Application, false)
13
+ ::Application
14
+ else
15
+ Console.warn(self, "No Application class defined, using default.")
16
+ ::Lively::Application
17
+ end
18
+ end
19
+
20
+ def middleware
21
+ ::Protocol::HTTP::Middleware.build do |builder|
22
+ builder.use Lively::Assets, root: File.expand_path('public', self.root)
23
+ builder.use Lively::Assets, root: File.expand_path('../../../public', __dir__)
24
+ builder.use self.application
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,5 +1,9 @@
1
+ # frozen_string_literal: true
1
2
 
2
- require 'trenni/template'
3
+ # Released under the MIT License.
4
+ # Copyright, 2021-2024, by Samuel Williams.
5
+
6
+ require 'xrb/template'
3
7
 
4
8
  module Lively
5
9
  module Pages
@@ -8,8 +12,8 @@ module Lively
8
12
  @title = title
9
13
  @body = body
10
14
 
11
- path = File.expand_path("index.trenni", __dir__)
12
- @template = Trenni::Template.load_file(path)
15
+ path = File.expand_path("index.xrb", __dir__)
16
+ @template = XRB::Template.load_file(path)
13
17
  end
14
18
 
15
19
  attr :title
@@ -10,10 +10,21 @@
10
10
  <link rel="stylesheet" href="/_static/site.css" type="text/css" media="screen" />
11
11
  <link rel="stylesheet" href="/_static/index.css" type="text/css" media="screen" />
12
12
 
13
- <script src="/_components/morphdom/morphdom-umd.js"></script>
14
- <script src="/_components/@socketry/live/live.js"></script>
13
+ <script type="importmap">
14
+ {
15
+ "imports": {
16
+ "live": "/_components/@socketry/live/Live.js",
17
+ "morphdom": "/_components/morphdom/morphdom-esm.js"
18
+ }
19
+ }
20
+ </script>
21
+
22
+ <script type="module">
23
+ import {Live} from 'live';
24
+ window.live = Live.start();
25
+ </script>
15
26
  </head>
16
-
27
+
17
28
  <body>
18
29
  #{self.body&.to_html || "No body specified!"}
19
30
  </body>
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Released under the MIT License.
4
+ # Copyright, 2021-2024, by Samuel Williams.
5
+
3
6
  module Lively
4
- VERSION = "0.2.1"
7
+ VERSION = "0.3.0"
5
8
  end
data/lib/lively.rb CHANGED
@@ -1,28 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
- #
5
- # Permission is hereby granted, free of charge, to any person obtaining a copy
6
- # of this software and associated documentation files (the "Software"), to deal
7
- # in the Software without restriction, including without limitation the rights
8
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- # copies of the Software, and to permit persons to whom the Software is
10
- # furnished to do so, subject to the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be included in
13
- # all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- # THE SOFTWARE.
3
+ # Released under the MIT License.
4
+ # Copyright, 2021-2024, by Samuel Williams.
22
5
 
23
6
  require_relative 'lively/version'
24
7
 
25
8
  require_relative 'lively/assets'
26
9
  require_relative 'lively/application'
27
10
 
28
- require_relative 'lively/environments/application'
11
+ require_relative 'lively/environment/application'
data/license.md ADDED
@@ -0,0 +1,21 @@
1
+ # MIT License
2
+
3
+ Copyright, 2021-2024, by Samuel Williams.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,214 @@
1
+
2
+ import morphdom from 'morphdom';
3
+
4
+ export class Live {
5
+ static start(options = {}) {
6
+ let window = options.window || globalThis;
7
+ let path = options.path || 'live'
8
+ let base = options.base || window.location.href;
9
+
10
+ let url = new URL(path, base);
11
+ url.protocol = url.protocol.replace('http', 'ws');
12
+
13
+ return new this(window, url);
14
+ }
15
+
16
+ constructor(window, url) {
17
+ this.window = window;
18
+ this.document = window.document;
19
+
20
+ this.url = url;
21
+ this.events = [];
22
+
23
+ this.failures = 0;
24
+
25
+ // Track visibility state and connect if required:
26
+ this.document.addEventListener("visibilitychange", () => this.handleVisibilityChange());
27
+ this.handleVisibilityChange();
28
+ }
29
+
30
+ // -- Connection Handling --
31
+
32
+ connect() {
33
+ if (this.server) return this.server;
34
+
35
+ let server = this.server = new this.window.WebSocket(this.url);
36
+
37
+ server.onopen = () => {
38
+ this.failures = 0;
39
+ this.attach();
40
+ };
41
+
42
+ server.onmessage = (message) => {
43
+ const [name, ..._arguments] = JSON.parse(message.data);
44
+
45
+ this[name](..._arguments);
46
+ };
47
+
48
+ // The remote end has disconnected:
49
+ server.addEventListener('error', () => {
50
+ this.failures += 1;
51
+ });
52
+
53
+ server.addEventListener('close', () => {
54
+ // Explicit disconnect will clear `this.server`:
55
+ if (this.server) {
56
+ // We need a minimum delay otherwise this can end up immediately invoking the callback:
57
+ const delay = Math.max(100 * (this.failures + 1) ** 2, 60000);
58
+ setTimeout(() => this.connect(), delay);
59
+ }
60
+
61
+ this.server = null;
62
+ });
63
+
64
+ return server;
65
+ }
66
+
67
+ disconnect() {
68
+ if (this.server) {
69
+ const server = this.server;
70
+ this.server = null;
71
+ server.close();
72
+ }
73
+ }
74
+
75
+ send(message) {
76
+ try {
77
+ this.server.send(message);
78
+ } catch (error) {
79
+ this.events.push(message);
80
+ }
81
+ }
82
+
83
+ flush() {
84
+ if (this.events.length === 0) return;
85
+
86
+ let events = this.events;
87
+ this.events = [];
88
+
89
+ for (var event of events) {
90
+ this.send(event);
91
+ }
92
+ }
93
+
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
+ handleVisibilityChange() {
109
+ if (this.document.hidden) {
110
+ this.disconnect();
111
+ } else {
112
+ this.connect();
113
+ }
114
+ }
115
+
116
+ attach() {
117
+ if (this.document.readyState === 'loading') {
118
+ this.document.addEventListener('DOMContentLoaded', () => this.bindElementsByClassName());
119
+ } else {
120
+ this.bindElementsByClassName();
121
+ }
122
+ }
123
+
124
+ createDocumentFragment(html) {
125
+ return this.document.createRange().createContextualFragment(html);
126
+ }
127
+
128
+ reply(options) {
129
+ if (options && options.reply) {
130
+ this.send(JSON.stringify({reply: options.reply}));
131
+ }
132
+ }
133
+
134
+ // -- RPC Methods --
135
+
136
+ update(id, html, options) {
137
+ let element = this.document.getElementById(id);
138
+ let fragment = this.createDocumentFragment(html);
139
+
140
+ morphdom(element, fragment);
141
+
142
+ this.reply(options);
143
+ }
144
+
145
+ replace(selector, html, options) {
146
+ let elements = this.document.querySelectorAll(selector);
147
+ let fragment = this.createDocumentFragment(html);
148
+
149
+ elements.forEach(element => morphdom(element, fragment.cloneNode(true)));
150
+
151
+ this.reply(options);
152
+ }
153
+
154
+ prepend(selector, html, options) {
155
+ let elements = this.document.querySelectorAll(selector);
156
+ let fragment = this.createDocumentFragment(html);
157
+
158
+ elements.forEach(element => element.prepend(fragment.cloneNode(true)));
159
+
160
+ this.reply(options);
161
+ }
162
+
163
+ append(selector, html, options) {
164
+ let elements = this.document.querySelectorAll(selector);
165
+ let fragment = this.createDocumentFragment(html);
166
+
167
+ elements.forEach(element => element.append(fragment.cloneNode(true)));
168
+
169
+ this.reply(options);
170
+ }
171
+
172
+ remove(selector, options) {
173
+ let elements = this.document.querySelectorAll(selector);
174
+
175
+ elements.forEach(element => element.remove());
176
+
177
+ this.reply(options);
178
+ }
179
+
180
+ dispatchEvent(selector, type, options) {
181
+ let elements = this.document.querySelectorAll(selector);
182
+
183
+ elements.forEach(element => element.dispatchEvent(
184
+ new this.window.CustomEvent(type, options)
185
+ ));
186
+
187
+ this.reply(options);
188
+ }
189
+
190
+ // -- Event Handling --
191
+
192
+ forward(id, event) {
193
+ this.connect();
194
+
195
+ this.send(
196
+ JSON.stringify({id: id, event: event})
197
+ );
198
+ }
199
+
200
+ forwardEvent(id, event, detail) {
201
+ event.preventDefault();
202
+
203
+ this.forward(id, {type: event.type, detail: detail});
204
+ }
205
+
206
+ forwardFormEvent(id, event, detail) {
207
+ event.preventDefault();
208
+
209
+ let form = event.form;
210
+ let formData = new FormData(form);
211
+
212
+ this.forward(id, {type: event.type, detail: detail, formData: [...formData]});
213
+ }
214
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@socketry/live",
3
+ "type": "module",
4
+ "version": "0.7.0",
5
+ "description": "Live HTML tags for Ruby.",
6
+ "main": "Live.js",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/socketry/live-js.git"
10
+ },
11
+ "scripts": {
12
+ "test": "node --test"
13
+ },
14
+ "devDependencies": {
15
+ "jsdom": "^24.0",
16
+ "ws": "^8.17"
17
+ },
18
+ "dependencies": {
19
+ "morphdom": "^2.7"
20
+ },
21
+ "keywords": [
22
+ "live",
23
+ "dynamic",
24
+ "html",
25
+ "ruby"
26
+ ],
27
+ "author": "Samuel Williams <samuel.williams@oriontransfer.co.nz> (http://www.codeotaku.com/)",
28
+ "license": "MIT",
29
+ "bugs": {
30
+ "url": "https://github.com/socketry/live-js/issues"
31
+ },
32
+ "homepage": "https://github.com/socketry/live-js#readme"
33
+ }
@@ -0,0 +1,58 @@
1
+ # Live (JavaScript)
2
+
3
+ This is the JavaScript library for implementing the Ruby gem of the same name.
4
+
5
+ ## Document Manipulation
6
+
7
+ ### `live.update(id, html, options)`
8
+
9
+ Updates the content of the element with the given `id` with the given `html`. The `options` parameter is optional and can be used to pass additional options to the update method.
10
+
11
+ - `options.reply` - If truthy, the server will reply with `{reply: options.reply}`.
12
+
13
+ ### `live.replace(selector, html, options)`
14
+
15
+ Replaces the element(s) selected by the given `selector` with the given `html`. The `options` parameter is optional and can be used to pass additional options to the replace method.
16
+
17
+ - `options.reply` - If truthy, the server will reply with `{reply: options.reply}`.
18
+
19
+ ### `live.prepend(selector, html, options)`
20
+
21
+ Prepends the given `html` to the element(s) selected by the given `selector`. The `options` parameter is optional and can be used to pass additional options to the prepend method.
22
+
23
+ - `options.reply` - If truthy, the server will reply with `{reply: options.reply}`.
24
+
25
+ ### `live.append(selector, html, options)`
26
+
27
+ Appends the given `html` to the element(s) selected by the given `selector`. The `options` parameter is optional and can be used to pass additional options to the append method.
28
+
29
+ - `options.reply` - If truthy, the server will reply with `{reply: options.reply}`.
30
+
31
+ ### `live.remove(selector, options)`
32
+
33
+ Removes the element(s) selected by the given `selector`. The `options` parameter is optional and can be used to pass additional options to the remove method.
34
+
35
+ - `options.reply` - If truthy, the server will reply with `{reply: options.reply}`.
36
+
37
+ ### `live.dispatchEvent(selector, type, options)`
38
+
39
+ Dispatches an event of the given `type` on the element(s) selected by the given `selector`. The `options` parameter is optional and can be used to pass additional options to the dispatchEvent method.
40
+
41
+ - `options.detail` - The detail object to pass to the event.
42
+ - `options.bubbles` - A boolean indicating whether the event should bubble up through the DOM.
43
+ - `options.cancelable` - A boolean indicating whether the event can be canceled.
44
+ - `options.composed` - A boolean indicating whether the event will trigger listeners outside of a shadow root.
45
+
46
+ ## Event Handling
47
+
48
+ ### `live.forward(id, event)`
49
+
50
+ Connect and forward an event on the element with the given `id`. If the connection can't be established, the event will be buffered.
51
+
52
+ ### `live.forwardEvent(id, event, details)`
53
+
54
+ Forward a HTML DOM event to the server. The `details` parameter is optional and can be used to pass additional details to the server.
55
+
56
+ ### `live.forwardFormEvent(id, event, details)`
57
+
58
+ Forward an event which has form data to the server. The `details` parameter is optional and can be used to pass additional details to the server.