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