Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@
- pat select: Add missing ``<span>`` 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
Expand Down
8 changes: 5 additions & 3 deletions src/core/registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
53 changes: 21 additions & 32 deletions src/pat/auto-submit/auto-submit.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,13 @@
/**
* 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";
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
Expand All @@ -26,55 +17,53 @@ 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);
}
return cfg;
},
},

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."
Expand All @@ -100,21 +89,21 @@ 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 {
$el.off(".pat-autosubmit");
}
},

onInputChange: function (ev) {
onInputChange(ev) {
ev.stopPropagation();
$(this).submit();
log.debug("triggered by " + ev.type);
Expand Down
10 changes: 10 additions & 0 deletions src/pat/auto-submit/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,16 @@
name="defocus"
data-pat-autosubmit="delay: defocus"
/></label>

<label>Select a item
<input
name="a1"
class="pat-autosuggest"
placeholder="Pick some fruit"
data-pat-autosuggest="words: Apples, Oranges, Pears, Bananas"
type="text"
/>
</label>
</fieldset>
</form>
</div>
Expand Down
112 changes: 52 additions & 60 deletions src/pat/auto-suggest/auto-suggest.js
Original file line number Diff line number Diff line change
Expand Up @@ -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", "");
Expand Down Expand Up @@ -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(
Expand All @@ -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 };
}
Expand All @@ -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) {
Expand All @@ -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 };
});
}
Expand All @@ -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) {
Expand All @@ -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 {
Expand All @@ -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,
Expand All @@ -240,10 +241,10 @@ export default Base.extend({
},
},
},
select2_config
config
);
}
return select2_config;
return config;
},

destroy($el) {
Expand All @@ -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 = $("<input type='hidden'/>").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");
});
},
});
Loading