jquery-rails 2.1.4 → 4.6.0

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.
@@ -1,98 +1,85 @@
1
- (function($, undefined) {
1
+ /* jshint node: true */
2
2
 
3
3
  /**
4
4
  * Unobtrusive scripting adapter for jQuery
5
- *
6
- * Requires jQuery 1.6.0 or later.
7
5
  * https://github.com/rails/jquery-ujs
8
-
9
- * Uploading file using rails.js
10
- * =============================
11
- *
12
- * By default, browsers do not allow files to be uploaded via AJAX. As a result, if there are any non-blank file fields
13
- * in the remote form, this adapter aborts the AJAX submission and allows the form to submit through standard means.
14
- *
15
- * The `ajax:aborted:file` event allows you to bind your own handler to process the form submission however you wish.
16
- *
17
- * Ex:
18
- * $('form').live('ajax:aborted:file', function(event, elements){
19
- * // Implement own remote file-transfer handler here for non-blank file inputs passed in `elements`.
20
- * // Returning false in this handler tells rails.js to disallow standard form submission
21
- * return false;
22
- * });
23
- *
24
- * The `ajax:aborted:file` event is fired when a file-type input is detected with a non-blank value.
25
- *
26
- * Third-party tools can use this hook to detect when an AJAX file upload is attempted, and then use
27
- * techniques like the iframe method to upload the file instead.
28
6
  *
29
- * Required fields in rails.js
30
- * ===========================
7
+ * Requires jQuery 1.8.0 or later.
31
8
  *
32
- * If any blank required inputs (required="required") are detected in the remote form, the whole form submission
33
- * is canceled. Note that this is unlike file inputs, which still allow standard (non-AJAX) form submission.
9
+ * Released under the MIT license
34
10
  *
35
- * The `ajax:aborted:required` event allows you to bind your own handler to inform the user of blank required inputs.
36
- *
37
- * !! Note that Opera does not fire the form's submit event if there are blank required inputs, so this event may never
38
- * get fired in Opera. This event is what causes other browsers to exhibit the same submit-aborting behavior.
39
- *
40
- * Ex:
41
- * $('form').live('ajax:aborted:required', function(event, elements){
42
- * // Returning false in this handler tells rails.js to submit the form anyway.
43
- * // The blank required inputs are passed to this function in `elements`.
44
- * return ! confirm("Would you like to submit the form with missing info?");
45
- * });
46
11
  */
47
12
 
48
- // Cut down on the number if issues from people inadvertently including jquery_ujs twice
49
- // by detecting and raising an error when it happens.
50
- var alreadyInitialized = function() {
51
- var events = $._data(document, 'events');
52
- return events && events.click && $.grep(events.click, function(e) { return e.namespace === 'rails'; }).length;
53
- }
13
+ (function() {
14
+ 'use strict';
15
+
16
+ var jqueryUjsInit = function($, undefined) {
54
17
 
55
- if ( alreadyInitialized() ) {
18
+ // Cut down on the number of issues from people inadvertently including jquery_ujs twice
19
+ // by detecting and raising an error when it happens.
20
+ if ( $.rails !== undefined ) {
56
21
  $.error('jquery-ujs has already been loaded!');
57
22
  }
58
23
 
59
24
  // Shorthand to make it a little easier to call public rails functions from within rails.js
60
25
  var rails;
26
+ var $document = $(document);
61
27
 
62
28
  $.rails = rails = {
63
29
  // Link elements bound by jquery-ujs
64
- linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote], a[data-disable-with]',
30
+ linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote]:not([disabled]), a[data-disable-with], a[data-disable]',
31
+
32
+ // Button elements bound by jquery-ujs
33
+ buttonClickSelector: 'button[data-remote]:not([form]):not(form button), button[data-confirm]:not([form]):not(form button)',
65
34
 
66
35
  // Select elements bound by jquery-ujs
67
36
  inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]',
68
37
 
69
38
  // Form elements bound by jquery-ujs
70
- formSubmitSelector: 'form',
39
+ formSubmitSelector: 'form:not([data-turbo=true])',
71
40
 
72
41
  // Form input elements bound by jquery-ujs
73
- formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not([type])',
42
+ formInputClickSelector: 'form:not([data-turbo=true]) input[type=submit], form:not([data-turbo=true]) input[type=image], form:not([data-turbo=true]) button[type=submit], form:not([data-turbo=true]) button:not([type]), input[type=submit][form], input[type=image][form], button[type=submit][form], button[form]:not([type])',
74
43
 
75
44
  // Form input elements disabled during form submission
76
- disableSelector: 'input[data-disable-with], button[data-disable-with], textarea[data-disable-with]',
45
+ disableSelector: 'input[data-disable-with]:enabled, button[data-disable-with]:enabled, textarea[data-disable-with]:enabled, input[data-disable]:enabled, button[data-disable]:enabled, textarea[data-disable]:enabled',
77
46
 
78
47
  // Form input elements re-enabled after form submission
79
- enableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled',
48
+ enableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled, input[data-disable]:disabled, button[data-disable]:disabled, textarea[data-disable]:disabled',
80
49
 
81
50
  // Form required input elements
82
- requiredInputSelector: 'input[name][required]:not([disabled]),textarea[name][required]:not([disabled])',
51
+ requiredInputSelector: 'input[name][required]:not([disabled]), textarea[name][required]:not([disabled])',
83
52
 
84
53
  // Form file input elements
85
- fileInputSelector: 'input:file',
54
+ fileInputSelector: 'input[name][type=file]:not([disabled])',
86
55
 
87
56
  // Link onClick disable selector with possible reenable after remote submission
88
- linkDisableSelector: 'a[data-disable-with]',
57
+ linkDisableSelector: 'a[data-disable-with], a[data-disable]',
58
+
59
+ // Button onClick disable selector with possible reenable after remote submission
60
+ buttonDisableSelector: 'button[data-remote][data-disable-with], button[data-remote][data-disable]',
61
+
62
+ // Up-to-date Cross-Site Request Forgery token
63
+ csrfToken: function() {
64
+ return $('meta[name=csrf-token]').attr('content');
65
+ },
66
+
67
+ // URL param that must contain the CSRF token
68
+ csrfParam: function() {
69
+ return $('meta[name=csrf-param]').attr('content');
70
+ },
89
71
 
90
72
  // Make sure that every Ajax request sends the CSRF token
91
73
  CSRFProtection: function(xhr) {
92
- var token = $('meta[name="csrf-token"]').attr('content');
74
+ var token = rails.csrfToken();
93
75
  if (token) xhr.setRequestHeader('X-CSRF-Token', token);
94
76
  },
95
77
 
78
+ // Make sure that all forms have actual up-to-date tokens (cached forms contain old ones)
79
+ refreshCSRFTokens: function(){
80
+ $('form input[name="' + rails.csrfParam() + '"]').val(rails.csrfToken());
81
+ },
82
+
96
83
  // Triggers an event on an element and returns false if the event result is false
97
84
  fire: function(obj, name, data) {
98
85
  var event = $.Event(name);
@@ -112,34 +99,44 @@
112
99
 
113
100
  // Default way to get an element's href. May be overridden at $.rails.href.
114
101
  href: function(element) {
115
- return element.attr('href');
102
+ return element[0].href;
103
+ },
104
+
105
+ // Checks "data-remote" if true to handle the request through a XHR request.
106
+ isRemote: function(element) {
107
+ return element.data('remote') !== undefined && element.data('remote') !== false;
116
108
  },
117
109
 
118
110
  // Submits "remote" forms and links with ajax
119
111
  handleRemote: function(element) {
120
- var method, url, data, elCrossDomain, crossDomain, withCredentials, dataType, options;
112
+ var method, url, data, withCredentials, dataType, options;
121
113
 
122
114
  if (rails.fire(element, 'ajax:before')) {
123
- elCrossDomain = element.data('cross-domain');
124
- crossDomain = elCrossDomain === undefined ? null : elCrossDomain;
125
115
  withCredentials = element.data('with-credentials') || null;
126
116
  dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType);
127
117
 
128
118
  if (element.is('form')) {
129
- method = element.attr('method');
130
- url = element.attr('action');
131
- data = element.serializeArray();
119
+ method = element.data('ujs:submit-button-formmethod') || element.attr('method');
120
+ url = element.data('ujs:submit-button-formaction') || element.attr('action');
121
+ data = $(element[0]).serializeArray();
132
122
  // memoized value from clicked submit button
133
123
  var button = element.data('ujs:submit-button');
134
124
  if (button) {
135
125
  data.push(button);
136
126
  element.data('ujs:submit-button', null);
137
127
  }
128
+ element.data('ujs:submit-button-formmethod', null);
129
+ element.data('ujs:submit-button-formaction', null);
138
130
  } else if (element.is(rails.inputChangeSelector)) {
139
131
  method = element.data('method');
140
132
  url = element.data('url');
141
133
  data = element.serialize();
142
- if (element.data('params')) data = data + "&" + element.data('params');
134
+ if (element.data('params')) data = data + '&' + element.data('params');
135
+ } else if (element.is(rails.buttonClickSelector)) {
136
+ method = element.data('method') || 'get';
137
+ url = element.data('url');
138
+ data = element.serialize();
139
+ if (element.data('params')) data = data + '&' + element.data('params');
143
140
  } else {
144
141
  method = element.data('method');
145
142
  url = rails.href(element);
@@ -153,7 +150,11 @@
153
150
  if (settings.dataType === undefined) {
154
151
  xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script);
155
152
  }
156
- return rails.fire(element, 'ajax:beforeSend', [xhr, settings]);
153
+ if (rails.fire(element, 'ajax:beforeSend', [xhr, settings])) {
154
+ element.trigger('ajax:send', xhr);
155
+ } else {
156
+ return false;
157
+ }
157
158
  },
158
159
  success: function(data, status, xhr) {
159
160
  element.trigger('ajax:success', [data, status, xhr]);
@@ -164,69 +165,125 @@
164
165
  error: function(xhr, status, error) {
165
166
  element.trigger('ajax:error', [xhr, status, error]);
166
167
  },
167
- xhrFields: {
168
- withCredentials: withCredentials
169
- },
170
- crossDomain: crossDomain
168
+ crossDomain: rails.isCrossDomain(url)
171
169
  };
170
+
171
+ // There is no withCredentials for IE6-8 when
172
+ // "Enable native XMLHTTP support" is disabled
173
+ if (withCredentials) {
174
+ options.xhrFields = {
175
+ withCredentials: withCredentials
176
+ };
177
+ }
178
+
172
179
  // Only pass url to `ajax` options if not blank
173
180
  if (url) { options.url = url; }
174
181
 
175
- var jqxhr = rails.ajax(options);
176
- element.trigger('ajax:send', jqxhr);
177
- return jqxhr;
182
+ return rails.ajax(options);
178
183
  } else {
179
184
  return false;
180
185
  }
181
186
  },
182
187
 
188
+ // Determines if the request is a cross domain request.
189
+ isCrossDomain: function(url) {
190
+ var originAnchor = document.createElement('a');
191
+ originAnchor.href = location.href;
192
+ var urlAnchor = document.createElement('a');
193
+
194
+ try {
195
+ urlAnchor.href = url;
196
+ // This is a workaround to a IE bug.
197
+ urlAnchor.href = urlAnchor.href;
198
+
199
+ // If URL protocol is false or is a string containing a single colon
200
+ // *and* host are false, assume it is not a cross-domain request
201
+ // (should only be the case for IE7 and IE compatibility mode).
202
+ // Otherwise, evaluate protocol and host of the URL against the origin
203
+ // protocol and host.
204
+ return !(((!urlAnchor.protocol || urlAnchor.protocol === ':') && !urlAnchor.host) ||
205
+ (originAnchor.protocol + '//' + originAnchor.host ===
206
+ urlAnchor.protocol + '//' + urlAnchor.host));
207
+ } catch (e) {
208
+ // If there is an error parsing the URL, assume it is crossDomain.
209
+ return true;
210
+ }
211
+ },
212
+
183
213
  // Handles "data-method" on links such as:
184
214
  // <a href="/users/5" data-method="delete" rel="nofollow" data-confirm="Are you sure?">Delete</a>
185
215
  handleMethod: function(link) {
186
216
  var href = rails.href(link),
187
217
  method = link.data('method'),
188
218
  target = link.attr('target'),
189
- csrf_token = $('meta[name=csrf-token]').attr('content'),
190
- csrf_param = $('meta[name=csrf-param]').attr('content'),
219
+ csrfToken = rails.csrfToken(),
220
+ csrfParam = rails.csrfParam(),
191
221
  form = $('<form method="post" action="' + href + '"></form>'),
192
- metadata_input = '<input name="_method" value="' + method + '" type="hidden" />';
222
+ metadataInput = '<input name="_method" value="' + method + '" type="hidden" />';
193
223
 
194
- if (csrf_param !== undefined && csrf_token !== undefined) {
195
- metadata_input += '<input name="' + csrf_param + '" value="' + csrf_token + '" type="hidden" />';
224
+ if (csrfParam !== undefined && csrfToken !== undefined && !rails.isCrossDomain(href)) {
225
+ metadataInput += '<input name="' + csrfParam + '" value="' + csrfToken + '" type="hidden" />';
196
226
  }
197
227
 
198
228
  if (target) { form.attr('target', target); }
199
229
 
200
- form.hide().append(metadata_input).appendTo('body');
230
+ form.hide().append(metadataInput).appendTo('body');
201
231
  form.submit();
202
232
  },
203
233
 
234
+ // Helper function that returns form elements that match the specified CSS selector
235
+ // If form is actually a "form" element this will return associated elements outside the from that have
236
+ // the html form attribute set
237
+ formElements: function(form, selector) {
238
+ return form.is('form') ? $(form[0].elements).filter(selector) : form.find(selector);
239
+ },
240
+
204
241
  /* Disables form elements:
205
242
  - Caches element value in 'ujs:enable-with' data store
206
243
  - Replaces element text with value of 'data-disable-with' attribute
207
244
  - Sets disabled property to true
208
245
  */
209
246
  disableFormElements: function(form) {
210
- form.find(rails.disableSelector).each(function() {
211
- var element = $(this), method = element.is('button') ? 'html' : 'val';
212
- element.data('ujs:enable-with', element[method]());
213
- element[method](element.data('disable-with'));
214
- element.prop('disabled', true);
247
+ rails.formElements(form, rails.disableSelector).each(function() {
248
+ rails.disableFormElement($(this));
215
249
  });
216
250
  },
217
251
 
252
+ disableFormElement: function(element) {
253
+ var method, replacement;
254
+
255
+ method = element.is('button') ? 'html' : 'val';
256
+ replacement = element.data('disable-with');
257
+
258
+ if (replacement !== undefined) {
259
+ element.data('ujs:enable-with', element[method]());
260
+ element[method](replacement);
261
+ }
262
+
263
+ element.prop('disabled', true);
264
+ element.data('ujs:disabled', true);
265
+ },
266
+
218
267
  /* Re-enables disabled form elements:
219
268
  - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`)
220
269
  - Sets disabled property to false
221
270
  */
222
271
  enableFormElements: function(form) {
223
- form.find(rails.enableSelector).each(function() {
224
- var element = $(this), method = element.is('button') ? 'html' : 'val';
225
- if (element.data('ujs:enable-with')) element[method](element.data('ujs:enable-with'));
226
- element.prop('disabled', false);
272
+ rails.formElements(form, rails.enableSelector).each(function() {
273
+ rails.enableFormElement($(this));
227
274
  });
228
275
  },
229
276
 
277
+ enableFormElement: function(element) {
278
+ var method = element.is('button') ? 'html' : 'val';
279
+ if (element.data('ujs:enable-with') !== undefined) {
280
+ element[method](element.data('ujs:enable-with'));
281
+ element.removeData('ujs:enable-with'); // clean up cache
282
+ }
283
+ element.prop('disabled', false);
284
+ element.removeData('ujs:disabled');
285
+ },
286
+
230
287
  /* For 'data-confirm' attribute:
231
288
  - Fires `confirm` event
232
289
  - Shows the confirmation dialog
@@ -243,7 +300,11 @@
243
300
  if (!message) { return true; }
244
301
 
245
302
  if (rails.fire(element, 'confirm')) {
246
- answer = rails.confirm(message);
303
+ try {
304
+ answer = rails.confirm(message);
305
+ } catch (e) {
306
+ (console.error || console.log).call(console, e.stack || e);
307
+ }
247
308
  callback = rails.fire(element, 'confirm:complete', [answer]);
248
309
  }
249
310
  return answer && callback;
@@ -251,25 +312,45 @@
251
312
 
252
313
  // Helper function which checks for blank inputs in a form that match the specified CSS selector
253
314
  blankInputs: function(form, specifiedSelector, nonBlank) {
254
- var inputs = $(), input, valueToCheck,
255
- selector = specifiedSelector || 'input,textarea',
256
- allInputs = form.find(selector);
257
-
258
- allInputs.each(function() {
315
+ var foundInputs = $(),
316
+ input,
317
+ valueToCheck,
318
+ radiosForNameWithNoneSelected,
319
+ radioName,
320
+ selector = specifiedSelector || 'input,textarea',
321
+ requiredInputs = form.find(selector),
322
+ checkedRadioButtonNames = {};
323
+
324
+ requiredInputs.each(function() {
259
325
  input = $(this);
260
- valueToCheck = input.is(':checkbox,:radio') ? input.is(':checked') : input.val();
261
- // If nonBlank and valueToCheck are both truthy, or nonBlank and valueToCheck are both falsey
262
- if (!valueToCheck === !nonBlank) {
326
+ if (input.is('input[type=radio]')) {
263
327
 
264
- // Don't count unchecked required radio if other radio with same name is checked
265
- if (input.is(':radio') && allInputs.filter('input:radio:checked[name="' + input.attr('name') + '"]').length) {
266
- return true; // Skip to next input
267
- }
328
+ // Don't count unchecked required radio as blank if other radio with same name is checked,
329
+ // regardless of whether same-name radio input has required attribute or not. The spec
330
+ // states https://www.w3.org/TR/html5/forms.html#the-required-attribute
331
+ radioName = input.attr('name');
332
+
333
+ // Skip if we've already seen the radio with this name.
334
+ if (!checkedRadioButtonNames[radioName]) {
335
+
336
+ // If none checked
337
+ if (form.find('input[type=radio]:checked[name="' + radioName + '"]').length === 0) {
338
+ radiosForNameWithNoneSelected = form.find(
339
+ 'input[type=radio][name="' + radioName + '"]');
340
+ foundInputs = foundInputs.add(radiosForNameWithNoneSelected);
341
+ }
268
342
 
269
- inputs = inputs.add(input);
343
+ // We only need to check each name once.
344
+ checkedRadioButtonNames[radioName] = radioName;
345
+ }
346
+ } else {
347
+ valueToCheck = input.is('input[type=checkbox],input[type=radio]') ? input.is(':checked') : !!input.val();
348
+ if (valueToCheck === nonBlank) {
349
+ foundInputs = foundInputs.add(input);
350
+ }
270
351
  }
271
352
  });
272
- return inputs.length ? inputs : false;
353
+ return foundInputs.length ? foundInputs : false;
273
354
  },
274
355
 
275
356
  // Helper function which checks for non-blank inputs in a form that match the specified CSS selector
@@ -284,146 +365,201 @@
284
365
  return false;
285
366
  },
286
367
 
287
- // find all the submit events directly bound to the form and
288
- // manually invoke them. If anyone returns false then stop the loop
289
- callFormSubmitBindings: function(form, event) {
290
- var events = form.data('events'), continuePropagation = true;
291
- if (events !== undefined && events['submit'] !== undefined) {
292
- $.each(events['submit'], function(i, obj){
293
- if (typeof obj.handler === 'function') return continuePropagation = obj.handler(event);
294
- });
295
- }
296
- return continuePropagation;
297
- },
298
-
299
- // replace element's html with the 'data-disable-with' after storing original html
368
+ // Replace element's html with the 'data-disable-with' after storing original html
300
369
  // and prevent clicking on it
301
370
  disableElement: function(element) {
302
- element.data('ujs:enable-with', element.html()); // store enabled state
303
- element.html(element.data('disable-with')); // set to disabled state
304
- element.bind('click.railsDisable', function(e) { // prevent further clicking
371
+ var replacement = element.data('disable-with');
372
+
373
+ if (replacement !== undefined) {
374
+ element.data('ujs:enable-with', element.html()); // store enabled state
375
+ element.html(replacement);
376
+ }
377
+
378
+ element.on('click.railsDisable', function(e) { // prevent further clicking
305
379
  return rails.stopEverything(e);
306
380
  });
381
+ element.data('ujs:disabled', true);
307
382
  },
308
383
 
309
- // restore element to its original state which was disabled by 'disableElement' above
384
+ // Restore element to its original state which was disabled by 'disableElement' above
310
385
  enableElement: function(element) {
311
386
  if (element.data('ujs:enable-with') !== undefined) {
312
387
  element.html(element.data('ujs:enable-with')); // set to old enabled state
313
- // this should be element.removeData('ujs:enable-with')
314
- // but, there is currently a bug in jquery which makes hyphenated data attributes not get removed
315
- element.data('ujs:enable-with', false); // clean up cache
388
+ element.removeData('ujs:enable-with'); // clean up cache
316
389
  }
317
- element.unbind('click.railsDisable'); // enable element
390
+ element.off('click.railsDisable'); // enable element
391
+ element.removeData('ujs:disabled');
318
392
  }
319
-
320
393
  };
321
394
 
322
- if (rails.fire($(document), 'rails:attachBindings')) {
395
+ if (rails.fire($document, 'rails:attachBindings')) {
323
396
 
324
397
  $.ajaxPrefilter(function(options, originalOptions, xhr){ if ( !options.crossDomain ) { rails.CSRFProtection(xhr); }});
325
398
 
326
- $(document).delegate(rails.linkDisableSelector, 'ajax:complete', function() {
399
+ // This event works the same as the load event, except that it fires every
400
+ // time the page is loaded.
401
+ //
402
+ // See https://github.com/rails/jquery-ujs/issues/357
403
+ // See https://developer.mozilla.org/en-US/docs/Using_Firefox_1.5_caching
404
+ $(window).on('pageshow.rails', function () {
405
+ $($.rails.enableSelector).each(function () {
406
+ var element = $(this);
407
+
408
+ if (element.data('ujs:disabled')) {
409
+ $.rails.enableFormElement(element);
410
+ }
411
+ });
412
+
413
+ $($.rails.linkDisableSelector).each(function () {
414
+ var element = $(this);
415
+
416
+ if (element.data('ujs:disabled')) {
417
+ $.rails.enableElement(element);
418
+ }
419
+ });
420
+ });
421
+
422
+ $document.on('ajax:complete', rails.linkDisableSelector, function() {
327
423
  rails.enableElement($(this));
328
424
  });
329
425
 
330
- $(document).delegate(rails.linkClickSelector, 'click.rails', function(e) {
331
- var link = $(this), method = link.data('method'), data = link.data('params');
426
+ $document.on('ajax:complete', rails.buttonDisableSelector, function() {
427
+ rails.enableFormElement($(this));
428
+ });
429
+
430
+ $document.on('click.rails', rails.linkClickSelector, function(e) {
431
+ var link = $(this), method = link.data('method'), data = link.data('params'), metaClick = e.metaKey || e.ctrlKey;
332
432
  if (!rails.allowAction(link)) return rails.stopEverything(e);
333
433
 
334
- if (link.is(rails.linkDisableSelector)) rails.disableElement(link);
434
+ if (!metaClick && link.is(rails.linkDisableSelector)) rails.disableElement(link);
335
435
 
336
- if (link.data('remote') !== undefined) {
337
- if ( (e.metaKey || e.ctrlKey) && (!method || method === 'GET') && !data ) { return true; }
436
+ if (rails.isRemote(link)) {
437
+ if (metaClick && (!method || method === 'GET') && !data) { return true; }
338
438
 
339
439
  var handleRemote = rails.handleRemote(link);
340
- // response from rails.handleRemote() will either be false or a deferred object promise.
440
+ // Response from rails.handleRemote() will either be false or a deferred object promise.
341
441
  if (handleRemote === false) {
342
442
  rails.enableElement(link);
343
443
  } else {
344
- handleRemote.error( function() { rails.enableElement(link); } );
444
+ handleRemote.fail( function() { rails.enableElement(link); } );
345
445
  }
346
446
  return false;
347
447
 
348
- } else if (link.data('method')) {
448
+ } else if (method) {
349
449
  rails.handleMethod(link);
350
450
  return false;
351
451
  }
352
452
  });
353
453
 
354
- $(document).delegate(rails.inputChangeSelector, 'change.rails', function(e) {
454
+ $document.on('click.rails', rails.buttonClickSelector, function(e) {
455
+ var button = $(this);
456
+
457
+ if (!rails.allowAction(button) || !rails.isRemote(button)) return rails.stopEverything(e);
458
+
459
+ if (button.is(rails.buttonDisableSelector)) rails.disableFormElement(button);
460
+
461
+ var handleRemote = rails.handleRemote(button);
462
+ // Response from rails.handleRemote() will either be false or a deferred object promise.
463
+ if (handleRemote === false) {
464
+ rails.enableFormElement(button);
465
+ } else {
466
+ handleRemote.fail( function() { rails.enableFormElement(button); } );
467
+ }
468
+ return false;
469
+ });
470
+
471
+ $document.on('change.rails', rails.inputChangeSelector, function(e) {
355
472
  var link = $(this);
356
- if (!rails.allowAction(link)) return rails.stopEverything(e);
473
+ if (!rails.allowAction(link) || !rails.isRemote(link)) return rails.stopEverything(e);
357
474
 
358
475
  rails.handleRemote(link);
359
476
  return false;
360
477
  });
361
478
 
362
- $(document).delegate(rails.formSubmitSelector, 'submit.rails', function(e) {
479
+ $document.on('submit.rails', rails.formSubmitSelector, function(e) {
363
480
  var form = $(this),
364
- remote = form.data('remote') !== undefined,
365
- blankRequiredInputs = rails.blankInputs(form, rails.requiredInputSelector),
366
- nonBlankFileInputs = rails.nonBlankInputs(form, rails.fileInputSelector);
481
+ remote = rails.isRemote(form),
482
+ blankRequiredInputs,
483
+ nonBlankFileInputs;
367
484
 
368
485
  if (!rails.allowAction(form)) return rails.stopEverything(e);
369
486
 
370
- // skip other logic when required values are missing or file upload is present
371
- if (blankRequiredInputs && form.attr("novalidate") == undefined && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs])) {
372
- return rails.stopEverything(e);
487
+ // Skip other logic when required values are missing or file upload is present
488
+ if (form.attr('novalidate') === undefined) {
489
+ if (form.data('ujs:formnovalidate-button') === undefined) {
490
+ blankRequiredInputs = rails.blankInputs(form, rails.requiredInputSelector, false);
491
+ if (blankRequiredInputs && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs])) {
492
+ return rails.stopEverything(e);
493
+ }
494
+ } else {
495
+ // Clear the formnovalidate in case the next button click is not on a formnovalidate button
496
+ // Not strictly necessary to do here, since it is also reset on each button click, but just to be certain
497
+ form.data('ujs:formnovalidate-button', undefined);
498
+ }
373
499
  }
374
500
 
375
501
  if (remote) {
502
+ nonBlankFileInputs = rails.nonBlankInputs(form, rails.fileInputSelector);
376
503
  if (nonBlankFileInputs) {
377
- // slight timeout so that the submit button gets properly serialized
504
+ // Slight timeout so that the submit button gets properly serialized
378
505
  // (make it easy for event handler to serialize form without disabled values)
379
506
  setTimeout(function(){ rails.disableFormElements(form); }, 13);
380
507
  var aborted = rails.fire(form, 'ajax:aborted:file', [nonBlankFileInputs]);
381
508
 
382
- // re-enable form elements if event bindings return false (canceling normal form submission)
509
+ // Re-enable form elements if event bindings return false (canceling normal form submission)
383
510
  if (!aborted) { setTimeout(function(){ rails.enableFormElements(form); }, 13); }
384
511
 
385
512
  return aborted;
386
513
  }
387
514
 
388
- // If browser does not support submit bubbling, then this live-binding will be called before direct
389
- // bindings. Therefore, we should directly call any direct bindings before remotely submitting form.
390
- if (!$.support.submitBubbles && $().jquery < '1.7' && rails.callFormSubmitBindings(form, e) === false) return rails.stopEverything(e);
391
-
392
515
  rails.handleRemote(form);
393
516
  return false;
394
517
 
395
518
  } else {
396
- // slight timeout so that the submit button gets properly serialized
519
+ // Slight timeout so that the submit button gets properly serialized
397
520
  setTimeout(function(){ rails.disableFormElements(form); }, 13);
398
521
  }
399
522
  });
400
523
 
401
- $(document).delegate(rails.formInputClickSelector, 'click.rails', function(event) {
524
+ $document.on('click.rails', rails.formInputClickSelector, function(event) {
402
525
  var button = $(this);
403
526
 
404
527
  if (!rails.allowAction(button)) return rails.stopEverything(event);
405
528
 
406
- // register the pressed submit button
529
+ // Register the pressed submit button
407
530
  var name = button.attr('name'),
408
531
  data = name ? {name:name, value:button.val()} : null;
409
532
 
410
- button.closest('form').data('ujs:submit-button', data);
533
+ var form = button.closest('form');
534
+ if (form.length === 0) {
535
+ form = $('#' + button.attr('form'));
536
+ }
537
+ form.data('ujs:submit-button', data);
538
+
539
+ // Save attributes from button
540
+ form.data('ujs:formnovalidate-button', button.attr('formnovalidate'));
541
+ form.data('ujs:submit-button-formaction', button.attr('formaction'));
542
+ form.data('ujs:submit-button-formmethod', button.attr('formmethod'));
411
543
  });
412
544
 
413
- $(document).delegate(rails.formSubmitSelector, 'ajax:beforeSend.rails', function(event) {
414
- if (this == event.target) rails.disableFormElements($(this));
545
+ $document.on('ajax:send.rails', rails.formSubmitSelector, function(event) {
546
+ if (this === event.target) rails.disableFormElements($(this));
415
547
  });
416
548
 
417
- $(document).delegate(rails.formSubmitSelector, 'ajax:complete.rails', function(event) {
418
- if (this == event.target) rails.enableFormElements($(this));
549
+ $document.on('ajax:complete.rails', rails.formSubmitSelector, function(event) {
550
+ if (this === event.target) rails.enableFormElements($(this));
419
551
  });
420
552
 
421
553
  $(function(){
422
- // making sure that all forms have actual up-to-date token(cached forms contain old one)
423
- csrf_token = $('meta[name=csrf-token]').attr('content');
424
- csrf_param = $('meta[name=csrf-param]').attr('content');
425
- $('form input[name="' + csrf_param + '"]').val(csrf_token);
554
+ rails.refreshCSRFTokens();
426
555
  });
427
556
  }
428
557
 
429
- })( jQuery );
558
+ };
559
+
560
+ if (window.jQuery) {
561
+ jqueryUjsInit(jQuery);
562
+ } else if (typeof exports === 'object' && typeof module === 'object') {
563
+ module.exports = jqueryUjsInit;
564
+ }
565
+ })();