trestle 0.8.6 → 0.8.7

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 (61) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +10 -9
  3. data/app/assets/javascripts/trestle/components/_dialog.js +29 -8
  4. data/app/assets/javascripts/trestle/components/_form.js +28 -7
  5. data/app/assets/javascripts/trestle/components/_sidebar.js +10 -8
  6. data/app/assets/javascripts/trestle/components/_tabs.js +2 -1
  7. data/app/assets/javascripts/trestle/components/_tooltips.js +16 -0
  8. data/app/assets/stylesheets/trestle/components/_modal.scss +4 -0
  9. data/app/assets/stylesheets/trestle/components/_navigation.scss +31 -11
  10. data/app/assets/stylesheets/trestle/components/_sidebar.scss +2 -2
  11. data/app/assets/stylesheets/trestle/components/_tags.scss +9 -0
  12. data/app/assets/stylesheets/trestle/components/_wells.scss +9 -1
  13. data/app/assets/stylesheets/trestle/core/_defaults.scss +4 -4
  14. data/app/assets/stylesheets/trestle/core/_layout.scss +8 -0
  15. data/app/assets/stylesheets/trestle/core/_typography.scss +39 -0
  16. data/app/controllers/concerns/trestle/controller/breadcrumbs.rb +21 -0
  17. data/app/controllers/concerns/trestle/controller/callbacks.rb +21 -0
  18. data/app/controllers/concerns/trestle/controller/dialog.rb +16 -0
  19. data/app/controllers/concerns/trestle/controller/helpers.rb +18 -0
  20. data/app/controllers/concerns/trestle/controller/layout.rb +16 -0
  21. data/app/controllers/concerns/trestle/controller/location.rb +15 -0
  22. data/app/controllers/trestle/application_controller.rb +6 -34
  23. data/app/helpers/trestle/debug_helper.rb +11 -0
  24. data/app/helpers/trestle/format_helper.rb +7 -3
  25. data/app/helpers/trestle/headings_helper.rb +27 -0
  26. data/app/helpers/trestle/panel_helper.rb +24 -0
  27. data/app/helpers/trestle/table_helper.rb +41 -2
  28. data/app/helpers/trestle/url_helper.rb +3 -1
  29. data/app/views/layouts/trestle/admin.html.erb +1 -1
  30. data/app/views/trestle/application/_flash.html.erb +1 -1
  31. data/app/views/trestle/shared/_sidebar.html.erb +2 -2
  32. data/app/views/trestle/table/_table.html.erb +2 -6
  33. data/lib/generators/trestle/resource/templates/admin.rb.erb +2 -2
  34. data/lib/trestle.rb +6 -4
  35. data/lib/trestle/adapters/active_record_adapter.rb +0 -4
  36. data/lib/trestle/adapters/adapter.rb +15 -10
  37. data/lib/trestle/adapters/sequel_adapter.rb +0 -4
  38. data/lib/trestle/admin.rb +18 -1
  39. data/lib/trestle/admin/builder.rb +22 -10
  40. data/lib/trestle/form/automatic.rb +5 -2
  41. data/lib/trestle/form/builder.rb +5 -1
  42. data/lib/trestle/form/field.rb +1 -1
  43. data/lib/trestle/form/fields/form_group.rb +3 -1
  44. data/lib/trestle/form/fields/select.rb +5 -1
  45. data/lib/trestle/form/fields/tag_select.rb +1 -1
  46. data/lib/trestle/form/renderer.rb +1 -1
  47. data/lib/trestle/navigation.rb +11 -5
  48. data/lib/trestle/navigation/item.rb +10 -0
  49. data/lib/trestle/resource.rb +27 -61
  50. data/lib/trestle/resource/builder.rb +15 -14
  51. data/lib/trestle/resource/collection.rb +48 -0
  52. data/lib/trestle/resource/controller.rb +36 -23
  53. data/lib/trestle/scope.rb +13 -3
  54. data/lib/trestle/table.rb +10 -4
  55. data/lib/trestle/table/column.rb +13 -2
  56. data/lib/trestle/table/row.rb +10 -0
  57. data/lib/trestle/version.rb +1 -1
  58. data/trestle.gemspec +1 -0
  59. data/vendor/assets/stylesheets/trestle/magnific-popup.scss +13 -1
  60. metadata +27 -4
  61. data/app/helpers/trestle/dialog_helper.rb +0 -7
@@ -34,10 +34,6 @@ module Trestle
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
data/lib/trestle/admin.rb CHANGED
@@ -8,7 +8,6 @@ module Trestle
8
8
  class << self
9
9
  attr_accessor :menu
10
10
 
11
- attr_accessor :table
12
11
  attr_accessor :form
13
12
 
14
13
  attr_accessor :additional_routes
@@ -20,6 +19,20 @@ module Trestle
20
19
  @options ||= {}
21
20
  end
22
21
 
22
+ def tables
23
+ @tables ||= {}
24
+ end
25
+
26
+ # Deprecated: Use `tables[:index]` instead
27
+ def table
28
+ tables[:index]
29
+ end
30
+
31
+ # Deprecated: Use `tables[:index]=` instead
32
+ def table=(table)
33
+ tables[:index] = table
34
+ end
35
+
23
36
  def breadcrumbs
24
37
  Breadcrumb::Trail.new(Array(Trestle.config.root_breadcrumbs) + [breadcrumb])
25
38
  end
@@ -75,6 +88,10 @@ module Trestle
75
88
  def railtie_routes_url_helpers(include_path_helpers=true)
76
89
  Trestle.railtie_routes_url_helpers(include_path_helpers)
77
90
  end
91
+
92
+ def build(&block)
93
+ Admin::Builder.build(self, &block)
94
+ end
78
95
  end
79
96
  end
80
97
  end
@@ -11,23 +11,29 @@ 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)
31
37
  end
32
38
 
33
39
  def menu(*args, &block)
@@ -38,8 +44,14 @@ module Trestle
38
44
  end
39
45
  end
40
46
 
41
- def table(options={}, &block)
42
- admin.table = Table::Builder.build(options.reverse_merge(admin: admin, sortable: true), &block)
47
+ def table(name_or_options={}, options={}, &block)
48
+ if name_or_options.is_a?(Hash)
49
+ name, options = :index, name_or_options.reverse_merge(admin: admin, sortable: true)
50
+ else
51
+ name = name_or_options
52
+ end
53
+
54
+ admin.tables[name] = Table::Builder.build(options, &block)
43
55
  end
44
56
 
45
57
  def form(options={}, &block)
@@ -47,7 +59,7 @@ module Trestle
47
59
  end
48
60
 
49
61
  def admin(&block)
50
- @admin.singleton_class.class_eval(&block) if block_given?
62
+ @admin.instance_eval(&block) if block_given?
51
63
  @admin
52
64
  end
53
65
 
@@ -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
@@ -2,10 +2,12 @@ module Trestle
2
2
  class Form
3
3
  module Fields
4
4
  class FormGroup < Field
5
+ WRAPPER_OPTIONS = [:help, :label, :hide_label]
6
+
5
7
  def render
6
8
  options[:class] << 'has-error' if errors.any?
7
9
 
8
- content_tag(:div, options) do
10
+ content_tag(:div, options.except(*WRAPPER_OPTIONS)) do
9
11
  concat label unless options[:label] == false
10
12
  concat block.call if block
11
13
  concat help_message if options[:help]
@@ -7,7 +7,7 @@ module Trestle
7
7
  def initialize(builder, template, name, choices=nil, options={}, html_options={}, &block)
8
8
  super(builder, template, name, options, &block)
9
9
 
10
- @choices = Choices.new(choices || builder.object.send(name))
10
+ @choices = Choices.new(choices || default_choices)
11
11
  @html_options = default_html_options.merge(html_options)
12
12
  end
13
13
 
@@ -19,6 +19,10 @@ module Trestle
19
19
  Trestle::Options.new(class: ["form-control"], data: { enable_select2: true })
20
20
  end
21
21
 
22
+ def default_choices
23
+ builder.object.send(name) if builder.object
24
+ end
25
+
22
26
  # Allows an array of model instances (or a scope) to be
23
27
  # passed to the select field as the list of choices.
24
28
  class Choices
@@ -7,7 +7,7 @@ module Trestle
7
7
  end
8
8
 
9
9
  def default_html_options
10
- super.merge(multiple: true, class: "tag-select", data: { tags: true, select_on_close: true })
10
+ super.merge(multiple: true, class: "tag-select", data: { tags: true })
11
11
  end
12
12
  end
13
13
  end
@@ -5,7 +5,7 @@ module Trestle
5
5
  include ::ActionView::Helpers::CaptureHelper
6
6
 
7
7
  # Whitelisted helpers will concatenate their result to the output buffer when called.
8
- WHITELISTED_HELPERS = [:row, :col, :render, :tab, :toolbar, :table, :divider]
8
+ WHITELISTED_HELPERS = [:row, :col, :render, :tab, :toolbar, :table, :divider, :h1, :h2, :h3, :h4, :h5, :h6, :panel, :well]
9
9
 
10
10
  # Raw block helpers will pass their block argument directly to the method
11
11
  # without wrapping it in a new output buffer.
@@ -7,12 +7,10 @@ module Trestle
7
7
  autoload :Group
8
8
  autoload :NullGroup, "trestle/navigation/group"
9
9
 
10
- def initialize(blocks)
11
- @blocks = blocks
12
- end
10
+ attr_reader :items
13
11
 
14
- def items
15
- @blocks.map(&:items).flatten
12
+ def initialize(items)
13
+ @items = items
16
14
  end
17
15
 
18
16
  def by_group
@@ -28,6 +26,14 @@ module Trestle
28
26
  sorted.first.first if sorted.any?
29
27
  end
30
28
 
29
+ def visible(context)
30
+ self.class.new(items.select { |item| item.visible?(context) })
31
+ end
32
+
33
+ def self.build(blocks)
34
+ new(blocks.map(&:items).flatten)
35
+ end
36
+
31
37
  private
32
38
  def stable_sort(items)
33
39
  items.sort_by.with_index { |item, i| [item, i] }
@@ -55,6 +55,16 @@ module Trestle
55
55
  Badge.new(options[:badge]) if badge?
56
56
  end
57
57
 
58
+ def visible?(context)
59
+ if options[:if]
60
+ context.instance_exec(&options[:if])
61
+ elsif options[:unless]
62
+ !context.instance_exec(&options[:unless])
63
+ else
64
+ true
65
+ end
66
+ end
67
+
58
68
  class Badge
59
69
  attr_reader :text
60
70
 
@@ -3,11 +3,17 @@ module Trestle
3
3
  extend ActiveSupport::Autoload
4
4
 
5
5
  autoload :Builder
6
+ autoload :Collection
6
7
  autoload :Controller
7
8
 
8
9
  RESOURCE_ACTIONS = [:index, :show, :new, :create, :edit, :update, :destroy]
9
10
  READONLY_ACTIONS = [:index, :show]
10
11
 
12
+ class_attribute :decorator
13
+
14
+ class_attribute :pagination_options
15
+ self.pagination_options = {}
16
+
11
17
  class << self
12
18
  def adapter
13
19
  @adapter ||= Trestle.config.default_adapter.new(self)
@@ -20,10 +26,11 @@ module Trestle
20
26
  # Defines a method that can be overridden with a custom block,
21
27
  # but is otherwise delegated to the adapter instance.
22
28
  def self.adapter_method(name)
23
- attr_writer name
29
+ block_method = :"#{name}_block"
30
+ attr_accessor block_method
24
31
 
25
32
  define_method(name) do |*args|
26
- if override = instance_variable_get("@#{name}")
33
+ if override = public_send(block_method)
27
34
  instance_exec(*args, &override)
28
35
  else
29
36
  adapter.public_send(name, *args)
@@ -31,33 +38,33 @@ module Trestle
31
38
  end
32
39
  end
33
40
 
41
+ # Collection-focused adapter methods
34
42
  adapter_method :collection
43
+ adapter_method :merge_scopes
44
+ adapter_method :sort
45
+ adapter_method :paginate
46
+ adapter_method :finalize_collection
47
+ adapter_method :decorate_collection
48
+ adapter_method :count
49
+
50
+ # Instance-focused adapter methods
35
51
  adapter_method :find_instance
36
52
  adapter_method :build_instance
37
53
  adapter_method :update_instance
38
54
  adapter_method :save_instance
39
55
  adapter_method :delete_instance
40
- adapter_method :to_param
41
56
  adapter_method :permitted_params
42
- adapter_method :decorate_collection
43
- adapter_method :unscope
44
- adapter_method :merge_scopes
45
- adapter_method :count
46
- adapter_method :sort
47
- adapter_method :paginate
57
+
58
+ # Common adapter methods
59
+ adapter_method :to_param
48
60
  adapter_method :human_attribute_name
61
+
62
+ # Automatic tables and forms adapter methods
49
63
  adapter_method :default_table_attributes
50
64
  adapter_method :default_form_attributes
51
65
 
52
- attr_accessor :decorator
53
-
54
66
  def prepare_collection(params)
55
- collection = initialize_collection(params)
56
- collection = apply_scopes(collection, params)
57
- collection = apply_sorting(collection, params)
58
- collection = paginate(collection, params)
59
- collection = decorate_collection(collection)
60
- collection
67
+ Collection.new(self).prepare(params)
61
68
  end
62
69
 
63
70
  def initialize_collection(params)
@@ -68,47 +75,10 @@ module Trestle
68
75
  @scopes ||= {}
69
76
  end
70
77
 
71
- def apply_scopes(collection, params)
72
- unscoped = unscope(collection)
73
-
74
- scopes_for(params).each do |scope|
75
- collection = merge_scopes(collection, scope.apply(unscoped))
76
- end
77
-
78
- collection
79
- end
80
-
81
- def scopes_for(params)
82
- result = []
83
-
84
- if params[:scope] && scopes.has_key?(params[:scope].to_sym)
85
- result << scopes[params[:scope].to_sym]
86
- end
87
-
88
- if result.empty? && default_scope = scopes.values.find(&:default?)
89
- result << default_scope
90
- end
91
-
92
- result
93
- end
94
-
95
78
  def column_sorts
96
79
  @column_sorts ||= {}
97
80
  end
98
81
 
99
- def apply_sorting(collection, params)
100
- return collection unless params[:sort]
101
-
102
- field = params[:sort].to_sym
103
- order = params[:order].to_s.downcase == "desc" ? :desc : :asc
104
-
105
- if column_sorts.has_key?(field)
106
- instance_exec(collection, order, &column_sorts[field])
107
- else
108
- sort(collection, field, order)
109
- end
110
- end
111
-
112
82
  def table
113
83
  super || Table::Automatic.new(self)
114
84
  end
@@ -148,15 +118,11 @@ module Trestle
148
118
  end
149
119
 
150
120
  def return_locations
151
- @return_locations ||= {
152
- create: Proc.new { |instance| path(:show, id: to_param(instance)) },
153
- update: Proc.new { |instance| path(:show, id: to_param(instance)) },
154
- destroy: Proc.new { path(:index) }
155
- }
121
+ @return_locations ||= {}
156
122
  end
157
123
 
158
- def return_location(action, instance=nil)
159
- instance_exec(instance, &return_locations[action])
124
+ def build(&block)
125
+ Resource::Builder.build(self, &block)
160
126
  end
161
127
 
162
128
  private
@@ -22,36 +22,36 @@ module Trestle
22
22
  end
23
23
 
24
24
  def collection(&block)
25
- admin.collection = block
25
+ admin.collection_block = block
26
26
  end
27
27
 
28
28
  def find_instance(&block)
29
- admin.find_instance = block
29
+ admin.find_instance_block = block
30
30
  end
31
31
  alias instance find_instance
32
32
 
33
33
  def build_instance(&block)
34
- admin.build_instance = block
34
+ admin.build_instance_block = block
35
35
  end
36
36
 
37
37
  def update_instance(&block)
38
- admin.update_instance = block
38
+ admin.update_instance_block = block
39
39
  end
40
40
 
41
41
  def save_instance(&block)
42
- admin.save_instance = block
42
+ admin.save_instance_block = block
43
43
  end
44
44
 
45
45
  def delete_instance(&block)
46
- admin.delete_instance = block
46
+ admin.delete_instance_block = block
47
47
  end
48
48
 
49
49
  def to_param(&block)
50
- admin.to_param = block
50
+ admin.to_param_block = block
51
51
  end
52
52
 
53
53
  def params(&block)
54
- admin.permitted_params = block
54
+ admin.permitted_params_block = block
55
55
  end
56
56
 
57
57
  def decorator(decorator)
@@ -59,27 +59,28 @@ module Trestle
59
59
  end
60
60
 
61
61
  def decorate_collection(&block)
62
- admin.decorate_collection = block
62
+ admin.decorate_collection_block = block
63
63
  end
64
64
 
65
65
  def merge_scopes(&block)
66
- admin.merge_scopes = block
66
+ admin.merge_scopes_block = block
67
67
  end
68
68
 
69
69
  def sort(&block)
70
- admin.sort = block
70
+ admin.sort_block = block
71
71
  end
72
72
 
73
73
  def sort_column(column, &block)
74
74
  admin.column_sorts[column.to_sym] = block
75
75
  end
76
76
 
77
- def paginate(&block)
78
- admin.paginate = block
77
+ def paginate(options={}, &block)
78
+ admin.pagination_options = admin.pagination_options.merge(options)
79
+ admin.paginate_block = block
79
80
  end
80
81
 
81
82
  def count(&block)
82
- admin.count = block
83
+ admin.count_block = block
83
84
  end
84
85
 
85
86
  def scope(name, scope=nil, options={}, &block)