trestle 0.8.4 → 0.8.11

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 (164) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +4 -0
  3. data/.travis.yml +14 -1
  4. data/Gemfile +6 -0
  5. data/README.md +15 -9
  6. data/app/assets/javascripts/trestle/admin.js +11 -10
  7. data/app/assets/javascripts/trestle/components/_confirmation.js +40 -0
  8. data/app/assets/javascripts/trestle/components/_datepicker.js +37 -0
  9. data/app/assets/javascripts/trestle/components/_dialog.js +136 -0
  10. data/app/assets/javascripts/trestle/{_errors.js → components/_errors.js} +2 -2
  11. data/app/assets/javascripts/trestle/components/_form.js +75 -0
  12. data/app/assets/javascripts/trestle/{_gallery.js → components/_gallery.js} +3 -3
  13. data/app/assets/javascripts/trestle/{_select.js → components/_select.js} +2 -2
  14. data/app/assets/javascripts/trestle/components/_sidebar.js +77 -0
  15. data/app/assets/javascripts/trestle/{_table.js → components/_table.js} +1 -1
  16. data/app/assets/javascripts/trestle/components/_tabs.js +25 -0
  17. data/app/assets/javascripts/trestle/components/_tooltips.js +19 -0
  18. data/app/assets/javascripts/trestle/core/_contexts.js +13 -0
  19. data/app/assets/javascripts/trestle/core/_cookies.js +24 -0
  20. data/app/assets/javascripts/trestle/core/_events.js +39 -0
  21. data/app/assets/javascripts/trestle/core/_l10n.js +23 -0
  22. data/app/assets/javascripts/trestle/core/_visit.js +10 -0
  23. data/app/assets/stylesheets/trestle/components/_alerts.scss +28 -0
  24. data/app/assets/stylesheets/trestle/components/_avatar.scss +23 -1
  25. data/app/assets/stylesheets/trestle/components/_breadcrumbs.scss +12 -0
  26. data/app/assets/stylesheets/trestle/components/_buttons.scss +111 -1
  27. data/app/assets/stylesheets/trestle/components/_content.scss +53 -21
  28. data/app/assets/stylesheets/trestle/components/_datepicker.scss +28 -0
  29. data/app/assets/stylesheets/trestle/components/_fields.scss +17 -0
  30. data/app/assets/stylesheets/trestle/components/_input-group.scss +26 -1
  31. data/app/assets/stylesheets/trestle/components/_modal.scss +108 -0
  32. data/app/assets/stylesheets/trestle/components/_navigation.scss +104 -14
  33. data/app/assets/stylesheets/trestle/components/_pagination.scss +3 -3
  34. data/app/assets/stylesheets/trestle/components/_scopes.scss +11 -2
  35. data/app/assets/stylesheets/trestle/components/_sidebar.scss +3 -2
  36. data/app/assets/stylesheets/trestle/components/_table.scss +34 -10
  37. data/app/assets/stylesheets/trestle/components/_tags.scss +9 -0
  38. data/app/assets/stylesheets/trestle/components/_timestamp.scss +4 -1
  39. data/app/assets/stylesheets/trestle/components/_toolbars.scss +55 -0
  40. data/app/assets/stylesheets/trestle/components/_wells.scss +9 -1
  41. data/app/assets/stylesheets/trestle/core/_defaults.scss +21 -6
  42. data/app/assets/stylesheets/trestle/core/_layout.scss +8 -0
  43. data/app/assets/stylesheets/trestle/core/_mixins.scss +11 -0
  44. data/app/assets/stylesheets/trestle/core/_typography.scss +39 -0
  45. data/app/controllers/concerns/trestle/controller/breadcrumbs.rb +21 -0
  46. data/app/controllers/concerns/trestle/controller/callbacks.rb +21 -0
  47. data/app/controllers/concerns/trestle/controller/dialog.rb +16 -0
  48. data/app/controllers/concerns/trestle/controller/helpers.rb +18 -0
  49. data/app/controllers/concerns/trestle/controller/layout.rb +16 -0
  50. data/app/controllers/concerns/trestle/controller/location.rb +15 -0
  51. data/app/controllers/trestle/application_controller.rb +6 -30
  52. data/app/helpers/trestle/avatar_helper.rb +9 -2
  53. data/app/helpers/trestle/flash_helper.rb +15 -0
  54. data/app/helpers/trestle/form_helper.rb +8 -5
  55. data/app/helpers/trestle/format_helper.rb +7 -3
  56. data/app/helpers/trestle/headings_helper.rb +27 -0
  57. data/app/helpers/trestle/hook_helper.rb +13 -4
  58. data/app/helpers/trestle/i18n_helper.rb +14 -0
  59. data/app/helpers/trestle/panel_helper.rb +24 -0
  60. data/app/helpers/trestle/tab_helper.rb +2 -2
  61. data/app/helpers/trestle/table_helper.rb +41 -2
  62. data/app/helpers/trestle/timestamp_helper.rb +49 -7
  63. data/app/helpers/trestle/toolbars_helper.rb +34 -0
  64. data/app/helpers/trestle/url_helper.rb +72 -8
  65. data/app/views/layouts/trestle/admin.html.erb +3 -5
  66. data/app/views/trestle/application/_dialog.html.erb +34 -0
  67. data/app/views/trestle/application/_header.html.erb +22 -20
  68. data/app/views/trestle/application/_layout.html.erb +1 -1
  69. data/app/views/trestle/application/_tabs.html.erb +8 -2
  70. data/app/views/trestle/{application → flash}/_alert.html.erb +7 -2
  71. data/app/views/trestle/flash/_debug.html.erb +8 -0
  72. data/app/views/trestle/flash/_flash.html.erb +7 -0
  73. data/app/views/trestle/resource/_scopes.html.erb +1 -1
  74. data/app/views/trestle/resource/edit.html.erb +8 -10
  75. data/app/views/trestle/resource/index.html.erb +16 -4
  76. data/app/views/trestle/resource/new.html.erb +6 -6
  77. data/app/views/trestle/resource/show.html.erb +8 -10
  78. data/app/views/trestle/shared/_sidebar.html.erb +10 -7
  79. data/app/views/trestle/shared/_title.html.erb +14 -0
  80. data/app/views/trestle/table/_table.html.erb +4 -6
  81. data/bower.json +1 -1
  82. data/config/locales/cs.rb +18 -0
  83. data/config/locales/cs.yml +95 -0
  84. data/config/locales/en.yml +65 -36
  85. data/config/locales/es-MX.yml +94 -0
  86. data/config/locales/es.yml +94 -0
  87. data/config/locales/fr.rb +18 -0
  88. data/config/locales/fr.yml +84 -0
  89. data/config/locales/lv.rb +18 -0
  90. data/config/locales/lv.yml +94 -0
  91. data/config/locales/nl.rb +18 -0
  92. data/config/locales/nl.yml +82 -0
  93. data/config/locales/pl.rb +18 -0
  94. data/config/locales/pl.yml +85 -0
  95. data/config/locales/pt-BR.rb +18 -0
  96. data/config/locales/pt-BR.yml +84 -0
  97. data/config/locales/zh-CN.rb +18 -0
  98. data/config/locales/zh-CN.yml +94 -0
  99. data/gemfiles/rails-4.2.gemfile +16 -0
  100. data/gemfiles/rails-5.0.gemfile +16 -0
  101. data/gemfiles/rails-5.1.gemfile +16 -0
  102. data/gemfiles/rails-5.2.gemfile +16 -0
  103. data/gemfiles/rails-edge.gemfile +17 -0
  104. data/lib/generators/trestle/install/templates/trestle.rb.erb +18 -2
  105. data/lib/generators/trestle/resource/templates/admin.rb.erb +2 -2
  106. data/lib/trestle/adapters/active_record_adapter.rb +2 -6
  107. data/lib/trestle/adapters/adapter.rb +40 -15
  108. data/lib/trestle/adapters/sequel_adapter.rb +2 -6
  109. data/lib/trestle/admin/builder.rb +42 -12
  110. data/lib/trestle/admin/controller.rb +3 -1
  111. data/lib/trestle/admin.rb +84 -3
  112. data/lib/trestle/breadcrumb.rb +16 -1
  113. data/lib/trestle/configurable.rb +6 -0
  114. data/lib/trestle/configuration.rb +28 -5
  115. data/lib/trestle/display.rb +1 -1
  116. data/lib/trestle/engine.rb +10 -4
  117. data/lib/trestle/form/automatic.rb +6 -3
  118. data/lib/trestle/form/builder.rb +5 -1
  119. data/lib/trestle/form/field.rb +1 -1
  120. data/lib/trestle/form/fields/date_picker.rb +2 -2
  121. data/lib/trestle/form/fields/form_control.rb +14 -4
  122. data/lib/trestle/form/fields/form_group.rb +14 -3
  123. data/lib/trestle/form/fields/select.rb +5 -1
  124. data/lib/trestle/form/fields/tag_select.rb +1 -1
  125. data/lib/trestle/form/renderer.rb +2 -2
  126. data/lib/trestle/form.rb +7 -3
  127. data/lib/trestle/hook.rb +27 -0
  128. data/lib/trestle/model_name.rb +64 -0
  129. data/lib/trestle/navigation/group.rb +16 -0
  130. data/lib/trestle/navigation/item.rb +11 -1
  131. data/lib/trestle/navigation.rb +24 -6
  132. data/lib/trestle/options.rb +7 -5
  133. data/lib/trestle/reloader.rb +1 -1
  134. data/lib/trestle/resource/adapter_methods.rb +62 -0
  135. data/lib/trestle/resource/builder.rb +43 -17
  136. data/lib/trestle/resource/collection.rb +68 -0
  137. data/lib/trestle/resource/controller.rb +70 -33
  138. data/lib/trestle/resource.rb +97 -105
  139. data/lib/trestle/scope.rb +13 -3
  140. data/lib/trestle/tab.rb +4 -0
  141. data/lib/trestle/table/actions_column.rb +41 -31
  142. data/lib/trestle/table/automatic.rb +1 -2
  143. data/lib/trestle/table/builder.rb +2 -2
  144. data/lib/trestle/table/column.rb +40 -9
  145. data/lib/trestle/table/row.rb +18 -2
  146. data/lib/trestle/table.rb +18 -2
  147. data/lib/trestle/toolbar/builder.rb +52 -0
  148. data/lib/trestle/toolbar/context.rb +39 -0
  149. data/lib/trestle/toolbar.rb +43 -0
  150. data/lib/trestle/version.rb +1 -1
  151. data/lib/trestle.rb +9 -4
  152. data/trestle.gemspec +5 -3
  153. data/vendor/assets/bower_components/trestle/select2/dist/js/select2.full.js +90 -69
  154. data/vendor/assets/javascripts/trestle/flatpickr.js.erb +2 -0
  155. data/vendor/assets/stylesheets/trestle/magnific-popup.scss +13 -1
  156. metadata +99 -22
  157. data/app/assets/javascripts/trestle/_confirmation.js +0 -23
  158. data/app/assets/javascripts/trestle/_datepicker.js +0 -22
  159. data/app/assets/javascripts/trestle/_form.js +0 -6
  160. data/app/assets/javascripts/trestle/_sidebar.js +0 -52
  161. data/app/assets/javascripts/trestle/_tabs.js +0 -13
  162. data/app/assets/javascripts/trestle/_tooltips.js +0 -3
  163. data/app/helpers/trestle/title_helper.rb +0 -26
  164. data/app/views/trestle/application/_flash.html.erb +0 -11
@@ -0,0 +1,17 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :test do
4
+ gem 'coveralls', require: false
5
+ gem 'simplecov', require: false
6
+
7
+ gem 'capybara'
8
+ gem 'capybara-selenium'
9
+ gem 'selenium-webdriver'
10
+ gem 'chromedriver-helper'
11
+ gem 'puma'
12
+ end
13
+
14
+ gem 'rails', github: 'rails/rails'
15
+ gem 'arel', github: 'rails/arel'
16
+
17
+ gemspec :path => "../"
@@ -20,6 +20,11 @@ Trestle.configure do |config|
20
20
  #
21
21
  # config.footer = "Powered by Trestle"
22
22
 
23
+ # Sets the default precision for timestamps (either :minutes or :seconds).
24
+ # Defaults to :minutes.
25
+ #
26
+ # config.timestamp_precision = :minutes
27
+
23
28
  # == Mounting Options
24
29
  #
25
30
  # Set the path at which to mount the Trestle admin. Defaults to /admin.
@@ -33,10 +38,15 @@ Trestle.configure do |config|
33
38
 
34
39
  # == Navigation Options
35
40
  #
41
+ # Set the path to consider the application root (for title links and breadcrumbs).
42
+ # Defaults to the same value as `config.path`.
43
+ #
44
+ # config.root = "/"
45
+
36
46
  # Set the initial breadcrumbs to display in the breadcrumb trail.
37
- # Defaults to a breadcrumb labeled 'Home' linking to to the admin root.
47
+ # Defaults to a breadcrumb labeled 'Home' linking to to the application root.
38
48
  #
39
- # config.root_breadcrumbs = -> { [Trestle::Breadcrumb.new("Home", Trestle.config.path)] }
49
+ # config.root_breadcrumbs = -> { [Trestle::Breadcrumb.new("Home", Trestle.config.root)] }
40
50
 
41
51
  # Set the default icon class to use when it is not explicitly provided.
42
52
  # Defaults to "fa fa-arrow-circle-o-right".
@@ -105,4 +115,10 @@ Trestle.configure do |config|
105
115
  # end
106
116
  #
107
117
  # config.form_field :custom, CustomFormField
118
+
119
+ # == Debugging Options
120
+ #
121
+ # Enable debugging of form errors. Defaults to true in development mode.
122
+ #
123
+ # config.debug_form_errors = true
108
124
  end
@@ -13,7 +13,7 @@ Trestle.resource(:<%= plural_name %><% if module? %>, scope: <%= module_name %><
13
13
 
14
14
  # Customize the form fields shown on the new/edit views.
15
15
  #
16
- # form do |<%= singular_name %>|
16
+ # form do |<%= singular_table_name %>|
17
17
  # text_field :name
18
18
  #
19
19
  # row do
@@ -30,6 +30,6 @@ Trestle.resource(:<%= plural_name %><% if module? %>, scope: <%= module_name %><
30
30
  # http://guides.rubyonrails.org/action_controller_overview.html#strong-parameters
31
31
  #
32
32
  # params do |params|
33
- # params.require(:<%= singular_name %>).require(:name, ...)
33
+ # params.require(:<%= singular_table_name %>).permit(:name, ...)
34
34
  # end
35
35
  end
@@ -17,18 +17,14 @@ module Trestle
17
17
  instance.assign_attributes(attrs)
18
18
  end
19
19
 
20
- def save_instance(instance)
20
+ def save_instance(instance, params={})
21
21
  instance.save
22
22
  end
23
23
 
24
- def delete_instance(instance)
24
+ def delete_instance(instance, params={})
25
25
  instance.destroy
26
26
  end
27
27
 
28
- def unscope(scope)
29
- scope.unscoped
30
- end
31
-
32
28
  def merge_scopes(scope, other)
33
29
  scope.merge(other)
34
30
  end
@@ -4,8 +4,9 @@ module Trestle
4
4
  attr_reader :admin
5
5
  delegate :model, to: :admin
6
6
 
7
- def initialize(admin)
7
+ def initialize(admin, context=nil)
8
8
  @admin = admin
9
+ @context = context
9
10
  end
10
11
 
11
12
  # Loads the initial collection for use by the index action.
@@ -46,21 +47,35 @@ module Trestle
46
47
  # Saves an instance (used by the create and update actions).
47
48
  #
48
49
  # instance - The instance to save
50
+ # params - Unfiltered params hash from the controller
49
51
  #
50
52
  # Returns a boolean indicating the success/fail status of the save.
51
- def save_instance(instance)
53
+ def save_instance(instance, params={})
52
54
  raise NotImplementedError
53
55
  end
54
56
 
55
57
  # Deletes an instance (used by the destroy action).
56
58
  #
57
59
  # instance - The instance to delete
60
+ # params - Unfiltered params hash from the controller
58
61
  #
59
62
  # Returns a boolean indicating the success/fail status of the deletion.
60
- def delete_instance(instance)
63
+ def delete_instance(instance, params={})
61
64
  raise NotImplementedError
62
65
  end
63
66
 
67
+ # Finalizes a collection so that it can be rendered within the index view.
68
+ #
69
+ # In most cases (e.g. ActiveRecord), no finalization is required. However if you are using a search library then
70
+ # you may need to explicitly execute the search, or access the models via a #records or #objects method.
71
+ #
72
+ # collection - The collection to finalize
73
+ #
74
+ # Returns an enumerable collection of instances.
75
+ def finalize_collection(collection)
76
+ collection
77
+ end
78
+
64
79
  # Decorates a collection for rendering by the index view.
65
80
  # Decorating is the final step in preparing the collection for the view.
66
81
  #
@@ -82,15 +97,6 @@ module Trestle
82
97
  instance.id
83
98
  end
84
99
 
85
- # Unscopes a collection so that it can be merged with other scopes without duplication or interference.
86
- #
87
- # scope - The scope to unscope
88
- #
89
- # Returns a scope object.
90
- def unscope(scope)
91
- scope
92
- end
93
-
94
100
  # Merges scopes together for Trestle scope application and counting.
95
101
  #
96
102
  # scope - The first scope
@@ -130,8 +136,10 @@ module Trestle
130
136
  #
131
137
  # Returns a Kaminari-compatible scope corresponding to a single page.
132
138
  def paginate(collection, params)
133
- collection = Kaminari.paginate_array(collection.to_a) unless collection.respond_to?(:page)
134
- collection.page(params[:page])
139
+ collection = Kaminari.paginate_array(collection.to_a) unless collection.respond_to?(Kaminari.config.page_method_name)
140
+ per_page = admin.pagination_options[:per]
141
+
142
+ collection.public_send(Kaminari.config.page_method_name, params[:page]).per(per_page)
135
143
  end
136
144
 
137
145
  # Filters the submitted form parameters and returns a whitelisted attributes 'hash'
@@ -144,7 +152,7 @@ module Trestle
144
152
  #
145
153
  # Returns the permitted set of parameters as a ActionController::Parameters object.
146
154
  def permitted_params(params)
147
- params.require(admin.admin_name.singularize).permit!
155
+ params.require(admin.parameter_name).permit!
148
156
  end
149
157
 
150
158
  # Produces a human-readable name for a given attribute, applying I18n where appropriate.
@@ -171,6 +179,23 @@ module Trestle
171
179
  def default_form_attributes
172
180
  raise NotImplementedError
173
181
  end
182
+
183
+ protected
184
+ # Missing methods are called on the given context if available.
185
+ #
186
+ # We include private methods as methods such as current_user
187
+ # are usually declared as private or protected.
188
+ def method_missing(name, *args, &block)
189
+ if @context && @context.respond_to?(name, true)
190
+ @context.send(name, *args, &block)
191
+ else
192
+ super
193
+ end
194
+ end
195
+
196
+ def respond_to_missing?(name, include_private=false)
197
+ (@context && @context.respond_to?(name, true)) || super
198
+ end
174
199
  end
175
200
  end
176
201
  end
@@ -26,18 +26,14 @@ module Trestle
26
26
  instance.set(attrs)
27
27
  end
28
28
 
29
- def save_instance(instance)
29
+ def save_instance(instance, params={})
30
30
  instance.save
31
31
  end
32
32
 
33
- def delete_instance(instance)
33
+ def delete_instance(instance, params={})
34
34
  instance.destroy
35
35
  end
36
36
 
37
- def unscope(scope)
38
- scope.unfiltered
39
- end
40
-
41
37
  def merge_scopes(scope, other)
42
38
  scope.intersect(other)
43
39
  end
@@ -11,23 +11,32 @@ module Trestle
11
11
 
12
12
  delegate :helper, :before_action, :after_action, :around_action, to: :@controller
13
13
 
14
- def initialize(name, options={})
14
+ def initialize(admin)
15
+ @admin, @controller = admin, admin.const_get(:AdminController)
16
+ end
17
+
18
+ def self.create(name, options={}, &block)
15
19
  # Create admin subclass
16
- @admin = Class.new(admin_class)
17
- @admin.options = options
20
+ admin = Class.new(admin_class)
21
+ admin.options = options
18
22
 
19
23
  # Define a constant based on the admin name
20
24
  scope = options[:scope] || Object
21
- scope.const_set("#{name.to_s.camelize}Admin", @admin)
25
+ scope.const_set("#{name.to_s.camelize}Admin", admin)
22
26
 
23
27
  # Define admin controller class
24
28
  # This is done using class_eval rather than Class.new so that the full
25
29
  # class name and parent chain is set when Rails' inherited hooks are called.
26
- @admin.class_eval("class AdminController < #{self.class.controller.name}; end")
30
+ admin.class_eval("class AdminController < #{controller.name}; end")
27
31
 
28
32
  # Set a reference on the controller class to the admin class
29
- @controller = @admin.const_get("AdminController")
30
- @controller.instance_variable_set("@admin", @admin)
33
+ controller = admin.const_get(:AdminController)
34
+ controller.instance_variable_set("@admin", admin)
35
+
36
+ admin.build(&block)
37
+ admin.validate!
38
+
39
+ admin
31
40
  end
32
41
 
33
42
  def menu(*args, &block)
@@ -38,16 +47,17 @@ module Trestle
38
47
  end
39
48
  end
40
49
 
41
- def table(options={}, &block)
42
- admin.table = Table::Builder.build(options.reverse_merge(admin: admin, sortable: true), &block)
50
+ def table(name_or_options={}, options={}, &block)
51
+ name, options = normalize_table_options(name_or_options, options)
52
+ admin.tables[name] = Table::Builder.build(options, &block)
43
53
  end
44
54
 
45
- def form(&block)
46
- admin.form = Form.new(&block)
55
+ def form(options={}, &block)
56
+ admin.form = Form.new(options, &block)
47
57
  end
48
58
 
49
59
  def admin(&block)
50
- @admin.singleton_class.class_eval(&block) if block_given?
60
+ @admin.instance_eval(&block) if block_given?
51
61
  @admin
52
62
  end
53
63
 
@@ -59,6 +69,26 @@ module Trestle
59
69
  def routes(&block)
60
70
  @admin.additional_routes = block
61
71
  end
72
+
73
+ def breadcrumb(label=nil, path=nil, &block)
74
+ if block_given?
75
+ @admin.breadcrumb = block
76
+ elsif label
77
+ @admin.breadcrumb = -> { Breadcrumb.new(label, path) }
78
+ else
79
+ @admin.breadcrumb = -> { false }
80
+ end
81
+ end
82
+
83
+ protected
84
+ def normalize_table_options(name, options)
85
+ if name.is_a?(Hash)
86
+ # Default index table
87
+ name, options = :index, name
88
+ end
89
+
90
+ [name, options]
91
+ end
62
92
  end
63
93
  end
64
94
  end
@@ -12,7 +12,9 @@ module Trestle
12
12
  end
13
13
  end
14
14
 
15
- delegate :admin, to: :class
15
+ def admin
16
+ @_admin ||= self.class.admin.new(self)
17
+ end
16
18
  helper_method :admin
17
19
 
18
20
  protected
data/lib/trestle/admin.rb CHANGED
@@ -5,32 +5,93 @@ module Trestle
5
5
  autoload :Builder
6
6
  autoload :Controller
7
7
 
8
+ delegate :to_param, to: :class
9
+
10
+ def initialize(context=nil)
11
+ @context = context
12
+ end
13
+
14
+ # Delegate all missing methods to corresponding class method if available
15
+ def method_missing(name, *args, &block)
16
+ if self.class.respond_to?(name)
17
+ self.class.send(name, *args, &block)
18
+ else
19
+ super
20
+ end
21
+ end
22
+
23
+ def respond_to_missing?(name, include_private=false)
24
+ self.class.respond_to?(name, include_private) || super
25
+ end
26
+
8
27
  class << self
9
28
  attr_accessor :menu
10
29
 
11
- attr_accessor :table
12
30
  attr_accessor :form
13
31
 
14
32
  attr_accessor :additional_routes
15
33
 
16
34
  attr_writer :options
35
+ attr_writer :breadcrumb
17
36
 
18
37
  def options
19
38
  @options ||= {}
20
39
  end
21
40
 
41
+ def tables
42
+ @tables ||= {}
43
+ end
44
+
45
+ # Deprecated: Use `tables[:index]` instead
46
+ def table
47
+ tables[:index]
48
+ end
49
+
50
+ # Deprecated: Use `tables[:index]=` instead
51
+ def table=(table)
52
+ tables[:index] = table
53
+ end
54
+
22
55
  def breadcrumbs
23
56
  Breadcrumb::Trail.new(Array(Trestle.config.root_breadcrumbs) + [breadcrumb])
24
57
  end
25
58
 
26
59
  def breadcrumb
27
- Breadcrumb.new(admin_name.titleize, path)
60
+ if @breadcrumb
61
+ Breadcrumb.cast(@breadcrumb.call)
62
+ else
63
+ default_breadcrumb
64
+ end
65
+ end
66
+
67
+ def default_breadcrumb
68
+ Breadcrumb.new(human_admin_name, path)
28
69
  end
29
70
 
30
71
  def admin_name
31
72
  name.underscore.sub(/_admin$/, '')
32
73
  end
33
74
 
75
+ def i18n_key
76
+ admin_name
77
+ end
78
+
79
+ def human_admin_name
80
+ I18n.t("admin.breadcrumbs.#{i18n_key}", default: name.demodulize.underscore.sub(/_admin$/, '').titleize)
81
+ end
82
+
83
+ def translate(key, options={})
84
+ defaults = [:"admin.#{i18n_key}.#{key}", :"admin.#{key}"]
85
+ defaults << options[:default] if options[:default]
86
+
87
+ I18n.t(defaults.shift, options.merge(default: defaults))
88
+ end
89
+ alias t translate
90
+
91
+ def parameter_name
92
+ admin_name.singularize
93
+ end
94
+
34
95
  def route_name
35
96
  "#{admin_name.tr('/', '_')}_admin"
36
97
  end
@@ -43,10 +104,22 @@ module Trestle
43
104
  "#{name.underscore}/admin"
44
105
  end
45
106
 
46
- def path(action=:index, options={})
107
+ def path(action=root_action, options={})
47
108
  Engine.routes.url_for(options.merge(controller: controller_namespace, action: action, only_path: true))
48
109
  end
49
110
 
111
+ def to_param(*)
112
+ raise NoMethodError, "#to_param called on non-resourceful admin. You may need to explicitly specify the admin."
113
+ end
114
+
115
+ def actions
116
+ [:index]
117
+ end
118
+
119
+ def root_action
120
+ :index
121
+ end
122
+
50
123
  def routes
51
124
  admin = self
52
125
 
@@ -62,6 +135,14 @@ module Trestle
62
135
  def railtie_routes_url_helpers(include_path_helpers=true)
63
136
  Trestle.railtie_routes_url_helpers(include_path_helpers)
64
137
  end
138
+
139
+ def build(&block)
140
+ Admin::Builder.build(self, &block)
141
+ end
142
+
143
+ def validate!
144
+ # No validations by default. This can be overridden in subclasses.
145
+ end
65
146
  end
66
147
  end
67
148
  end
@@ -10,11 +10,26 @@ module Trestle
10
10
  label == other.label && path == other.path
11
11
  end
12
12
 
13
+ def self.cast(obj)
14
+ case obj
15
+ when Breadcrumb
16
+ obj
17
+ when String
18
+ new(obj)
19
+ when Array
20
+ new(*obj)
21
+ when NilClass, false
22
+ nil
23
+ else
24
+ raise ArgumentError, "Unable to cast #{obj.inspect} to Breadcrumb"
25
+ end
26
+ end
27
+
13
28
  class Trail
14
29
  include Enumerable
15
30
 
16
31
  def initialize(breadcrumbs=[])
17
- @breadcrumbs = Array(breadcrumbs)
32
+ @breadcrumbs = Array(breadcrumbs).compact
18
33
  end
19
34
 
20
35
  def ==(other)
@@ -17,6 +17,12 @@ module Trestle
17
17
  @options ||= {}
18
18
  end
19
19
 
20
+ def as_json(options=nil)
21
+ @options.each_with_object({}) do |(k, v), h|
22
+ h[k] = v.as_json(options)
23
+ end
24
+ end
25
+
20
26
  def inspect
21
27
  "#<#{self.class.name || "Anonymous(Trestle::Configurable)"}>"
22
28
  end
@@ -16,6 +16,9 @@ module Trestle
16
16
  # Text shown in the admin page footer
17
17
  option :footer, -> { I18n.t("trestle.footer", default: "Powered by Trestle") }
18
18
 
19
+ # Default timestamp precision
20
+ option :timestamp_precision, :minutes
21
+
19
22
 
20
23
  ## Mounting Options
21
24
 
@@ -28,8 +31,11 @@ module Trestle
28
31
 
29
32
  ## Navigation Options
30
33
 
34
+ # Path to consider the application root (for title links and breadcrumbs)
35
+ option :root, -> { Trestle.config.path }
36
+
31
37
  # Initial breadcrumbs to display in the breadcrumb trail
32
- option :root_breadcrumbs, -> { [Trestle::Breadcrumb.new(I18n.t("admin.breadcrumbs.home", default: "Home"), Trestle.config.path)] }
38
+ option :root_breadcrumbs, -> { [Trestle::Breadcrumb.new(I18n.t("admin.breadcrumbs.home", default: "Home"), Trestle.config.root)] }
33
39
 
34
40
  # Default icon class to use when it is not explicitly provided
35
41
  option :default_navigation_icon, "fa fa-arrow-circle-o-right"
@@ -64,7 +70,7 @@ module Trestle
64
70
  option :persistent_params, [:sort, :order, :scope]
65
71
 
66
72
  # List of methods to try calling on an instance when displayed by the `display` helper
67
- option :display_methods, [:display_name, :full_name, :name, :title, :username, :login, :email, :to_s]
73
+ option :display_methods, [:display_name, :full_name, :name, :title, :username, :login, :email]
68
74
 
69
75
  # Default adapter class used by all admin resources
70
76
  option :default_adapter, Adapters.compose(Adapters::ActiveRecordAdapter, Adapters::DraperAdapter)
@@ -78,12 +84,29 @@ module Trestle
78
84
  option :hooks, Hash.new { |h, k| h[k] = [] }
79
85
 
80
86
  # Register an extension hook
81
- def hook(name, &block)
82
- hooks[name.to_s] << block
87
+ def hook(name, options={}, &block)
88
+ hooks[name.to_s] << Hook.new(name.to_s, options, &block)
83
89
  end
84
90
 
85
91
  # List of i18n keys to pass into the Trestle.i18n JavaScript object
86
- option :javascript_i18n_keys, ["admin.confirmation.title", "admin.confirmation.delete", "admin.confirmation.cancel"]
92
+ option :javascript_i18n_keys, [
93
+ "trestle.confirmation.title", "trestle.confirmation.delete", "trestle.confirmation.cancel", "trestle.dialog.error",
94
+ "admin.buttons.ok", "admin.datepicker.formats.date", "admin.datepicker.formats.datetime", "admin.datepicker.formats.time"
95
+ ]
96
+
97
+ # List of load paths for where to find admin definitions
98
+ option :load_paths, [
99
+ -> { ActiveSupport::Dependencies.autoload_paths.grep(/\/app\/admin\Z/) }
100
+ ]
101
+
102
+ # When to reload Trestle admin within a to_prepare block (`:always` or `:on_update`)
103
+ option :reload, :on_update
104
+
105
+
106
+ ## Debugging
107
+
108
+ # Enable debugging of form errors
109
+ option :debug_form_errors, Rails.env.development?
87
110
 
88
111
 
89
112
  ## Callbacks
@@ -14,7 +14,7 @@ module Trestle
14
14
 
15
15
  private
16
16
  def display_method
17
- @display_method ||= Trestle.config.display_methods.find { |m| @instance.respond_to?(m) }
17
+ @display_method ||= Trestle.config.display_methods.find { |m| @instance.respond_to?(m) } || :to_s
18
18
  end
19
19
  end
20
20
  end
@@ -7,9 +7,11 @@ module Trestle
7
7
  config.assets.precompile << "trestle/admin.css" << "trestle/admin.js"
8
8
 
9
9
  # Vendor assets
10
- config.assets.precompile << %r(trestle/bootstrap-sass/assets/fonts/bootstrap/glyphicons-halflings-regular\.(?:eot|svg|ttf|woff|woff2)$)
11
- config.assets.precompile << %r(trestle/font-awesome/fonts/fontawesome-webfont\.(?:eot|svg|ttf|woff|woff2)$)
12
- config.assets.precompile << %r(trestle/ionicons/fonts/ionicons\.(?:eot|svg|ttf|woff)$)
10
+ %w(eot svg ttf woff woff2).each do |ext|
11
+ config.assets.precompile << "trestle/bootstrap-sass/assets/fonts/bootstrap/glyphicons-halflings-regular.#{ext}"
12
+ config.assets.precompile << "trestle/font-awesome/fonts/fontawesome-webfont.#{ext}"
13
+ config.assets.precompile << "trestle/ionicons/fonts/ionicons.#{ext}"
14
+ end
13
15
 
14
16
  initializer "trestle.automount" do |app|
15
17
  if Trestle.config.automount
@@ -35,7 +37,11 @@ module Trestle
35
37
  end
36
38
 
37
39
  config.to_prepare do
38
- Engine.reloader.execute_if_updated
40
+ if Trestle.config.reload == :always
41
+ Engine.reloader.execute
42
+ else
43
+ Engine.reloader.execute_if_updated
44
+ end
39
45
  end
40
46
 
41
47
  def reloader
@@ -2,7 +2,7 @@ module Trestle
2
2
  class Form
3
3
  class Automatic < Form
4
4
  def initialize(admin)
5
- @block = Proc.new do |instance|
5
+ super() do |instance|
6
6
  admin.default_form_attributes.each do |attribute|
7
7
  if attribute.array?
8
8
  if [:string, :text].include?(attribute.type)
@@ -13,8 +13,11 @@ module Trestle
13
13
  when :association
14
14
  if attribute.polymorphic?
15
15
  static_field attribute.name do
16
- associated_instance = instance.public_send(attribute.association_name)
17
- admin_link_to format_value(associated_instance), associated_instance
16
+ if associated_instance = instance.public_send(attribute.association_name)
17
+ admin_link_to format_value(associated_instance), associated_instance
18
+ else
19
+ content_tag(:span, I18n.t("admin.format.blank"), class: "blank")
20
+ end
18
21
  end
19
22
  else
20
23
  prompt = I18n.t("admin.form.select.prompt", default: "- Select %{attribute_name} -", attribute_name: admin.human_attribute_name(attribute.association_name))
@@ -10,7 +10,11 @@ module Trestle
10
10
  self.fields = {}
11
11
 
12
12
  def errors(name)
13
- object.errors[name].to_a
13
+ if object.respond_to?(:errors) && object.errors.respond_to?(:[])
14
+ object.errors[name].to_a
15
+ else
16
+ []
17
+ end
14
18
  end
15
19
 
16
20
  def self.register(name, klass)
@@ -39,7 +39,7 @@ module Trestle
39
39
  end
40
40
 
41
41
  def extract_options!
42
- @wrapper = extract_wrapper_options(:help, :label, :hide_label).merge(options.delete(:wrapper))
42
+ @wrapper = extract_wrapper_options(*Fields::FormGroup::WRAPPER_OPTIONS).merge(options.delete(:wrapper))
43
43
  end
44
44
 
45
45
  private
@@ -1,7 +1,7 @@
1
1
  module Trestle::Form::Fields::DatePicker
2
2
  def extract_options!
3
- options[:prepend] ||= options.key?(:icon) ? options.delete(:icon) : default_icon
4
- options.merge!(data: { picker: options.key?(:picker) ? options.delete(:picker) : true })
3
+ options[:prepend] ||= options.delete(:icon) { default_icon }
4
+ options.reverse_merge!(data: { picker: options.delete(:picker) { true }, allow_clear: true })
5
5
 
6
6
  super
7
7
  end