From 15ddd72387d76cd554819aa4f016ff4945d99ba5 Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Wed, 11 Nov 2020 10:38:58 +0100 Subject: [PATCH 1/4] pat auto submit: Modernize code. --- src/pat/auto-submit/auto-submit.js | 53 ++++++++++++------------------ 1 file changed, 21 insertions(+), 32 deletions(-) diff --git a/src/pat/auto-submit/auto-submit.js b/src/pat/auto-submit/auto-submit.js index e7b1363f5..99d58976b 100644 --- a/src/pat/auto-submit/auto-submit.js +++ b/src/pat/auto-submit/auto-submit.js @@ -1,12 +1,3 @@ -/** - * Patterns autosubmit - automatic submission of forms - * - * Copyright 2012-2013 Florian Friesdorf - * Copyright 2012 Simplon B.V. - Wichert Akkerman - * Copyright 2013 Marko Durkovic - * Copyright 2014-2015 Syslab.com GmbH - JC Brand - */ - import $ from "jquery"; import logging from "../../core/logging"; import Base from "../../core/base"; @@ -14,9 +5,9 @@ import Parser from "../../core/parser"; import input_change_events from "../../lib/input-change-events"; import utils from "../../core/utils"; -var log = logging.getLogger("autosubmit"), - parser = new Parser("autosubmit"); +const log = logging.getLogger("autosubmit"); +const parser = new Parser("autosubmit"); // - 400ms -> 400 // - 400 -> 400 // - defocus @@ -26,8 +17,8 @@ export default Base.extend({ name: "autosubmit", trigger: ".pat-autosubmit, .pat-auto-submit", parser: { - parse: function ($el, opts) { - var cfg = parser.parse($el, opts); + parse($el, opts) { + const cfg = parser.parse($el, opts); if (cfg.delay !== "defocus") { cfg.delay = parseInt(cfg.delay.replace(/[^\d]*/g, ""), 10); } @@ -35,46 +26,44 @@ export default Base.extend({ }, }, - init: function () { - this.options = this.parser.parse(this.$el, arguments[1]); + init() { + this.options = this.parser.parse(this.$el, this.options); input_change_events.setup(this.$el, "autosubmit"); this.registerListeners(); this.registerTriggers(); return this.$el; }, - registerListeners: function () { + registerListeners() { this.$el.on("input-change-delayed.pat-autosubmit", this.onInputChange); this.registerSubformListeners(); this.$el.on("patterns-injected", this.refreshListeners.bind(this)); }, - registerSubformListeners: function (ev) { + registerSubformListeners(ev) { /* If there are subforms, we need to listen on them as well, so * that only the subform gets submitted if an element inside it * changes. */ - var $el = typeof ev !== "undefined" ? $(ev.target) : this.$el; + const $el = typeof ev !== "undefined" ? $(ev.target) : this.$el; $el.find(".pat-subform") .not(".pat-autosubmit") - .each( - function (idx, el) { - $(el).on( - "input-change-delayed.pat-autosubmit", - this.onInputChange - ); - }.bind(this) - ); + .each((idx, el) => { + $(el).on( + "input-change-delayed.pat-autosubmit", + this.onInputChange + ); + }); }, - refreshListeners: function (ev, cfg, el, injected) { + refreshListeners(ev, cfg, el, injected) { this.registerSubformListeners(); // Register change event handlers for new inputs injected into this form input_change_events.setup($(injected), "autosubmit"); }, - registerTriggers: function () { - var isText = this.$el.is("input:text, input[type=search], textarea"); + registerTriggers() { + const isText = this.$el.is("input:text, input[type=search], textarea"); if (this.options.delay === "defocus" && !isText) { log.error( "The defocus delay value makes only sense on text input elements." @@ -100,13 +89,13 @@ export default Base.extend({ } }, - destroy: function ($el) { + destroy($el) { input_change_events.remove($el, "autosubmit"); if (this.$el.is("form")) { this.$el .find(".pat-subform") .addBack(this.$el) - .each(function (idx, el) { + .each((idx, el) => { $(el).off(".pat-autosubmit"); }); } else { @@ -114,7 +103,7 @@ export default Base.extend({ } }, - onInputChange: function (ev) { + onInputChange(ev) { ev.stopPropagation(); $(this).submit(); log.debug("triggered by " + ev.type); From 9fa3f5c7675d09412c2db7818bf0bb4e282a3e99 Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Wed, 11 Nov 2020 10:39:08 +0100 Subject: [PATCH 2/4] pat auto suggest: Modernize code and add testcase for problem with auto-submit. --- src/pat/auto-suggest/auto-suggest.js | 112 ++++++++++------------ src/pat/auto-suggest/auto-suggest.test.js | 9 +- 2 files changed, 60 insertions(+), 61 deletions(-) diff --git a/src/pat/auto-suggest/auto-suggest.js b/src/pat/auto-suggest/auto-suggest.js index 6c7c89513..28444d356 100644 --- a/src/pat/auto-suggest/auto-suggest.js +++ b/src/pat/auto-suggest/auto-suggest.js @@ -5,6 +5,7 @@ import logging from "../../core/logging"; import Parser from "../../core/parser"; const log = logging.getLogger("autosuggest"); + const parser = new Parser("autosuggest"); parser.addArgument("ajax-data-type", "JSON"); parser.addArgument("ajax-search-index", ""); @@ -38,27 +39,29 @@ export default Base.extend({ name: "autosuggest", trigger: ".pat-autosuggest,.pat-auto-suggest", - async init($el, opts) { + async init() { await import("select2"); - this.options = parser.parse($el, opts); - this.select2_options = { + this.options = parser.parse(this.el, this.options); + + let config = { tokenSeparators: [","], openOnEnter: false, maximumSelectionSize: this.options.maxSelectionSize, minimumInputLength: this.options.minimumInputLength, allowClear: - this.options.maxSelectionSize === 1 && !$el.prop("required"), + this.options.maxSelectionSize === 1 && + !this.el.hasAttribute("required"), }; - if ($el.attr("readonly")) { - this.select2_options.placeholder = ""; + if (this.el.hasAttribute("readonly")) { + config.placeholder = ""; } else if (this.options.placeholder) { - this.select2_options.placeholder = this.options.placeholder; + config.placeholder = this.options.placeholder; } if (this.options.selectionClasses) { // We need to customize the formatting/markup of the selection - this.select2_options.formatSelection = (obj, container) => { + config.formatSelection = (obj, container) => { let selectionClasses = null; try { selectionClasses = JSON.parse( @@ -78,44 +81,42 @@ export default Base.extend({ }; } - if ($el[0].tagName === "INPUT") { - this.select2_options = this.configureInput( - $el, - this.select2_options - ); + if (this.el.tagName === "INPUT") { + config = this.create_input_config(config); } - $el.select2(this.select2_options); - $el.on("pat-update", (e, data) => { + this.$el.select2(config); + this.$el.on("pat-update", (e, data) => { if (data.pattern === "depends") { if (data.enabled === true) { - $el.select2("enable", true); + this.$el.select2("enable", true); } else if (data.enabled === false) { - $el.select2("disable", true); + this.$el.select2("disable", true); } } }); // suppress propagation for second input field - $el.prev().on("input-change input-defocus input-change-delayed", (e) => - e.stopPropagation() - ); + this.$el + .prev() + .on("input-change input-defocus input-change-delayed", (e) => + e.stopPropagation() + ); // Clear the values when a reset button is pressed - $el.closest("form") + this.$el + .closest("form") .find("button[type=reset]") - .on("click", () => $el.select2("val", "")); - return $el; + .on("click", () => this.$el.select2("val", "")); }, - configureInput($el, select2_config) { + create_input_config(config) { let words = []; - select2_config.createSearchChoice = (term, data) => { + config.createSearchChoice = (term, data) => { if (this.options.allowNewWords) { if ( - $(data).filter( - (idx, el) => el.text.localeCompare(term) === 0 - ).length === 0 + data.filter((el) => el.text.localeCompare(term) === 0) + .length === 0 ) { return { id: term, text: term }; } @@ -124,7 +125,7 @@ export default Base.extend({ } }; - if (this.options.wordsJson && this.options.wordsJson.length) { + if (this.options.wordsJson?.length) { try { words = JSON.parse(this.options.wordsJson); } catch (SyntaxError) { @@ -134,14 +135,14 @@ export default Base.extend({ ); } if (!Array.isArray(words)) { - words = $.map(words, (v, k) => { + words = words.map((v, k) => { return { id: k, text: v }; }); } } if (this.options.words) { words = this.options.words.split(/\s*,\s*/); - words = $.map(words, (v) => { + words = words.map((v) => { return { id: v, text: v }; }); } @@ -152,19 +153,19 @@ export default Base.extend({ // That was then properly working with ajax if configured. if (this.options.maxSelectionSize === 1) { - select2_config.data = words; + config.data = words; // We allow exactly one value, use dropdown styles. How do we feed in words? } else { // We allow multiple values, use the pill style - called tags in select 2 speech - select2_config.tags = words; + config.tags = words; } - if (this.options.prefill && this.options.prefill.length) { - $el.val(this.options.prefill.split(",")); - select2_config.initSelection = (element, callback) => { + if (this.options.prefill?.length) { + this.el.value = this.options.prefill.split(","); + config.initSelection = (element, callback) => { let data = []; const values = element.val().split(","); - for (let value of values) { + for (const value of values) { data.push({ id: value, text: value }); } if (this.options.maxSelectionSize === 1) { @@ -186,17 +187,17 @@ export default Base.extend({ try { const data = JSON.parse(this.options.prefillJson); let ids = []; - for (let d in data) { + for (const d in data) { if (typeof d === "object") { ids.push(d.id); } else { ids.push(d); } } - $el.val(ids); - select2_config.initSelection = (element, callback) => { + this.el.value = ids; + config.initSelection = (element, callback) => { let _data = []; - for (let d in data) { + for (const d in data) { if (typeof d === "object") { _data.push({ id: d.id, text: d.text }); } else { @@ -215,8 +216,8 @@ export default Base.extend({ } } - if (this.options.ajax && this.options.ajax.url) { - select2_config = $.extend( + if (this.options.ajax?.url) { + config = $.extend( true, { minimumInputLength: this.options.minimumInputLength, @@ -240,10 +241,10 @@ export default Base.extend({ }, }, }, - select2_config + config ); } - return select2_config; + return config; }, destroy($el) { @@ -254,21 +255,12 @@ export default Base.extend({ transform($content) { $content .findInclusive("input[type=text].pat-autosuggest") - .each((idx, el) => { - let $src = $(el); - let $dest = $("").insertAfter($src); - - // measure in IE8, otherwise hidden will have width 0 - if (document.all && !document.addEventListener) { - $dest.css("width", $src.outerWidth(false) + "px"); - } - $src.detach(); - $.each($src.prop("attributes"), (el_) => { - if (el_.name !== "type") { - $dest.attr(el_.name, el_.value); - } - }); - $src.remove(); + .each(function (idx, el) { + // We need the original element to be hidden not only for not + // displaying it, but also input-change-events registering a + // change handler which allows e.g. for auto-submitting. + // ``input`` event isn't thrown when updating select2. + el.setAttribute("type", "hidden"); }); }, }); diff --git a/src/pat/auto-suggest/auto-suggest.test.js b/src/pat/auto-suggest/auto-suggest.test.js index 48ebfd5e6..96e2dd1c2 100644 --- a/src/pat/auto-suggest/auto-suggest.test.js +++ b/src/pat/auto-suggest/auto-suggest.test.js @@ -1,6 +1,7 @@ import $ from "jquery"; import pattern from "./auto-suggest"; import utils from "../../core/utils"; +import registry from "../../core/registry"; var testutils = { createInputElement: function (c) { @@ -9,6 +10,7 @@ var testutils = { "id": cfg.id || "select2", "data-pat-autosuggest": "" || cfg.data, "class": "pat-autosuggest", + "type": "text", }).appendTo($("div#lab")); }, @@ -52,11 +54,16 @@ describe("pat-autosuggest", function () { expect($(".select2-container").length).toBe(0); expect($el.hasClass("select2-offscreen")).toBeFalsy(); - pattern.init($el); + + registry.scan(document.body); await utils.timeout(1); // wait a tick for async to settle. + + $el = $("input.pat-autosuggest"); // element was replaced - re-get it. expect($el.hasClass("select2-offscreen")).toBeTruthy(); expect($(".select2-container").length).toBe(1); testutils.removeSelect2(); + + expect($el[0].getAttribute("type")).toBe("hidden"); }); }); From a4421bac559f1254ee9917158afe9f853a619012 Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Tue, 10 Nov 2020 16:26:34 +0100 Subject: [PATCH 3/4] pat-auto-suggest: Add auto-suggest to auto-submit demo --- src/pat/auto-submit/index.html | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/pat/auto-submit/index.html b/src/pat/auto-submit/index.html index 444f571a7..727309352 100644 --- a/src/pat/auto-submit/index.html +++ b/src/pat/auto-submit/index.html @@ -81,6 +81,16 @@ name="defocus" data-pat-autosubmit="delay: defocus" /> + + From 43c0442f3a48f0ee5ec84baefe967521855d5517 Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Wed, 11 Nov 2020 09:11:42 +0100 Subject: [PATCH 4/4] Core registry: Fix ``transformPattern`` to also work with patterns which extend from Base. Fixes a problem with pat-auto-suggest not auto submitting. --- CHANGES.md | 2 ++ src/core/registry.js | 8 +++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 9ce2c9ead..5f2fa55bf 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -80,6 +80,8 @@ - pat select: Add missing ```` element around the select element itself. Fixes: https://github.com/quaive/ploneintranet.prototype/issues/1087 - pat depends/core utils: Do not set inline styles when showing elements in transition mode ``none``. Fixes #719. - pat scroll: Fix scrolling offset incorrectly applied. Fixes: #763. +- Core registry: Fix ``transformPattern`` to also work with patterns which extend from Base. + Fixes a problem with pat-auto-suggest not auto submitting. ## 3.0.0-dev - unreleased diff --git a/src/core/registry.js b/src/core/registry.js index d81d4806e..994d3e950 100644 --- a/src/core/registry.js +++ b/src/core/registry.js @@ -73,10 +73,12 @@ var registry = { log.debug("Skipping disabled pattern:", name); return; } - var pattern = registry.patterns[name]; - if (pattern.transform) { + + const pattern = registry.patterns[name]; + const transform = pattern.transform || pattern.prototype?.transform; + if (transform) { try { - pattern.transform($(content)); + transform($(content)); } catch (e) { if (dont_catch) { throw e;