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
@@ -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);