Skip to content

Commit 9d34006

Browse files
authored
Merge pull request #2567 from mroderick/feature/meeting-organisers-tomselect
feat: replace Member.all with TomSelect for meeting organisers
2 parents 1d0031a + a2c4c9c commit 9d34006

5 files changed

Lines changed: 75 additions & 17 deletions

File tree

app/assets/javascripts/application.js

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,43 @@ $(function() {
9999
});
100100
}
101101

102+
// TomSelect for meeting organisers (multi-select)
103+
if ($('#meeting_organisers').length) {
104+
new TomSelect('#meeting_organisers', {
105+
plugins: ['remove_button'],
106+
placeholder: 'Type to search members...',
107+
valueField: 'id',
108+
labelField: 'full_name',
109+
searchField: ['full_name', 'email'],
110+
create: false,
111+
loadThrottle: 300,
112+
shouldLoad: function(query) {
113+
return query.length >= 3;
114+
},
115+
load: function(query, callback) {
116+
fetch('/admin/members/search?q=' + encodeURIComponent(query))
117+
.then(response => response.json())
118+
.then(json => callback(json))
119+
.catch(() => callback());
120+
},
121+
render: {
122+
option: function(item, escape) {
123+
return '<div>' + escape(item.full_name) + ' <small class="text-muted">' + escape(item.email) + '</small></div>';
124+
},
125+
no_results: function(data, escape) {
126+
return '<div class="no-results">No members found</div>';
127+
}
128+
}
129+
});
130+
}
131+
102132
// Chosen for all other selects (exclude TomSelect fields)
103133
// Chosen hides inputs and selects, which becomes problematic when they are
104134
// required: browser validation doesn't get shown to the user.
105135
// This fix places "the original input behind the Chosen input, matching the
106136
// height and width so that the warning appears in the correct position."
107137
// https://github.com/harvesthq/chosen/issues/515#issuecomment-474588057
108-
$('select').not('#member_lookup_id, #meeting_invitations_member').on('chosen:ready', function () {
138+
$('select').not('#member_lookup_id, #meeting_invitations_member, #meeting_organisers').on('chosen:ready', function () {
109139
var height = $(this).next('.chosen-container').height();
110140
var width = $(this).next('.chosen-container').width();
111141

@@ -117,7 +147,7 @@ $(function() {
117147
}).show();
118148
});
119149

120-
$('select').not('#member_lookup_id, #meeting_invitations_member').chosen({
150+
$('select').not('#member_lookup_id, #meeting_invitations_member, #meeting_organisers').chosen({
121151
allow_single_deselect: true,
122152
no_results_text: 'No results matched'
123153
});

app/controllers/admin/meetings_controller.rb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ def new
77

88
def create
99
@meeting = Meeting.new(meeting_params)
10-
set_organisers(organiser_ids)
11-
set_chapters(chapter_ids)
1210

1311
if @meeting.save
12+
set_organisers(organiser_ids)
13+
set_chapters(chapter_ids)
1414
redirect_to [:admin, @meeting], notice: t('admin.messages.meeting.created')
1515
else
1616
flash[:notice] = @meeting.errors.full_messages.join(', ')
@@ -62,10 +62,10 @@ def slug
6262
end
6363

6464
def meeting_params
65-
params.expect(meeting: [
66-
:name, :description, :slug, :date_and_time, :local_date, :local_time, :local_end_time,
67-
:invitable, :spaces, :venue_id, :sponsor_id, :chapters
68-
])
65+
params.expect(meeting: %i[
66+
name description slug date_and_time local_date local_time local_end_time
67+
invitable spaces venue_id sponsor_id chapters
68+
])
6969
end
7070

7171
def organiser_ids

app/views/admin/meetings/_form.html.haml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
- content_for :head do
2+
%link{ href: 'https://cdn.jsdelivr.net/npm/tom-select@2.4.3/dist/css/tom-select.bootstrap5.min.css', rel: 'stylesheet', type: 'text/css' }
3+
%script{ src: 'https://cdn.jsdelivr.net/npm/tom-select@2.4.3/dist/js/tom-select.complete.min.js' }
4+
15
= simple_form_for [:admin, @meeting] do |f|
26
.row
37
.col-12
@@ -17,7 +21,11 @@
1721
.col-12
1822
= f.association :venue, input_html: { data: { placeholder: 'Select venue' }}, required: true
1923
.col-12
20-
= f.input :organisers, collection: Member.all, label_method: :full_name, value_method: :id, selected: @meeting.organisers.map(&:id), input_html: { multiple: true }
24+
= f.label :organisers
25+
= f.select :organisers,
26+
options_for_select(@meeting.organisers.map { |o| [o.full_name, o.id] }, @meeting.organisers.map(&:id)),
27+
{ include_blank: false },
28+
{ multiple: true, class: 'tom-select', data: { placeholder: 'Type to search members...' } }
2129
.col-12
2230
= f.input :chapters, collection: Chapter.all, label_method: :name, value_method: :id, selected: @meeting.chapters.map(&:id), input_html: { multiple: true }
2331
.col-12

spec/features/admin/meeting_spec.rb

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
click_on 'Save'
2222

2323
expect(page).to have_content('Meeting successfully created')
24-
expect(page.current_path)
25-
.to eq(admin_meeting_path("#{I18n.l(today, format: :year_month).downcase}-august-meeting-1"))
24+
expect(page)
25+
.to have_current_path(admin_meeting_path("#{I18n.l(today, format: :year_month).downcase}-august-meeting-1"), ignore_query: true)
2626
expect(page).to have_content 'Invite'
2727
end
2828

@@ -48,18 +48,30 @@
4848
expect(page).to have_content('Slug has already been taken')
4949
end
5050

51-
scenario 'successfully' do
51+
scenario 'successfully', :js do
5252
permissions = Fabricate(:permission, resource: meeting, name: 'organiser')
5353

5454
visit edit_admin_meeting_path(meeting)
55-
fill_in 'Name', with: "March Meeting"
56-
unselect permissions.members.first.full_name
55+
fill_in 'Name', with: 'March Meeting'
56+
remove_from_tom_select(permissions.members.first.full_name)
5757

5858
click_on 'Save'
5959

6060
expect(page).to have_content('You have successfully updated the details of this meeting')
6161
expect(page).to have_css(%(span[title="#{permissions.members.last.full_name}"]))
62-
expect(page).to_not have_css(%(span[title="#{permissions.members.first.full_name}"]))
62+
expect(page).not_to have_css(%(span[title="#{permissions.members.first.full_name}"]))
63+
end
64+
65+
scenario 'adding an organiser', :js do
66+
meeting = Fabricate(:meeting)
67+
new_organiser = Fabricate(:member)
68+
69+
visit edit_admin_meeting_path(meeting)
70+
select_from_tom_select(new_organiser.full_name, from: 'meeting_organisers')
71+
72+
click_on 'Save'
73+
74+
expect(page).to have_css(%(span[title="#{new_organiser.full_name}"]))
6375
end
6476
end
6577

@@ -78,7 +90,7 @@
7890
scenario 'when no format is used then it redirects to the meeting page' do
7991
visit attendees_emails_admin_meeting_path(meeting)
8092

81-
expect(page.current_path).to eq(admin_meeting_path(meeting))
93+
expect(page).to have_current_path(admin_meeting_path(meeting), ignore_query: true)
8294
end
8395
end
8496

@@ -88,7 +100,7 @@
88100
meeting = Fabricate(:meeting, chapters: [chapter])
89101

90102
visit invite_admin_meeting_path(meeting)
91-
expect(page).to have_content("Invitations are being sent out")
103+
expect(page).to have_content('Invitations are being sent out')
92104
end
93105

94106
scenario 'does not send the invitations to banned members' do

spec/support/select_from_tom_select.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ def select_from_tom_select(item_text, from: nil)
2929
# Click the matching option
3030
find('.ts-dropdown .option', text: item_text, match: :prefer_exact).click
3131
end
32+
33+
# Remove an item from a TomSelect multi-select
34+
# @param item_text [String] The text of the item to remove (must match exactly)
35+
def remove_from_tom_select(item_text)
36+
within '.ts-wrapper' do
37+
find('.item', text: item_text, match: :prefer_exact).find('.remove').click
38+
end
39+
end
3240
end
3341

3442
RSpec.configure do |config|

0 commit comments

Comments
 (0)