Merge pull request #7572 from tchak/refactor-use-turbo-instead-of-ujs

refactor(ujs): remove old ujs helpers
This commit is contained in:
Paul Chavard 2022-09-29 17:26:25 +02:00 committed by GitHub
commit 9cc5415a1a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 64 additions and 272 deletions

View file

@ -53,7 +53,7 @@ class FranceConnect::ParticulierController < ApplicationController
if !user.can_france_connect?
flash.alert = t('errors.messages.france_connect.forbidden_html', reset_link: new_user_password_path)
render js: ajax_redirect(root_path)
redirect_to root_path
else
@fci.update(user: user)
@fci.delete_merge_token!
@ -63,8 +63,6 @@ class FranceConnect::ParticulierController < ApplicationController
end
else
flash.alert = t('france_connect.particulier.flash.invalid_password')
render js: helpers.render_flash
end
end
@ -112,10 +110,7 @@ class FranceConnect::ParticulierController < ApplicationController
if @fci.nil? || !@fci.valid_for_merge?
flash.alert = t('france_connect.particulier.flash.merger_token_expired', application_name: APPLICATION_NAME)
respond_to do |format|
format.html { redirect_to root_path }
format.js { render js: ajax_redirect(root_path) }
end
redirect_to root_path
end
end
@ -134,12 +129,7 @@ class FranceConnect::ParticulierController < ApplicationController
user.update_attribute('loged_in_with_france_connect', User.loged_in_with_france_connects.fetch(:particulier))
redirection_location = stored_location_for(current_user) || root_path(current_user)
respond_to do |format|
format.html { redirect_to redirection_location }
format.js { render js: ajax_redirect(root_path) }
end
redirect_to stored_location_for(current_user) || root_path(current_user)
end
def redirect_france_connect_error_connection

View file

@ -40,77 +40,6 @@ module ApplicationHelper
tag.div(**html.merge(data: { controller: 'react', react_component_value: name, react_props_value: props.to_json }))
end
def render_to_element(selector, partial:, outer: false, locals: {})
method = outer ? 'outerHTML' : 'innerHTML'
html = escape_javascript(render partial: partial, locals: locals)
# rubocop:disable Rails/OutputSafety
raw("document.querySelector('#{selector}').#{method} = \"#{html}\";")
# rubocop:enable Rails/OutputSafety
end
def append_to_element(selector, partial:, locals: {})
html = escape_javascript(render partial: partial, locals: locals)
# rubocop:disable Rails/OutputSafety
raw("document.querySelector('#{selector}').insertAdjacentHTML('beforeend', \"#{html}\");")
# rubocop:enable Rails/OutputSafety
end
def render_flash(timeout: false, sticky: false, fixed: false)
if flash.any?
html = render_to_element('#flash_messages', partial: 'layouts/flash_messages', locals: { sticky: sticky, fixed: fixed }, outer: true)
flash.clear
if timeout
html += remove_element('#flash_messages', timeout: timeout, inner: true)
end
html
end
end
def remove_element(selector, timeout: 0, inner: false)
script = "(function() {";
script << "var el = document.querySelector('#{selector}');"
method = (inner ? "el.innerHTML = ''" : "el.parentNode.removeChild(el)")
if timeout.present? && timeout > 0
script << "if (el) { setTimeout(function() { #{method}; }, #{timeout}); }"
else
script << "if (el) { #{method} };"
end
script << "})();"
# rubocop:disable Rails/OutputSafety
raw(script);
# rubocop:enable Rails/OutputSafety
end
def show_element(selector)
# rubocop:disable Rails/OutputSafety
raw("document.querySelector('#{selector}').classList.remove('hidden');")
# rubocop:enable Rails/OutputSafety
end
def focus_element(selector)
# rubocop:disable Rails/OutputSafety
raw("document.querySelector('#{selector}').focus();")
# rubocop:enable Rails/OutputSafety
end
def disable_element(selector)
# rubocop:disable Rails/OutputSafety
raw("document.querySelector('#{selector}').disabled = true;")
# rubocop:enable Rails/OutputSafety
end
def enable_element(selector)
# rubocop:disable Rails/OutputSafety
raw("document.querySelector('#{selector}').disabled = false;")
# rubocop:enable Rails/OutputSafety
end
def fire_event(event_name, data)
# rubocop:disable Rails/OutputSafety
raw("DS.fire('#{event_name}', #{raw(data)});")
# rubocop:enable Rails/OutputSafety
end
def current_email
current_user&.email ||
current_instructeur&.email ||

View file

@ -5,17 +5,14 @@ import { Application } from '@hotwired/stimulus';
import '@gouvfr/dsfr/dist/dsfr.module.js';
import '../shared/activestorage/ujs';
import '../shared/remote-poller';
import '../shared/safari-11-file-xhr-workaround';
import '../shared/safari-11-empty-file-workaround';
import '../shared/toggle-target';
import '../shared/ujs-error-handling';
import { registerControllers } from '../shared/stimulus-loader';
import '../new_design/form-validation';
import '../new_design/procedure-context';
import '../new_design/procedure-form';
import '../new_design/spinner';
import '../new_design/support';
import {
@ -31,11 +28,7 @@ import {
acceptEmailSuggestion,
discardEmailSuggestionBox
} from '../new_design/user-sign_up';
import {
showFusion,
showNewAccount,
showNewAccountPasswordConfirmation
} from '../new_design/fc-fusion';
import { showFusion, showNewAccount } from '../new_design/fc-fusion';
const application = Application.start();
registerControllers(application);
@ -49,7 +42,6 @@ const DS = {
showImportJustificatif,
showFusion,
showNewAccount,
showNewAccountPasswordConfirmation,
replaceSemicolonByComma,
acceptEmailSuggestion,
discardEmailSuggestionBox

View file

@ -3,17 +3,11 @@ import { show, hide } from '@utils';
export function showFusion() {
show(document.querySelector('.fusion'));
hide(document.querySelector('.new-account'));
hide(document.querySelector('.new-account-password-confirmation'));
hide(document.querySelector('#new-account-password-confirmation'));
}
export function showNewAccount() {
hide(document.querySelector('.fusion'));
show(document.querySelector('.new-account'));
hide(document.querySelector('.new-account-password-confirmation'));
}
export function showNewAccountPasswordConfirmation() {
hide(document.querySelector('.fusion'));
hide(document.querySelector('.new-account'));
show(document.querySelector('.new-account-password-confirmation'));
hide(document.querySelector('#new-account-password-confirmation'));
}

View file

@ -1,13 +0,0 @@
import { show, hide, delegate } from '@utils';
function showSpinner() {
[...document.querySelectorAll('.spinner')].forEach(show);
}
function hideSpinner() {
[...document.querySelectorAll('.spinner')].forEach(hide);
}
delegate('ajax:complete', '[data-spinner]', hideSpinner);
delegate('ajax:stopped', '[data-spinner]', hideSpinner);
delegate('ajax:send', '[data-spinner]', showSpinner);

View file

@ -1,101 +0,0 @@
import { httpRequest, delegate } from '@utils';
addEventListener('DOMContentLoaded', () => {
attachementPoller.deactivate();
exportPoller.deactivate();
const attachments = document.querySelectorAll('[data-attachment-poll-url]');
const exports = document.querySelectorAll('[data-export-poll-url]');
for (let { dataset } of attachments) {
attachementPoller.add(dataset.attachmentPollUrl);
}
for (let { dataset } of exports) {
exportPoller.add(dataset.exportPollUrl);
}
});
addEventListener('attachment:update', ({ detail: { url } }) => {
attachementPoller.add(url);
});
addEventListener('export:update', ({ detail: { url } }) => {
exportPoller.add(url);
});
delegate('click', '[data-attachment-refresh]', (event) => {
event.preventDefault();
attachementPoller.check();
});
// Periodically check the state of a set of URLs.
//
// Each time the given URL is requested, the matching `show.js.erb` view is rendered,
// causing the state to be refreshed.
//
// This is used mainly to refresh attachments during the anti-virus check,
// but also to refresh the state of a pending spreadsheet export.
class RemotePoller {
urls = new Set();
timeout;
checks = 0;
constructor(settings = {}) {
this.interval = settings.interval;
this.maxChecks = settings.maxChecks;
}
get isEnabled() {
return this.checks <= this.maxChecks;
}
get isActive() {
return this.timeout !== undefined;
}
add(url) {
if (this.isEnabled) {
if (!this.isActive) {
this.activate();
}
this.urls.add(url);
}
}
check() {
let urls = this.urls;
this.reset();
for (let url of urls) {
// Start the request. The JS payload in the response will update the page.
// (Errors are ignored, because background tasks shouldn't report errors to the user.)
httpRequest(url)
.js()
.catch(() => {});
}
}
activate() {
clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
this.checks++;
this.currentInterval = this.interval * 1.5;
this.check();
}, this.currentInterval);
}
deactivate() {
this.checks = 0;
this.currentInterval = this.interval;
this.reset();
}
reset() {
clearTimeout(this.timeout);
this.urls = new Set();
this.timeout = undefined;
}
}
const attachementPoller = new RemotePoller({ interval: 3000, maxChecks: 5 });
const exportPoller = new RemotePoller({ interval: 6000, maxChecks: 10 });

View file

@ -0,0 +1,36 @@
// iOS 11.3 Safari / macOS Safari 11.1 empty <input type="file"> XHR bug workaround.
// This should work with every modern browser which supports ES5 (including IE9).
// https://stackoverflow.com/questions/49614091/safari-11-1-ajax-xhr-form-submission-fails-when-inputtype-file-is-empty
// https://github.com/rails/rails/issues/32440
document.documentElement.addEventListener(
'turbo:before-fetch-request',
(event) => {
const target = event.target as Element;
const inputs = target.querySelectorAll<HTMLInputElement>(
'input[type="file"]:not([disabled])'
);
for (const input of inputs) {
if (input.files?.length == 0) {
input.setAttribute('data-safari-temp-disabled', 'true');
input.setAttribute('disabled', '');
}
}
}
);
document.documentElement.addEventListener(
'turbo:before-fetch-response',
(event) => {
const target = event.target as Element;
const inputs = target.querySelectorAll(
'input[type="file"][data-safari-temp-disabled]'
);
for (const input of inputs) {
input.removeAttribute('data-safari-temp-disabled');
input.removeAttribute('disabled');
}
}
);
export {};

View file

@ -1,26 +0,0 @@
// iOS 11.3 Safari / macOS Safari 11.1 empty <input type="file"> XHR bug workaround.
// This should work with every modern browser which supports ES5 (including IE9).
// https://stackoverflow.com/questions/49614091/safari-11-1-ajax-xhr-form-submission-fails-when-inputtype-file-is-empty
// https://github.com/rails/rails/issues/32440
document.addEventListener('ajax:before', function (e) {
let inputs = e.target.querySelectorAll('input[type="file"]:not([disabled])');
inputs.forEach(function (input) {
if (input.files.length > 0) {
return;
}
input.setAttribute('data-safari-temp-disabled', 'true');
input.setAttribute('disabled', '');
});
});
// You should call this by yourself when you aborted an ajax request by stopping a event in ajax:before hook.
document.addEventListener('ajax:beforeSend', function (e) {
let inputs = e.target.querySelectorAll(
'input[type="file"][data-safari-temp-disabled]'
);
inputs.forEach(function (input) {
input.removeAttribute('data-safari-temp-disabled');
input.removeAttribute('disabled');
});
});

View file

@ -1,8 +0,0 @@
// For links and requests done through rails-ujs (mostly data-remote links),
// redirect to the sign-in page when the server responds '401 Unauthorized'.
document.addEventListener('ajax:error', (event) => {
const [, , xhr] = event.detail;
if (xhr && xhr.status == 401) {
location.reload(); // reload whole page so Devise will redirect to sign-in
}
});

View file

@ -16,7 +16,7 @@
url: allow_expert_review_admin_procedure_path(@procedure),
html: { class: 'form procedure-form__column--form no-background' } do |f|
%label.toggle-switch
= f.check_box :allow_expert_review, class: 'toggle-switch-checkbox', onchange: 'this.form.submit()'
= f.check_box :allow_expert_review, class: 'toggle-switch-checkbox', onchange: 'this.form.requestSubmit()'
%span.toggle-switch-control.round
%span.toggle-switch-label.on
%span.toggle-switch-label.off
@ -30,7 +30,7 @@
url: experts_require_administrateur_invitation_admin_procedure_path(@procedure),
html: { class: 'form procedure-form__column--form no-background' } do |f|
%label.toggle-switch
= f.check_box :experts_require_administrateur_invitation, class: 'toggle-switch-checkbox', onchange: 'this.form.submit()'
= f.check_box :experts_require_administrateur_invitation, class: 'toggle-switch-checkbox', onchange: 'this.form.requestSubmit()'
%span.toggle-switch-control.round
%span.toggle-switch-label.on
%span.toggle-switch-label.off
@ -76,12 +76,11 @@
%td.text-center
= form_for expert_procedure,
url: admin_procedure_expert_path(id: expert_procedure),
remote: true,
method: :put,
authenticity_token: true,
data: { turbo: true },
html: { class: 'form procedure-form__column--form no-background' } do |f|
%label.toggle-switch
= f.check_box :allow_decision_access, class: 'toggle-switch-checkbox', onchange: 'this.form.submit()'
= f.check_box :allow_decision_access, class: 'toggle-switch-checkbox', onchange: 'this.form.requestSubmit()'
%span.toggle-switch-control.round
%span.toggle-switch-label.on
%span.toggle-switch-label.off

View file

@ -3,7 +3,7 @@
%br
= t('.fill_in_password')
= form_tag france_connect_particulier_merge_with_existing_account_path, remote: true, class: 'mt-2 form fconnect-form' do
= form_tag france_connect_particulier_merge_with_existing_account_path, data: { turbo: true }, class: 'mt-2 form fconnect-form' do
= hidden_field_tag :merge_token, merge_token
= hidden_field_tag :email, email
= label_tag :password, t('views.registrations.new.password_label', min_length: 8)

View file

@ -19,9 +19,9 @@
.fusion.hidden
%p= t('.title_fill_in_password')
= form_tag france_connect_particulier_merge_with_existing_account_path, remote: true, class: 'mt-2 form fconnect-form' do
= hidden_field_tag :merge_token, @fci.merge_token
= hidden_field_tag :email, @fci.email_france_connect
= form_tag france_connect_particulier_merge_with_existing_account_path, data: { turbo: true }, class: 'mt-2 form fconnect-form' do
= hidden_field_tag :merge_token, @fci.merge_token, id: dom_id(@fci, :fusion_merge_token)
= hidden_field_tag :email, @fci.email_france_connect, id: dom_id(@fci, :fusion_email)
= label_tag :password, t('views.registrations.new.password_label', min_length: 8)
= password_field_tag :password, nil, autocomplete: 'current-password', class: 'mb-1'
@ -36,11 +36,11 @@
.new-account.hidden
%p= t('.title_fill_in_email', application_name: APPLICATION_NAME)
= form_tag france_connect_particulier_merge_with_new_account_path, remote: true, class: 'mt-2 form' do
= hidden_field_tag :merge_token, @fci.merge_token
= label_tag :email, t('views.registrations.new.email_label')
= email_field_tag :email, "", required: true
= form_tag france_connect_particulier_merge_with_new_account_path, data: { turbo: true }, class: 'mt-2 form' do
= hidden_field_tag :merge_token, @fci.merge_token, id: dom_id(@fci, :new_account_merge_token)
= label_tag :email, t('views.registrations.new.email_label'), for: dom_id(@fci, :new_account_email)
= email_field_tag :email, "", required: true, id: dom_id(@fci, :new_account_email)
= submit_tag t('.button_use_this_email'), class: 'button primary'
.new-account-password-confirmation.hidden
#new-account-password-confirmation.hidden

View file

@ -1,3 +0,0 @@
<%= render_to_element('.new-account-password-confirmation', partial: 'password_confirmation', locals: { email: @email, merge_token: @merge_token }) %>
DS.showNewAccountPasswordConfirmation();

View file

@ -0,0 +1,4 @@
= turbo_stream.update 'new-account-password-confirmation', partial: 'password_confirmation', locals: { email: @email, merge_token: @merge_token }
= turbo_stream.hide_all '.fusion'
= turbo_stream.hide_all '.new-account'
= turbo_stream.show 'new-account-password-confirmation'

View file

@ -1 +0,0 @@
= render_flash(sticky: true)

View file

@ -198,7 +198,7 @@ describe FranceConnect::ParticulierController, type: :controller do
let(:merge_token) { fci.create_merge_token! }
let(:email) { 'EXISTING_account@a.com ' }
let(:password) { 'my-s3cure-p4ssword' }
let(:format) { :js }
let(:format) { :turbo_stream }
subject { post :merge_with_existing_account, params: { merge_token: merge_token, email: email, password: password }, format: format }
@ -309,7 +309,7 @@ describe FranceConnect::ParticulierController, type: :controller do
let(:fci) { FranceConnectInformation.create!(user_info) }
let(:merge_token) { fci.create_merge_token! }
let(:email) { ' Account@a.com ' }
let(:format) { :js }
let(:format) { :turbo_stream }
subject { post :merge_with_new_account, params: { merge_token: merge_token, email: email }, format: format }
@ -323,7 +323,7 @@ describe FranceConnect::ParticulierController, type: :controller do
expect(fci.user.email).to eq(email.downcase.strip)
expect(fci.merge_token).to be_nil
expect(controller.current_user).to eq(fci.user)
expect(response.body).to include("window.location.href='/'")
expect(response).to redirect_to(root_path)
end
end

View file

@ -83,7 +83,7 @@ describe 'France Connect Particulier Connexion' do
expect(page).to have_css('#password-for-another-account', visible: true)
within '.new-account-password-confirmation' do
within '#new-account-password-confirmation' do
fill_in 'password', with: 'my-s3cure-p4ssword'
click_on 'Fusionner les comptes'
end