Skip to content

Commit de7e905

Browse files
committed
Feature: Allow toolsets to be used in event user fields.
1 parent e6aa0b6 commit de7e905

4 files changed

Lines changed: 129 additions & 29 deletions

File tree

htdocs/js/pages/PageUtils.class.js

Lines changed: 109 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2905,58 +2905,61 @@ Page.PageUtils = class PageUtils extends Page.Base {
29052905
var is_valid = true;
29062906

29072907
plugin.params.forEach( function(param) {
2908+
if (!is_valid) return;
2909+
var elem_sel = '#fe_pp_' + plugin_id + '_' + CSS.escape(param.id);
2910+
29082911
switch (param.type) {
29092912
case 'hidden':
29102913
params[ param.id ] = param.value;
29112914
break;
29122915

29132916
case 'checkbox':
2914-
params[ param.id ] = !!$('#fe_pp_' + plugin_id + '_' + CSS.escape(param.id)).is(':checked');
2917+
params[ param.id ] = !!$(elem_sel).is(':checked');
29152918
break;
29162919

29172920
case 'toolset':
2918-
var tool_id = params[ param.id ] = $('#fe_pp_' + plugin_id + '_' + CSS.escape(param.id)).val();
2921+
var tool_id = params[ param.id ] = $(elem_sel).val();
29192922

29202923
if (!param.data || !param.data.tools) return; // sanity
29212924
var tool = find_object( param.data.tools, { id: tool_id } );
29222925
if (!tool) return; // sanity
29232926

2924-
var tool_values = self.getParamValues(tool.fields || []);
2927+
var tool_values = self.getParamValues(tool.fields || [], !force);
29252928
if (!tool_values) { is_valid = false; return; } // invalid
29262929

29272930
merge_hash_into( params, tool_values );
29282931
break;
29292932

29302933
default:
2931-
params[ param.id ] = $('#fe_pp_' + plugin_id + '_' + CSS.escape(param.id)).val();
2934+
params[ param.id ] = $(elem_sel).val();
29322935
if (param.required && !params[ param.id ].length && !force) {
2933-
app.badField('#fe_pp_' + plugin_id + '_' + CSS.escape(param.id), "The “" + param.title + "” field is required.");
2936+
app.badField(elem_sel, "The “" + param.title + "” field is required.");
29342937
is_valid = false;
29352938
}
2936-
else if (!force && param.variant && !param.variant.match(/^(password|text|tel)$/) && !$('#fe_pp_' + plugin_id + '_' + CSS.escape(param.id))[0].validity.valid) {
2937-
app.badField('#fe_pp_' + plugin_id + '_' + CSS.escape(param.id), "The “" + param.title + "” field is invalid.");
2939+
else if (!force && param.variant && !param.variant.match(/^(password|text|tel)$/) && !$(elem_sel)[0].validity.valid) {
2940+
app.badField(elem_sel, "The “" + param.title + "” field is invalid.");
29382941
is_valid = false;
29392942
}
29402943
else if (param.type == 'json') {
29412944
try { params[ param.id ] = JSON.parse( params[param.id] ); }
29422945
catch (err) {
2943-
app.badField('#fe_pp_' + plugin_id + '_' + CSS.escape(param.id), "The “" + param.title + "” field is invalid.");
2946+
app.badField(elem_sel, "The “" + param.title + "” field is invalid.");
29442947
is_valid = false;
29452948
}
29462949
}
29472950
else if (param.variant == 'number') {
29482951
if (param.required || params[ param.id ].length) {
29492952
params[ param.id ] = parseFloat( params[ param.id ] );
29502953
if (isNaN(params[ param.id ])) {
2951-
app.badField('#fe_pp_' + plugin_id + '_' + CSS.escape(param.id), "The “" + param.title + "” field is invalid.");
2954+
app.badField(elem_sel, "The “" + param.title + "” field is invalid.");
29522955
is_valid = false;
29532956
} // NaN
29542957
} // required or length
29552958
else params[ param.id ] = null;
29562959
} // number
29572960

29582961
if (param.regex && str_value(params[ param.id ]).length && !str_value(params[ param.id ]).match( new RegExp(param.regex) )) {
2959-
app.badField('#fe_pp_' + plugin_id + '_' + CSS.escape(param.id), "The “" + param.title + "” field is invalid.");
2962+
app.badField(elem_sel, "The “" + param.title + "” field is invalid.");
29602963
is_valid = false;
29612964
}
29622965
break;
@@ -4699,7 +4702,7 @@ Page.PageUtils = class PageUtils extends Page.Base {
46994702
var show_lock = !!(this.pluginType === 'event');
47004703

47014704
// prepare control type menu
4702-
var ctypes = (this.controlTypes || ['checkbox', 'code', 'json', 'hidden', 'select', 'bucket', 'system', 'text', 'textarea', 'group']).map (function(key) {
4705+
var ctypes = Object.keys(config.ui.control_type_labels).map( function(key) {
47034706
return {
47044707
id: key,
47054708
title: config.ui.control_type_labels[key],
@@ -5066,7 +5069,7 @@ Page.PageUtils = class PageUtils extends Page.Base {
50665069
catch (err) { return app.doError("Failed to parse toolset JSON: " + err); }
50675070

50685071
// validate tools (control types, etc.)
5069-
if (!self.validateToolsetData(param)) return false;
5072+
if (!self.validateToolsetData(param, self.params)) return false;
50705073

50715074
delete param.required;
50725075
delete param.value;
@@ -5271,7 +5274,7 @@ Page.PageUtils = class PageUtils extends Page.Base {
52715274
this.triggerEditChange();
52725275
}
52735276

5274-
validateToolsetData(param) {
5277+
validateToolsetData(param, params) {
52755278
// ensure toolset data is properly formatted
52765279
var data = param.data;
52775280
var err_prefix = "Toolset Data Error: ";
@@ -5324,9 +5327,11 @@ Page.PageUtils = class PageUtils extends Page.Base {
53245327
if (field.id.match(app.MATCH_BAD_KEY)) { err_msg = `Tool '${tool.id}' field '${field.id}' ID is invalid (reserved word).`; is_valid = false; return; }
53255328
if (field.id == param.id) { err_msg = `Tool '${tool.id}' field '${field.id}' ID is invalid (cannot reuse toolset param ID).`; is_valid = false; return; }
53265329

5327-
if (fids[field.id]) { err_msg = `Tool '${tool.id}' field ID ${field.id} is duplicated (each must be unique per tool).`; is_valid = false; return; }
5330+
if (fids[field.id]) { err_msg = `Tool '${tool.id}' field ID '${field.id}' is duplicated (each must be unique per tool).`; is_valid = false; return; }
53285331
fids[field.id] = 1;
53295332

5333+
if (find_object(params, { id: field.id })) { err_msg = `Tool '${tool.id}' field ID '${field.id}' is already in use as a parameter.`; is_valid = false; return; }
5334+
53305335
// title
53315336
if (!field.title || !field.title.length) { err_msg = `Tool '${tool.id}' field '${field.id}' is missing a title.`; is_valid = false; return; }
53325337
if (typeof(field.title) != 'string') { err_msg = `Tool '${tool.id}' field '${field.id}' title is not a string.`; is_valid = false; return; }
@@ -5427,6 +5432,9 @@ Page.PageUtils = class PageUtils extends Page.Base {
54275432
return;
54285433
}
54295434

5435+
// toolset breaks out of a previous group
5436+
if ((param.type == 'toolset') && in_group) { html += `</fieldset>`; in_group = false; }
5437+
54305438
if (param.type != 'checkbox') {
54315439
if (elem_dis) html += '<div class="info_label" style="color:var(--red)"><i class="mdi mdi-lock">&nbsp;</i>' + param.title + ' (Admin Locked)</div>';
54325440
else html += '<div class="info_label">' + param.title + '</div>';
@@ -5540,10 +5548,52 @@ Page.PageUtils = class PageUtils extends Page.Base {
55405548
}
55415549
break;
55425550

5551+
case 'toolset':
5552+
var data = param.data || { tools: [] };
5553+
if (!data.tools) data.tools = [];
5554+
var tools = data.tools;
5555+
if (!tools.length) {
5556+
tools.push( { id: '', title: 'No Tools', description: 'No tools defined in toolset.', fields: [] } );
5557+
elem_dis = true;
5558+
}
5559+
5560+
var default_tool_id = data.default || tools[0].id;
5561+
elem_value = (param.id in params) ? params[param.id] : default_tool_id;
5562+
5563+
// make sure tool exists (plugin may have changed), default to first tool
5564+
var tool = find_object( tools, { id: elem_value } );
5565+
if (!tool) {
5566+
tool = tools[0];
5567+
elem_value = tool.id;
5568+
}
5569+
5570+
// store toolset data hidden in the DOM, so changeParamTool can get access to it
5571+
html += self.getFormTextarea({ id: elem_id + '_xy_tooldata', value: JSON.stringify(data), rows: 1, style: 'display:none' });
5572+
5573+
html += self.getFormMenuSingle({
5574+
id: elem_id,
5575+
value: elem_value,
5576+
options: tools,
5577+
disabled: elem_dis,
5578+
title: param.title,
5579+
onChange: `$P().changeParamTool('${param.id}',${explore})`
5580+
});
5581+
5582+
if (param.caption) html += '<div class="info_caption">' + inline_marked( strip_html(param.caption) ) + '</div>';
5583+
5584+
html += `<fieldset id="fs_toolset_${param.id}" class="info_fieldset">`;
5585+
html += `<legend>${strip_html(tool.title)}</legend>`;
5586+
if (tool.description) html += `<div class="tool_desc">${inline_marked(strip_html(tool.description))}</div>`;
5587+
if (tool.fields && tool.fields.length) html += self.getParamEditor(tool.fields, params, explore);
5588+
html += `</fieldset>`;
5589+
break;
5590+
55435591
} // switch type
55445592

5545-
if (param.caption) html += '<div class="info_caption">' + inline_marked( strip_html(param.caption) ) + '</div>';
5546-
else if (elem_dis) html += '<div class="info_caption">This field is locked, and only editable by administrators.</div>';
5593+
if (param.type != 'toolset') {
5594+
if (param.caption) html += '<div class="info_caption">' + inline_marked( strip_html(param.caption) ) + '</div>';
5595+
else if (elem_dis) html += '<div class="info_caption">This field is locked, and only editable by administrators.</div>';
5596+
}
55475597

55485598
html += '</div>';
55495599
} ); // foreach param
@@ -5553,6 +5603,25 @@ Page.PageUtils = class PageUtils extends Page.Base {
55535603
return html;
55545604
}
55555605

5606+
changeParamTool(param_id, explore = false) {
5607+
// change tool in toolset, redraw fields
5608+
var elem_id = 'fe_uf_' + CSS.escape(param_id);
5609+
var elem_value = $('#' + elem_id).val();
5610+
5611+
var data = JSON.parse( $('#' + elem_id + '_xy_tooldata').val() );
5612+
var tool = find_object( data.tools, { id: elem_value } );
5613+
if (!tool) return; // sanity
5614+
5615+
var $fieldset = $(`#fs_toolset_${CSS.escape(param_id)}`);
5616+
var html = '';
5617+
5618+
html += `<legend>${strip_html(tool.title)}</legend>`;
5619+
if (tool.description) html += `<div class="tool_desc">${inline_marked(strip_html(tool.description))}</div>`;
5620+
if (tool.fields && tool.fields.length) html += this.getParamEditor(tool.fields, {}, explore);
5621+
5622+
$fieldset.html(html).buttonize();
5623+
}
5624+
55565625
viewParamCode(param_id) {
55575626
// show param code (no editing)
55585627
var elem_id = 'fe_uf_' + CSS.escape(param_id);
@@ -5586,43 +5655,59 @@ Page.PageUtils = class PageUtils extends Page.Base {
55865655

55875656
getParamValues(fields, validate = true) {
55885657
// get all values for params hash
5658+
var self = this;
55895659
var params = {};
55905660
var is_valid = true;
55915661
if (!fields || !fields.length) return {}; // none defined
55925662

55935663
fields.forEach( function(param) {
5664+
if (!is_valid) return;
5665+
var elem_sel = '#fe_uf_' + CSS.escape(param.id);
5666+
55945667
if (param.type == 'hidden') params[ param.id ] = param.value;
5595-
else if (param.type == 'checkbox') params[ param.id ] = !!$('#fe_uf_' + CSS.escape(param.id)).is(':checked');
5668+
else if (param.type == 'checkbox') params[ param.id ] = !!$(elem_sel).is(':checked');
5669+
else if (param.type == 'toolset') {
5670+
var tool_id = params[ param.id ] = $(elem_sel).val();
5671+
5672+
if (!param.data || !param.data.tools) return; // sanity
5673+
var tool = find_object( param.data.tools, { id: tool_id } );
5674+
if (!tool) return; // sanity
5675+
5676+
var tool_values = self.getParamValues(tool.fields || [], validate);
5677+
if (!tool_values) { is_valid = false; return; } // invalid
5678+
5679+
merge_hash_into( params, tool_values );
5680+
}
55965681
else {
5597-
params[ param.id ] = $('#fe_uf_' + CSS.escape(param.id)).val();
5682+
params[ param.id ] = $(elem_sel).val();
55985683
if (param.required && !params[ param.id ].length && validate) {
5599-
app.badField('#fe_uf_' + CSS.escape(param.id), "The &ldquo;" + param.title + "&rdquo; field is required.");
5684+
app.badField(elem_sel, "The &ldquo;" + param.title + "&rdquo; field is required.");
56005685
is_valid = false;
56015686
}
5602-
else if (validate && param.variant && !param.variant.match(/^(password|text|tel)$/) && !$('#fe_uf_' + param.id)[0].validity.valid) {
5603-
app.badField('#fe_uf_' + CSS.escape(param.id), "The &ldquo;" + param.title + "&rdquo; field is invalid.");
5687+
else if (validate && param.variant && !param.variant.match(/^(password|text|tel)$/) && !$(elem_sel)[0].validity.valid) {
5688+
app.badField(elem_sel, "The &ldquo;" + param.title + "&rdquo; field is invalid.");
56045689
is_valid = false;
56055690
}
56065691
else if (param.type == 'json') {
56075692
try { params[ param.id ] = JSON.parse( params[param.id] ); }
56085693
catch (err) {
5609-
app.badField('#fe_uf_' + CSS.escape(param.id), "The &ldquo;" + param.title + "&rdquo; field is invalid.");
5694+
app.badField(elem_sel, "The &ldquo;" + param.title + "&rdquo; field is invalid.");
56105695
is_valid = false;
56115696
}
56125697
}
56135698
else if (param.variant == 'number') {
56145699
if (param.required || params[ param.id ].length) {
56155700
params[ param.id ] = parseFloat( params[ param.id ] );
56165701
if (isNaN(params[ param.id ])) {
5617-
app.badField('#fe_uf_' + CSS.escape(param.id), "The &ldquo;" + param.title + "&rdquo; field is invalid.");
5702+
app.badField(elem_sel, "The &ldquo;" + param.title + "&rdquo; field is invalid.");
56185703
is_valid = false;
56195704
} // NaN
56205705
} // required or has length
56215706
else params[ param.id ] = null;
56225707
} // number
56235708

56245709
if (param.regex && str_value(params[ param.id ]).length && !str_value(params[ param.id ]).match( new RegExp(param.regex) )) {
5625-
app.badField('#fe_uf_' + CSS.escape(param.id), "The &ldquo;" + param.title + "&rdquo; field is invalid.");
5710+
app.badField(elem_sel, "The &ldquo;" + param.title + "&rdquo; field is invalid.");
56265711
is_valid = false;
56275712
}
56285713
} // textish

htdocs/js/pages/admin/Plugins.class.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ Page.Plugins = class Plugins extends Page.PageUtils {
1010
// called once at page load
1111
this.default_sub = 'list';
1212
this.dom_prefix = 'ep';
13-
this.controlTypes = ['checkbox', 'code', 'json', 'hidden', 'select', 'bucket', 'system', 'text', 'textarea', 'toolset', 'group'];
1413
}
1514

1615
onActivate(args) {

lib/api/events.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,6 +1055,7 @@ class Events {
10551055
if (params.fields) {
10561056
if (!Array.isArray(params.fields)) return this.doError('api', "Malformed event parameter: fields (must be array)", callback);
10571057
var plugin = Tools.findObject(this.plugins, { id: params.plugin }) || { params: [] };
1058+
var ids = {};
10581059

10591060
for (var idx = 0, len = params.fields.length; idx < len; idx++) {
10601061
var field = params.fields[idx];
@@ -1064,12 +1065,25 @@ class Events {
10641065
if (field.id.match(Tools.MATCH_BAD_KEY)) return this.doError('api', err_prefix + ": Invalid ID parameter: " + params.id, callback);
10651066

10661067
if (typeof(field.type) != 'string') return this.doError('api', err_prefix + ": Type must be a string", callback);
1068+
if (!this.config.getPath('ui.control_type_labels.' + field.type)) return this.doError('api', err_prefix + ": Invalid type", callback);
10671069

10681070
if (field.title && (typeof(field.title) != 'string')) return this.doError('api', err_prefix + ": Title must be a string", callback);
10691071
if (field.title && field.title.match(/[<>]/)) return this.doError('api', err_prefix + ": Title contains illegal characters", callback);
10701072

10711073
if (Tools.findObject(plugin.params, { id: field.id })) return this.doError('api', err_prefix + ": ID is already in use (as plugin parameter).", callback);
10721074

1075+
if (ids[field.id]) return this.doError('api', err_prefix + ": Duplicate field ID: " + field.id, callback);
1076+
ids[field.id] = 1;
1077+
1078+
if (field.type == 'toolset') {
1079+
if (!this.requireValidToolsetData(field, params.fields, callback)) return false;
1080+
}
1081+
else if ((field.type == 'text') && field.variant) {
1082+
if (!Tools.findObject( this.config.getPath('ui.text_field_variants'), { id: field.variant } )) {
1083+
return this.doError('api', err_prefix + ": Invalid text field variant", callback);
1084+
}
1085+
}
1086+
10731087
if (field.regex) try { new RegExp(field.regex); }
10741088
catch (err) { return this.doError('api', err_prefix + ": Invalid regular expression: " + err, callback); }
10751089
}

lib/api/plugins.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ class Plugins {
378378
if (!param.id.match(/^[\w\-\.]+$/)) return this.doError('api', err_prefix + `: ID field must contain only alphanumerics, underscore, dash and period.`, callback);
379379
if (!param.id.match(/^[A-Za-z_]/)) return this.doError('api', err_prefix + `: ID field must begin with an alpha character or underscore.`, callback);
380380
if (param.id.match(Tools.MATCH_BAD_KEY)) return this.doError('api', err_prefix + ": Invalid ID parameter: " + params.id, callback);
381-
if (ids[param.id]) return this.doError('api', err_prefix + ": Duplicate ID parameter: " + params.id, callback);
381+
if (ids[param.id]) return this.doError('api', err_prefix + ": Duplicate parameter ID: " + param.id, callback);
382382
ids[param.id] = 1;
383383

384384
if (typeof(param.type) != 'string') return this.doError('api', err_prefix + ": Type must be a string", callback);
@@ -391,7 +391,7 @@ class Plugins {
391391
if (param.caption && param.caption.match(/[<>]/)) return this.doError('api', err_prefix + ": Caption contains illegal characters", callback);
392392

393393
if (param.type == 'toolset') {
394-
if (!this.requireValidToolsetData(param, callback)) return false;
394+
if (!this.requireValidToolsetData(param, params.params, callback)) return false;
395395
}
396396
else if ((param.type == 'text') && param.variant) {
397397
if (!Tools.findObject( this.config.getPath('ui.text_field_variants'), { id: param.variant } )) {
@@ -407,7 +407,7 @@ class Plugins {
407407
return true;
408408
}
409409

410-
requireValidToolsetData(param, callback) {
410+
requireValidToolsetData(param, params, callback) {
411411
// ensure toolset data is properly formatted
412412
var self = this;
413413
var data = param.data;
@@ -461,9 +461,11 @@ class Plugins {
461461
if (field.id.match(Tools.MATCH_BAD_KEY)) { err_msg = `Tool '${tool.id}' field '${field.id}' ID is invalid (reserved word).`; is_valid = false; return; }
462462
if (field.id == param.id) { err_msg = `Tool '${tool.id}' field '${field.id}' ID is invalid (cannot reuse toolset param ID).`; is_valid = false; return; }
463463

464-
if (fids[field.id]) { err_msg = `Tool '${tool.id}' field ID ${field.id} is duplicated (each must be unique per tool).`; is_valid = false; return; }
464+
if (fids[field.id]) { err_msg = `Tool '${tool.id}' field ID '${field.id}' is duplicated (each must be unique per tool).`; is_valid = false; return; }
465465
fids[field.id] = 1;
466466

467+
if (Tools.findObject(params, { id: field.id })) { err_msg = `Tool '${tool.id}' field ID '${field.id}' is already in use as a parameter.`; is_valid = false; return; }
468+
467469
// title
468470
if (!field.title || !field.title.length) { err_msg = `Tool '${tool.id}' field '${field.id}' is missing a title.`; is_valid = false; return; }
469471
if (typeof(field.title) != 'string') { err_msg = `Tool '${tool.id}' field '${field.id}' title is not a string.`; is_valid = false; return; }

0 commit comments

Comments
 (0)