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
@@ -1,17 +1,21 @@
1
1
  module Para
2
2
  module Inputs
3
- class NestedOneInput < SimpleForm::Inputs::Base
3
+ class NestedOneInput < NestedBaseInput
4
4
  def input(wrapper_options = nil)
5
5
  input_html_options[:class] << "nested-one"
6
6
 
7
7
  parent_model = object.class
8
8
  association = object.association(attribute_name)
9
9
  relation = parent_model.reflect_on_association(attribute_name)
10
- model = relation.klass
11
10
 
12
- unless (resource = object.send(:"#{ attribute_name }"))
11
+ resource = object.send(attribute_name)
12
+ model = (resource && resource.class) || relation.klass
13
+
14
+ unless resource
13
15
  # Build association without trying to save the new record
14
16
  resource = case association
17
+ when ActiveRecord::Associations::HasOneThroughAssociation
18
+ association.replace(model.new)
15
19
  when ActiveRecord::Associations::HasOneAssociation
16
20
  association.replace(model.new, false)
17
21
  else
@@ -19,16 +23,26 @@ module Para
19
23
  end
20
24
  end
21
25
 
26
+ locals = options.fetch(:locals, {})
27
+
22
28
  template.render(
23
29
  partial: 'para/inputs/nested_one',
24
30
  locals: {
25
31
  form: @builder,
26
32
  model: model,
27
33
  resource: resource,
28
- attribute_name: attribute_name
34
+ attribute_name: attribute_name,
35
+ nested_locals: locals,
36
+ collapsible: collapsible
29
37
  }
30
38
  )
31
39
  end
40
+
41
+ private
42
+
43
+ def collapsible
44
+ options.fetch(:collapsible, false)
45
+ end
32
46
  end
33
47
  end
34
48
  end
data/lib/para/inputs.rb CHANGED
@@ -2,6 +2,7 @@ module Para
2
2
  module Inputs
3
3
  extend ActiveSupport::Autoload
4
4
 
5
+ autoload :NestedBaseInput
5
6
  autoload :NestedOneInput
6
7
  autoload :NestedManyInput
7
8
  autoload :MultiSelectInput
@@ -0,0 +1,14 @@
1
+ module Para
2
+ class LogConfig
3
+ def self.with_log_level(level, &block)
4
+ begin
5
+ log_level = Rails.logger.level
6
+ Rails.logger.level = :fatal
7
+
8
+ block.call
9
+ ensure
10
+ Rails.logger.level = log_level
11
+ end
12
+ end
13
+ end
14
+ end
@@ -169,7 +169,7 @@ module Para
169
169
  end
170
170
 
171
171
  def delete_button(resource)
172
- path = component.relation_path(resource)
172
+ path = component.relation_path(resource, return_to: view.request.fullpath)
173
173
 
174
174
  options = {
175
175
  method: :delete,
@@ -178,7 +178,7 @@ module Para
178
178
  },
179
179
  class: 'btn btn-sm btn-icon-danger btn-shadow hint--left',
180
180
  aria: {
181
- label: ::I18n.t('para.shared.destroy')
181
+ label: ::I18n.t('para.shared.destroy')
182
182
  }
183
183
  }
184
184
 
@@ -11,13 +11,24 @@ module Para
11
11
  )
12
12
  end
13
13
 
14
- # Catch multi select inputs from form attributes mappings
15
14
  find_attributes_for_mapping(:nested_many).each do |attribute|
16
15
  fields_hash[attribute] = AttributeField::NestedManyField.new(
17
16
  model, name: attribute, type: 'has_many', field_type: 'nested_many'
18
17
  )
19
18
  end
20
19
 
20
+ find_attributes_for_mapping(:nested_one).each do |attribute|
21
+ fields_hash[attribute] = AttributeField::NestedOneField.new(
22
+ model, name: attribute, type: 'has_one', field_type: 'nested_one'
23
+ )
24
+ end
25
+
26
+ find_attributes_for_mapping(:selectize).each do |attribute|
27
+ fields_hash[attribute] = AttributeField::BelongsToField.new(
28
+ model, name: attribute, type: 'belongs_to', field_type: 'selectize'
29
+ )
30
+ end
31
+
21
32
  model.reflections.each do |name, reflection|
22
33
  # We ensure that name is a symbol and not a string for 4.2+
23
34
  # versions of AR
@@ -6,7 +6,8 @@ module Para
6
6
  included do
7
7
  has_many :sections, -> { ordered }, class_name: '::Para::Page::Section',
8
8
  as: :page,
9
- dependent: :destroy
9
+ dependent: :destroy,
10
+ inverse_of: :page
10
11
  accepts_nested_attributes_for :sections, allow_destroy: true
11
12
  end
12
13
  end
@@ -2,15 +2,17 @@ module Para
2
2
  class PostgresExtensionsChecker
3
3
  class << self
4
4
  def check_all
5
- %w(hstore unaccent).each do |extname|
6
- unless extension_exists?(extname)
7
- # Could not use Rails.logger here, using puts as a temporary
8
- # solution.
9
- puts "[Warning] PostgreSQL \"#{ extname }\" extension is not " +
10
- "installed in your database. This means that you " +
11
- "missing some migrations that you can install " +
12
- "with the following command : " +
13
- "`rake para_engine:install:migrations` and then migrate."
5
+ Para::LogConfig.with_log_level(:fatal) do
6
+ %w(hstore unaccent).each do |extname|
7
+ unless extension_exists?(extname)
8
+ # Could not use Rails.logger here, using puts as a temporary
9
+ # solution.
10
+ puts "[Warning] PostgreSQL \"#{ extname }\" extension is not " +
11
+ "installed in your database. This means that you " +
12
+ "missing some migrations that you can install " +
13
+ "with the following command : " +
14
+ "`rake para_engine:install:migrations` and then migrate."
15
+ end
14
16
  end
15
17
  end
16
18
  end
@@ -18,10 +20,12 @@ module Para
18
20
  private
19
21
 
20
22
  def extension_exists?(extname)
21
- ActiveRecord::Base.connection.execute(
23
+ result = ActiveRecord::Base.connection.execute(
22
24
  "SELECT COUNT(*) FROM pg_catalog.pg_extension " +
23
25
  "WHERE extname = '#{ extname }'"
24
- ).first['count'].to_i > 0
26
+ ).first
27
+
28
+ result && result['count'].to_i > 0
25
29
  rescue ActiveRecord::NoDatabaseError
26
30
  true # Do not issue warning when no database is installed
27
31
  end
data/lib/para/routes.rb CHANGED
@@ -18,9 +18,9 @@ module Para
18
18
  end
19
19
 
20
20
  # Components are namespaced into :admin in their respective methods
21
- crud_component
22
- form_component
23
- component :settings
21
+ crud_component scoped_in_para: true
22
+ form_component scoped_in_para: true
23
+ component :settings, scoped_in_para: true
24
24
  end
25
25
 
26
26
  block.call if block
@@ -17,7 +17,7 @@ module Para
17
17
 
18
18
  def result
19
19
  selects = build_selects
20
- search.result.select(selects).uniq
20
+ search.result.select(selects).distinct
21
21
  end
22
22
 
23
23
  private
data/lib/para/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Para
2
- VERSION = '0.7.1'
2
+ VERSION = '0.7.2'
3
3
  end
data/lib/para.rb CHANGED
@@ -33,6 +33,7 @@ require 'vertebra'
33
33
  require 'rails/routing_mapper'
34
34
  require 'rails/relation_length_validator'
35
35
 
36
+ require 'para/log_config'
36
37
  require 'para/postgres_extensions_checker'
37
38
 
38
39
  module Para
@@ -50,6 +51,7 @@ module Para
50
51
  autoload :Breadcrumbs
51
52
  autoload :Cache
52
53
  autoload :Logging
54
+ autoload :IframeTransport
53
55
  end
54
56
 
55
57
  def self.config(&block)
@@ -20,8 +20,6 @@ module ActionDispatch
20
20
  )
21
21
 
22
22
  controller = options.fetch(:controller, "#{ component_name }_component")
23
- imports_controller = options.fetch(:imports_controller, '/para/admin/imports')
24
- exports_controller = options.fetch(:exports_controller, '/para/admin/exports')
25
23
 
26
24
  constraints Para::Routing::ComponentNameConstraint.new(component) do
27
25
  namespace :admin do
@@ -33,13 +31,7 @@ module ActionDispatch
33
31
  instance_eval(&block) if block
34
32
  add_extensions_for(:component)
35
33
 
36
- scope ':importer' do
37
- resources :imports, controller: imports_controller
38
- end
39
-
40
- scope ':exporter' do
41
- resources :exports, controller: exports_controller
42
- end
34
+ common_component_routes(options)
43
35
  end
44
36
  end
45
37
  end
@@ -60,8 +52,6 @@ module ActionDispatch
60
52
  # namespacing issues in plugins and other module namespaced scenarios
61
53
  #
62
54
  controller = options.fetch(:controller, '/para/admin/crud_resources')
63
- imports_controller = options.fetch(:imports_controller, '/para/admin/imports')
64
- exports_controller = options.fetch(:exports_controller, '/para/admin/exports')
65
55
 
66
56
  constraints Para::Routing::ComponentNameConstraint.new(component) do
67
57
  constraints Para::Routing::ComponentControllerConstraint.new(controller) do
@@ -82,13 +72,7 @@ module ActionDispatch
82
72
  add_extensions_for(:crud_component)
83
73
  end
84
74
 
85
- scope ':importer' do
86
- resources :imports, controller: imports_controller
87
- end
88
-
89
- scope ':exporter' do
90
- resources :exports, controller: exports_controller
91
- end
75
+ common_component_routes(options)
92
76
  end
93
77
  end
94
78
  end
@@ -108,8 +92,6 @@ module ActionDispatch
108
92
  )
109
93
 
110
94
  controller = options.fetch(:controller, '/para/admin/form_resources')
111
- imports_controller = options.fetch(:imports_controller, '/para/admin/imports')
112
- exports_controller = options.fetch(:exports_controller, '/para/admin/exports')
113
95
 
114
96
  constraints Para::Routing::ComponentNameConstraint.new(component) do
115
97
  constraints Para::Routing::ComponentControllerConstraint.new(controller) do
@@ -119,13 +101,7 @@ module ActionDispatch
119
101
  add_extensions_for(:form_component)
120
102
  end
121
103
 
122
- scope ':importer' do
123
- resources :imports, controller: imports_controller
124
- end
125
-
126
- scope ':exporter' do
127
- resources :exports, controller: exports_controller
128
- end
104
+ common_component_routes(options)
129
105
  end
130
106
  end
131
107
  end
@@ -159,6 +135,22 @@ module ActionDispatch
159
135
  instance_eval(&extension)
160
136
  end
161
137
  end
138
+
139
+ def common_component_routes(options)
140
+ nested_forms_controller = options.fetch(:nested_forms_controller, '/para/admin/nested_forms')
141
+ imports_controller = options.fetch(:imports_controller, '/para/admin/imports')
142
+ exports_controller = options.fetch(:exports_controller, '/para/admin/exports')
143
+
144
+ resource 'nested_form', controller: nested_forms_controller, only: [:show]
145
+
146
+ scope ':importer' do
147
+ resources :imports, controller: imports_controller
148
+ end
149
+
150
+ scope ':exporter' do
151
+ resources :exports, controller: exports_controller
152
+ end
153
+ end
162
154
  end
163
155
  end
164
156
  end
@@ -0,0 +1,260 @@
1
+ // This [jQuery](https://jquery.com/) plugin implements an `<iframe>`
2
+ // [transport](https://api.jquery.com/jQuery.ajax/#extending-ajax) so that
3
+ // `$.ajax()` calls support the uploading of files using standard HTML file
4
+ // input fields. This is done by switching the exchange from `XMLHttpRequest`
5
+ // to a hidden `iframe` element containing a form that is submitted.
6
+
7
+ // The [source for the plugin](https://github.com/cmlenz/jquery-iframe-transport)
8
+ // is available on [Github](https://github.com/) and licensed under the [MIT
9
+ // license](https://github.com/cmlenz/jquery-iframe-transport/blob/master/LICENSE).
10
+
11
+ // ## Usage
12
+
13
+ // To use this plugin, you simply add an `iframe` option with the value `true`
14
+ // to the Ajax settings an `$.ajax()` call, and specify the file fields to
15
+ // include in the submssion using the `files` option, which can be a selector,
16
+ // jQuery object, or a list of DOM elements containing one or more
17
+ // `<input type="file">` elements:
18
+
19
+ // $("#myform").submit(function() {
20
+ // $.ajax(this.action, {
21
+ // files: $(":file", this),
22
+ // iframe: true
23
+ // }).complete(function(data) {
24
+ // });
25
+ // });
26
+
27
+ // The plugin will construct hidden `<iframe>` and `<form>` elements, add the
28
+ // file field(s) to that form, submit the form, and process the response.
29
+
30
+ // If you want to include other form fields in the form submission, include
31
+ // them in the `data` option, and set the `processData` option to `false`:
32
+
33
+ // $("#myform").submit(function() {
34
+ // $.ajax(this.action, {
35
+ // data: $(":text", this).serializeArray(),
36
+ // files: $(":file", this),
37
+ // iframe: true,
38
+ // processData: false
39
+ // }).complete(function(data) {
40
+ // });
41
+ // });
42
+
43
+ // ### Response Data Types
44
+
45
+ // As the transport does not have access to the HTTP headers of the server
46
+ // response, it is not as simple to make use of the automatic content type
47
+ // detection provided by jQuery as with regular XHR. If you can't set the
48
+ // expected response data type (for example because it may vary depending on
49
+ // the outcome of processing by the server), you will need to employ a
50
+ // workaround on the server side: Send back an HTML document containing just a
51
+ // `<textarea>` element with a `data-type` attribute that specifies the MIME
52
+ // type, and put the actual payload in the textarea:
53
+
54
+ // <textarea data-type="application/json">
55
+ // {"ok": true, "message": "Thanks so much"}
56
+ // </textarea>
57
+
58
+ // The iframe transport plugin will detect this and pass the value of the
59
+ // `data-type` attribute on to jQuery as if it was the "Content-Type" response
60
+ // header, thereby enabling the same kind of conversions that jQuery applies
61
+ // to regular responses. For the example above you should get a Javascript
62
+ // object as the `data` parameter of the `complete` callback, with the
63
+ // properties `ok: true` and `message: "Thanks so much"`.
64
+
65
+ // ### Handling Server Errors
66
+
67
+ // Another problem with using an `iframe` for file uploads is that it is
68
+ // impossible for the javascript code to determine the HTTP status code of the
69
+ // servers response. Effectively, all of the calls you make will look like they
70
+ // are getting successful responses, and thus invoke the `done()` or
71
+ // `complete()` callbacks. You can only communicate problems using the content
72
+ // of the response payload. For example, consider using a JSON response such as
73
+ // the following to indicate a problem with an uploaded file:
74
+
75
+ // <textarea data-type="application/json">
76
+ // {"ok": false, "message": "Please only upload reasonably sized files."}
77
+ // </textarea>
78
+
79
+ // ### Compatibility
80
+
81
+ // This plugin has primarily been tested on Safari 5 (or later), Firefox 4 (or
82
+ // later), and Internet Explorer (all the way back to version 6). While I
83
+ // haven't found any issues with it so far, I'm fairly sure it still doesn't
84
+ // work around all the quirks in all different browsers. But the code is still
85
+ // pretty simple overall, so you should be able to fix it and contribute a
86
+ // patch :)
87
+
88
+ // ## Annotated Source
89
+
90
+ (function($, undefined) {
91
+ // Register a prefilter that checks whether the `iframe` option is set, and
92
+ // switches to the "iframe" data type if it is `true`.
93
+ $.ajaxPrefilter(function(options, origOptions, jqXHR) {
94
+ if (options.iframe) {
95
+ options.originalURL = options.url;
96
+ return "iframe";
97
+ }
98
+ });
99
+
100
+ // Register a transport for the "iframe" data type. It will only activate
101
+ // when the "files" option has been set to a non-empty list of enabled file
102
+ // inputs.
103
+ $.ajaxTransport("iframe", function(options, origOptions, jqXHR) {
104
+ var form = null,
105
+ iframe = null,
106
+ name = "iframe-" + $.now(),
107
+ files = $(options.files).filter(":file:enabled"),
108
+ markers = null,
109
+ accepts = null;
110
+
111
+
112
+ // This function gets called after a successful submission or an abortion
113
+ // and should revert all changes made to the page to enable the
114
+ // submission via this transport.
115
+ function cleanUp() {
116
+ files.each(function(i, file) {
117
+ var $file = $(file);
118
+ $file.data("clone").replaceWith($file);
119
+ });
120
+ form.remove();
121
+ iframe.one("load", function() { iframe.remove(); });
122
+ iframe.attr("src", "about:blank");
123
+ }
124
+
125
+ // Remove "iframe" from the data types list so that further processing is
126
+ // based on the content type returned by the server, without attempting an
127
+ // (unsupported) conversion from "iframe" to the actual type.
128
+ options.dataTypes.shift();
129
+
130
+ // Use the data from the original AJAX options, as it doesn't seem to be
131
+ // copied over since jQuery 1.7.
132
+ // See https://github.com/cmlenz/jquery-iframe-transport/issues/6
133
+ options.data = origOptions.data;
134
+
135
+ if (files.length) {
136
+ form = $("<form enctype='multipart/form-data' method='post'></form>").
137
+ hide().attr({action: options.originalURL, target: name});
138
+
139
+ // If there is any additional data specified via the `data` option,
140
+ // we add it as hidden fields to the form. This (currently) requires
141
+ // the `processData` option to be set to false so that the data doesn't
142
+ // get serialized to a string.
143
+ if (typeof(options.data) === "string" && options.data.length > 0) {
144
+ $.error("data must not be serialized");
145
+ }
146
+ $.each(options.data || {}, function(name, value) {
147
+ if ($.isPlainObject(value)) {
148
+ name = value.name;
149
+ value = value.value;
150
+ }
151
+
152
+
153
+ $("<input type='hidden' />").attr({name: name, value: value}).
154
+ appendTo(form);
155
+ });
156
+
157
+ // Add a hidden `X-Requested-With` field with the value `IFrame` to the
158
+ // field, to help server-side code to determine that the upload happened
159
+ // through this transport.
160
+ $("<input type='hidden' value='IFrame' name='X-Requested-With' />").
161
+ appendTo(form);
162
+
163
+ // ----------------------------------------------------------------------
164
+ // Modified section
165
+ // ----------------------------------------------------------------------
166
+
167
+ // Add CSRF token to the form since jquery-ujs adds it to the XHR request
168
+ // headers and the iframe transport doesn't support them
169
+ $("<input type='hidden' />").attr({name: $.rails.csrfParam(), value: $.rails.csrfToken()}).
170
+ appendTo(form);
171
+
172
+ // ----------------------------------------------------------------------
173
+ // End of modified section
174
+ // ----------------------------------------------------------------------
175
+
176
+ // Borrowed straight from the JQuery source.
177
+ // Provides a way of specifying the accepted data type similar to the
178
+ // HTTP "Accept" header
179
+ if (options.dataTypes[0] && options.accepts[options.dataTypes[0]]) {
180
+ accepts = options.accepts[options.dataTypes[0]] +
181
+ (options.dataTypes[0] !== "*" ? ", */*; q=0.01" : "");
182
+ } else {
183
+ accepts = options.accepts["*"];
184
+ }
185
+ $("<input type='hidden' name='X-HTTP-Accept'>").
186
+ attr("value", accepts).appendTo(form);
187
+
188
+ // Move the file fields into the hidden form, but first remember their
189
+ // original locations in the document by replacing them with disabled
190
+ // clones. This should also avoid introducing unwanted changes to the
191
+ // page layout during submission.
192
+ markers = files.after(function(idx) {
193
+ var $this = $(this),
194
+ $clone = $this.clone().prop("disabled", true);
195
+ $this.data("clone", $clone);
196
+ return $clone;
197
+ }).next();
198
+ files.appendTo(form);
199
+
200
+ return {
201
+
202
+ // The `send` function is called by jQuery when the request should be
203
+ // sent.
204
+ send: function(headers, completeCallback) {
205
+ iframe = $("<iframe src='about:blank' name='" + name +
206
+ "' id='" + name + "' style='display:none'></iframe>");
207
+
208
+ // The first load event gets fired after the iframe has been injected
209
+ // into the DOM, and is used to prepare the actual submission.
210
+ iframe.one("load", function() {
211
+
212
+ // The second load event gets fired when the response to the form
213
+ // submission is received. The implementation detects whether the
214
+ // actual payload is embedded in a `<textarea>` element, and
215
+ // prepares the required conversions to be made in that case.
216
+ iframe.one("load", function() {
217
+ var doc = this.contentWindow ? this.contentWindow.document :
218
+ (this.contentDocument ? this.contentDocument : this.document),
219
+ root = doc.documentElement ? doc.documentElement : doc.body,
220
+ dataInput = $(root).find("[data-iframe-response-data]").remove()[0],
221
+ type = dataInput && dataInput.getAttribute("data-type") || null,
222
+ status = (dataInput && dataInput.getAttribute("data-status")) || 200,
223
+ statusText = dataInput && dataInput.getAttribute("data-statusText") || "OK",
224
+ content = {
225
+ html: root.innerHTML,
226
+ text: root ? (root.textContent || root.innerText) : null
227
+ };
228
+
229
+ jqXHR.responseText = content.html;
230
+
231
+ cleanUp();
232
+ completeCallback(status, statusText, content, type ?
233
+ ("Content-Type: " + type) :
234
+ null);
235
+ });
236
+
237
+ // Now that the load handler has been set up, submit the form.
238
+ form[0].submit();
239
+ });
240
+
241
+ // After everything has been set up correctly, the form and iframe
242
+ // get injected into the DOM so that the submission can be
243
+ // initiated.
244
+ $("body").append(form, iframe);
245
+ },
246
+
247
+ // The `abort` function is called by jQuery when the request should be
248
+ // aborted.
249
+ abort: function() {
250
+ if (iframe !== null) {
251
+ iframe.unbind("load").attr("src", "about:blank");
252
+ cleanUp();
253
+ }
254
+ }
255
+
256
+ };
257
+ }
258
+ });
259
+
260
+ })(jQuery);