Merge pull request #32574 from DmytroVasin/rails-ujs-stoppable-events

Rails-ujs: Info about stoppable events
This commit is contained in:
Rafael França 2018-04-19 23:37:01 -04:00 committed by GitHub
commit 3812ef8b66
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 73 additions and 64 deletions

@ -75,9 +75,9 @@ asyncTest('setting data("with-credentials",true) with "ajax:before" uses new set
asyncTest('stopping the "ajax:beforeSend" event aborts the request', 1, function() { asyncTest('stopping the "ajax:beforeSend" event aborts the request', 1, function() {
submit(function(form) { submit(function(form) {
form.bindNative('ajax:beforeSend', function() { form.bindNative('ajax:beforeSend', function(e) {
ok(true, 'aborting request in ajax:beforeSend') ok(true, 'aborting request in ajax:beforeSend')
return false e.preventDefault()
}) })
form.unbind('ajax:send').bindNative('ajax:send', function() { form.unbind('ajax:send').bindNative('ajax:send', function() {
ok(false, 'ajax:send should not run') ok(false, 'ajax:send should not run')
@ -148,8 +148,8 @@ function skipIt() {
.bind('iframe:loading', function() { .bind('iframe:loading', function() {
ok(false, 'form should not get submitted') ok(false, 'form should not get submitted')
}) })
.bindNative('ajax:aborted:file', function() { .bindNative('ajax:aborted:file', function(e) {
return false e.preventDefault()
}) })
.triggerNative('submit') .triggerNative('submit')
@ -162,9 +162,9 @@ function skipIt() {
} }
asyncTest('"ajax:beforeSend" can be observed and stopped with event delegation', 1, function() { asyncTest('"ajax:beforeSend" can be observed and stopped with event delegation', 1, function() {
$(document).delegate('form[data-remote]', 'ajax:beforeSend', function() { $(document).delegate('form[data-remote]', 'ajax:beforeSend', function(e) {
ok(true, 'ajax:beforeSend observed with event delegation') ok(true, 'ajax:beforeSend observed with event delegation')
return false e.preventDefault()
}) })
submit(function(form) { submit(function(form) {

@ -210,7 +210,7 @@ asyncTest('allow empty form "action"', 1, function() {
buildForm({ action: '' }) buildForm({ action: '' })
$('#qunit-fixture').find('form') $('#qunit-fixture').find('form')
.bindNative('ajax:beforeSend', function(e, xhr, settings) { .bindNative('ajax:beforeSend', function(evt, xhr, settings) {
// Get current location (the same way jQuery does) // Get current location (the same way jQuery does)
try { try {
currentLocation = location.href currentLocation = location.href
@ -229,7 +229,7 @@ asyncTest('allow empty form "action"', 1, function() {
// Prevent the request from actually getting sent to the current page and // Prevent the request from actually getting sent to the current page and
// causing an error. // causing an error.
return false evt.preventDefault()
}) })
.triggerNative('submit') .triggerNative('submit')
@ -257,7 +257,7 @@ asyncTest('intelligently guesses crossDomain behavior when target URL has a diff
equal(settings.crossDomain, true, 'crossDomain should be set to true') equal(settings.crossDomain, true, 'crossDomain should be set to true')
// prevent request from actually getting sent off-domain // prevent request from actually getting sent off-domain
return false evt.preventDefault()
}) })
.triggerNative('submit') .triggerNative('submit')
@ -276,7 +276,7 @@ asyncTest('intelligently guesses crossDomain behavior when target URL consists o
equal(settings.crossDomain, false, 'crossDomain should be set to false') equal(settings.crossDomain, false, 'crossDomain should be set to false')
// prevent request from actually getting sent off-domain // prevent request from actually getting sent off-domain
return false evt.preventDefault()
}) })
.triggerNative('submit') .triggerNative('submit')

@ -173,9 +173,9 @@ asyncTest('binding to confirm event of a link and returning false', 1, function(
} }
$('a[data-confirm]') $('a[data-confirm]')
.bindNative('confirm', function() { .bindNative('confirm', function(e) {
App.assertCallbackInvoked('confirm') App.assertCallbackInvoked('confirm')
return false e.preventDefault()
}) })
.bindNative('confirm:complete', function() { .bindNative('confirm:complete', function() {
App.assertCallbackNotInvoked('confirm:complete') App.assertCallbackNotInvoked('confirm:complete')
@ -194,9 +194,9 @@ asyncTest('binding to confirm event of a button and returning false', 1, functio
} }
$('button[data-confirm]') $('button[data-confirm]')
.bindNative('confirm', function() { .bindNative('confirm', function(e) {
App.assertCallbackInvoked('confirm') App.assertCallbackInvoked('confirm')
return false e.preventDefault()
}) })
.bindNative('confirm:complete', function() { .bindNative('confirm:complete', function() {
App.assertCallbackNotInvoked('confirm:complete') App.assertCallbackNotInvoked('confirm:complete')
@ -216,9 +216,9 @@ asyncTest('binding to confirm:complete event of a link and returning false', 2,
} }
$('a[data-confirm]') $('a[data-confirm]')
.bindNative('confirm:complete', function() { .bindNative('confirm:complete', function(e) {
App.assertCallbackInvoked('confirm:complete') App.assertCallbackInvoked('confirm:complete')
return false e.preventDefault()
}) })
.bindNative('ajax:beforeSend', function() { .bindNative('ajax:beforeSend', function() {
App.assertCallbackNotInvoked('ajax:beforeSend') App.assertCallbackNotInvoked('ajax:beforeSend')
@ -238,9 +238,9 @@ asyncTest('binding to confirm:complete event of a button and returning false', 2
} }
$('button[data-confirm]') $('button[data-confirm]')
.bindNative('confirm:complete', function() { .bindNative('confirm:complete', function(e) {
App.assertCallbackInvoked('confirm:complete') App.assertCallbackInvoked('confirm:complete')
return false e.preventDefault()
}) })
.bindNative('ajax:beforeSend', function() { .bindNative('ajax:beforeSend', function() {
App.assertCallbackNotInvoked('ajax:beforeSend') App.assertCallbackNotInvoked('ajax:beforeSend')

@ -132,7 +132,8 @@ test('form input[type=submit][data-disable-with] re-enables when `pageshow` even
}) })
asyncTest('form[data-remote] input[type=submit][data-disable-with] is replaced in ajax callback', 2, function() { asyncTest('form[data-remote] input[type=submit][data-disable-with] is replaced in ajax callback', 2, function() {
var form = $('form:not([data-remote])').attr('data-remote', 'true'), origFormContents = form.html() var form = $('#qunit-fixture form:not([data-remote])').attr('data-remote', 'true'),
origFormContents = form.html()
form.bindNative('ajax:success', function() { form.bindNative('ajax:success', function() {
form.html(origFormContents) form.html(origFormContents)
@ -146,7 +147,8 @@ asyncTest('form[data-remote] input[type=submit][data-disable-with] is replaced i
}) })
asyncTest('form[data-remote] input[data-disable-with] is replaced with disabled field in ajax callback', 2, function() { asyncTest('form[data-remote] input[data-disable-with] is replaced with disabled field in ajax callback', 2, function() {
var form = $('form:not([data-remote])').attr('data-remote', 'true'), input = form.find('input[type=submit]'), var form = $('#qunit-fixture form:not([data-remote])').attr('data-remote', 'true'),
input = form.find('input[type=submit]'),
newDisabledInput = input.clone().attr('disabled', 'disabled') newDisabledInput = input.clone().attr('disabled', 'disabled')
form.bindNative('ajax:success', function() { form.bindNative('ajax:success', function() {
@ -238,9 +240,9 @@ asyncTest('a[data-remote][data-disable-with] re-enables when `ajax:before` event
App.checkEnabledState(link, 'Click me') App.checkEnabledState(link, 'Click me')
link link
.bindNative('ajax:before', function() { .bindNative('ajax:before', function(e) {
App.checkDisabledState(link, 'clicking...') App.checkDisabledState(link, 'clicking...')
return false e.preventDefault()
}) })
.triggerNative('click') .triggerNative('click')
@ -256,9 +258,9 @@ asyncTest('a[data-remote][data-disable-with] re-enables when `ajax:beforeSend` e
App.checkEnabledState(link, 'Click me') App.checkEnabledState(link, 'Click me')
link link
.bindNative('ajax:beforeSend', function() { .bindNative('ajax:beforeSend', function(e) {
App.checkDisabledState(link, 'clicking...') App.checkDisabledState(link, 'clicking...')
return false e.preventDefault()
}) })
.triggerNative('click') .triggerNative('click')
@ -293,8 +295,9 @@ asyncTest('form[data-remote] input|button|textarea[data-disable-with] does not d
submit = $('<input type="submit" data-disable-with="submitting ..." name="submit2" value="Submit" />').appendTo(form) submit = $('<input type="submit" data-disable-with="submitting ..." name="submit2" value="Submit" />').appendTo(form)
form form
.bindNative('ajax:beforeSend', function() { .bindNative('ajax:beforeSend', function(e) {
return false e.preventDefault()
e.stopPropagation()
}) })
.triggerNative('submit') .triggerNative('submit')
@ -343,9 +346,9 @@ asyncTest('button[data-remote][data-disable-with] re-enables when `ajax:before`
App.checkEnabledState(button, 'Click me') App.checkEnabledState(button, 'Click me')
button button
.bindNative('ajax:before', function() { .bindNative('ajax:before', function(e) {
App.checkDisabledState(button, 'clicking...') App.checkDisabledState(button, 'clicking...')
return false e.preventDefault()
}) })
.triggerNative('click') .triggerNative('click')
@ -361,9 +364,9 @@ asyncTest('button[data-remote][data-disable-with] re-enables when `ajax:beforeSe
App.checkEnabledState(button, 'Click me') App.checkEnabledState(button, 'Click me')
button button
.bindNative('ajax:beforeSend', function() { .bindNative('ajax:beforeSend', function(e) {
App.checkDisabledState(button, 'clicking...') App.checkDisabledState(button, 'clicking...')
return false e.preventDefault()
}) })
.triggerNative('click') .triggerNative('click')

@ -91,7 +91,7 @@ asyncTest('form input[type=submit][data-disable] disables', 6, function() {
}) })
asyncTest('form[data-remote] input[type=submit][data-disable] is replaced in ajax callback', 2, function() { asyncTest('form[data-remote] input[type=submit][data-disable] is replaced in ajax callback', 2, function() {
var form = $('form:not([data-remote])').attr('data-remote', 'true'), origFormContents = form.html() var form = $('#qunit-fixture form:not([data-remote])').attr('data-remote', 'true'), origFormContents = form.html()
form.bindNative('ajax:success', function() { form.bindNative('ajax:success', function() {
form.html(origFormContents) form.html(origFormContents)
@ -105,7 +105,7 @@ asyncTest('form[data-remote] input[type=submit][data-disable] is replaced in aja
}) })
asyncTest('form[data-remote] input[data-disable] is replaced with disabled field in ajax callback', 2, function() { asyncTest('form[data-remote] input[data-disable] is replaced with disabled field in ajax callback', 2, function() {
var form = $('form:not([data-remote])').attr('data-remote', 'true'), input = form.find('input[type=submit]'), var form = $('#qunit-fixture form:not([data-remote])').attr('data-remote', 'true'), input = form.find('input[type=submit]'),
newDisabledInput = input.clone().attr('disabled', 'disabled') newDisabledInput = input.clone().attr('disabled', 'disabled')
form.bindNative('ajax:success', function() { form.bindNative('ajax:success', function() {
@ -168,9 +168,9 @@ asyncTest('a[data-remote][data-disable] re-enables when `ajax:before` event is c
App.checkEnabledState(link, 'Click me') App.checkEnabledState(link, 'Click me')
link link
.bindNative('ajax:before', function() { .bindNative('ajax:before', function(e) {
App.checkDisabledState(link, 'Click me') App.checkDisabledState(link, 'Click me')
return false e.preventDefault()
}) })
.triggerNative('click') .triggerNative('click')
@ -186,9 +186,9 @@ asyncTest('a[data-remote][data-disable] re-enables when `ajax:beforeSend` event
App.checkEnabledState(link, 'Click me') App.checkEnabledState(link, 'Click me')
link link
.bindNative('ajax:beforeSend', function() { .bindNative('ajax:beforeSend', function(e) {
App.checkDisabledState(link, 'Click me') App.checkDisabledState(link, 'Click me')
return false e.preventDefault()
}) })
.triggerNative('click') .triggerNative('click')
@ -223,8 +223,9 @@ asyncTest('form[data-remote] input|button|textarea[data-disable] does not disabl
submit = $('<input type="submit" data-disable="submitting ..." name="submit2" value="Submit" />').appendTo(form) submit = $('<input type="submit" data-disable="submitting ..." name="submit2" value="Submit" />').appendTo(form)
form form
.bindNative('ajax:beforeSend', function() { .bindNative('ajax:beforeSend', function(e) {
return false e.preventDefault()
e.stopPropagation()
}) })
.triggerNative('submit') .triggerNative('submit')
@ -273,9 +274,9 @@ asyncTest('button[data-remote][data-disable] re-enables when `ajax:before` event
App.checkEnabledState(button, 'Click me') App.checkEnabledState(button, 'Click me')
button button
.bindNative('ajax:before', function() { .bindNative('ajax:before', function(e) {
App.checkDisabledState(button, 'Click me') App.checkDisabledState(button, 'Click me')
return false e.preventDefault()
}) })
.triggerNative('click') .triggerNative('click')
@ -291,9 +292,9 @@ asyncTest('button[data-remote][data-disable] re-enables when `ajax:beforeSend` e
App.checkEnabledState(button, 'Click me') App.checkEnabledState(button, 'Click me')
button button
.bindNative('ajax:beforeSend', function() { .bindNative('ajax:beforeSend', function(e) {
App.checkDisabledState(button, 'Click me') App.checkDisabledState(button, 'Click me')
return false e.preventDefault()
}) })
.triggerNative('click') .triggerNative('click')

@ -272,9 +272,10 @@ asyncTest('returning false in form\'s submit bindings in non-submit-bubbling bro
form form
.append($('<input type="submit" />')) .append($('<input type="submit" />'))
.bindNative('submit', function() { .bindNative('submit', function(e) {
ok(true, 'binding handler is called') ok(true, 'binding handler is called')
return false e.preventDefault()
e.stopPropagation()
}) })
.bindNative('ajax:beforeSend', function() { .bindNative('ajax:beforeSend', function() {
ok(false, 'form should not be submitted') ok(false, 'form should not be submitted')
@ -296,8 +297,8 @@ asyncTest('clicking on a link with falsy "data-remote" attribute does not fire a
.bindNative('ajax:beforeSend', function() { .bindNative('ajax:beforeSend', function() {
ok(false, 'ajax should not be triggered') ok(false, 'ajax should not be triggered')
}) })
.bindNative('click', function() { .bindNative('click', function(e) {
return false e.preventDefault()
}) })
.triggerNative('click') .triggerNative('click')
@ -314,8 +315,8 @@ asyncTest('ctrl-clicking on a link with falsy "data-remote" attribute does not f
.bindNative('ajax:beforeSend', function() { .bindNative('ajax:beforeSend', function() {
ok(false, 'ajax should not be triggered') ok(false, 'ajax should not be triggered')
}) })
.bindNative('click', function() { .bindNative('click', function(e) {
return false e.preventDefault()
}) })
.triggerNative('click', { metaKey: true }) .triggerNative('click', { metaKey: true })
@ -333,8 +334,8 @@ asyncTest('clicking on a button with falsy "data-remote" attribute', 0, function
.bindNative('ajax:beforeSend', function() { .bindNative('ajax:beforeSend', function() {
ok(false, 'ajax should not be triggered') ok(false, 'ajax should not be triggered')
}) })
.bindNative('click', function() { .bindNative('click', function(e) {
return false e.preventDefault()
}) })
.triggerNative('click') .triggerNative('click')
@ -347,8 +348,8 @@ asyncTest('submitting a form with falsy "data-remote" attribute', 0, function()
.bindNative('ajax:beforeSend', function() { .bindNative('ajax:beforeSend', function() {
ok(false, 'ajax should not be triggered') ok(false, 'ajax should not be triggered')
}) })
.bindNative('submit', function() { .bindNative('submit', function(e) {
return false e.preventDefault()
}) })
.triggerNative('submit') .triggerNative('submit')
@ -429,7 +430,7 @@ asyncTest('changing a select option without "data-url" attribute still fires aja
ajaxLocation = settings.url.replace(settings.data, '').replace(/&$/, '').replace(/\?$/, '') ajaxLocation = settings.url.replace(settings.data, '').replace(/&$/, '').replace(/\?$/, '')
equal(ajaxLocation, currentLocation, 'URL should be current page by default') equal(ajaxLocation, currentLocation, 'URL should be current page by default')
return false e.preventDefault()
}) })
.val('optionValue2') .val('optionValue2')
.triggerNative('change') .triggerNative('change')

@ -25,7 +25,7 @@ asyncTest('the getter for an element\'s href is overridable', 1, function() {
$('#qunit-fixture a') $('#qunit-fixture a')
.bindNative('ajax:beforeSend', function(e, xhr, options) { .bindNative('ajax:beforeSend', function(e, xhr, options) {
equal('/data/href', options.url) equal('/data/href', options.url)
return false e.preventDefault()
}) })
.triggerNative('click') .triggerNative('click')
start() start()
@ -35,7 +35,7 @@ asyncTest('the getter for an element\'s href works normally if not overridden',
$('#qunit-fixture a') $('#qunit-fixture a')
.bindNative('ajax:beforeSend', function(e, xhr, options) { .bindNative('ajax:beforeSend', function(e, xhr, options) {
equal(location.protocol + '//' + location.host + '/real/href', options.url) equal(location.protocol + '//' + location.host + '/real/href', options.url)
return false e.preventDefault()
}) })
.triggerNative('click') .triggerNative('click')
start() start()

@ -103,14 +103,16 @@ $.fn.extend({
bindNative: function(event, handler) { bindNative: function(event, handler) {
if (!handler) return this if (!handler) return this
this.bind(event, function(e) { var el = this[0]
el.addEventListener(event, function(e) {
var args = [] var args = []
if (e.originalEvent.detail) { if (e.detail) {
args = e.originalEvent.detail.slice() args = e.detail.slice()
} }
args.unshift(e) args.unshift(e)
return handler.apply(this, args) return handler.apply(el, args)
}) }, false)
return this return this
} }
}) })

@ -382,10 +382,9 @@ have been bundled into `event.detail`. For information about the previously used
`jquery-ujs` in Rails 5 and earlier, read the [`jquery-ujs` wiki](https://github.com/rails/jquery-ujs/wiki/ajax). `jquery-ujs` in Rails 5 and earlier, read the [`jquery-ujs` wiki](https://github.com/rails/jquery-ujs/wiki/ajax).
### Stoppable events ### Stoppable events
You can stop execution of the Ajax request by running `event.preventDefault()`
If you stop `ajax:before` or `ajax:beforeSend` by returning false from the from the handlers methods `ajax:before` or `ajax:beforeSend`.
handler method, the Ajax request will never take place. The `ajax:before` event The `ajax:before` event can manipulate form data before serialization and the
can manipulate form data before serialization and the
`ajax:beforeSend` event is useful for adding custom request headers. `ajax:beforeSend` event is useful for adding custom request headers.
If you stop the `ajax:aborted:file` event, the default behavior of allowing the If you stop the `ajax:aborted:file` event, the default behavior of allowing the
@ -393,6 +392,9 @@ browser to submit the form via normal means (i.e. non-Ajax submission) will be
canceled and the form will not be submitted at all. This is useful for canceled and the form will not be submitted at all. This is useful for
implementing your own Ajax file upload workaround. implementing your own Ajax file upload workaround.
Note, you should use `return false` to prevent event for `jquery-ujs` and
`e.preventDefault()` for `rails-ujs`
Server-Side Concerns Server-Side Concerns
-------------------- --------------------