quince 0.1.1 → 0.3.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 +4 -4
- data/Gemfile.lock +2 -2
- data/README.md +176 -10
- data/lib/quince/attributes_by_element.rb +10 -4
- data/lib/quince/callback.rb +36 -0
- data/lib/quince/component.rb +24 -18
- data/lib/quince/html_tag_components.rb +13 -14
- data/lib/quince/serialiser.rb +2 -91
- data/lib/quince/singleton_methods.rb +7 -5
- data/lib/quince/version.rb +1 -1
- data/scripts.js +5 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2189200f4bee6758d6a981e3f4ba7b0d8dc558d0e0e2bb66fd421c11b404f954
|
4
|
+
data.tar.gz: 4153ceb85c057cfe7c2e74852aee81b82dde74362aecec53647b485fea191b87
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 923689df34d18c7357785812424acc62f38f89a3966c568bfd6e86f03c41eb1a9dd629eebdee4b1ae9321e42e1180d0be65af560613e31543496ebc312c40a30
|
7
|
+
data.tar.gz: 2624224d31f066ef9562333264e6f1cc00275bf72ab35942a1b0b3097864a14c89f04a6d4ebeee7ffb07ce240f22bde8f07939b61ee64a8019017ceceead23c9
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
quince (0.
|
4
|
+
quince (0.3.0)
|
5
5
|
oj (~> 3.13)
|
6
6
|
typed_struct (>= 0.1.4)
|
7
7
|
|
@@ -9,7 +9,7 @@ GEM
|
|
9
9
|
remote: https://rubygems.org/
|
10
10
|
specs:
|
11
11
|
diff-lcs (1.4.4)
|
12
|
-
oj (3.13.
|
12
|
+
oj (3.13.6)
|
13
13
|
rake (13.0.6)
|
14
14
|
rbs (1.6.2)
|
15
15
|
rspec (3.10.0)
|
data/README.md
CHANGED
@@ -1,15 +1,162 @@
|
|
1
1
|
# Quince
|
2
2
|
|
3
|
-
|
3
|
+
### What is Quince?
|
4
4
|
|
5
|
-
|
5
|
+
Quince is an opinionated framework for building dynamic yet fully server-rendered web apps, with little to no JavaScript.
|
6
|
+
|
7
|
+
### Inspired by
|
8
|
+
|
9
|
+
React, Turbo, Hotwire amongst others
|
10
|
+
|
11
|
+
### Current status
|
12
|
+
|
13
|
+
Proof of concept, but [working in production](https://quince-rb.herokuapp.com/), and with decent performance despite few optimisations at this stage
|
14
|
+
|
15
|
+
### How it works
|
16
|
+
|
17
|
+
- Define some components and `expose` them at certain routes
|
18
|
+
- Define some interactions that can take place, which can change the state of the components, and are handled with ruby methods
|
19
|
+
- The front end will swap out the updated components with new HTML re-rendered by the back end
|
20
|
+
|
21
|
+
## Minimal 'hello world' example
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
# app.rb
|
25
|
+
require "quince_sinatra"
|
26
|
+
|
27
|
+
class App < Quince::Component
|
28
|
+
def render
|
29
|
+
html(
|
30
|
+
head,
|
31
|
+
body("hello world")
|
32
|
+
)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
expose App, at: "/"
|
37
|
+
```
|
38
|
+
|
39
|
+
- Run it via
|
40
|
+
```sh
|
41
|
+
ruby app.rb
|
42
|
+
```
|
43
|
+
|
44
|
+
- Visit `localhost:4567/`!
|
45
|
+
|
46
|
+
## More complex example
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
require 'quince_sinatra'
|
50
|
+
|
51
|
+
class App < Quince::Component
|
52
|
+
def render
|
53
|
+
Layout(title: "First app") {[
|
54
|
+
Counter()
|
55
|
+
]}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class Layout < Quince::Component
|
60
|
+
Props(title: String)
|
61
|
+
|
62
|
+
def render
|
63
|
+
html(
|
64
|
+
head(
|
65
|
+
internal_scripts
|
66
|
+
),
|
67
|
+
body(
|
68
|
+
h1(props.title),
|
69
|
+
children
|
70
|
+
)
|
71
|
+
)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class Counter < Quince::Component
|
76
|
+
State(val: Integer)
|
77
|
+
|
78
|
+
def initialize
|
79
|
+
@state = State.new(
|
80
|
+
val: params.fetch(:val, 0),
|
81
|
+
)
|
82
|
+
end
|
83
|
+
|
84
|
+
exposed def increment
|
85
|
+
state.val += 1
|
86
|
+
end
|
87
|
+
|
88
|
+
exposed def decrement
|
89
|
+
state.val -= 1
|
90
|
+
end
|
91
|
+
|
92
|
+
def render
|
93
|
+
div(
|
94
|
+
h2("count is #{state.val}"),
|
95
|
+
button(onclick: method(:increment)) { "++" },
|
96
|
+
button(onclick: method(:decrement)) { "--" }
|
97
|
+
)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
expose App, at: "/"
|
102
|
+
```
|
103
|
+
|
104
|
+
#### See https://github.com/johansenja/quince-demo and https://quince-rb.herokuapp.com/ for more
|
105
|
+
|
106
|
+
## Why Quince?
|
107
|
+
|
108
|
+
### Why not?
|
109
|
+
|
110
|
+
- You have pre-existing APIs which you want to integrate a front end with
|
111
|
+
- You want to share the back end API with a different service
|
112
|
+
- You want more offline functionality
|
113
|
+
- You need a super complex/custom front end
|
114
|
+
|
115
|
+
### Why?
|
116
|
+
|
117
|
+
- Lightweight 🪶
|
118
|
+
- Very few dependencies
|
119
|
+
- Just a couple hundred lines of core logic
|
120
|
+
- Fewer than 30 lines (unminified) of JavaScript in the front end
|
121
|
+
- Plug and play into multiple back ends 🔌
|
122
|
+
- Components > templates 🧩
|
123
|
+
- Write html-like elements, but with strong typo resistence
|
124
|
+
- no special syntax or compilation required
|
125
|
+
- Shallow learning curve if you are already familiar with React 📈
|
126
|
+
- Just worry about your core business logic and how the app looks 🧪
|
127
|
+
- No need to worry about
|
128
|
+
- routes
|
129
|
+
- controllers
|
130
|
+
- front end -> back end communication/APIs/Data transfer
|
131
|
+
- front end -> back end code sharing
|
132
|
+
- Quince handles these for you
|
133
|
+
- No node_modules 📦
|
134
|
+
- No yarn/npm
|
135
|
+
- Minimise bundle size concerns
|
136
|
+
- Manage your dependencies just using bundler & rubygems
|
137
|
+
- Make use of other pre-built Quince components via rubygems
|
138
|
+
- Get full use of Ruby's rich and comprehensive standard library 💎
|
139
|
+
- Take advantage of Ruby's ability to wrap native libraries (eg. gems using C) ⚡️
|
140
|
+
- Fully server-rendered responses 📡
|
141
|
+
- Single source of truth for your app's code (no code-sharing needed)
|
142
|
+
- Better SEO out the box
|
143
|
+
- Know exactly what your user is seeing
|
144
|
+
- Tracking a user's activity on the front end has become a big deal, especially in heavily front-end driven apps/SPAs, in order to be able to see how a user is actually using the app (ie. to track how the state has been changing on the front end)
|
145
|
+
- This normally requires cookies and a premium third party service
|
146
|
+
- But if everything a user sees is generated server-side, it would be easy to reconstruct a user's journey and their state changes
|
6
147
|
|
7
148
|
## Installation
|
8
149
|
|
9
|
-
|
150
|
+
Quince itself is framework agnostic, so you should use an adaptor which plugs it into an existing framework for handling basic server needs
|
151
|
+
|
152
|
+
### Install it via adapters
|
153
|
+
|
154
|
+
- [Sinatra](https://github.com/johansenja/quince_sinatra)
|
155
|
+
|
156
|
+
Pick one, and add it to your application's Gemfile, eg:
|
10
157
|
|
11
158
|
```ruby
|
12
|
-
gem '
|
159
|
+
gem 'quince_sinatra'
|
13
160
|
```
|
14
161
|
|
15
162
|
And then execute:
|
@@ -18,11 +165,30 @@ And then execute:
|
|
18
165
|
|
19
166
|
Or install it yourself as:
|
20
167
|
|
21
|
-
$ gem install
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
168
|
+
$ gem install quince_sinatra
|
169
|
+
|
170
|
+
|
171
|
+
## Usage notes
|
172
|
+
|
173
|
+
- All HTML tags are available via a method of the same name, eg. `div()`, `section()`, `span()` - **with the exception of `para` standing in for `p` to avoid clashes with Ruby's common `Kernel#p` method**
|
174
|
+
- All HTML attributes are available, and are the same as they would be in a regular html document, eg. `onclick` rather than `onClick` - **with the exception of a `Class`, `Max`, `Min`, `Method`** - which start with capital letters to avoid clashes with some internal methods.
|
175
|
+
- Type checking is available at runtime for a component's `State` and `Props`, and is done in accordance with [Typed Struct](https://github.com/johansenja/typed_struct)
|
176
|
+
- Children can be specified in one of two places, depending on what you would prefer:
|
177
|
+
- as a block argument, to maintain similar readability with real html elements, where attributes come first
|
178
|
+
```ruby
|
179
|
+
div(id: :my_div, style: "color: red") { h1("Single child") }
|
180
|
+
div(id: "div2", style: "color: green") {[
|
181
|
+
h2("multiple"),
|
182
|
+
h3("children")
|
183
|
+
]}
|
184
|
+
```
|
185
|
+
- as positional arguments (for convenience and cleanliness when no props are passed)
|
186
|
+
```ruby
|
187
|
+
div(
|
188
|
+
h1("hello world")
|
189
|
+
)
|
190
|
+
```
|
191
|
+
- A component's `render` method should always return a single top level element, ie. if you wanted to return 2 elements you should wrap them in a `div`
|
26
192
|
|
27
193
|
## Development
|
28
194
|
|
@@ -32,7 +198,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
32
198
|
|
33
199
|
## Contributing
|
34
200
|
|
35
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
201
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/johansenja/quince.
|
36
202
|
|
37
203
|
## License
|
38
204
|
|
@@ -11,11 +11,12 @@ module Quince
|
|
11
11
|
t = Quince::Types
|
12
12
|
opt_string_sym = Rbs("#{t::OptionalString} | Symbol")
|
13
13
|
opt_bool = t::OptionalBoolean
|
14
|
-
|
14
|
+
opt_callback = Rbs("Quince::Callback::Interface | Quince::Types::Undefined")
|
15
15
|
value = opt_string_sym # for now
|
16
16
|
|
17
17
|
ATTRIBUTES_BY_ELEMENT = {
|
18
18
|
"A" => {
|
19
|
+
# download: t::Any,
|
19
20
|
href: opt_string_sym,
|
20
21
|
hreflang: opt_string_sym,
|
21
22
|
media: opt_string_sym,
|
@@ -336,7 +337,7 @@ module Quince
|
|
336
337
|
"Area" => {
|
337
338
|
alt: opt_string_sym,
|
338
339
|
coords: opt_string_sym,
|
339
|
-
download: t::Any,
|
340
|
+
# download: t::Any,
|
340
341
|
href: opt_string_sym,
|
341
342
|
hreflang: opt_string_sym,
|
342
343
|
media: opt_string_sym,
|
@@ -467,8 +468,13 @@ module Quince
|
|
467
468
|
}.freeze
|
468
469
|
|
469
470
|
DOM_EVENTS = {
|
470
|
-
onclick:
|
471
|
-
onsubmit:
|
471
|
+
onclick: opt_callback,
|
472
|
+
onsubmit: opt_callback,
|
473
|
+
onblur: opt_callback,
|
474
|
+
onchange: opt_callback,
|
475
|
+
onsearch: opt_callback,
|
476
|
+
onkeyup: opt_callback,
|
477
|
+
onselect: opt_callback,
|
472
478
|
}.freeze
|
473
479
|
end
|
474
480
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Quince
|
2
|
+
class Callback
|
3
|
+
module ComponentHelpers
|
4
|
+
protected
|
5
|
+
|
6
|
+
DEFAULT_CALLBACK_OPTIONS = {
|
7
|
+
prevent_default: false,
|
8
|
+
take_form_values: false,
|
9
|
+
# others could include:
|
10
|
+
# debounce: false,
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
def callback(method_name, **opts)
|
14
|
+
unless self.class.instance_variable_get(:@exposed_actions).member?(method_name)
|
15
|
+
raise "The action you called is not exposed"
|
16
|
+
end
|
17
|
+
|
18
|
+
opts = DEFAULT_CALLBACK_OPTIONS.merge opts
|
19
|
+
|
20
|
+
Callback.new(self, method_name, **opts)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module Interface
|
25
|
+
attr_reader :receiver, :method_name, :prevent_default, :take_form_values
|
26
|
+
end
|
27
|
+
|
28
|
+
include Interface
|
29
|
+
|
30
|
+
def initialize(receiver, method_name, prevent_default:, take_form_values:)
|
31
|
+
@receiver, @method_name = receiver, method_name
|
32
|
+
@prevent_default = prevent_default
|
33
|
+
@take_form_values = take_form_values
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/quince/component.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require_relative "callback"
|
2
|
+
|
1
3
|
module Quince
|
2
4
|
class Component
|
3
5
|
class << self
|
@@ -29,13 +31,10 @@ module Quince
|
|
29
31
|
verb: meth0d,
|
30
32
|
route: route,
|
31
33
|
) do |params|
|
32
|
-
instance = Quince::Serialiser.deserialise
|
34
|
+
instance = Quince::Serialiser.deserialise(CGI.unescapeHTML(params[:component]))
|
35
|
+
Quince::Component.class_variable_set :@@params, params
|
33
36
|
if @exposed_actions.member? action
|
34
|
-
|
35
|
-
instance.send action
|
36
|
-
else
|
37
|
-
instance.send action, params[:params]
|
38
|
-
end
|
37
|
+
instance.send action
|
39
38
|
instance
|
40
39
|
else
|
41
40
|
raise "The action you called is not exposed"
|
@@ -45,12 +44,17 @@ module Quince
|
|
45
44
|
route
|
46
45
|
end
|
47
46
|
|
48
|
-
def
|
49
|
-
|
47
|
+
def create(*children, **props, &block_children)
|
48
|
+
allocate.tap do |instance|
|
49
|
+
id = SecureRandom.alphanumeric 6
|
50
|
+
instance.instance_variable_set :@__id, id
|
51
|
+
instance.instance_variable_set :@props, initialize_props(self, id, **props)
|
52
|
+
kids = block_children ? block_children.call : children
|
53
|
+
instance.instance_variable_set(:@children, kids)
|
54
|
+
instance.send :initialize
|
55
|
+
end
|
50
56
|
end
|
51
57
|
|
52
|
-
attr_reader :initial_state
|
53
|
-
|
54
58
|
private
|
55
59
|
|
56
60
|
def initialize_props(const, id, **props)
|
@@ -58,14 +62,12 @@ module Quince
|
|
58
62
|
end
|
59
63
|
end
|
60
64
|
|
61
|
-
|
65
|
+
include Callback::ComponentHelpers
|
62
66
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
@children = block_children || children
|
68
|
-
end
|
67
|
+
# set default
|
68
|
+
@@params = {}
|
69
|
+
|
70
|
+
attr_reader :props, :state, :children
|
69
71
|
|
70
72
|
def render
|
71
73
|
raise "not implemented"
|
@@ -77,11 +79,15 @@ module Quince
|
|
77
79
|
self.class.exposed route, meth0d: via
|
78
80
|
end
|
79
81
|
|
82
|
+
def params
|
83
|
+
@@params
|
84
|
+
end
|
85
|
+
|
80
86
|
private
|
81
87
|
|
82
88
|
attr_reader :__id
|
83
89
|
|
84
|
-
HTML_SELECTOR_ATTR = :"data-
|
90
|
+
HTML_SELECTOR_ATTR = :"data-quid"
|
85
91
|
|
86
92
|
def html_element_selector
|
87
93
|
"[#{HTML_SELECTOR_ATTR}='#{__id}']".freeze
|
@@ -33,23 +33,22 @@ module Quince
|
|
33
33
|
attrib = case value
|
34
34
|
when String, Integer, Float, Symbol
|
35
35
|
value.to_s
|
36
|
-
when
|
37
|
-
owner = value.owner
|
36
|
+
when Callback::Interface
|
38
37
|
receiver = value.receiver
|
39
|
-
|
38
|
+
owner = receiver.class.name
|
39
|
+
name = value.method_name
|
40
40
|
selector = receiver.send :html_element_selector
|
41
41
|
internal = Quince::Serialiser.serialise receiver
|
42
|
-
payload = { component: internal }.to_json
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
end
|
42
|
+
payload = { component: CGI.escapeHTML(internal) }.to_json
|
43
|
+
payload_var_name = "p"
|
44
|
+
stringify_payload = if value.take_form_values
|
45
|
+
"{...#{payload_var_name}, params: getFormValues(this)}"
|
46
|
+
else
|
47
|
+
payload_var_name
|
48
|
+
end
|
49
|
+
cb = %Q{const #{payload_var_name} = #{payload}; callRemoteEndpoint(`/api/#{owner}/#{name}`, JSON.stringify(#{stringify_payload}),`#{selector}`)}
|
50
|
+
cb += ";return false" if value.prevent_default
|
51
|
+
CGI.escape_html(cb)
|
53
52
|
when true
|
54
53
|
return key
|
55
54
|
when false, nil, Quince::Types::Undefined
|
data/lib/quince/serialiser.rb
CHANGED
@@ -2,100 +2,11 @@ module Quince
|
|
2
2
|
class Serialiser
|
3
3
|
class << self
|
4
4
|
def serialise(obj)
|
5
|
-
|
6
|
-
when Quince::Component
|
7
|
-
{
|
8
|
-
id: serialise(obj.send(:__id)),
|
9
|
-
props: serialise(obj.props),
|
10
|
-
state: serialise(obj.state),
|
11
|
-
children: serialise(obj.children),
|
12
|
-
html_element_selector: serialise(obj.send(:html_element_selector)),
|
13
|
-
}
|
14
|
-
when Array
|
15
|
-
obj.map { |e| serialise e }
|
16
|
-
when TypedStruct, Struct, OpenStruct, Hash
|
17
|
-
result = obj.each_pair.each_with_object({}) do |(k, v), ob|
|
18
|
-
case v
|
19
|
-
when Undefined
|
20
|
-
next
|
21
|
-
else
|
22
|
-
ob[k] = serialise(v)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
if result.empty? && !obj.is_a?(Hash)
|
26
|
-
obj = nil
|
27
|
-
nil
|
28
|
-
else
|
29
|
-
result
|
30
|
-
end
|
31
|
-
when Proc
|
32
|
-
obj = obj.call # is there a more efficient way of doing this?
|
33
|
-
serialise(obj)
|
34
|
-
when String
|
35
|
-
res = obj.gsub "
|
36
|
-
", "
|
37
|
-
"
|
38
|
-
res.gsub! ?", '\"'
|
39
|
-
res
|
40
|
-
else
|
41
|
-
obj
|
42
|
-
end
|
43
|
-
|
44
|
-
{ t: obj.class&.name, v: val }
|
5
|
+
Oj.dump(obj)
|
45
6
|
end
|
46
7
|
|
47
8
|
def deserialise(json)
|
48
|
-
|
49
|
-
when "String", "Integer", "Float", "NilClass", "TrueClass", "FalseClass"
|
50
|
-
json[:v]
|
51
|
-
when "Symbol"
|
52
|
-
json[:v].to_sym
|
53
|
-
when "Array"
|
54
|
-
json[:v].map { |e| deserialise e }
|
55
|
-
when "Hash"
|
56
|
-
transform_hash json[:v]
|
57
|
-
when "OpenStruct"
|
58
|
-
OpenStruct.new(**transform_hash(props))
|
59
|
-
when nil
|
60
|
-
nil
|
61
|
-
else
|
62
|
-
klass = Object.const_get(json[:t])
|
63
|
-
if klass < TypedStruct
|
64
|
-
transform_hash_for_struct(json[:v]) || {}
|
65
|
-
elsif klass < Quince::Component
|
66
|
-
instance = klass.allocate
|
67
|
-
val = json[:v]
|
68
|
-
id = deserialise val[:id]
|
69
|
-
|
70
|
-
instance.instance_variable_set :@__id, id
|
71
|
-
instance.instance_variable_set(
|
72
|
-
:@props,
|
73
|
-
klass.send(
|
74
|
-
:initialize_props,
|
75
|
-
klass,
|
76
|
-
id,
|
77
|
-
**(deserialise(val[:props]) || {}),
|
78
|
-
),
|
79
|
-
)
|
80
|
-
st = deserialise(val[:state])
|
81
|
-
instance.instance_variable_set :@state, klass::State.new(**st) if st
|
82
|
-
instance.instance_variable_set :@children, deserialise(val[:children])
|
83
|
-
instance
|
84
|
-
else
|
85
|
-
klass = Object.const_get(json[:t])
|
86
|
-
klass.new(deserialise(json[:v]))
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
private
|
92
|
-
|
93
|
-
def transform_hash(hsh)
|
94
|
-
hsh.transform_values! { |v| deserialise v }
|
95
|
-
end
|
96
|
-
|
97
|
-
def transform_hash_for_struct(hsh)
|
98
|
-
hsh.to_h { |k, v| [k.to_sym, deserialise(v)] }
|
9
|
+
Oj.load(json)
|
99
10
|
end
|
100
11
|
end
|
101
12
|
end
|
@@ -10,21 +10,23 @@ module Quince
|
|
10
10
|
def middleware=(middleware)
|
11
11
|
@middleware = middleware
|
12
12
|
Object.define_method(:expose) do |component, at:|
|
13
|
-
component = component.new if component.instance_of? Class
|
14
|
-
|
15
13
|
Quince.middleware.create_route_handler(
|
16
14
|
verb: :GET,
|
17
15
|
route: at,
|
18
|
-
|
19
|
-
|
16
|
+
) do |params|
|
17
|
+
component = component.create if component.instance_of? Class
|
18
|
+
Quince::Component.class_variable_set :@@params, params
|
19
|
+
component
|
20
|
+
end
|
20
21
|
end
|
22
|
+
Object.send :private, :expose
|
21
23
|
end
|
22
24
|
|
23
25
|
def define_constructor(const, constructor_name = const.to_s)
|
24
26
|
HtmlTagComponents.instance_eval do
|
25
27
|
define_method(constructor_name) do |*children, **props, &block_children|
|
26
28
|
new_props = { **props, Quince::Component::HTML_SELECTOR_ATTR => __id }
|
27
|
-
const.
|
29
|
+
const.create(*children, **new_props, &block_children)
|
28
30
|
end
|
29
31
|
end
|
30
32
|
end
|
data/lib/quince/version.rb
CHANGED
data/scripts.js
CHANGED
@@ -18,7 +18,11 @@ const callRemoteEndpoint = (endpoint, payload, selector) => {
|
|
18
18
|
})
|
19
19
|
}
|
20
20
|
|
21
|
-
const getFormValues = (
|
21
|
+
const getFormValues = (elem) => {
|
22
|
+
let form = elem.localName === "form" ? elem : elem.form;
|
23
|
+
if (!form) {
|
24
|
+
throw `element ${elem} should belong to a form`;
|
25
|
+
}
|
22
26
|
const fd = new FormData(form);
|
23
27
|
return Object.fromEntries(fd.entries());
|
24
28
|
}
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: quince
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joseph Johansen
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-09-
|
11
|
+
date: 2021-09-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: typed_struct
|
@@ -57,6 +57,7 @@ files:
|
|
57
57
|
- bin/setup
|
58
58
|
- lib/quince.rb
|
59
59
|
- lib/quince/attributes_by_element.rb
|
60
|
+
- lib/quince/callback.rb
|
60
61
|
- lib/quince/component.rb
|
61
62
|
- lib/quince/html_tag_components.rb
|
62
63
|
- lib/quince/serialiser.rb
|