LSattr_html :: mail : add autocomplete feature

This commit is contained in:
Benjamin Renard 2020-04-23 19:53:56 +02:00
parent 103689d6ad
commit ffdb8d4cf7
5 changed files with 465 additions and 15 deletions

View file

@ -1,13 +1,51 @@
<sect4 id="config-LSattr_html_mail"> <sect4 id="config-LSattr_html_mail">
<title>LSattr_html_mail</title> <title>LSattr_html_mail</title>
<para>Ce type est utilisé pour la gestion des attributs dont la valeur est <para>Ce type est utilisé pour la gestion des attributs dont la valeur est
une adresse e-mail. Il propose directement dans l'interface, la possibilité une adresse e-mail. Il offre les fonctionnalités suivantes :
d'envoyer des mails à l'adresse saisie.</para> <itemizedlist>
<listitem><simpara>la possibilité d'envoyer des mails directement depuis l'interface
de l'application ;</simpara></listitem>
<listitem><simpara>l'autocomplétion lors de la saisie d'une adresse.</simpara></listitem>
</itemizedlist>
</para>
<programlisting linenumbering="unnumbered"> <programlisting linenumbering="unnumbered">
<citetitle>Structure</citetitle>... <citetitle>Structure</citetitle>...
<![CDATA['html_options' => array( <![CDATA['html_options' => array(
'disableMailSending' => [booléen] 'disableMailSending' => [booléen],
// Autocomplétion pour un type d'LSobject donné
'autocomplete' => array (
'object_type' => '[Type d'LSobject]',
'mail_attributes' => array (
'mail',
'mailAlternateAddress',
[...]
),
'filter' => '[filtre LDAP]',
'basedn' => '[base DN spécifique]',
'scope' => '[scope de recherche]',
'displayFormat' => '[LSformat]',
'onlyAccessible' => [booléen],
),
// Autocomplétion sur la base d'une recherche LDAP brute
'autocomplete' => array (
'mail_attributes' => array (
'mail',
'mailAlternateAddress',
[...]
),
'filter' => '[filtre LDAP]',
'basedn' => '[base DN spécifique]',
'scope' => '[scope de recherche]',
'displayFormat' => '[LSformat]',
'onlyAccessible' => [booléen],
),
// Autocomplétion (par défaut)
'autocomplete' => true,
),]]> ),]]>
... ...
</programlisting> </programlisting>
@ -25,9 +63,89 @@
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term>autocomplete</term>
<listitem>
<para>Paramètrage de l'autocomplétion des adresses mails saisies : Il peut s'agir
d'un tableau configurant les paramètres de l'autocomplétion ou simplement
<literal>true</literal> pour activer l'autocomplétion par défaut, c'est à dire la recherche brute
dans l'annuaire de n'importe quel objet ayant l'attribut <literal>mail</literal>.</para>
<para>En cas de configuration avancée, il est possible de faire une recherche :
<itemizedlist>
<listitem><simpara>Sur la base d'un type d'&LSobject; donné : l'autocomplétion se fera
alors comme n'importe quelle recherche d'un type d'objet donné.</simpara></listitem>
<listitem><simpara>Sur la base d'une recherche brute dans l'annuaire : l'autocomplétion se
fera alors sur la valeur de l'adresse mail recherchée et au travers une recherche brute dans
l'annuaire sur n'importe quels objets ayant une adresse email correspondant.</simpara></listitem>
</itemizedlist>
</para>
<para>Les paramètres associés à ces deux cas de figure sont décrits ci-dessous :
<variablelist>
<varlistentry>
<term>object_type</term>
<listitem>
<simpara>Le type d'&LSobject; recherché.</simpara>
</listitem>
</varlistentry>
<varlistentry>
<term>mail_attributes</term>
<listitem>
<simpara>Le(s) nom de l'attribut stockant les adresses emails recherchées. Il peut s'agir d'une chaîne
de caractères ou d'un tableau s'il y a plusieurs attributs.</simpara>
</listitem>
</varlistentry>
<varlistentry>
<term>filter</term>
<listitem>
<simpara>Un filtre de recherche falcultatif venant en plus de celui calculé automatiquement à partir
du mot clé de recherche.</simpara>
</listitem>
</varlistentry>
<varlistentry>
<term>basedn</term>
<listitem>
<simpara>Le <emphasis>basedn</emphasis> de la recherche. <emphasis>Paramètre
facultatif.</emphasis></simpara>
</listitem>
</varlistentry>
<varlistentry>
<term>scope</term>
<listitem>
<simpara>Le <emphasis>scope</emphasis> de la recherche. <emphasis>Paramètre
facultatif, par défaut : <literal>sub</literal>.</emphasis></simpara>
</listitem>
</varlistentry>
<varlistentry>
<term>displayFormat</term>
<listitem>
<simpara>Le &LSformat; d'affichage des objets trouvés. Ce paramètre est facultatif et par défaut,
il s'agira du format d'affichage propre au type d'&LSobject; (si défini) et à défaut, l'adresse
mail trouvée sera affichée.</simpara>
</listitem>
</varlistentry>
<varlistentry>
<term>onlyAccessible</term>
<listitem>
<simpara>Booléen falcultatif définissant si seul les &LSobjects; auxquels l'utilisateur connecté à accès
doivent être considérés comme sélectionnables (Faux par défaut). Ce paramètre n'est appliqué que dans
le cas d'une recherche pour un type d'&LSobject; donné.</simpara>
</listitem>
</varlistentry>
</variablelist>
</para>
</listitem>
</varlistentry>
</variablelist> </variablelist>
<important><simpara>Ce type d'attribut HTML est dérivé du type <important><simpara>Ce type d'attribut HTML est dérivé du type
<link linkend='config-LSattr_html_text'>text</link>. Il profite donc de toutes <link linkend='config-LSattr_html_text'>text</link>. Il profite donc de toutes
les fonctionnalités d'un champ de ce type (autogénération, ...).</simpara> les fonctionnalités d'un champ de ce type (autogénération, ...).</simpara>
</important> </important>

View file

@ -0,0 +1,29 @@
ul.LSformElement_mail_autocomplete {
border: 1px solid #ccc;
width: 200px;
margin: 0;
margin-top: 0.1em;
max-height: 10em;
overflow: auto;
padding: 0;
list-style-type: none;
}
li.LSformElement_mail_autocomplete {
cursor: pointer;
border-bottom: 1px dotted #ccc;
font-size: 0.8em;
}
li.LSformElement_mail_autocomplete:last-of-type {
border: none;
}
li.LSformElement_mail_autocomplete_over {
background-color: #ccc;
}
li.LSformElement_mail_autocomplete_current {
font-style: italic;
color: #777;
}

View file

@ -34,16 +34,21 @@ LSsession :: loadLSclass('LSformElement_text');
class LSformElement_mail extends LSformElement_text { class LSformElement_mail extends LSformElement_text {
var $JSscripts = array( var $JSscripts = array(
'LSformElement_mail_field.js',
'LSformElement_mail.js' 'LSformElement_mail.js'
); );
var $CSSfiles = array(
'LSformElement_mail.css',
);
var $fetchVariables = array( var $fetchVariables = array(
'uriClass' => 'LSformElement_mail', 'uriClass' => 'LSformElement_mail',
'uriPrefix' => 'mailto:' 'uriPrefix' => 'mailto:'
); );
var $fieldTemplate = 'LSformElement_uri_field.tpl'; var $fieldTemplate = 'LSformElement_uri_field.tpl';
public function getDisplay() { public function getDisplay() {
LSsession :: addHelpInfos ( LSsession :: addHelpInfos (
'LSformElement_mail', 'LSformElement_mail',
@ -54,6 +59,9 @@ class LSformElement_mail extends LSformElement_text {
if (LSsession :: loadLSclass('LSmail')) { if (LSsession :: loadLSclass('LSmail')) {
LSmail :: loadDependenciesDisplay(); LSmail :: loadDependenciesDisplay();
} }
if (!$this -> isFreeze() && $this -> getParam('html_options.autocomplete')) {
LSsession :: addJSconfigParam('LSformElement_mail_autocomplete_noResultLabel', _('No result'));
}
return parent :: getDisplay(); return parent :: getDisplay();
} }
@ -61,8 +69,135 @@ class LSformElement_mail extends LSformElement_text {
if ($this -> getParam('html_options.disableMailSending', false, 'bool')) { if ($this -> getParam('html_options.disableMailSending', false, 'bool')) {
$this -> fetchVariables['uriClass'] .= " LSformElement_mail_disableMailSending"; $this -> fetchVariables['uriClass'] .= " LSformElement_mail_disableMailSending";
} }
if ($this -> getParam('html_options.autocomplete', false, 'bool')) {
$this -> fetchVariables['uriClass'] .= " LSformElement_mail_autocomplete";
}
return parent :: fetchTemplate($template,$variables); return parent :: fetchTemplate($template,$variables);
} }
} /**
* Autocomplete email
*
* @param[in] $pattern The pattern of the search
*
* @retval array(mail -> displayName) Found emails
*/
public function autocomplete($pattern) {
$ret = array();
if ($this -> getParam('html_options.autocomplete')) {
$mail_attributes = $this -> getParam('html_options.autocomplete.mail_attributes', array('mail'));
if (!is_array($mail_attributes)) $mail_attributes = array($mail_attributes);
$obj_type = $this -> getParam('html_options.autocomplete.object_type');
if ($obj_type) {
// Search with a specific objectType
if (LSsession :: loadLSobject($obj_type)) {
$obj = new $obj_type();
$filters = array();
foreach($mail_attributes as $attr) {
$filters[] = Net_LDAP2_Filter::create($attr, 'present');
}
$filter = (count($filters)==1?$filters[0]:Net_LDAP2_Filter::combine('or', $filters));
if ($this -> getParam('html_options.autocomplete.filter')) {
$filter = Net_LDAP2_Filter::combine(
'and',
array(
Net_LDAP2_Filter::parse($this -> getParam('html_options.autocomplete.filter')),
$filter,
)
);
}
$sparams = array(
'pattern' => $pattern,
'attributes' => $mail_attributes,
'displayFormat' => $this -> getParam('html_options.autocomplete.display_name_format'),
'filter' => $filter,
'onlyAccessible' => $this -> getParam('html_options.autocomplete.onlyAccessible', false, 'bool'),
);
LSdebug($filter->as_string());
$search = new LSsearch(
$obj_type,
'LSformElement_mail::autocomplete',
$sparams,
true
);
$search -> run();
foreach($search -> getSearchEntries() as $e) {
foreach($mail_attributes as $attr) {
$mails = $e->get($attr);
if (!$mails) continue;
if (!is_array($mails)) $mails = array($mails);
foreach($mails as $mail)
$ret[$mail] = $e->displayName;
}
}
}
}
else {
$filters = array();
foreach($mail_attributes as $attr) {
$filters[] = Net_LDAP2_Filter::create($attr, 'contains', $pattern);
}
$filter = (count($filters)==1?$filters[0]:Net_LDAP2_Filter::combine('or', $filters));
if ($this -> getParam('html_options.autocomplete.filter')) {
$filter = Net_LDAP2_Filter::combine(
'and',
array(
Net_LDAP2_Filter::parse($this -> getParam('html_options.autocomplete.filter')),
$filter,
)
);
}
$displayNameFormat = $this -> getParam('html_options.autocomplete.display_name_format', false);
$attributes = $mail_attributes;
if ($displayNameFormat)
foreach(getFieldInFormat($displayNameFormat) as $attr)
if(!in_array($attr, $attributes))
$attributes[] = $attr;
$objects = LSldap :: search (
$filter,
$this -> getParam('html_options.autocomplete.basedn', null),
array (
'attributes' => $attributes,
'scope' => $this -> getParam('html_options.autocomplete.scope', 'sub'),
)
);
if (is_array($objects)) {
foreach($objects as $object) {
$displayName = ($displayNameFormat?getFData($displayNameFormat, $object['attrs']):null);
foreach($mail_attributes as $attr) {
if (!isset($object['attrs'][$attr])) continue;
$mails = $object['attrs'][$attr];
if (!is_array($mails)) $mails = array($mails);
foreach($mails as $mail)
$ret[$mail] = ($displayName?$displayName:$mail);
}
}
}
}
}
return $ret;
}
/**
* This ajax method is used by the autocomplete function of the form element.
*
* @param[in] $data The address to the array of data witch will be return by the ajax request
*
* @retval void
**/
public static function ajax_autocomplete(&$data) {
if ((isset($_REQUEST['attribute'])) && (isset($_REQUEST['objecttype'])) && (isset($_REQUEST['pattern'])) && (isset($_REQUEST['idform'])) ) {
if (LSsession ::loadLSobject($_REQUEST['objecttype'])) {
$object = new $_REQUEST['objecttype']();
$form = $object -> getForm($_REQUEST['idform']);
$field=$form -> getElement($_REQUEST['attribute']);
$data['mails'] = $field -> autocomplete($_REQUEST['pattern']);
}
}
}
}

View file

@ -1,18 +1,19 @@
var LSformElement_mail = new Class({ var LSformElement_mail = new Class({
initialize: function(){ initialize: function(){
this.fields = [];
this.initialiseLSformElement_mail(); this.initialiseLSformElement_mail();
if (typeof(varLSform) != "undefined") { if (typeof(varLSform) != "undefined") {
varLSform.addModule("LSformElement_mail",this); varLSform.addModule("LSformElement_mail",this);
} }
this.LSmail_open = 0; this.LSmail_open = 0;
}, },
initialiseLSformElement_mail: function(el) { initialiseLSformElement_mail: function(el) {
if (typeof(el) == 'undefined') { if (typeof(el) == 'undefined') {
el = document; el = document;
} }
el.getElements('input.LSformElement_mail').each(function(input) { el.getElements('input.LSformElement_mail').each(function(input) {
if (!input.hasClass('LSformElement_mail_disableMailSending')) { if (!input.hasClass('LSformElement_mail_disableMailSending')) {
this.addBtnAfter.bind(this)(input); this.addBtnAfter.bind(this)(input);
} }
}, this); }, this);
@ -21,8 +22,18 @@ var LSformElement_mail = new Class({
this.addBtnAfter.bind(this)(a); this.addBtnAfter.bind(this)(a);
} }
}, this); }, this);
var getName = /^(.*)\[\]$/;
el.getElements('input.LSformElement_mail_autocomplete').each(function(input) {
this.fields.push(
new LSformElement_mail_field(
getName.exec(input.name)[1],
input
)
);
}, this);
}, },
addBtnAfter: function(el) { addBtnAfter: function(el) {
var btn = new Element('img'); var btn = new Element('img');
btn.setProperties({ btn.setProperties({
@ -33,12 +44,12 @@ var LSformElement_mail = new Class({
btn.addEvent('click',this.onBtnClick.bind(this,btn)); btn.addEvent('click',this.onBtnClick.bind(this,btn));
varLSdefault.addHelpInfo(btn,'LSformElement_mail','mail'); varLSdefault.addHelpInfo(btn,'LSformElement_mail','mail');
}, },
reinitialize: function(el) { reinitialize: function(el) {
varLSform.initializeModule('LSformElement_text',el); varLSform.initializeModule('LSformElement_text',el);
this.initialiseLSformElement_mail(el); this.initialiseLSformElement_mail(el);
}, },
onBtnClick: function(btn) { onBtnClick: function(btn) {
if (this.LSmail_open==0) { if (this.LSmail_open==0) {
var mail = btn.getParent().getFirst().innerHTML; var mail = btn.getParent().getFirst().innerHTML;
@ -58,12 +69,12 @@ var LSformElement_mail = new Class({
} }
} }
}, },
onLSmailClose: function(LSmail) { onLSmailClose: function(LSmail) {
LSdebug('LSformElement_mail : close LSmail'); LSdebug('LSformElement_mail : close LSmail');
this.LSmail_open = 0; this.LSmail_open = 0;
}, },
onLSmailValid: function(LSmail) { onLSmailValid: function(LSmail) {
LSdebug('LSformElement_mail : valid LSmail'); LSdebug('LSformElement_mail : valid LSmail');
LSmail.send(); LSmail.send();

View file

@ -0,0 +1,157 @@
var LSformElement_mail_field = new Class({
initialize: function(name, input){
this.name = name;
this.input = input;
this.ul = input.getParent('ul');
this.li = input.getParent('li');
this.keyUpTimer = null;
this.lastKeyUpValue = null;
this.lastAutocompletePattern = null;
this.lastAutocompleteMails = null;
this.initialiseLSformElement_mail_field();
},
initialiseLSformElement_mail_field: function() {
this.input.addEvent('keyup',this.onKeyUp.bindWithEvent(this));
this.input.addEvent('keydown',this.onKeyDown.bindWithEvent(this));
},
onKeyDown: function(event) {
event = new Event(event);
if (event.key=='tab' && this.input.value) {
event.stop();
if (this.keyUpTimer) {
clearTimeout(this.keyUpTimer);
}
this.launchAutocomplete(this.input.value);
}
},
onKeyUp: function(event) {
this.lastKeyUpValue = this.input.value;
if (this.keyUpTimer) {
clearTimeout(this.keyUpTimer);
}
if (this.lastKeyUpValue) {
this.keyUpTimer = this.onkeyUpTimeout.delay(800, this);
}
},
onkeyUpTimeout: function() {
this.keyUpTimer = null;
if (this.lastKeyUpValue == this.input.value) {
this.launchAutocomplete(this.input.value);
}
},
launchAutocomplete: function(pattern) {
if (this.lastAutocompletePattern == pattern) {
if (!this.autocompleteIsOpen()) this.showAutocompleteMails();
return true;
}
this.input.set('disabled', 'disabled');
this.lastAutocompletePattern=pattern;
var data = {
template: 'LSformElement_mail',
action: 'autocomplete',
attribute: this.name,
objecttype: varLSform.objecttype,
idform: varLSform.idform,
pattern: pattern
};
data.imgload=varLSdefault.loadingImgDisplay(this.input);
new Request({url: 'index_ajax.php', data: data, onSuccess: this.onAutocompleteComplete.bind(this)}).send();
},
onAutocompleteComplete: function(responseText, responseXML) {
var data = JSON.decode(responseText);
this.input.erase('disabled');
if ( varLSdefault.checkAjaxReturn(data) ) {
this.lastAutocompleteMails = new Hash(data.mails);
this.showAutocompleteMails();
}
},
showAutocompleteMails: function() {
if (!this.lastAutocompleteMails) return;
if (!$type(this.autocompleteUl)) {
this.autocompleteUl = new Element('ul');
this.autocompleteUl.addClass('LSformElement_mail_autocomplete');
this.autocompleteUl.injectInside(this.li);
document.addEvent('click', this.closeAutocompleteIfOpen.bind(this));
}
this.autocompleteUl.empty();
if (this.lastAutocompleteMails) {
this.lastAutocompleteMails.each(this.addAutocompleteLi, this);
}
this.addAutocompleteNoValueLabelIfEmpty();
this.autocompleteUl.setStyle('display','block');
},
addAutocompleteLi: function(name, mail) {
var current = 0;
this.ul.getElements("input").each(function(input){
if (input.value==mail && input != this.input) {
current=1;
}
},this);
var li = new Element('li');
li.addClass('LSformElement_mail_autocomplete');
li.set('data-mail', mail);
li.set('html', name);
li.addEvent('mouseenter',this.onAutocompleteLiMouseEnter.bind(this,li));
li.addEvent('mouseleave',this.onAutocompleteLiMouseLeave.bind(this,li));
if (current) {
li.addClass('LSformElement_mail_autocomplete_current');
}
else {
li.addEvent('click',this.onAutocompleteLiClick.bind(this,li));
}
li.injectInside(this.autocompleteUl);
},
addAutocompleteNoValueLabelIfEmpty: function() {
if (this.autocompleteUl.getElement('li') == null) {
var li = new Element('li');
li.addClass('LSformElement_mail_autocomplete');
li.set('html', varLSdefault.LSjsConfig['LSformElement_mail_autocomplete_noResultLabel']);
li.injectInside(this.autocompleteUl);
}
},
onAutocompleteLiMouseEnter: function(li) {
li.addClass('LSformElement_mail_autocomplete_over');
},
onAutocompleteLiMouseLeave: function(li) {
li.removeClass('LSformElement_mail_autocomplete_over');
},
onAutocompleteLiClick: function(li) {
this.closeAutocomplete();
if (li.get('data-mail')) {
this.input.value = li.get('data-mail');
}
},
autocompleteIsOpen: function() {
return ($type(this.autocompleteUl) == 'element' && this.autocompleteUl.getStyle('display') != 'none');
},
closeAutocomplete: function() {
if (!this.autocompleteIsOpen()) return true;
this.autocompleteUl.setStyle('display', 'none');
},
closeAutocompleteIfOpen: function(event) {
event = new Event(event);
if (!this.autocompleteIsOpen())
return true;
if (event.target==this.input || event.target==this.autocompleteUl)
return true;
this.closeAutocomplete();
},
});