formular 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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