Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow injecting a custom renderer class #166

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,6 @@ Naming/PredicateName:
Naming/VariableName:
Enabled: true

Layout/AlignParameters:
Enabled: true

Layout/BlockAlignment:
Enabled: true
EnforcedStyleAlignWith: start_of_block
Expand Down Expand Up @@ -89,6 +86,9 @@ Layout/EmptyLines:
Layout/EmptyLinesAroundAccessModifier:
Enabled: true

Layout/ParameterAlignment:
Enabled: true

Layout/SpaceAroundBlockParameters:
Enabled: true

Expand Down
44 changes: 42 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ This gem has been inspired by our Rails development practices at [Ouvrages](http
- [Helpers](#helpers)
- [Component partials](#component-partials)
- [Namespacing components](#namespacing-components)
- [Custom Renderer](#custom-renderer)
- [Stimulus integration](#stimulus-integration)
- [Internationalization](#internationalization)
- [Available locales configuration](#available-locales-configuration)
Expand Down Expand Up @@ -182,7 +183,7 @@ Each component comes with a Ruby `module`. You can use it to set properties:

module ButtonComponent
extend ComponentHelper

property :href, required: true
property :text, default: 'My button'
end
Expand All @@ -204,7 +205,7 @@ If your partial becomes too complex and you want to extract logic from it, you m

module ButtonComponent
extend ComponentHelper

property :href, required: true
property :text, default: 'My button'

Expand Down Expand Up @@ -257,6 +258,45 @@ rails generate component admin/header

This will create the component in an `admin` folder, and name its Ruby module `AdminHeaderComponent`.

### Custom renderer

Komponent supports using a custom renderer if you want to customize rendering behaviour.

For instance, you can use this to add I18n to your properties:

```rb
# frontend/components/button/button_component.rb

module ButtonComponent
extend ComponentHelper

property :text, default: 'My button', custom_localize: true
end
```

```rb
# somwhere inside your app, e.g. app/models/localized_component_renderer.rb

class LocalizedComponentRenderer < Komponent::ComponentRenderer
def assign_property_value(property_name, property_options, locals)
if property_options[:custom_localize]
original_value = locals[property_name]
locals[property_name] = I18n.t(property_name, default: original_value)
else
super
end
end
end
```

```slim
/ app/views/pages/home.html.slim

/ Use custom renderer class
= component "button", { text: 'Click here' }, renderer: LocalizedComponentRenderer
```


### Stimulus integration

Komponent supports [Stimulus](https://github.com/stimulusjs/stimulus) >= 1.0.
Expand Down
28 changes: 16 additions & 12 deletions lib/komponent/component_renderer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@ def render(component, locals = {}, options = {}, &block)
end
end

def assign_property_value(property_name, property_options, locals)
return if locals.has_key?(property_name)

if property_options.has_key?(:default)
locals[property_name] = property_options[:default]
elsif property_options[:required]
raise "Missing required component parameter: #{property_name}"
end
end

private

def _render(component, locals = {}, options = {}, &block)
Expand All @@ -53,20 +63,14 @@ def _render(component, locals = {}, options = {}, &block)

@lookup_context.prefixes = ["components/#{component}"]

@context.instance_eval do
if component_module.respond_to?(:properties)
locals = locals.dup
component_module.properties.each do |name, options|
unless locals.has_key?(name)
if options.has_key?(:default)
locals[name] = options[:default]
elsif options[:required]
raise "Missing required component parameter: #{name}"
end
end
end
if component_module.respond_to?(:properties)
locals = locals.dup
component_module.properties.each do |name, options|
assign_property_value(name, options, locals)
end
end

@context.instance_eval do
locals.each do |name, value|
instance_variable_set(:"@#{name}", locals[name])
end
Expand Down
3 changes: 2 additions & 1 deletion lib/komponent/komponent_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
module KomponentHelper
def component(component_name, locals = {}, options = {}, &block)
captured_block = proc { |args| capture(args, &block) } if block_given?
Komponent::ComponentRenderer.new(
renderer_klass = options.delete(:renderer) || Komponent::ComponentRenderer
renderer_klass.new(
controller,
view_flow || (view && view.view_flow),
).render(
Expand Down
12 changes: 12 additions & 0 deletions test/komponent/komponent_helper_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,18 @@ def test_helper_raises_component_missing_error
end
end

def test_inject_custom_renderer
custom_renderer_klass = Class.new(Komponent::ComponentRenderer) do
def render(*args)
'custom_render_method'
end
end

assert_equal \
'custom_render_method',
component('world', {}, renderer: custom_renderer_klass)
end

def test_helper_renders_namespaced_component
assert_equal \
%(<div class="namespaced-button">namespaced_button_component</div>),
Expand Down