Skip to content

Commit 176a0f4

Browse files
committed
Add bootstrap 5 themes
1 parent 3bddc69 commit 176a0f4

12 files changed

+450
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
- **UNRELEASED**
55
- [View Diff](https://github.com/westonganger/form_builder.cr/compare/v1.0.1...master)
66
- [#4](https://github.com/westonganger/form_builder.cr/pulls/4) - Remove validations on field `:type` option
7+
- [#6](https://github.com/westonganger/form_builder.cr/pulls/6) - Add bootstrap 5 themes
78

89
- **1.0.1** - Apr 24, 2021
910
- [View Diff](https://github.com/westonganger/form_builder.cr/compare/v1.0.0...v1.0.1)

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ crystal spec
292292

293293
# Ruby Alternative
294294

295-
This library has been ported to the Ruby language as [SexyForm.rb](https://github.com/westonganger/sexy_form.rb)
295+
This library has been ported to the Ruby language as [FormBuilder.rb](https://github.com/westonganger/sexy_form.rb)
296296

297297
The pattern/implementation of this form builder library turned out so beautifully that I felt the desire to have the same syntax available in the Ruby language. Many Crystal developers also write Ruby and vice versa so this only made sense. What was awesome is that, the Crystal and Ruby syntax is so similar that converting Crystal code to Ruby was straight forward and quite simple.
298298

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
require "../../spec_helper"
2+
require "./theme_spec_helper"
3+
4+
base_klass = FormBuilder::Themes::Bootstrap5Base
5+
theme_klass = FormBuilder::Themes::Bootstrap5Horizontal
6+
theme = theme_klass.new
7+
8+
describe theme_klass do
9+
10+
describe ".theme_name" do
11+
it "is not a valid theme" do
12+
(base_klass < FormBuilder::Themes::BaseTheme).should eq(false)
13+
expect_raises(ArgumentError){ FormBuilder.form(theme: base_klass.name.underscore) }
14+
end
15+
end
16+
17+
describe "form_html_attributes" do
18+
it "returns correct attributes" do
19+
attrs = StringHash.new
20+
21+
theme.form_html_attributes(html_attrs: StringHash.new).should eq(attrs)
22+
end
23+
end
24+
25+
TESTED_FIELD_TYPES.each do |field_type|
26+
describe "input_html_attributes" do
27+
it "returns correct #{field_type} attributes" do
28+
attrs = StringHash.new
29+
30+
case field_type
31+
when "checkbox", "radio"
32+
attrs["class"] = "form-check-input"
33+
when "select"
34+
attrs["class"] = "form-select"
35+
else
36+
attrs["class"] = "form-control"
37+
end
38+
39+
theme.input_html_attributes(html_attrs: StringHash.new, field_type: field_type, has_errors?: false).should eq(attrs)
40+
end
41+
end
42+
43+
describe "label_html_attributes" do
44+
it "returns correct #{field_type} attributes" do
45+
attrs = StringHash.new
46+
47+
if {"checkbox", "radio"}.includes?(field_type)
48+
attrs["class"] = "form-check-label"
49+
end
50+
51+
theme.label_html_attributes(html_attrs: StringHash.new, field_type: field_type, has_errors?: false).should eq(attrs)
52+
end
53+
end
54+
55+
describe "build_html_help_text" do
56+
it "returns correct #{field_type} attributes" do
57+
expected = "<small class=\"form-text\">foobar</small>"
58+
59+
attrs = StringHash.new
60+
61+
theme.build_html_help_text(html_attrs: attrs, field_type: field_type, help_text: "foobar").should eq(expected)
62+
end
63+
end
64+
65+
describe "build_html_error" do
66+
it "returns correct #{field_type} attributes" do
67+
expected = "<div class=\"invalid-feedback\">foobar</div>"
68+
69+
attrs = StringHash.new
70+
71+
theme.build_html_error(html_attrs: attrs, field_type: field_type, error: "foobar").should eq(expected)
72+
end
73+
end
74+
end
75+
76+
end
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
require "../../spec_helper"
2+
require "./theme_spec_helper"
3+
4+
theme_klass = FormBuilder::Themes::Bootstrap5Horizontal
5+
theme = theme_klass.new
6+
7+
describe theme_klass do
8+
9+
describe "theme_name" do
10+
it "is correct" do
11+
theme_klass.theme_name.should eq("bootstrap_5_horizontal")
12+
end
13+
end
14+
15+
describe "FormBuilder.form" do
16+
it "matches docs example" do
17+
expected = String.build do |str|
18+
str << %Q(<form method="post">)
19+
20+
str << %Q(<div>)
21+
str << %Q(<div class="row">)
22+
str << %Q(<div class="col-sm-3">)
23+
str << %Q(<label for="email">Email</label>)
24+
str << %Q(</div>)
25+
str << %Q(<div class="col-sm-9">)
26+
str << %Q(<input type="text" class="form-control" name="email" id="email">)
27+
str << %Q(</div>)
28+
str << %Q(</div>)
29+
str << %Q(</div>)
30+
31+
str << %Q(<div>)
32+
str << %Q(<div class="row">)
33+
str << %Q(<div class="col-sm-3">)
34+
str << %Q(<label for="password">Password</label>)
35+
str << %Q(</div>)
36+
str << %Q(<div class="col-sm-9">)
37+
str << %Q(<input type="password" class="form-control" name="password" id="password">)
38+
str << %Q(</div>)
39+
str << %Q(</div>)
40+
str << %Q(</div>)
41+
42+
str << %Q(<div>)
43+
str << %Q(<div class="row">)
44+
str << %Q(<div class="col-sm-3"></div>)
45+
str << %Q(<div class="col-sm-9">)
46+
str << %Q(<div class="form-check">)
47+
str << %Q(<input type="checkbox" class="form-check-input" name="remember_me" id="remember_me">)
48+
str << %Q(<label class="form-check-label" for="remember_me">Remember Me</label>)
49+
str << %Q(</div>)
50+
str << %Q(</div>)
51+
str << %Q(</div>)
52+
str << %Q(</div>)
53+
54+
str << %Q(</form>)
55+
end
56+
57+
actual = FormBuilder.form(theme: theme_klass.new(column_classes: ["col-sm-3", "col-sm-9"])) do |f|
58+
f << f.field(type: :text, name: :email)
59+
f << f.field(type: :password, name: :password)
60+
f << f.field(type: :checkbox, name: :remember_me)
61+
end
62+
63+
actual.should eq(expected)
64+
end
65+
end
66+
67+
end
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
require "../../spec_helper"
2+
require "./theme_spec_helper"
3+
4+
theme_klass = FormBuilder::Themes::Bootstrap5Inline
5+
theme = theme_klass.new
6+
7+
describe theme_klass do
8+
9+
describe "theme_name" do
10+
it "is correct" do
11+
theme_klass.theme_name.should eq("bootstrap_5_inline")
12+
end
13+
end
14+
15+
describe "FormBuilder.form" do
16+
it "matches docs example" do
17+
expected = String.build do |str|
18+
str << %Q(<form class="row" method="post">)
19+
str << %Q(<div class="col-auto">)
20+
str << %Q(<label for="email">Email</label>)
21+
str << %Q(</div>)
22+
str << %Q(<div class="col-auto">)
23+
str << %Q(<input type="text" class="form-control" name="email" id="email">)
24+
str << %Q(</div>)
25+
26+
str << %Q(<div class="col-auto">)
27+
str << %Q(<label for="password">Password</label>)
28+
str << %Q(</div>)
29+
str << %Q(<div class="col-auto">)
30+
str << %Q(<input type="password" class="form-control" name="password" id="password">)
31+
str << %Q(</div>)
32+
33+
str << %Q(<div class="col-auto form-check">)
34+
str << %Q(<input type="checkbox" class="form-check-input" name="remember_me" id="remember_me">)
35+
str << %Q(<label class="form-check-label" for="remember_me">Remember Me</label>)
36+
str << %Q(</div>)
37+
38+
str << %Q(</form>)
39+
end
40+
41+
actual = FormBuilder.form(theme: theme_klass.theme_name) do |f|
42+
f << f.field(type: :text, name: :email)
43+
f << f.field(type: :password, name: :password)
44+
f << f.field(type: :checkbox, name: :remember_me)
45+
end
46+
47+
actual.should eq(expected)
48+
end
49+
end
50+
51+
end
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
require "../../spec_helper"
2+
require "./theme_spec_helper"
3+
4+
theme_klass = FormBuilder::Themes::Bootstrap5Vertical
5+
theme = theme_klass.new
6+
7+
describe theme_klass do
8+
9+
describe "theme_name" do
10+
it "is correct" do
11+
theme_klass.theme_name.should eq("bootstrap_5_vertical")
12+
end
13+
end
14+
15+
describe "FormBuilder.form" do
16+
it "matches docs example" do
17+
expected = String.build do |str|
18+
str << %Q(<form method="post">)
19+
str << %Q(<div>)
20+
str << %Q(<label for="email">Email</label>)
21+
str << %Q(<input type="text" class="form-control" name="email" id="email">)
22+
str << %Q(</div>)
23+
24+
str << %Q(<div>)
25+
str << %Q(<label for="password">Password</label>)
26+
str << %Q(<input type="password" class="form-control" name="password" id="password">)
27+
str << %Q(</div>)
28+
29+
str << %Q(<div class="form-check">)
30+
str << %Q(<input type="checkbox" class="form-check-input" name="remember_me" id="remember_me">)
31+
str << %Q(<label class="form-check-label" for="remember_me">Remember Me</label>)
32+
str << %Q(</div>)
33+
str << %Q(</form>)
34+
end
35+
36+
actual = FormBuilder.form(theme: theme_klass.theme_name) do |f|
37+
f << f.field(type: :text, name: :email)
38+
f << f.field(type: :password, name: :password)
39+
f << f.field(type: :checkbox, name: :remember_me)
40+
end
41+
42+
actual.should eq(expected)
43+
end
44+
end
45+
46+
end

spec/form_builder/themes_spec.cr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ describe FormBuilder::Themes do
66

77
describe ".classes" do
88
it "Comes with default themes" do
9-
classes = [FormBuilder::Themes::Bootstrap2Horizontal, FormBuilder::Themes::Bootstrap2Inline, FormBuilder::Themes::Bootstrap2Vertical, FormBuilder::Themes::Bootstrap3Horizontal, FormBuilder::Themes::Bootstrap3Inline, FormBuilder::Themes::Bootstrap3Vertical, FormBuilder::Themes::Bootstrap4Horizontal, FormBuilder::Themes::Bootstrap4Inline, FormBuilder::Themes::Bootstrap4Vertical, FormBuilder::Themes::BulmaHorizontal, FormBuilder::Themes::BulmaVertical, FormBuilder::Themes::Default, FormBuilder::Themes::Foundation, FormBuilder::Themes::Materialize, FormBuilder::Themes::Milligram, FormBuilder::Themes::SemanticUIInline, FormBuilder::Themes::SemanticUIVertical]
9+
classes = [FormBuilder::Themes::Bootstrap2Horizontal, FormBuilder::Themes::Bootstrap2Inline, FormBuilder::Themes::Bootstrap2Vertical, FormBuilder::Themes::Bootstrap3Horizontal, FormBuilder::Themes::Bootstrap3Inline, FormBuilder::Themes::Bootstrap3Vertical, FormBuilder::Themes::Bootstrap4Horizontal, FormBuilder::Themes::Bootstrap4Inline, FormBuilder::Themes::Bootstrap4Vertical, FormBuilder::Themes::Bootstrap5Horizontal, FormBuilder::Themes::Bootstrap5Inline, FormBuilder::Themes::Bootstrap5Vertical, FormBuilder::Themes::BulmaHorizontal, FormBuilder::Themes::BulmaVertical, FormBuilder::Themes::Default, FormBuilder::Themes::Foundation, FormBuilder::Themes::Materialize, FormBuilder::Themes::Milligram, FormBuilder::Themes::SemanticUIInline, FormBuilder::Themes::SemanticUIVertical]
1010

1111
FormBuilder::Themes.classes.should eq(classes)
1212

13-
expected = ["bootstrap_2_horizontal", "bootstrap_2_inline", "bootstrap_2_vertical", "bootstrap_3_horizontal", "bootstrap_3_inline", "bootstrap_3_vertical", "bootstrap_4_horizontal", "bootstrap_4_inline", "bootstrap_4_vertical", "bulma_horizontal", "bulma_vertical", "default", "foundation", "materialize", "milligram", "semantic_ui_inline", "semantic_ui_vertical"]
13+
expected = ["bootstrap_2_horizontal", "bootstrap_2_inline", "bootstrap_2_vertical", "bootstrap_3_horizontal", "bootstrap_3_inline", "bootstrap_3_vertical", "bootstrap_4_horizontal", "bootstrap_4_inline", "bootstrap_4_vertical", "bootstrap_5_horizontal", "bootstrap_5_inline", "bootstrap_5_vertical", "bulma_horizontal", "bulma_vertical", "default", "foundation", "materialize", "milligram", "semantic_ui_inline", "semantic_ui_vertical"]
1414

1515
classes.map{|x| x.theme_name}.should eq(expected)
1616
end

src/form_builder.cr

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,12 @@ module FormBuilder
5353
end
5454

5555
protected def self.build_html_attr_string(h : Hash)
56-
h.map{|k, v| "#{k}=\"#{v}\""}.join(" ")
56+
h.reject{|_,v| v.nil? || v.to_s.strip.empty? }.map{|k, v| "#{k}=\"#{v.to_s.strip}\""}.join(" ")
57+
end
58+
59+
def self.build_html_element(type : (String|Symbol), h : Hash)
60+
attr_str = build_html_attr_string(h)
61+
attr_str.empty? ? "<#{type}>" : "<#{type} #{attr_str}>"
5762
end
5863

5964
protected def self.safe_string_hash(h : Hash)
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
module FormBuilder
2+
module Themes
3+
module Bootstrap5Base
4+
5+
def wrap_field(field_type : String, html_field : String, html_label : String?, html_help_text : String?, html_errors : Array(String)?, wrapper_html_attributes : StringHash)
6+
String.build do |s|
7+
wrapper_html_attributes["class"] = "form-group #{"form-check" if {"checkbox", "radio"}.includes?(field_type)} #{wrapper_html_attributes["class"]?}".strip
8+
9+
attr_str = FormBuilder.build_html_attr_string(wrapper_html_attributes)
10+
s << "#{attr_str.empty? ? "<div>" : %(<div #{attr_str}>)}"
11+
12+
if {"checkbox", "radio"}.includes?(field_type)
13+
s << html_field
14+
s << html_label
15+
else
16+
s << html_label
17+
s << html_field
18+
end
19+
s << html_help_text
20+
s << html_errors.join if html_errors
21+
22+
s << "</div>"
23+
end
24+
end
25+
26+
def input_html_attributes(html_attrs : StringHash, field_type : String, has_errors? : Bool)
27+
case field_type
28+
when "checkbox", "radio"
29+
html_attrs["class"] = "form-check-input#{" is-invalid" if has_errors?} #{html_attrs["class"]?}".strip
30+
when "select"
31+
html_attrs["class"] = "form-select#{" is-invalid" if has_errors?} #{html_attrs["class"]?}".strip
32+
else
33+
html_attrs["class"] = "form-control#{" is-invalid" if has_errors?} #{html_attrs["class"]?}".strip
34+
end
35+
36+
html_attrs
37+
end
38+
39+
def label_html_attributes(html_attrs : StringHash, field_type : String, has_errors? : Bool)
40+
if {"checkbox", "radio"}.includes?(field_type)
41+
html_attrs["class"] = "form-check-label #{html_attrs["class"]?}".strip
42+
end
43+
44+
html_attrs
45+
end
46+
47+
def form_html_attributes(html_attrs : StringHash)
48+
html_attrs
49+
end
50+
51+
def build_html_help_text(help_text : String, html_attrs : StringHash, field_type : String)
52+
html_attrs["class"] = "form-text #{html_attrs["class"]?}".strip
53+
54+
String.build do |s|
55+
s << (html_attrs.empty? ? "<small>" : %(<small #{FormBuilder.build_html_attr_string(html_attrs)}>))
56+
s << help_text
57+
s << "</small>"
58+
end
59+
end
60+
61+
def build_html_error(error : String, html_attrs : StringHash, field_type : String)
62+
html_attrs["class"] = "invalid-feedback #{html_attrs["class"]?}".strip
63+
64+
String.build do |s|
65+
s << (html_attrs.empty? ? "<div>" : %(<div #{FormBuilder.build_html_attr_string(html_attrs)}>))
66+
s << error
67+
s << "</div>"
68+
end
69+
end
70+
71+
end
72+
end
73+
end

0 commit comments

Comments
 (0)