stimulus_tag_helper 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []