para 0.7.1 → 0.7.2

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