Skip to content

Commit

Permalink
Merge pull request #5 from bridgetownrb/string-rendering-only
Browse files Browse the repository at this point in the history
Use String-based template rendering only
  • Loading branch information
jaredcwhite authored Nov 3, 2023
2 parents a462714 + 2a159dc commit 3dd0f4b
Show file tree
Hide file tree
Showing 15 changed files with 215 additions and 203 deletions.
2 changes: 0 additions & 2 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ require:
AllCops:
TargetRubyVersion: 3.0
NewCops: enable
Exclude:
- lib/lifeform/phlex_renderable.rb

Lint/MissingSuper:
Enabled: false
Expand Down
22 changes: 13 additions & 9 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ PATH
remote: .
specs:
lifeform (0.11.0)
activesupport (>= 7.0)
phlex (>= 1.7)
hash_with_dot_access (>= 1.2)
sequel (>= 5.72)
serbea (>= 2.0)
zeitwerk (~> 2.5)

GEM
Expand All @@ -18,13 +19,14 @@ GEM
ast (2.4.2)
backport (1.2.0)
benchmark (0.2.1)
bigdecimal (3.1.4)
builder (3.2.4)
cgi (0.3.6)
concurrent-ruby (1.2.2)
diff-lcs (1.5.0)
e2mmap (0.1.0)
erb (4.0.2)
cgi (>= 0.3.3)
erubi (1.12.0)
hash_with_dot_access (1.2.0)
activesupport (>= 5.0.0, < 8.0)
i18n (1.14.1)
concurrent-ruby (~> 1.0)
jaro_winkler (1.5.6)
Expand All @@ -47,10 +49,6 @@ GEM
parser (3.2.2.3)
ast (~> 2.4.1)
racc
phlex (1.8.1)
concurrent-ruby (~> 1.2)
erb (>= 4)
zeitwerk (~> 2.6)
racc (1.7.1)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
Expand Down Expand Up @@ -78,6 +76,12 @@ GEM
rubocop-rake (0.6.0)
rubocop (~> 1.0)
ruby-progressbar (1.13.0)
sequel (5.74.0)
bigdecimal
serbea (2.0.0)
activesupport (>= 6.0)
erubi (>= 1.10)
tilt (~> 2.0)
solargraph (0.45.0)
backport (~> 1.2)
benchmark
Expand Down
13 changes: 1 addition & 12 deletions lib/lifeform.rb
Original file line number Diff line number Diff line change
@@ -1,29 +1,18 @@
# frozen_string_literal: true

require "phlex"
require "active_support/core_ext/string/output_safety"

require "serbea/pipeline"
require "zeitwerk"
loader = Zeitwerk::Loader.for_gem
loader.setup

module Lifeform
class Error < StandardError; end

module RefineProcToString
refine Proc do
def to_s
call.to_s
end
end
end
end

if defined?(Bridgetown)
# Check compatibility
raise "The Lifeform support for Bridgetown requires v1.2 or newer" if Bridgetown::VERSION.to_f < 1.2

Bridgetown.initializer :lifeform do # |config|
require "lifeform/phlex_renderable" unless Phlex::HTML.instance_methods.include?(:render_in)
end
end
17 changes: 0 additions & 17 deletions lib/lifeform/capturing_renderable.rb

This file was deleted.

51 changes: 29 additions & 22 deletions lib/lifeform/form.rb
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
# frozen_string_literal: true

require "active_support/core_ext/string/inflections"
require "active_support/ordered_options"
require "hash_with_dot_access"

module Lifeform
FieldDefinition = Struct.new(:type, :library, :parameters)

# A form object which stores field definitions and can be rendered as a component
class Form < Phlex::HTML # rubocop:todo Metrics/ClassLength
include CapturingRenderable
class Form # rubocop:todo Metrics/ClassLength
include Lifeform::Renderable
extend Sequel::Inflections

MODEL_PATH_HELPER = :polymorphic_path

class << self
Expand All @@ -20,7 +21,7 @@ def inherited(subclass)
# Helper to point to `I18n.t` method
def t(...) = I18n.t(...)

def configuration = @configuration ||= ActiveSupport::OrderedOptions.new
def configuration = @configuration ||= HashWithDotAccess::Hash.new

# @param block [Proc, nil]
# @return [Hash<Symbol, FieldDefinition>]
Expand All @@ -41,7 +42,7 @@ def subforms = @subforms ||= {}

def field(name, type: :text, library: self.library, **parameters)
parameters[:name] = name.to_sym
fields[name] = FieldDefinition.new(type, Libraries.const_get(library.to_s.classify), parameters)
fields[name] = FieldDefinition.new(type, Libraries.const_get(camelize(library)), parameters)
end

def subform(name, klass, parent_name: nil)
Expand Down Expand Up @@ -92,7 +93,7 @@ def name_of_model(model)
model.to_model.model_name.param_key
else
# Or just use basic underscore
model.class.name.underscore.tr("/", "_")
underscore(model.class.name).tr("/", "_")
end
end

Expand Down Expand Up @@ -126,7 +127,7 @@ def initialize( # rubocop:disable Metrics/ParameterLists
)
@model, @url, @library_name, @parameters, @emit_form_tag, @parent_name =
model, url, library, parameters, emit_form_tag, parent_name
@library = Libraries.const_get(@library_name.to_s.classify)
@library = Libraries.const_get(self.class.send(:camelize, @library_name))
@subform_instances = {}

self.class.initialize_field_definitions!
Expand All @@ -139,15 +140,13 @@ def initialize( # rubocop:disable Metrics/ParameterLists
def verify_method
return if %w[get post].include?(parameters[:method].to_s.downcase)

@method_tag = Class.new(Phlex::HTML) do # TODO: break this out into a real component
def initialize(method:)
@method = method
end
method_value = @parameters[:method].to_s.downcase

def template
input type: "hidden", name: "_method", value: @method, autocomplete: "off"
end
end.new(method: @parameters[:method].to_s.downcase)
@method_tag = -> {
<<~HTML
<input type="hidden" name="_method" #{attribute_segment :value, method_value} autocomplete="off">
HTML
}

parameters[:method] = :post
end
Expand Down Expand Up @@ -191,13 +190,21 @@ def template(&block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComple
form_tag = library::FORM_TAG
parameters[:action] ||= url || (model ? helpers.send(self.class.const_get(:MODEL_PATH_HELPER), model) : nil)

send(form_tag, **attributes) do
unsafe_raw(add_authenticity_token) unless parameters[:method].to_s.downcase == "get"
unsafe_raw @method_tag&.() || ""
block ? yield_content(&block) : auto_render_fields
end
html -> {
<<~HTML
<#{form_tag}#{attrs -> { attributes }}>
#{add_authenticity_token unless parameters[:method].to_s.downcase == "get"}
#{@method_tag&.() || ""}
#{block ? capture(self, &block) : auto_render_fields}
</#{form_tag}>
HTML
}
end

def auto_render_fields = self.class.fields.map { |k, _v| render(field(k)) }
def auto_render_fields = html_map(self.class.fields) { |k, _v| render(field(k)) }

def render(field_object)
field_object.render_in(helpers || self)
end
end
end
95 changes: 95 additions & 0 deletions lib/lifeform/helpers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# frozen_string_literal: true

require "sequel/model/default_inflections"
require "sequel/model/inflections"

module Lifeform
module Helpers
def attributes_from_options(options)
segments = []
options.each do |attr, option|
attr = dashed(attr)
if option.is_a?(Hash)
option = option.transform_keys { |key| "#{attr}-#{dashed(key)}" }
segments << attributes_from_options(option)
else
segments << attribute_segment(attr, option)
end
end
segments.join(" ")
end

# Covert an underscored value into a dashed string.
#
# @example "foo_bar_baz" => "foo-bar-baz"
#
# @param value [String|Symbol]
# @return [String]
def dashed(value)
value.to_s.tr("_", "-")
end

# Create an attribute segment for a tag.
#
# @param attr [String] the HTML attribute name
# @param value [String] the attribute value
# @return [String]
def attribute_segment(attr, value)
"#{attr}=#{value.to_s.encode(xml: :attr)}"
end

def attrs(callback)
attrs_string = attributes_from_options(callback.() || {})

attrs_string = " #{attrs_string}" unless attrs_string.blank?

attrs_string
end

# Below is verbatim copied over from Bridgetown
# TODO: extract both out to a shared gem

module PipeableProc
include Serbea::Pipeline::Helper

attr_accessor :pipe_block, :touched

def pipe(&block)
return super(self.(), &pipe_block) if pipe_block && !block

self.touched = true
return self unless block

tap { _1.pipe_block = block }
end

def to_s
return self.().to_s if touched

super
end

def encode(...)
to_s.encode(...)
end
end

Proc.prepend(PipeableProc) unless defined?(Bridgetown::HTMLinRuby::PipeableProc)

def text(callback)
(callback.is_a?(Proc) ? html(callback) : callback).to_s.then do |str|
next str if str.respond_to?(:html_safe) && str.html_safe?

str.encode(xml: :attr).gsub(/\A"|"\Z/, "")
end
end

def html(callback)
callback.pipe
end

def html_map(input, &callback)
input.map(&callback).join
end
end
end
2 changes: 1 addition & 1 deletion lib/lifeform/libraries/default.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class Default
# @param attributes [Hash]
# @return [Input]
def self.object_for_field_definition(form, field_definition, attributes)
type_classname = field_definition[:type].to_s.classify
type_classname = Lifeform::Form.send(:camelize, field_definition[:type])
if const_defined?(type_classname)
const_get(type_classname)
else
Expand Down
35 changes: 19 additions & 16 deletions lib/lifeform/libraries/default/button.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,14 @@
module Lifeform
module Libraries
class Default
class Button < Phlex::HTML
using RefineProcToString
include CapturingRenderable
class Button
include Lifeform::Renderable

attr_reader :form, :field_definition, :attributes

WRAPPER_TAG = :form_button
BUTTON_TAG = :button

register_element WRAPPER_TAG

def initialize(form, field_definition, **attributes)
@form = form
@field_definition = field_definition
Expand All @@ -23,21 +20,27 @@ def initialize(form, field_definition, **attributes)
@attributes[:type] ||= "button"
end

def template(&block)
return if !@if.nil? && !@if
def template(&block) # rubocop:disable Metrics/AbcSize
return "" if !@if.nil? && !@if

wrapper_tag = dashed self.class.const_get(:WRAPPER_TAG)
button_tag = dashed self.class.const_get(:BUTTON_TAG)

wrapper_tag = self.class.const_get(:WRAPPER_TAG)
button_tag = self.class.const_get(:BUTTON_TAG)
label_text = block ? capture(self, &block) : @label.is_a?(Proc) ? @label.pipe : @label # rubocop:disable Style/NestedTernaryOperator

field_body = proc {
send(button_tag, **@attributes) do
unsafe_raw(@label.to_s) unless block
yield_content(&block)
end
field_body = html -> {
<<-HTML
<#{button_tag}#{attrs -> { @attributes }}>#{text -> { label_text }}</#{button_tag}>
HTML
}
return field_body.() unless wrapper_tag

send wrapper_tag, name: @attributes[:name], &field_body
return field_body unless wrapper_tag

html -> {
<<-HTML
<#{wrapper_tag}#{attrs -> { { name: @attributes[:name] } }}>#{field_body}</#{wrapper_tag}>
HTML
}
end
end
end
Expand Down
Loading

0 comments on commit 3dd0f4b

Please sign in to comment.