goodmin 0.0.1

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 (147) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +1359 -0
  4. data/Rakefile +56 -0
  5. data/app/assets/stylesheets/goodmin/application.css +75 -0
  6. data/app/controllers/goodmin/application_controller.rb +43 -0
  7. data/app/controllers/goodmin/resource_controller.rb +235 -0
  8. data/app/helpers/goodmin/application_helper.rb +45 -0
  9. data/app/javascript/goodmin/application.js +8 -0
  10. data/app/javascript/goodmin/controllers/batch_actions_controller.js +101 -0
  11. data/app/javascript/goodmin/controllers/datetimepicker_controller.js +24 -0
  12. data/app/javascript/goodmin/controllers/navigation_controller.js +30 -0
  13. data/app/jobs/goodmin/application_job.rb +4 -0
  14. data/app/mailers/goodmin/application_mailer.rb +6 -0
  15. data/app/models/goodmin/application_record.rb +5 -0
  16. data/app/views/goodmin/application/welcome.html.erb +17 -0
  17. data/app/views/goodmin/fields/association/_form.html.erb +19 -0
  18. data/app/views/goodmin/fields/association/_index.html.erb +6 -0
  19. data/app/views/goodmin/fields/association/_show.html.erb +6 -0
  20. data/app/views/goodmin/fields/boolean/_form.html.erb +7 -0
  21. data/app/views/goodmin/fields/boolean/_index.html.erb +1 -0
  22. data/app/views/goodmin/fields/boolean/_show.html.erb +1 -0
  23. data/app/views/goodmin/fields/date/_form.html.erb +6 -0
  24. data/app/views/goodmin/fields/date/_index.html.erb +1 -0
  25. data/app/views/goodmin/fields/date/_show.html.erb +1 -0
  26. data/app/views/goodmin/fields/date_time/_form.html.erb +6 -0
  27. data/app/views/goodmin/fields/date_time/_index.html.erb +1 -0
  28. data/app/views/goodmin/fields/date_time/_show.html.erb +1 -0
  29. data/app/views/goodmin/fields/enum/_index.html.erb +1 -0
  30. data/app/views/goodmin/fields/enum/_show.html.erb +1 -0
  31. data/app/views/goodmin/fields/nested_has_one/_form.html.erb +6 -0
  32. data/app/views/goodmin/fields/nested_has_one/_index.html.erb +1 -0
  33. data/app/views/goodmin/fields/nested_has_one/_show.html.erb +1 -0
  34. data/app/views/goodmin/fields/number/_form.html.erb +5 -0
  35. data/app/views/goodmin/fields/number/_index.html.erb +1 -0
  36. data/app/views/goodmin/fields/number/_show.html.erb +1 -0
  37. data/app/views/goodmin/fields/password/_form.html.erb +5 -0
  38. data/app/views/goodmin/fields/password/_index.html.erb +1 -0
  39. data/app/views/goodmin/fields/password/_show.html.erb +1 -0
  40. data/app/views/goodmin/fields/select/_form.html.erb +5 -0
  41. data/app/views/goodmin/fields/select/_index.html.erb +1 -0
  42. data/app/views/goodmin/fields/select/_show.html.erb +1 -0
  43. data/app/views/goodmin/fields/string/_form.html.erb +5 -0
  44. data/app/views/goodmin/fields/string/_index.html.erb +1 -0
  45. data/app/views/goodmin/fields/string/_show.html.erb +1 -0
  46. data/app/views/goodmin/fields/text/_form.html.erb +5 -0
  47. data/app/views/goodmin/fields/text/_index.html.erb +1 -0
  48. data/app/views/goodmin/fields/text/_show.html.erb +1 -0
  49. data/app/views/goodmin/resource/_actions.html.erb +9 -0
  50. data/app/views/goodmin/resource/_batch_actions.html.erb +12 -0
  51. data/app/views/goodmin/resource/_breadcrumb.html.erb +33 -0
  52. data/app/views/goodmin/resource/_breadcrumb_actions.html.erb +41 -0
  53. data/app/views/goodmin/resource/_button_actions.html.erb +3 -0
  54. data/app/views/goodmin/resource/_errors.html.erb +9 -0
  55. data/app/views/goodmin/resource/_export_actions.html.erb +15 -0
  56. data/app/views/goodmin/resource/_filters.html.erb +22 -0
  57. data/app/views/goodmin/resource/_form.html.erb +26 -0
  58. data/app/views/goodmin/resource/_pagination.html.erb +40 -0
  59. data/app/views/goodmin/resource/_scopes.html.erb +14 -0
  60. data/app/views/goodmin/resource/_table.html.erb +45 -0
  61. data/app/views/goodmin/resource/columns/_actions.html.erb +28 -0
  62. data/app/views/goodmin/resource/edit.html.erb +5 -0
  63. data/app/views/goodmin/resource/index.csv.csvbuilder +5 -0
  64. data/app/views/goodmin/resource/index.html.erb +10 -0
  65. data/app/views/goodmin/resource/index.json.jbuilder +3 -0
  66. data/app/views/goodmin/resource/new.html.erb +5 -0
  67. data/app/views/goodmin/resource/show.html.erb +11 -0
  68. data/app/views/goodmin/resource/show.json.jbuilder +1 -0
  69. data/app/views/goodmin/sessions/new.html.erb +11 -0
  70. data/app/views/goodmin/shared/_navigation.html.erb +0 -0
  71. data/app/views/goodmin/shared/_navigation_aside.html.erb +7 -0
  72. data/app/views/layouts/goodmin/_content.html.erb +13 -0
  73. data/app/views/layouts/goodmin/_layout.html.erb +22 -0
  74. data/app/views/layouts/goodmin/application.html.erb +28 -0
  75. data/app/views/layouts/goodmin/login.html.erb +18 -0
  76. data/config/importmap.rb +5 -0
  77. data/config/locales/en.yml +49 -0
  78. data/config/locales/pl-BR.yml +49 -0
  79. data/config/locales/pt-BR.yml +49 -0
  80. data/config/locales/sv.yml +49 -0
  81. data/config/routes.rb +3 -0
  82. data/lib/generators/goodmin/authentication/authentication_generator.rb +41 -0
  83. data/lib/generators/goodmin/authentication/templates/sessions_controller.rb +9 -0
  84. data/lib/generators/goodmin/install/install_generator.rb +41 -0
  85. data/lib/generators/goodmin/policy/policy_generator.rb +7 -0
  86. data/lib/generators/goodmin/policy/templates/policy.rb +23 -0
  87. data/lib/generators/goodmin/resource/resource_generator.rb +31 -0
  88. data/lib/generators/goodmin/resource/templates/resource.rb +25 -0
  89. data/lib/generators/goodmin/resource/templates/resource_controller.rb +9 -0
  90. data/lib/generators/goodmin/resource/templates/resource_model.rb +4 -0
  91. data/lib/generators/goodmin/resource/templates/resource_service.rb +23 -0
  92. data/lib/goodmin/authentication/sessions_controller.rb +46 -0
  93. data/lib/goodmin/authentication/user.rb +27 -0
  94. data/lib/goodmin/authentication.rb +35 -0
  95. data/lib/goodmin/authorization/policy.rb +41 -0
  96. data/lib/goodmin/authorization.rb +69 -0
  97. data/lib/goodmin/engine.rb +30 -0
  98. data/lib/goodmin/fields/association.rb +62 -0
  99. data/lib/goodmin/fields/base.rb +57 -0
  100. data/lib/goodmin/fields/boolean.rb +6 -0
  101. data/lib/goodmin/fields/date.rb +6 -0
  102. data/lib/goodmin/fields/date_time.rb +6 -0
  103. data/lib/goodmin/fields/enum.rb +15 -0
  104. data/lib/goodmin/fields/nested_has_one.rb +41 -0
  105. data/lib/goodmin/fields/number.rb +6 -0
  106. data/lib/goodmin/fields/password.rb +6 -0
  107. data/lib/goodmin/fields/select.rb +19 -0
  108. data/lib/goodmin/fields/string.rb +6 -0
  109. data/lib/goodmin/fields/text.rb +6 -0
  110. data/lib/goodmin/generators/base.rb +49 -0
  111. data/lib/goodmin/generators/named_base.rb +31 -0
  112. data/lib/goodmin/helpers/application.rb +47 -0
  113. data/lib/goodmin/helpers/batch_actions.rb +21 -0
  114. data/lib/goodmin/helpers/filters.rb +123 -0
  115. data/lib/goodmin/helpers/forms.rb +42 -0
  116. data/lib/goodmin/helpers/navigation.rb +52 -0
  117. data/lib/goodmin/helpers/tables.rb +25 -0
  118. data/lib/goodmin/helpers/translations.rb +19 -0
  119. data/lib/goodmin/paginator.rb +55 -0
  120. data/lib/goodmin/resolver.rb +141 -0
  121. data/lib/goodmin/resources/attribute.rb +46 -0
  122. data/lib/goodmin/resources/form_builder.rb +96 -0
  123. data/lib/goodmin/resources/form_component.rb +65 -0
  124. data/lib/goodmin/resources/form_components/col.rb +33 -0
  125. data/lib/goodmin/resources/form_components/row.rb +25 -0
  126. data/lib/goodmin/resources/form_components/section.rb +51 -0
  127. data/lib/goodmin/resources/form_components/tab.rb +49 -0
  128. data/lib/goodmin/resources/resource/associations.rb +23 -0
  129. data/lib/goodmin/resources/resource/batch_actions.rb +56 -0
  130. data/lib/goodmin/resources/resource/filters.rb +44 -0
  131. data/lib/goodmin/resources/resource/ordering.rb +41 -0
  132. data/lib/goodmin/resources/resource/pagination.rb +22 -0
  133. data/lib/goodmin/resources/resource/scopes.rb +61 -0
  134. data/lib/goodmin/resources/resource.rb +199 -0
  135. data/lib/goodmin/resources/resource_controller/batch_actions.rb +49 -0
  136. data/lib/goodmin/resources/resource_service/associations.rb +23 -0
  137. data/lib/goodmin/resources/resource_service/batch_actions.rb +52 -0
  138. data/lib/goodmin/resources/resource_service/filters.rb +44 -0
  139. data/lib/goodmin/resources/resource_service/ordering.rb +41 -0
  140. data/lib/goodmin/resources/resource_service/pagination.rb +22 -0
  141. data/lib/goodmin/resources/resource_service/scopes.rb +61 -0
  142. data/lib/goodmin/resources/resource_service.rb +199 -0
  143. data/lib/goodmin/service_locator.rb +25 -0
  144. data/lib/goodmin/version.rb +3 -0
  145. data/lib/goodmin.rb +44 -0
  146. data/lib/tasks/goodmin_tasks.rake +4 -0
  147. metadata +461 -0
@@ -0,0 +1,62 @@
1
+ module Goodmin
2
+ module Fields
3
+ class Association < Base
4
+ def belongs_to?
5
+ macro == :belongs_to
6
+ end
7
+
8
+ def many_to_many?
9
+ reflection = record.class.reflect_on_association(attribute)
10
+ return false unless reflection
11
+ reflection.macro == :has_and_belongs_to_many ||
12
+ (reflection.macro == :has_many && reflection.options[:through].present?)
13
+ end
14
+
15
+ def has_many?
16
+ return false unless reflection
17
+ macro == :has_many && !many_to_many?
18
+ end
19
+
20
+ def select_attribute
21
+ belongs_to? ? :"#{attribute}_id" : :"#{attribute.to_s.singularize}_ids"
22
+ end
23
+
24
+ def collection
25
+ klass = reflection&.klass
26
+ return [] unless klass
27
+
28
+ if associated_service
29
+ klass.all.map { |a| [display_name_for(a), a.id] }
30
+ else
31
+ method_name = resource_service.option_text_for_association(attribute)
32
+ klass.all.map { |a| [a.public_send(method_name), a.id] }
33
+ end
34
+ end
35
+
36
+ def display_name_for(associated_record)
37
+ return nil unless associated_record
38
+ service = associated_service
39
+ service ? service.display_name(associated_record) : associated_record.to_s
40
+ end
41
+
42
+ def associated_service
43
+ @associated_service ||= begin
44
+ service_class = find_associated_service_class(reflection&.klass)
45
+ service_class&.new
46
+ rescue StandardError
47
+ nil
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def macro
54
+ reflection&.macro
55
+ end
56
+
57
+ def reflection
58
+ record.class.reflect_on_association(attribute)
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,57 @@
1
+ module Goodmin
2
+ module Fields
3
+ class Base
4
+ attr_reader :attribute, :record, :resource_service, :options
5
+
6
+ def initialize(attribute:, record:, resource_service:, **options)
7
+ @attribute = attribute
8
+ @record = record
9
+ @resource_service = resource_service
10
+ @options = options
11
+ end
12
+
13
+ def value
14
+ record.public_send(attribute)
15
+ end
16
+
17
+ def hint
18
+ hint_option = options[:hint]
19
+ result = if hint_option.is_a?(Proc)
20
+ hint_option.call(record)
21
+ elsif hint_option.present?
22
+ hint_option
23
+ else
24
+ i18n_hint
25
+ end
26
+ result&.html_safe
27
+ end
28
+
29
+ def find_associated_service_class(klass)
30
+ Goodmin::ServiceLocator.find_service_class_for(klass, context_service_class: resource_service.class)
31
+ end
32
+
33
+ def self.partial_index
34
+ "goodmin/fields/#{field_type}/index"
35
+ end
36
+
37
+ def self.partial_show
38
+ "goodmin/fields/#{field_type}/show"
39
+ end
40
+
41
+ def self.partial_form
42
+ "goodmin/fields/#{field_type}/form"
43
+ end
44
+
45
+ private_class_method def self.field_type
46
+ name.demodulize.underscore
47
+ end
48
+
49
+ private
50
+
51
+ def i18n_hint
52
+ model_key = record.class.model_name.i18n_key
53
+ I18n.t("activerecord.hints.#{model_key}.#{attribute}", default: nil)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,6 @@
1
+ module Goodmin
2
+ module Fields
3
+ class Boolean < Base
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Goodmin
2
+ module Fields
3
+ class Date < Base
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Goodmin
2
+ module Fields
3
+ class DateTime < Base
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,15 @@
1
+ module Goodmin
2
+ module Fields
3
+ class Enum < Select
4
+ def self.partial_form
5
+ Fields::Select.partial_form
6
+ end
7
+
8
+ def collection
9
+ (record.class.defined_enums[attribute.to_s] || {}).keys.map do |key|
10
+ [key.humanize, key]
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,41 @@
1
+ module Goodmin
2
+ module Fields
3
+ class NestedHasOne < Base
4
+ def associated_record
5
+ @associated_record ||= begin
6
+ existing = record.public_send(attribute)
7
+ return existing if existing
8
+
9
+ build_method = :"build_#{attribute}"
10
+ record.public_send(build_method) if record.respond_to?(build_method)
11
+ end
12
+ end
13
+
14
+ def associated_service
15
+ @associated_service ||= associated_service_class&.new
16
+ end
17
+
18
+ def nested_attributes
19
+ associated_service&.attrs_for_form || []
20
+ end
21
+
22
+ def nested_form_nodes
23
+ associated_service&.form_nodes || []
24
+ end
25
+
26
+ private
27
+
28
+ def reflection
29
+ record.class.reflect_on_association(attribute)
30
+ end
31
+
32
+ def associated_model_class
33
+ reflection&.klass
34
+ end
35
+
36
+ def associated_service_class
37
+ find_associated_service_class(associated_model_class)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,6 @@
1
+ module Goodmin
2
+ module Fields
3
+ class Number < Base
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Goodmin
2
+ module Fields
3
+ class Password < Base
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,19 @@
1
+ module Goodmin
2
+ module Fields
3
+ class Select < Base
4
+ def collection
5
+ col = options[:collection]
6
+ return [] if col.nil?
7
+ col.respond_to?(:call) ? col.call(record) : col
8
+ end
9
+
10
+ def select_options
11
+ options.fetch(:options, {})
12
+ end
13
+
14
+ def select_html_options
15
+ { class: "form-control" }.merge(options.fetch(:html_options, {}))
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,6 @@
1
+ module Goodmin
2
+ module Fields
3
+ class String < Base
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Goodmin
2
+ module Fields
3
+ class Text < Base
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,49 @@
1
+ require "active_support/all"
2
+
3
+ module Goodmin
4
+ module Generators
5
+ class Base < Rails::Generators::Base
6
+ def self.source_paths
7
+ %w[authentication install policy resource].map do |path|
8
+ File.expand_path("../../../generators/goodmin/#{path}/templates", __FILE__)
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ def namespace
15
+ @_namespace ||= Rails::Generators.namespace
16
+ end
17
+
18
+ def namespaced?
19
+ @_namespaced ||= namespace.present?
20
+ end
21
+
22
+ def namespaced_path
23
+ @_namespaced_path ||= begin
24
+ if namespaced?
25
+ namespace.name.split("::").map(&:underscore)
26
+ else
27
+ []
28
+ end
29
+ end
30
+ end
31
+
32
+ def module_namespacing(&block)
33
+ content = capture(&block)
34
+ content = wrap_with_namespace(content) if namespaced?
35
+ concat(content)
36
+ end
37
+
38
+ def indent(content, multiplier = 2)
39
+ spaces = " " * multiplier
40
+ content.each_line.map { |line| line.blank? ? line : "#{spaces}#{line}" }.join
41
+ end
42
+
43
+ def wrap_with_namespace(content)
44
+ content = indent(content).chomp
45
+ "module #{namespace.name}\n#{content}\nend\n"
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,31 @@
1
+ require "goodmin/generators/base"
2
+
3
+ module Goodmin
4
+ module Generators
5
+ class NamedBase < Base
6
+ argument :name, type: :string
7
+
8
+ private
9
+
10
+ def full_class_name
11
+ if namespaced?
12
+ "#{namespace}::#{class_name}"
13
+ else
14
+ class_name
15
+ end
16
+ end
17
+
18
+ def class_name
19
+ @_class_name ||= name.classify
20
+ end
21
+
22
+ def class_path
23
+ @_class_path ||= namespaced_path + name.classify.deconstantize.split("::").map(&:underscore)
24
+ end
25
+
26
+ def file_name
27
+ @_file_name ||= class_name.demodulize.underscore
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,47 @@
1
+ module Goodmin
2
+ module Helpers
3
+ module Application
4
+ # Renders the provided partial with locals if it exists, otherwise
5
+ # yields the given block. The lookup context call is cached for
6
+ # each partial.
7
+ # Returns the display name for a resource record by looking up its service
8
+ # class via ServiceLocator and calling display_name on it. Falls back to
9
+ # record.to_s if no service class is found.
10
+ def resource_display_name(record)
11
+ service_class = Goodmin::ServiceLocator.find_service_class_for(
12
+ record.class,
13
+ context_service_class: @resource_service&.class
14
+ )
15
+ service = service_class&.new
16
+ service ? service.display_name(record) : record.to_s
17
+ rescue StandardError
18
+ record.to_s
19
+ end
20
+
21
+ def partial_override(partial, locals = {}, &block)
22
+ @_partial_override ||= {}
23
+
24
+ unless @_partial_override.key?(partial)
25
+ @_partial_override[partial] = lookup_context.exists?(partial, nil, true)
26
+ end
27
+
28
+ if @_partial_override[partial]
29
+ render partial: partial, locals: locals
30
+ else
31
+ capture(&block)
32
+ end
33
+ end
34
+
35
+ # Wraps the policy helper so that it is always accessible, even when
36
+ # authorization is not enabled. When that is the case, it returns a
37
+ # policy that always returns true.
38
+ def policy(resource)
39
+ if authorization_enabled?
40
+ controller.policy(resource)
41
+ else
42
+ Authorization::Policy.new(nil, nil, default: true)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,21 @@
1
+ module Goodmin
2
+ module Helpers
3
+ module BatchActions
4
+ def batch_action_link(name, options)
5
+ return unless @resource_service.include_batch_action?(name)
6
+
7
+ link_to(
8
+ translate_scoped("batch_actions.labels.#{name}", default: name.to_s.titleize),
9
+ [*@resource_parents, @resource_class],
10
+ class: "btn btn-default hidden",
11
+ data: {
12
+ batch_actions_target: "actionLink",
13
+ action: "click->batch-actions#triggerAction",
14
+ confirm: options[:confirm] ? translate_scoped("batch_actions.confirm_message") : false,
15
+ value: name
16
+ }
17
+ )
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,123 @@
1
+ module Goodmin
2
+ module Helpers
3
+ module Filters
4
+ def filter_form(url: params.to_unsafe_h)
5
+ builder = FormBuilders::FilterFormBuilder.new("", nil, self, {})
6
+ form_tag(url, method: :get, class: "form-inline") do
7
+ yield(builder)
8
+ end
9
+ end
10
+ end
11
+ end
12
+
13
+ module FormBuilders
14
+ class FilterFormBuilder < ActionView::Helpers::FormBuilder
15
+ def filter_field(name, options, html_options = {})
16
+ case options[:as]
17
+ when :string
18
+ string_filter_field(name, options, html_options)
19
+ when :select
20
+ select_filter_field(name, options, html_options)
21
+ when :multiselect
22
+ multiselect_filter_field(name, options, html_options)
23
+ end
24
+ end
25
+
26
+ def string_filter_field(name, _options, html_options = {})
27
+ label_text = @template.translate_scoped("filters.labels.#{name}", default: name.to_s.titleize)
28
+ @template.content_tag(:div, class: "form-group filter") do
29
+ @template.concat(@template.label_tag(name, label_text, class: "control-label"))
30
+ @template.concat(@template.text_field_tag(
31
+ "filter[#{name}]",
32
+ default_filter_value(name),
33
+ { placeholder: label_text, class: "form-control", id: name }.merge(html_options)
34
+ ))
35
+ end
36
+ end
37
+
38
+ def select_filter_field(name, options, html_options = {})
39
+ filter_select(
40
+ name, options, {
41
+ name: "filter[#{name}]",
42
+ data: {
43
+ placeholder: @template.translate_scoped("filters.select.placeholder.one")
44
+ }
45
+ }.deep_merge(html_options)
46
+ )
47
+ end
48
+
49
+ def multiselect_filter_field(name, options, html_options = {})
50
+ filter_select(
51
+ name, {
52
+ include_hidden: false
53
+ }.deep_merge(options), {
54
+ name: "filter[#{name}][]",
55
+ multiple: true,
56
+ data: {
57
+ placeholder: @template.translate_scoped("filters.select.placeholder.many")
58
+ }
59
+ }.deep_merge(html_options)
60
+ )
61
+ end
62
+
63
+ def apply_filters_button
64
+ submit @template.translate_scoped("filters.buttons.apply"), class: "btn btn-primary"
65
+ end
66
+
67
+ def clear_filters_button
68
+ @template.link_to(
69
+ @template.translate_scoped("filters.buttons.clear"),
70
+ @template.url_for(
71
+ @template.params.to_unsafe_h.slice(:scope, :order)
72
+ ),
73
+ class: "btn btn-default"
74
+ )
75
+ end
76
+
77
+ private
78
+
79
+ def filter_select(name, options, html_options)
80
+ unless options[:collection].is_a? Proc
81
+ raise "A collection proc must be specified for select filters"
82
+ end
83
+
84
+ options = options.dup
85
+
86
+ collection = options.delete(:collection).call
87
+ options.delete(:include_hidden)
88
+
89
+ label_text = @template.translate_scoped("filters.labels.#{name}", default: name.to_s.titleize)
90
+
91
+ choices =
92
+ if collection.is_a? ActiveRecord::Relation
93
+ @template.options_from_collection_for_select(
94
+ collection,
95
+ options.delete(:option_value),
96
+ options.delete(:option_text),
97
+ selected: default_filter_value(name)
98
+ )
99
+ else
100
+ @template.options_for_select(
101
+ collection,
102
+ selected: default_filter_value(name)
103
+ )
104
+ end
105
+
106
+ blank_option = @template.content_tag(:option, "", value: "")
107
+
108
+ @template.content_tag(:div, class: "form-group filter") do
109
+ @template.concat(@template.label_tag(name, label_text, class: "control-label"))
110
+ @template.concat(@template.select_tag(
111
+ html_options[:name] || "filter[#{name}]",
112
+ @template.safe_join([blank_option, choices]),
113
+ { class: "form-control", id: name }.merge(html_options.except(:name))
114
+ ))
115
+ end
116
+ end
117
+
118
+ def default_filter_value(name)
119
+ @template.params[:filter] ? @template.params[:filter][name] : nil
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,42 @@
1
+ module Goodmin
2
+ module Helpers
3
+ module Forms
4
+ def form_for(record, options = {}, &block)
5
+ super(record, {
6
+ url: [*@resource_parents, record]
7
+ }.merge(options), &block)
8
+ end
9
+
10
+ def render_form_nodes(nodes, f)
11
+ safe_join(nodes.map { |node| render_form_node(node, f) })
12
+ end
13
+
14
+ def render_form_nodes_for(nodes, f, record:, resource_service:)
15
+ old_resource = @resource
16
+ old_resource_service = @resource_service
17
+ @resource = record
18
+ @resource_service = resource_service
19
+ render_form_nodes(nodes, f)
20
+ ensure
21
+ @resource = old_resource
22
+ @resource_service = old_resource_service
23
+ end
24
+
25
+ private
26
+
27
+ def render_form_node(node, f)
28
+ case node
29
+ when Resources::AttributeNode
30
+ field = node.attribute.to_field(record: @resource, resource_service: @resource_service)
31
+ render partial: field.class.partial_form, locals: { field: field, f: f }
32
+ when Resources::HtmlNode
33
+ content_tag(node.tag, node.html_attrs) do
34
+ render_form_nodes(node.children, f)
35
+ end
36
+ when Resources::ComponentNode
37
+ node.component.render(self, f)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,52 @@
1
+ module Goodmin
2
+ module Helpers
3
+ module Navigation
4
+ def navbar_item(resource, url = resource, show: nil, icon: nil, **options)
5
+ show ||= lambda do
6
+ resource.is_a?(String) ? true : policy(resource).index?
7
+ end
8
+
9
+ return unless show.call
10
+
11
+ link_text =
12
+ if block_given?
13
+ capture do
14
+ yield
15
+ end
16
+ else
17
+ resource.respond_to?(:model_name) ? resource.model_name.human(count: :many) : resource
18
+ end
19
+
20
+ content_tag :li do
21
+ link_to url, options do
22
+ if icon.present?
23
+ concat content_tag :span, "", class: "glyphicon glyphicon-#{icon}"
24
+ concat " "
25
+ end
26
+ concat link_text
27
+ end
28
+ end
29
+ end
30
+
31
+ def navbar_dropdown(title)
32
+ dropdown_toggle = link_to "#", class: "dropdown-toggle", data: { toggle: "dropdown" } do
33
+ concat "#{title} "
34
+ concat content_tag :span, "", class: "caret"
35
+ end
36
+
37
+ dropdown_menu = content_tag :ul, class: "dropdown-menu" do
38
+ yield
39
+ end
40
+
41
+ content_tag :li, class: "dropdown" do
42
+ concat dropdown_toggle
43
+ concat dropdown_menu
44
+ end
45
+ end
46
+
47
+ def navbar_divider
48
+ content_tag :li, "", class: "divider"
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,25 @@
1
+ module Goodmin
2
+ module Helpers
3
+ module Tables
4
+ def column_header(attribute)
5
+ if @resource_service.orderable_column?(attribute.name.to_s)
6
+ direction =
7
+ if params[:order].present?
8
+ if params[:order].match(/\A#{attribute.name.to_s}_(asc|desc)\z/)
9
+ $1 == "desc" ? "asc" : "desc"
10
+ elsif params[:order].match(/\A\w+_(asc|desc)\z/)
11
+ $1
12
+ else
13
+ "desc"
14
+ end
15
+ else
16
+ "desc"
17
+ end
18
+ link_to @resource_class.human_attribute_name(attribute.name.to_s), url_for(params.to_unsafe_h.merge(order: "#{attribute.name}_#{direction}"))
19
+ else
20
+ @resource_class.human_attribute_name(attribute.name.to_s)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,19 @@
1
+ module Goodmin
2
+ module Helpers
3
+ module Translations
4
+ def translate_scoped(translate, scope: nil, default: nil, **options)
5
+ if @resource_class
6
+ scope ||= @resource_class.to_s.underscore
7
+ end
8
+
9
+ defaults = [
10
+ ["goodmin", scope, translate].compact.join(".").to_sym,
11
+ ["goodmin", translate].compact.join(".").to_sym,
12
+ default
13
+ ]
14
+
15
+ t(defaults.shift, default: defaults, **options)
16
+ end
17
+ end
18
+ end
19
+ end