diff options
Diffstat (limited to '')
| -rw-r--r-- | pysite/constants.py | 1 | ||||
| -rw-r--r-- | pysite/views/staff/jams/actions.py | 29 | ||||
| -rw-r--r-- | pysite/views/staff/jams/forms/__init__.py | 0 | ||||
| -rw-r--r-- | pysite/views/staff/jams/forms/questions_edit.py | 75 | ||||
| -rw-r--r-- | pysite/views/staff/jams/forms/questions_view.py | 24 | ||||
| -rw-r--r-- | pysite/views/staff/jams/forms/view.py | 44 | ||||
| -rw-r--r-- | static/js/jams.js | 11 | ||||
| -rw-r--r-- | templates/staff/jams/forms/questions_edit.html | 357 | ||||
| -rw-r--r-- | templates/staff/jams/forms/questions_view.html | 596 | ||||
| -rw-r--r-- | templates/staff/jams/forms/view.html | 757 | ||||
| -rw-r--r-- | templates/staff/jams/index.html | 1 | 
11 files changed, 1895 insertions, 0 deletions
| diff --git a/pysite/constants.py b/pysite/constants.py index e30ed10b..f4ea8449 100644 --- a/pysite/constants.py +++ b/pysite/constants.py @@ -76,6 +76,7 @@ JAM_STATES = [  ]  JAM_QUESTION_TYPES = [ +    "checkbox",      "email",      "number",      "radio", diff --git a/pysite/views/staff/jams/actions.py b/pysite/views/staff/jams/actions.py index 9aa7e79f..3683db39 100644 --- a/pysite/views/staff/jams/actions.py +++ b/pysite/views/staff/jams/actions.py @@ -7,6 +7,7 @@ from pysite.mixins import DBMixin  GET_ACTIONS = ["questions"]  POST_ACTIONS = ["associate_question", "disassociate_question", "questions", "state"] +DELETE_ACTIONS = ["question"]  KEYS = ["action"]  QUESTION_KEYS = ["optional", "title", "type"] @@ -166,3 +167,31 @@ class ActionView(APIView, DBMixin):                  )              return jsonify({"id": result["generated_keys"][0]}) + +    @csrf +    @require_roles(*ALL_STAFF_ROLES) +    def delete(self): +        action = request.args.get("action") + +        if action not in DELETE_ACTIONS: +            return self.error(ErrorCodes.incorrect_parameters) + +        if action == "question": +            question = request.args.get("id") + +            if not question: +                return self.error(ErrorCodes.incorrect_parameters, f"Missing key: id") + +            question_obj = self.db.get(self.questions_table, question) + +            if not question_obj: +                return self.error(ErrorCodes.incorrect_parameters, f"Unknown question: {question}") + +            self.db.delete(self.questions_table, question) + +            for form_obj in self.db.get_all(self.forms_table): +                if question in form_obj["questions"]: +                    form_obj["questions"].remove(question) +                    self.db.insert(self.forms_table, form_obj, conflict="replace") + +            return jsonify({"id": question}) diff --git a/pysite/views/staff/jams/forms/__init__.py b/pysite/views/staff/jams/forms/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/pysite/views/staff/jams/forms/__init__.py diff --git a/pysite/views/staff/jams/forms/questions_edit.py b/pysite/views/staff/jams/forms/questions_edit.py new file mode 100644 index 00000000..94132e0d --- /dev/null +++ b/pysite/views/staff/jams/forms/questions_edit.py @@ -0,0 +1,75 @@ +import json + +from flask import request, redirect, url_for +from werkzeug.exceptions import NotFound, BadRequest + +from pysite.base_route import RouteView +from pysite.constants import ALL_STAFF_ROLES +from pysite.decorators import require_roles, csrf +from pysite.mixins import DBMixin + +REQUIRED_KEYS = ["title", "date_start", "date_end"] + + +class StaffView(RouteView, DBMixin): +    path = "/jams/forms/questions/<question>" +    name = "jams.forms.questions.edit" + +    questions_table = "code_jam_questions" + +    @require_roles(*ALL_STAFF_ROLES) +    def get(self, question): +        question_obj = self.db.get(self.questions_table, question) + +        if not question_obj: +            return NotFound() + +        return self.render( +            "staff/jams/forms/questions_edit.html", question=question_obj +        ) + +    @require_roles(*ALL_STAFF_ROLES) +    @csrf +    def post(self, question): +        question_obj = self.db.get(self.questions_table, question) + +        if not question_obj: +            return NotFound() + +        title = request.form.get("title") +        optional = request.form.get("optional") +        question_type = request.form.get("type") + +        print(question_type) + +        if not title or not optional or not question_type: +            return BadRequest() + +        question_obj["title"] = title +        question_obj["optional"] = optional == "optional" +        question_obj["type"] = question_type + +        if question_type == "radio": +            options = request.form.get("options") + +            if not options: +                return BadRequest() + +            options = json.loads(options)["options"]  # No choice this time +            question_obj["data"] = {"options": options} + +        elif question_type in ("number", "range", "slider"): +            question_min = request.form.get("min") +            question_max = request.form.get("max") + +            if question_min is None or question_max is None: +                return BadRequest() + +            question_obj["data"] = { +                "min": question_min, +                "max": question_max +            } + +        self.db.insert(self.questions_table, question_obj, conflict="replace") + +        return redirect(url_for("staff.jams.forms.questions")) diff --git a/pysite/views/staff/jams/forms/questions_view.py b/pysite/views/staff/jams/forms/questions_view.py new file mode 100644 index 00000000..00331810 --- /dev/null +++ b/pysite/views/staff/jams/forms/questions_view.py @@ -0,0 +1,24 @@ +from werkzeug.exceptions import NotFound + +from pysite.base_route import RouteView +from pysite.constants import ALL_STAFF_ROLES +from pysite.decorators import require_roles +from pysite.mixins import DBMixin + +REQUIRED_KEYS = ["title", "date_start", "date_end"] + + +class StaffView(RouteView, DBMixin): +    path = "/jams/forms/questions" +    name = "jams.forms.questions" + +    questions_table = "code_jam_questions" + +    @require_roles(*ALL_STAFF_ROLES) +    def get(self): +        questions = self.db.get_all(self.questions_table) + +        return self.render( +            "staff/jams/forms/questions_view.html", questions=questions, +            question_ids=[q["id"] for q in questions] +        ) diff --git a/pysite/views/staff/jams/forms/view.py b/pysite/views/staff/jams/forms/view.py new file mode 100644 index 00000000..eaa910f2 --- /dev/null +++ b/pysite/views/staff/jams/forms/view.py @@ -0,0 +1,44 @@ +from werkzeug.exceptions import NotFound + +from pysite.base_route import RouteView +from pysite.constants import ALL_STAFF_ROLES +from pysite.decorators import require_roles +from pysite.mixins import DBMixin + +REQUIRED_KEYS = ["title", "date_start", "date_end"] + + +class StaffView(RouteView, DBMixin): +    path = "/jams/forms/<int:jam>" +    name = "jams.forms.view" + +    table_name = "code_jams" +    forms_table = "code_jam_forms" +    questions_table = "code_jam_questions" + +    @require_roles(*ALL_STAFF_ROLES) +    def get(self, jam): +        jam_obj = self.db.get(self.table_name, jam) + +        if not jam_obj: +            return NotFound() + +        form_obj = self.db.get(self.forms_table, jam) + +        if not form_obj: +            form_obj = { +                "number": jam, +                "questions": [] +            } + +            self.db.insert(self.forms_table, form_obj) + +        if form_obj["questions"]: +            questions = self.db.get_all(self.questions_table, *[q for q in form_obj["questions"]]) +        else: +            questions = [] + +        return self.render( +            "staff/jams/forms/view.html", jam=jam_obj, form=form_obj, +            questions=questions, question_ids=[q["id"] for q in questions] +        ) diff --git a/static/js/jams.js b/static/js/jams.js index b2d5b1bd..37d8a764 100644 --- a/static/js/jams.js +++ b/static/js/jams.js @@ -139,6 +139,17 @@ class Actions {          )      } +    delete_question(id, callback) { +        this.send( +            "question", +            "DELETE", +            { +                "id": id +            }, +            callback +        ) +    } +      associate_question(form, question, callback) {          this.send(              "associate_question", diff --git a/templates/staff/jams/forms/questions_edit.html b/templates/staff/jams/forms/questions_edit.html new file mode 100644 index 00000000..af143540 --- /dev/null +++ b/templates/staff/jams/forms/questions_edit.html @@ -0,0 +1,357 @@ +{% extends "main/base.html" %} +{% block title %}Staff | Jams | Question Edit{% endblock %} +{% block og_title %}Staff | Jams | Question Edit{% endblock %} +{% block og_description %}Edit a question{% endblock %} +{% block extra_head %} +    <script src="{{ static_file('js/jams.js') }}"></script> +{% endblock %} +{% block content %} +    <div class="uk-container uk-container-small uk-section"> +        <h1>Question Edit: {{ question.id }}</h1> + +        <form class="uk-form-horizontal" method="post" action="{{ url_for("staff.jams.forms.questions.edit", question=question.id) }}"> +            <div> +                <div class="uk-form-label"> +                    <label class="uk-form-label" for="title">Title</label> +                </div> +                <div class="uk-form-controls-text uk-form-controls"> +                    <input type="text" id="title" name="title" class="uk-input" value="{{ question.title }}"> +                </div> +            </div> +            <div> +                <div class="uk-form-label"> +                    <label class="uk-form-label" for="optional">Optional</label> +                </div> +                <div class="uk-form-controls-text uk-form-controls"> +                    <select class="uk-select" id="optional" name="optional"> +                        {% if question.optional %} +                            <option value="optional" selected>Optional</option> +                            <option value="required">Required</option> +                        {% else %} +                            <option value="optional">Optional</option> +                            <option value="required" selected>Required</option> +                        {% endif %} +                    </select> +                </div> +            </div> +            <div> +                <div class="uk-form-label"> +                    <label class="uk-form-label" for="type">Type</label> +                </div> +                <div class="uk-form-controls-text uk-form-controls"> +                    <select class="uk-select" id="type" name="type"> +                        {% if question.type == "checkbox" %} +                            <option value="checkbox" selected>Checkbox</option> +                        {% else %} +                            <option value="checkbox">Checkbox</option> +                        {% endif %} + +                        {% if question.type == "email" %} +                            <option value="email" selected>Email</option> +                        {% else %} +                            <option value="email">Email</option> +                        {% endif %} + +                        {% if question.type == "number" %} +                            <option value="number" selected>Number</option> +                        {% else %} +                            <option value="number">Number</option> +                        {% endif %} + +                        {% if question.type == "radio" %} +                            <option value="radio" selected>Radio</option> +                        {% else %} +                            <option value="radio">Radio</option> +                        {% endif %} + +                        {% if question.type == "range" %} +                            <option value="range" selected>Range</option> +                        {% else %} +                            <option value="range">Range</option> +                        {% endif %} + +                        {% if question.type == "slider" %} +                            <option value="slider" selected>Slider</option> +                        {% else %} +                            <option value="slider">Slider</option> +                        {% endif %} + +                        {% if question.type == "textarea" %} +                            <option value="textarea" selected>Text (Block)</option> +                        {% else %} +                            <option value="textarea">Text (Block)</option> +                        {% endif %} + +                        {% if question.type == "text" %} +                            <option value="text" selected>Text (Line)</option> +                        {% else %} +                            <option value="text">Text (Line)</option> +                        {% endif %} +                    </select> +                </div> +            </div> + +            {% if question.type == "radio" %} +                <div id="radio-section"> +            {% else %} +                <div id="radio-section" hidden="hidden"> +            {% endif %} +                <br /> +                <div> +                    <div class="uk-form-label"> +                        <button type="button" class="uk-button uk-button-primary uk-width-1-1" id="radio-add-button"><i class="uk-icon fa-fw far fa-plus"></i>  Add</button> +                    </div> +                    <div class="uk-form-controls-text uk-form-controls"> +                        <input type="text" id="radio-add-input" class="uk-input" placeholder="Item"> +                    </div> +                </div> +                <div> +                    <div class="uk-form-label"> +                        <button type="button" class="uk-button uk-button-danger uk-width-1-1" id="radio-remove-button"><i class="uk-icon fa-fw far fa-minus"></i>  Remove</button> +                    </div> +                    <div class="uk-form-controls-text uk-form-controls"> +                        <select class="uk-select" id="radio-options"> +                            <option hidden="hidden" disabled selected value="none"></option> + +                            {% for option in question.data.options %} +                            <option value="{{ option }}">{{ option }}</option> +                            {% endfor %} +                        </select> +                        {% if question.data.options %} +                            <input type="hidden" id="options" name="options" value="{{ "{\"options\": " + question.data.options.__str__() + "}" | safe }}"> +                        {% else %} +                            <input type="hidden" id="options" name="options" value="{{ "{\"options\": []}" }}"> +                        {% endif %} +                    </div> +                </div> +            </div> + +            {% if question.type in ["number", "range", "slider"] %} +            <div id="number-section"> +            {% else %} +            <div id="number-section" hidden="hidden"> +            {% endif %} +                <br /> +                <div> +                    <div class="uk-form-label"> +                        <label class="uk-form-label" for="min">Min Value</label> +                    </div> +                    <div class="uk-form-controls-text uk-form-controls"> +                        <input type="text" id="min" name="min" class="uk-input" value="{{ question.data.min }}"> +                    </div> +                </div> +                <div> +                    <div class="uk-form-label"> +                        <label class="uk-form-label" for="max">Max Value</label> +                    </div> +                    <div class="uk-form-controls-text uk-form-controls"> +                        <input type="text" id="max" name="max" class="uk-input" value="{{ question.data.max }}"> +                    </div> +                </div> +            </div> + +            <br /> + +            <div> +                <input type="hidden" name="csrf_token" id="csrf_token" value="{{ csrf_token() }}"/> + +                <a class="uk-button uk-button-danger uk-modal-close" href="{{ url_for("staff.jams.forms.questions") }}"> +                    <i class="uk-icon fa-fw far fa-times"></i>  Cancel +                </a> +                <button class="uk-button uk-button-primary" type="submit" id="question-submit"> +                    <i class="uk-icon fa-fw far fa-check"></i>  Save +                </button> +            </div> +        </form> +    </div> + +    <script type="application/javascript"> +        "use strict"; +        const new_question_title = document.getElementById("title"); +        const new_question_optional = document.getElementById("optional"); +        const new_question_type = document.getElementById("type"); + +        const radio_section = document.getElementById("radio-section"); + +        const radio_add_button = document.getElementById("radio-add-button"); +        const radio_add_input = document.getElementById("radio-add-input"); +        const radio_remove_button = document.getElementById("radio-remove-button"); +        const radio_options = document.getElementById("radio-options"); +        const hidden_radio_options = document.getElementById("options"); + +        const number_section = document.getElementById("number-section"); + +        const number_min = document.getElementById("min"); +        const number_max = document.getElementById("max"); + +        const submit_button = document.getElementById("question-submit"); + +        let current_radio_options; + +        {% if question.data.options %} +            current_radio_options = {{ question.data.options | safe }}; +        {% else %} +            current_radio_options = Array(); +        {% endif %} + +        new_question_type.onchange = function() { +            if (this.value === "checkbox") { +                radio_section.setAttribute("hidden", "hidden"); +                number_section.setAttribute("hidden", "hidden"); +            } else if (this.value === "email") { +                radio_section.setAttribute("hidden", "hidden"); +                number_section.setAttribute("hidden", "hidden"); +            } else if (this.value === "number") { +                radio_section.setAttribute("hidden", "hidden"); +                number_section.removeAttribute("hidden"); +            } else if (this.value === "radio") { +                radio_section.removeAttribute("hidden"); +                number_section.setAttribute("hidden", "hidden"); +            } else if (this.value === "range") { +                radio_section.setAttribute("hidden", "hidden"); +                number_section.removeAttribute("hidden"); +            } else if (this.value === "slider") { +                radio_section.setAttribute("hidden", "hidden"); +                number_section.removeAttribute("hidden"); +            } else if (this.value === "textarea") { +                radio_section.setAttribute("hidden", "hidden"); +                number_section.setAttribute("hidden", "hidden"); +            } else if (this.value === "text") { +                radio_section.setAttribute("hidden", "hidden"); +                number_section.setAttribute("hidden", "hidden"); +            } else { +                radio_section.setAttribute("hidden", "hidden"); +                number_section.setAttribute("hidden", "hidden"); +            } + +            checkValid(); +        }; + +        new_question_title.oninput = checkValid; +        new_question_optional.onchange = checkValid; + +        radio_add_input.onkeyup = function(event) { +            event.preventDefault(); + +            if (event.which === 13 || event.keyCode === 13) { +                radio_add_button.onclick(undefined); +            } +        }; + +        radio_add_button.onclick = function() { +            let value = radio_add_input.value; + +            if (value.length < 1) { +                radio_add_input.classList.add("uk-form-danger"); +                radio_add_input.focus(); +            } else { +                let index = current_radio_options.indexOf(value); + +                if (index > -1 || value === "none") { +                    radio_add_input.classList.add("uk-form-danger"); +                    radio_add_input.focus(); +                } else { +                    radio_add_input.classList.remove("uk-form-danger"); +                    radio_add_input.value = ""; + +                    let element = document.createElement("option"); +                    element.value = value; +                    element.text = value; + +                    radio_options.appendChild(element); +                    current_radio_options.push(value); +                    hidden_radio_options.value = JSON.stringify({"options": current_radio_options}); +                } +            } + +            checkValid(); +        }; + +        radio_remove_button.onclick = function() { +            let value = radio_options.value; + +            if (value === "none") { +                return; +            } + +            let index = current_radio_options.indexOf(value); + +            if (index < 0) { // We have a problem! +                console.log("Unable to remove value from radio values because it doesn't exist: " + value) +            } else { +                current_radio_options.splice(index, 1); +            } + +            for (let element of radio_options.getElementsByTagName("option")) { +                if (element.value === "none") { +                    continue; +                } + +                if (element.value === value) { +                    radio_options.removeChild(element); +                } +            } + +            hidden_radio_options.value = JSON.stringify({"options": current_radio_options}); +            radio_options.value = "none"; +            radio_add_input.focus(); +            checkValid(); +        }; + +        number_min.oninput = function() { +            if (this.value.length > 0 && isNaN(parseInt(this.value))) { +                this.classList.add("uk-form-danger") +            } else { +                this.classList.remove("uk-form-danger") +            } + +            checkValid(); +        }; + +        number_max.oninput = function() { +            if (this.value.length > 0 && isNaN(parseInt(this.value))) { +                this.classList.add("uk-form-danger") +            } else { +                this.classList.remove("uk-form-danger") +            } + +            checkValid(); +        }; + +        function checkValid() { +            if (new_question_title.value.length < 1) { +                return setButtonEnabled(false); +            } + +            let question_type = new_question_type.value; + +            if (question_type === "radio") { +                if (current_radio_options.length < 1) { +                    return setButtonEnabled(false); +                } +            } + +            if (   question_type === "number" +                || question_type === "range" +                || question_type === "slider" +            ) { +                if (isNaN(parseInt(number_min.value))) { +                    return setButtonEnabled(false); +                } +                if (isNaN(parseInt(number_max.value))) { +                    return setButtonEnabled(false); +                } + +                if (number_min.value.length < 1 || number_max.value.length < 1) { +                    return setButtonEnabled(false); +                } +            } + +            return setButtonEnabled(true); +        } + +        function setButtonEnabled(enabled) { +            submit_button.disabled = !enabled; +        } +    </script> +{% endblock %} diff --git a/templates/staff/jams/forms/questions_view.html b/templates/staff/jams/forms/questions_view.html new file mode 100644 index 00000000..eb545451 --- /dev/null +++ b/templates/staff/jams/forms/questions_view.html @@ -0,0 +1,596 @@ +{% extends "main/base.html" %} +{% block title %}Staff | Jams | Questions{% endblock %} +{% block og_title %}Staff | Jams | Questions{% endblock %} +{% block og_description %}Manage all created questions{% endblock %} +{% block extra_head %} +    <script src="{{ static_file('js/jams.js') }}"></script> +{% endblock %} +{% block content %} +    <div class="uk-container uk-container-small uk-section"> +        <h1>Questions List</h1> + +        <a class="uk-button uk-button-default" href="{{ url_for("staff.jams.index") }}"><i class="uk-icon fa-fw far fa-arrow-left"></i>  Back</a> +        <button class="uk-button uk-button-primary" id="add-button"><i class="uk-icon fa-fw far fa-plus"></i>  Add Question</button> +     {# <a class="uk-button uk-button-secondary" target="_blank" href="{{ url_for("staff.index") }}"><i class="uk-icon fa-fw far fa-eye"></i>  Preview</a>  #} + +        {% if not questions %} +            <p id="no-questions-paragraph">No questions found. Add one above!</p> +            <table class="uk-table uk-table-divider uk-table-striped uk-border" id="table" hidden="hidden"> +                <thead> +                    <tr> +                        <th class="uk-table-shrink"> </th> +                        <th><strong>ID</strong></th> +                        <th class="uk-table-shrink">Optional</th> +                        <th>Title</th> +                        <th class="uk-table-shrink">Type</th> +                        <th>Data</th> +                    </tr> +                </thead> +                <tbody id="table-body"> +                </tbody> +            </table> +        {% else %} +            <p id="no-questions-paragraph" hidden="hidden">No questions found. Add one above!</p> + +            <div class="uk-overflow-auto"> +                <br /> +                <table class="uk-table uk-table-divider uk-table-striped uk-border" id="table"> +                    <thead> +                        <tr> +                            <th class="uk-table-shrink"> </th> +                            <th><strong>ID</strong></th> +                            <th class="uk-table-shrink">Optional</th> +                            <th>Title</th> +                            <th class="uk-table-shrink">Type</th> +                            <th>Data</th> +                        </tr> +                    </thead> +                    <tbody id="table-body"> +                        {% for question in questions %} +                            <tr id="row-{{ question.id }}"> +                                <td class="uk-table-shrink"> +                                    <button class="uk-button-small uk-button uk-button-danger delete-question-button" style="padding-left: 5px; padding-right: 5px;" data-question-id="{{ question.id }}"><i class="uk-icon fa-fw far fa-trash"></i></button> +                                    <a href="{{ url_for("staff.jams.forms.questions.edit", question=question.id) }}" class="uk-button-small uk-button uk-button-primary edit-question-button" style="padding-left: 5px; padding-right: 5px;"><i class="uk-icon fa-fw far fa-pencil"></i></a> +                                </td> +                                <td class="uk-text-truncate" title="{{ question.id }}">{{ question.id }}</td> +                                <td class="uk-table-shrink"> +                                    {% if question.optional %} +                                        <i class="uk-icon uk-text-success fa-fw far fa-check"></i> +                                    {% else %} +                                        <i class="uk-icon uk-text-danger fa-fw far fa-times"></i> +                                    {% endif %} +                                </td> +                                <td title="{{ question.title }}">{{ question.title }}</td> +                                <td class="uk-table-shrink" title="{{ question.type.title() }}">{{ question.type.title() }}</td> +                                <td> +                                    {% if question.type == "text" %} + +                                    {% elif question.type == "number" %} +                                        <i class="uk-icon fa-fw far fa-arrow-up" title="Max value"></i>  {{ question.data.max }} +                                        <br /> +                                        <i class="uk-icon fa-fw far fa-arrow-down" title="Min value"></i>  {{ question.data.min }} +                                    {% elif question.type == "checkbox" %} + +                                    {% elif question.type == "email" %} + +                                    {% elif question.type == "textarea" %} + +                                    {% elif question.type == "radio" %} +                                        <ul> +                                            {% for option in question.data.options %} +                                                <li>{{ option }}</li> +                                            {% endfor %} +                                        </ul> +                                    {% elif question.type == "range" %} +                                        <i class="uk-icon fa-fw far fa-arrow-up" title="Max value"></i>  {{ question.data.max }} +                                        <br /> +                                        <i class="uk-icon fa-fw far fa-arrow-down" title="Min value"></i>  {{ question.data.min }} +                                    {% elif question.type == "slider" %} +                                        <i class="uk-icon fa-fw far fa-arrow-up" title="Max value"></i>  {{ question.data.max }} +                                        <br /> +                                        <i class="uk-icon fa-fw far fa-arrow-down" title="Min value"></i>  {{ question.data.min }} +                                    {% else %} +                                        {{ question.data }} +                                    {% endif %} +                                </td> +                            </tr> +                        {% endfor %} +                    </tbody> +                </table> +            </div> +        {% endif %} +    </div> + +    <div id="question-modal" class="uk-flex-top" uk-modal> +        <div class="uk-modal-dialog"> +            <button class="uk-modal-close-default" type="button" uk-close></button> + +            <div class="uk-modal-header"> +                <h2 class="uk-modal-title">Add Question</h2> +            </div> + +            <div class="uk-modal-body"> +                <form class="uk-form-horizontal"> +                    <div id="loading-spinner" class="uk-text-center uk-margin-small-top" hidden="hidden"> +                        <div uk-spinner></div> +                    </div> +                    <div id="new-question-section"> +                        <div> +                            <div class="uk-form-label"> +                                <label class="uk-form-label" for="new-question-title">Title</label> +                            </div> +                            <div class="uk-form-controls-text uk-form-controls"> +                                <input type="text" id="new-question-title" class="uk-input"> +                            </div> +                        </div> +                        <div> +                            <div class="uk-form-label"> +                                <label class="uk-form-label" for="new-question-optional">Optional</label> +                            </div> +                            <div class="uk-form-controls-text uk-form-controls"> +                                <select class="uk-select" id="new-question-optional"> +                                    <option hidden="hidden" disabled selected value="none"></option> +                                    <option value="optional">Optional</option> +                                    <option value="required">Required</option> +                                </select> +                            </div> +                        </div> +                        <div> +                            <div class="uk-form-label"> +                                <label class="uk-form-label" for="new-question-type">Type</label> +                            </div> +                            <div class="uk-form-controls-text uk-form-controls"> +                                <select class="uk-select" id="new-question-type"> +                                    <option hidden="hidden" disabled selected value="none"></option> +                                    <option value="checkbox">Checkbox</option> +                                    <option value="email">Email</option> +                                    <option value="number">Number</option> +                                    <option value="radio">Radio</option> +                                    <option value="range">Range</option> +                                    <option value="slider">Slider</option> +                                    <option value="textarea">Text (Block)</option> +                                    <option value="text">Text (Line)</option> +                                </select> +                            </div> +                        </div> +                    </div> + +                    <div id="radio-section" hidden="hidden"> +                        <br /> +                        <div> +                            <div class="uk-form-label"> +                                <button type="button" class="uk-button uk-button-primary uk-width-1-1" id="radio-add-button"><i class="uk-icon fa-fw far fa-plus"></i>  Add</button> +                            </div> +                            <div class="uk-form-controls-text uk-form-controls"> +                                <input type="text" id="radio-add-input" class="uk-input" placeholder="Item"> +                            </div> +                        </div> +                        <div> +                            <div class="uk-form-label"> +                                <button type="button" class="uk-button uk-button-danger uk-width-1-1" id="radio-remove-button"><i class="uk-icon fa-fw far fa-minus"></i>  Remove</button> +                            </div> +                            <div class="uk-form-controls-text uk-form-controls"> +                                <select class="uk-select" id="radio-options"> +                                    <option hidden="hidden" disabled selected value="none"></option> +                                </select> +                            </div> +                        </div> +                    </div> + +                    <div id="number-section" hidden="hidden"> +                        <br /> +                        <div> +                            <div class="uk-form-label"> +                                <label class="uk-form-label" for="number-min">Min Value</label> +                            </div> +                            <div class="uk-form-controls-text uk-form-controls"> +                                <input type="text" id="number-min" class="uk-input"> +                            </div> +                        </div> +                        <div> +                            <div class="uk-form-label"> +                                <label class="uk-form-label" for="number-max">Max Value</label> +                            </div> +                            <div class="uk-form-controls-text uk-form-controls"> +                                <input type="text" id="number-max" class="uk-input"> +                            </div> +                        </div> +                    </div> +                </form> +            </div> + +            <div class="uk-modal-footer"> +                <div class="uk-text-center"> +                    <button class="uk-button uk-button-danger uk-modal-close" type="button" id="state-cancel"> +                        <i class="uk-icon fa-fw far fa-times"></i>  Cancel +                    </button> +                    <button class="uk-button uk-button-primary" type="button" id="question-submit" disabled> +                        <i class="uk-icon fa-fw far fa-check"></i>  Save +                    </button> +                </div> +            </div> +        </div> +    </div> + +    <script type="application/javascript"> +        "use strict"; + +        const actions = new Actions("{{ url_for("staff.jams.action") }}", "{{ csrf_token() }}"); +        const table_body = document.getElementById("table-body"); +        const table = document.getElementById("table"); +        const no_questions_paragraph = document.getElementById("no-questions-paragraph"); + +        let all_questions = {{ question_ids | safe }}; +        const question_edit_url = "{{ url_for("staff.jams.forms.questions.edit", question="{NONE}") }}"; + +        const add_button = document.getElementById("add-button"); +        const modal = UIkit.modal(document.getElementById("question-modal")); +        const loading_spinner = document.getElementById("loading-spinner"); + +        const new_question_section = document.getElementById("new-question-section"); +        const new_question_title = document.getElementById("new-question-title"); +        const new_question_optional = document.getElementById("new-question-optional"); +        const new_question_type = document.getElementById("new-question-type"); + +        const radio_section = document.getElementById("radio-section"); + +        const radio_add_button = document.getElementById("radio-add-button"); +        const radio_add_input = document.getElementById("radio-add-input"); +        const radio_remove_button = document.getElementById("radio-remove-button"); +        const radio_options = document.getElementById("radio-options"); + +        const number_section = document.getElementById("number-section"); + +        const number_min = document.getElementById("number-min"); +        const number_max = document.getElementById("number-max"); + +        const submit_button = document.getElementById("question-submit"); + +        let current_radio_options = Array(); + +        add_button.onclick = function() { +            clearModal(); +            checkModal(); +            modal.show(); +        }; + +        new_question_type.onchange = function() { +            if (this.value === "checkbox") { +                radio_section.setAttribute("hidden", "hidden"); +                number_section.setAttribute("hidden", "hidden"); +            } else if (this.value === "email") { +                radio_section.setAttribute("hidden", "hidden"); +                number_section.setAttribute("hidden", "hidden"); +            } else if (this.value === "number") { +                radio_section.setAttribute("hidden", "hidden"); +                number_section.removeAttribute("hidden"); +            } else if (this.value === "radio") { +                radio_section.removeAttribute("hidden"); +                number_section.setAttribute("hidden", "hidden"); +            } else if (this.value === "range") { +                radio_section.setAttribute("hidden", "hidden"); +                number_section.removeAttribute("hidden"); +            } else if (this.value === "slider") { +                radio_section.setAttribute("hidden", "hidden"); +                number_section.removeAttribute("hidden"); +            } else if (this.value === "textarea") { +                radio_section.setAttribute("hidden", "hidden"); +                number_section.setAttribute("hidden", "hidden"); +            } else if (this.value === "text") { +                radio_section.setAttribute("hidden", "hidden"); +                number_section.setAttribute("hidden", "hidden"); +            } else { +                radio_section.setAttribute("hidden", "hidden"); +                number_section.setAttribute("hidden", "hidden"); +            } + +            checkModal(); +        }; + +        new_question_title.oninput = checkModal; +        new_question_optional.onchange = checkModal; + +        radio_add_input.onkeyup = function(event) { +            event.preventDefault(); + +            if (event.which === 13 || event.keyCode === 13) { +                radio_add_button.onclick(undefined); +            } +        }; + +        radio_add_button.onclick = function() { +            let value = radio_add_input.value; + +            if (value.length < 1) { +                radio_add_input.classList.add("uk-form-danger"); +                radio_add_input.focus(); +            } else { +                let index = current_radio_options.indexOf(value); + +                if (index > -1 || value === "none") { +                    radio_add_input.classList.add("uk-form-danger"); +                    radio_add_input.focus(); +                } else { +                    radio_add_input.classList.remove("uk-form-danger"); +                    radio_add_input.value = ""; + +                    let element = document.createElement("option"); +                    element.value = value; +                    element.text = value; + +                    radio_options.appendChild(element); +                    current_radio_options.push(value); +                } +            } + +            checkModal(); +        }; + +        radio_remove_button.onclick = function() { +            let value = radio_options.value; + +            if (value === "none") { +                return; +            } + +            let index = current_radio_options.indexOf(value); + +            if (index < 0) { // We have a problem! +                console.log("Unable to remove value from radio values because it doesn't exist: " + value) +            } else { +                current_radio_options.splice(index, 1); +            } + +            for (let element of radio_options.getElementsByTagName("option")) { +                if (element.value === "none") { +                    continue; +                } + +                if (element.value === value) { +                    radio_options.removeChild(element); +                } +            } + +            radio_options.value = "none"; +            radio_add_input.focus(); +            checkModal(); +        }; + +        number_min.oninput = function() { +            if (this.value.length > 0 && isNaN(parseInt(this.value))) { +                this.classList.add("uk-form-danger") +            } else { +                this.classList.remove("uk-form-danger") +            } + +            checkModal(); +        }; + +        number_max.oninput = function() { +            if (this.value.length > 0 && isNaN(parseInt(this.value))) { +                this.classList.add("uk-form-danger") +            } else { +                this.classList.remove("uk-form-danger") +            } + +            checkModal(); +        }; + +        submit_button.onclick = function () { +            let type = new_question_type.value; +            let optional = new_question_optional.value === "optional"; +            let title = new_question_title.value; + +            let question_data = { +                "type": type, +                "optional": optional, +                "title": title +            }; + +            if (type === "radio") { +                question_data.data = {"options": current_radio_options}; +            } else if (type === "number" +                    || type === "range" +                    || type === "slider") { +                question_data.data = { +                    "max": parseInt(number_max.value), +                    "min": parseInt(number_min.value) +                }; +            } + +            number_section.setAttribute("hidden", "hidden"); +            new_question_section.setAttribute("hidden", "hidden"); +            radio_section.setAttribute("hidden", "hidden"); +            loading_spinner.removeAttribute("hidden"); + +            actions.create_question(question_data, function(result, data) { +                if (result) { +                    question_data["id"] = data.id; +                    addToTable(question_data); +                    modal.hide(); +                    clearModal(); + +                    UIkit.notification({ +                        "message": "Question added", +                        "status": "success", +                        "pos": "bottom-center", +                        "timeout": 5000, +                    }); +                } else { +                    console.log(data); +                    UIkit.notification({ +                        "message": "Failed to create question", +                        "status": "danger", +                        "pos": "bottom-center", +                        "timeout": 5000, +                    }); +                } +            }) +        }; + +        const toTitleCase = (str) => str.replace(/\b\S/g, t => t.toUpperCase()); + +        function hookUpDeleteButtons() { +            for (let element of document.getElementsByClassName("delete-question-button")) { +                element.onclick = function() { +                    let question_id = this.getAttribute("data-question-id"); +                    let row = document.getElementById("row-" + question_id); + +                    actions.delete_question(question_id, function(result, data) { +                        if (result) { +                            document.getElementById("table-body").removeChild(row); +                            UIkit.notification({ +                                "message": "Question deleted", +                                "status": "success", +                                "pos": "bottom-center", +                                "timeout": 5000, +                            }); +                        } else { +                            console.log(data); +                            UIkit.notification({ +                                "message": "Failed to delete question", +                                "status": "danger", +                                "pos": "bottom-center", +                                "timeout": 5000, +                            }); +                        } +                    }) +                } +            } +        } + +        function clearModal() { +            // Existing question section +            loading_spinner.setAttribute("hidden", "hidden"); + +            new_question_section.removeAttribute("hidden"); + +            // New question section +            new_question_title.value = ""; +            new_question_optional.value = "none"; +            new_question_type.value = "none"; + +            // Radio question section +            radio_section.setAttribute("hidden", "hidden"); + +            radio_add_input.value = ""; +            radio_options.innerHTML = "<option hidden=\"hidden\" disabled selected value=\"none\"></option>"; +            radio_options.value = "none"; + +            current_radio_options = Array(); + +            // Number question section +            number_section.setAttribute("hidden", "hidden"); +        } + +        function checkModal() { +            if (new_question_title.value.length < 1) { +                return setButtonEnabled(false); +            } + +            if (new_question_optional.value === "none") { +                return setButtonEnabled(false); +            } + +            let question_type = new_question_type.value; + +            if (question_type === "none") { +                return setButtonEnabled(false); +            } + +            if (question_type === "radio") { +                if (current_radio_options.length < 1) { +                    return setButtonEnabled(false); +                } +            } + +            if (   question_type === "number" +                || question_type === "range" +                || question_type === "slider" +            ) { +                if (isNaN(parseInt(number_min.value))) { +                    return setButtonEnabled(false); +                } +                if (isNaN(parseInt(number_max.value))) { +                    return setButtonEnabled(false); +                } + +                if (number_min.value.length < 1 || number_max.value.length < 1) { +                    return setButtonEnabled(false); +                } +            } + +            return setButtonEnabled(true); +        } + +        function setButtonEnabled(enabled) { +            submit_button.disabled = !enabled; +        } + +        function addToTable(question) { +            console.log(question); +            if (all_questions.indexOf(question.id) === -1) { +                all_questions.push(question.id); + +                let element = document.createElement("tr"); +                element.id = "row-" + question.id; +                element.innerHTML = getRowHTML(question); + +                table_body.appendChild(element); +            } + +            if (all_questions.length > 0) { +                table.removeAttribute("hidden"); +                no_questions_paragraph.setAttribute("hidden", "hidden"); +            } +            hookUpDeleteButtons(); +        } + +        function getRowHTML(question) { +            let optional; +            let data; + +            if (question.optional) { +                optional = "<i class=\"uk-icon uk-text-success fa-fw far fa-check\"></i>" +            } else { +                optional = "<i class=\"uk-icon uk-text-danger fa-fw far fa-times\"></i>" +            } + +            if (question.type === "number" || question.type === "range" || question.type === "slider") { +                data = ` +<i class="uk-icon fa-fw far fa-arrow-up" title="Max value"></i>  ${question.data.max} +<br /> +<i class="uk-icon fa-fw far fa-arrow-down" title="Min value"></i>  ${question.data.min} +                ` + +            } else if (question.type === "radio") { +                data = "<ul>"; + +                for (let option of question.data.options) { +                    data = data + `<li>${option}</li>` +                } +                data = data + "</ul>"; +            } else { +                data = "" +            } + +            let type = toTitleCase(question.type); +            let q_url = question_edit_url.replace("{NONE}", question.id); + +            const row = ` +    <td class="uk-table-shrink"> +        <button class="uk-button-small uk-button uk-button-danger delete-question-button" style="padding-left: 5px; padding-right: 5px;" data-question-id="${question.id}"><i class="uk-icon fa-fw far fa-trash"></i></button> +        <a href="${q_url}" class="uk-button-small uk-button uk-button-primary edit-question-button" style="padding-left: 5px; padding-right: 5px;"><i class="uk-icon fa-fw far fa-pencil"></i></a> +    </td> +    <td class="uk-text-truncate" title="${question.id}">${question.id}</td> +    <td class="uk-table-shrink">${optional}</td> +    <td title="${question.title}">${question.title}</td> +    <td class="uk-table-shrink" title="${type}">${type}</td> +    <td>${data}</td> +                        `; +            return row +        } + +        hookUpDeleteButtons(); +    </script> +{% endblock %} diff --git a/templates/staff/jams/forms/view.html b/templates/staff/jams/forms/view.html new file mode 100644 index 00000000..6d0a86af --- /dev/null +++ b/templates/staff/jams/forms/view.html @@ -0,0 +1,757 @@ +{% extends "main/base.html" %} +{% block title %}Staff | Jams | Form{% endblock %} +{% block og_title %}Staff | Jams | Form{% endblock %} +{% block og_description %}Manage the form for a code jam{% endblock %} +{% block extra_head %} +    <script src="{{ static_file('js/jams.js') }}"></script> +{% endblock %} +{% block content %} +    <div class="uk-container uk-container-small uk-section"> +        <h1>Application Form {{ jam.number }}: {{ jam.title }}</h1> + +        <a class="uk-button uk-button-default" href="{{ url_for("staff.jams.index") }}"><i class="uk-icon fa-fw far fa-arrow-left"></i>  Back</a> +        <button class="uk-button uk-button-primary" id="add-button"><i class="uk-icon fa-fw far fa-plus"></i>  Add Question</button> +     {# <a class="uk-button uk-button-secondary" target="_blank" href="{{ url_for("staff.index") }}"><i class="uk-icon fa-fw far fa-eye"></i>  Preview</a>  #} + +        {% if not questions %} +            <p id="no-questions-paragraph">No questions found. Add one above!</p> +            <table class="uk-table uk-table-divider uk-table-striped uk-border" id="table" hidden="hidden"> +                <thead> +                    <tr> +                        <th class="uk-table-shrink"> </th> +                        <th><strong>ID</strong></th> +                        <th class="uk-table-shrink">Optional</th> +                        <th>Title</th> +                        <th class="uk-table-shrink">Type</th> +                        <th>Data</th> +                    </tr> +                </thead> +                <tbody id="table-body"> +                </tbody> +            </table> +        {% else %} +            <p id="no-questions-paragraph" hidden="hidden">No questions found. Add one above!</p> + +            <div class="uk-overflow-auto"> +                <br /> +                <table class="uk-table uk-table-divider uk-table-striped uk-border" id="table"> +                    <thead> +                        <tr> +                            <th class="uk-table-shrink"> </th> +                            <th><strong>ID</strong></th> +                            <th class="uk-table-shrink">Optional</th> +                            <th>Title</th> +                            <th class="uk-table-shrink">Type</th> +                            <th>Data</th> +                        </tr> +                    </thead> +                    <tbody id="table-body"> +                        {% for question in questions %} +                            <tr id="row-{{ question.id }}"> +                                <td class="uk-table-shrink"> +                                    <button class="uk-button-small uk-button uk-button-danger delete-question-button" style="padding-left: 5px; padding-right: 5px;" data-question-id="{{ question.id }}"><i class="uk-icon fa-fw far fa-times"></i></button> +                                </td> +                                <td class="uk-text-truncate" title="{{ question.id }}">{{ question.id }}</td> +                                <td class="uk-table-shrink"> +                                    {% if question.optional %} +                                        <i class="uk-icon uk-text-success fa-fw far fa-check"></i> +                                    {% else %} +                                        <i class="uk-icon uk-text-danger fa-fw far fa-times"></i> +                                    {% endif %} +                                </td> +                                <td title="{{ question.title }}">{{ question.title }}</td> +                                <td class="uk-table-shrink" title="{{ question.type.title() }}">{{ question.type.title() }}</td> +                                <td> +                                    {% if question.type == "text" %} + +                                    {% elif question.type == "number" %} +                                        <i class="uk-icon fa-fw far fa-arrow-up" title="Max value"></i>  {{ question.data.max }} +                                        <br /> +                                        <i class="uk-icon fa-fw far fa-arrow-down" title="Min value"></i>  {{ question.data.min }} +                                    {% elif question.type == "checkbox" %} + +                                    {% elif question.type == "email" %} + +                                    {% elif question.type == "textarea" %} + +                                    {% elif question.type == "radio" %} +                                        <ul> +                                            {% for option in question.data.options %} +                                                <li>{{ option }}</li> +                                            {% endfor %} +                                        </ul> +                                    {% elif question.type == "range" %} +                                        <i class="uk-icon fa-fw far fa-arrow-up" title="Max value"></i>  {{ question.data.max }} +                                        <br /> +                                        <i class="uk-icon fa-fw far fa-arrow-down" title="Min value"></i>  {{ question.data.min }} +                                    {% elif question.type == "slider" %} +                                        <i class="uk-icon fa-fw far fa-arrow-up" title="Max value"></i>  {{ question.data.max }} +                                        <br /> +                                        <i class="uk-icon fa-fw far fa-arrow-down" title="Min value"></i>  {{ question.data.min }} +                                    {% else %} +                                        {{ question.data }} +                                    {% endif %} +                                </td> +                            </tr> +                        {% endfor %} +                    </tbody> +                </table> +            </div> +        {% endif %} +    </div> + +    <div id="question-modal" class="uk-flex-top" uk-modal> +        <div class="uk-modal-dialog"> +            <button class="uk-modal-close-default" type="button" uk-close></button> + +            <div class="uk-modal-header"> +                <h2 class="uk-modal-title">Add Question</h2> +            </div> + +            <div class="uk-modal-body"> +                <form class="uk-form-horizontal"> +                    <div id="question-source-section"> +                        <div> +                            <div class="uk-form-label"> +                                <label class="uk-form-label" for="question-source">Source</label> +                            </div> +                            <div class="uk-form-controls-text uk-form-controls"> +                                <select class="uk-select" id="question-source" required> +                                    <option hidden="hidden" disabled selected value="none"></option> +                                    <option value="new">New</option> +                                    <option value="existing">Existing</option> +                                </select> +                            </div> +                        </div> +                    </div> +                    <div id="loading-spinner" class="uk-text-center uk-margin-small-top" hidden="hidden"> +                        <div uk-spinner></div> +                    </div> +                    <div id="new-question-section" hidden="hidden"> +                        <br /> +                        <div> +                            <div class="uk-form-label"> +                                <label class="uk-form-label" for="new-question-title">Title</label> +                            </div> +                            <div class="uk-form-controls-text uk-form-controls"> +                                <input type="text" id="new-question-title" class="uk-input"> +                            </div> +                        </div> +                        <div> +                            <div class="uk-form-label"> +                                <label class="uk-form-label" for="new-question-optional">Optional</label> +                            </div> +                            <div class="uk-form-controls-text uk-form-controls"> +                                <select class="uk-select" id="new-question-optional"> +                                    <option hidden="hidden" disabled selected value="none"></option> +                                    <option value="optional">Optional</option> +                                    <option value="required">Required</option> +                                </select> +                            </div> +                        </div> +                        <div> +                            <div class="uk-form-label"> +                                <label class="uk-form-label" for="new-question-type">Type</label> +                            </div> +                            <div class="uk-form-controls-text uk-form-controls"> +                                <select class="uk-select" id="new-question-type"> +                                    <option hidden="hidden" disabled selected value="none"></option> +                                    <option value="checkbox">Checkbox</option> +                                    <option value="email">Email</option> +                                    <option value="number">Number</option> +                                    <option value="radio">Radio</option> +                                    <option value="range">Range</option> +                                    <option value="slider">Slider</option> +                                    <option value="textarea">Text (Block)</option> +                                    <option value="text">Text (Line)</option> +                                </select> +                            </div> +                        </div> +                    </div> + +                    <div id="radio-section" hidden="hidden"> +                        <br /> +                        <div> +                            <div class="uk-form-label"> +                                <button type="button" class="uk-button uk-button-primary uk-width-1-1" id="radio-add-button"><i class="uk-icon fa-fw far fa-plus"></i>  Add</button> +                            </div> +                            <div class="uk-form-controls-text uk-form-controls"> +                                <input type="text" id="radio-add-input" class="uk-input" placeholder="Item"> +                            </div> +                        </div> +                        <div> +                            <div class="uk-form-label"> +                                <button type="button" class="uk-button uk-button-danger uk-width-1-1" id="radio-remove-button"><i class="uk-icon fa-fw far fa-minus"></i>  Remove</button> +                            </div> +                            <div class="uk-form-controls-text uk-form-controls"> +                                <select class="uk-select" id="radio-options"> +                                    <option hidden="hidden" disabled selected value="none"></option> +                                </select> +                            </div> +                        </div> +                    </div> + +                    <div id="number-section" hidden="hidden"> +                        <br /> +                        <div> +                            <div class="uk-form-label"> +                                <label class="uk-form-label" for="number-min">Min Value</label> +                            </div> +                            <div class="uk-form-controls-text uk-form-controls"> +                                <input type="text" id="number-min" class="uk-input"> +                            </div> +                        </div> +                        <div> +                            <div class="uk-form-label"> +                                <label class="uk-form-label" for="number-max">Max Value</label> +                            </div> +                            <div class="uk-form-controls-text uk-form-controls"> +                                <input type="text" id="number-max" class="uk-input"> +                            </div> +                        </div> +                    </div> + +                    <div id="existing-question-section" hidden="hidden"> +                        <br /> +                        <div> +                            <div class="uk-form-label"> +                                <label class="uk-form-label" for="existing-question">Question</label> +                            </div> +                            <div class="uk-form-controls-text uk-form-controls"> +                                <select class="uk-select" id="existing-question"> +                                    <option hidden="hidden" disabled selected value="none"></option> +                                </select> +                            </div> +                        </div> +                    </div> +                </form> +            </div> + +            <div class="uk-modal-footer"> +                <div class="uk-text-center"> +                    <button class="uk-button uk-button-danger uk-modal-close" type="button" id="state-cancel"> +                        <i class="uk-icon fa-fw far fa-times"></i>  Cancel +                    </button> +                    <button class="uk-button uk-button-primary" type="button" id="question-submit" disabled> +                        <i class="uk-icon fa-fw far fa-check"></i>  Save +                    </button> +                </div> +            </div> +        </div> +    </div> + +    <script type="application/javascript"> +        "use strict"; +        const actions = new Actions("{{ url_for("staff.jams.action") }}", "{{ csrf_token() }}"); +        const form = parseInt("{{ jam.number }}"); +        const table_body = document.getElementById("table-body"); +        const table = document.getElementById("table"); +        const no_questions_paragraph = document.getElementById("no-questions-paragraph"); + +        let all_questions = {{ question_ids | safe }}; + +        const add_button = document.getElementById("add-button"); +        const modal = UIkit.modal(document.getElementById("question-modal")); + +        const question_source_section = document.getElementById("question-source-section"); +        const question_source = document.getElementById("question-source"); +        const loading_spinner = document.getElementById("loading-spinner"); + +        const new_question_section = document.getElementById("new-question-section"); +        const new_question_title = document.getElementById("new-question-title"); +        const new_question_optional = document.getElementById("new-question-optional"); +        const new_question_type = document.getElementById("new-question-type"); + +        const radio_section = document.getElementById("radio-section"); + +        const radio_add_button = document.getElementById("radio-add-button"); +        const radio_add_input = document.getElementById("radio-add-input"); +        const radio_remove_button = document.getElementById("radio-remove-button"); +        const radio_options = document.getElementById("radio-options"); + +        const number_section = document.getElementById("number-section"); + +        const number_min = document.getElementById("number-min"); +        const number_max = document.getElementById("number-max"); + +        const existing_question_section = document.getElementById("existing-question-section"); +        const existing_question_select = document.getElementById("existing-question"); + +        const submit_button = document.getElementById("question-submit"); + +        let current_radio_options = Array(); + +        add_button.onclick = function() { +            clearModal(); +            checkModal(); +            modal.show(); +        }; + +        question_source.onchange = function () { +            checkModal(); + +            if (this.value === "new") { +                existing_question_section.setAttribute("hidden", "hidden"); +                loading_spinner.setAttribute("hidden", "hidden"); +                new_question_section.removeAttribute("hidden"); +            } else { +                new_question_section.setAttribute("hidden", "hidden"); +                loading_spinner.removeAttribute("hidden"); + +                actions.get_questions(function(result, data) { +                    existing_question_section.setAttribute("hidden", "hidden"); +                    loading_spinner.setAttribute("hidden", "hidden"); + +                    if (!result) { +                        UIkit.notification({ +                            "message": "Failed to fetch questions", +                            "status": "danger", +                            "pos": "bottom-center", +                            "timeout": 5000 +                        }) +                    } else { +                        existing_question_select.innerHTML = "<option hidden=\"hidden\" disabled selected value=\"none\"></option>"; + +                        for (let question of data.questions) { +                            let element = document.createElement("option"); +                            element.value = question.id; +                            element.text = question.title; + +                            existing_question_select.appendChild(element); +                        } + +                        existing_question_section.removeAttribute("hidden"); +                    } +                }) +            } +        }; + +        existing_question_select.onchange = function() { +            if (all_questions.indexOf(existing_question_select.value) > -1) { +                existing_question_select.classList.add("uk-form-danger"); +            } else { +                existing_question_select.classList.remove("uk-form-danger"); +            } + +            checkModal(); +        }; + +        new_question_type.onchange = function() { +            if (this.value === "checkbox") { +                radio_section.setAttribute("hidden", "hidden"); +                number_section.setAttribute("hidden", "hidden"); +            } else if (this.value === "email") { +                radio_section.setAttribute("hidden", "hidden"); +                number_section.setAttribute("hidden", "hidden"); +            } else if (this.value === "number") { +                radio_section.setAttribute("hidden", "hidden"); +                number_section.removeAttribute("hidden"); +            } else if (this.value === "radio") { +                radio_section.removeAttribute("hidden"); +                number_section.setAttribute("hidden", "hidden"); +            } else if (this.value === "range") { +                radio_section.setAttribute("hidden", "hidden"); +                number_section.removeAttribute("hidden"); +            } else if (this.value === "slider") { +                radio_section.setAttribute("hidden", "hidden"); +                number_section.removeAttribute("hidden"); +            } else if (this.value === "textarea") { +                radio_section.setAttribute("hidden", "hidden"); +                number_section.setAttribute("hidden", "hidden"); +            } else if (this.value === "text") { +                radio_section.setAttribute("hidden", "hidden"); +                number_section.setAttribute("hidden", "hidden"); +            } else { +                radio_section.setAttribute("hidden", "hidden"); +                number_section.setAttribute("hidden", "hidden"); +            } + +            checkModal(); +        }; + +        new_question_title.oninput = checkModal; +        new_question_optional.onchange = checkModal; + +        radio_add_input.onkeyup = function(event) { +            event.preventDefault(); + +            if (event.which === 13 || event.keyCode === 13) { +                radio_add_button.onclick(undefined); +            } +        }; + +        radio_add_button.onclick = function() { +            let value = radio_add_input.value; + +            if (value.length < 1) { +                radio_add_input.classList.add("uk-form-danger"); +                radio_add_input.focus(); +            } else { +                let index = current_radio_options.indexOf(value); + +                if (index > -1 || value === "none") { +                    radio_add_input.classList.add("uk-form-danger"); +                    radio_add_input.focus(); +                } else { +                    radio_add_input.classList.remove("uk-form-danger"); +                    radio_add_input.value = ""; + +                    let element = document.createElement("option"); +                    element.value = value; +                    element.text = value; + +                    radio_options.appendChild(element); +                    current_radio_options.push(value); +                } +            } + +            checkModal(); +        }; + +        radio_remove_button.onclick = function() { +            let value = radio_options.value; + +            if (value === "none") { +                return; +            } + +            let index = current_radio_options.indexOf(value); + +            if (index < 0) { // We have a problem! +                console.log("Unable to remove value from radio values because it doesn't exist: " + value) +            } else { +                current_radio_options.splice(index, 1); +            } + +            for (let element of radio_options.getElementsByTagName("option")) { +                if (element.value === "none") { +                    continue; +                } + +                if (element.value === value) { +                    radio_options.removeChild(element); +                } +            } + +            radio_options.value = "none"; +            radio_add_input.focus(); +            checkModal(); +        }; + +        number_min.oninput = function() { +            if (this.value.length > 0 && isNaN(parseInt(this.value))) { +                this.classList.add("uk-form-danger") +            } else { +                this.classList.remove("uk-form-danger") +            } + +            checkModal(); +        }; + +        number_max.oninput = function() { +            if (this.value.length > 0 && isNaN(parseInt(this.value))) { +                this.classList.add("uk-form-danger") +            } else { +                this.classList.remove("uk-form-danger") +            } + +            checkModal(); +        }; + +        submit_button.onclick = function () { +            if (question_source.value === "existing") { +                number_section.setAttribute("hidden", "hidden"); +                existing_question_section.setAttribute("hidden", "hidden"); +                new_question_section.setAttribute("hidden", "hidden"); +                radio_section.setAttribute("hidden", "hidden"); +                question_source_section.setAttribute("hidden", "hidden"); +                loading_spinner.removeAttribute("hidden"); + +                actions.associate_question(form, existing_question_select.value, function(result, data) { +                    if (result) { +                        addToTable(data.question); +                        modal.hide(); +                        clearModal(); + +                        UIkit.notification({ +                            "message": "Question added", +                            "status": "success", +                            "pos": "bottom-center", +                            "timeout": 5000, +                        }); +                    } else { +                        console.log(data); +                        UIkit.notification({ +                            "message": "Failed to add question to form", +                            "status": "danger", +                            "pos": "bottom-center", +                            "timeout": 5000, +                        }); +                    } +                }) +            } else { +                let type = new_question_type.value; +                let optional = new_question_optional.value === "optional"; +                let title = new_question_title.value; + +                let question_data = { +                    "type": type, +                    "optional": optional, +                    "title": title +                }; + +                if (type === "radio") { +                    question_data.data = {"options": current_radio_options}; +                } else if (type === "number" +                        || type === "range" +                        || type === "slider") { +                    question_data.data = { +                        "max": parseInt(number_max.value), +                        "min": parseInt(number_min.value) +                    }; +                } + +                number_section.setAttribute("hidden", "hidden"); +                existing_question_section.setAttribute("hidden", "hidden"); +                new_question_section.setAttribute("hidden", "hidden"); +                radio_section.setAttribute("hidden", "hidden"); +                question_source_section.setAttribute("hidden", "hidden"); +                loading_spinner.removeAttribute("hidden"); + +                actions.create_question(question_data, function(result, data) { +                    if (result) { +                        actions.associate_question(form, data.id, function(result, data) { +                            modal.hide(); +                            clearModal(); + +                            if (result) { +                                addToTable(data.question); + +                                UIkit.notification({ +                                    "message": "Question added", +                                    "status": "success", +                                    "pos": "bottom-center", +                                    "timeout": 5000, +                                }); +                            } else { +                                console.log(data); +                                UIkit.notification({ +                                    "message": "Question created, but failed to associate with the form", +                                    "status": "warning", +                                    "pos": "bottom-center", +                                    "timeout": 5000, +                                }); +                            } +                        }) +                    } else { +                        console.log(data); +                        UIkit.notification({ +                            "message": "Failed to create question", +                            "status": "danger", +                            "pos": "bottom-center", +                            "timeout": 5000, +                        }); +                    } +                }) +            } +        }; + +        const toTitleCase = (str) => str.replace(/\b\S/g, t => t.toUpperCase()); + +        function hookUpDeleteButtons() { +            for (let element of document.getElementsByClassName("delete-question-button")) { +                element.onclick = function() { +                    let question_id = this.getAttribute("data-question-id"); +                    let row = document.getElementById("row-" + question_id); + +                    actions.disassociate_question(form, question_id, function(result, data) { +                        if (result) { +                            table_body.removeChild(row); + +                            let index = all_questions.indexOf(question_id); + +                            if (index < 0) { // We have a problem! +                                console.log("Unable to remove question from memory because it doesn't exist: " + question_id) +                            } else { +                                all_questions.splice(index, 1); +                            } + +                            if (all_questions.length < 1) { +                                table.setAttribute("hidden", "hidden"); +                                no_questions_paragraph.removeAttribute("hidden"); +                            } + +                            UIkit.notification({ +                                "message": "Question removed", +                                "status": "success", +                                "pos": "bottom-center", +                                "timeout": 5000, +                            }); +                        } else { +                            console.log(data); +                            UIkit.notification({ +                                "message": "Failed to remove question", +                                "status": "danger", +                                "pos": "bottom-center", +                                "timeout": 5000, +                            }); +                        } +                    }) +                } +            } +        } + +        function clearModal() { +            // Question source section +            question_source.value = "none"; +            question_source_section.removeAttribute("hidden"); + +            // Existing question section +            loading_spinner.setAttribute("hidden", "hidden"); + +            new_question_section.setAttribute("hidden", "hidden"); + +            // New question section +            new_question_title.value = ""; +            new_question_optional.value = "none"; +            new_question_type.value = "none"; + +            // Radio question section +            radio_section.setAttribute("hidden", "hidden"); + +            radio_add_input.value = ""; +            radio_options.innerHTML = "<option hidden=\"hidden\" disabled selected value=\"none\"></option>"; +            radio_options.value = "none"; + +            current_radio_options = Array(); + +            // Number question section +            number_section.setAttribute("hidden", "hidden"); + +            existing_question_section.setAttribute("hidden", "hidden"); + +            existing_question_select.innerHTML = "<option hidden=\"hidden\" disabled selected value=\"none\"></option>"; +            existing_question_select.value = "none"; +        } + +        function checkModal() { +            if (question_source.value === "none") { +                return setButtonEnabled(false); +            } else if (question_source.value === "new") { +                if (new_question_title.value.length < 1) { +                    return setButtonEnabled(false); +                } + +                if (new_question_optional.value === "none") { +                    return setButtonEnabled(false); +                } + +                let question_type = new_question_type.value; + +                if (question_type === "none") { +                    return setButtonEnabled(false); +                } + +                if (question_type === "radio") { +                    if (current_radio_options.length < 1) { +                        return setButtonEnabled(false); +                    } +                } + +                if (   question_type === "number" +                    || question_type === "range" +                    || question_type === "slider" +                ) { +                    if (isNaN(parseInt(number_min.value))) { +                        return setButtonEnabled(false); +                    } +                    if (isNaN(parseInt(number_max.value))) { +                        return setButtonEnabled(false); +                    } + +                    if (number_min.value.length < 1 || number_max.value.length < 1) { +                        return setButtonEnabled(false); +                    } +                } +            } else { +                if (existing_question_select.value === "none"){ +                    return setButtonEnabled(false); +                } + +                if (all_questions.indexOf(existing_question_select.value) > -1) { +                    return setButtonEnabled(false); +                } +            } + +            return setButtonEnabled(true); +        } + +        function setButtonEnabled(enabled) { +            submit_button.disabled = !enabled; +        } + +        function addToTable(question) { +            console.log(question); +            if (all_questions.indexOf(question.id) === -1) { +                all_questions.push(question.id); + +                let element = document.createElement("tr"); +                element.id = "row-" + question.id; +                element.innerHTML = getRowHTML(question); + +                table_body.appendChild(element); +            } + +            if (all_questions.length > 0) { +                table.removeAttribute("hidden"); +                no_questions_paragraph.setAttribute("hidden", "hidden"); +            } +            hookUpDeleteButtons(); +        } + +        function getRowHTML(question) { +            let optional; +            let data; + +            if (question.optional) { +                optional = "<i class=\"uk-icon uk-text-success fa-fw far fa-check\"></i>" +            } else { +                optional = "<i class=\"uk-icon uk-text-danger fa-fw far fa-times\"></i>" +            } + +            if (question.type === "number" || question.type === "range" || question.type === "slider") { +                data = ` +<i class="uk-icon fa-fw far fa-arrow-up" title="Max value"></i>  ${question.data.max} +<br /> +<i class="uk-icon fa-fw far fa-arrow-down" title="Min value"></i>  ${question.data.min} +                ` + +            } else if (question.type === "radio") { +                data = "<ul>"; + +                for (let option of question.data.options) { +                    data = data + `<li>${option}</li>` +                } +                data = data + "</ul>"; +            } else { +                data = "" +            } + +            let type = toTitleCase(question.type); + +            const row = ` +    <td class="uk-table-shrink"> +        <button class="uk-button-small uk-button uk-button-danger delete-question-button" style="padding-left: 5px; padding-right: 5px;" data-question-id="${question.id}"><i class="uk-icon fa-fw far fa-times"></i></button> +    </td> +    <td class="uk-text-truncate" title="${question.id}">${question.id}</td> +    <td class="uk-table-shrink">${optional}</td> +    <td title="${question.title}">${question.title}</td> +    <td class="uk-table-shrink" title="${type}">${type}</td> +    <td>${data}</td> +                        `; +            return row +        } + +        hookUpDeleteButtons(); +    </script> +{% endblock %} diff --git a/templates/staff/jams/index.html b/templates/staff/jams/index.html index 336addee..b249ec3f 100644 --- a/templates/staff/jams/index.html +++ b/templates/staff/jams/index.html @@ -11,6 +11,7 @@          <a class="uk-button uk-button-default" href="{{ url_for("staff.index") }}"><i class="uk-icon fa-fw far fa-arrow-left"></i>  Back</a>          <a class="uk-button uk-button-primary" href="{{ url_for("staff.jams.create") }}"><i class="uk-icon fa-fw far fa-plus"></i>  Create</a> +        <a class="uk-button uk-button-primary" href="{{ url_for("staff.jams.forms.questions") }}"><i class="uk-icon fa-fw far fa-list"></i>  Questions</a>          {% if not jams %}              <p> | 
