Merge pull request #2139 from betagouv/frederic/fix_1421-double_dropdwon_ui

Frederic/fix 1421 double dropdown ui
This commit is contained in:
Frederic Merizen 2018-06-27 17:22:12 +02:00 committed by GitHub
commit 5af6d0511a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 243 additions and 2 deletions

View file

@ -0,0 +1,30 @@
document.addEventListener('turbolinks:load', function() {
var primaries, i, primary, secondary, secondaryOptions;
primaries = document.querySelectorAll('select[data-secondary-options]');
for (i = 0; i < primaries.length; i++) {
primary = primaries[i];
secondary = document.querySelector('select[data-secondary-id="' + primary.dataset.primaryId + '"]');
secondaryOptions = JSON.parse(primary.dataset.secondaryOptions);
primary.addEventListener('change', function(e) {
var option, options, element;
while ((option = secondary.firstChild)) {
secondary.removeChild(option);
}
options = secondaryOptions[e.target.value];
for (i = 0; i < options.length; i++) {
option = options[i];
element = document.createElement("option");
element.textContent = option;
element.value = option;
secondary.appendChild(element);
}
secondary.selectedIndex = 0;
});
}
});

View file

@ -145,7 +145,7 @@ module NewUser
def champs_params
params.permit(dossier: {
champs_attributes: [
:id, :value, :piece_justificative_file, value: [],
:id, :value, :primary_value, :secondary_value, :piece_justificative_file, value: [],
etablissement_attributes: Champs::SiretChamp::ETABLISSEMENT_ATTRIBUTES
]
})

View file

@ -35,6 +35,10 @@ class Champ < ApplicationRecord
end
end
def main_value_name
:value
end
private
def string_value

View file

@ -1,2 +1,36 @@
class Champs::LinkedDropDownListChamp < Champ
attr_reader :primary_value, :secondary_value
delegate :primary_options, :secondary_options, to: :type_de_champ
after_initialize :unpack_value
def unpack_value
if value.present?
primary, secondary = JSON.parse(value)
else
primary = secondary = ''
end
@primary_value ||= primary
@secondary_value ||= secondary
end
def primary_value=(value)
@primary_value = value
pack_value
end
def secondary_value=(value)
@secondary_value = value
pack_value
end
def main_value_name
:primary_value
end
private
def pack_value
self.value = JSON.generate([ primary_value, secondary_value ])
end
end

View file

@ -1,2 +1,31 @@
class TypesDeChamp::LinkedDropDownListTypeDeChamp < TypeDeChamp
PRIMARY_PATTERN = /^--(.*)--$/
def primary_options
primary_options = unpack_options.map(&:first)
if primary_options.present?
primary_options.unshift('')
end
primary_options
end
def secondary_options
secondary_options = unpack_options.to_h
if secondary_options.present?
secondary_options[''] = []
end
secondary_options
end
private
def unpack_options
_, *options = drop_down_list.options
chunked = options.slice_before(PRIMARY_PATTERN)
chunked.map do |chunk|
primary, *secondary = chunk
secondary.unshift('')
[PRIMARY_PATTERN.match(primary)[1], secondary]
end
end
end

View file

@ -1,4 +1,4 @@
= form.label :value do
= form.label champ.main_value_name do
#{champ.libelle}
- if champ.mandatory?
%span.mandatory *

View file

@ -0,0 +1,10 @@
- if champ.drop_down_list && champ.drop_down_list.options.any?
- champ_id = champ.object_id
= form.select :primary_value,
champ.primary_options,
{ required: champ.mandatory? },
{ data: { "secondary-options" => champ.secondary_options, "primary-id" => champ_id } }
= form.select :secondary_value,
champ.secondary_options[champ.primary_value],
{ required: champ.mandatory? },
{ data: { "secondary-id" => champ_id } }

View file

@ -0,0 +1,76 @@
require 'spec_helper'
feature 'linked dropdown lists' do
let(:password) { 'secret_password' }
let!(:user) { create(:user, password: password) }
let(:list_items) do
<<~END_OF_LIST
--Primary 1--
Secondary 1.1
Secondary 1.2
--Primary 2--
Secondary 2.1
Secondary 2.2
Secondary 2.3
END_OF_LIST
end
let(:drop_down_list) { create(:drop_down_list, value: list_items) }
let(:type_de_champ) { create(:type_de_champ_linked_drop_down_list, libelle: 'linked dropdown', drop_down_list: drop_down_list) }
let!(:procedure) do
p = create(:procedure, :published, :for_individual)
p.types_de_champ << type_de_champ
p
end
let(:user_dossier) { user.dossiers.first }
scenario 'change primary value, secondary options are updated', js: true do
log_in(user.email, password, procedure)
fill_individual
# Select a primary value
select('Primary 2', from: primary_id_for('linked dropdown'))
# Secondary menu reflects chosen primary value
expect(page).to have_select(secondary_id_for('linked dropdown'), options: ['', 'Secondary 2.1', 'Secondary 2.2', 'Secondary 2.3'])
# Select another primary value
select('Primary 1', from: primary_id_for('linked dropdown'))
# Secondary menu gets updated
expect(page).to have_select(secondary_id_for('linked dropdown'), options: ['', 'Secondary 1.1', 'Secondary 1.2'])
end
private
def log_in(email, password, procedure)
visit "/commencer/#{procedure.procedure_path.path}"
expect(page).to have_current_path(new_user_session_path)
fill_in 'user_email', with: email
fill_in 'user_password', with: password
click_on 'Se connecter'
expect(page).to have_current_path(identite_dossier_path(user_dossier))
end
def fill_individual
fill_in('individual_prenom', with: 'prenom')
fill_in('individual_nom', with: 'nom')
check 'dossier_autorisation_donnees'
click_on 'Continuer'
expect(page).to have_current_path(modifier_dossier_path(user_dossier))
end
def primary_id_for(libelle)
find(:xpath, ".//label[contains(text()[normalize-space()], '#{libelle}')]")[:for]
end
def secondary_id_for(libelle)
primary_id = primary_id_for(libelle)
link = find("\##{primary_id}")['data-primary-id']
find("[data-secondary-id=\"#{link}\"]")['id']
end
end

View file

@ -0,0 +1,18 @@
require 'spec_helper'
describe Champs::LinkedDropDownListChamp do
describe '#unpack_value' do
let(:champ) { described_class.new(value: '["tata", "tutu"]') }
it { expect(champ.primary_value).to eq('tata') }
it { expect(champ.secondary_value).to eq('tutu') }
end
describe '#pack_value' do
let(:champ) { described_class.new(primary_value: 'tata', secondary_value: 'tutu') }
before { champ.save }
it { expect(champ.value).to eq('["tata","tutu"]') }
end
end

View file

@ -0,0 +1,40 @@
require 'spec_helper'
describe TypesDeChamp::LinkedDropDownListTypeDeChamp do
describe '#unpack_options' do
let(:drop_down_list) { build(:drop_down_list, value: menu_options) }
let(:type_de_champ) { described_class.new(drop_down_list: drop_down_list) }
context 'with no options' do
let(:menu_options) { '' }
it { expect(type_de_champ.secondary_options).to eq({}) }
it { expect(type_de_champ.primary_options).to eq([]) }
end
context 'with two primary options' do
let(:menu_options) do
<<~END_OPTIONS
--Primary 1--
secondary 1.1
secondary 1.2
--Primary 2--
secondary 2.1
secondary 2.2
secondary 2.3
END_OPTIONS
end
it do
expect(type_de_champ.secondary_options).to eq(
{
'' => [],
'Primary 1' => [ '', 'secondary 1.1', 'secondary 1.2'],
'Primary 2' => [ '', 'secondary 2.1', 'secondary 2.2', 'secondary 2.3']
}
)
end
it { expect(type_de_champ.primary_options).to eq([ '', 'Primary 1', 'Primary 2' ]) }
end
end
end