Skip to content

Commit

Permalink
Use tomselect instead of select2 (some todos left)
Browse files Browse the repository at this point in the history
  • Loading branch information
he3lixxx committed May 13, 2022
1 parent cd58c8e commit 3eae208
Show file tree
Hide file tree
Showing 22 changed files with 445 additions and 145 deletions.
2 changes: 1 addition & 1 deletion LICENSE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

The software is licensed under the MIT license. The source code includes other
components in whole or in part; namely Bootstrap Datepicker, Font Awesome,
jQuery, jQuery Formset, Moment, Select2, Sisyphus, Sortable.
jQuery, jQuery Formset, Moment, Sisyphus, Sortable.
These components are used under the MIT resp. SIL OFL licenses.

The source repository may include logos, names or other trademarks of the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ <h3>
{{ evaluation.full_name }} ({{ evaluation.course.semester.name }})
</h3>

<form method="POST" class="form-horizontal multiselect-form select2form" id="evaluation-form">
<form method="POST" class="form-horizontal multiselect-form tomselectform" id="evaluation-form">
{% csrf_token %}
<div class="card mb-3">
<div class="card-body">
Expand Down
3 changes: 0 additions & 3 deletions evap/contributor/templates/contributor_index.html
Original file line number Diff line number Diff line change
Expand Up @@ -250,9 +250,6 @@ <h5 class="modal-title" id="{{ modal_id }}Label">{% trans 'Delegate preparation'
// put the correct evaluation name in the modal
$('#{{ modal_id }} [data-label=""]').text(evaluationName);

// fix incorrect select2 width calculation that occurrs because the select2 is applied while the modal is invisible
$('#{{ modal_id }} span.select2').width("100%");

// unselect any previously selected options in the modal
$('#{{ modal_id }} select').val(null).trigger('change');

Expand Down
2 changes: 1 addition & 1 deletion evap/evaluation/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def label_from_instance(self, obj):


class UserModelMultipleChoiceField(forms.ModelMultipleChoiceField):
widget = forms.SelectMultiple(attrs={"data-selection-css-class": "user-multi-select"})
widget = forms.SelectMultiple(attrs={"data-tomselect-fullwidth": ""})

def label_from_instance(self, obj):
return obj.full_name_with_additional_info
Expand Down
81 changes: 54 additions & 27 deletions evap/evaluation/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<title>{% block title %}EvaP{% endblock %}</title>

<link rel="stylesheet" href="{% static 'css/select2.min.css' %}" />
<link rel="stylesheet" href="{% static 'css/tom-select.bootstrap5.min.css' %}" />
<link rel="stylesheet" href="{% static 'css/bootstrap-datetimepicker.min.css' %}" />
<link rel="stylesheet" href="{% static 'css/evap.css' %}" />
{% if debug %}
Expand Down Expand Up @@ -62,9 +62,7 @@
{% include 'footer.html' %}

<script type="text/javascript" src="{% static 'js/jquery-2.1.3.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/select2.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/locale/select2_de.js' %}"></script>
<script type="text/javascript" src="{% static 'js/locale/select2_en.js' %}"></script>
<script type="text/javascript" src="{% static 'js/tom-select.complete.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/plugins/jquery.formset.js' %}"></script>
<script type="text/javascript" src="{% static 'js/Sortable.min.js' %}"></script>

Expand All @@ -85,41 +83,70 @@
};
activateTooltips();

// These are workarounds around incompatibilities of django, django-dynamic-formsets, and select2.
// select2 can't handle already select2'd forms that were copies made by
// django-dynamic-formsets' "add another" button, so we take a copy of a non-select2'd form here
var templateForm = $(".select2tr").last().clone();
// # TODO: Still necessary?
// These are workarounds around incompatibilities of django, django-dynamic-formsets, and tomselect.
// tomselect can't handle already tomselect'd forms that were copies made by
// django-dynamic-formsets' "add another" button, so we take a copy of a non-tomselect'd form here
var templateForm = $(".tomselecttr").last().clone();

// later we use this class to give this to django-dynamic-formsets
// as template and to make sure it does not get select2'd
// as template and to make sure it does not get tomselect'd
templateForm.addClass("form-template").css("display", "none");
// for some reason django-dynamic-formsets does not hide the checkbox like it should
templateForm.find(':checkbox').last().attr("type", "hidden");
// For some reason, django validates this template if it's part of the form, so we insert the copy outside of the form.
templateForm.insertAfter($('.select2form'));
templateForm.insertAfter($('.tomselectform'));

// put this into a function for later re-use
applySelect2 = function(elem, parent = $(document.body)) {
elem.each(function() {
$(this).select2({
language: "{{ LANGUAGE_CODE }}",
// used in derived templates
const tagTomSelectOptions = {
create: true,
dropdownParent: document.createElement("div"),
};

applyTomSelect = function(elements, additionalOptions = {}) {
elements.forEach(function(element) {
if(element.tomselect) {
element.tomselect.destroy();
}
element.classList.remove("form-select"); // TomSelect applies their own matching classes / styles
if(element.parentElement.classList.contains("is-invalid")) { // hotfix for our wrongly placed is-invalid
element.parentElement.classList.remove("is-invalid");
element.classList.add("is-invalid");
}
const baseOptions = {
createOnBlur: true,
placeholder: "{% trans 'Please select...' %}",
minimumResultsForSearch: 15,
minimumInputLength: $(this).children('option').length > 50 ? 3 : 0,
allowClear: this.multiple,
dropdownParent: parent
});
hidePlaceholder: true,
render: {
option_create: function( data, escape ){return '<div class="create">' + escape(data.input) + '</div>';},
no_results: function( data, escape ){return '<div class="no-results">{% trans "No results found" %}</div>';},
},
plugins: {
// "dropdown_input": {}, // # TODO: Do we want this? I think both are a bit weird, with and without
},
};
if(element.hasAttribute("data-tomselect-fullwidth")) {
baseOptions.render.item = function(data, escape) {return '<div class="w-100">' + escape(data.text) + '</div>';};
}
if(element.multiple) {
baseOptions.plugins.clear_button = {"title": "{% trans 'Remove all items' %}"};
baseOptions.plugins.remove_button = {"title": "{% trans 'Remove this item' %}"};
}
if(element.getElementsByTagName("option").length > 50) {
// # TODO: This doesn't work
baseOptions.shouldLoad = function(query) {return query.length >= 3;};
}
new TomSelect(element, Object.assign({}, baseOptions, additionalOptions));
});
};
applySelect2($('select').not('.form-template select').not('[disabled]').not('.no-select2').not('#id_delegate_to'));
// handle delegation modal, which includes a dropdown select field
applySelect2($('select#id_delegate_to'), $('#delegateSelectionModal'));
applyTomSelect(document.querySelectorAll("select:not(.form-template select):not(.no-tomselect)"));

// don't use the placeholder text on disabled select2 elements
applySelect2Disabled = function(elem) {
elem.select2();
};
applySelect2Disabled($('select[disabled]').not('.form-template select'));
// # TODO
// applySelect2Disabled = function(elem) {
// elem.select2();
// };
// applySelect2Disabled($('select[disabled]').not('.form-template select'));

// fix Bootstrap bug that selecting toggle buttons via keyboard controls is only visually
// https://github.com/twbs/bootstrap/issues/26855
Expand Down
2 changes: 1 addition & 1 deletion evap/evaluation/templates/contribution_formset.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ <h5 class="card-title me-auto">{% trans 'Contributors' %}</h5>
<tbody>
{% for form in formset %}
{% include 'bootstrap_form_errors.html' with errors=form.non_field_errors %}
<tr class="contribution select2tr{% if editable %} sortable{% endif %}">
<tr class="contribution tomselecttr{% if editable %} sortable{% endif %}">
<td class="movable">
{% if editable %}<span class="movable-icon fas fa-up-down"></span>{% endif %}
</td>
Expand Down
2 changes: 1 addition & 1 deletion evap/evaluation/templates/evap_evaluation_edit_js.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

rowAdded = function(row) {
assignClickHandlers();
applySelect2(row.find("select"));
applyTomSelect(row.find("select").get());
activateTooltips('#contribution_table');
}
makeFormSortable("contribution_table", "contributions", rowChanged, rowAdded, "pointer", false, true);
Expand Down
2 changes: 1 addition & 1 deletion evap/results/templates/results_index.html
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ <h5 class="card-title mb-lg-0">
<span class="fas fa-arrow-up-z-a-alt"></span>
</label>
</div>
<select name="result-sort-column" class="form-select no-select2">
<select name="result-sort-column" class="form-select no-tomselect">
<option value="name-semester">{% trans 'Evaluation and semester' %}</option>
<option value="name">{% trans 'Evaluation' %}</option>
<option value="semester">{% trans 'Semester' %}</option>
Expand Down
2 changes: 1 addition & 1 deletion evap/staff/templates/staff_course_copyform.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<h3>
{% trans 'Create copy of course' %}
</h3>
<form id="course-form" method="POST" class="form-horizontal multiselect-form select2form">
<form id="course-form" method="POST" class="form-horizontal multiselect-form tomselectform">
{% csrf_token %}

<div class="card mb-3">
Expand Down
2 changes: 1 addition & 1 deletion evap/staff/templates/staff_course_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ <h3>
{% trans 'Create course' %}
{% endif %}
</h3>
<form id="course-form" method="POST" class="form-horizontal multiselect-form select2form">
<form id="course-form" method="POST" class="form-horizontal multiselect-form tomselectform">
{% csrf_token %}

<div class="card mb-3">
Expand Down
21 changes: 4 additions & 17 deletions evap/staff/templates/staff_course_type_index.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<a href="{% url 'staff:course_type_merge_selection' %}" class="btn btn-sm btn-secondary">{% trans 'Merge course types' %}</a>
</div>

<form id="course-type-form" method="POST" enctype="multipart/form-data" class="form-horizontal select2form">
<form id="course-type-form" method="POST" enctype="multipart/form-data" class="form-horizontal tomselectform">
{% csrf_token %}
{{ formset.management_form }}

Expand All @@ -30,7 +30,7 @@
</thead>
<tbody>
{% for form in formset %}
<tr class="select2tr sortable">
<tr class="tomselecttr sortable">
<td class="movable">
<span class="movable-icon fas fa-up-down"></span>
</td>
Expand Down Expand Up @@ -80,22 +80,9 @@
return nameDe || nameEn
};
rowAdded = function(row) {
applySelect2(row.find("select"));
applyTomSelect(row.find("select").get(), tagTomSelectOptions);
};
makeFormSortable("course_types_table", "form", rowChanged, rowAdded, "", true, true);

function applySelect2(element) {
element.select2({
language: "{{ LANGUAGE_CODE }}",
placeholder: "{% trans 'Add items...' %}",
tags: true,
// Disable search so that new items can be created instead of using an existing suggestion
matcher: () => null,
// Use a detached container to hide the dropdown
dropdownParent: $("<div>"),
});
}

applySelect2($("select").not('.form-template select'));
applyTomSelect(document.querySelectorAll("select"), tagTomSelectOptions);
</script>
{% endblock %}
20 changes: 4 additions & 16 deletions evap/staff/templates/staff_degree_index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
{% block content %}
{{ block.super }}

<form id="degree-form" method="POST" enctype="multipart/form-data" class="form-horizontal select2form">
<form id="degree-form" method="POST" enctype="multipart/form-data" class="form-horizontal tomselectform">
{% csrf_token %}
{{ formset.management_form }}

Expand All @@ -26,7 +26,7 @@
</thead>
<tbody>
{% for form in formset %}
<tr class="select2tr sortable">
<tr class="tomselecttr sortable">
<td class="movable">
<span class="movable-icon fas fa-up-down"></span>
</td>
Expand Down Expand Up @@ -76,22 +76,10 @@
return nameDe || nameEn
};
rowAdded = function(row) {
applySelect2(row.find("select"));
applyTomSelect(row.find("select").get(), tagTomSelectOptions);
};
makeFormSortable("degree_table", "form", rowChanged, rowAdded, "", true, true);

function applySelect2(element) {
element.select2({
language: "{{ LANGUAGE_CODE }}",
placeholder: "{% trans 'Add items...' %}",
tags: true,
// Disable search so that new items can be created instead of using an existing suggestion
matcher: () => null,
// Use a detached container to hide the dropdown
dropdownParent: $("<div>"),
});
}

applySelect2($("select").not('.form-template select'));
applyTomSelect(document.querySelectorAll("select"), tagTomSelectOptions);
</script>
{% endblock %}
2 changes: 1 addition & 1 deletion evap/staff/templates/staff_evaluation_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ <h3>
{% include 'course_badges.html' with course=evaluation.course %}
</div>
{% endif %}
<form id="evaluation-form" method="POST" class="form-horizontal multiselect-form select2form">
<form id="evaluation-form" method="POST" class="form-horizontal multiselect-form tomselectform">
{% csrf_token %}

<div class="card mb-3">
Expand Down
6 changes: 3 additions & 3 deletions evap/staff/templates/staff_questionnaire_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
{% trans 'Some fields are disabled as this questionnaire is already in use.' %}
</div>
{% endif %}
<form id="questionnaire-form" method="POST" class="form-horizontal select2form">
<form id="questionnaire-form" method="POST" class="form-horizontal tomselectform">
{% csrf_token %}
<fieldset>
<div class="card mb-3">
Expand Down Expand Up @@ -38,7 +38,7 @@ <h5 class="card-title">{% trans 'Questions' %}</h5>
{% for form_element in formset %}
{% include 'bootstrap_form_errors.html' with errors=form_element.non_field_errors %}

<tr class="select2tr sortable">
<tr class="tomselecttr sortable">
{% for hidden in form_element.hidden_fields %}
{{ hidden }}
{% endfor %}
Expand Down Expand Up @@ -88,7 +88,7 @@ <h5 class="card-title">{% trans 'Questions' %}</h5>
};
rowAdded = function(row) {
row.find('a.btn-secondary').remove();
applySelect2(row.find("select"));
applyTomSelect(row.find("select").get());
};
makeFormSortable("question_table", "questions", rowChanged, rowAdded, "", true, true);

Expand Down
20 changes: 4 additions & 16 deletions evap/staff/templates/staff_text_answer_warnings.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
{% block content %}
{{ block.super }}

<form id="text-answer-warnings-form" method="POST" class="form-horizontal select2form">
<form id="text-answer-warnings-form" method="POST" class="form-horizontal tomselectform">
{% csrf_token %}
{{ formset.management_form }}

Expand All @@ -36,7 +36,7 @@
</thead>
<tbody>
{% for form in formset %}
<tr class="select2tr sortable">
<tr class="tomselecttr sortable">
<td class="movable">
<span class="movable-icon fas fa-up-down"></span>
</td>
Expand Down Expand Up @@ -109,22 +109,10 @@
return triggerStrings || warningTextDe || warningTextEn;
};
const rowAdded = function(row) {
applySelect2(row.find("select"));
applyTomSelect(row.find("select").get(), tagTomSelectOptions);
};
makeFormSortable("text-answer-warnings-table", "form", rowChanged, rowAdded, "", true, true);

function applySelect2(element) {
element.select2({
language: "{{ LANGUAGE_CODE }}",
placeholder: "{% trans 'Add items...' %}",
tags: true,
// Disable search so that new items can be created instead of using an existing suggestion
matcher: () => null,
// Use a detached container to hide the dropdown
dropdownParent: $("<div>"),
});
}

applySelect2($("select").not('.form-template select'));
applyTomSelect(document.querySelectorAll("select"), tagTomSelectOptions);
</script>
{% endblock %}
1 change: 0 additions & 1 deletion evap/static/css/select2.min.css

This file was deleted.

Loading

0 comments on commit 3eae208

Please sign in to comment.