From 6ac0568053484c115b03f8b7fda4c6410612cf09 Mon Sep 17 00:00:00 2001 From: Henry Blyth Date: Thu, 23 Jun 2022 14:57:59 +0100 Subject: [PATCH 1/3] Reset to rails/jquery-ujs v1.2.3 --- README.md | 15 ++++ package.json | 4 +- src/rails.js | 240 +++++++++++++++++++++++---------------------------- 3 files changed, 123 insertions(+), 136 deletions(-) diff --git a/README.md b/README.md index df6aee68..2e429bd1 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,21 @@ Installation using npm. Run `npm install --save jquery-ujs` to install the jquery-ujs package. +Installation using Rails and Webpacker +------------ + +If you're using [webpacker](https://github.com/rails/webpacker) (introduced in [Rails 5.1](http://edgeguides.rubyonrails.org/5_1_release_notes.html#optional-webpack-support)) to manage JavaScript assets, then you can add the jquery-ujs npm package to your project using the [yarn](https://yarnpkg.com/en/) CLI. + +``` +$ yarn add jquery-ujs +``` + +Then, from any of your included files (e.g. `app/javascript/packs/application.js`, or from a JavaScript file imported by such a pack), you need only import the package for jquery-ujs to be initialized: + +```js +import {} from 'jquery-ujs' +``` + Installation using Bower ------------ diff --git a/package.json b/package.json index 08a5a245..9299d5f5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery-ujs", - "version": "1.2.2", + "version": "1.2.3", "description": "Unobtrusive scripting adapter for jQuery", "main": "src/rails.js", "scripts": { @@ -20,7 +20,7 @@ "url": "https://github.com/rails/jquery-ujs/issues" }, "homepage": "https://github.com/rails/jquery-ujs#readme", - "dependencies": { + "peerDependencies": { "jquery": ">=1.8.0" } } diff --git a/src/rails.js b/src/rails.js index 7571588e..0e27110d 100644 --- a/src/rails.js +++ b/src/rails.js @@ -1,4 +1,4 @@ -(function($, undefined) { +/* jshint node: true */ /** * Unobtrusive scripting adapter for jQuery @@ -10,10 +10,13 @@ * */ - // Cut down on the number of issues from people inadvertently including jquery_ujs twice - // by detecting and raising an error when it happens. +(function() { 'use strict'; + var jqueryUjsInit = function($, undefined) { + + // Cut down on the number of issues from people inadvertently including jquery_ujs twice + // by detecting and raising an error when it happens. if ( $.rails !== undefined ) { $.error('jquery-ujs has already been loaded!'); } @@ -33,10 +36,10 @@ inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]', // Form elements bound by jquery-ujs - formSubmitSelector: 'form', + formSubmitSelector: 'form:not([data-turbo=true])', // Form input elements bound by jquery-ujs - formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not([type]), input[type=submit][form], input[type=image][form], button[type=submit][form], button[form]:not([type])', + 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])', // Form input elements disabled during form submission 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', @@ -85,8 +88,8 @@ }, // Default confirm dialog, may be overridden with custom confirm dialog in $.rails.confirm - confirm: function(message, element, callback) { - callback(confirm(message)); + confirm: function(message) { + return confirm(message); }, // Default ajax function, may be overridden with custom function in $.rails.ajax @@ -286,29 +289,25 @@ - Shows the confirmation dialog - Fires the `confirm:complete` event - Callback function gets `true` if no function stops the chain and user chose yes; `false` otherwise. + Returns `true` if no function stops the chain and user chose yes; `false` otherwise. Attaching a handler to the element's `confirm` event that returns a `falsy` value cancels the confirmation dialog. - Attaching a handler to the element's `confirm:complete` event that returns a `falsy` value makes callback function - gets false. The `confirm:complete` event is fired whether or not the user answered true or false to the dialog. + Attaching a handler to the element's `confirm:complete` event that returns a `falsy` value makes this function + return false. The `confirm:complete` event is fired whether or not the user answered true or false to the dialog. */ - allowAction: function(element, callback) { - var message = element.data('confirm'); - if (!message) { - callback(true); - } else if (rails.fire(element, 'confirm')) { - var confirmCallback = function(answer) { - var completeResult = rails.fire(element, 'confirm:complete', [answer]); - callback(answer && completeResult); - }; + allowAction: function(element) { + var message = element.data('confirm'), + answer = false, callback; + if (!message) { return true; } + + if (rails.fire(element, 'confirm')) { try { - rails.confirm(message, element, confirmCallback); + answer = rails.confirm(message); } catch (e) { (console.error || console.log).call(console, e.stack || e); - confirmCallback(false); } - } else { - callback(false); + callback = rails.fire(element, 'confirm:complete', [answer]); } + return answer && callback; }, // Helper function which checks for blank inputs in a form that match the specified CSS selector @@ -430,60 +429,50 @@ $document.on('click.rails', rails.linkClickSelector, function(e) { var link = $(this), method = link.data('method'), data = link.data('params'), metaClick = e.metaKey || e.ctrlKey; - rails.allowAction(link, function(answer) { - if (answer) { - if (!metaClick && link.is(rails.linkDisableSelector)) rails.disableElement(link); + if (!rails.allowAction(link)) return rails.stopEverything(e); - if (rails.isRemote(link)) { - if (metaClick && (!method || method === 'GET') && !data) { return true; } + if (!metaClick && link.is(rails.linkDisableSelector)) rails.disableElement(link); - var handleRemote = rails.handleRemote(link); - // Response from rails.handleRemote() will either be false or a deferred object promise. - if (handleRemote === false) { - rails.enableElement(link); - } else { - handleRemote.fail( function() { rails.enableElement(link); } ); - } - } else if (method) { - rails.handleMethod(link); - } + if (rails.isRemote(link)) { + if (metaClick && (!method || method === 'GET') && !data) { return true; } + + var handleRemote = rails.handleRemote(link); + // Response from rails.handleRemote() will either be false or a deferred object promise. + if (handleRemote === false) { + rails.enableElement(link); } else { - rails.stopEverything(e); + handleRemote.fail( function() { rails.enableElement(link); } ); } - }); - return false; + return false; + + } else if (method) { + rails.handleMethod(link); + return false; + } }); $document.on('click.rails', rails.buttonClickSelector, function(e) { var button = $(this); - rails.allowAction(button, function(answer) { - if (!answer || !rails.isRemote(button)) { - rails.stopEverything(e); - } else { - if (button.is(rails.buttonDisableSelector)) rails.disableFormElement(button); - - var handleRemote = rails.handleRemote(button); - // Response from rails.handleRemote() will either be false or a deferred object promise. - if (handleRemote === false) { - rails.enableFormElement(button); - } else { - handleRemote.fail( function() { rails.enableFormElement(button); } ); - } - } - }); + if (!rails.allowAction(button) || !rails.isRemote(button)) return rails.stopEverything(e); + + if (button.is(rails.buttonDisableSelector)) rails.disableFormElement(button); + + var handleRemote = rails.handleRemote(button); + // Response from rails.handleRemote() will either be false or a deferred object promise. + if (handleRemote === false) { + rails.enableFormElement(button); + } else { + handleRemote.fail( function() { rails.enableFormElement(button); } ); + } return false; }); $document.on('change.rails', rails.inputChangeSelector, function(e) { var link = $(this); - rails.allowAction(link, function(answer) { - if (!answer || !rails.isRemote(link)) { - rails.stopEverything(e); - } else { - rails.handleRemote(link); - } - }); + if (!rails.allowAction(link) || !rails.isRemote(link)) return rails.stopEverything(e); + + rails.handleRemote(link); return false; }); @@ -493,88 +482,64 @@ blankRequiredInputs, nonBlankFileInputs; - if (form.data('ujs:skip-event')) { - form.data('ujs:skip-event', undefined); - return; - } + if (!rails.allowAction(form)) return rails.stopEverything(e); - rails.allowAction(form, function(answer) { - if (!answer) { - rails.stopEverything(e); - } else { - // Skip other logic when required values are missing or file upload is present - if (form.attr('novalidate') === undefined) { - if (form.data('ujs:formnovalidate-button') === undefined) { - blankRequiredInputs = rails.blankInputs(form, rails.requiredInputSelector, false); - if (blankRequiredInputs && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs])) { - return rails.stopEverything(e); - } - } else { - // Clear the formnovalidate in case the next button click is not on a formnovalidate button - // Not strictly necessary to do here, since it is also reset on each button click, but just to be certain - form.data('ujs:formnovalidate-button', undefined); - } + // Skip other logic when required values are missing or file upload is present + if (form.attr('novalidate') === undefined) { + if (form.data('ujs:formnovalidate-button') === undefined) { + blankRequiredInputs = rails.blankInputs(form, rails.requiredInputSelector, false); + if (blankRequiredInputs && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs])) { + return rails.stopEverything(e); } + } else { + // Clear the formnovalidate in case the next button click is not on a formnovalidate button + // Not strictly necessary to do here, since it is also reset on each button click, but just to be certain + form.data('ujs:formnovalidate-button', undefined); + } + } - if (remote) { - nonBlankFileInputs = rails.nonBlankInputs(form, rails.fileInputSelector); - if (nonBlankFileInputs) { - // Slight timeout so that the submit button gets properly serialized - // (make it easy for event handler to serialize form without disabled values) - setTimeout(function(){ rails.disableFormElements(form); }, 13); - var aborted = rails.fire(form, 'ajax:aborted:file', [nonBlankFileInputs]); - - if (aborted) { - form.data('ujs:skip-event', true).submit(); - } else { - // Re-enable form elements if event bindings return false (canceling normal form submission) - setTimeout(function(){ rails.enableFormElements(form); }, 13); - } - } else { - rails.handleRemote(form); - } - } else { - // Slight timeout so that the submit button gets properly serialized - setTimeout(function(){ rails.disableFormElements(form); }, 13); - form.data('ujs:skip-event', true).submit(); - } + if (remote) { + nonBlankFileInputs = rails.nonBlankInputs(form, rails.fileInputSelector); + if (nonBlankFileInputs) { + // Slight timeout so that the submit button gets properly serialized + // (make it easy for event handler to serialize form without disabled values) + setTimeout(function(){ rails.disableFormElements(form); }, 13); + var aborted = rails.fire(form, 'ajax:aborted:file', [nonBlankFileInputs]); + + // Re-enable form elements if event bindings return false (canceling normal form submission) + if (!aborted) { setTimeout(function(){ rails.enableFormElements(form); }, 13); } + + return aborted; } - }); - return false; + rails.handleRemote(form); + return false; + + } else { + // Slight timeout so that the submit button gets properly serialized + setTimeout(function(){ rails.disableFormElements(form); }, 13); + } }); $document.on('click.rails', rails.formInputClickSelector, function(event) { var button = $(this); - if (button.data('ujs:skip-event')) { - button.data('ujs:skip-event', undefined); - return; - } - rails.allowAction(button, function(answer) { - if (answer) { - // Register the pressed submit button - var name = button.attr('name'), - data = name ? {name:name, value:button.val()} : null; + if (!rails.allowAction(button)) return rails.stopEverything(event); - var form = button.closest('form'); - if (form.length === 0) { - form = $('#' + button.attr('form')); - } - form.data('ujs:submit-button', data); - - // Save attributes from button - form.data('ujs:formnovalidate-button', button.attr('formnovalidate')); - form.data('ujs:submit-button-formaction', button.attr('formaction')); - form.data('ujs:submit-button-formmethod', button.attr('formmethod')); - button.data('ujs:skip-event', true); - setTimeout(function() { button.click(); }, 0); - } else { - rails.stopEverything(event); - } - }); + // Register the pressed submit button + var name = button.attr('name'), + data = name ? {name:name, value:button.val()} : null; - return false; + var form = button.closest('form'); + if (form.length === 0) { + form = $('#' + button.attr('form')); + } + form.data('ujs:submit-button', data); + + // Save attributes from button + form.data('ujs:formnovalidate-button', button.attr('formnovalidate')); + form.data('ujs:submit-button-formaction', button.attr('formaction')); + form.data('ujs:submit-button-formmethod', button.attr('formmethod')); }); $document.on('ajax:send.rails', rails.formSubmitSelector, function(event) { @@ -590,4 +555,11 @@ }); } -})( jQuery ); + }; + + if (window.jQuery) { + jqueryUjsInit(jQuery); + } else if (typeof exports === 'object' && typeof module === 'object') { + module.exports = jqueryUjsInit; + } +})(); From 687f1e9199cd0db8386901bcf4ef3e8f6cec9989 Mon Sep 17 00:00:00 2001 From: Henry Blyth Date: Thu, 23 Jun 2022 14:59:03 +0100 Subject: [PATCH 2/3] Pass actioning element to rails.confirm So custom confirm can take action based on the element rather than just the message alone. --- src/rails.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rails.js b/src/rails.js index 0e27110d..a6ea8e47 100644 --- a/src/rails.js +++ b/src/rails.js @@ -88,7 +88,7 @@ }, // Default confirm dialog, may be overridden with custom confirm dialog in $.rails.confirm - confirm: function(message) { + confirm: function(message, element) { return confirm(message); }, @@ -301,7 +301,7 @@ if (rails.fire(element, 'confirm')) { try { - answer = rails.confirm(message); + answer = rails.confirm(message, element); } catch (e) { (console.error || console.log).call(console, e.stack || e); } From ba44cf4219248d6525f82e5af7b0175a9bea3afd Mon Sep 17 00:00:00 2001 From: Henry Blyth Date: Thu, 23 Jun 2022 22:14:27 +0100 Subject: [PATCH 3/3] Pass event to rails.confirm too --- src/rails.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/rails.js b/src/rails.js index a6ea8e47..b54fd8f0 100644 --- a/src/rails.js +++ b/src/rails.js @@ -88,7 +88,7 @@ }, // Default confirm dialog, may be overridden with custom confirm dialog in $.rails.confirm - confirm: function(message, element) { + confirm: function(message, element, event) { return confirm(message); }, @@ -294,14 +294,14 @@ Attaching a handler to the element's `confirm:complete` event that returns a `falsy` value makes this function return false. The `confirm:complete` event is fired whether or not the user answered true or false to the dialog. */ - allowAction: function(element) { + allowAction: function(element, event) { var message = element.data('confirm'), answer = false, callback; if (!message) { return true; } if (rails.fire(element, 'confirm')) { try { - answer = rails.confirm(message, element); + answer = rails.confirm(message, element, event); } catch (e) { (console.error || console.log).call(console, e.stack || e); } @@ -429,7 +429,7 @@ $document.on('click.rails', rails.linkClickSelector, function(e) { var link = $(this), method = link.data('method'), data = link.data('params'), metaClick = e.metaKey || e.ctrlKey; - if (!rails.allowAction(link)) return rails.stopEverything(e); + if (!rails.allowAction(link, e)) return rails.stopEverything(e); if (!metaClick && link.is(rails.linkDisableSelector)) rails.disableElement(link); @@ -454,7 +454,7 @@ $document.on('click.rails', rails.buttonClickSelector, function(e) { var button = $(this); - if (!rails.allowAction(button) || !rails.isRemote(button)) return rails.stopEverything(e); + if (!rails.allowAction(button, e) || !rails.isRemote(button)) return rails.stopEverything(e); if (button.is(rails.buttonDisableSelector)) rails.disableFormElement(button); @@ -470,7 +470,7 @@ $document.on('change.rails', rails.inputChangeSelector, function(e) { var link = $(this); - if (!rails.allowAction(link) || !rails.isRemote(link)) return rails.stopEverything(e); + if (!rails.allowAction(link, e) || !rails.isRemote(link)) return rails.stopEverything(e); rails.handleRemote(link); return false; @@ -482,7 +482,7 @@ blankRequiredInputs, nonBlankFileInputs; - if (!rails.allowAction(form)) return rails.stopEverything(e); + if (!rails.allowAction(form, e)) return rails.stopEverything(e); // Skip other logic when required values are missing or file upload is present if (form.attr('novalidate') === undefined) { @@ -524,7 +524,7 @@ $document.on('click.rails', rails.formInputClickSelector, function(event) { var button = $(this); - if (!rails.allowAction(button)) return rails.stopEverything(event); + if (!rails.allowAction(button, event)) return rails.stopEverything(event); // Register the pressed submit button var name = button.attr('name'),