para 0.7.1 → 0.7.2

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 (94) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/para/admin/tabs.coffee +20 -1
  3. data/app/assets/javascripts/para/admin.coffee +1 -0
  4. data/app/assets/javascripts/para/inputs/multi-select-input.coffee +5 -3
  5. data/app/assets/javascripts/para/inputs/nested_many.coffee +35 -1
  6. data/app/assets/javascripts/para/lib/remote-file-forms.coffee +6 -7
  7. data/app/assets/javascripts/para/lib/turbolinks-loading.coffee +1 -1
  8. data/app/assets/stylesheets/para/admin/main.sass +1 -0
  9. data/app/assets/stylesheets/para/admin/src/_affix.sass +4 -0
  10. data/app/assets/stylesheets/para/admin/src/_base.sass +4 -1
  11. data/app/assets/stylesheets/para/admin/src/_breadcrumb.sass +12 -2
  12. data/app/assets/stylesheets/para/admin/src/_form.sass +22 -3
  13. data/app/assets/stylesheets/para/admin/src/_navtabs.sass +23 -6
  14. data/app/assets/stylesheets/para/admin/src/_nested-many.sass +5 -0
  15. data/app/assets/stylesheets/para/admin/src/_nested_one.sass +6 -5
  16. data/app/controllers/para/admin/base_controller.rb +5 -0
  17. data/app/controllers/para/admin/crud_resources_controller.rb +1 -1
  18. data/app/controllers/para/admin/exports_controller.rb +2 -1
  19. data/app/controllers/para/admin/nested_forms_controller.rb +13 -0
  20. data/app/controllers/para/admin/resources_controller.rb +14 -3
  21. data/app/controllers/para/admin/search_controller.rb +7 -5
  22. data/app/decorators/para/component/base_decorator.rb +7 -2
  23. data/app/decorators/para/component/crud_decorator.rb +4 -0
  24. data/app/decorators/para/component/form_decorator.rb +6 -0
  25. data/app/exporters/call_for_projects_votes_exporter.rb +79 -0
  26. data/app/helpers/para/admin/base_helper.rb +2 -3
  27. data/app/helpers/para/admin/history_helper.rb +16 -0
  28. data/app/helpers/para/form_helper.rb +9 -1
  29. data/app/helpers/para/model_helper.rb +0 -2
  30. data/app/models/para/cache/item.rb +4 -0
  31. data/app/models/para/component/base.rb +4 -0
  32. data/app/models/para/component/crud.rb +1 -0
  33. data/app/models/para/component/form.rb +2 -0
  34. data/app/models/para/library/file.rb +40 -12
  35. data/app/models/para/page/section.rb +13 -1
  36. data/app/views/admin/para/exporter/bases/_completed.html.haml +1 -1
  37. data/app/views/layouts/para/admin.html.haml +5 -5
  38. data/app/views/para/admin/form_resources/show.html.haml +6 -2
  39. data/app/views/para/admin/nested_forms/show.html.haml +9 -0
  40. data/app/views/para/admin/resources/_exports_menu.html.haml +2 -2
  41. data/app/views/para/admin/resources/_list.html.haml +26 -21
  42. data/app/views/para/admin/resources/_remote_nested_form.html.haml +1 -0
  43. data/app/views/para/admin/resources/edit.html.haml +7 -2
  44. data/app/views/para/admin/resources/history/_index.html.haml +3 -0
  45. data/app/views/para/admin/shared/_header.html.haml +3 -3
  46. data/app/views/para/admin/shared/_navigation.html.haml +1 -1
  47. data/app/views/para/form/_tabs.html.haml +12 -9
  48. data/app/views/para/inputs/_multi_select.html.haml +6 -2
  49. data/app/views/para/inputs/_nested_many.html.haml +4 -4
  50. data/app/views/para/inputs/_nested_one.html.haml +12 -1
  51. data/app/views/para/inputs/multi_select/_no_items.html.haml +8 -4
  52. data/app/views/para/inputs/nested_many/_add.html.haml +2 -2
  53. data/app/views/para/inputs/nested_many/_add_with_subclasses.html.haml +3 -3
  54. data/app/views/para/inputs/nested_many/_container.html.haml +10 -7
  55. data/lib/generators/para/exporter/templates/csv_exporter.rb +24 -0
  56. data/lib/generators/para/exporter/templates/xls_exporter.rb +24 -0
  57. data/lib/para/attribute_field/belongs_to.rb +4 -1
  58. data/lib/para/attribute_field/nested_field.rb +4 -2
  59. data/lib/para/attribute_field/nested_many.rb +1 -1
  60. data/lib/para/attribute_field/nested_one.rb +1 -1
  61. data/lib/para/cache/database_store.rb +14 -1
  62. data/lib/para/cloneable.rb +44 -2
  63. data/lib/para/component/history.rb +15 -0
  64. data/lib/para/component.rb +1 -0
  65. data/lib/para/components_configuration.rb +8 -3
  66. data/lib/para/engine.rb +8 -0
  67. data/lib/para/exporter/base.rb +49 -1
  68. data/lib/para/ext/request_iframe_xhr.rb +17 -0
  69. data/lib/para/ext.rb +1 -0
  70. data/lib/para/form_builder/attributes_mappings_tracker.rb +15 -3
  71. data/lib/para/form_builder/containers.rb +6 -1
  72. data/lib/para/form_builder/nested_form.rb +7 -4
  73. data/lib/para/form_builder/tabs.rb +49 -8
  74. data/lib/para/iframe_transport/middleware.rb +58 -0
  75. data/lib/para/iframe_transport.rb +7 -0
  76. data/lib/para/importer/base.rb +1 -0
  77. data/lib/para/inputs/multi_select_input.rb +27 -8
  78. data/lib/para/inputs/nested_base_input.rb +26 -0
  79. data/lib/para/inputs/nested_many_input.rb +43 -18
  80. data/lib/para/inputs/nested_one_input.rb +18 -4
  81. data/lib/para/inputs.rb +1 -0
  82. data/lib/para/log_config.rb +14 -0
  83. data/lib/para/markup/resources_table.rb +2 -2
  84. data/lib/para/model_field_parsers/relations.rb +12 -1
  85. data/lib/para/page/model.rb +2 -1
  86. data/lib/para/postgres_extensions_checker.rb +15 -11
  87. data/lib/para/routes.rb +3 -3
  88. data/lib/para/search/distinct.rb +1 -1
  89. data/lib/para/version.rb +1 -1
  90. data/lib/para.rb +2 -0
  91. data/lib/rails/routing_mapper.rb +19 -27
  92. data/vendor/assets/javascripts/jquery.iframe-transport.js +260 -0
  93. data/vendor/assets/javascripts/jquery.remote-modal-form.coffee +80 -2
  94. metadata +23 -9
@@ -17,6 +17,10 @@ module Para
17
17
  read_attribute(:expires_at).try(:past?) || false
18
18
  end
19
19
 
20
+ def mismatched?(_version = nil)
21
+ false
22
+ end
23
+
20
24
  # From ActiveSupport::Cache::Store::Entry
21
25
  # Seconds since the epoch when the entry will expire.
22
26
  def expires_at
@@ -39,6 +39,10 @@ module Para
39
39
  false
40
40
  end
41
41
 
42
+ def history?
43
+ false
44
+ end
45
+
42
46
  def self.model_name
43
47
  @model_name ||= ModelName.new(self)
44
48
  end
@@ -6,6 +6,7 @@ module Para
6
6
  include Para::Component::Exportable
7
7
  include Para::Component::Importable
8
8
  include Para::Component::Subclassable
9
+ include Para::Component::History
9
10
 
10
11
  configurable_on :model_type
11
12
  configurable_on :namespaced
@@ -3,6 +3,8 @@ module Para
3
3
  class Form < Para::Component::Resource
4
4
  register :form, self
5
5
 
6
+ include Para::Component::History
7
+
6
8
  configurable_on :model_type
7
9
 
8
10
  has_one :component_resource, class_name: 'Para::ComponentResource',
@@ -1,21 +1,49 @@
1
1
  module Para
2
2
  module Library
3
3
  class File < ActiveRecord::Base
4
- has_attached_file :attachment
4
+ if defined?(ActiveStorage)
5
+ has_one_attached :attachment
5
6
 
6
- validates :attachment, presence: true
7
- do_not_validate_attachment_file_type :attachment
7
+ # Return attachment.path or attachment.url depending on the storage
8
+ # backend, allowing 'openuri' and 'roo' libraries to load easily the
9
+ # file at the right path, on filesystem or othe storage systems, like S3.
10
+ #
11
+ def attachment_path
12
+ return unless attachment.attached?
8
13
 
9
- # Return attachment.path or attachment.url depending on the storage
10
- # backend, allowing 'openuri' and 'roo' libraries to load easily the
11
- # file at the right path, on filesystem or othe storage systems, like S3.
12
- #
13
- def attachment_path
14
- return unless attachment?
14
+ attachment.service_url
15
+ end
16
+
17
+ alias_method :attachment_url, :attachment_path
18
+
19
+ def attachment_ext
20
+ ::File.extname(attachment.filename.to_s) if attachment.attached?
21
+ end
22
+ else
23
+ has_attached_file :attachment
24
+
25
+ validates :attachment, presence: true
26
+ do_not_validate_attachment_file_type :attachment
27
+
28
+ # Return attachment.path or attachment.url depending on the storage
29
+ # backend, allowing 'openuri' and 'roo' libraries to load easily the
30
+ # file at the right path, on filesystem or othe storage systems, like S3.
31
+ #
32
+ def attachment_path
33
+ return unless attachment?
34
+
35
+ case attachment.options[:storage]
36
+ when :filesystem then attachment.path
37
+ else attachment.url
38
+ end
39
+ end
40
+
41
+ def attachment_url
42
+ attachment.url if attachment?
43
+ end
15
44
 
16
- case attachment.options[:storage]
17
- when :filesystem then attachment.path
18
- else attachment.url
45
+ def attachment_ext
46
+ ::File.extname(attachment_file_name) if attachment.attached?
19
47
  end
20
48
  end
21
49
  end
@@ -5,7 +5,16 @@ module Para
5
5
 
6
6
  acts_as_orderable
7
7
 
8
- belongs_to :page, polymorphic: true
8
+ page_relation_options = { polymorphic: true }
9
+
10
+ # Make Rails 5+ belongs_to relation optional for the parent page, to allow
11
+ # using sections in other contexts that directly included into pages
12
+ #
13
+ if ActiveRecord::Associations::Builder::BelongsTo.valid_options({}).include?(:optional)
14
+ page_relation_options[:optional] = true
15
+ end
16
+
17
+ belongs_to :page, page_relation_options
9
18
 
10
19
  def css_class
11
20
  @css_class ||= self.class.name.demodulize.underscore.gsub(/_/, '-')
@@ -33,6 +42,7 @@ module Para
33
42
  has_one :section_resource, -> { ordered },
34
43
  foreign_key: 'section_id',
35
44
  class_name: '::Para::Page::SectionResource',
45
+ inverse_of: :section,
36
46
  dependent: :destroy
37
47
 
38
48
  accepts_nested_attributes_for :section_resource, allow_destroy: true
@@ -46,7 +56,9 @@ module Para
46
56
  has_many :section_resources, -> { ordered },
47
57
  foreign_key: 'section_id',
48
58
  class_name: '::Para::Page::SectionResource',
59
+ inverse_of: :section,
49
60
  dependent: :destroy
61
+
50
62
  accepts_nested_attributes_for :section_resources, allow_destroy: true
51
63
 
52
64
  @section_resources_already_initialized = true
@@ -4,7 +4,7 @@
4
4
 
5
5
  = javascript_tag do
6
6
  :plain
7
- window.location.href = '#{ job.file.attachment.url }';
7
+ window.location.href = '#{ job.file.attachment_url }';
8
8
 
9
9
  = modal.footer do
10
10
  %button.btn.btn-default{ data: { dismiss: 'modal' } }
@@ -17,13 +17,13 @@
17
17
 
18
18
  = csrf_meta_tags
19
19
 
20
- %body
21
- = render partial: 'para/admin/shared/navigation'
22
- = render partial: 'para/admin/shared/header'
20
+ %body{ class: admin_body_class }
21
+ = render partial: template_path_lookup('admin/shared/_navigation', 'para/admin/shared/_navigation')
22
+ = render partial: template_path_lookup('admin/shared/_header', 'para/admin/shared/_header')
23
23
 
24
- .container-fluid.page-content
24
+ .container-fluid.page-content.admin-main-page-content
25
25
  = display_admin_flash
26
26
 
27
27
  = yield
28
28
 
29
- = javascript_include_tag 'para/admin', :'data-turbolinks-eval' => false
29
+ = javascript_include_tag 'para/admin', :'data-turbolinks-eval' => false, :'data-turbolinks-suppress-warning' => true
@@ -1,4 +1,8 @@
1
1
  = page_top_bar(title: @component.name, type: 'form/show')
2
2
 
3
- .page-content-wrap.form-fixed-actions
4
- = render find_partial_for(resource, :form)
3
+ .row
4
+ .page-content-wrap.form-fixed-actions{ class: @component.page_container_class }
5
+ = render find_partial_for(resource, :form)
6
+
7
+ -# Render history module if configured to be displayed
8
+ = history_for(@component, action: :edit, resource: resource)
@@ -0,0 +1,9 @@
1
+ - nested_form = nil
2
+
3
+ - para_form_for(@object, url: "") do |form|
4
+ - form.object_name = @object_name
5
+ - nested_form = capture do
6
+ = render(partial: find_partial_for(@model, :remote_nested_form), locals: { form: form, model: @model, object: @object, object_name: @object_name })
7
+ = form.attributes_mappings_field_for(form)
8
+
9
+ = nested_form
@@ -2,7 +2,7 @@
2
2
  - if component.exporters.length == 1
3
3
  - exporter = component.exporters.first
4
4
 
5
- = link_to component.path(namespace: :exports, exporter: exporter.name, q: params[:q]), class: 'btn btn-default btn-shadow', method: :post, remote: true, data: { :'job-tracker-button' => true, :'refresh-on-close' => false } do
5
+ = link_to component.path(namespace: :exports, exporter: exporter.name, q: params[:q].try(:permit!)), class: 'btn btn-default btn-shadow', method: :post, remote: true, data: { :'job-tracker-button' => true, :'refresh-on-close' => false } do
6
6
  %i.fa.fa-download.fa-fw
7
7
  = exporter.model_name.human
8
8
 
@@ -15,5 +15,5 @@
15
15
  %ul.dropdown-menu
16
16
  - component.exporters.each do |exporter|
17
17
  %li
18
- = link_to component.path(namespace: :exports, exporter: exporter.name, q: params[:q]), method: :post, remote: true, data: { :'job-tracker-button' => true, :'refresh-on-close' => false } do
18
+ = link_to component.path(namespace: :exports, exporter: exporter.name, q: params[:q].try(:permit!)), method: :post, remote: true, data: { :'job-tracker-button' => true, :'refresh-on-close' => false } do
19
19
  = exporter.model_name.human
@@ -1,27 +1,32 @@
1
- .page-content-wrap
2
- .page-list
3
- .page-list-heading
4
- = render partial: find_partial_for(relation, :actions), locals: { relation: relation, component: component, model: model, allow_adding_resource: allow_adding_resource }
1
+ .row.page-content-row
2
+ %div{ class: @component.page_container_class }
3
+ .page-content-wrap
4
+ .page-list
5
+ .page-list-heading
6
+ = render partial: find_partial_for(relation, :actions), locals: { relation: relation, component: component, model: model, allow_adding_resource: allow_adding_resource }
5
7
 
6
- .page-list-heading
7
- .flextable
8
- = render partial: find_partial_for(relation, :filters), locals: { attributes: attributes }
8
+ .page-list-heading
9
+ .flextable
10
+ = render partial: find_partial_for(relation, :filters), locals: { attributes: attributes }
9
11
 
10
- - if resources.length > 0
11
- .page-entries-info
12
- = page_entries(resources)
13
- .page-list-body
14
- = table_for(resources: resources, model: model, component: component, attributes: attributes)
12
+ - if resources.length > 0
13
+ .page-entries-info
14
+ = page_entries(resources)
15
+ .page-list-body
16
+ = table_for(resources: resources, model: model, component: component, attributes: attributes)
15
17
 
16
- - else
17
- = alert class: 'no-results', dismissable: false do
18
- - if filtered?(attributes)
19
- = t('para.list.no_results')
20
18
  - else
21
- = t('para.list.empty')
19
+ = alert class: 'no-results', dismissable: false do
20
+ - if filtered?(attributes)
21
+ = t('para.list.no_results')
22
+ - else
23
+ = t('para.list.empty')
22
24
 
23
- = add_button_for(component, relation, model) if allow_adding_resource
25
+ = add_button_for(component, relation, model) if allow_adding_resource
24
26
 
25
- - if resources.total_count > resources.length
26
- .page-list-footer
27
- = paginate resources, theme: Para.config.pagination_theme
27
+ - if resources.total_count > resources.length
28
+ .page-list-footer
29
+ = paginate resources, theme: Para.config.pagination_theme
30
+
31
+ -# Render history module if configured to be displayed
32
+ = history_for(@component, action: :index, resources: resources, relation: relation)
@@ -0,0 +1 @@
1
+ = render partial: find_partial_for(model, :fields), locals: { form: form }
@@ -1,4 +1,9 @@
1
1
  = page_top_bar(title: resource_title_for(resource), type: 'crud/edit')
2
2
 
3
- .page-content-wrap.form-fixed-actions
4
- = render find_partial_for(resource, :form)
3
+ .row.page-content-row
4
+ %div{ class: @component.page_container_class }
5
+ .page-content-wrap.form-fixed-actions
6
+ = render find_partial_for(resource, :form)
7
+
8
+ -# Render history module if configured to be displayed
9
+ = history_for(@component, action: :edit, resource: resource)
@@ -0,0 +1,3 @@
1
+ .col-md-4
2
+ .resource-history.page-content-wrap
3
+ Module history
@@ -6,13 +6,13 @@
6
6
  %span.icon-bar
7
7
  %span.icon-bar
8
8
 
9
+ = render_breadcrumbs
10
+
9
11
  %ul.nav.navbar-nav.pull-right
10
12
  %li
11
13
  = link_to root_path, target: "_blank", class: 'hint--bottom-left btn-home btn-shadow', aria: { label: t('para.admin.back_to_app') } do
12
14
  %i.fa.fa-home
13
-
15
+
14
16
  %li
15
17
  = link_to destroy_admin_user_session_path, method: :delete, class: 'hint--bottom-left btn-power-off btn-shadow', aria: { label: t('para.admin.sign_out') } do
16
18
  %i.fa.fa-sign-out
17
-
18
- = render_breadcrumbs
@@ -1,4 +1,4 @@
1
- .navmenu.navmenu-default.navmenu-fixed-left.offcanvas-sm{ class: ('dark-theme' if Para.config.dark_theme) }
1
+ .navmenu.navmenu-default.navmenu-fixed-left.offcanvas-sm{ class: ('dark-theme' if Para.config.dark_theme), data: { :'admin-left-sidebar' => true } }
2
2
  = link_to admin_path, class: 'navmenu-brand' do
3
3
  = t('para.admin.brand').html_safe
4
4
 
@@ -1,12 +1,15 @@
1
- .form-tabs{ data: { :'form-tabs' => true } }
2
- %ul.nav.nav-tabs{ role: 'tablist' }
3
- - tabs.each_with_index do |tab, index|
4
- %li{ class: ('active' if index == 0), role: 'presentation' }
5
- = link_to "##{ tab.dom_id }", role: 'tab', aria: { controls: 'settings' }, data: { toggle: 'tab' } do
6
- %i.fa{ class: "fa-#{ tab.icon }" }
7
- = tab.title
1
+ .form-tabs{ data: { :'form-tabs' => true, :'top-level-tabs' => !tabs_manager.builder.nested? } }
2
+ -# The .nav-tabs-wrapper is used to maintain the tabs height when the tabs
3
+ -# are fixed to top
4
+ .nav-tabs-wrapper{ data: { :'nav-tabs-wrapper' => true } }
5
+ %ul.nav.nav-tabs.affix-no-mobile{ role: 'tablist', data: { :'tabs-nav-affix' => tabs_manager.affix? } }
6
+ - tabs.each do |tab|
7
+ %li{ class: ('active' if tab.active?), role: 'presentation' }
8
+ = link_to "##{ tab.dom_id }", role: 'tab', aria: { controls: 'settings' }, data: { toggle: 'tab' } do
9
+ %i.fa{ class: "fa-#{ tab.icon }" }
10
+ = tab.title
8
11
 
9
12
  .tab-content
10
- - tabs.each_with_index do |tab, index|
11
- .tab-pane{ id: tab.dom_id, class: ('active' if index == 0), role: 'tabpanel' }
13
+ - tabs.each do |tab|
14
+ .tab-pane{ id: tab.dom_id, class: ('active' if tab.active?), role: 'tabpanel' }
12
15
  = tab.content
@@ -4,7 +4,11 @@
4
4
  .row
5
5
  .col-md-6
6
6
  .input-group.filter-form
7
- = text_field_tag :search, nil, class: 'string required form-control', autocomplete: 'off', data: { :'search-field' => true }
7
+ = text_field :q, search_param, class: 'string required form-control', autocomplete: 'off', data: { :'search-field' => true, :'search-field-input' => true }
8
+
9
+ -# Scope search results to the provided collection
10
+ = hidden_field_tag :'q[id_in]', collection.map(&:id).join(','), data: { :'search-field-input' => true } if collection
11
+
8
12
  %span.input-group-btn
9
13
  = button_tag type: 'button', class: 'btn' do
10
14
  = fa_icon 'search'
@@ -20,7 +24,7 @@
20
24
  = t('para.form.multi_select.available.add_all')
21
25
 
22
26
  .panel-body
23
- %table.table{ data: { :'available-items' => true, :'no-available-items' => render(partial: 'para/inputs/multi_select/no_items', locals: { available: true }) } }
27
+ %table.table{ :'data-available-items' => true, :'data-no-available-items' => render(partial: 'para/inputs/multi_select/no_items', locals: { available: true }) }
24
28
  %tbody
25
29
  = render partial: 'para/admin/search/result', collection: option_resources
26
30
 
@@ -1,10 +1,10 @@
1
1
  .nested-many-field{ class: [('orderable' if orderable), ('nested-many-field-inset' if inset)] }
2
2
  .fields-list{ id: dom_identifier }
3
- = form.simple_fields_for attribute_name, resources, nested_attribute_name: attribute_name, orderable: orderable do |nested_form|
4
- = render partial: find_partial_for(model, 'nested_many/container', partial_dir: 'inputs'), locals: { form: nested_form, model: nested_form.object.class, subclass: subclass, nested_locals: nested_locals, inset: inset }
3
+ = form.simple_fields_for attribute_name, resources, nested_attribute_name: attribute_name, orderable: orderable, track_attribute_mappings: render_partial do |nested_form|
4
+ = render partial: find_partial_for(model, 'nested_many/container', partial_dir: 'inputs'), locals: { form: nested_form, model: nested_form.object.class, subclass: subclass, nested_locals: nested_locals, inset: inset, render_partial: render_partial, remote_partial_params: remote_partial_params }
5
5
  -# Add button
6
6
  - if add_button
7
7
  - if subclass
8
- = render partial: 'para/inputs/nested_many/add_with_subclasses', locals: { form: form, model: model, dom_identifier: dom_identifier, nested_locals: nested_locals, attribute_name: attribute_name, orderable: orderable, subclasses: subclasses, inset: inset }
8
+ = render partial: 'para/inputs/nested_many/add_with_subclasses', locals: { form: form, model: model, dom_identifier: dom_identifier, nested_locals: nested_locals, attribute_name: attribute_name, orderable: orderable, subclasses: subclasses, inset: inset, add_button_label: add_button_label, add_button_class: add_button_class, render_partial: render_partial, remote_partial_params: remote_partial_params }
9
9
  - else
10
- = render partial: 'para/inputs/nested_many/add', locals: { form: form, model: model, dom_identifier: dom_identifier, nested_locals: nested_locals, attribute_name: attribute_name, orderable: orderable, inset: inset }
10
+ = render partial: 'para/inputs/nested_many/add', locals: { form: form, model: model, dom_identifier: dom_identifier, nested_locals: nested_locals, attribute_name: attribute_name, orderable: orderable, inset: inset, add_button_label: add_button_label, add_button_class: add_button_class, render_partial: render_partial, remote_partial_params: remote_partial_params }
@@ -1,3 +1,14 @@
1
1
  .nested-one-field
2
2
  = form.simple_fields_for attribute_name, resource, nested_attribute_name: attribute_name do |nested_form|
3
- = render partial: find_partial_for(model, :fields), locals: { form: nested_form, parent: form.object }
3
+ - if defined?(collapsible) && collapsible
4
+ .panel.panel-default.form-fields
5
+ .panel-heading
6
+ %h3.panel-title
7
+ = link_to "##{ nested_form.nested_resource_dom_id }", class: "nested-input-title collapsed#{ (' has-error' if nested_form.object.errors.any?) }", data: { toggle: 'collapse', :'open-on-insert' => true } do
8
+ = nested_form.nested_resource_name
9
+ %i.fa.fa-angle-up
10
+
11
+ .panel-body.panel-collapse.form-inputs.collapse{ id: nested_form.nested_resource_dom_id }
12
+ = render partial: find_partial_for(model, :fields), locals: { form: nested_form, parent: form.object }.merge(nested_locals)
13
+ - else
14
+ = render partial: find_partial_for(model, :fields), locals: { form: nested_form, parent: form.object }.merge(nested_locals)
@@ -1,6 +1,10 @@
1
- - if available
1
+ - if available
2
2
  %tr
3
- %td.no-items= t('para.form.multi_select.available.no_items')
4
- - else
3
+ %td
4
+ = t('para.form.multi_select.available.no_items')
5
+
6
+ - else
5
7
  %tr
6
- %td.no-items= t('para.form.multi_select.selected.no_items')
8
+ %td
9
+ = t('para.form.multi_select.selected.no_items')
10
+
@@ -1,2 +1,2 @@
1
- = link_to_add_association form, attribute_name, partial: find_partial_for(model, 'nested_many/container', partial_dir: 'inputs'), form_name: 'form', class: 'btn btn-primary btn-shadow add-button nested-many-inset-add-button', data: { :'association-insertion-node' => "##{ dom_identifier }", :'association-insertion-method' => 'append' }, render_options: { nested_attribute_name: attribute_name, orderable: orderable, locals: { model: model, nested_locals: nested_locals, inset: inset } } do
2
- = t('para.form.nested.add')
1
+ = link_to_add_association form, attribute_name, partial: find_partial_for(model, 'nested_many/container', partial_dir: 'inputs'), form_name: 'form', class: "btn btn-shadow add-button nested-many-inset-add-button #{add_button_class}", data: { :'association-insertion-node' => "##{ dom_identifier }", :'association-insertion-method' => 'append' }, render_options: { nested_attribute_name: attribute_name, orderable: orderable, locals: { model: model, nested_locals: nested_locals, inset: inset, render_partial: render_partial, remote_partial_params: remote_partial_params } } do
2
+ = add_button_label
@@ -1,9 +1,9 @@
1
1
  .add-button.nested-many-inset-add-button.dropdown
2
- %button.btn.btn-shadow.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown' } }
3
- = t('para.form.nested.add')
2
+ %button.btn.btn-shadow.dropdown-toggle{ class: add_button_class, type: 'button', data: { toggle: 'dropdown' } }
3
+ = add_button_label
4
4
  %i.fa.fa-angle-down
5
5
  %ul.dropdown-menu
6
6
  - subclasses.each do |submodel|
7
7
  %li
8
- = link_to_add_association form, attribute_name, wrap_object: proc { submodel.new }, partial: find_partial_for(submodel, 'nested_many/container', partial_dir: 'inputs'), form_name: 'form', class: 'dropdown-link', data: { :'association-insertion-node' => "##{ dom_identifier }", :'association-insertion-method' => 'append' }, render_options: { nested_attribute_name: attribute_name, orderable: orderable, locals: { model: submodel, nested_locals: nested_locals, inset: inset } } do
8
+ = link_to_add_association form, attribute_name, wrap_object: proc { submodel.new }, partial: find_partial_for(submodel, 'nested_many/container', partial_dir: 'inputs'), form_name: 'form', class: 'dropdown-link', data: { :'association-insertion-node' => "##{ dom_identifier }", :'association-insertion-method' => 'append' }, render_options: { nested_attribute_name: attribute_name, orderable: orderable, locals: { model: submodel, nested_locals: nested_locals, inset: inset, render_partial: render_partial, remote_partial_params: remote_partial_params } } do
9
9
  = submodel.model_name.human
@@ -1,21 +1,24 @@
1
1
  - nested_locals ||= {}
2
2
 
3
- .panel.panel-default.form-fields{ class: ('nested-many-inset-panel' if inset), data: { :'persisted' => form.object.persisted? } }
3
+ .panel.panel-default.form-fields{ class: ('nested-many-inset-panel' if inset), data: { persisted: form.object.persisted? } }
4
4
  .panel-heading
5
5
  = form.reorder_anchor
6
6
 
7
7
  %h3.panel-title
8
- = link_to "##{ form.nested_resource_dom_id }", class: 'collapsed', data: { toggle: 'collapse', :'open-on-insert' => true } do
8
+ = link_to "##{ form.nested_resource_dom_id }", class: "nested-input-title collapsed#{ (' has-error' if form.object.errors.any?) }", data: { toggle: 'collapse', :'open-on-insert' => true } do
9
9
  = form.nested_resource_name
10
10
  %i.fa.fa-angle-up
11
11
 
12
12
  = form.remove_association_button
13
13
 
14
- .panel-collapse.form-inputs.collapse{ id: form.nested_resource_dom_id, class: ('in' if inset) }
15
- .panel-body
16
- = render partial: find_partial_for(model, :fields), locals: { form: form }.merge(nested_locals)
14
+ .panel-collapse.form-inputs.collapse{ id: form.nested_resource_dom_id, class: ('in' if inset && form.object.persisted?), data: { rendered: render_partial, render_path: @component.path(remote_partial_params), id: form.object.id, :"object-name" => form.object_name, :"model-name" => model.name } }
15
+ .panel-body{ data: { :"nested-form-container" => true } }
16
+ - if render_partial
17
+ = render partial: find_partial_for(model, :fields), locals: { form: form }.merge(nested_locals)
18
+ - else
19
+ = fa_icon "spinner spin"
17
20
 
18
- -# Add inheritance column field if needed
19
- = form.hidden_field form.object.class.inheritance_column if form.object.class.column_names.include?(form.object.class.inheritance_column)
21
+ -# Add inheritance column field if needed
22
+ = form.hidden_field form.object.class.inheritance_column if form.object.class.column_names.include?(form.object.class.inheritance_column)
20
23
 
21
24
  .clearfix
@@ -27,6 +27,30 @@ class <%= exporter_class_name %> < Para::Exporter::Csv
27
27
  # def row_for(resource)
28
28
  # end
29
29
 
30
+ # Whitelist params to be fetched from the controller and passed down to the
31
+ # exporter.
32
+ #
33
+ # For example, if you want to export posts for a given category, you
34
+ # can add the `:category_id` param to your export link, and whitelist
35
+ # this param here with :
36
+ #
37
+ # def self.params_whitelist
38
+ # [:category_id]
39
+ # end
40
+ #
41
+ # It will be passed from the controller to the importer so it can be used
42
+ # to scope resources before exporting.
43
+ #
44
+ # Note that you'll manually need to scope the resources by overriding the
45
+ # #resources method.
46
+ #
47
+ # If you need automatic scoping, please use the `:q` param that accepts
48
+ # ransack search params and applies it to the resources.
49
+ #
50
+ # def self.params_whitelist
51
+ # []
52
+ # end
53
+
30
54
  # If you need complete control over you CSV generation, use the following
31
55
  # method instead of the #fields or #row_for methods, and return a valid CSV
32
56
  # string
@@ -27,6 +27,30 @@ class <%= exporter_class_name %> < Para::Exporter::Xls
27
27
  # def row_for(resource)
28
28
  # end
29
29
 
30
+ # Whitelist params to be fetched from the controller and passed down to the
31
+ # exporter.
32
+ #
33
+ # For example, if you want to export posts for a given category, you
34
+ # can add the `:category_id` param to your export link, and whitelist
35
+ # this param here with :
36
+ #
37
+ # def self.params_whitelist
38
+ # [:category_id]
39
+ # end
40
+ #
41
+ # It will be passed from the controller to the importer so it can be used
42
+ # to scope resources before exporting.
43
+ #
44
+ # Note that you'll manually need to scope the resources by overriding the
45
+ # #resources method.
46
+ #
47
+ # If you need automatic scoping, please use the `:q` param that accepts
48
+ # ransack search params and applies it to the resources.
49
+ #
50
+ # def self.params_whitelist
51
+ # []
52
+ # end
53
+
30
54
  # If you need complete control over you XLS generation, use the following
31
55
  # method instead of the #fields or #row_for methods, and return a valid XLS
32
56
  # StringIO object.
@@ -20,7 +20,10 @@ module Para
20
20
  end
21
21
 
22
22
  def parse_input(params, resource)
23
- if (id = params[reflection.foreign_key].presence) && !reflection.klass.exists?(id: id)
23
+ if reflection &&
24
+ (id = params[reflection.foreign_key].presence) &&
25
+ !reflection.klass.exists?(id: id)
26
+ then
24
27
  on_the_fly_creation(id) do |resource|
25
28
  params[reflection.foreign_key] = resource.id
26
29
  end
@@ -1,8 +1,10 @@
1
1
  module Para
2
2
  module AttributeField
3
3
  module NestedField
4
- def nested_model_mappings(nested_attributes)
5
- model = if (type = nested_attributes[:type]).present?
4
+ def nested_model_mappings(nested_attributes, resource)
5
+ model = if resource
6
+ resource.class
7
+ elsif (type = nested_attributes[:type]).present?
6
8
  nested_attributes[:type].try(:constantize)
7
9
  else
8
10
  reflection.klass
@@ -9,8 +9,8 @@ module Para
9
9
  def parse_input(params, resource)
10
10
  if (nested_attributes = params[nested_attributes_key])
11
11
  nested_attributes.each do |index, attributes|
12
- mappings = nested_model_mappings(attributes)
13
12
  nested_resource = fetch_or_build_nested_resource_for(resource, index, attributes)
13
+ mappings = nested_model_mappings(attributes, nested_resource)
14
14
 
15
15
  mappings.fields.each do |field|
16
16
  field.parse_input(attributes, nested_resource)
@@ -8,8 +8,8 @@ module Para
8
8
 
9
9
  def parse_input(params, resource)
10
10
  if (nested_attributes = params[nested_attributes_key])
11
- mappings = nested_model_mappings(nested_attributes)
12
11
  nested_resource = fetch_or_build_nested_resource_for(resource, nested_attributes)
12
+ mappings = nested_model_mappings(nested_attributes, nested_resource)
13
13
 
14
14
  mappings.fields.each do |field|
15
15
  field.parse_input(nested_attributes, nested_resource)
@@ -36,7 +36,20 @@ module Para
36
36
 
37
37
  item.value = entry.value
38
38
  item.expires_at = options[:expires_in].try(:since)
39
- item.save!
39
+
40
+ # Save chace item in its own thread, to get a clean
41
+ # ActiveRecord::Base.connection.
42
+ #
43
+ # It allows cache to be saved inside transactions but without waiting
44
+ # transactions to be committed to commit chache changes.
45
+ #
46
+ # This is needed for checking progress of long running tasks, like
47
+ # imports, that use the cache inside transactions but need other
48
+ # processes to get their state in real time.
49
+ #
50
+ Thread.new do
51
+ item.save!
52
+ end.join
40
53
 
41
54
  # Ensure cached item in RequestStore is up to date
42
55
  RequestStore.store[cache_key_for(key)] = item