diff --git a/app/packs/src/decidim/decidim_awesome/admin/custom_fields_builder.js b/app/packs/src/decidim/decidim_awesome/admin/custom_fields_builder.js new file mode 100644 index 0000000000..c426e9645a --- /dev/null +++ b/app/packs/src/decidim/decidim_awesome/admin/custom_fields_builder.js @@ -0,0 +1,79 @@ +require("formBuilder/dist/form-builder.min.js") +import "src/decidim/decidim_awesome/forms/rich_text_plugin" + +window.CustomFieldsBuilders = window.CustomFieldsBuilders || []; + +$(() => { + $(".awesome-edit-config .proposal_custom_fields_editor").each((_idx, el) => { + const key = $(el).closest(".proposal_custom_fields_container").data("key"); + const configVar = $(el).closest(".proposal_custom_fields_container").data("var"); + // DOCS: https://formbuilder.online/docs + window.CustomFieldsBuilders.push({ + el: el, + key: key, + var: configVar, + config: { + i18n: { + locale: "fr-FR", + location: "https://decidim.storage.opensourcepolitics.eu/osp-cdn/form_builder/1.1.0" + }, + formData: $(`input[name="config[${configVar}][${key}]"]`).val(), + disableFields: ["button", "file"], + disabledActionButtons: ["save", "data", "clear"], + disabledAttrs: [ + "access", + "inline", + "className" + ], + controlOrder: [ + "text", + "textarea", + "number", + "date", + "checkbox-group", + "radio-group", + "select", + "autocomplete", + "header", + "paragraph" + ], + disabledSubtypes: { + // default color as it generate hashtags in decidim (TODO: fix hashtag generator with this) + text: ["color"], + // disable default wysiwyg editors as they present problems + textarea: ["tinymce", "quill"] + } + }, + instance: null + }); + }); + + $(document).on("formBuilder.create", (_event, idx, list) => { + if (!list[idx]) { + return; + } + + $(list[idx].el).formBuilder(list[idx].config).promise.then(function(res) { + list[idx].instance = res; + // Attach to DOM + list[idx].el.FormBuilder = res; + // remove spinner + $(list[idx].el).find(".loading-spinner").remove(); + // for external use + $(document).trigger("formBuilder.created", [list[idx]]); + if (idx < list.length) { + $(document).trigger("formBuilder.create", [idx + 1, list]); + } + }); + }); + + if (window.CustomFieldsBuilders.length) { + $(document).trigger("formBuilder.create", [0, window.CustomFieldsBuilders]); + } + + $("form.awesome-edit-config").on("submit", () => { + window.CustomFieldsBuilders.forEach((builder) => { + $(`input[name="config[${builder.var}][${builder.key}]"]`).val(builder.instance.actions.getData("json")); + }); + }); +}); diff --git a/app/packs/src/decidim/decidim_awesome/forms/custom_fields_renderer.js b/app/packs/src/decidim/decidim_awesome/forms/custom_fields_renderer.js new file mode 100644 index 0000000000..41e7b97843 --- /dev/null +++ b/app/packs/src/decidim/decidim_awesome/forms/custom_fields_renderer.js @@ -0,0 +1,225 @@ +import "formBuilder/dist/form-render.min.js"; +import "src/decidim/decidim_awesome/forms/rich_text_plugin" + +export default class CustomFieldsRenderer { // eslint-disable-line no-unused-vars + constructor(containerSelector) { + this.containerSelector = containerSelector || ".proposal_custom_field:last"; + this.lang = this.getLang(window.DecidimAwesome.currentLocale); + } + + getLang(lang) { + const langs = { + // ar: 'ar-SA', // Not in decidim yet + "ar": "ar-TN", + "ca": "ca-ES", + "cs": "cs-CZ", + "da": "da-DK", + "de": "de-DE", + "el": "el-GR", + "en": "en-US", + "es": "es-ES", + "fa": "fa-IR", + "fi": "fi-FI", + "fr": "fr-FR", + "he": "he-IL", + "hu": "hu-HU", + "it": "it-IT", + "ja": "ja-JP", + "my": "my-MM", + "nb": "nb-NO", + "nl": "nl-NL", + "pl": "pl-PL", + "pt": "pt-BR", + "qz": "qz-MM", + "ro": "ro-RO", + "ru": "ru-RU", + "sl": "sl-SI", + "th": "th-TH", + "tr": "tr-TR", + "uk": "uk-UA", + "vi": "vi-VN", + "zh-TW": "zh-TW", + "zh": "zh-CN" + }; + if (langs[lang]) { + return langs[lang]; + } + if (langs[lang.substr(0, 2)]) { + return langs[lang.substr(0, 2)]; + } + return "en-US"; + } + + /* + * Creates an XML document with a subset of html-compatible dl/dd/dt elements + * to store the custom fields answers + */ + dataToXML(data) { + const $dl = $("
"); + let $dd = null, + $div = null, + $dt = null, + datum = null, + key = null, + label = null, + text = null, + val = null; + $dl.attr("class", "decidim_awesome-custom_fields"); + $dl.attr("data-generator", "decidim_awesome"); + $dl.attr("data-version", window.DecidimAwesome.version); + for (key in data) { // eslint-disable-line guard-for-in + // console.log("get the data!", key, data[key]); + // Richtext plugin does not saves userdata, so we get it from the hidden input + if (data[key].type === "textarea" && data[key].subtype === "richtext") { + data[key].userData = [$(`#${data[key].name}-input`).val()]; + } + if (data[key].userData && data[key].userData.length) { + $dt = $("
"); + $dt.text(data[key].label); + $dt.attr("name", data[key].name); + $dd = $("
"); + // console.log("data for", key, data[key].name, data[key]) + for (val in data[key].userData) { // eslint-disable-line guard-for-in + $div = $("
"); + label = data[key].userData[val]; + text = null; + if (data[key].values) { + datum = data[key].values.find((obj) => obj.value === label); // eslint-disable-line no-loop-func + if (datum) { // eslint-disable-line max-depth + text = label; + label = datum.label; + } + } else if (data[key].type === "date" && label) { + datum = new Date(label).toLocaleDateString(); + if (datum) { // eslint-disable-line max-depth + text = label; + label = datum; + } + } + // console.log("userData", text, "label", label, 'key', key, 'data', data) + if (data[key].type === "textarea" && data[key].subtype === "richtext") { + $div.html(label); + } else { + $div.text(label); + } + if (text) { + $div.attr("alt", text); + } + $dd.append($div); + } + $dd.attr("id", data[key].name); + $dd.attr("name", data[key].type); + $dl.append($dt); + $dl.append($dd); + } + } + return `${$dl[0].outerHTML}`; + } + + fixBuggyFields() { + if (!this.$element) { + return false; + } + + /** + * Hack to fix required checkboxes being reset + * Issue: https://github.com/decidim-ice/decidim-module-decidim_awesome/issues/82 + */ + this.$element.find(".formbuilder-checkbox-group").each((_key, group) => { + const inputs = $(".formbuilder-checkbox input", group); + const $label = $(group).find("label"); + const data = this.spec.find((obj) => obj.type === "checkbox-group" && obj.name === $label.attr("for")); + let values = data.userData; + if (!inputs.length || !data || !values) { + return; + } + + inputs.each((_idx, input) => { + let index = values.indexOf(input.value); + if (index >= 0) { + values.splice(index, 1) + // setting checked=true do not makes the browser aware that the form is valid if the field is required + if (!input.checked) + {$(input).click();} + } else if (input.checked) + {$(input).click();} + }); + + // Fill "other" option + const otherOption = $(".other-option", inputs.parent())[0]; + const otherVal = $(".other-val", inputs.parent())[0]; + const otherText = values.join(" "); + + if (otherOption) { + if (otherText) { + otherOption.checked = true; + otherOption.value = otherText; + otherVal.value = otherText; + } else { + otherOption.checked = false; + otherOption.value = ""; + otherVal.value = ""; + } + } + }); + + /** + * Hack to fix required radio buttons "other" value + * Issue: https://github.com/decidim-ice/decidim-module-decidim_awesome/issues/133 + */ + this.$element.find(".formbuilder-radio input.other-val").on("input", (input) => { + const $input = $(input.currentTarget); + const $group = $input.closest(".formbuilder-radio-group"); + $group.find("input").each((_key, radio) => { + const name = $(radio).attr("name"); + if (name && name.endsWith("[]")) { + $(radio).attr("name", name.slice(0, -2)); + } + }); + }); + return this; + } + + // Saves xml to the hidden input + storeData() { + if (!this.$element) { + return false; + } + const $form = this.$element.closest("form"); + const $body = $form.find(`input[name="${this.$element.data("name")}"]`); + if ($body.length && this.instance) { + this.spec = this.instance.userData; + console.log("Spec data:", this.spec); + $body.val(this.dataToXML(this.spec)); + this.$element.data("spec", this.spec); + } + // console.log("storeData spec", this.spec, "$body", $body,"$form",$form,"this",this); + return this; + } + + init($element) { + console.log("CustomFieldsRenderer init"); + this.$element = $element; + console.log("Element for rendering:", $element); + + this.spec = $element.data("spec"); + console.log("Spec data:", this.spec); + // console.log("init", $element, "this", this) + // in case of multilang tabs we only render one form due a limitation in the library for handling several instances + this.instance = $element.formRender({ + i18n: { + locale: this.lang, + location: "https://decidim.storage.opensourcepolitics.eu/osp-cdn/form_builder/1.1.0" + }, + formData: this.spec, + render: true, + disableInjectedStyle: true, + controlConfig: { + "textarea.richtext": { + editorOptions: $element.data("editorOptions") + } + } + }); + this.fixBuggyFields(); + } +} diff --git a/config/initializers/awesome_defaults.rb b/config/initializers/awesome_defaults.rb new file mode 100644 index 0000000000..97e20a45ec --- /dev/null +++ b/config/initializers/awesome_defaults.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +# A URL where to obtain the translations for the FormBuilder component +# you can a custom place if you are worried about the CDN geolocation +# Download them from https://github.com/kevinchappell/formBuilder-languages + +# For instance, copy them to your /public/fb_locales/ directory and set the path here: +Decidim::DecidimAwesome.configure do |config| + config.form_builder_langs_location = "https://decidim.storage.opensourcepolitics.eu/osp-cdn/form_builder/1.1.0/" +end