glimmer-dsl-web 0.10.2 → 0.10.4

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: aad28a356df583d4a4784a532111d9e4d4f3743f8855998deb69ff03521a0107
4
- data.tar.gz: 79c3a03baea98bd0774d95e4dbd049e30ddde606402b17770b037cffcbdb63ce
3
+ metadata.gz: 64fc435ce1a727a890882f43c0851f932f699a610fbfea88041e0606f1fac8b7
4
+ data.tar.gz: da253a9bccb769f519c20f53810410360cd45034722fbe5083f2847c8e5f3080
5
5
  SHA512:
6
- metadata.gz: 427e0f03396027917230ad86fbfbfc258253b31044cadb32718caab75de8230edf22c5dc628a561db08c9b1bb82729e9e8347eef097ccb67d5e823fbcf90686f
7
- data.tar.gz: e6f98162f1ab9a278547bf21d73cd861606b7595dc8ddd86cddf6901cc3b4f0d2e24af477bcbaf3bbaf870898abaa0901696e526ff040682bcede3cd595d3e07
6
+ metadata.gz: f67ac1fc2adadfd190106296d70fec7aa267368a7d33660836dde5198f9e4ad05942674707758ae10293c9c2213ad7031fd0f9ab9fc8a0ca75dd7cb8f1a75fa4
7
+ data.tar.gz: aa2115656cc49ec3eafe7f2717051dd6fda8bec038906fcbc574d25265a094231b7409ecd2c49ada131515365729f2bf6fc0331bc5cbe68e07b69cb35967307c
data/CHANGELOG.md CHANGED
@@ -1,8 +1,23 @@
1
1
  # Change Log
2
2
 
3
+ ## 0.10.4
4
+
5
+ - Support `parent.insert_at(index) {}` on `ElementProxy` and `Glimmer::Web::Component`, which is a method that inserts elements to the content of a parent element at a specified index
6
+ - Support `parent.insert(index) {}` alias for `parent.insert_at(index) {}`
7
+ - Update Hello, Mutation! sample with "Insert list item" functionality
8
+ - Update Hello, Mutation Content Data-Binding! sample with "Insert list item" functionality
9
+ - Document DOM mutation operations in README
10
+
11
+ ## 0.10.3
12
+
13
+ - Support `parent.prepend {}` on `ElementProxy` and `Glimmer::Web::Component`, which is a method that prepends elements to the content of a parent element
14
+ - Support `element.focused` & `element.focused=` methods, including `focused` data-binding support. Note that setting `element.focused=` as `true` gives an element focus, but setting it to `false` does not change focus (you would have to set another element as focused to remove focus from a previous element).
15
+ - Hello, Mutation! sample: `require 'glimmer-dsl-web/samples/hello/hello_mutation.rb'`
16
+ - Hello, Mutation Content Data-Binding! sample: `require 'glimmer-dsl-web/samples/hello/hello_mutation_content_data_binding.rb'`
17
+
3
18
  ## 0.10.2
4
19
 
5
- - Support `ElementProxy#append {}`/`Glimmer::Web::Component#append {}` as an alias for `content {}` to enable appending elements to the content of a parent element
20
+ - Support `parent.append {}` as an alias for `content {}` on `ElementProxy` and `Glimmer::Web::Component`, which is a method that appends elements to the content of a parent element
6
21
 
7
22
  ## 0.10.1
8
23
 
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=85 />](https://github.com/AndyObtiva/glimmer) Glimmer DSL for Web 0.10.2 (Beta)
1
+ # [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=85 />](https://github.com/AndyObtiva/glimmer) Glimmer DSL for Web 0.10.4 (Beta)
2
2
  ## Ruby-in-the-Browser Web Frontend Framework
3
3
  ### The "Rails" of Frontend Frameworks!!! ([Fukuoka Award Winning](https://andymaleh.blogspot.com/2025/01/glimmer-dsl-for-web-wins-in-fukuoka.html))
4
4
  #### Finally, Ruby Developer Productivity, Happiness, and Fun in the Frontend!!!
@@ -1498,6 +1498,52 @@ p(class: 'summary') {
1498
1498
 
1499
1499
  You can get/set any element property or invoke any element function by simply calling the lowercase underscored version of their name in Ruby like `input.check_validity`, `input.value`, and `input.id`.
1500
1500
 
1501
+ Additionally, these DOM mutation operations are available as methods on elements/components acting as parents:
1502
+ - `parent.append { }` : apprends elements inside the content of the parent
1503
+ - `parent.prepend { }` : prepends elements inside the content of the parent
1504
+ - `parent.insert_at(index) { }` : inserts elements inside the content of the parent (`insert` is an alias for `insert_at`)
1505
+
1506
+ Example:
1507
+
1508
+ ```ruby
1509
+ div {
1510
+ div(class: 'actions') {
1511
+ @input = input(placeholder: 'Enter list item content')
1512
+
1513
+ @append_button = button('Append list item') {
1514
+ onclick do
1515
+ @list.append { # DOM mutation operation
1516
+ li { @input.value }
1517
+ }
1518
+ @input.value = ''
1519
+ @input.focus
1520
+ end
1521
+ }
1522
+ }
1523
+
1524
+ @list = ul
1525
+ }
1526
+ ```
1527
+
1528
+ Specifically, this line:
1529
+
1530
+ ```ruby
1531
+ @list.append {
1532
+ li { @input.value }
1533
+ }
1534
+ ```
1535
+
1536
+ You can append multiple elements too. Here is a different hypothetical example:
1537
+
1538
+ ```ruby
1539
+ @some_div.append {
1540
+ h1 { @post.title }
1541
+ p { @post.content }
1542
+ }
1543
+ ```
1544
+
1545
+ DOM mutation operations are useful for adding elements after the fact of initial rendering of the page as triggered by a user action or some system timer.
1546
+
1501
1547
  Next, check out [Samples](#samples).
1502
1548
 
1503
1549
  Note that for serious app usage, it is recommended to build [components](#hello-component) and use the [`glimmer_component` Rails Helper](#hello-glimmer_component-rails-helper) to embed the top-level Web Frontend component in a Rails View.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.10.2
1
+ 0.10.4
@@ -2,11 +2,11 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: glimmer-dsl-web 0.10.2 ruby lib
5
+ # stub: glimmer-dsl-web 0.10.4 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "glimmer-dsl-web".freeze
9
- s.version = "0.10.2".freeze
9
+ s.version = "0.10.4".freeze
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib".freeze]
@@ -50,6 +50,8 @@ Gem::Specification.new do |s|
50
50
  "lib/glimmer-dsl-web/samples/hello/hello_input_date_time.rb",
51
51
  "lib/glimmer-dsl-web/samples/hello/hello_input_date_time_data_binding.rb",
52
52
  "lib/glimmer-dsl-web/samples/hello/hello_modal.rb",
53
+ "lib/glimmer-dsl-web/samples/hello/hello_mutation.rb",
54
+ "lib/glimmer-dsl-web/samples/hello/hello_mutation_content_data_binding.rb",
53
55
  "lib/glimmer-dsl-web/samples/hello/hello_observer.rb",
54
56
  "lib/glimmer-dsl-web/samples/hello/hello_observer_data_binding.rb",
55
57
  "lib/glimmer-dsl-web/samples/hello/hello_page_component_link/conference_talk_page.rb",
@@ -117,7 +119,7 @@ Gem::Specification.new do |s|
117
119
 
118
120
  s.specification_version = 4
119
121
 
120
- s.add_runtime_dependency(%q<glimmer>.freeze, ["~> 2.8.2".freeze])
122
+ s.add_runtime_dependency(%q<glimmer>.freeze, ["~> 2.8.3".freeze])
121
123
  s.add_runtime_dependency(%q<glimmer-dsl-xml>.freeze, ["~> 1.4.0".freeze])
122
124
  s.add_runtime_dependency(%q<glimmer-dsl-css>.freeze, ["~> 1.5.2".freeze])
123
125
  s.add_runtime_dependency(%q<opal>.freeze, [">= 1.8.2".freeze, "< 2.0.0".freeze])
@@ -12,6 +12,11 @@ module Glimmer
12
12
  end
13
13
 
14
14
  def add_content(parent, keyword, *args, &block)
15
+ options = args.last.is_a?(Hash) ? args.last : {}
16
+ parent_original_add_child_mutation = parent&.add_child_mutation
17
+ parent_original_insert_index = parent&.insert_index
18
+ parent&.add_child_mutation = options[:add_child_mutation] if options[:add_child_mutation]
19
+ parent&.insert_index = options[:insert_index] if options[:insert_index]
15
20
  if parent.bulk_render? || parent.rendered? || parent.skip_content_on_render_blocks?
16
21
  return_value = super(parent, keyword, *args, &block)
17
22
  parent.add_text_content(return_value, on_empty: true) if return_value.is_a?(String)
@@ -20,6 +25,9 @@ module Glimmer
20
25
  else
21
26
  parent.add_content_on_render(&block)
22
27
  end
28
+ ensure
29
+ parent&.add_child_mutation = parent_original_add_child_mutation
30
+ parent&.insert_index = parent_original_insert_index
23
31
  end
24
32
  end
25
33
  end
@@ -512,11 +512,28 @@ module Glimmer
512
512
  end
513
513
  alias append content
514
514
 
515
+ def prepend(bulk_render: false, &block)
516
+ @markup_root.prepend(bulk_render:, &block)
517
+ end
518
+
519
+ def insert_at(index, bulk_render: false, &block)
520
+ @markup_root.insert_at(index, bulk_render:, &block)
521
+ end
522
+ alias insert insert_at
523
+
515
524
  def remove_all_listeners
516
525
  data_bindings.each do |option_binding, model_binding|
517
526
  option_binding.unregister_all_observables
518
527
  end
519
528
  end
529
+
530
+ def add_child_mutation
531
+ markup_root.add_child_mutation
532
+ end
533
+
534
+ def add_child_mutation=(value)
535
+ markup_root.add_child_mutation = value
536
+ end
520
537
 
521
538
  def method_missing(method_name, *args, &block)
522
539
  if can_handle_observation_request?(method_name)
@@ -176,6 +176,7 @@ module Glimmer
176
176
  REGEX_CLASS_NAME_SUB_PROPERTY = /^(class_name)_(.*)$/
177
177
 
178
178
  attr_reader :keyword, :parent, :parent_component, :ancestor_component, :component, :args, :options, :slot, :children, :enabled, :foreground, :background, :removed, :rendered
179
+ attr_accessor :insert_index # to be used when add_child_mutation is insert_at
179
180
  alias rendered? rendered
180
181
  alias removed? removed
181
182
 
@@ -223,7 +224,14 @@ module Glimmer
223
224
 
224
225
  # Executes for the parent of a child that just got added
225
226
  def post_initialize_child(child)
226
- @children << child
227
+ case parent&.add_child_mutation
228
+ when :prepend
229
+ @children.prepend(child)
230
+ when :insert_at
231
+ @children.insert(parent&.insert_index, child)
232
+ else # append
233
+ @children << child
234
+ end
227
235
  child.render if !bulk_render? && !render_after_create?
228
236
  end
229
237
 
@@ -445,7 +453,7 @@ module Glimmer
445
453
  brand_new ||= @dom.nil? || !options[:parent].to_s.empty? || (old_element = dom_element).empty?
446
454
  build_dom(layout: !custom_parent_dom_element) # TODO handle custom parent layout by passing parent instead of parent dom element
447
455
  if brand_new
448
- attach(the_parent_dom_element)
456
+ attach(the_parent_dom_element, add_child_mutation: self.parent&.add_child_mutation, insert_index: self.parent&.insert_index)
449
457
  else
450
458
  reattach(old_element)
451
459
  end
@@ -458,14 +466,36 @@ module Glimmer
458
466
  end
459
467
  alias rerender render
460
468
 
461
- def attach(the_parent_dom_element)
462
- the_parent_dom_element.append(@dom)
469
+ def attach(the_parent_dom_element, add_child_mutation: :append, insert_index: nil)
470
+ case add_child_mutation
471
+ when :prepend
472
+ the_parent_dom_element.prepend(@dom)
473
+ when :insert_at
474
+ if insert_index == the_parent_dom_element.children.size
475
+ the_parent_dom_element.append(@dom)
476
+ else
477
+ the_parent_dom_element.children.eq(insert_index).before(@dom)
478
+ end
479
+ else # append
480
+ the_parent_dom_element.append(@dom)
481
+ end
463
482
  end
464
483
 
465
484
  def reattach(old_element)
466
485
  old_element.replace_with(@dom)
467
486
  end
468
487
 
488
+ def focused
489
+ @focused = `#{dom_element}[0] == document.activeElement`
490
+ @focused
491
+ end
492
+
493
+ def focused=(value)
494
+ @focused = value
495
+ focus if @focused
496
+ @focused
497
+ end
498
+
469
499
  def mark_rendered
470
500
  @rendered = true
471
501
  children.each(&:mark_rendered) if bulk_render?
@@ -545,15 +575,33 @@ module Glimmer
545
575
  html_options
546
576
  end
547
577
 
548
- def content(bulk_render: false, &block)
578
+ def add_child_mutation
579
+ @add_child_mutation || :append
580
+ end
581
+
582
+ def add_child_mutation=(value)
583
+ @add_child_mutation = value
584
+ end
585
+
586
+ def content(bulk_render: false, add_child_mutation: :append, insert_index: nil, &block)
549
587
  original_bulk_render = options[:bulk_render]
550
588
  options[:bulk_render] = bulk_render if rendered?
551
- return_value = Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::Web::ElementExpression.new, keyword, &block)
589
+ return_value = Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::Web::ElementExpression.new, keyword, add_child_mutation:, insert_index:, &block)
552
590
  options[:bulk_render] = original_bulk_render if rendered?
553
591
  return_value
554
592
  end
555
593
  alias append content
556
594
 
595
+ def prepend(bulk_render: false, &block)
596
+ content(bulk_render:, add_child_mutation: :prepend, &block)
597
+ end
598
+
599
+ def insert_at(index, bulk_render: false, &block)
600
+ index = index.to_i if index.is_a?(String) && index.match(/^\d+$/)
601
+ content(bulk_render:, add_child_mutation: :insert_at, insert_index: index, &block)
602
+ end
603
+ alias insert insert_at
604
+
557
605
  # Subclasses must override with their own mappings
558
606
  def observation_request_to_event_mapping
559
607
  {}
@@ -38,131 +38,135 @@ end
38
38
  # generates a new Glimmer HTML DSL keyword that matches the lowercase underscored version
39
39
  # of the name of the class. AddressForm generates address_form_with_slots keyword, which can be used
40
40
  # elsewhere in Glimmer HTML DSL code as done inside HelloComponentSlots below.
41
- class AddressFormWithSlots
42
- include Glimmer::Web::Component
43
-
44
- option :address
45
-
46
- # markup block provides the content of the
47
- markup {
48
- div {
49
- # designate this div as a slot with the slot name :address_header to enable
50
- # consumers to contribute elements to `address_header {...}` slot
51
- div(slot: :address_header, class: 'address-form-header')
52
-
53
- div(class: 'address-field-container', style: {display: :grid, grid_auto_columns: '80px 260px'}) {
54
- label('Full Name: ', for: 'full-name-field')
55
- input(id: 'full-name-field') {
56
- value <=> [address, :full_name]
57
- }
58
-
59
- @somelabel = label('Street: ', for: 'street-field')
60
- input(id: 'street-field') {
61
- value <=> [address, :street]
62
- }
63
-
64
- label('Street 2: ', for: 'street2-field')
65
- textarea(id: 'street2-field') {
66
- value <=> [address, :street2]
67
- }
41
+ unless Object.const_defined?(:AddressFormWithSlots)
42
+ class AddressFormWithSlots
43
+ include Glimmer::Web::Component
44
+
45
+ option :address
46
+
47
+ # markup block provides the content of the
48
+ markup {
49
+ div {
50
+ # designate this div as a slot with the slot name :address_header to enable
51
+ # consumers to contribute elements to `address_header {...}` slot
52
+ div(slot: :address_header, class: 'address-form-header')
68
53
 
69
- label('City: ', for: 'city-field')
70
- input(id: 'city-field') {
71
- value <=> [address, :city]
54
+ div(class: 'address-field-container', style: {display: :grid, grid_auto_columns: '80px 260px'}) {
55
+ label('Full Name: ', for: 'full-name-field')
56
+ input(id: 'full-name-field') {
57
+ value <=> [address, :full_name]
58
+ }
59
+
60
+ @somelabel = label('Street: ', for: 'street-field')
61
+ input(id: 'street-field') {
62
+ value <=> [address, :street]
63
+ }
64
+
65
+ label('Street 2: ', for: 'street2-field')
66
+ textarea(id: 'street2-field') {
67
+ value <=> [address, :street2]
68
+ }
69
+
70
+ label('City: ', for: 'city-field')
71
+ input(id: 'city-field') {
72
+ value <=> [address, :city]
73
+ }
74
+
75
+ label('State: ', for: 'state-field')
76
+ select(id: 'state-field') {
77
+ Address::STATES.each do |state_code, state|
78
+ option(value: state_code) { state }
79
+ end
80
+
81
+ value <=> [address, :state_code]
82
+ }
83
+
84
+ label('Zip Code: ', for: 'zip-code-field')
85
+ input(id: 'zip-code-field', type: 'number', min: '0', max: '99999') {
86
+ value <=> [address, :zip_code,
87
+ on_write: :to_s,
88
+ ]
89
+ }
72
90
  }
73
91
 
74
- label('State: ', for: 'state-field')
75
- select(id: 'state-field') {
76
- Address::STATES.each do |state_code, state|
77
- option(value: state_code) { state }
78
- end
79
-
80
- value <=> [address, :state_code]
92
+ div(style: {margin: 5}) {
93
+ inner_text <= [address, :summary,
94
+ computed_by: address.members + ['state_code'],
95
+ ]
81
96
  }
82
97
 
83
- label('Zip Code: ', for: 'zip-code-field')
84
- input(id: 'zip-code-field', type: 'number', min: '0', max: '99999') {
85
- value <=> [address, :zip_code,
86
- on_write: :to_s,
87
- ]
88
- }
89
- }
90
-
91
- div(style: {margin: 5}) {
92
- inner_text <= [address, :summary,
93
- computed_by: address.members + ['state_code'],
94
- ]
98
+ # designate this div as a slot with the slot name :address_footer to enable
99
+ # consumers to contribute elements to `address_footer {...}` slot
100
+ div(slot: :address_footer, class: 'address-form-footer')
95
101
  }
96
-
97
- # designate this div as a slot with the slot name :address_footer to enable
98
- # consumers to contribute elements to `address_footer {...}` slot
99
- div(slot: :address_footer, class: 'address-form-footer')
100
- }
101
- }
102
-
103
- style {
104
- r('.address-field-container *') {
105
- margin 5
106
102
  }
107
- r('.address-field-container input, .address-field-container select') {
108
- grid_column '2'
103
+
104
+ style {
105
+ r('.address-field-container *') {
106
+ margin 5
107
+ }
108
+ r('.address-field-container input, .address-field-container select') {
109
+ grid_column '2'
110
+ }
109
111
  }
110
- }
112
+ end
111
113
  end
112
114
 
113
115
  # HelloComponentSlots Glimmer Web Component (View component)
114
116
  #
115
117
  # This View component represents the main page being rendered,
116
118
  # as done by its `render` class method below
117
- class HelloComponentSlots
118
- include Glimmer::Web::Component
119
-
120
- before_render do
121
- @shipping_address = Address.new(
122
- full_name: 'Johnny Doe',
123
- street: '3922 Park Ave',
124
- street2: 'PO BOX 8382',
125
- city: 'San Diego',
126
- state: 'California',
127
- zip_code: '91913',
128
- )
129
- @billing_address = Address.new(
130
- full_name: 'John C Doe',
131
- street: '123 Main St',
132
- street2: 'Apartment 3C',
133
- city: 'San Diego',
134
- state: 'California',
135
- zip_code: '91911',
136
- )
137
- end
138
-
139
- markup {
140
- div {
141
- address_form_with_slots(address: @shipping_address) {
142
- address_header { # contribute elements to the address_header component slot
143
- h1('Shipping Address')
144
- legend('This is the address that is used for shipping your purchase.', style: {margin_bottom: 10})
145
- }
146
- address_footer { # contribute elements to the address_footer component slot
147
- p {
148
- sub("#{strong('Note:')} #{em('Purchase will be returned if the Shipping Address does not accept it in one week.')}")
119
+ unless Object.const_defined?(:HelloComponentSlots)
120
+ class HelloComponentSlots
121
+ include Glimmer::Web::Component
122
+
123
+ before_render do
124
+ @shipping_address = Address.new(
125
+ full_name: 'Johnny Doe',
126
+ street: '3922 Park Ave',
127
+ street2: 'PO BOX 8382',
128
+ city: 'San Diego',
129
+ state: 'California',
130
+ zip_code: '91913',
131
+ )
132
+ @billing_address = Address.new(
133
+ full_name: 'John C Doe',
134
+ street: '123 Main St',
135
+ street2: 'Apartment 3C',
136
+ city: 'San Diego',
137
+ state: 'California',
138
+ zip_code: '91911',
139
+ )
140
+ end
141
+
142
+ markup {
143
+ div {
144
+ address_form_with_slots(address: @shipping_address) {
145
+ address_header { # contribute elements to the address_header component slot
146
+ h1('Shipping Address')
147
+ legend('This is the address that is used for shipping your purchase.', style: {margin_bottom: 10})
148
+ }
149
+ address_footer { # contribute elements to the address_footer component slot
150
+ p {
151
+ sub("#{strong('Note:')} #{em('Purchase will be returned if the Shipping Address does not accept it in one week.')}")
152
+ }
149
153
  }
150
154
  }
151
- }
152
-
153
- address_form_with_slots(address: @billing_address) {
154
- address_header { # contribute elements to the address_header component slot
155
- h1('Billing Address')
156
- legend('This is the address that is used for your billing method (e.g. credit card).', style: {margin_bottom: 10})
157
- }
158
- address_footer { # contribute elements to the address_footer component slot
159
- p {
160
- sub("#{strong('Note:')} #{em('Payment will fail if payment method does not match the Billing Address.')}")
155
+
156
+ address_form_with_slots(address: @billing_address) {
157
+ address_header { # contribute elements to the address_header component slot
158
+ h1('Billing Address')
159
+ legend('This is the address that is used for your billing method (e.g. credit card).', style: {margin_bottom: 10})
160
+ }
161
+ address_footer { # contribute elements to the address_footer component slot
162
+ p {
163
+ sub("#{strong('Note:')} #{em('Payment will fail if payment method does not match the Billing Address.')}")
164
+ }
161
165
  }
162
166
  }
163
167
  }
164
168
  }
165
- }
169
+ end
166
170
  end
167
171
 
168
172
  Document.ready? do
@@ -0,0 +1,95 @@
1
+ require 'glimmer-dsl-web'
2
+
3
+ unless Object.const_defined?(:HelloMutation) # this is only needed in sample selector app due to file reloading, but not in real apps.
4
+ class HelloMutation
5
+ include Glimmer::Web::Component
6
+
7
+ after_render do
8
+ @append_button.disabled = @prepend_button.disabled = @insert_button.disabled = true
9
+ end
10
+
11
+ markup {
12
+ div {
13
+ h1('Hello, Mutation!')
14
+
15
+ p {
16
+ 'Add items to a list via element DOM mutations.'
17
+ }
18
+
19
+ div(class: 'actions') {
20
+ @input = input(placeholder: 'Enter list item content') {
21
+ oninput do
22
+ if @input.value.to_s.strip == ''
23
+ @append_button.disabled = @prepend_button.disabled = @insert_button.disabled = true
24
+ else
25
+ @append_button.disabled = @prepend_button.disabled = @insert_button.disabled = false
26
+ end
27
+ end
28
+ }
29
+
30
+ @append_button = button('Append list item') {
31
+ onclick do
32
+ @list.append {
33
+ li { @input.value }
34
+ }
35
+ @append_button.disabled = @prepend_button.disabled = @insert_button.disabled = true
36
+ @input.value = ''
37
+ @input.focus
38
+ end
39
+ }
40
+
41
+ @prepend_button = button('Prepend list item') {
42
+ onclick do
43
+ @list.prepend {
44
+ li { @input.value }
45
+ }
46
+ @append_button.disabled = @prepend_button.disabled = @insert_button.disabled = true
47
+ @input.value = ''
48
+ @input.focus
49
+ end
50
+ }
51
+
52
+ @insert_button = button('Insert list item') {
53
+ onclick do
54
+ index = [@insert_index_input.value.to_i, @list.children.size].min
55
+ @list.insert_at(index) {
56
+ li { @input.value }
57
+ }
58
+ @append_button.disabled = @prepend_button.disabled = @insert_button.disabled = true
59
+ @input.value = ''
60
+ @input.focus
61
+ end
62
+ }
63
+
64
+ label(for: 'insert-index-input') { 'at index: ' }
65
+ @insert_index_input = input(id: 'insert-index-input', type: 'number', value: 0, min: 0) {
66
+ oninput do
67
+ max_value = @list.children.size
68
+ @insert_index_input.value = max_value if @insert_index_input.value.to_i > max_value
69
+ end
70
+ }
71
+ }
72
+
73
+ @list = ul
74
+ }
75
+ }
76
+
77
+ style {
78
+ r('.actions input, .actions button') {
79
+ margin '10px 10px 10px 0'
80
+ }
81
+
82
+ r('.actions input#insert-index-input') {
83
+ width 40
84
+ }
85
+
86
+ r('.actions label[for=insert-index-input]') {
87
+ margin_left -5
88
+ }
89
+ }
90
+ end
91
+ end
92
+
93
+ Document.ready? do
94
+ HelloMutation.render
95
+ end
@@ -0,0 +1,125 @@
1
+ require 'glimmer-dsl-web'
2
+
3
+ unless Object.const_defined?(:ListPresenter) # this is only needed in sample selector app due to file reloading, but not in real apps.
4
+ class ListPresenter
5
+ attr_accessor :list, :new_list_item, :insert_index
6
+
7
+ def initialize
8
+ @list = []
9
+ @new_list_item = ''
10
+ @insert_index = 0
11
+ end
12
+
13
+ def append_list_item
14
+ list.push(new_list_item)
15
+ clear_new_list_item
16
+ end
17
+
18
+ def prepend_list_item
19
+ list.prepend(new_list_item)
20
+ clear_new_list_item
21
+ end
22
+
23
+ def insert_list_item
24
+ list.insert(insert_index, new_list_item)
25
+ clear_new_list_item
26
+ end
27
+
28
+ def clear_new_list_item
29
+ self.new_list_item = ''
30
+ end
31
+
32
+ def entering_new_list_item?
33
+ new_list_item.empty?
34
+ end
35
+ alias edit_list_disabled? entering_new_list_item?
36
+
37
+ def max_insert_index
38
+ list.size
39
+ end
40
+ end
41
+ end
42
+
43
+ unless Object.const_defined?(:HelloMutationContentDataBinding) # this is only needed in sample selector app due to file reloading, but not in real apps.
44
+ class HelloMutationContentDataBinding
45
+ include Glimmer::Web::Component
46
+
47
+ before_render do
48
+ @list_presenter = ListPresenter.new
49
+ end
50
+
51
+ markup {
52
+ div {
53
+ h1('Hello, Mutation Content Data-Binding!')
54
+
55
+ p {
56
+ 'Add items to a list via content data-binding (which performs implicit DOM mutations).'
57
+ }
58
+
59
+ div(class: 'actions') {
60
+ input(placeholder: 'Enter list item content') {
61
+ value <=> [@list_presenter, :new_list_item]
62
+ focused <= [@list_presenter, :entering_new_list_item?, computed_by: :new_list_item]
63
+ }
64
+
65
+ button('Append list item') {
66
+ disabled <= [@list_presenter, :edit_list_disabled?, computed_by: :new_list_item]
67
+
68
+ onclick do
69
+ @list_presenter.append_list_item
70
+ end
71
+ }
72
+
73
+ button('Prepend list item') {
74
+ disabled <= [@list_presenter, :edit_list_disabled?, computed_by: :new_list_item]
75
+
76
+ onclick do
77
+ @list_presenter.prepend_list_item
78
+ end
79
+ }
80
+
81
+ button('Insert list item') {
82
+ disabled <= [@list_presenter, :edit_list_disabled?, computed_by: :new_list_item]
83
+
84
+ onclick do
85
+ @list_presenter.insert_list_item
86
+ end
87
+ }
88
+
89
+ label(for: 'insert-index-input') { 'at index: ' }
90
+ input(id: 'insert-index-input', type: 'number') {
91
+ value <=> [@list_presenter, :insert_index]
92
+ min 0
93
+ max <= [@list_presenter, :max_insert_index, computed_by: :list]
94
+ }
95
+ }
96
+
97
+ ul {
98
+ content(@list_presenter, :list) {
99
+ @list_presenter.list.each do |list_item|
100
+ li { list_item }
101
+ end
102
+ }
103
+ }
104
+ }
105
+ }
106
+
107
+ style {
108
+ r('.actions input, .actions button') {
109
+ margin '10px 10px 10px 0'
110
+ }
111
+
112
+ r('.actions input#insert-index-input') {
113
+ width 40
114
+ }
115
+
116
+ r('.actions label[for=insert-index-input]') {
117
+ margin_left -5
118
+ }
119
+ }
120
+ end
121
+ end
122
+
123
+ Document.ready? do
124
+ HelloMutationContentDataBinding.render
125
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glimmer-dsl-web
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.2
4
+ version: 0.10.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Maleh
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: 2.8.2
18
+ version: 2.8.3
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: 2.8.2
25
+ version: 2.8.3
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: glimmer-dsl-xml
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -323,6 +323,8 @@ files:
323
323
  - lib/glimmer-dsl-web/samples/hello/hello_input_date_time.rb
324
324
  - lib/glimmer-dsl-web/samples/hello/hello_input_date_time_data_binding.rb
325
325
  - lib/glimmer-dsl-web/samples/hello/hello_modal.rb
326
+ - lib/glimmer-dsl-web/samples/hello/hello_mutation.rb
327
+ - lib/glimmer-dsl-web/samples/hello/hello_mutation_content_data_binding.rb
326
328
  - lib/glimmer-dsl-web/samples/hello/hello_observer.rb
327
329
  - lib/glimmer-dsl-web/samples/hello/hello_observer_data_binding.rb
328
330
  - lib/glimmer-dsl-web/samples/hello/hello_page_component_link/conference_talk_page.rb