formular 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.travis.yml +29 -0
  4. data/CHANGELOG.md +3 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +105 -0
  8. data/Rakefile +12 -0
  9. data/formular.gemspec +33 -0
  10. data/lib/formular.rb +8 -0
  11. data/lib/formular/attributes.rb +45 -0
  12. data/lib/formular/builder.rb +52 -0
  13. data/lib/formular/builders/basic.rb +64 -0
  14. data/lib/formular/builders/bootstrap3.rb +28 -0
  15. data/lib/formular/builders/bootstrap3_horizontal.rb +32 -0
  16. data/lib/formular/builders/bootstrap3_inline.rb +10 -0
  17. data/lib/formular/builders/bootstrap4.rb +36 -0
  18. data/lib/formular/builders/bootstrap4_horizontal.rb +39 -0
  19. data/lib/formular/builders/bootstrap4_inline.rb +15 -0
  20. data/lib/formular/builders/foundation6.rb +28 -0
  21. data/lib/formular/element.rb +135 -0
  22. data/lib/formular/element/bootstrap3.rb +70 -0
  23. data/lib/formular/element/bootstrap3/checkable_control.rb +105 -0
  24. data/lib/formular/element/bootstrap3/column_control.rb +45 -0
  25. data/lib/formular/element/bootstrap3/horizontal.rb +146 -0
  26. data/lib/formular/element/bootstrap3/input_group.rb +83 -0
  27. data/lib/formular/element/bootstrap4.rb +49 -0
  28. data/lib/formular/element/bootstrap4/checkable_control.rb +94 -0
  29. data/lib/formular/element/bootstrap4/custom_control.rb +120 -0
  30. data/lib/formular/element/bootstrap4/horizontal.rb +87 -0
  31. data/lib/formular/element/foundation6.rb +69 -0
  32. data/lib/formular/element/foundation6/checkable_control.rb +72 -0
  33. data/lib/formular/element/foundation6/input_group.rb +88 -0
  34. data/lib/formular/element/foundation6/wrapped_control.rb +21 -0
  35. data/lib/formular/element/module.rb +35 -0
  36. data/lib/formular/element/modules/checkable.rb +96 -0
  37. data/lib/formular/element/modules/collection.rb +17 -0
  38. data/lib/formular/element/modules/container.rb +60 -0
  39. data/lib/formular/element/modules/control.rb +42 -0
  40. data/lib/formular/element/modules/error.rb +48 -0
  41. data/lib/formular/element/modules/hint.rb +36 -0
  42. data/lib/formular/element/modules/label.rb +30 -0
  43. data/lib/formular/element/modules/wrapped_control.rb +73 -0
  44. data/lib/formular/elements.rb +295 -0
  45. data/lib/formular/helper.rb +53 -0
  46. data/lib/formular/html_block.rb +70 -0
  47. data/lib/formular/path.rb +30 -0
  48. data/lib/formular/version.rb +3 -0
  49. metadata +247 -0
@@ -0,0 +1,10 @@
1
+ require 'formular/builders/bootstrap3'
2
+ module Formular
3
+ module Builders
4
+ class Bootstrap3Inline < Formular::Builders::Bootstrap3
5
+ Form = Class.new(Formular::Element::Form) { set_default :class, ['form-inline'] }
6
+
7
+ element_set form: Form
8
+ end # class Bootstrap3Inline
9
+ end # module Builders
10
+ end # module Formular
@@ -0,0 +1,36 @@
1
+ require 'formular/builders/basic'
2
+ require 'formular/element/bootstrap4'
3
+ require 'formular/element/bootstrap3'
4
+ require 'formular/element/bootstrap3/input_group'
5
+
6
+ module Formular
7
+ module Builders
8
+ class Bootstrap4 < Formular::Builders::Basic
9
+ element_set(
10
+ error_notification: Formular::Element::Bootstrap3::ErrorNotification,
11
+ error: Formular::Element::Bootstrap4::Error,
12
+ hint: Formular::Element::Bootstrap4::Hint,
13
+ input: Formular::Element::Bootstrap4::Input,
14
+ input_group: Formular::Element::Bootstrap3::InputGroup,
15
+ checkbox: Formular::Element::Bootstrap4::StackedCheckbox,
16
+ radio: Formular::Element::Bootstrap4::StackedRadio,
17
+ select: Formular::Element::Bootstrap3::Select,
18
+ custom_select: Formular::Element::Bootstrap4::CustomSelect,
19
+ custom_file: Formular::Element::Bootstrap4::CustomFile,
20
+ custom_radio: Formular::Element::Bootstrap4::Inline::CustomRadio,
21
+ custom_checkbox: Formular::Element::Bootstrap4::Inline::CustomCheckbox,
22
+ custom_stacked_radio: Formular::Element::Bootstrap4::CustomStackedRadio,
23
+ custom_stacked_checkbox: Formular::Element::Bootstrap4::CustomStackedCheckbox,
24
+ inline_radio: Formular::Element::Bootstrap4::InlineRadio,
25
+ inline_checkbox: Formular::Element::Bootstrap4::InlineCheckbox,
26
+ label: Formular::Element::Label,
27
+ checkable_group_label: Formular::Element::Legend,
28
+ textarea: Formular::Element::Bootstrap3::Textarea,
29
+ wrapper: Formular::Element::Bootstrap4::Wrapper,
30
+ error_wrapper: Formular::Element::Bootstrap4::ErrorWrapper,
31
+ submit: Formular::Element::Bootstrap4::Submit,
32
+ row: Formular::Element::Bootstrap3::Row
33
+ )
34
+ end # class Bootstrap4
35
+ end # module Builders
36
+ end # module Formular
@@ -0,0 +1,39 @@
1
+ require 'formular/builders/bootstrap4'
2
+ require 'formular/element/bootstrap3/horizontal'
3
+ require 'formular/element/bootstrap4/horizontal'
4
+ module Formular
5
+ module Builders
6
+ class Bootstrap4Horizontal < Formular::Builders::Bootstrap4
7
+ element_set(
8
+ input: Formular::Element::Bootstrap4::Horizontal::Input,
9
+ custom_file: Formular::Element::Bootstrap4::Horizontal::CustomFile,
10
+ input_group: Formular::Element::Bootstrap3::Horizontal::InputGroup,
11
+ select: Formular::Element::Bootstrap3::Horizontal::Select,
12
+ custom_select: Formular::Element::Bootstrap4::Horizontal::CustomSelect,
13
+ checkbox: Formular::Element::Bootstrap4::Horizontal::Checkbox,
14
+ custom_stacked_checkbox: Formular::Element::Bootstrap4::Horizontal::CustomStackedCheckbox,
15
+ radio: Formular::Element::Bootstrap4::Horizontal::Radio,
16
+ custom_stacked_radio: Formular::Element::Bootstrap4::Horizontal::CustomStackedRadio,
17
+ inline_radio: Formular::Element::Bootstrap4::Horizontal::InlineRadio,
18
+ custom_radio: Formular::Element::Bootstrap4::Horizontal::CustomRadio,
19
+ inline_checkbox: Formular::Element::Bootstrap4::Horizontal::InlineCheckbox,
20
+ custom_checkbox: Formular::Element::Bootstrap4::Horizontal::CustomCheckbox,
21
+ label: Formular::Element::Bootstrap4::Horizontal::Label,
22
+ legend: Formular::Element::Bootstrap4::Horizontal::Legend,
23
+ checkable_group_label: Formular::Element::Bootstrap4::Horizontal::Legend,
24
+ textarea: Formular::Element::Bootstrap3::Horizontal::Textarea,
25
+ error_wrapper: Formular::Element::Bootstrap4::ErrorWrapper,
26
+ input_column_wrapper: Formular::Element::Bootstrap3::Horizontal::InputColumnWrapper,
27
+ submit: Formular::Element::Bootstrap4::Horizontal::Submit
28
+ )
29
+ inheritable_attr :column_classes
30
+
31
+ #these options should be easily configurable
32
+ self.column_classes = {
33
+ left_column: ['col-sm-2'],
34
+ right_column: ['col-sm-10'],
35
+ left_offset: ['offset-sm-2']
36
+ }
37
+ end # class Bootstrap4Horizontal
38
+ end # module Builders
39
+ end # module Formular
@@ -0,0 +1,15 @@
1
+ require 'formular/builders/bootstrap4'
2
+ require 'formular/builders/bootstrap3_inline'
3
+ require 'formular/element/bootstrap4/custom_control'
4
+
5
+ module Formular
6
+ module Builders
7
+ class Bootstrap4Inline < Formular::Builders::Bootstrap4
8
+ element_set(
9
+ form: Bootstrap3Inline::Form,
10
+ custom_select: Formular::Element::Bootstrap4::CustomControl::Inline::CustomSelect,
11
+ custom_file: Formular::Element::Bootstrap4::CustomControl::Inline::CustomFile
12
+ )
13
+ end # class Bootstrap4Inline
14
+ end # module Builders
15
+ end # module Formular
@@ -0,0 +1,28 @@
1
+ require 'formular/builders/basic'
2
+ require 'formular/elements'
3
+ require 'formular/element/foundation6'
4
+ require 'formular/element/foundation6/input_group'
5
+ module Formular
6
+ module Builders
7
+ class Foundation6 < Formular::Builders::Basic
8
+ element_set(
9
+ error_notification: Formular::Element::Foundation6::ErrorNotification,
10
+ checkable_group_label: Formular::Element::Legend,
11
+ checkbox: Formular::Element::Foundation6::Checkbox,
12
+ radio: Formular::Element::Foundation6::Radio,
13
+ stacked_checkbox: Formular::Element::Foundation6::StackedCheckbox,
14
+ stacked_radio: Formular::Element::Foundation6::StackedRadio,
15
+ input: Formular::Element::Foundation6::Input,
16
+ input_group: Formular::Element::Foundation6::InputGroup,
17
+ file: Formular::Element::Foundation6::File,
18
+ select: Formular::Element::Foundation6::Select,
19
+ textarea: Formular::Element::Foundation6::Textarea,
20
+ wrapper: Formular::Element::Label,
21
+ error_wrapper: Formular::Element::Foundation6::LabelWithError,
22
+ error: Formular::Element::Foundation6::Error,
23
+ hint: Formular::Element::Foundation6::Hint,
24
+ submit: Formular::Element::Foundation6::Submit,
25
+ )
26
+ end # class Foundation6
27
+ end # module Builders
28
+ end # module Formular
@@ -0,0 +1,135 @@
1
+ require "formular/attributes"
2
+ require "formular/html_block"
3
+ require 'uber/inheritable_attr'
4
+ require 'uber/options'
5
+
6
+ module Formular
7
+ # The Element class is responsible for defining what the html should look like.
8
+ # This includes default attributes, and the function to use to render the html
9
+ # actual rendering is done via a HtmlBlock class
10
+ class Element
11
+ extend Uber::InheritableAttr
12
+
13
+ inheritable_attr :html_context
14
+ inheritable_attr :html_blocks
15
+ inheritable_attr :default_hash
16
+ inheritable_attr :option_keys
17
+ inheritable_attr :tag_name
18
+
19
+ self.default_hash = {}
20
+ self.html_blocks = {}
21
+ self.html_context = :default
22
+ self.option_keys = []
23
+
24
+ # set the default value of an option or attribute
25
+ # you can make this conditional by providing a condition
26
+ # e.g. if: :some_method or unless: :some_method
27
+ def self.set_default(key, value, condition = {})
28
+ self.default_hash[key] = { value: value, condition: condition }
29
+ end
30
+
31
+ # define what your html should look like
32
+ # this block is executed in the context of an HtmlBlock instance
33
+ def self.html(context = :default, &block)
34
+ self.html_blocks[context] = block
35
+ end
36
+
37
+ # a convenient way of changing the key for a context
38
+ # useful for inheritance if you want to replace a context
39
+ # but still access the original function
40
+ def self.rename_html_context(old_context, new_context)
41
+ self.html_blocks[new_context] = self.html_blocks.delete(old_context)
42
+ end
43
+
44
+ # blacklist the keys that should NOT end up as html attributes
45
+ def self.add_option_keys(*keys)
46
+ self.option_keys += keys
47
+ end
48
+
49
+ # define the name of the html tag for the element
50
+ # e.g.
51
+ # tag :span
52
+ # tag 'input'
53
+ # Note that if you leave this out, the tag will be inferred
54
+ # based on the name of your class
55
+ # Also, this is not inherited
56
+ def self.tag(name)
57
+ self.tag_name = name
58
+ end
59
+
60
+ def self.call(**options, &block)
61
+ new(**options, &block)
62
+ end
63
+
64
+ def initialize(**options, &block)
65
+ @builder = options.delete(:builder)
66
+ normalize_attributes(options)
67
+ @block = block
68
+ @tag = self.class.tag_name
69
+ @html_blocks = define_html_blocks
70
+ end
71
+ attr_reader :tag, :html_blocks, :builder, :attributes, :options
72
+
73
+ def to_html(context: nil)
74
+ context ||= self.class.html_context
75
+ html_blocks[context].call
76
+ end
77
+ alias_method :to_s, :to_html
78
+
79
+ private
80
+
81
+ def define_html_blocks
82
+ self.class.html_blocks.each_with_object({}) do |(context, block), hash|
83
+ hash[context] = HtmlBlock.new(self, block)
84
+ end
85
+ end
86
+
87
+ # we split the options hash between options and attributes
88
+ # based on the option_keys defined on the class
89
+ # we then get the default_hash from the class
90
+ # and merge with the user options and attributes
91
+ def normalize_attributes(**options)
92
+ @attributes = Attributes[options]
93
+ @options = @attributes.select { |k, v| @attributes.delete(k) || true if option_key?(k) }
94
+ merge_default_hash
95
+ end
96
+
97
+ # Take each default value and merge it with attributes && options.
98
+ # This way ordering is important and we can access values as they are evaluated
99
+ def merge_default_hash
100
+ self.class.default_hash.each do |k, v|
101
+ next unless evaluate_option_condition?(v[:condition])
102
+
103
+ val = Uber::Options::Value.new(v[:value]).evaluate(self)
104
+
105
+ next if val.nil?
106
+
107
+ if option_key?(k)
108
+ @options[k] = val if @options[k].nil?
109
+ else
110
+ # make sure that we merge classes, not override them
111
+ k == :class && !@attributes[k].nil? ? @attributes[k] += val : @attributes[k] ||= val
112
+ end
113
+ end
114
+ end
115
+
116
+ def option_key?(k)
117
+ self.class.option_keys.include?(k)
118
+ end
119
+
120
+ # this evaluates any conditons placed on our defaults returning true or false
121
+ # e.g.
122
+ # set_default :checked, "checked", if: :is_checked?
123
+ # set_default :class, ["form-control"], unless: :file_input?
124
+ def evaluate_option_condition?(condition = {})
125
+ return true if condition.empty?
126
+ operator = condition.keys[0]
127
+ condition_result = Uber::Options::Value.new(condition.values[0]).evaluate(self)
128
+
129
+ case operator.to_sym
130
+ when :if then condition_result
131
+ when :unless then !condition_result
132
+ end
133
+ end
134
+ end # class Element
135
+ end # module Formular
@@ -0,0 +1,70 @@
1
+ require 'formular/element'
2
+ require 'formular/elements'
3
+ require 'formular/element/modules/wrapped_control'
4
+ require 'formular/element/module'
5
+ require 'formular/element/bootstrap3/checkable_control'
6
+ require 'formular/element/bootstrap3/column_control'
7
+
8
+ module Formular
9
+ class Element
10
+ module Bootstrap3
11
+ include CheckableControl
12
+
13
+ Label = Class.new(Formular::Element::Label) { set_default :class, ['control-label'] }
14
+ Row = Class.new(Formular::Element::Div) { set_default :class, ['row'] }
15
+
16
+ class Submit < Formular::Element::Button
17
+ set_default :class, ['btn', 'btn-default']
18
+ set_default :type, 'submit'
19
+ end # class Submit
20
+
21
+ class ErrorNotification < Formular::Element::ErrorNotification
22
+ set_default :class, ['alert alert-danger']
23
+ set_default :role, 'alert'
24
+
25
+ end
26
+
27
+ class Error < Formular::Element::Error
28
+ tag :span
29
+ set_default :class, ['help-block']
30
+ end # class Error
31
+
32
+ class Hint < Formular::Element::Span
33
+ set_default :class, ['help-block']
34
+ end # class Hint
35
+
36
+ class Input < Formular::Element::Input
37
+ include Formular::Element::Modules::WrappedControl
38
+ include Formular::Element::Bootstrap3::ColumnControl
39
+
40
+ set_default :class, ['form-control'], unless: :file_input?
41
+
42
+ def file_input?
43
+ attributes[:type] == 'file'
44
+ end
45
+ end # class Input
46
+
47
+ class Select < Formular::Element::Select
48
+ include Formular::Element::Modules::WrappedControl
49
+ include Formular::Element::Bootstrap3::ColumnControl
50
+
51
+ set_default :class, ['form-control']
52
+ end # class Select
53
+
54
+ class Textarea < Formular::Element::Textarea
55
+ include Formular::Element::Modules::WrappedControl
56
+ include Formular::Element::Bootstrap3::ColumnControl
57
+
58
+ set_default :class, ['form-control']
59
+ end # class Textarea
60
+
61
+ class Wrapper < Formular::Element::Div
62
+ set_default :class, ['form-group']
63
+ end # class Wrapper
64
+
65
+ class ErrorWrapper < Formular::Element::Div
66
+ set_default :class, ['form-group', 'has-error']
67
+ end # class Wrapper
68
+ end # module Bootstrap3
69
+ end # class Element
70
+ end # module Formular
@@ -0,0 +1,105 @@
1
+ require 'formular/elements'
2
+ require 'formular/element/modules/wrapped_control'
3
+ require 'formular/element/module'
4
+
5
+ module Formular
6
+ class Element
7
+ module Bootstrap3
8
+ module CheckableControl
9
+ module InlineCheckable
10
+ include Formular::Element::Module
11
+
12
+ html(:wrapped) do |input|
13
+ input.wrapper do
14
+ concat input.group_label
15
+ concat input.hidden_tag unless input.collection?
16
+ if input.has_group_label?
17
+ concat Formular::Element::Div.(content: input.to_html(context: :collection))
18
+ else
19
+ concat input.to_html(context: :collection)
20
+ end
21
+ concat input.hidden_tag if input.collection?
22
+ concat input.hint
23
+ concat input.error
24
+ end
25
+ end
26
+ end # class InlineCheckable
27
+
28
+ class InlineRadio < Formular::Element::Radio
29
+ include Formular::Element::Modules::WrappedControl
30
+ include InlineCheckable
31
+
32
+ add_option_keys :control_label_options
33
+ set_default :control_label_options, { class: ['radio-inline'] }
34
+
35
+ def hidden_tag
36
+ ''
37
+ end
38
+ end# class InlineRadio
39
+
40
+ class InlineCheckbox < Formular::Element::Checkbox
41
+ include Formular::Element::Modules::WrappedControl
42
+ include InlineCheckable
43
+
44
+ set_default :control_label_options, { class: ['checkbox-inline'] }
45
+ set_default :value, '1' # instead of reader value
46
+
47
+ html { closed_start_tag }
48
+ end # class InlineCheckbox
49
+
50
+ module StackedCheckable
51
+ include Formular::Element::Module
52
+
53
+ html(:wrapped) do |input|
54
+ input.wrapper do
55
+ concat input.group_label
56
+ concat input.hidden_tag unless input.collection?
57
+ concat input.to_html(context: :collection)
58
+ concat input.hidden_tag if input.collection?
59
+ concat input.hint
60
+ concat input.error
61
+ end
62
+ end
63
+
64
+ html(:collection) do |input|
65
+ input.collection.map { |control|
66
+ control.inner_wrapper { control.to_html(context: :checkable_label) }
67
+ }.join('')
68
+ end
69
+
70
+ module InstanceMethods
71
+ def inner_wrapper(&block)
72
+ Formular::Element::Div.(class: inner_wrapper_class, &block).to_s
73
+ end
74
+ end
75
+ end # module StackedCheckable
76
+
77
+ class Checkbox < Formular::Element::Checkbox
78
+ include Formular::Element::Modules::WrappedControl
79
+ include StackedCheckable
80
+
81
+ set_default :value, '1' # instead of reader value
82
+
83
+ html { closed_start_tag }
84
+
85
+ def inner_wrapper_class
86
+ ['checkbox']
87
+ end
88
+ end # class Checkbox
89
+
90
+ class Radio < Formular::Element::Radio
91
+ include Formular::Element::Modules::WrappedControl
92
+ include StackedCheckable
93
+
94
+ def inner_wrapper_class
95
+ ['radio']
96
+ end
97
+
98
+ def hidden_tag
99
+ ''
100
+ end
101
+ end # class Radio
102
+ end # module CheckableControl
103
+ end # module Bootstrap3
104
+ end # class Element
105
+ end # module Formular