fix many errors around et2-select emulating old taglist:

- preprocessor: translate attributes autocomplete_url -> searchUrl, autocomplete_params -> searchOptions, and allow options
- sending search query as URL/GET parameter with default of app: <appname>
- handle searchUrl like allowFreeEntries by adding selected result to select_options via createFreeEntries, as they otherwise get removed by fix_bad_value not finding the value in select_options
- change taglist validation (again) to not validate search values
- still requires changes in application code, as taglist always behaved like multiple=true (returning and expecting an array of values) and did automatically search from client-side for it's initial value(s)
--> maybe more changes are in order to NOT require changing application code
--> fixes editing Sieve rules
This commit is contained in:
ralf 2022-08-08 17:27:21 +02:00
parent b0d1d82736
commit e7eb9f42e3
6 changed files with 49 additions and 27 deletions

View File

@ -199,7 +199,7 @@ function send_template()
}, $str); }, $str);
// handling of select and taglist widget, incl. removing of type attribute // handling of select and taglist widget, incl. removing of type attribute
$str = preg_replace_callback('#<(select|taglist|listbox)(-[^ ]+)? ([^>]+?)(/|>(.*?)</select)>#s', static function (array $matches) $str = preg_replace_callback('#<(select|taglist|listbox)(-[^ ]+)? ([^>]+?)(/|>(.*?)</(select|taglist|listbox))>#s', static function (array $matches)
{ {
$attrs = parseAttrs($matches[3]); $attrs = parseAttrs($matches[3]);
@ -209,16 +209,31 @@ function send_template()
$attrs['multiple'] = 'true'; $attrs['multiple'] = 'true';
unset($attrs['tags']); unset($attrs['tags']);
} }
// taglist had allowFreeEntries and enableEditMode with a default of true, while et2-select has it with a default of false // converting taglist to et2-select
if($matches['1'] === 'taglist' && !$matches[2]) if($matches['1'] === 'taglist')
{ {
if(!isset($attrs['allowFreeEntries'])) // taglist had allowFreeEntries and enableEditMode with a default of true, while et2-select has it with a default of false
if(!$matches[2] && !isset($attrs['allowFreeEntries']) && (empty($matches[5]) || !preg_match('#</?option(\s[^>]+|/)>#', $matches[5])))
{ {
$attrs['allowFreeEntries'] = 'true'; $attrs['allowFreeEntries'] = 'true';
if(!isset($attrs['editModeEnabled']))
{
$attrs['editModeEnabled'] = 'true';
}
} }
if(!isset($attrs['editModeEnabled'])) if (isset($attrs['autocomplete_url']))
{ {
$attrs['editModeEnabled'] = 'true'; $attrs['searchUrl'] = $attrs['autocomplete_url'];
if (isset($attrs['autocomplete_params']))
{
$attrs['searchOptions'] = $attrs['autocomplete_params'];
}
unset($attrs['autocomplete_url'], $attrs['autocomplete_params']);
}
if (isset($attrs['maxSelection']) && $attrs['maxSelection'] === '1')
{
unset($attrs['multiple'], $attrs['maxSelection']);
} }
} }
// no multiple="toggle" or expand_multiple_rows="N" currently, thought Shoelace's select multiple="true" is relative close // no multiple="toggle" or expand_multiple_rows="N" currently, thought Shoelace's select multiple="true" is relative close

View File

@ -235,7 +235,13 @@ export function cleanSelectOptions(options : SelectOption[] | string[] | object)
// make sure value is a string, and label not an object with sub-options // make sure value is a string, and label not an object with sub-options
options.forEach(option => options.forEach(option =>
{ {
if (typeof option.value !== 'string') // old taglist used id, instead of value
if (typeof option.value === 'undefined' && typeof option.id !== 'undefined')
{
option.value = option.id;
delete option.id;
}
if (typeof option.value === 'number')
{ {
option.value = option.value.toString(); option.value = option.value.toString();
} }

View File

@ -239,7 +239,7 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
this.search = false; this.search = false;
this.searchUrl = ""; this.searchUrl = "";
this.searchOptions = {}; this.searchOptions = {app: "addressbook"};
this.allowFreeEntries = false; this.allowFreeEntries = false;
this.editModeEnabled = false; this.editModeEnabled = false;
@ -402,13 +402,13 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
{ {
super.value = new_value; super.value = new_value;
if(!new_value || !this.allowFreeEntries) if(!new_value || !this.allowFreeEntries && !this.searchUrl)
{ {
return; return;
} }
// Overridden to add options if allowFreeEntries=true // Overridden to add options if allowFreeEntries=true
if(typeof this.value == "string" && !this._menuItems.find(o => o.value == this.value)) if(typeof this.value == "string" && !this._menuItems.find(o => o.value == this.value && !o.classList.contains('remote')))
{ {
this.createFreeEntry(this.value); this.createFreeEntry(this.value);
} }
@ -416,7 +416,7 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
{ {
this.value.forEach((e) => this.value.forEach((e) =>
{ {
if(!this._menuItems.find(o => o.value == e)) if(!this._menuItems.find(o => o.value == e && !o.classList.contains('remote')))
{ {
this.createFreeEntry(e); this.createFreeEntry(e);
} }
@ -696,7 +696,9 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
/** /**
* Actually query the server. * Actually query the server.
* *
* This can be overridden to change request parameters * This can be overridden to change request parameters or eg. send them as POST parameters.
*
* Default implementation here sends options as (additional) GET paramters plus search string as $_GET['query']!
* *
* @param {string} search * @param {string} search
* @param {object} options * @param {object} options
@ -705,7 +707,7 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
*/ */
protected remoteQuery(search : string, options : object) protected remoteQuery(search : string, options : object)
{ {
return this.egw().request(this.searchUrl, [search, options]).then((result) => return this.egw().request(this.egw().link(this.egw().ajaxUrl(this.searchUrl), {query: search, ...options})).then((result) =>
{ {
this.processRemoteResults(result); this.processRemoteResults(result);
}); });

View File

@ -167,7 +167,7 @@ class Taglist extends Etemplate\Widget
} }
continue; continue;
} }
if(count($allowed) && !$this->attrs['allowFreeEntries'] && !array_key_exists($val,$allowed)) if(count($allowed) && !$this->attrs['allowFreeEntries'] && empty($this->attrs['autocomplete_url']) && !array_key_exists($val,$allowed))
{ {
self::set_validation_error($form_name,lang("'%1' is NOT allowed ('%2')!",$val,implode("','",array_keys($allowed))),''); self::set_validation_error($form_name,lang("'%1' is NOT allowed ('%2')!",$val,implode("','",array_keys($allowed))),'');
unset($value[$key]); unset($value[$key]);

View File

@ -230,7 +230,7 @@ class mail_sieve
{ {
//Instantiate an eTemplate object, representing sieve.edit template //Instantiate an eTemplate object, representing sieve.edit template
$etmpl = new Etemplate('mail.sieve.edit'); $etmpl = new Etemplate('mail.sieve.edit');
$etmpl->setElementAttribute('action_folder_text','autocomplete_params', array('noPrefixId'=> true)); $etmpl->setElementAttribute('action_folder_text','searchOptions', array('noPrefixId'=> true));
if (!is_array($content)) if (!is_array($content))
{ {
if ( $this->getRules($_GET['ruleID']) && isset($_GET['ruleID'])) if ( $this->getRules($_GET['ruleID']) && isset($_GET['ruleID']))
@ -242,7 +242,7 @@ class mail_sieve
switch ($rules['action']) switch ($rules['action'])
{ {
case 'folder': case 'folder':
$content['action_folder_text'][] = $rules['action_arg']; $content['action_folder_text'] = $rules['action_arg'];
break; break;
case 'address': case 'address':
@ -297,7 +297,7 @@ class mail_sieve
switch ($content['action']) switch ($content['action'])
{ {
case 'folder': case 'folder':
$newRule['action_arg'] = implode($content['action_folder_text']); $newRule['action_arg'] = $content['action_folder_text'];
break; break;
case 'address': case 'address':
$content['action_address_text'] = self::strip_rfc882_addresses($content['action_address_text']); $content['action_address_text'] = self::strip_rfc882_addresses($content['action_address_text']);
@ -398,8 +398,10 @@ class mail_sieve
$content['no_forward'] = $this->account->acc_smtp_type !== Api\Mail\Smtp::class && !$this->account->acc_user_forward; $content['no_forward'] = $this->account->acc_smtp_type !== Api\Mail\Smtp::class && !$this->account->acc_user_forward;
//Set the preselect_options for mail/folders as we are not allow free entry for folder taglist //Set the preselect_options for mail/folders as we are not allow free entry for folder taglist
$sel_options['action_folder_text'] = $this->ajax_getFolders(0,true,null,true); if (!empty($content['action_folder_text']))
{
$sel_options['action_folder_text'] = [$content['action_folder_text'] => $content['action_folder_text']];
}
return $etmpl->exec('mail.mail_sieve.edit',$content,$sel_options,$readonlys,array(),2); return $etmpl->exec('mail.mail_sieve.edit',$content,$sel_options,$readonlys,array(),2);
} }
@ -1299,5 +1301,4 @@ class mail_sieve
if ($_REQUEST['noPrefixId']) $_noPrefixId = $_REQUEST['noPrefixId']; if ($_REQUEST['noPrefixId']) $_noPrefixId = $_REQUEST['noPrefixId'];
$mailCompose->ajax_searchFolder($_searchStringLength, $_returnList, $_mailaccountToSearch, $_noPrefixId); $mailCompose->ajax_searchFolder($_searchStringLength, $_returnList, $_mailaccountToSearch, $_noPrefixId);
} }
} }

View File

@ -149,16 +149,14 @@
<checkbox label="Use regular expressions (see wikipedia for information on POSIX regular expressions)" id="regexp"/> <checkbox label="Use regular expressions (see wikipedia for information on POSIX regular expressions)" id="regexp"/>
</row> </row>
<row class="dialogFooterToolbar"> <row class="dialogFooterToolbar">
<hbox> <hbox width="100%">
<button statustext="Saves this rule" label="Save" id="button[save]"/> <button statustext="Saves this rule" label="Save" id="button[save]"/>
<button statustext="Applies the changes made" label="Apply" id="button[apply]"/> <button statustext="Applies the changes made" label="Apply" id="button[apply]"/>
<button label="Cancel" id="button[cancel]"/> <button label="Cancel" id="button[cancel]" onclick="window.close()"/>
<hbox align="right"> <button label="Delete" id="button[delete]" align="right"/>
<button label="Delete" id="button[delete]" />
</hbox>
</hbox> </hbox>
</row> </row>
</rows> </rows>
</grid> </grid>
</template> </template>
</overlay> </overlay>