stimulus_tag_helper 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b6abf8489a80e3128ea3a0d0a81b935e5b1b2303e0e911af50069ddaad5d84d8
4
+ data.tar.gz: 45e22a4a2b76553502ffdf0cca5b49b24f59c3e6f8139f57052b159a0e3a45b0
5
+ SHA512:
6
+ metadata.gz: 72427f49698c21330c73061f040fdd1c4eb7a630dd41b15555dc703234771dd614a00d9ade29258d88d4ffc74b33fa4f23c88d24230d62dc2daaa7770440f58e
7
+ data.tar.gz: 06c29df33bf224027b006d059ef0631693d7be19a92dddba137a004c774ebb8a91c2408d90a5f5061ba1130f2b700109b2997de8c2d9fe80077d2811332396c7
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Anton Topchii
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.
data/README.md ADDED
@@ -0,0 +1,133 @@
1
+ # stimulus_tag_helper
2
+
3
+ [![Rubocop](https://github.com/crawler/stimulus_tag_helper/workflows/Rubocop/badge.svg)](https://github.com/crawler/stimulus_tag_helper/actions)
4
+
5
+ [![Gem](https://img.shields.io/gem/v/stimulus_tag_helper.svg)](https://rubygems.org/gems/stimulus_tag_helper)
6
+
7
+ ## WARING!
8
+
9
+ This gem is in the proof of concept stage. I made it for myself, and it is not yet covered with tests and the documentation.
10
+
11
+ ## Description
12
+
13
+ The stimulusjs is a great js framework for the HTML that you already have, but once u start using it the HTML becomes pretty big as you need to extend it with the attributes that your required to tight it with controllers. So I made this modest (even more modest than the stimulus itself) helper. Currently, I use it with the Slim(http://slim-lang.com/). So for now examples will be in it.
14
+
15
+ ---
16
+
17
+ - [Quick start](#quick-start)
18
+ - [Examples](#examples)
19
+ - [Support](#support)
20
+ - [License](#license)
21
+ - [Code of conduct](#code-of-conduct)
22
+ - [Contribution guide](#contribution-guide)
23
+
24
+ ## Quick start
25
+
26
+ ```ruby
27
+ gem 'stimulus_tag_helper', github: 'crawler/stimulus_tag_helper'
28
+ ```
29
+
30
+ ### In the rails Controller
31
+
32
+ ```ruby
33
+ helper StimulusTagHelper
34
+ ```
35
+
36
+ ### In the ViewComponent
37
+
38
+ ```ruby
39
+ include StimulusTagHelper
40
+ ```
41
+
42
+ ## Examples
43
+
44
+ Some examples is taken from the real app so may be not very)
45
+
46
+ ### "Hello, Stimulus" controller from the stimulusjs homepage
47
+
48
+ ```slim
49
+ = stimulus_controller("hello", tag: "div") do |sc|
50
+ = text_field_tag('', '', **sc.target("name"))
51
+ = button_tag('Greet', type: 'button', **sc.action("click->greet"))
52
+ span[*sc.target("output")]
53
+ ```
54
+
55
+ Please note that to support nesting, and the stimulus 2.0 attributes notation, the actions and the targets will be prefixed with controller name
56
+
57
+ So example above will became:
58
+
59
+ ```html
60
+ <div data-controller="hello">
61
+ <input type="text" name="" id="" value="" data-hello-target="name" />
62
+ <button name="button" type="button" data-action="click->hello#greet">Greet</button>
63
+ <span data-hello-target="output"></span>
64
+ </div>
65
+ ```
66
+
67
+
68
+ ## "Slideshow" define controller without tag rendering
69
+
70
+ (buttons messed up for the demonstation purposes)
71
+
72
+ ```slim
73
+ = stimulus_controller("slideshow") do |sc|
74
+ div[*sc.controller_attribute]
75
+ = button_tag ' ← ', type: 'button', **sc.action("previous")
76
+ button[*sc.action("next")]
77
+ ' →
78
+ div[*sc.target("slide")] 🐵
79
+ div[*sc.target("slide")] 🙈
80
+ div[*sc.target("slide")] 🙉
81
+ div[*sc.target("slide")] 🙊
82
+ ```
83
+
84
+ Became:
85
+
86
+ ```html
87
+ <div data-controller="slideshow">
88
+ <button name="button" type="button" data-action="slideshow#previous"> ← </button>
89
+ <button data-action="slideshow#next"> → </button>
90
+ <div data-slideshow-target="slide">🐵</div>
91
+ <div data-slideshow-target="slide">🙈</div>
92
+ <div data-slideshow-target="slide">🙉</div>
93
+ <div data-slideshow-target="slide">🙊</div>
94
+ </div>
95
+ ```
96
+
97
+ ### Nested controllers
98
+
99
+ ```slim
100
+ = stimulus_controller( \
101
+ "flash", tag: "div", class: "flash flash_global", values: { "page-cached" => page_will_be_cached?, src: flash_path },
102
+ actions: %w[flash-message:connect->registerMessage flash-message:disconnect->removeMessage] \
103
+ ) do |fc|
104
+ - unless page_will_be_cached?
105
+ - flash.keys.each do |type|
106
+ .flash__message
107
+ = stimulus_controller( \
108
+ "flash-message",
109
+ tag: "div", class: "flash-message flash-message_type_#{type}",
110
+ actions: %w[animationend->next click->hide] \
111
+ ) do |fmc|
112
+ .flash-message__text = flash[type]
113
+ button.flash-message__dismiss-button[aria-label="Close flash message" *fmc.action("dismiss")]
114
+ = svg_sprite "icons/common.svg#common-times", class: "icon"
115
+
116
+ ```
117
+
118
+
119
+ ## Support
120
+
121
+ If you want to report a bug, or have ideas, feedback or questions about the gem, [let me know via GitHub issues](https://github.com/crawler/stimulus_tag_helper/issues/new) and I will do my best to provide a helpful answer. Happy hacking!
122
+
123
+ ## License
124
+
125
+ The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
126
+
127
+ ## Code of conduct
128
+
129
+ Everyone interacting in this project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](CODE_OF_CONDUCT.md).
130
+
131
+ ## Contribution guide
132
+
133
+ Pull requests are welcome!
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusTagHelper
4
+ autoload :VERSION, "stimulus_tag_helper/version"
5
+ autoload :StimulusAction, "stimulus_tag_helper/stimulus_action"
6
+ autoload :StimulusControllerBuilder, "stimulus_tag_helper/stimulus_controller_builder"
7
+
8
+ def self.base_properties
9
+ %i[controller values classes targets target actions]
10
+ end
11
+
12
+ def self.aliases_map
13
+ { values: :value, classes: :class, actions: :action }
14
+ end
15
+
16
+ def self.alias_properties
17
+ @alias_properties ||= aliases_map.values
18
+ end
19
+
20
+ def self.property_names
21
+ @property_names ||= base_properties + alias_properties
22
+ end
23
+
24
+ def self.prevent_duplicates(properties_set, name)
25
+ alias_name = StimulusTagHelper.aliases_map[name]
26
+ return unless alias_name && (properties_set[alias_name])
27
+
28
+ raise(ArgumentError, <<~STRING)
29
+ "#{name} and #{alias_name} can't be passed at same time"
30
+ STRING
31
+ end
32
+
33
+ def stimulus_controller(identifier, tag: nil, **args, &block)
34
+ raise(ArgumentError, "Missing block") unless block
35
+
36
+ builder = StimulusControllerBuilder.new(identifier: identifier, template: self)
37
+
38
+ return capture(builder, &block) unless tag
39
+
40
+ stimulus_controller_tag(identifier, tag: tag, **args) do
41
+ capture(builder, &block)
42
+ end
43
+ end
44
+
45
+ def stimulus_controller_tag(identifier, tag:, data: {}, **args, &block)
46
+ data.merge!(stimulus_controllers_property(identifier))
47
+ # class option goes to the tag
48
+ data.merge!(stimulus_properties(identifier, args.extract!(*StimulusTagHelper.property_names - %i[class])))
49
+ tag_builder.tag_string(tag, **args.merge(data: data), &block)
50
+ end
51
+
52
+ def stimulus_properties(identifier, props)
53
+ {}.tap do |data|
54
+ StimulusTagHelper.property_names.each do |name|
55
+ next unless props[name]
56
+
57
+ params = Array.wrap(props[name]).unshift(identifier)
58
+ kwparams = params.last.is_a?(Hash) ? params.pop : {}
59
+ StimulusTagHelper.prevent_duplicates(props, name)
60
+ name = name.to_s
61
+ property = name.pluralize == name ? "properties" : "property"
62
+ data.merge!(public_send("stimulus_#{name}_#{property}", *params, **kwparams))
63
+ end
64
+ end
65
+ end
66
+
67
+ def stimulus_controllers_property(*identifiers)
68
+ { controller: Array.wrap(identifiers).join(" ") }
69
+ end
70
+
71
+ alias stimulus_controller_property stimulus_controllers_property
72
+
73
+ def stimulus_values_properties(identifier, **values)
74
+ {}.tap do |properties|
75
+ values.each_pair do |name, value|
76
+ properties["#{identifier}-#{name}-value"] = value
77
+ end
78
+ end
79
+ end
80
+
81
+ alias stimulus_value_property stimulus_values_properties
82
+
83
+ def stimulus_classes_properties(identifier, **classes)
84
+ {}.tap do |properties|
85
+ classes.each_pair do |name, value|
86
+ properties["#{identifier}-#{name}-class"] = value
87
+ end
88
+ end
89
+ end
90
+
91
+ alias stimulus_class_property stimulus_classes_properties
92
+
93
+ def stimulus_targets_properties(identifier, *targets)
94
+ {}.tap do |properties|
95
+ targets.each do |target|
96
+ properties.merge!(stimulus_target_property(identifier, target))
97
+ end
98
+ end
99
+ end
100
+
101
+ def stimulus_target_property(identifier, name)
102
+ { "#{identifier}-target" => name }
103
+ end
104
+
105
+ def stimulus_actions_properties(identifier, *actions_params)
106
+ {
107
+ "action" => actions_params.map { |action_params| stimulus_action_value(identifier, action_params) }.join(" ")
108
+ }
109
+ end
110
+
111
+ alias stimulus_action_property stimulus_actions_properties
112
+
113
+ def stimulus_action_value(identifier, action_params_or_action_str)
114
+ if action_params_or_action_str.is_a?(String)
115
+ action_params_or_action_str = StimulusAction.parse(action_params_or_action_str)
116
+ end
117
+
118
+ StimulusAction.new(identifier: identifier, **action_params_or_action_str).to_s
119
+ end
120
+
121
+ property_names.each do |name|
122
+ name = name.to_s
123
+ attribute, property = name.pluralize == name ? %w[attributes properties] : %w[attribute property]
124
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
125
+ def stimulus_#{name}_#{attribute}(...) # def stimulus_value_attribute(...)
126
+ { data: stimulus_#{name}_#{property}(...) } # { data: stimulus_value_property(...) }
127
+ end # end
128
+ RUBY
129
+ end
130
+
131
+ def stimulus_attribute(...)
132
+ { data: stimulus_properties(...) }
133
+ end
134
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusTagHelper
4
+ class StimulusAction
5
+ STANDARD_PARSER =
6
+ /^(?<void>(?<event>.+?)(?<void>@(?<target>window|document))?->)?(?<identifier>.+?)(?<void>#(?<method>[^:]+?))(?<void>:(?<options>.+))?$/.freeze
7
+ NO_CONTROLLER_PARSER =
8
+ /^(?<void>(?<event>.+?)?(?<void>@(?<target>window|document))?->)?(?<method>[^#:]+?)(?<void>:(?<options>.+))?$/.freeze
9
+ class_attribute :parts, default: %i[identifier method event target options]
10
+ attr_reader(*parts)
11
+
12
+ # event is nil to let stimulusjs decide default event for the element
13
+ def initialize(identifier:, method:, event: nil, target: nil, options: nil)
14
+ parts.each do |part|
15
+ instance_variable_set(:"@#{part}", binding.local_variable_get(part))
16
+ end
17
+ end
18
+
19
+ def event_part
20
+ "#{event}#{"@#{target}" if target}"
21
+ end
22
+
23
+ def handler_part
24
+ "#{identifier}##{method}#{":#{options}" if options}"
25
+ end
26
+
27
+ def to_s
28
+ "#{event_part.presence&.+'->'}#{handler_part}".html_safe
29
+ end
30
+
31
+ def self.parse(str)
32
+ parsed =
33
+ str.include?("#") ? STANDARD_PARSER.match(str) : NO_CONTROLLER_PARSER.match(str)
34
+ raise(ArgumentError, "can't parse action string #{str}") unless parsed
35
+
36
+ parsed.named_captures.except("void").symbolize_keys
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusTagHelper
4
+ class StimulusControllerBuilder
5
+ def initialize(identifier:, template:)
6
+ @identifier = identifier
7
+ @template = template
8
+ end
9
+
10
+ StimulusTagHelper.property_names.each do |name|
11
+ name = name.to_s
12
+ attribute, property = name.pluralize == name ? %w[attributes properties] : %w[attribute property]
13
+
14
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
15
+ def #{name}_#{attribute}(...) # def value_attribute(...)
16
+ { data: #{name}_#{property}(...) } # { data: value_property(...) }
17
+ end # end
18
+
19
+ alias #{name} #{name}_#{attribute} # alias value value_attribute
20
+
21
+ def #{name}_#{property}(*args, **kws) # def value_property(*args, **kws)
22
+ @template.stimulus_#{name}_#{property}(*args.unshift(@identifier), **kws) # stimulus_value_property(@identifier, *args, **kws)
23
+ end # end
24
+ RUBY
25
+ end
26
+
27
+ def attributes(props)
28
+ { data: @template.stimulus_properties(@identifier, props) }
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusTagHelper
4
+ VERSION = "0.1.0"
5
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stimulus_tag_helper
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Anton Topchii
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-06-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 6.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 6.0.0
27
+ description:
28
+ email:
29
+ - player1@infinitevoid.net
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - LICENSE.txt
35
+ - README.md
36
+ - lib/stimulus_tag_helper.rb
37
+ - lib/stimulus_tag_helper/stimulus_action.rb
38
+ - lib/stimulus_tag_helper/stimulus_controller_builder.rb
39
+ - lib/stimulus_tag_helper/version.rb
40
+ homepage: https://github.com/crawler/stimulus_tag_helper
41
+ licenses:
42
+ - MIT
43
+ metadata:
44
+ bug_tracker_uri: https://github.com/crawler/stimulus_tag_helper/issues
45
+ changelog_uri: https://github.com/crawler/stimulus_tag_helper/releases
46
+ source_code_uri: https://github.com/crawler/stimulus_tag_helper
47
+ homepage_uri: https://github.com/crawler/stimulus_tag_helper
48
+ post_install_message:
49
+ rdoc_options: []
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: 2.7.0
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ requirements: []
63
+ rubygems_version: 3.2.15
64
+ signing_key:
65
+ specification_version: 4
66
+ summary: A form_for like, compact and elegant way to define stimulus attributes in
67
+ your views.
68
+ test_files: []