diff --git a/src/sdg/components/templates/components/fields/base.html b/src/sdg/components/templates/components/fields/base.html new file mode 100644 index 000000000..57c8ce8ae --- /dev/null +++ b/src/sdg/components/templates/components/fields/base.html @@ -0,0 +1,21 @@ +{% if as_row %} + + +{% endif %} + +
+
+ {% block header %}{% endblock %} +
+ +
+ {% block body %}{% endblock %} +
+ + {% block footer %}{% endblock %} +
+ +{% if as_row %} + + +{% endif %} \ No newline at end of file diff --git a/src/sdg/components/templates/components/fields/checkbox.html b/src/sdg/components/templates/components/fields/checkbox.html new file mode 100644 index 000000000..8be45986d --- /dev/null +++ b/src/sdg/components/templates/components/fields/checkbox.html @@ -0,0 +1,18 @@ +{% extends 'components/fields/base.html' %} +{% load fields %} + +{% block header %} + +{% endblock %} + +{% block body %} +
+
+ {{ field }} + {{ field.errors }} +
+
+{% endblock %} \ No newline at end of file diff --git a/src/sdg/components/templates/components/fields/core.html b/src/sdg/components/templates/components/fields/core.html new file mode 100644 index 000000000..f38935e40 --- /dev/null +++ b/src/sdg/components/templates/components/fields/core.html @@ -0,0 +1,17 @@ +{% extends 'components/fields/base.html' %} +{% load fields %} + +{% block header %} + +{% endblock %} + +{% block body %} +
+
+

{{ value }}

+
+
+{% endblock %} \ No newline at end of file diff --git a/src/sdg/components/templates/components/fields/localized.html b/src/sdg/components/templates/components/fields/localized.html new file mode 100644 index 000000000..bb3b6ffb4 --- /dev/null +++ b/src/sdg/components/templates/components/fields/localized.html @@ -0,0 +1,91 @@ +{% extends 'components/fields/base.html' %} +{% load fields utils i18n %} + +{% block header %} + {% for field in object.bound_fields %} + {% if field.language == 'nl' %} + + {% endif %} + {% endfor %} +{% endblock %} + +{% block body %} + {% for field in object.bound_fields %} +
+
+

+ {% if field.language == 'nl' %} + {% trans 'Nederlands' %} + {% else %} + {% trans 'Engels' %} + {% endif %} + + +

+
+
+ {% if inline != True and 'dynamic_array' not in field.field.widget.template_name %} + + {% endif %} + + {# Field. #} +
+ {{ field|addclass:"form__input" }} + {{ field.errors }} +
+
+ + {% if inline != True and 'dynamic_array' not in field.field.widget.template_name %} + + {% endif %} +
+ {% endfor %} +{% endblock %} + +{% block footer %} + {% for field in object.bound_fields %} + {% if forloop.counter == 1 and inline != True and 'dynamic_array' not in field.field.widget.template_name %} + + {% endif %} + {% endfor %} +{% endblock %} \ No newline at end of file diff --git a/src/sdg/components/templates/components/fields/localized_url.html b/src/sdg/components/templates/components/fields/localized_url.html new file mode 100644 index 000000000..9debb32bc --- /dev/null +++ b/src/sdg/components/templates/components/fields/localized_url.html @@ -0,0 +1,51 @@ +{% extends 'components/fields/base.html' %} +{% load utils fields i18n %} + +{% block header %} + {% for field in bound_fields %} + {% if field.link and field.label and field.language == 'nl' %} + + {% endif %} + {% endfor %} +{% endblock %} + +{% block body %} + {% for field in bound_fields %} + {% if field.link and field.label %} +
+
+

+ {% if field.language == 'nl' %} + {% trans 'Nederlands' %} + {% else %} + {% trans 'Engels' %} + {% endif %} + + +

+
+
+
+
+
+ {{ field.label|addlabelclass:"form__input form__input--left" }} + {{ field.link|addlinkclass:"form__input form__input--right" }} +
+ {% if field.label.errors or field.link.errors %} +
+ {{ field.label.errors }}{{ field.link.errors}} +
+ {% endif %} +
+
+
+
+ {% endif %} + {% endfor %} +{% endblock %} \ No newline at end of file diff --git a/src/sdg/components/templates/components/fields/nonlocalized.html b/src/sdg/components/templates/components/fields/nonlocalized.html new file mode 100644 index 000000000..e071fe65d --- /dev/null +++ b/src/sdg/components/templates/components/fields/nonlocalized.html @@ -0,0 +1,23 @@ +{% extends 'components/fields/base.html' %} +{% load fields utils %} + +{% block header %} + {% for field in object.bound_fields %} + + {% endfor %} +{% endblock %} + +{% block body %} + {% for field in object.bound_fields %} + {# Field. #} +
+
+ {{ field|addclass:"form__input" }} + {{ field.errors }} +
+
+ {% endfor %} +{% endblock %} \ No newline at end of file diff --git a/src/sdg/components/templates/components/fields/readonly.html b/src/sdg/components/templates/components/fields/readonly.html new file mode 100644 index 000000000..f2b50d744 --- /dev/null +++ b/src/sdg/components/templates/components/fields/readonly.html @@ -0,0 +1,43 @@ +{% extends 'components/fields/base.html' %} +{% load producten fields i18n %} + +{% block header %} + {% for field in object.bound_fields %} + {% if field.language == 'nl' %} + + {% endif %} + {% endfor %} +{% endblock %} + + +{% block body %} + {% for field in object.bound_fields %} +
+
+

+ {% if field.language == 'nl' %} + {% trans 'Nederlands' %} + {% else %} + {% trans 'Engels' %} + {% endif %} + + +

+
+
+
+ {% if field.name == "product_titel" and field.landelijke_link %} + + {% field_info field %} + + {% else %} + {% field_info field %} + {% endif %} +
+
+
+ {% endfor %} +{% endblock %} \ No newline at end of file diff --git a/src/sdg/components/templates/components/fields/select.html b/src/sdg/components/templates/components/fields/select.html new file mode 100644 index 000000000..57cc687ad --- /dev/null +++ b/src/sdg/components/templates/components/fields/select.html @@ -0,0 +1,22 @@ +{% extends 'components/fields/base.html' %} +{% load utils fields %} + +{% block header %} + +{% endblock %} + +{% block body %} +
+
+
+
+ {{ field }} +
+
+ {{ field.errors }} +
+
+{% endblock %} \ No newline at end of file diff --git a/src/sdg/components/templates/components/localized_form/localized_form.html b/src/sdg/components/templates/components/localized_form/localized_form.html deleted file mode 100644 index af0565d6c..000000000 --- a/src/sdg/components/templates/components/localized_form/localized_form.html +++ /dev/null @@ -1,105 +0,0 @@ -{% load i18n toggle utils %} - -{% if include_form %} -
- {# Meta. #} - {% csrf_token %} - {{ formset.management_form }} - {% endif %} - - {# Each object is a dict with bound_fields and field (name). #} - {% for object in object_list %} - -
- {# Form control for each locale. #} - {% for field in object.bound_fields %} - - {% endfor %} -
- {% endfor %} - {% if include_form %} -
-{% endif %} diff --git a/src/sdg/components/templates/components/nonlocalized_form/nonlocalized_form.html b/src/sdg/components/templates/components/nonlocalized_form/nonlocalized_form.html deleted file mode 100644 index 6b8056ba3..000000000 --- a/src/sdg/components/templates/components/nonlocalized_form/nonlocalized_form.html +++ /dev/null @@ -1,57 +0,0 @@ -{% load i18n toggle utils %} - -{% if include_form %} -
- {# Meta. #} - {% csrf_token %} - {% endif %} - - {# Each object is a dict with bound_fields and field (name). #} - {% for object in object_list %} - -
- {# Form control for each locale. #} - {% for field in object.bound_fields %} - - {% endfor %} -
- {% endfor %} - {% if include_form %} -
-{% endif %} diff --git a/src/sdg/components/templates/components/update_form/base.html b/src/sdg/components/templates/components/update_form/base.html new file mode 100644 index 000000000..eec4ce51c --- /dev/null +++ b/src/sdg/components/templates/components/update_form/base.html @@ -0,0 +1,20 @@ +{% load update_form %} + +
+
+ + + + + + + + + {% block form %}{% endblock %} + +
+ + {% block title %}{% endblock %} +
+
+
\ No newline at end of file diff --git a/src/sdg/components/templates/components/update_form/update_form_general.html b/src/sdg/components/templates/components/update_form/update_form_general.html new file mode 100644 index 000000000..c19782167 --- /dev/null +++ b/src/sdg/components/templates/components/update_form/update_form_general.html @@ -0,0 +1,42 @@ + +{% extends 'components/update_form/base.html' %} +{% load utils static i18n producten fields %} + +{% block title %} + + 1. {% trans "Algemene gegevens" %} + +{% endblock %} + +{% block form %} + {% if not product.is_referentie_product %} + + {% core inline=True label=_("Doelgroep")|capfirst tooltip=doelgroep.help_text value=doelgroep.value %} + + {% select inline=True field=product_form.product_aanwezig %} + + {% localized inline=True object=localized_object_dict.product_aanwezig_toelichting %} + + {% select inline=True field=product_form.product_valt_onder %} + + {% localized inline=True object=localized_object_dict.product_valt_onder_toelichting %} + + {% checkbox inline=True field=product_form.locaties %} + + {% endif %} + + {# DISABLED PER REQUEST VNG #} + {% select hide_element=True inline=True field=product_form.api_verborgen %} + + {% select inline=True field=product_form.bevoegde_organisatie %} + + {% with configuration=publication_date.configuration.label %} + {% core inline=True label=configuration.label|default:_("Publicatie datum")|capfirst tooltip=configuration.tooltip|default:_('Publicatiedatum van de productversie') value=publication_date.value|default:"Concept" %} + {% endwith %} + + {% select inline=True field=product_form.heeft_kosten %} + + {% core inline=True label=_("Informatiegebied") tooltip=_("Informatiegebied in verband met dit product.") value=areas|join:", " %} + + {% nonlocalized inline=True object=nonlocalized_object_dict.interne_opmerkingen %} +{% endblock %} \ No newline at end of file diff --git a/src/sdg/components/templates/components/update_form/update_form_generic.html b/src/sdg/components/templates/components/update_form/update_form_generic.html new file mode 100644 index 000000000..641983416 --- /dev/null +++ b/src/sdg/components/templates/components/update_form/update_form_generic.html @@ -0,0 +1,15 @@ +{% extends 'components/update_form/base.html' %} + +{% load i18n fields %} + +{% block title %} + + 2. {% trans "Generieke gegevens" %} + +{% endblock %} + +{% block form %} + {% for object in object_list %} + {% readonly object=object %} + {% endfor %} +{% endblock %} \ No newline at end of file diff --git a/src/sdg/components/templates/components/update_form/update_form_specific.html b/src/sdg/components/templates/components/update_form/update_form_specific.html new file mode 100644 index 000000000..fb593ed23 --- /dev/null +++ b/src/sdg/components/templates/components/update_form/update_form_specific.html @@ -0,0 +1,23 @@ +{% extends 'components/update_form/base.html' %} + +{% load i18n fields utils %} + +{% block title %} + + 3. {% trans "Specifieke gegevens" %} + +{% endblock %} + +{% block form %} + {% for form in form.forms %} + {{ form.non_field_errors }} + {{ form.id.as_hidden }} + {{ form.taal.as_hidden }} + {% endfor %} + + {% for object in object_list %} + {% localized object=object %} + {% endfor %} + + {% localized_url form=form %} +{% endblock %} \ No newline at end of file diff --git a/src/sdg/components/templatetags/fields.py b/src/sdg/components/templatetags/fields.py new file mode 100644 index 000000000..6abb78821 --- /dev/null +++ b/src/sdg/components/templatetags/fields.py @@ -0,0 +1,197 @@ +from django import template + +register = template.Library() + + +@register.inclusion_tag("components/fields/checkbox.html", takes_context=True) +def checkbox(context, field, **kwargs): + """ + Checkbox field, built for update view. + + Args: + - context + - field + + Kwargs: + - as_row, define if the checkbox field is placed inside a tr and td. + - inline, define if the checkbox field is rendered inline. + """ + + as_row = kwargs.get("as_row", True) + inline = kwargs.get("inline", False) + + return { + **kwargs, + "context": context, + "field": field, + "as_row": as_row, + "inline": inline, + } + + +@register.inclusion_tag("components/fields/core.html", takes_context=True) +def core(context, label, tooltip, value, **kwargs): + """ + Core (read-only) field, built for update view. + + Args: + - context + - label + - tooltip + - value + + Kwargs: + - as_row, define if the core field is placed inside a tr and td. + - inline, define if the core field is rendered inline. + """ + + inline = kwargs.get("inline", False) + as_row = kwargs.get("as_row", True) + + return { + **kwargs, + "context": context, + "label": label, + "as_row": as_row, + "tooltip": tooltip, + "value": value, + "inline": inline, + } + + +@register.inclusion_tag("components/fields/localized.html", takes_context=True) +def localized(context, object, **kwargs): + """ + Localized field, built for update view. + + Args: + - context + - object, containing the bound_fields to render + + Kwargs: + - as_row, define if the localized field is placed inside a tr and td. + - inline, define if the localized field is rendered inline. + """ + + inline = kwargs.get("inline", False) + as_row = kwargs.get("as_row", True) + + return { + **kwargs, + "context": context, + "as_row": as_row, + "inline": inline, + "object": object, + } + + +@register.inclusion_tag("components/fields/localized_url.html", takes_context=True) +def localized_url(context, form, **kwargs): + """ + Localized url field, built for update view. + + Args: + - context + - form, the form property to extract some date from. + + Kwargs: + - as_row, define if the localized url field is placed inside a tr and td. + """ + + as_row = kwargs.get("as_row", True) + + def object_format(form): + return { + "label": form["decentrale_procedure_label"], + "link": form["decentrale_procedure_link"], + "language": form["taal"].value, + } + + def get_object_list(forms): + object_list = [object_format(form) for form in forms] + return object_list + + return { + "context": context, + "bound_fields": get_object_list(form.forms), + "form": form, + "as_row": as_row, + } + + +@register.inclusion_tag("components/fields/nonlocalized.html", takes_context=True) +def nonlocalized(context, object, **kwargs): + """ + Non-localized field, built for update view. + + Args: + - context + - object, containing the bound_fields to render + + Kwargs: + - as_row, define if the non-localized field is placed inside a tr and td. + - inline, define if the non-localized field is rendered inline. + """ + + as_row = kwargs.get("as_row", True) + inline = kwargs.get("inline", False) + + return { + **kwargs, + "context": context, + "as_row": as_row, + "object": object, + "inline": inline, + } + + +@register.inclusion_tag("components/fields/select.html", takes_context=True) +def select(context, field, **kwargs): + """ + Select field, built for update view. + + Args: + - context + - field + + Kwargs: + - as_row, define if the select field is placed inside a tr and td. + - inline, define if the select field is rendered inline. + + """ + + as_row = kwargs.get("as_row", True) + inline = kwargs.get("inline", False) + hide_element = kwargs.get("hide_element", False) + + return { + **kwargs, + "context": context, + "field": field, + "as_row": as_row, + "inline": inline, + "hide_element": hide_element, + } + + +@register.inclusion_tag("components/fields/readonly.html", takes_context=True) +def readonly(context, object, **kwargs): + """ + Readonly field, built for update view. + + Args: + - context + - object, containing the bound_fields to render + + Kwargs: + - as_row, define if the readonly field is placed inside a tr and td. + """ + + as_row = kwargs.get("as_row", True) + + return { + **kwargs, + "context": context, + "as_row": as_row, + "object": object, + } diff --git a/src/sdg/components/templatetags/localized_form.py b/src/sdg/components/templatetags/localized_form.py deleted file mode 100644 index a13de5e01..000000000 --- a/src/sdg/components/templatetags/localized_form.py +++ /dev/null @@ -1,68 +0,0 @@ -from django import template -from django.forms import BaseFormSet - -from sdg.core.constants import TaalChoices - -register = template.Library() - - -@register.inclusion_tag( - "components/localized_form/localized_form.html", takes_context=True -) -def localized_form(context, formset: BaseFormSet, **kwargs) -> dict: - """ - Localized form, built for update view. - - Args: - - context - - formset: BaseFormSet - Forms (one for each language) to render.ked. - - Kwargs: - - [include_form]: bool - Whether to render the form tags. - - [fields]: string[] - The fields names to render. - """ - - def get_fields() -> list: - try: - base_form = formset.forms[0] - base_form_fields = base_form.fields - except IndexError: - return [] - - fields = kwargs.get("fields") - if fields: - return [field for field in base_form_fields if field in fields] - return base_form_fields - - def get_languages() -> list: - languages = [ - form.initial.get("taal") - for form in formset.forms - if form.initial.get("taal") - ] - return languages or list(TaalChoices.labels.keys()) - - def get_object_list(formset: BaseFormSet, fields: list) -> list: - object_list = [] - languages = get_languages() - for field in fields: - obj = { - "bound_fields": [form[field] for form in formset.forms], - "field": field, - } - for index, language in enumerate(languages): - obj["bound_fields"][index].language = language - object_list.append(obj) - return object_list - - fields = get_fields() - - return { - **kwargs, - "context": context, - "include_form": kwargs.get("include_form", True), - "formset": formset, - "fields": fields, - "languages": get_languages(), - "object_list": get_object_list(formset, fields), - } diff --git a/src/sdg/components/templatetags/nonlocalized_form.py b/src/sdg/components/templatetags/nonlocalized_form.py deleted file mode 100644 index 767c697a6..000000000 --- a/src/sdg/components/templatetags/nonlocalized_form.py +++ /dev/null @@ -1,53 +0,0 @@ -from django import template -from django.forms import BaseForm - -register = template.Library() - - -@register.inclusion_tag( - "components/nonlocalized_form/nonlocalized_form.html", takes_context=True -) -def nonlocalized_form(context, form: BaseForm, **kwargs) -> dict: - """ - Non-localized Form, built for update view. - - Args: - - context - - form: BaseFormSet - form to render. - - Kwargs: - - [include_form]: bool - Whether to render the form tags. - - [fields]: string[] - The fields names to render. - """ - - def get_fields() -> list: - try: - base_form_fields = form.fields - except IndexError: - return [] - - fields = kwargs.get("fields") - if fields: - return [field for field in base_form_fields if field in fields] - return base_form_fields - - def get_object_list(form: BaseForm, fields: list) -> list: - object_list = [] - for field in fields: - obj = { - "bound_fields": [form[field]], - "field": field, - } - object_list.append(obj) - return object_list - - fields = get_fields() - - return { - **kwargs, - "context": context, - "include_form": kwargs.get("include_form", True), - "form": form, - "fields": fields, - "object_list": get_object_list(form, fields), - } diff --git a/src/sdg/components/templatetags/update_form.py b/src/sdg/components/templatetags/update_form.py new file mode 100644 index 000000000..aa2dd8194 --- /dev/null +++ b/src/sdg/components/templatetags/update_form.py @@ -0,0 +1,170 @@ +from django import template +from django.forms import BaseFormSet, BaseForm + +from sdg.producten.utils import get_languages, get_fields + +register = template.Library() + + +@register.inclusion_tag( + "components/update_form/update_form_generic.html", takes_context=True +) +def update_form_generic(context) -> dict: + """ + Generic form (readonly), built for update view. + + Args: + - context + - product as generic_products + """ + # Define the name of the form + form_name = "form__generic" + + # Get some properties from the context + generic_products = context.get("generic_products") + formset = context.get("formset") + + def get_object_list(): + obj_list = [] + + fields = generic_products[0].template_fields + + for index, field in enumerate(fields): + obj = { + "bound_fields": [ + product.template_fields[index] for product in generic_products + ], + "field_name": field.name, + } + + for index, language in enumerate(get_languages(formset)): + obj["bound_fields"][index].language = language + obj["bound_fields"][index].landelijke_link = generic_products[ + index + ].landelijke_link + + obj_list.append(obj) + + return obj_list + + return { + "context": context, + "form_name": form_name, + "object_list": get_object_list(), + } + + +@register.inclusion_tag( + "components/update_form/update_form_specific.html", takes_context=True +) +def update_form_specific(context) -> dict: + """ + Specific form, built for update view. + + Args: + - context + """ + # Define the name of the form + form_name = "form__specific" + + # Get some properties from the context + localized_form_fields = context.get("localized_form_fields") + formset: BaseFormSet = context.get("formset") + form = context.get("formset") + + # Get languages and fields form utils functions. + languages = get_languages(formset) + fields = get_fields(formset.forms[0], localized_form_fields) + + def get_object_list(formset: BaseFormSet, fields: list) -> list: + object_list = [] + for field in fields: + obj = { + "bound_fields": [form[field] for form in formset.forms], + "field": field, + } + for index, language in enumerate(languages): + obj["bound_fields"][index].language = language + object_list.append(obj) + return object_list + + return { + "context": context, + "form": form, + "form_name": form_name, + "object_list": get_object_list(formset, fields), + } + + +@register.inclusion_tag( + "components/update_form/update_form_general.html", takes_context=True +) +def update_form_general(context) -> dict: + """ + General form, built for update view. + + Args: + - context + """ + # Define the name of the form + form_name = "form__general" + + # Get some properties from the context + areas = context.get("areas") + doelgroep = context.get("doelgroep") + formset: BaseFormSet = context.get("formset") + product = context.get("product") + product_form = context.get("product_form") + version_form: BaseForm = context.get("version_form") + + # Get the used languages in the formset + languages = get_languages(formset) + + # Localized fields in the general update form + localized_field_names = [ + "product_aanwezig_toelichting", + "product_valt_onder_toelichting", + ] + localized_fields = get_fields(formset.forms[0], localized_field_names) + + # Nonlocalized fields in the general update form + nonlocalized_field_names = ["interne_opmerkingen"] + nonlocalized_fields = get_fields(version_form, nonlocalized_field_names) + + # Get publication date + publication_date = product.active_version.get_field("publicatie_datum") + + def get_localized_object_dict(formset: BaseFormSet, fields: list) -> dict: + object_list = {} + for field in fields: + obj = { + "bound_fields": [form[field] for form in formset.forms], + "field": field, + } + for index, language in enumerate(languages): + obj["bound_fields"][index].language = language + object_list[field] = obj + return object_list + + def get_nonlocalized_object_dict(form: BaseForm, fields: list) -> dict: + object_list = {} + for field in fields: + obj = { + "bound_fields": [form[field]], + "field": field, + } + object_list[field] = obj + return object_list + + return { + "areas": areas, + "context": context, + "doelgroep": doelgroep, + "form_name": form_name, + "localized_object_dict": get_localized_object_dict(formset, localized_fields), + "nonlocalized_object_dict": get_nonlocalized_object_dict( + version_form, nonlocalized_fields + ), + "product_form": product_form, + "publication_date": publication_date, + } diff --git a/src/sdg/js/components/form/abstract/form_component.js b/src/sdg/js/components/form/abstract/form_component.js index 81fcf1435..715ec108a 100644 --- a/src/sdg/js/components/form/abstract/form_component.js +++ b/src/sdg/js/components/form/abstract/form_component.js @@ -1,6 +1,5 @@ -import {Component} from '../../abstract/component'; -import {availableEditors} from '../../markdown'; - +import { Component } from "../../abstract/component"; +import { availableEditors } from "../../markdown"; /** * Base class for implementing components within the update form. @@ -12,183 +11,200 @@ export class FormComponent extends Component { * Use this to define EventListeners, MutationObservers etc. */ bindEvents() { - this.node.addEventListener('click', this.onClick.bind(this)); + this.node.addEventListener("click", this.onClick.bind(this)); } /** * Gets called when this.node gets clicked. * @param {MouseEvent} event */ - onClick(event) { - } + onClick(event) {} /** - * Returns the form control (table cell) containing this button. - * @param {HTMLElement} [child] - * @param {string} [language] - * @return {HTMLElement} + * Returns the form field containing multiple fields. + * @param {HTMLElement} child + * @returns {HTMLElement} */ - getFormControl(child = undefined) { - return this._getParent('form__control', child); + getFormField(child = undefined) { + return this._getParent("form__field", child); } /** - * Returns whether the form control is disabled. - * @return {boolean} + * Return the current reference form. + * @param {String} language + * @return {HTMLTemplateElement} */ - getFormControlDisabled() { - return this.getFormControl().classList.contains('form__control--disabled'); + getCurrentReferenceForm(language) { + return document.querySelector(`.form__reference-${language}`); } /** - * Sets the disabled state of the form control. - * @param {boolean} disabled - * @param {HTMLElement} [formControl] + * Returen the previous reference form. + * @return {HTMLTemplateElement} */ - setFormControlDisabled(disabled, formControl = this.getFormControl()) { - formControl.classList.toggle('form__control--disabled', disabled); - const toggleInput = formControl.querySelector('.toolbar .toggle input '); - - // Update toggle. - if (toggleInput) { - toggleInput.checked = !disabled; - } - - // Update input/select/textarea. - [...formControl.querySelectorAll(` - .form__control input, - .form__control select, - .form__control textarea - `)].forEach((field) => { - field.readOnly = disabled; - }); - - // Update reference button. - formControl.querySelector('.form__control .form__reference-btn')?.toggleAttribute('disabled', disabled); + getPreviousReferenceForm(language) { + return document.querySelector(`.form__previousreference-${language}`); } /** - * Returns the container for the field. + * Returns the reference toolbar. + * @param {HTMLElement} child * @return {HTMLElement} */ - getFieldContainer() { - return this.getFormControl().querySelector(`.form__control-body`); + getReferenceTextToolbar(child = undefined) { + return this.getFormField(child).querySelector(".form__field-toolbar"); } /** - * Returns either the input or textarea for this diff button. - * @return {(HTMLInputElement|HTMLTextAreaElement)} + * Returns the language of the control - works only from a nested element inside `.form__control` + * @param {HTMLElement} child + * @returns {string} language string defined on the '.form__control' element - eg. 'nl' */ - getInputOrTextarea() { - return this.getFieldContainer().querySelector('input, textarea'); + getControlLanguage(child = undefined) { + return this._getParent("form__control", child).lang; } /** - * Returns a single HTMLElement containig all visible fields for the input or textarea. + * Returns an object containing a property for each different `.reference` and it's language. + * @param {HTMLElement} child + * @returns {{ [language: string]: HTMLElement }} */ - getVisibleInputOrTextarea() { - const fieldContainer = this.getFieldContainer(); - const markdownxField = fieldContainer.querySelector('input, .markdownx') - - if (markdownxField) { - return markdownxField.parentElement - } + getReferenceElements(child = undefined) { + const nodes = this.getFormField(child).querySelectorAll(".reference"); + return this._localizedGetter(nodes); } /** - * Returns all the language wrappers. - * @return {HTMLElement} + * Returns an object containing a property for each different `.reference__preview` and it's language. + * @param {HTMLElement} child + * @returns {{ [language: string]: HTMLElement }} */ - getLanguageWrappers() { - return this._getParent('form').querySelectorAll('.form__language-wrapper'); + getReferencePreviewElements(child = undefined) { + const nodes = this.getFormField(child).querySelectorAll( + ".reference__preview" + ); + return this._localizedGetter(nodes); } /** - * Returns the language wrapper for child. - * @param [child] - * @return {HTMLElement} + * Returns an object containing a property for each different `.reference__versions` and it's language. + * @param {HTMLElement} child + * @returns {{ [language: string]: HTMLElement }} */ - getLanguageWrapper(child) { - return this._getParent('form__language-wrapper', child); + getReferenceVersionElements(child = undefined) { + const nodes = this.getFormField(child).querySelectorAll( + ".reference__versions" + ); + return this._localizedGetter(nodes); } /** - * Returns the language of the field container containing `this.node`. - * @return {string} + * Returns an object containing a property for each different `.diff` and it's language. + * @param {HTMLElement} child + * @returns {{ [language: string]: HTMLElement }} */ - getLanguage() { - return this.getFieldContainer().lang; + getDiffElements(child = undefined) { + const nodes = this.getFormField(child).querySelectorAll(".diff"); + return this._localizedGetter(nodes); } /** - * Returns the active language. - * @return {string} + * Returns an object containing a property for each different `.diff__preview` and it's language. + * @param {HTMLElement} child + * @returns {{ [language: string]: HTMLElement }} */ - getActiveLanguage() { - return this.getLanguageWrapper().lang; + getDiffPreviewElements(child = undefined) { + const nodes = + this.getFormField(child).querySelectorAll(".diff__preview"); + return this._localizedGetter(nodes); } /** - * Set the languages. - * @param {string} language - * @param {boolean} [global=false] Whether to set all form controls to language. + * Returns an object containing a property for each different `.diff__versions` and it's language. + * @param {HTMLElement} child + * @returns {{ [language: string]: HTMLElement }} */ - setActiveLanguage(language, global = false) { - const languageWrappers = (global) ? this.getLanguageWrappers() : [this.getLanguageWrapper()]; - - [...languageWrappers].forEach((languageWrapper) => { - languageWrapper.lang = language; - - [...languageWrapper.querySelectorAll('.form__control')].forEach((formControl) => { - if (formControl.lang === language) { - formControl.removeAttribute('aria-hidden'); - } else { - formControl.setAttribute('aria-hidden', true); - } - }); - }) + getDiffVersionElements(child = undefined) { + const nodes = + this.getFormField(child).querySelectorAll(".diff__versions"); + return this._localizedGetter(nodes); } /** - * Returns the table. - * @param {HTMLTableElement} [child] - * @return {HTMLTableElement} + * Returns an object containing a property for each different `input, textarea` and it's language. + * @param {HTMLElement} child + * @returns {{ [language: string]: HTMLElement }} */ - getTable(child) { - return this._getParent('tabs__table', child); + getInputOrTextareas(child = undefined) { + const nodes = + this.getFormField(child).querySelectorAll("input, textarea"); + return this._localizedGetter(nodes); } /** - * Returns the HTMLElement containing the version labels. - * @return {HTMLElement} + * Returns an object containing all visible fields as value with the key as the language. + * @returns {{ [language: string]: HTMLElement} } */ - getVersionsContainer() { - return this.getFormControl().querySelector('.tabs__table-cell--versions'); + getVisibleInputOrTextareas(child = undefined) { + const nodes = + this.getFormField(child).querySelectorAll("input, .markdownx"); + return this._localizedGetter(nodes, "parentElement"); } /** - * Returns the value of either the input or textarea. - * @return {string} + * Returns the values of either the inputs or textareas. + * @returns {{ [language: string]: string} } */ - getValue() { - const inputOrTextarea = this.getInputOrTextarea(); - return availableEditors[inputOrTextarea.id]?.getValue() || inputOrTextarea.value + getValues() { + return Object.entries(this.getInputOrTextareas()).reduce( + (acc, [language, node]) => { + const value = availableEditors[node.id]?.getValue(); + if (value) acc[language] = value; + else if (value == "") acc[language] = value; + else acc[language] = node.value; + return acc; + }, + {} + ); } /** * Sets the value of either the input or textarea. + * @param {string} id * @param {string} value */ - setValue(value) { - const inputOrTextarea = this.getInputOrTextarea(); - const editor = availableEditors[inputOrTextarea.id]; + setValue(id, value) { + const editor = availableEditors[id]; + const inputOrTextareas = this.getInputOrTextareas(); if (editor) { editor.setValue(value); return; } - inputOrTextarea.value = value; + Object.values(inputOrTextareas).forEach((node) => { + if (node.id == id) node.value = value; + }); + } + + /** + * Format the nodes in an object that has the key as language and the element als value. + * @param {NodeListOf} nodes + * @param {string} nodeProperty a property of node that can be returned + * @returns {{ [language: string]: HTMLElement} } + * @private + */ + _localizedGetter(nodes, nodeProperty = undefined) { + return Array.from(nodes).reduce((acc, node) => { + const language = node.lang + ? node.lang + : this.getControlLanguage(node); + + // return the property of a node under the key of the language. + if (nodeProperty) acc[language] = node[nodeProperty]; + else acc[language] = node; + return acc; + }, {}); } /** @@ -207,7 +223,9 @@ export class FormComponent extends Component { i++; if (!iteratedNode || i > 100) { - throw new Error(`Maximum recursion depth exceeded while localizing parent with className ${className} for ${child}.`); + throw new Error( + `Maximum recursion depth exceeded while localizing parent with className ${className} for ${child}.` + ); } } diff --git a/src/sdg/js/components/form/abstract/reference_text_component.js b/src/sdg/js/components/form/abstract/reference_text_component.js index d4cdeaa6d..06c8c05ba 100644 --- a/src/sdg/js/components/form/abstract/reference_text_component.js +++ b/src/sdg/js/components/form/abstract/reference_text_component.js @@ -2,8 +2,8 @@ * Base class for implementing components within the update form. * @abstract */ -import showdown from 'showdown'; -import {FormComponent} from './form_component'; +import showdown from "showdown"; +import { FormComponent } from "./form_component"; export class ReferenceTextComponent extends FormComponent { /** @@ -14,7 +14,7 @@ export class ReferenceTextComponent extends FormComponent { */ static options = { observe: true, - } + }; /** * Binds events to callbacks. @@ -22,7 +22,11 @@ export class ReferenceTextComponent extends FormComponent { */ bindEvents() { super.bindEvents(); - this.getReferenceTextToolbar().querySelector('.form__reference-btn').addEventListener('click', () => this.setState({active: false})); + + // Hide the references if the reference copied and used. + this.getReferenceTextToolbar() + .querySelector(".form__reference-btn") + .addEventListener("click", () => this.setState({ active: false })); } /** @@ -45,8 +49,10 @@ export class ReferenceTextComponent extends FormComponent { onMutation(mutationRecord) { super.onMutation(mutationRecord); - const disabled = Boolean(mutationRecord.target[mutationRecord.attributeName]); - this.setState({disabled: disabled}); + const disabled = Boolean( + mutationRecord.target[mutationRecord.attributeName] + ); + this.setState({ disabled: disabled }); this.updateDisabled(); this.updateLabel(); @@ -56,8 +62,13 @@ export class ReferenceTextComponent extends FormComponent { * Updates the disabled state based on whether reference HTML is available. */ updateDisabled() { - if (!this.getReferenceHTML()) { - this.setState({disabled: true}) + const referenceHTML = this.getCurrentReferenceHTML(); + + if ( + !referenceHTML || + Object.values(referenceHTML).every((value) => !value) + ) { + this.setState({ disabled: true }); } } @@ -67,104 +78,103 @@ export class ReferenceTextComponent extends FormComponent { updateLabel() { // Make sure we keep track of the original label. if (!this.state.originalLabel) { - this.setState({originalLabel: this.node.textContent}); + this.setState({ originalLabel: this.node.textContent }); } + const referenceHTML = this.getCurrentReferenceHTML(); // Show custom label if no reference text is available. - if (!this.getReferenceHTML()) { - this.setState({label: 'Geen standaardtekst beschikbaar'}); + if (!referenceHTML || Object.values(referenceHTML).every((v) => !v)) { + this.setState({ label: "Geen standaardteksten beschikbaar" }); } else if (this.state.originalLabel) { - this.setState({label: this.state.originalLabel}); + this.setState({ label: this.state.originalLabel }); } } /** - * Returns the current version data. - * @return {{input: (HTMLInputElement|HTMLTextAreaElement), title: string}} + * Returns an array containing the current version data for each language + * @return {{ + * [language: string]: { + * input: HTMLElement, + * title: string + * } + * }} */ getCurrentVersionData() { - const inputOrTextarea = this.getInputOrTextarea(); - const currentReferenceForm = this.getCurrentReferenceForm(); - const currentReferenceInput = currentReferenceForm.content.querySelector(`#${inputOrTextarea?.id}`); - - return { - 'title': currentReferenceForm.dataset.title, - 'input': currentReferenceInput, - }; - } - - /** - * Returns the previous version data. - * @return {{input: (HTMLInputElement|HTMLTextAreaElement), title: string}} + return Object.entries(this.getInputOrTextareas()).reduce( + (acc, [language, node]) => { + const currentReferenceForm = + this.getCurrentReferenceForm(language); + const referenceField = + currentReferenceForm.content.getElementById(node.id); + acc[language] = { + input: referenceField, + title: currentReferenceForm.dataset.title, + }; + + return acc; + }, + {} + ); + } + + /** + * Returns an array containing the previous version data for each language + * @return {{ + * [language: string]: { + * input: HTMLElement, + * title: string + * } + * }} */ getPreviousVersionData() { - const inputOrTextarea = this.getInputOrTextarea(); - const previousReferenceForm = this.getPreviousReferenceForm(); - const previousReferenceInput = previousReferenceForm.content.querySelector(`#${inputOrTextarea?.id}`); - - return { - 'title': previousReferenceForm.dataset.title, - 'input': previousReferenceInput, - }; - } + const inputOrTextareas = this.getInputOrTextareas(); - /** - * Returen the reference form. - * @return {HTMLTemplateElement} - */ - getCurrentReferenceForm() { - return document.querySelector(`.form__reference-${this.getLanguage()}`); - } + return Object.entries(inputOrTextareas).reduce( + (acc, [language, node]) => { + const previousFormReference = + this.getPreviousReferenceForm(language); + const referenceField = + previousFormReference.content.getElementById(node.id); + acc[language] = { + input: referenceField, + title: previousFormReference.dataset.title, + }; - /** - * Returen the previous reference form. - * @return {HTMLTemplateElement} - */ - getPreviousReferenceForm() { - return document.querySelector(`.form__previousreference-${this.getLanguage()}`); + return acc; + }, + {} + ); } /** - * Returns the reference topbar. - * @return {HTMLElement} - */ - getReferenceTopbar() { - return this.getFormControl().querySelector('.reference__display--topbar'); - } - - /** - * Returns the reference container. - * @return {HTMLElement} - */ - getReferenceTextContainer() { - return this.getFormControl().querySelector('.form__reference'); - } - - /** - * Returns the reference text template. - * @return {HTMLTemplateElement} + * Returns the reference HTML. + * @return {{[language: string]: any}} */ - getReferenceTemplate() { - return document.querySelector(".form__reference--display-template"); + getCurrentReferenceHTML() { + return Object.entries(this.getCurrentVersionData()).reduce( + (acc, [language, { input }]) => { + acc[language] = new showdown.Converter({ + tables: true, + }).makeHtml(input.value); + return acc; + }, + {} + ); } /** - * Returns the reference toolbar. - * @return {HTMLElement} + * Create the references below each field + * @param {{[language: string]: any}} referenceHTML */ - getReferenceTextToolbar() { - return this.getFormControl().querySelector('.form__reference + .toolbar'); - } + createCurrentReferences() { + const referenceHTML = this.getCurrentReferenceHTML(); - /** - * Returns the reference HTML. - * @return {string} - */ - getReferenceHTML() { - const inputOrTextarea = this.getInputOrTextarea(); - const referenceForm = this.getCurrentReferenceForm(); - const referenceField = referenceForm.content.getElementById(inputOrTextarea?.id); - return new showdown.Converter({tables: true}).makeHtml(referenceField?.value); + Object.entries(this.getReferencePreviewElements()).forEach( + ([language, node]) => { + if (referenceHTML[language]) + node.innerHTML = referenceHTML[language]; + } + ); } /** @@ -174,16 +184,16 @@ export class ReferenceTextComponent extends FormComponent { * @param {Object} state Read only state. */ render(state) { - const {active, disabled, label} = state; + const { active, disabled, label } = state; super.render(state); - this.node.classList.toggle('button--active', Boolean(active)); + this.node.classList.toggle("button--active", Boolean(active)); if (disabled === true) { - this.node.setAttribute('disabled', true); + this.node.setAttribute("disabled", true); } else if (disabled === false) { - this.node.removeAttribute('disabled'); + this.node.removeAttribute("disabled"); } if (label) { diff --git a/src/sdg/js/components/form/form_diff_button.js b/src/sdg/js/components/form/form_diff_button.js index cf865d1a4..bab504a56 100644 --- a/src/sdg/js/components/form/form_diff_button.js +++ b/src/sdg/js/components/form/form_diff_button.js @@ -1,11 +1,8 @@ -import Diff from 'text-diff'; -import showdown from 'showdown'; +import { ReferenceTextComponent } from "./abstract/reference_text_component"; +import { hideElement, returnDiffHTML, showElement } from "./utils"; -import {ReferenceTextComponent} from './abstract/reference_text_component'; - - -/** @type {NodeListOf} */ -const DIFF_BUTTONS = document.querySelectorAll('.form__diff-btn'); +/** @type {NodeListOf} */ +const DIFF_BUTTONS = document.querySelectorAll(".form__diff-btn"); /** * Button showing diffs between user and stored value of an input. @@ -17,105 +14,88 @@ class FormDiffButton extends ReferenceTextComponent { */ onClick(event) { event.preventDefault(); - this.setState({active: !this.state.active, diffHTML: this.getDiffHTML()}); + this.setState({ + active: !this.state.active, + diffHTML: this.getDiffHTML(), + }); + } + + /** + * Get the diff HTML containing ins and del elements. + * @return {{ [language: string]: string }} Object with each key as a language and a property of the string containing the diff. + */ + getDiffHTML() { + const currentVersionData = this.getCurrentVersionData(); + + return Object.entries(currentVersionData).reduce( + (acc, [language, { input }]) => { + const values = this.getValues(); + console.log(values); + const currentVersionValue = input.value; + const currentValue = values[language]; + acc[language] = returnDiffHTML( + currentVersionValue, + currentValue + ); + return acc; + }, + {} + ); } /** * Shows input or textarea. */ showInputOrTextarea() { - const visibleInputOrTextarea = this.getVisibleInputOrTextarea(); - visibleInputOrTextarea?.style.removeProperty('display'); + Object.values(this.getVisibleInputOrTextareas()).forEach(showElement); } /** * Hides input or textarea. */ hideInputOrTextarea() { - const visibleInputOrTextarea = this.getVisibleInputOrTextarea(); - if (visibleInputOrTextarea) - visibleInputOrTextarea.style.display = 'none'; + Object.values(this.getVisibleInputOrTextareas()).forEach(hideElement); } /** * Shows the diff. */ showDiff() { - const fieldContainer = this.getFieldContainer(); - const versionsContainer = this.getVersionsContainer(); - const diffElement = fieldContainer.querySelector('.diff'); - versionsContainer.style.removeProperty('display'); - - if (diffElement) { - diffElement.style.removeProperty('display'); - } + Object.values(this.getDiffElements()).forEach(showElement); } /** * Hides the diff. */ hideDiff() { - const fieldContainer = this.getFieldContainer(); - const versionsContainer = this.getVersionsContainer(); - const diffElement = fieldContainer.querySelector('.diff'); - versionsContainer.style.display = 'none'; - - if (diffElement) { - diffElement.style.display = 'none'; - } + Object.values(this.getDiffElements()).forEach(hideElement); } /** - * Updates the diff. - * @return {string} HTML string containing diff. + * Render diffHTMl into the preview element + * @param {{ [language: string]: string } | undefined} diffHTML */ - getDiffHTML() { - const currentVersionData = this.getCurrentVersionData(); - let referenceValue = currentVersionData.input.value; - let ownValue = this.getValue(); - - const diff = new Diff({timeout: 0, editCost: 4}); - const textDiff = diff.main(referenceValue, ownValue); - diff.cleanupEfficiency(textDiff) - const prettyHtml = diff.prettyHtml(textDiff).replace(/<\/?span[^>]*>/g,"").replace(//g, "\n"); - return new showdown.Converter({tables: true}).makeHtml(prettyHtml) - } - - /** - * Renders the diff element. - * @param {Object} state Read only state. - */ - renderDiffElement(state) { - const {diffHTML} = state; - - if (!this.diffElement) { - const fieldContainer = this.getFieldContainer(); - this.diffElement = document.createElement('div'); - this.diffElement.classList.add('form__input', 'diff', 'tabs__table-cell', 'tabs__table-cell--value'); - fieldContainer.append(this.diffElement); - } - - this.diffElement.innerHTML = diffHTML; + renderDiffPreview(diffHTML) { + console.log(diffHTML); + if (!diffHTML) return; + Object.entries(this.getDiffPreviewElements()).forEach( + ([language, node]) => (node.innerHTML = diffHTML[language]) + ); } /** - * Renders the diff element. + * Render the diff versions into the versions element + * @param {{ [language: string]: string } | undefined} diffHTML */ - renderVersionContainer() { - const versionsContainer = this.getVersionsContainer(); - - if (!versionsContainer.children.length) { - const currentVersionData = this.getCurrentVersionData(); - - const currentVersionTopElement = document.createElement('ins'); - const previousVersionTopElement = document.createElement('del'); - - currentVersionTopElement.innerText = "Mijn tekst"; - previousVersionTopElement.innerText = currentVersionData.title; - - versionsContainer.append(currentVersionTopElement); - versionsContainer.append(previousVersionTopElement); - } + renderDiffVersions() { + Object.entries(this.getDiffVersionElements()).forEach( + ([language, node]) => { + const currentVersionData = this.getCurrentVersionData(); + node.querySelector("ins").innerText = "Mijn text"; + node.querySelector("del").innerText = + currentVersionData[language].title; + } + ); } /** @@ -127,12 +107,12 @@ class FormDiffButton extends ReferenceTextComponent { render(state) { super.render(state); - const {active} = state; + const { active, diffHTML } = state; - this.renderDiffElement(state); - this.renderVersionContainer(); + this.renderDiffVersions(); if (active) { + this.renderDiffPreview(diffHTML); this.hideInputOrTextarea(); this.showDiff(); } else { diff --git a/src/sdg/js/components/form/global_language_switch.js b/src/sdg/js/components/form/global_language_switch.js deleted file mode 100644 index 6988d5426..000000000 --- a/src/sdg/js/components/form/global_language_switch.js +++ /dev/null @@ -1,53 +0,0 @@ -import {LanguageSwitch} from './language_switch'; - -/** @type {NodeListOf} */ -const GLOBAL_LANGUAGE_SWITCHES = document.querySelectorAll('.global-language-switch'); - -/** - * Sets the language of all form controls. - */ -class GlobalLanguageSwitch extends LanguageSwitch { - - /** - * Returns whether this language switch is global. - */ - isGlobal() { - return true; - } - - /** - * Returns the active form control. - * @return {(HTMLElement|null)} - */ - getFormControl() { - return null; - } - - /** - * Returns the icon. - * @return {SVGSVGElement} - */ - getIcon() { - return null; - } - - /** - * Returns all the language wrappers. - * @return {HTMLElement} - */ - getLanguageWrappers() { - return document.querySelectorAll('.form__language-wrapper'); - } - - - /** - * Returns the language of this button. - * @return {string} - */ - getLanguage() { - return this.node.querySelector('.toggle input[type=checkbox]').checked ? 'en' : 'nl'; - } -} - -// Start! -[...GLOBAL_LANGUAGE_SWITCHES].forEach((node) => new GlobalLanguageSwitch(node)); diff --git a/src/sdg/js/components/form/index.js b/src/sdg/js/components/form/index.js index 96421d521..312084098 100644 --- a/src/sdg/js/components/form/index.js +++ b/src/sdg/js/components/form/index.js @@ -1,7 +1,5 @@ -import './form_diff_button'; -import './reference_diff_button'; -import './language_switch'; -import './global_language_switch'; -import './show_reference_button'; -import './toggle'; -import './use_reference_button'; +import "./form_diff_button"; +import "./reference_diff_button"; +import "./show_reference_button"; +import "./use_reference_button"; +import "./utils"; diff --git a/src/sdg/js/components/form/language_switch.js b/src/sdg/js/components/form/language_switch.js deleted file mode 100644 index e3dddc6ae..000000000 --- a/src/sdg/js/components/form/language_switch.js +++ /dev/null @@ -1,142 +0,0 @@ -import {FormComponent} from './abstract/form_component'; - -/** @type {NodeListOf} */ -const LANGUAGE_SWITCHES = document.querySelectorAll('.form__language-switch'); - -/** - * Sets the language of the form control. - */ -export class LanguageSwitch extends FormComponent { - /** - * Binds events to callbacks. - * Use this to define EventListeners, MutationObservers etc. - */ - bindEvents() { - super.bindEvents(); - - if (this.isGlobal()) { - this.node.parentElement.addEventListener('click', this.onButtonGroupClick.bind(this)); - return; - } - - const formControl = this.getFormControl(); - - [...formControl.querySelectorAll('input, textarea')].forEach((inputOrTextarea) => { - inputOrTextarea.addEventListener('change', this.updateChanged.bind(this)); - }); - - const observer = new MutationObserver(this.updateActive.bind(this)); - observer.observe(formControl, {attributes: true}); - } - - /** - * Returns whether this language switch is global. - */ - isGlobal() { - return !this.getFormControl(); // Detect based on presence of form control, null means global. - } - - /** - * Returns the active form control. - * @return {(HTMLElement|null)} - */ - getFormControl() { - const language = this.getLanguage(); - try { - return this._getParent('form__language-wrapper').querySelector(`.form__control[lang=${language}]`); - } catch (e) { - return null; - } - } - - /** - * Returns the icon. - * @return {SVGSVGElement} - */ - getIcon() { - return this.node.querySelector('svg'); - } - - - /** - * Returns the language of this button. - * @return {string} - */ - getLanguage() { - return this.node.lang; - } - - /** - * Gets called before the first render cycle. - * Use this to sync state with DOM before first render. - */ - beforeMount() { - super.beforeMount(); - const active = this.node.classList.contains('button--active'); - let initialValue; - - try { - initialValue = this.getValue(); - } catch (e) { - initialValue = null; - - } - this.setState({active, initialValue}); - } - - /** - * Gets called when `this.node` is clicked. - * @param {MouseEvent} event - */ - onClick(event) { - super.onClick(event); - this.setActiveLanguage(this.getLanguage(), this.isGlobal()); - } - - /** - * Gets called when a global language switch button group is clicked. - * @param {MouseEvent} e - */ - onButtonGroupClick(e) { - const active = this.getLanguage() === e.target.lang; - this.setState({active: active}); - } - - /** - * Updates the active state based the active language. - */ - updateActive() { - const active = this.getActiveLanguage() === this.getLanguage(); - this.setState({active}); - } - - /** - * Updates the changes state based on the reference. - */ - updateChanged(e) { - const changed = this.getValue() !== this.state.initialValue; - this.setState({changed}); - } - - /** - * Renders state. - * Gets called when state gets updated. - * Use this to persist (read only) state to DOM. - * @param {Object} state Read only state. - */ - render(state) { - const {active, changed} = state; - this.node.classList.toggle('button--active', Boolean(active)); - - const icon = this.getIcon(); - if (!icon) return; - - icon.setAttribute('aria-hidden', true); - if (changed) { - icon.removeAttribute('aria-hidden'); - } - } -} - -// Start! -[...LANGUAGE_SWITCHES].forEach((node) => new LanguageSwitch(node)); diff --git a/src/sdg/js/components/form/reference_diff_button.js b/src/sdg/js/components/form/reference_diff_button.js index 313151b19..99a170011 100644 --- a/src/sdg/js/components/form/reference_diff_button.js +++ b/src/sdg/js/components/form/reference_diff_button.js @@ -1,11 +1,8 @@ -import Diff from 'text-diff'; -import showdown from 'showdown'; - -import {ReferenceTextComponent} from './abstract/reference_text_component'; - +import { ReferenceTextComponent } from "./abstract/reference_text_component"; +import { hideElement, returnDiffHTML, showElement } from "./utils"; /** @type {NodeListOf} */ -const DIFF_BUTTONS = document.querySelectorAll('.reference__diff-btn'); +const DIFF_BUTTONS = document.querySelectorAll(".reference__diff-btn"); /** * Button showing diffs between reference and previous reference. @@ -17,46 +14,77 @@ class ReferenceDiffButton extends ReferenceTextComponent { */ onClick(event) { event.preventDefault(); - this.setState({active: !this.state.active, diffHTML: this.getDiffHTML()}); + this.setState({ + active: !this.state.active, + diffHTML: this.getDiffHTML(), + }); } /** - * Updates the diff. - * @return {string} HTML string containing diff. + * Get the diff HTML containing ins and del elements. + * @return {{ [language: string]: string }} Object with each key as a language and a property of the string containing the diff. */ getDiffHTML() { const previousVersionData = this.getPreviousVersionData(); - const previousVersionValue = previousVersionData.input.value; const currentVersionData = this.getCurrentVersionData(); - const currentVersionValue = currentVersionData.input.value; - const diff = new Diff({timeout: 0, editCost: 4}); - const textDiff = diff.main(previousVersionValue, currentVersionValue); - diff.cleanupEfficiency(textDiff) - const prettyHtml = diff.prettyHtml(textDiff).replace(/<\/?span[^>]*>/g,"").replace(//g, "\n"); - return new showdown.Converter({tables: true}).makeHtml(prettyHtml); + return Object.entries(previousVersionData).reduce( + (acc, [language, { input }]) => { + const previousVersionValue = input.value; + const currentVersionValue = + currentVersionData[language].input.value; + + acc[language] = returnDiffHTML( + previousVersionValue, + currentVersionValue + ); + return acc; + }, + {} + ); } /** - * Renders the diff element. + * Shows the reference version containers */ - renderVersionContainer() { - const referenceTextContainer = this.getReferenceTextContainer().parentElement; - const versionsContainer = document.createElement('div'); - versionsContainer.classList.add('tabs__table-cell--versions'); - referenceTextContainer.prepend(versionsContainer); - - const previousVersionData = this.getPreviousVersionData(); - const currentVersionData = this.getCurrentVersionData(); + showReferenceVerions() { + Object.values(this.getReferenceVersionElements()).forEach(showElement); + } - const previousVersionTopElement = document.createElement('del'); - const currentVersionTopElement = document.createElement('ins'); + /** + * Hides the reference version containers + */ + hideReferenceVerions() { + Object.values(this.getReferenceVersionElements()).forEach(hideElement); + } - previousVersionTopElement.innerText = previousVersionData.title; - currentVersionTopElement.innerText = currentVersionData.title; + /** + * Render diffHTMl into the preview element + * @param {{ [language: string]: string } | undefined} diffHTML + */ + renderReferencePreview(diffHTML) { + if (!diffHTML) return; + Object.entries(this.getReferencePreviewElements()).forEach( + ([language, node]) => { + if (diffHTML[language]) node.innerHTML = diffHTML[language]; + } + ); + } - versionsContainer.append(previousVersionTopElement); - versionsContainer.append(currentVersionTopElement); + /** + * Renders the reference version elements. + */ + renderReferenceVersions() { + Object.entries(this.getReferenceVersionElements()).forEach( + ([language, node]) => { + const previousVersionData = this.getPreviousVersionData(); + const currentVersionData = this.getCurrentVersionData(); + node.querySelector("del").innerText = + previousVersionData[language].title; + node.querySelector("ins").innerText = + currentVersionData[language].title; + } + ); } /** @@ -67,19 +95,16 @@ class ReferenceDiffButton extends ReferenceTextComponent { */ render(state) { super.render(state); - const {active, diffHTML} = state; + const { active, diffHTML } = state; - const referenceTextContainer = this.getReferenceTextContainer(); + this.renderReferenceVersions(); - if(active) { - referenceTextContainer.innerHTML = diffHTML; - this.renderVersionContainer(); + if (active) { + this.renderReferencePreview(diffHTML); + this.showReferenceVerions(); } else { - referenceTextContainer.innerHTML = this.getReferenceHTML(); - const version = referenceTextContainer.parentNode.getElementsByClassName("tabs__table-cell--versions") - if (version !== undefined && version.length > 0) { - referenceTextContainer.parentNode.removeChild(version[0]) - } + this.createCurrentReferences(); + this.hideReferenceVerions(); } } } diff --git a/src/sdg/js/components/form/show_reference_button.js b/src/sdg/js/components/form/show_reference_button.js index bb39465fa..9d3610cb4 100644 --- a/src/sdg/js/components/form/show_reference_button.js +++ b/src/sdg/js/components/form/show_reference_button.js @@ -1,52 +1,39 @@ -import {ReferenceTextComponent} from './abstract/reference_text_component'; - +import { ReferenceTextComponent } from "./abstract/reference_text_component"; +import { hideElement, showElement } from "./utils"; /** @type {NodeListOf} */ -const SHOW_REFERENCE_BUTTONS = document.querySelectorAll('.form__display-btn'); - +const SHOW_REFERENCE_BUTTONS = document.querySelectorAll(".form__display-btn"); /** * Button allow the user to use the reference text. */ class ShowReferenceButton extends ReferenceTextComponent { - onMount() { - super.onMount(); - if(this.getInputOrTextarea()?.id === 'id_vertalingen-0-specifieke_tekst') { - window.showref=this; - } - } - /** * Gets called when this.node gets clicked. * @param {MouseEvent} event */ onClick(event) { event.preventDefault(); - this.setState({active: !this.state.active, referenceHTML: this.getReferenceHTML()}); - } - - /** - * Returns the icon. - * @return {SVGSVGElement} - */ - getIcon() { - return this.node.querySelector('svg'); + this.setState({ + active: !this.state.active, + referenceHTML: this.getCurrentReferenceHTML(), + }); } /** * Shows the reference text. */ showReferenceText() { - this.getReferenceTextContainer().removeAttribute('aria-hidden'); - this.getReferenceTextToolbar().removeAttribute('aria-hidden'); + showElement(this.getReferenceTextToolbar()); + Object.values(this.getReferenceElements()).forEach(showElement); } /** * Hide the refence text. */ hideReferenceText() { - this.getReferenceTextContainer().setAttribute('aria-hidden', true); - this.getReferenceTextToolbar().setAttribute('aria-hidden', true); + hideElement(this.getReferenceTextToolbar()); + Object.values(this.getReferenceElements()).forEach(hideElement); } /** @@ -57,14 +44,10 @@ class ShowReferenceButton extends ReferenceTextComponent { */ render(state) { super.render(state); + const { active } = state; - const {active, referenceHTML} = state; - const iconRotation = (active) ? 90 : 0; - - this.getIcon().style.transform = `rotate(${iconRotation}deg)`; - + this.createCurrentReferences(); if (active) { - this.getReferenceTextContainer().innerHTML = referenceHTML; this.showReferenceText(); } else { this.hideReferenceText(); diff --git a/src/sdg/js/components/form/toggle.js b/src/sdg/js/components/form/toggle.js deleted file mode 100644 index 53b256049..000000000 --- a/src/sdg/js/components/form/toggle.js +++ /dev/null @@ -1,64 +0,0 @@ -import {FormComponent} from './abstract/form_component'; - - -/** - * @type {NodeListOf} - * TODO: All toggles within a form a considered to control editable for now. - **/ -const FORM_TOGGLES = document.querySelectorAll('.form .toggle, .global-edit-toggle .toggle'); - - -/** - * Contains logic for a form to contain a toggle which controls whether fields should be editable. - * Can be applied to all forms, only has effect when toggle is found (by id). - */ -class FormToggle extends FormComponent { - /** - * Constructor method. - * @param {HTMLElement} node - * @param {Object} initialState - */ - constructor(node, initialState = {}) { - super(node, initialState); - this.node = this.node.querySelector('input'); - }; - - /** - * Binds events to callbacks. - * Use this to define EventListeners, MutationObservers etc. - */ - bindEvents() { - this.node.addEventListener('change', (e) => this.setState({ - editable: e.target.checked, - })); - } - - /** - * Returns an HTMLElement representing the scope of the toggle. - * For an "edit all" toggle the form is considered to be the scope, for an "edit control" toggle the form control is - * considered to be the element scope. - * @return {([HTMLElement]|[HTMLFormElement])} - */ - getElementScope() { - try { - return [this.getFormControl()]; - } catch (e) { - return document.querySelectorAll('.form__control'); - } - } - - /** - * Renders state. - * Gets called when state gets updated. - * Use this to persist (read only) state to DOM. - * @param {Object} state Read only state. - */ - render(state) { - const {editable} = state; - const elementScope = this.getElementScope(); - [...elementScope].forEach((formControl) => this.setFormControlDisabled(!editable, formControl)); - } -} - -// Start! -[...FORM_TOGGLES].forEach((node) => new FormToggle(node)); diff --git a/src/sdg/js/components/form/use_reference_button.js b/src/sdg/js/components/form/use_reference_button.js index c6cbfed48..2fbb2f520 100644 --- a/src/sdg/js/components/form/use_reference_button.js +++ b/src/sdg/js/components/form/use_reference_button.js @@ -1,12 +1,10 @@ -import {ReferenceTextComponent} from './abstract/reference_text_component'; - +import { ReferenceTextComponent } from "./abstract/reference_text_component"; /** @type {NodeListOf} */ -const USE_REFERENCE_BUTTONS = document.querySelectorAll('.form__reference-btn'); - +const USE_REFERENCE_BUTTONS = document.querySelectorAll(".form__reference-btn"); /** - * Button allow the user to use the reference text. + * Button allow the user to take over the reference text in all localized fields at the same time. */ class UseReferenceButton extends ReferenceTextComponent { /** @@ -15,20 +13,11 @@ class UseReferenceButton extends ReferenceTextComponent { */ onClick(event) { event.preventDefault(); - this.setValue(this.getCurrentVersionData().input.value); - } - - /** - * Updates the label state based on disabled attribute. - */ - updateLabel() { - super.updateLabel(); - if(this.getFormControlDisabled()) { - this.setState({label: 'Bewerken uitgeschakeld'}); - } else { - this.setState({label: this.state.originalLabel}); - } + Object.values(this.getCurrentVersionData()).forEach(({ input }) => { + // Set the value on both inputs at the same time (if there is a value availible) + if (input.value) this.setValue(input.id, input.value); + }); } } diff --git a/src/sdg/js/components/form/utils.js b/src/sdg/js/components/form/utils.js new file mode 100644 index 000000000..642400736 --- /dev/null +++ b/src/sdg/js/components/form/utils.js @@ -0,0 +1,34 @@ +import Diff from "text-diff"; +import showdown from "showdown"; + +/** + * Global diff html function + * Returns a string containing html elements of the compared values. + * @param {string} oldValue - original value. + * @param {string} newValue - value to compare with - this is also the value that will contain the ins and del elements + * @returns {string} diffHTML - containing the del and ins elements. + */ +export const returnDiffHTML = (oldValue, newValue) => { + console.log(oldValue, newValue); + const diff = new Diff({ timeout: 0, editCost: 4 }); + const textDiff = diff.main(oldValue, newValue); + diff.cleanupEfficiency(textDiff); + const prettyHtml = diff + .prettyHtml(textDiff) + .replace(/<\/?span[^>]*>/g, "") + .replace(//g, "\n"); + const diffHTML = new showdown.Converter({ tables: true }).makeHtml( + prettyHtml + ); + return diffHTML; +}; + +export const hideElement = (node) => { + node.style.display = "none"; + node.setAttribute("aria-hidden", true); +}; + +export const showElement = (node) => { + node.removeAttribute("aria-hidden"); + node.style.removeProperty("display"); +}; diff --git a/src/sdg/js/components/generic.js b/src/sdg/js/components/generic.js index b08f4d6e7..6eb05ddf4 100644 --- a/src/sdg/js/components/generic.js +++ b/src/sdg/js/components/generic.js @@ -1,5 +1,5 @@ -import {availableEditors} from './markdown' -const form = document.querySelector('.form'); +import { availableEditors } from "./markdown"; +const form = document.querySelector(".form"); class GenericForm { /** @@ -10,12 +10,12 @@ class GenericForm { /** @type {HTMLFormElement} */ this.node = node; this.display = { - "valtOnder": false, - "aanwezig": false - } + valtOnder: false, + aanwezig: false, + }; - if(!this.isGenericForm()) { - return; // Not a generic form. + if (!this.isGenericForm()) { + return; // Not a generic form. } if (this.node) { @@ -42,56 +42,57 @@ class GenericForm { collapseExpand() { const formSpecific = document.querySelector(".form__specific"); - const formSpecificClassList = formSpecific.classList + const formSpecificClassList = formSpecific.classList; if (!this.display.valtOnder || !this.display.aanwezig) { formSpecific.style.pointerEvents = "none"; - formSpecificClassList.add("tabs__table--hidden") - formSpecificClassList.add("form__specific--hidden") + formSpecificClassList.add("tabs__table--hidden"); + formSpecificClassList.add("form__specific--hidden"); } - if (this.display.valtOnder && this.display.aanwezig) { + if (this.display.valtOnder && this.display.aanwezig) { formSpecific.style.pointerEvents = "all"; - formSpecificClassList.remove("tabs__table--hidden") - formSpecificClassList.remove("form__specific--hidden") + formSpecificClassList.remove("tabs__table--hidden"); + formSpecificClassList.remove("form__specific--hidden"); } } displayHidden(dependency, field) { dependency.style.display = "none"; - this.display[field] = true - this.collapseExpand() + this.display[field] = true; + this.collapseExpand(); } - displayBlock(dependency, field) { - dependency.style.display = "block"; - this.display[field] = false - this.collapseExpand() + displayGrid(dependency, field) { + dependency.style.display = "grid"; + this.display[field] = false; + this.collapseExpand(); } async emptyFieldValues(fields) { - if (await fields === []) { + fields = Array.isArray(fields) ? fields : [fields]; + if (fields) { fields.forEach((item) => { const textarea = item.querySelector("textarea"); textarea.value = ""; - }) + }); } } async getProductTranslationName(productId, language) { // SubPath is a globally defined constant. - const url = `${window.location.origin}${SubPath}/cmsapi/translation/?product_id=${productId}&taal=${language}` + const url = `${window.location.origin}${SubPath}/cmsapi/translation/?product_id=${productId}&taal=${language}`; const translationJsonResults = await fetch(url) - .then(response => response.json()) - .then(data => data.results[0]) + .then((response) => response.json()) + .then((data) => data.results[0]); if (translationJsonResults) { if (translationJsonResults.productTitel) { - return translationJsonResults.productTitel + return translationJsonResults.productTitel; } - return translationJsonResults.generiekProduct + return translationJsonResults.generiekProduct; } - return "" + return ""; } resetSpecefiekeGegevens() { @@ -101,73 +102,82 @@ class GenericForm { "[name=vertalingen-0-decentrale_procedure_link]", "[name=vertalingen-1-decentrale_procedure_link]", "[name=vertalingen-0-decentrale_procedure_label]", - "[name=vertalingen-1-decentrale_procedure_label]" - ] + "[name=vertalingen-1-decentrale_procedure_label]", + ]; const array_fields = [ "[name=vertalingen-0-verwijzing_links]", "[name=vertalingen-1-verwijzing_links]", - ] - const markdown_editor_fields = document.querySelectorAll('.markdownx textarea'); + ]; + const markdown_editor_fields = document.querySelectorAll( + ".markdownx textarea" + ); none_markdown_editor_fields.forEach((field) => { let input_field = this.node.querySelector(field); if (input_field) { - input_field.value = '' + input_field.value = ""; } - }) + }); array_fields.forEach((field) => { let input_fields = this.node.querySelectorAll(field); input_fields.forEach((input_field) => { if (input_field) { - input_field.value = '' + input_field.value = ""; } - }) - }) + }); + }); markdown_editor_fields.forEach((field) => { - field.value = '' - }) + field.value = ""; + }); Object.values(availableEditors).forEach((instance) => { - instance.editor.setData("") - }) + instance.editor.setData(""); + }); } setUpDynamicProductAanwezig() { const input = this.node.querySelector("[name=product_aanwezig]"); - const dependency = this.getClarificationField().closest(".form__language-wrapper"); + const dependency = this.getClarificationField().closest(".form__field"); if (input) { let previousIndex = input.selectedIndex; const displayFunc = async (displayDependency) => { - const controls = await displayDependency.querySelectorAll(".form__control") + const controls = await displayDependency.querySelectorAll( + ".form__control" + ); if (input.selectedIndex === 2) { if (input.selectedIndex != previousIndex) { - if (confirm("Weet u zeker dat u dit product niet aanbiedt?\nAls u 'OK' antwoordt worden alle teksten leeggemaakt.")) { + if ( + confirm( + "Weet u zeker dat u dit product niet aanbiedt?\nAls u 'OK' antwoordt worden alle teksten leeggemaakt." + ) + ) { this.resetSpecefiekeGegevens(); - this.displayBlock(displayDependency, "aanwezig") - } - else { + this.displayGrid(displayDependency, "aanwezig"); + } else { input.selectedIndex = previousIndex; - return + return; } } - controls.forEach(element => { + controls.forEach((element) => { const textarea = element.querySelector("textarea"); if (!textarea.value) { - const defaultExplanation = document.querySelector(`.form__reference-${element.lang}`).dataset.productAanwezigToelichting; + const defaultExplanation = document.querySelector( + `.form__reference-${element.lang}` + ).dataset.productAanwezigToelichting; textarea.value = defaultExplanation; } }); } else { - controls.forEach(async (fields) => { + controls.forEach((fields) => { this.displayHidden(dependency, "aanwezig"); this.emptyFieldValues(fields); }); - }; + } previousIndex = input.selectedIndex; }; @@ -176,56 +186,72 @@ class GenericForm { input.addEventListener("change", () => { displayFunc(dependency); }); - }; + } } setUpDynamicProductValtOnder() { const select = document.querySelector("#id_product_valt_onder"); - const dependency = document.querySelector('[id$="product_valt_onder_toelichting"]').closest(".form__language-wrapper"); + const dependency = document + .querySelector('[id$="product_valt_onder_toelichting"]') + .closest(".form__field"); if (select) { let previousSelectedProduct = select.value; const displayFunc = async (select, displayDependency) => { if (select.selectedIndex > 0) { - const controls = displayDependency.querySelectorAll(".form__control"); + const controls = + displayDependency.querySelectorAll(".form__control"); if (previousSelectedProduct == null) { - if (confirm("Weet u zeker dat dit product onder een ander product valt?\nAls u 'OK' antwoordt worden alle teksten leeggemaakt.")) { + if ( + confirm( + "Weet u zeker dat dit product onder een ander product valt?\nAls u 'OK' antwoordt worden alle teksten leeggemaakt." + ) + ) { this.resetSpecefiekeGegevens(); - this.displayBlock(displayDependency, "valtOnder") + this.displayGrid(displayDependency, "valtOnder"); } else { previousSelectedProduct = null; select.selectedIndex = 0; - return + return; } } for (const element of controls) { previousSelectedProduct = select.value; const textarea = element.querySelector("textarea"); - const defaultExplanation = document.querySelector(`.form__reference-${element.lang}`).dataset.defaultToelichting; - const explanation = defaultExplanation.replace(/\[product\]/g, await this.getProductTranslationName(select.value, element.lang)); + const defaultExplanation = document.querySelector( + `.form__reference-${element.lang}` + ).dataset.defaultToelichting; + const explanation = defaultExplanation.replace( + /\[product\]/g, + await this.getProductTranslationName( + select.value, + element.lang + ) + ); textarea.value = explanation; - }; + } } else { - const controls = displayDependency.querySelectorAll(".form__control") - let index = 0 + const controls = + displayDependency.querySelectorAll(".form__control"); + let index = 0; for (const element of controls) { const textarea = element.querySelector("textarea"); - this.displayHidden(dependency, "valtOnder") - this.emptyFieldValues([controls[index], element]) - previousSelectedProduct = null - index++ + this.displayHidden(dependency, "valtOnder"); + this.emptyFieldValues([controls[index], element]); + previousSelectedProduct = null; + index++; } - }; + } }; displayFunc(select, dependency); select.addEventListener("change", (event) => { displayFunc(event.target, dependency); }); - }; + } } } diff --git a/src/sdg/js/components/index.js b/src/sdg/js/components/index.js index 718602761..eca34f581 100644 --- a/src/sdg/js/components/index.js +++ b/src/sdg/js/components/index.js @@ -11,4 +11,3 @@ import "./tooltip"; import "./choices"; import "./bevoegde_organisaties"; import "./prevent_submit_twice"; -import "./toolbox_language"; diff --git a/src/sdg/js/components/toolbox_language.js b/src/sdg/js/components/toolbox_language.js deleted file mode 100644 index 7c2eb98a4..000000000 --- a/src/sdg/js/components/toolbox_language.js +++ /dev/null @@ -1,46 +0,0 @@ -const language_toolboxes = document.querySelectorAll('.toolbox__language_div'); - -class LanguageToolboxToggle { - /** - * Constructor method. - * @param {HTMLFormElement} node - */ - constructor(node) { - this.node = node; - - if (this.node) { - if (this.getLanguage()) { - this.toggle(); - } - } - } - - toggle() { - this.getLanguage().addEventListener("change", (event) => { - activeLang = this.getLanguage().checked ? 'en' : 'nl' - - let items = this.node.querySelectorAll('.toolbox__language_item') - - items.forEach((item) => { - if (item.lang === activeLang) { - item.removeAttribute('aria-hidden') - } else { - item.setAttribute('aria-hidden', true); - } - }); - }) - } - - getLanguage() { - try { - const globalSwitch = document.querySelector('.global-language-switch'); - return globalSwitch.querySelector('.toggle input[type=checkbox]'); - } catch (e) { - return null; - } - } -} - -if (language_toolboxes) { - [...language_toolboxes].forEach((node) => new LanguageToolboxToggle(node)); -} diff --git a/src/sdg/producten/utils.py b/src/sdg/producten/utils.py index 096464b7c..b01ccea9e 100644 --- a/src/sdg/producten/utils.py +++ b/src/sdg/producten/utils.py @@ -5,6 +5,7 @@ from django.utils import translation from django.utils.timezone import now from django.utils.translation import gettext as _ +from django.forms import BaseFormSet from sdg.conf.utils import org_type_cfg from sdg.core.constants import TaalChoices @@ -87,3 +88,23 @@ def get_placeholder_maps(product): ) return available_explanation_map, falls_under_explanation_map + + +def get_languages(formset: BaseFormSet) -> list: + """Get all the languages availible in the formset or TaalChoices as fallback""" + languages = [ + form.initial.get("taal") for form in formset.forms if form.initial.get("taal") + ] + return languages or list(TaalChoices.labels.keys()) + + +def get_fields(base_form, fields=None): + """Get all the fields from the base_form or get only the specified ones.""" + try: + base_form_fields = base_form.fields + except IndexError: + return [] + + if fields: + return [field for field in base_form_fields if field in fields] + return base_form_fields diff --git a/src/sdg/scss/components/_diff.scss b/src/sdg/scss/components/_diff.scss index 0d6b6f191..ca85066d0 100644 --- a/src/sdg/scss/components/_diff.scss +++ b/src/sdg/scss/components/_diff.scss @@ -1,13 +1,49 @@ @import 'colors'; -.diff:hover { - cursor: pointer; +.diff, .reference { + &[aria-hidden='true'] { + visibility: hidden; + display: none; + } + + &__versions { + display: flex; + justify-content: end; + grid-template-columns: 1fr 1fr; + grid-gap: 6px; + right: 0; + top: 0; + + &[aria-hidden='true'] { + visibility: hidden; + display: none; + } - & > svg { - color: $color_primary_light !important; + ins, del { + padding: 4px; + width: 200px; + border-radius: 16px; + text-align: center; + text-decoration: none; + font-size: 12px; + font-weight: bold; + border-bottom-left-radius: 0px; + border-bottom-right-radius: 0px; + } } -} + &__preview { + table, th, td { + border: 1px solid $color_grey; + border-collapse: collapse; + padding: 6px; + } + + th { + background: #0000000d; + } + } +} del { background-color: #ead5d5; diff --git a/src/sdg/scss/components/_dynamic.scss b/src/sdg/scss/components/_dynamic.scss index 853846ed0..9ddd9189d 100644 --- a/src/sdg/scss/components/_dynamic.scss +++ b/src/sdg/scss/components/_dynamic.scss @@ -1,36 +1,57 @@ -@import 'colors'; +@import "colors"; .dynamic { + // Reset error border if the field is empty + &:has(+ .errorlist) .form__input:placeholder-shown { + --field-border-color: #{$color_grey_light}; + } - .dynamic__container { - + &__container { .form__input { - width: 93%; - &--left { - width: 32% !important; + width: 32%; } - &--right { - width: 68% !important; + width: 68%; } + } + &-list { + display: flex; + flex-direction: column; + gap: 0.375rem; + } + + &-item { + position: relative; + + &:first-of-type button { + display: none; + } + + &:not(:first-of-type) .form__input--right { + padding-right: 42px; + } } .dynamic__container__button { border: none; - padding: 6px; + padding: 0.75rem; cursor: pointer; + .svg-inline--fa { + display: block; + visibility: visible; + } + &--side { @extend .dynamic__container__button; background-color: transparent; - position: relative; - right: 24px; + height: 100%; + position: absolute; + right: 0; color: $color_secondary_darkest; } } - } - } diff --git a/src/sdg/scss/components/_form.scss b/src/sdg/scss/components/_form.scss index 8e0d6a280..2f4f29a0a 100644 --- a/src/sdg/scss/components/_form.scss +++ b/src/sdg/scss/components/_form.scss @@ -1,75 +1,199 @@ @import 'colors'; .form { - &__control { - border-bottom: 1px solid #e0e0e0; + $form-gap: $grid-margin-2; + $form-label-row-gap: $grid-margin-0; + --field-border-color: #{$color_grey_light}; + + &__field { + border-bottom: 1px solid $color_grey_light; + padding: $form-gap 0; display: grid; - grid-template-areas: "hdr bdy" - "hdr ftr"; - grid-template-columns: 28% auto; + grid-template-areas: "hdr" + "bdy"; grid-template-rows: auto auto; - padding: 12px 0; - } + gap: $form-label-row-gap; + width: 100%; + + &--inline { + padding: 12px 0; + grid-template-areas: "hdr bdy"; + grid-template-rows: auto; + grid-template-columns: 28% auto; + gap: 0; + } - &__control[aria-hidden="true"], - .svg-inline--fa[aria-hidden="true"] { - display: none; - visibility: hidden; - } + &-header { + grid-area: hdr; + } - &__control-header { - grid-area: hdr; - } + &-body { + display: grid; + grid-auto-columns: minmax(0, 1fr); + grid-auto-flow: column; + gap: $form-gap; + grid-area: bdy; + } + + &-label { + gap: $grid-margin-0; + align-items: center; + display: flex; + } - &__control-header &__label { - align-items: center; - display: flex; + &-toolbar[aria-hidden=true] { + display: none; + visibility: hidden; + } } - &__control-header .svg-inline--fa { - margin: 0 4px; - } + &__input { + border: 1px solid var(--field-border-color); + color: #212121; + font-size: 16px; + line-height: 1.5; + min-height: 57px; + outline: none; + padding: .75rem; + vertical-align: top; + width: 100%; - &__language-switch { - white-space: nowrap; + // Input states + &[disabled], + &[readonly]:not(.flatpickr-input), + &--disabled { + background-color: $color_grey_lightest; + } - .svg-inline--fa { - color: $color_accent!important; + &[disabled]::placeholder, + &[readonly]::placeholder { + color: transparent; + } + + // Modifiers + &--preview { + --field-border-color: #{$color_grey_light}; + background-color: $color_grey_lightest; + cursor: default; + min-height: auto; + + &:empty{ + min-height: 50px; + } + } + + &--wrapper { + padding: 0; + border: 0; + } + + // Nested elements + ol { + counter-reset: li; + list-style: none; + + li { + counter-increment: li; + display:table-row; + } + + li:before { + content: counter(li) '.'; + color: #777; + width: 16px; + display: table-cell; + vertical-align: top; + } + } + + ul { + list-style: none; + + li { + display:table-row; + } + + li:before { + content: "—"; + color: #777; + width: 16px; + display: table-cell; + vertical-align: top; + } + } - } - &__control-body { + // All elements inside `.form__input` has a margin-bottom of 1rem except the last one. + & > *:not(:last-child) { + margin-bottom: 1rem; + } + } + + &__control { display: flex; flex-direction: column; - grid-area: bdy; - position: relative; - width: 100%; - } + gap: $form-label-row-gap; - &__control-footer { - grid-area: ftr; - } -} + + + &-hint { + font-size: .875rem; + color: $color_secondary_darkest; + } -// DEPRECATED -.form { + &-body { + display: flex; + flex-direction: column; + grid-area: bdy; + position: relative; + width: 100%; + + // Field error indication as a border. + &:has(.errorlist) { + --ck-color-base-border: #{$color-red}; + --field-border-color: #{$color-red}; + + .ck .ck-insert-table-dropdown-grid-box { + // reset error border in the insertable table option from CKEditor + --ck-color-base-border: #ccced1; + } + } + } - .form__cell { + // Apply error styling to the the border of the input on error. + &-body:has(.errorlist) { + --ck-color-base-border: #{$color-red}; + --field-border-color: #{$color-red}; - ol, - ul, - li { - margin: 1em 1em; + .ck .ck-insert-table-dropdown-grid-box { + --ck-color-base-border: #ccced1; // reset error border in the insertable table option from CKEditor + } } + + &-footer { + grid-area: ftr; + } + } - .form__group { + // Control states + .form__control[aria-hidden="true"], + .svg-inline--fa[aria-hidden="true"] { + display: none; + visibility: hidden; + } + + &__group { display: flex; flex-direction: column; border: 0; margin-bottom: 24px; transition: opacity 0.3s ease; + &:has(.errorlist) { + --field-border-color: #{$color-red}; + } + &--pad { border: 0; padding-bottom: 8px; @@ -83,57 +207,36 @@ opacity: 0; pointer-events: none; } - } - .form__label { + &__label { margin-bottom: 8px; font-size: 16px; } - .form__input { - border: 1px solid #dfdfdf; - padding: 12px; - outline: none; - font-size: 16px; - width: 100%; - min-height: 57px; - - &[disabled], - &[readonly]:not(.flatpickr-input), - &--disabled { - background-color: $color_grey_lightest; - } - - &[disabled]::placeholder, - &[readonly]::placeholder { - color: transparent; - } - } - - .form__block { + &__block { width: 425px; - } - - .form__block-inline { - display: grid; - grid-template-columns: repeat(4, 1fr); - } - .form__block-group { - border: 1px solid $color_secondary; - background-color: $color_secondary_lightest; - border-radius: 15px; - display: grid; - grid-template-columns: 1fr 1fr; - margin-bottom: 24px; + &-inline { + display: grid; + grid-template-columns: repeat(4, 1fr); + } - & > div { - padding: 24px; + &-group { + border: 1px solid $color_secondary; + background-color: $color_secondary_lightest; + border-radius: 15px; + display: grid; + grid-template-columns: 1fr 1fr; + margin-bottom: 24px; + + & > div { + padding: 24px; + } } } - .form__table { + &__table { display: grid; grid-template-columns: 95px 1fr; grid-gap: 8px; @@ -143,39 +246,38 @@ border-left: 1px solid $color_secondary; } - &.form__special-group { + .form__special-group { .form__help-text { display: none; } } - } - - .form__table-header { - text-transform: uppercase; - font-weight: bold; - } + &-header { + text-transform: uppercase; + font-weight: bold; - .form__table-header-left { - text-transform: uppercase; - font-weight: bold; - margin-bottom: 16px; - min-height: 18px; - } + &-left { + text-transform: uppercase; + font-weight: bold; + margin-bottom: 16px; + min-height: 18px; + } + } + } - .form__subtitle { + &__subtitle { padding: 16px 16px; font-size: 16px; display: flex; justify-content: space-between; } - .form__forms { + &__forms { width: 62%; padding: 8px 0; } - .form__subforms { + &__subforms { margin-bottom: 32px; color: $color_secondary_darkest; @@ -186,48 +288,29 @@ .formset__remove { text-decoration: none; } - } - .form__help-text { + &__help-text { margin-top: 4px; font-size: 14px; color: #666; } - .form__delete { - color: #396a88; - } - - .form__add-subform { + &__add-subform { color: #0B71A1; cursor: pointer; margin-bottom: 42px; margin-left: 16px; } - .form__actions { - display: flex; - align-items: center; - } - - .form__action-help { - font-size: 14px; - color: #666; - } - - .form__action-button { - margin-right: 16px; - } - - .form__buttons { + &__buttons { &-link { margin-left: 16px; } } - .form__checkbox { + &__checkbox { transition: opacity 400ms; &--invisible { @@ -252,4 +335,8 @@ } } } -} + + textarea { + resize: none; + } +} \ No newline at end of file diff --git a/src/sdg/scss/components/_highlight.scss b/src/sdg/scss/components/_highlight.scss deleted file mode 100644 index 388bdc375..000000000 --- a/src/sdg/scss/components/_highlight.scss +++ /dev/null @@ -1,22 +0,0 @@ -@import '../settings'; -@import './colors'; - -.highlight { - --border-color: #{$color_grey_light}; - --border-size: #{$typography-size-border}; - --main-color: #{$color_white}; - --spacing: #{$grid-margin-1}; - background-color: var(--main-color); - border: var(--border-size) solid var(--border-color); - padding: var(--spacing); - position: relative; - - &[aria-hidden=true] { - display: none; - visibility: hidden; - } - - &:empty { - display: none; - } -} diff --git a/src/sdg/scss/components/_index.scss b/src/sdg/scss/components/_index.scss index 39518b69b..c397b2d83 100644 --- a/src/sdg/scss/components/_index.scss +++ b/src/sdg/scss/components/_index.scss @@ -1,6 +1,5 @@ // Use this file to include individual components. @import 'colors'; -@import 'highlight'; @import 'button-group'; @import 'login'; @import 'container'; diff --git a/src/sdg/scss/components/_select.scss b/src/sdg/scss/components/_select.scss index a8ecc8557..9769c98f7 100644 --- a/src/sdg/scss/components/_select.scss +++ b/src/sdg/scss/components/_select.scss @@ -13,11 +13,11 @@ select { cursor: pointer; font-family: 'Source Sans Pro', sans-serif; background-color: $color_grey_light; -} -/* Remove IE arrow */ -select::-ms-expand { - display: none; + /* Remove IE arrow */ + &::-ms-expand { + display: none; + } } /* Custom Select wrapper */ @@ -28,23 +28,25 @@ select::-ms-expand { height: 40px; border-radius: .25em; overflow: hidden; -} -/* Arrow */ -.select::after { - content: '\25BC'; - position: absolute; - top: 0; - right: 0; - padding: 9px; - background-color: $color_primary_dark; - color: #fff; - transition: .25s all ease; - pointer-events: none; -} - -/* Transition */ -.select:hover::after { - color: $color_accent; -} + /* Arrow */ + &::after { + content: '\25BC'; + position: absolute; + display: flex; + align-items: center; + top: 0; + right: 0; + padding: 9px; + height: 100%; + background-color: $color_primary_dark; + color: #fff; + transition: .25s all ease; + pointer-events: none; + } + /* Transition */ + &:hover::after { + color: $color_accent; + } +} \ No newline at end of file diff --git a/src/sdg/scss/components/_tabs.scss b/src/sdg/scss/components/_tabs.scss index 8b8801fc4..ef80661ce 100644 --- a/src/sdg/scss/components/_tabs.scss +++ b/src/sdg/scss/components/_tabs.scss @@ -4,6 +4,25 @@ .tabs { + &__table { + border-collapse: collapse; + width: 100%; + border-spacing: 0; + margin-bottom: 48px; + + code { + font-size: 12px; + display: block; + + .flag-icon { + border-radius: 50%; + background-size: cover; + height: 14px; + width: 14px; + } + } + } + .tabs__subproduct { margin-bottom: 94px; } @@ -14,43 +33,6 @@ grid-gap: 4px; } - .tabs__tab { - color: #fff; - background-color: #285a77; - padding: 16px; - border-radius: 10px 10px 0 0; - font-weight: bold; - transition: background-color 300ms; - text-decoration: none; - - &:hover { - background-color: hsl(0deg 0% 100% / 20%); - } - - &.tabs__tab--selected { - background-color: #f2f2f2; - color: #396a88; - text-decoration: none; - } - - &-contents { - margin-top: 16px; - } - - } - - .tabs__tab-content { - display: none; - - &.tabs__tab-content--active { - display: block; - } - } - - .tabs__tab-content > .toolbar { - position: relative; - } - .tabs__tab-header { padding: 0 12px; display: flex; @@ -59,50 +41,6 @@ align-items: center; } - .tabs__status { - display: flex; - } - - .tabs__status-label { - margin-right: 16px; - font-weight: bold; - color: $color_grey_darkest; - } - - .tabs__status-label--result { - margin-left: 4px; - } - - .tabs__status-published { - color: $color_primary; - } - - .tabs__status-unpublished { - color: $color_accent_dark; - } - - .tabs__status-concept { - color: $color_secondary_darkest; - } - - .tabs__table { - width: 100%; - border-spacing: 0; - margin-bottom: 48px; - - code { - font-size: 12px; - display: block; - - .flag-icon { - border-radius: 50%; - background-size: cover; - height: 14px; - width: 14px; - } - } - } - .tabs__table-header { font-weight: bold; text-align: left; @@ -348,37 +286,12 @@ top: 1px; } - &-outer { - margin-top: 0; - } - - .tabs__tab { - background-color: $color_grey_light; - border: 1px solid $color_grey_light; - border-bottom: none; - color: #525257; - - &.tabs__tab--selected { - color: #525257; - text-decoration: none; - } - - &:hover { - background-color: #f7f7f7; - } - } - .tabs__tab-contents { border: 1px solid $color_grey_light; margin-top: 0; padding: 32px 64px; border-bottom-left-radius: 13px; border-bottom-right-radius: 13px; - - .preview { - text-decoration: none; - font-weight: bold; - } } } } diff --git a/src/sdg/scss/components/_url_label_field.scss b/src/sdg/scss/components/_url_label_field.scss index 62af13aa4..a660684fa 100644 --- a/src/sdg/scss/components/_url_label_field.scss +++ b/src/sdg/scss/components/_url_label_field.scss @@ -2,6 +2,27 @@ .url_label_field { + &__container-item { + display: flex; + + &--errorlist{ + display: block; + + // Fixes margins between errorlist item + ul:first-of-type { + margin-bottom: 8px; + } + + ul:nth-of-type(n+2){ + margin: 8px 0; + } + + ul:last-of-type { + margin-bottom: 1rem; + } + } + } + .url_label_field__container { .form__input { diff --git a/src/sdg/scss/screen.scss b/src/sdg/scss/screen.scss index 1e3277452..c829d4738 100644 --- a/src/sdg/scss/screen.scss +++ b/src/sdg/scss/screen.scss @@ -84,3 +84,6 @@ blockquote { } } +.italic { + font-style: italic; +} \ No newline at end of file diff --git a/src/sdg/templates/forms/localized_url_label_field.html b/src/sdg/templates/forms/localized_url_label_field.html deleted file mode 100644 index ba893e73d..000000000 --- a/src/sdg/templates/forms/localized_url_label_field.html +++ /dev/null @@ -1,56 +0,0 @@ -{% load i18n toggle utils %} - -
- - -
\ No newline at end of file diff --git a/src/sdg/templates/forms/select.html b/src/sdg/templates/forms/select.html deleted file mode 100644 index 80d396114..000000000 --- a/src/sdg/templates/forms/select.html +++ /dev/null @@ -1,10 +0,0 @@ -{% load utils %} - -
-
- {{ field }} - {% if field.errors %} - {{ field.errors }} - {% endif %} -
-
diff --git a/src/sdg/templates/forms/table_row.html b/src/sdg/templates/forms/table_row.html deleted file mode 100644 index 301e87408..000000000 --- a/src/sdg/templates/forms/table_row.html +++ /dev/null @@ -1,38 +0,0 @@ -{% load i18n %} -{% load utils %} -{% load producten %} - -{% for product in products %} -
- {% for field in product.template_fields %} - - - {% endfor %} -
-{% endfor %} diff --git a/src/sdg/templates/forms/widgets/dynamic_array.html b/src/sdg/templates/forms/widgets/dynamic_array.html index 7e53ca819..21565dd53 100644 --- a/src/sdg/templates/forms/widgets/dynamic_array.html +++ b/src/sdg/templates/forms/widgets/dynamic_array.html @@ -1,9 +1,9 @@ -{% load i18n static toggle %} +{% load i18n static %} {% spaceless %}
-
+
{% for subwidget in widget.subwidgets %} @@ -24,9 +24,6 @@ {% trans "Toevoegen" %} - {% if lockable %} - {% toggle _('Aanpassen') hide_label=True icon_before='fa fa-lock fa-xs' icon_after='fa fa-lock-open fa-xs' %} - {% endif %}
{% endif %}
diff --git a/src/sdg/templates/producten/update.html b/src/sdg/templates/producten/update.html index f664a9678..9d5a25d41 100644 --- a/src/sdg/templates/producten/update.html +++ b/src/sdg/templates/producten/update.html @@ -1,5 +1,5 @@ {% extends 'producten/base.html' %} -{% load utils static i18n producten toggle localized_form nonlocalized_form %} +{% load utils static i18n producten toggle update_form %} {% block page_intro %}

@@ -108,278 +108,14 @@ {% csrf_token %} {{ form.management_form }} {{ product_form.non_field_errors }} -

- - - - - - - - - {% if not product.is_referentie_product %} - - - - {% with field=product_form.product_aanwezig %} - - - - {% endwith %} - - - - - - {% with field=product_form.product_valt_onder %} - - - - {% endwith %} - - - - - - {% with field=product_form.locaties %} - - - - {% endwith %} - {% endif %} - - {# DISABLED PER REQUEST VNG #} - {% with field=product_form.api_verborgen %} - - - - {% endwith %} - - {% with field=product_form.bevoegde_organisatie %} - - - - {% endwith %} - - {% with publication_date=product.active_version|get_field:"publicatie_datum" %} - {% with configuration=publication_date.configuration.label %} - - - - {% endwith %} - {% endwith %} - {% with field=product_form.heeft_kosten %} - - - - {% endwith %} - - - - - - - - - -
- - 1. {% trans "Algemene Gegevens" %} -
-
-
- -
-
-
- {{ doelgroep.value }} -
-
-
-
-
- -
-
-
- {% comment %} Didn't use template because error msg would render in the select element {% endcomment %} -
-
- {{ field }} -
-
- {{ field.errors }} -
-
-
- {% localized_form formset include_form=False fields="product_aanwezig_toelichting" %} -
-
-
- -
-
-
- {% select field %} - {{ field.errors }} -
-
-
- {% localized_form formset include_form=False fields="product_valt_onder_toelichting" %} -
-
-
- -
-
-
- {{ field }} - {{ field.errors }} -
-
-
-
-
- -
-
-
- {% select field %} - {{ field.errors }} -
-
-
-
-
- -
-
-
- {% select field %} - {{ field.errors }} -
-
-
-
-
- -
-
-
- {{ publication_date.value|default:"Concept" }} -
-
-
-
-
- -
-
-
- {% select field %} - {{ field.errors }} -
-
-
-
-
- -
-
-
- {{ areas|join:", " }} -
-
-
- {% nonlocalized_form version_form include_form=False fields="interne_opmerkingen" %} -
-
- -
- {% for generic_information, form in informatie_forms %} -
- - {% if generic_products %} - - - - - - - - - -
- - 2. {% trans 'Generieke gegevens' %} -
- {% table_row products=generic_products languages=languages generic_fields=generic_fields %} -
- {% endif %} - -
- {% endfor %} - - - - - - - - - - -
- - 3. {% trans 'Specifieke gegevens' %} -
-
- {% for form in form.forms %} - {{ form.non_field_errors }} - {{ form.id.as_hidden }} - {{ form.taal.as_hidden }} - {% endfor %} - - {% localized_form formset include_form=False fields=localized_form_fields %} + + {% update_form_general %} + + {% if generic_products %} + {% update_form_generic %} + {% endif %} - {% comment %} loop though from to get decentrale_procedure_label and decentrale_procedure_link form instances to combine them in the localized_url_label_field template {% endcomment %} - {% for form in form.forms %} - {% if form.decentrale_procedure_label and form.decentrale_procedure_link %} - {% localized_url_label_field label=form.decentrale_procedure_label link=form.decentrale_procedure_link taal=form.taal.value languages=languages %} - {% endif %} - {% endfor %} -
-
-
+ {% update_form_specific %} {% if history %}
diff --git a/src/sdg/utils/templatetags/utils.py b/src/sdg/utils/templatetags/utils.py index 79daea4cc..a54de4372 100644 --- a/src/sdg/utils/templatetags/utils.py +++ b/src/sdg/utils/templatetags/utils.py @@ -117,27 +117,6 @@ def table_grid_field(field, **kwargs): return {**kwargs, "field": field} -@register.inclusion_tag("forms/table_row.html") -def table_row(products, **kwargs): - return {**kwargs, "products": products} - - -@register.inclusion_tag("forms/localized_url_label_field.html") -def localized_url_label_field(label, link, taal, languages, **kwargs): - return { - **kwargs, - "label": label, - "link": link, - "taal": taal, - "languages": languages, - } - - -@register.inclusion_tag("forms/select.html") -def select(field, **kwargs): - return {**kwargs, "field": field} - - @register.inclusion_tag("forms/checkbox.html") def checkbox(field, **kwargs): return {**kwargs, "field": field}