Last active
March 19, 2021 11:35
-
-
Save frank-who/6f083f84e6f2daea420b9d4b128d7934 to your computer and use it in GitHub Desktop.
Stimulus slim view helpers
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# app/helpers/application_helper.rb | |
module ApplicationHelper | |
include StimulusHelper::HelperMethods | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
div*stim(:search).controller(data_map: { url: request.url }) | |
= simple_form_for :search, | |
url: search_path, | |
remote: true, | |
html: \ | |
stim(:search) \ | |
.target(:searchForm) \ | |
.ajax(before: :showLoader, success: :showResults, error: :showError) \ | |
.html(class: 'o-form') do |f| | |
= f.input :q, | |
input_html: stim(:search) \ | |
.target(:input) \ | |
.focus(:focusSearchInput) \ | |
.html(class: 'a-input -search') | |
= f.button :button, 'Search', class: 'a-btn -hollow' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class StimulusHelper < BasicObject | |
module HelperMethods | |
def stim(controller) | |
helper_class = "#{controller}_stimulus_helper" | |
if !File.exists?(Rails.root.join('app', 'lib', 'stimulus_helpers', "#{helper_class}.rb")) | |
helper_class = 'stimulus_helper' | |
end | |
instance_variable_get(:"@_stimulus_#{controller}") || | |
instance_variable_set(:"@_stimulus_#{controller}", helper_class.classify.constantize.new(controller)) | |
end | |
def stims(*list) | |
return if list.nil? | |
result = Hash.new() | |
list.each { |h| h.each { |k, v| Array(result[k] ||= []).push(v) } } | |
result.transform_values { |v| v.size == 1 ? v.first : v } | |
end | |
end | |
attr_reader \ | |
:identifier, | |
:attr_hash | |
def initialize(identifier, attr_hash={}) | |
@identifier = identifier | |
@attr_hash = attr_hash | |
end | |
def controller(html_attrs={}) | |
self_class.new(identifier, attr_hash.merge(controller_hash(html_attrs))) | |
end | |
def target(targets, html_attrs={}) | |
self_class.new(identifier, attr_hash.merge(target_hash(targets, html_attrs))) | |
end | |
def target2(targets, html_attrs={}) | |
self_class.new(identifier, attr_hash.merge(target2_hash(targets, html_attrs))) | |
end | |
def html(html_attrs) | |
self_class.new(identifier, attr_hash.merge(html_attrs)) | |
end | |
def ajax(actions) | |
actions = actions.map { |k, v| ["ajax:#{k}".to_sym, v] }.to_h | |
self_class.new(identifier, attr_hash.merge(action_hash(actions))) | |
end | |
def action(actions) | |
self_class.new(identifier, attr_hash.merge(action_hash(actions))) | |
end | |
def blur(actions) | |
self_class.new(identifier, attr_hash.merge(action_hash(blur: actions))) | |
end | |
def click(actions) | |
self_class.new(identifier, attr_hash.merge(action_hash(click: actions))) | |
end | |
def change(actions) | |
self_class.new(identifier, attr_hash.merge(action_hash(change: actions))) | |
end | |
def focus(actions) | |
self_class.new(identifier, attr_hash.merge(action_hash(focus: actions))) | |
end | |
def input(actions) | |
self_class.new(identifier, attr_hash.merge(action_hash(input: actions))) | |
end | |
def keydown(actions) | |
self_class.new(identifier, attr_hash.merge(action_hash(keydown: actions))) | |
end | |
def keyup(actions) | |
self_class.new(identifier, attr_hash.merge(action_hash(keyup: actions))) | |
end | |
private | |
def self_class | |
(class << self; self end).superclass | |
end | |
def method_missing(*args, &block) | |
reload! unless loaded? | |
@target_hash.send(*args, &block) | |
end | |
def loaded? | |
!!@loaded | |
end | |
def reload! | |
@target_hash = attr_hash | |
@loaded = true | |
end | |
# ==== Hash builders | |
def controller_string | |
identifier.to_s.dasherize | |
end | |
def controller_hash(html_attrs={}) | |
data_map = controller_attributes_for(:data_map, html_attrs: html_attrs) # TODO: Deprecate: Progressively enhance data_map to populate values | |
values = controller_attributes_for(:values, suffix: '-value', html_attrs: html_attrs) | |
classes = controller_attributes_for(:classes, suffix: '-class', html_attrs: html_attrs) | |
{ 'data-controller': controller_string } | |
.merge(html_attrs || {}) | |
.merge(data_map) | |
.merge(values) | |
.merge(classes) | |
end | |
def target_string(target) | |
if target.to_s.match?(/\./) | |
target | |
else | |
[controller_string, target.to_s.camelize(:lower)].join('.') | |
end | |
end | |
def target_hash(targets, html_attrs={}) | |
if targets.blank? | |
{} | |
else | |
targets = [targets].flatten.map { |t| target_string(t) }.join(' ') | |
{ 'data-target': targets }.merge(html_attrs || {}) | |
end | |
end | |
def target2_string(target) | |
target.to_s.camelize(:lower) | |
end | |
def target2_hash(targets, html_attrs={}) | |
if targets.blank? | |
{} | |
else | |
targets = [targets].flatten.map { |t| target2_string(t) }.join(' ') | |
{ "data-#{controller_string}-target": targets }.merge(html_attrs || {}) | |
end | |
end | |
def action_string(event, action) | |
return if action.blank? | |
if action.is_a?(::Array) | |
action.map { |a| [event, [controller_string, a.to_s.camelize(:lower)].join('#')].join('->') }.join(' ') | |
else | |
[event, [controller_string, action.to_s.camelize(:lower)].join('#')].join('->') | |
end | |
end | |
def action_hash(actions) | |
if actions.blank? | |
{} | |
else | |
{ 'data-action': actions.map { |k, v| action_string(k, v) }.join(' ') } | |
end | |
end | |
def controller_attributes_for(key, suffix: nil, html_attrs: {}) | |
[html_attrs.delete(key) || {}].each_with_object({}) do |i, h| | |
i.each { |k, v| h["data-#{controller_string}-#{k.to_s.underscore.dasherize}#{suffix}".to_sym] = v } | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'rails_helper' | |
SingleCov.covered! | |
RSpec.describe StimulusHelper do | |
subject { described_class.new(:folder__controller) } | |
describe 'full usage' do | |
let(:call) do | |
call = subject | |
.controller(data_map: { id: 123 }, classes: { will_hide: 'u-hide' }, values: { item_id: 123 }, id: 'element') | |
.target(:moving_target, class: 'css2') | |
.target2(:moving_target, class: 'css2') | |
.action(click: :doSomething) | |
.html(role: 'user') | |
end | |
it do | |
expect(call.keys.map(&:to_s).sort).to match_array( | |
%w[ | |
class | |
data-action | |
data-controller | |
data-folder--controller-id | |
data-folder--controller-target | |
data-folder--controller-will-hide-class | |
data-folder--controller-item-id-value | |
data-target | |
id | |
role | |
] | |
) | |
end | |
it { expect(call[:'data-controller']).to eq('folder--controller') } | |
it { expect(call[:'data-target']).to eq('folder--controller.movingTarget') } | |
it { expect(call[:'data-folder--controller-target']).to eq('movingTarget') } | |
it { expect(call[:'data-action']).to eq('click->folder--controller#doSomething') } | |
it { expect(call[:'data-folder--controller-id']).to eq(123) } | |
it { expect(call[:'data-folder--controller-will-hide-class']).to eq('u-hide') } | |
it { expect(call[:'data-folder--controller-item-id-value']).to eq(123) } | |
it { expect(call[:class]).to eq('css2') } | |
it { expect(call[:id]).to eq('element') } | |
it { expect(call[:role]).to eq('user') } | |
end | |
describe ".target('')" do | |
it { expect(subject.target('').to_h).to eq({}) } | |
end | |
describe ".target2('')" do | |
it { expect(subject.target2('').to_h).to eq({}) } | |
end | |
describe ".action('')" do | |
it { expect(subject.action('').to_h).to eq({}) } | |
end | |
describe '.ajax' do | |
it { expect(subject.ajax(success: :doSomething, complete: :doSomethingElse).to_h).to eq({ 'data-action': 'ajax:success->folder--controller#doSomething ajax:complete->folder--controller#doSomethingElse' }) } | |
end | |
describe '.click' do | |
it { expect(subject.click(:do_something).to_h).to eq({ 'data-action': 'click->folder--controller#doSomething' }) } | |
end | |
describe '.change' do | |
it { expect(subject.change(:do_something).to_h).to eq({ 'data-action': 'change->folder--controller#doSomething' }) } | |
end | |
describe '.focus' do | |
it { expect(subject.focus(:do_something).to_h).to eq({ 'data-action': 'focus->folder--controller#doSomething' }) } | |
end | |
describe '.input' do | |
it { expect(subject.input(:do_something).to_h).to eq({ 'data-action': 'input->folder--controller#doSomething' }) } | |
end | |
describe '.keydown' do | |
it { expect(subject.keydown(:do_something).to_h).to eq({ 'data-action': 'keydown->folder--controller#doSomething' }) } | |
end | |
describe '.keyup' do | |
it { expect(subject.keyup(%i[do_something another]).to_h).to eq({ 'data-action': 'keyup->folder--controller#doSomething keyup->folder--controller#another' }) } | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment