Skip to content
This repository has been archived by the owner on Oct 17, 2023. It is now read-only.

Commit

Permalink
Add bootstrap 5 themes
Browse files Browse the repository at this point in the history
  • Loading branch information
westonganger committed Mar 25, 2023
1 parent ca4ff3f commit e66c074
Show file tree
Hide file tree
Showing 13 changed files with 469 additions and 11 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ CHANGELOG
- [View Diff](https://github.com/westonganger/sexy_form.rb/compare/v0.9.0...master)
- [#2](https://github.com/westonganger/sexy_form.rb/pulls/2) - Remove validations on field `:type` option
- Fix bug when field `:errors` is provided but is empty
- [#3](https://github.com/westonganger/sexy_form.rb/pulls/3) - Add bootstrap 5 themes

- **0.9.0** - February 15, 2019
- Gem Initial Release
18 changes: 11 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ Dead simple HTML form builder for Ruby with built-in support for many popular UI

Out of the box Form Builder can generate HTML markup for the following UI libraries:

- Bootstrap 4
- Bootstrap 5
* `theme: :bootstrap_5_vertical`
* `theme: :bootstrap_5_inline`
* `theme: :bootstrap_5_horizontal` or `theme: SexyForm::Themes::Bootstrap4Horizontal.new(column_classes: ["col-sm-3","col-sm-9"])`
- Bootstrap 4
* `theme: :bootstrap_4_vertical`
* `theme: :bootstrap_4_inline`
* `theme: :bootstrap_4_horizontal` or `theme: SexyForm::Themes::Bootstrap4Horizontal.new(column_classes: ["col-sm-3","col-sm-9"])`
Expand Down Expand Up @@ -91,8 +95,8 @@ The following field types are supported:
### label_html : (Optional) Hash ### contains attributes to be added to the label
### wrapper_html : (Optional) Hash ### contains attributes to be added to the outer wrapper for the label and input
### help_text_html : (Optional) Hash ### contains attributes to be added to the help text container
### error_html : (Optional) Hash ### contains attributes to be added to the error container(s)
### error_html : (Optional) Hash ### contains attributes to be added to the error container(s)

= f.field name: "product[name]", label: "Name", type: :text, errors: product_errors["name"]

= f.field name: "product[description]", label: "Description", type: :textarea, input_html: {class: "foobar"}, wrapper_html: {style: "margin-top: 10px"}, label_html: {style: "color: red;"}
Expand Down Expand Up @@ -200,9 +204,9 @@ module SexyForm
s << "#{html_label}"
s << "#{html_field}"
end
s << "#{html_help_text}"
if html_errors
s << html_errors.join
end
Expand All @@ -215,11 +219,11 @@ module SexyForm
def input_html_attributes(field_type: , has_errors: , html_attrs:)
html_attrs["class"] = "form-field other-class #{html_attrs["class"]}".strip
html_attrs["style"] = "color: blue; #{html_attrs["style"]}".strip
unless html_attrs.has_key?("data-foo")
html_attrs["data-foo"] = "bar"
end
html_attrs
end
Expand Down
7 changes: 6 additions & 1 deletion lib/sexy_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,12 @@ def self.form(action: nil, method: "post", theme: nil, form_html: {})
protected

def self.build_html_attr_string(hash)
hash.map{|k, v| "#{k}=\"#{v}\""}.join(" ")
hash.delete_if{|_,v| v.nil? || v.to_s.strip.empty? }.map{|k, v| "#{k}=\"#{v.to_s.strip}\""}.join(" ")
end

def self.build_html_element(type, hash)
attr_str = build_html_attr_string(hash)
attr_str.empty? ? "<#{type}>" : "<#{type} #{attr_str}>"
end

def self.safe_string_hash(h)
Expand Down
56 changes: 56 additions & 0 deletions lib/sexy_form/themes/bootstrap_5_base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
module SexyForm
module Themes
module Bootstrap5Base

def input_html_attributes(html_attrs:, field_type:, has_errors:)
html_attrs["class"] ||= ""
html_attrs["class"].concat(" is-invalid").strip! if has_errors

case field_type
when "checkbox", "radio"
html_attrs["class"].concat(" form-check-input").strip!
when "select"
html_attrs["class"].concat(" form-select").strip!
else
html_attrs["class"].concat(" form-control").strip!
end

html_attrs
end

def label_html_attributes(html_attrs:, field_type:, has_errors:)
if ["checkbox", "radio"].include?(field_type)
html_attrs["class"] = "form-check-label #{html_attrs['class']}".strip
end

html_attrs
end

def form_html_attributes(html_attrs:)
html_attrs
end

def build_html_help_text(help_text:, html_attrs:, field_type:)
html_attrs["class"] = "form-text #{html_attrs['class']}".strip
html_attrs["style"] = "display:block; #{html_attrs['style']}".strip

s = ""
s << SexyForm.build_html_element(:small, html_attrs)
s << "#{help_text}"
s << "</small>"
s
end

def build_html_error(error:, html_attrs:, field_type:)
html_attrs["class"] = "invalid-feedback #{html_attrs['class']}".strip

s = ""
s << "<div #{SexyForm.build_html_attr_string(html_attrs)}>"
s << "#{error}"
s << "</div>"
s
end

end
end
end
57 changes: 57 additions & 0 deletions lib/sexy_form/themes/bootstrap_5_horizontal.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
module SexyForm
module Themes
class Bootstrap5Horizontal < BaseTheme
include Bootstrap5Base

def self.theme_name
"bootstrap_5_horizontal"
end

def initialize(column_classes: ["col-sm-3", "col-sm-9"])
@column_classes = column_classes.first(2)

s = "#{@column_classes[0]}"
@offset_class = (i = s.index(/-\d/)) ? s.insert(i+1, "offset-") : ""
end

def wrap_field(field_type:, html_field:, html_label:, html_help_text: nil, html_errors: nil, wrapper_html_attributes:)
s = ""

s << SexyForm.build_html_element(:div, wrapper_html_attributes)

if ["checkbox", "radio"].include?(field_type)
s << %Q(<div class="row">)

s << %Q(<div class="#{@column_classes[0]}"></div>)

s << %Q(<div class="#{@column_classes[1]}">)
s << %Q(<div class="form-check">)
s << "#{html_field}"
s << "#{html_label}"
s << "#{html_help_text}"
s << html_errors.join if html_errors
s << "</div>"
s << "</div>"

s << "</div>"
else
s << %Q(<div class="row">)

s << %Q(<div class="#{@column_classes[0]}">)
s << "#{html_label}"
s << "</div>"

s << %Q(<div class="#{@column_classes[1]}">)
s << "#{html_field}"
s << "#{html_help_text}"
s << html_errors.join if html_errors
s << "</div>"

s << "</div>"
end

s << "</div>"
end
end
end
end
39 changes: 39 additions & 0 deletions lib/sexy_form/themes/bootstrap_5_inline.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
module SexyForm
module Themes
class Bootstrap5Inline < BaseTheme
include Bootstrap5Base

def self.theme_name
"bootstrap_5_inline"
end

def wrap_field(field_type:, html_field:, html_label:, html_help_text: nil, html_errors: nil, wrapper_html_attributes:)
s = ""

if ["checkbox", "radio"].include?(field_type)
wrapper_html_attributes["class"] = "col-auto form-check #{wrapper_html_attributes['class']}".strip

s << SexyForm.build_html_element(:div, wrapper_html_attributes)
s << html_field
s << html_label
else
s << %Q(<div class="col-auto">#{html_label}</div>)
s << %Q(<div class="col-auto">)
s << html_field
end

s << "#{html_help_text}"
s << html_errors.join if html_errors
s << "</div>"

s
end

def form_html_attributes(html_attrs:)
html_attrs["class"] = "row #{html_attrs['class']}".strip
super(html_attrs: html_attrs)
end

end
end
end
36 changes: 36 additions & 0 deletions lib/sexy_form/themes/bootstrap_5_vertical.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
module SexyForm
module Themes
class Bootstrap5Vertical < BaseTheme
include Bootstrap5Base

def self.theme_name
"bootstrap_5_vertical"
end

def wrap_field(field_type:, html_field:, html_label:, html_help_text: nil, html_errors: nil, wrapper_html_attributes:)
s = ""

if ["checkbox", "radio"].include?(field_type)
wrapper_html_attributes["class"] = "form-check #{wrapper_html_attributes['class']}".strip
end

s << SexyForm.build_html_element(:div, wrapper_html_attributes)

if ["checkbox", "radio"].include?(field_type)
s << "#{html_field}"
s << "#{html_label}"
else
s << "#{html_label}"
s << "#{html_field}"
end
s << "#{html_help_text}"
s << html_errors.join if html_errors

s << "</div>"

s
end

end
end
end
74 changes: 74 additions & 0 deletions spec/sexy_form/themes/bootstrap_5_base_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
require_relative "../../spec_helper"
require_relative "theme_spec_helper"

base_klass = SexyForm::Themes::Bootstrap5Base
theme_klass = SexyForm::Themes::Bootstrap5Horizontal
theme = theme_klass.new

describe theme do

it "is not a valid theme" do
expect(base_klass < SexyForm::Themes::BaseTheme).to eq(nil)
expect{ SexyForm.form(theme: base_klass) }.to raise_error(ArgumentError)
end

describe "form_html_attributes" do
it "returns correct attributes" do
attrs = {}

theme.form_html_attributes(html_attrs: {}).should eq(attrs)
end
end

TESTED_FIELD_TYPES.each do |field_type|
describe "input_html_attributes" do
it "returns correct #{field_type} attributes" do
attrs = {}

case field_type
when "checkbox", "radio"
attrs["class"] = "form-check-input"
when "select"
attrs["class"] = "form-select"
else
attrs["class"] = "form-control"
end

theme.input_html_attributes(html_attrs: {}, field_type: field_type, has_errors: false).should eq(attrs)
end
end

describe "label_html_attributes" do
it "returns correct #{field_type} attributes" do
attrs = {}

if ["checkbox", "radio"].include?(field_type)
attrs["class"] = "form-check-label"
end

theme.label_html_attributes(html_attrs: {}, field_type: field_type, has_errors: false).should eq(attrs)
end
end

describe "build_html_help_text" do
it "returns correct #{field_type} attributes" do
expected = %Q(<small class="form-text" style="display:block;">foobar</small>)

attrs = {}

theme.build_html_help_text(html_attrs: attrs, field_type: field_type, help_text: "foobar").should eq(expected)
end
end

describe "build_html_error" do
it "returns correct #{field_type} attributes" do
expected = "<div class=\"invalid-feedback\">foobar</div>"

attrs = {}

theme.build_html_error(html_attrs: attrs, field_type: field_type, error: "foobar").should eq(expected)
end
end
end

end
Loading

0 comments on commit e66c074

Please sign in to comment.