/* COMPOSANT */
function init_components(selector){

	//Suppression apr defaut des légendes pour les graphiques
	if(window.Chart) Chart.defaults.global.legend.display = false;

	var selected = selector ? $('[data-type]',selector) : '[data-type]';

	$(selected).each(function(i,input){
		var input = $(input);
		switch($(input).attr('data-type')){
			/**
			 * data-format : Supporte les formats dd/mm/yyyy ou yyyy/mm/dd avec séparateur "/" ou "-"
			 * data-begin : date de début de sélection par rapport à aujourd'hui(peut être en nb de jour, en objet Date ou en string de type dateFormat ou en date relative [+1m +7d]==>Début à 1 mois et 7 jours)
			 * data-end : date de début de sélection par rapport à aujourd'hui(peut être en nb de jour, en objet Date ou en string de type dateFormat ou en date relative [+1m +7d]==>Début à 1 mois et 7 jours)
			 * data-workdays : si attribut est présent, seuls les jours ouvrés sont sélectionnables
			 */
			case 'date':
				//Les séparateurs doivent être similaires entre les éléments de la chaîne
				var dateFormatRegex = /^(dd|yy)([\/|-])(mm)\2(dd|yy)$/;
				var dateFormat = input.attr('data-format') != undefined && input.attr('data-format').match(dateFormatRegex) ? input.attr('data-format').match(dateFormatRegex)[0] : "dd/mm/yy";

				var begin = input.attr('data-begin');
				begin = (begin != undefined && (begin.match(/-?\d+/) || begin.match(/^(\d{2}(?:\d{2})?)\/(\d{2})\/(\d{2}(?:\d{2})?)$/))) ? begin : null ;
				var end = input.attr('data-end');
				end = (end != undefined && (end.match(/-?\d+/) || end.match(/^(\d{2}(?:\d{2})?)[-|\/](\d{2})[-|\/](\d{2}(?:\d{2})?)$/))) ? end : null ;

				input.removeClass('hasDatepicker');
				input.date({
					placeholder: input.attr('placeholder') != undefined ? input.attr('placeholder') : "jj/mm/aaaa",
					dateFormat: dateFormat,
					beginDate: begin,
					endDate: end,
					workdays: input.attr('data-workdays') != null
				}).click(function(event) {
					$(this).select();
				});
			break;
			/**
			 * data-format : Supporte les formats dd/mm/yyyy ou yyyy/mm/dd avec séparateur "/" ou "-"
			 * data-step : L'intervalle entre 2 valeurs en minutes
			 */
			case 'hour':
				var timeFormatRegex = /^(H):(i)$/;
				var timeFormat = input.attr('data-format') != undefined && input.attr('data-format').match(timeFormatRegex) ? input.attr('data-format').match(timeFormatRegex)[0] : "H:i";
				var step = input.attr('data-step');

				input.hour({
					placeholder: input.attr('placeholder') != undefined ? input.attr('placeholder') : "hh:mm",
					timeFormat: timeFormat,
					step: is_numeric(step) && step>0 ? step : 1
				}).click(function(event){
					$(this).select();
				});
			break;

			case 'decimal':
				input.off('keydown').on('keydown',function(e){
					switch(e.key){
						case ',':
							//on interdit la saisie si la chaine contient déja un . ou une ,
							if(input.val().match(/[\.,]/i)) return false;

							//remplace les , par les . pour chiffre valide en db
							input.val(input.val()+'.');
							return false;
						break;
						case '.':
							//on interdit la saisie si la chaine contient déja un . ou une ,
							if(input.val().match(/[\.,]/i)) return false;
						break;
						case 'Backspace':
						case 'ArrowRight':
						case 'ArrowLeft':
						case 'Delete':
						case 'Insert':
						case '-':
							return true
						break;
						default:
							//autorise le coller ctrl+v
							if(e.key == 'v' && e.ctrlKey ) return true;
							//dégage les caractères différents de .,0123456789
							if(!e.key.match(/[\-0-9,\.]/i)) return false;
						break;
					}
					return true;
				});
				//supprime les caracteres pourris sur un coller
				input.off('keyup blur').on('keyup blur',function(e){
					var value = input.val().replace(/[^0-9\-,.]/ig,'');
					//on remplace les "," par les "."
					var value = value.replace(/,/ig,'.');

					//on s'assure qu'il n'existe pas plusieurs "."
					var splits = value.split('.');
					if(splits.length > 1){
						var newValue = '';
						for(var i in splits)
							newValue+= (i!=splits.length-1 ? '' : '.')+splits[i];
						value = newValue;
					}
					//On s'assure que le - est en début ou n'existe pas
					if(value.indexOf('-') !== -1 && value.indexOf('-')!==0 ) value = value.replace('-','');
					input.val(value);

				});
			break;

			case 'color':
				input.colorInput();
			break;

			case 'history':
				var data = input.data();
				if(isNaN(input.attr('data-uid'))) break;

				input.off();
				if(data.showImportant){
					if(!window.componentQueue['history']) window.componentQueue['history'] = {timeout : null,scopes :{} };
					clearTimeout(window.componentQueue['history'].timeout);

					if(!window.componentQueue['history'].scopes[data.scope]) window.componentQueue['history'].scopes[data.scope] = [];
					window.componentQueue['history'].scopes[data.scope].push(data.uid);

					window.componentQueue['history'].timeout = setTimeout(function(){
						$.action({
							action : 'core_history_search',
							scopes : window.componentQueue['history'].scopes,
							showImportant : true
						},function(r){
							var tpl = $('#history-notification-tpl').html();
							for(var k in r.rows){
								var row = r.rows[k];
								var html = Mustache.render(tpl, {number: row.number});
								inputs = $('[data-scope="'+row.scope+'"][data-uid="'+row.uid+'"][data-type="history"][data-show-important="true"]');
								inputs.addClass('position-relative').append(html);
								if(row.number == 0) inputs.find('.history-notification').addClass('hidden');

								init_components(inputs);
							}
						});
						window.componentQueue['history'] = {timeout : null,scopes :{} };
					},50);
				}

				input.click(function(event){
					var data = input.data();
					var panel = $('.history-panel');
					var uid;
					if(panel.hasClass('hidden')) panel.removeClass('hidden');

					panel.toggleClass('fold').one('webkitAnimationEnd oanimationend msAnimationEnd animationend', function(e) {
						var currentPanel = $(this);
						if(!currentPanel.hasClass('fold')) return;
			            currentPanel.addClass('hidden');
			        });
					if(panel.hasClass('fold')) return;
					if(input.attr('data-uid')) panel.attr('data-uid',input.attr('data-uid'));
					if(data.scope) panel.attr('data-scope',data.scope);
					core_history_search(function(){
						//Redimensionnement du panel historique
						setTimeout(function(){
							uid = $('.history-panel').panelResize({direction : 'left'});
						}, 100);
					});
					event.stopPropagation();

					//Fermeture du panel au click en dehors du composant
					$(window).click(function(e) {
						var target = $(e.target);
						var panel = target.closest('.history-panel');
						var handler = target.closest('.panel-resize-handler');
						var targetInPanel = panel.length || handler.length;
						//Si un commentaire était en cours d'édition, on le sauvegarde si on a cliqué en dehors du wysiwyg et uniquement s'il n'est pas vide
						if(target.hasClass('history-panel') || target.hasClass('comments') || !targetInPanel){
							$('li.comment:not(.hidden)').each(function(){
							    if($(this).find('.history-content .trumbowyg-editor-visible').length && $(this).find('.history-content .trumbowyg-editor').text().length)
							        core_history_save(this);
							});
						}
						if(targetInPanel) return;
						$('.history-panel').addClass('fold');
						$('#'+uid).remove();
					});
				});
			break;
			/**
			 * data-regex : regex de vérification (defaut : ^[0-9]{10}$, les espaces sont trimés pour vérification)
			 * data-spacing : espace automatiquement les chiffre si collés (défaut true)
			 * data-empty-field : vide le champs si invalide (défaut false)
			 * data-invalid-class : définit la classe a ajouter si invalide (défaut .invalid-value)
			 * data-blur : si défini, trigger le blur indiqué après validation du champ (le onblur classique trigger avant)
			 * data-type-only : empeche l'utilisateur de taper autre caractere que définis dans cette expression (defaut : [0-9\+\s])
			 */
			case 'phone':
				input.phone(input.data());
			break;
			/**
			 * data-delete : définit la fonction JS qui sera appelée au clic sur la croix pour supprimer l'image
			 * data-save :  définit la fonction JS qui sera appelée après le changement de l'image
			 * data-default-src : chemin vers l'image par défaut à utiliser
			 */
			case 'image':
				console.warn('[DEPRECATED] : Image doit être remplacé par le composant file');
				if(input.hasClass('hidden')) return;
				if(input.closest('.type-image-bloc').length!=0) break;
				input.attr('type', 'file');
				input.wrap( "<div class='type-image-bloc'></div>");
				var save = input.attr('data-save');
				var src = ($(input).attr('value')!='') ? $(input).attr('value') : 'img/default-image-muted.png';

				src += src.indexOf('?')!=-1 ? '&' : '?';
				src += 't='+(Math.random()*1000);
				var thumbnail = $('<img src="'+src+'" >');
				var deleteBtn = !input.attr('data-delete') ? '' : '<div class="btn btn-delete-img noPrint" onclick="'+input.attr('data-delete')+'"><i class="fas fa-times"></i></div>';
				input.before(thumbnail);
				if(thumbnail.attr('src').indexOf('default-') === -1) thumbnail.before(deleteBtn);
				input.addClass('noPrint');
				input.change(function(){
					var reader = new FileReader();
					reader.onload = function (e) {
						thumbnail.attr('src', e.target.result);
						thumbnail.before(deleteBtn);
					}
					reader.readAsDataURL(input.get(0).files[0]);

					if(save) window[save](input);
				});
			break;

			//Sélection de valeurs multiples
			case 'checkbox-list':
				var data = input.data();
				if(!data.depth) data.depth = 1;
				var liClasses = '';
				var isDropDown = data.display == 'dropdown';
				//Si défini à false => bloque le choix multi niveau
				var multiLevelSelect = data.multiLevelSelect;

				var initValues = function(){
					ul.find('li input').prop("checked",false);
					ul.find('li[data-folded="0"]').attr('data-folded','1');
					if(input.val() ){
						var checkeds = input.val().split(',')
						for(var i in checkeds){
							var checkbox = ul.find('li[data-value="'+checkeds[i]+'"] input').eq(0);
							checkbox.prop("checked",true);
							checkbox.closest('ul').parents().attr('data-folded','0');
						}
						refreshLabel();
					}
				}
				var refreshLabel = function(){
					if(!isDropDown) return;
					var checkedLabels = [];
					ul.find('>li input:checked').each(function(){
						checkedLabels.push($(this).closest('li').find('label').eq(0).text());
					});
					var button = container.find('.dropdown-toggle');
					button.html(checkedLabels.length>0 ? checkedLabels.join(',') : data.button);
					$('.checkbox-list-counter',container).text(checkedLabels.length);
				}

				var printChilds = function(childs,level){
					if(!level) level = 1;
					html = '';


					if(level==1){
						html += '<li class="user-select-none border-bottom pb-1 mb-1 checkbox-list-header"><small class="text-muted text-center"><span class="checkbox-list-counter">0</span> Sélection(s) <span class="fa-stack right mt-2 checkbox-list-check-all" title="Tout (dé)cocher"> ';
						html += '<i class="far fa-square fa-stack-2x"></i><i class="fas fa-check-double fa-stack-1x"></i></span></small></li>';
					}

					for(var k in childs){
						var child = childs[k];
						var hasChilds = child.childs && child.childs.length!=0;
						if(!data.multiLevelSelect && level == 1 && hasChilds && (child.childs.length < data.depth)) continue;

						html += '<li class="'+liClasses+' user-select-none" data-folded="1" data-value="'+child.id+'"> ';
						if(level == data.depth){
							html += '<label><input data-type="checkbox" type="checkbox"> '+child.label+'</label>';
						}else{
							if(hasChilds || multiLevelSelect){
								html += '<span class="dropdown-element">'
								html += hasChilds ? '<i class="fas fa-chevron-right fa-fw checkbox-toggle-fold"></i> <label class="label-group">' : '<label>';

								if(level == data.depth || multiLevelSelect) html += '<input data-type="checkbox" type="checkbox"> ';
								html += child.label+'</label>';

								if(hasChilds){
									html += '<span class="fa-stack checkbox-list-check-all hide" title="Tout (dé)cocher"> ';
									html += '<i class="far fa-square fa-stack-2x"></i> ';
									html += '<i class="fas fa-check-double fa-stack-1x"></i></span>';
								}
								html += '</span> ';
							}
						}
						if(hasChilds) html += '<ul>'+printChilds(child.childs,level+1)+'</ul>';
						html += '</li>';
					}
					return html;
				}

				if(!input.data("data-component")){
					container = $('<div class="'+(isDropDown?'dropdown':'')+' '+input.attr('class')+' data-type-checkbox-list"><ul class="'+(isDropDown?'dropdown-menu':'')+'"></ul></div>');
					if(isDropDown){
						data.button = !data.button ? 'Choix': data.button;
						container.append('<div class="dropdown-toggle" type="button" data-toggle="dropdown" >'+data.button+'</div>');
						liClasses += ' dropdown-item ';
					}
					input.before(container);
					input.data("data-component", container);
					input.addClass('component-raw-value'); // utilisé par les filtres / etc.. pour dissocier la valeur brute du composant

					if(input.attr("required")) container.attr("required", "");
					if(input.attr("disabled")) container.attr("disabled", "");
					if(input.attr("readonly")) container.attr("readonly", "");

					var ul = container.find('>ul');
					ul.on('click','>li',function(e){
						e.stopPropagation();
					}).on('click','>li input',function(e){
						e.stopPropagation();
						var checked = [];
						ul.find('>li input:checked').each(function(){
							checked.push($(this).closest('li').attr('data-value'));
						});
						input.val(checked.join(','));
						refreshLabel();
						input.trigger('change');
					});
				}else{
					container = input.data("data-component");
					var ul = container.find('>ul');
				}

				var html = '';
				data.values = input.attr('data-values');
				if(data.values){
					data.values = JSON.parse(input.attr('data-values'));
					for(var k in data.values)
						if(typeof(data.values[k]) === "string") data.values[k] = {id: k, label: data.values[k]};

					html += printChilds(data.values);
				}
				ul.html(html);
				ul.find('[data-type="checkbox"]').removeClass('component-raw-value');

				var initCheckbox = function(ul, callback){
					init_components(ul);
					ul.find('[data-type="checkbox"]').removeClass('component-raw-value');

					ul.find('.checkbox-toggle-fold').click(function(){
						var li = $(this).closest('li');
						li.attr('data-folded',li.attr('data-folded')=='1'?0:1);
					});

					$('.dropdown-element').mouseover(function(){
						$(this).find('.checkbox-list-check-all').eq(0).removeClass('hide');
					});
					$('.dropdown-element').mouseout(function(){
						$(this).find('.checkbox-list-check-all').eq(0).addClass('hide');
					});

					container.find('.checkbox-list-check-all').click(function(event){

						event.stopPropagation();
						var element = $(this);

						var parentContainer = element.parent().parent();

						//cas du checkbox-all racine
						if(parentContainer.hasClass('checkbox-list-header'))
							parentContainer = parentContainer.parent();

						parentContainer.find('input[type="checkbox"]').click();

						var li = element.closest('li');
						li.attr('data-folded',0);
						if(multiLevelSelect) li.find('li').attr('data-folded',0);
					});
					if(callback!=null) callback();
				}

				if(data.slug){
					if(!data.depth) data.depth = 1;
					$.action({
						action: 'core_dictionary_component_load',
						slug: data.slug,
						depth: data.depth,
						multiLevelSelect: data.multiLevelSelect,
					},function(response){
						html = '';
						if(response.content.childs && response.content.childs.length!=0){
							html += printChilds(response.content.childs);
						}else{
							html += '<li class="text-muted text-center"><small><i class="fas fa-exclamation-circle"></i> Liste vide ou introuvable</small></li>';
						}


						ul.append(html);
						initCheckbox(ul, initValues);
					});
				}else{
					initCheckbox(ul, initValues);
				}
			break;

			//Selection de tag
			case 'tag':
				var container;
				var picker;
				var pickerLi;

				if(!input.data("data-component")){
					var placeholder = input.attr("placeholder") != undefined ? input.attr("placeholder") : "";
					container = $('<div class="'+input.attr('class')+' data-type-tag"><ul><li class="tag-picker-li"><input placeholder="'+placeholder+'" type="text"></li></ul></div>');
					input.before(container);
					input.addClass('component-raw-value'); // utilisé par les filtres / etc.. pour dissocier la valeur brute du composant
					input.data("data-component", container);
					if(input.attr("required")) container.attr("required","");
					if(input.attr("disabled")) container.attr("disabled","");
					if(input.attr("readonly")) container.attr("readonly","");
				} else {
					container = input.data("data-component");
				}
				picker =  container.find('input:eq(0)');
				pickerLi =  container.find('ul li.tag-picker-li');
				if(container.attr('disabled') || container.attr('readonly'))
					pickerLi.find('input').attr('readonly','');
				container.find('.tag-picker-tag').remove();
				pickerLi.removeClass('hidden');

				var methods = {
					//Récuperation des valeurs sélectionnées (objet et id) en fonction des tags visuels présents
					values : function(container){
						var tags =  container.find('ul .tag-picker-tag');
						var values = {object:[],id:[]};
						tags.each(function(i,element){
							if($(element).attr('data-value') == '') return;
							var data = $(element).data();
							values['object'].push(data);
							values['id'].push(data.value);
						});
						return values;
					},
					remove : function(tag){
						tag.remove();
						var values = methods.values(container);

						var plainValue = [];
						for(var k in values.object){
							plainValue.push(values.object[k].value);
						}

						input.val(plainValue.length!=0? plainValue.join(',') :'');
						input.data('values',values['object']);
						if((values['id'].length-1) ==1 && input.attr('data-multiple') == null){
							pickerLi.addClass('hidden');
						}else{
							pickerLi.removeClass('hidden');
						}

						//on trigger le change
						input.trigger('change');
					},
					//Ajout d'un tag visuel et mise à jour de l'input brut en fonction de l'objet  fournis
					add : function(container,tag){
						pickerLi =  container.find('ul li.tag-picker-li');
						if( container.find('li[data-id="'+tag.id+'"]').length>0) return;
						var closeBtn = input.is('[readonly]') || input.is('[disabled]') ? '' : '<i class="fa fa-times btn-remove"></i>';
						var tag = $('<li class="tag-picker-tag" data-value="'+tag.value+'"><div>'+tag.label+closeBtn+'</div></li>');
						pickerLi.before(tag);
						var values = methods.values(container);
						input.val(values['id'].join(','));
						input.data('values',values['object']);
						if((values['id'].length) == 1 && input.attr('data-multiple') == null){
							pickerLi.addClass('hidden');
						}else{
							pickerLi.removeClass('hidden');
						}
						tag.find('.btn-remove').click(function(){
							if(input.is('[readonly]') || input.is('[disabled]')) return;
							methods.remove($(this).closest('.tag-picker-tag'));
						});
					}
				}
				//Gestion des champs déja remplis au chargement de la page
				if(input.val() !=''){
					var id = input.val();
					//picker.addClass('hidden');
					var tags = id.split(',');


					if(input.data('dictionarySlug') && input.data("data-autocomplete-value") != 'label'){
						$.action({
							action : 'core_tag_list_by_id',
							id : id
						},function(response){
							for(var key in response.tags){
								if(response.tags[key] == '') continue;
								methods.add( container,{label:response.tags[key].label,value:response.tags[key].id});
							}
						});

					}else{
						for(var key in tags){
							if(tags[key] == '') continue;
							methods.add( container,{label:tags[key],value:tags[key]});
						}
					}


				}

				picker.off('blur').blur(function(e){
					if(window.tagBlur) clearTimeout(window.tagBlur);
					window.tagBlur = setTimeout(function(){

						var value = picker.val();
						if(value.trim() == '') return;

						methods.add(container,{label:value,value:value});
						picker.val('');
						$('.typeahead.dropdown-menu').hide();
					},500);
				});

				if(!input.data('autocomplete') && input.data('dictionarySlug'))
					input.data('autocomplete','core_tag_list_autocomplete');

				if(input.data('autocomplete')){
					var dictionarySlug = input.data('dictionarySlug');
					$(picker).autocomplete({
						action: input.data('autocomplete'),
						selectEqual : false, //evite le select auto du tag test quand on veux taper test2
						suggest : true,
						dynamicData : function(){
							return dictionarySlug ? { parent : dictionarySlug} : {}
						},
						onClick : function(selected,element){

							if(window.tagBlur) clearTimeout(window.tagBlur);

							methods.add(container,{label:selected.name,value: (input.data("data-autocomplete-value") == 'label' ? selected.name : selected.id) });
							//on vide l'input de saisie
							picker.val('');
							//on trigger le change
							input.trigger('change');
						},
					});
				}

				picker.off('keydown').keydown(function(e){
					var validationChars = ['Enter',' ',',','Tab'];
					if(e.key == 'Backspace' && picker.val() == ''){
						methods.remove($('.tag-picker-tag:last',container));
						return;
					}
					//si appui sur Entrée, virgule ou espace ou tab on valide le tag
					if( validationChars.indexOf(e.key) === -1 || input.is('[readonly]') || input.is('[disabled]') ) return;
					var value = picker.val();
					if(value.trim() == '') return;
					//on cache l'automcomplete si present
					$('.typeahead.dropdown-menu').hide();
					//on ajoute le tag visuel
					methods.add(container,{label:value,value:value});
					//on vide l'input de saisie
					picker.val('');
					//on trigger le change
					input.trigger('change');
					return false;
				});

			break;


			//composant permettant les listes sous
			//form de dropdown avec icones et couleurs
			case 'dropdown-select':
				var container;
				if(!input.data("data-component")){
					container = $('<div class="'+input.attr('class')+' data-type-dropdown-select"> \
						<button '+(input.attr('title')!=null?'title="'+input.attr('title')+'"':'')+' type="button" class="btn btn-small  dropdown-toggle-button dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></button> \
						<div class="dropdown-menu w-100 p-0 text-center"></div> \
						</div>');
					input.addClass('component-raw-value'); // utilisé par les filtres / etc.. pour dissocier la valeur brute du composant
					input.before(container);
					input.data("data-component", container);
					if(input.attr("required")) container.attr("required","");
					var button = container.find('.dropdown-toggle-button');
				} else {
					container = input.data("data-component");
					var button = container.find('.dropdown-toggle-button');
				}

				container.find('.dropdown-menu').html('');
				var html = '';
				$.each(input.get(0).options, function(i,element){
					var option = $(element);
					var classes = option.attr('class') ? option.attr('class') : '';
					var icon = option.attr("data-icon") ? '<i class="'+option.attr("data-icon")+'"></i> ': '';
					var title = option.attr("data-title") ? 'title="'+option.attr("data-title")+'"': '';
					var backColor = option.get(0).style.getPropertyValue('background-color') ? option.get(0).style.getPropertyValue('background-color') : '#ffffff';
					var fontColor =  color_light(backColor) < 50 ? '#ffffff' : '#333333' ;

					html += '<a class="dropdown-item pointer '+classes+'" '+title+' data-value="'+option.val()+'" style="background-color:'+backColor+';color:'+fontColor+'">'+icon+option.html()+'</a>';
				});
				container.find('.dropdown-menu').append(html);

				var changeMenu = function(container,menu){
					$('.dropdown-menu a',container).removeClass('active');
					var item = $('.dropdown-menu a[data-value="'+menu+'"]', container);
					var backColor = item.css('background-color') ? item.css('background-color') : '#ffffff' ;
					var fontColor =  color_light(backColor) < 50 ? '#ffffff' : '#333333' ;
					container.find('.dropdown-toggle-button').html(item.html()).css({
						backgroundColor:backColor,
						color:fontColor
					});
					item.addClass('active');
				};

				$('.dropdown-menu a',container).click(function(){
					if(input.attr("readonly") == 'readonly') return;
					var item = $(this);
					changeMenu(container,$(this).attr('data-value'));
					input.val(item.attr('data-value')).trigger('change');
				});

				if(input[0].hasAttribute('data-no-toggle')) button.removeClass('dropdown-toggle');

				var state = input.attr('data-value') != null ? input.attr('data-value') : input.val();
				if(state == '') return;
				var item = $('.dropdown-menu a[data-value="'+state+'"]', container);

				//Utilisation background-color pour fonctionner sous FF

				var backColor = item.get(0) && item.get(0).style.getPropertyValue('background-color') ? item.get(0).style.getPropertyValue('background-color') : '#ffffff' ;
				var fontColor = color_light(backColor) < 50 ? '#ffffff' : '#333333' ;
				button.html(item.html()).css({
					backgroundColor:backColor,
					color:fontColor
				});

				item.addClass('active');
				input.val(item.attr('data-value'));

				input.change(function(){
					changeMenu(container,input.val());
				});

			break;

			case 'firm':
				//autocomplete classique
				input.component_autocomplete('firm',{
					edit : 'core_firm_by_uid',
					autocomplete : 'core_firm_autocomplete'
				});
			break;

			case 'map':

				//on recréé le container a chaque fois car leaflet
				//ne sais pas refresh sans tout recharger (a verifier)

				var container = $('<div></div>');
				container.attr('class',input.attr('class'));
				input.data('container',container);
				input.before(container);


				if(container.height() <1){
					container.css('height',(input.height() <1 ? '300px' : input.height()+'px'));
					container.css('width',(input.width() <1 ? '300px' : input.width()+'px'));
				}


				var data = input.data();
				var json = input.get(0).innerHtml;


				if(!json) json = '[]';

				var points = JSON.parse(json);

				var existingMap = L.DomUtil.get(container.get(0));
				if(existingMap != null)
					existingMap._leaflet_id = null;

				var map = L.map(container.get(0));
				map.invalidateSize();



				//si une latitude/longitude pour la vue global est définie on l'utilise
				if(data.latitude && data.longitude){
					if(!data.zoom) data.zoom = 13;
					map.setView([data.longitude, data.latitude], data.zoom);
				//Sinon on se base sur le groupe de marqueurs proposés pour définie une vue englobante
				}else{
					var pointArray = [];
					if(points.length!=0){
						for(var k in points){
							var point = points[k];
							if(point.longitude && point.latitude)pointArray.push([point.latitude,point.longitude]);
						}
						var markerGroup = new L.LatLngBounds(pointArray);

						map.fitBounds(markerGroup);


					}
				}
				if(data.zoom) map.setZoom(data.zoom);

				L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
					attribution: ''
				}).addTo(map);
			/*	var defaultIcon = L.icon({
				    iconUrl: 'img/leaflet/marker-icon.png',
				    shadowUrl: 'img/leaflet/marker-shadow.png',
				    iconSize:     [25, 41], // size of the icon
				    shadowSize:   [41, 41], // size of the shadow
				    iconAnchor:   [0, 0], // point of the icon which will correspond to marker's location
				    shadowAnchor: [0, 0],  // the same for the shadow
				    popupAnchor:  [10, 0] // point from which the popup should open relative to the iconAnchor
				});*/


				map.on('click', function(args){
					if(data.click && window[data.click]) window[data.click]({
						longitude : args.latlng.lng,
						latitude : args.latlng.lat,
						layerX : args.layerPoint.x,
						layerY : args.layerPoint.y,
						x : args.containerPoint.x,
						y : args.containerPoint.y,
						event : args.originalEvent,
						target : args.target,
						sourceTarget : args.sourceTarget

					});
				});

				var mapPoints = {};

				var markers = L.markerClusterGroup();

				for(var k in points){
					var point = points[k];
					if(!point.type) point.type = 'marker';
					switch(point.type){
						case 'marker':
							var currentPoint = L.marker([point.latitude,point.longitude]/*, {icon: defaultIcon}*/);
						break;
						case 'circle':
							if(!point.borderColor) point.borderColor = '#38f';
							if(!point.backgroundColor) point.backgroundColor = '#38f';
							if(!point.opacity) point.opacity = 0.5;
							if(!point.radius) point.radius = 500;

							var currentPoint = L.circle([point.latitude,point.longitude], {
											    color: point.borderColor,
											    fillColor: point.backgroundColor,
											    fillOpacity: point.opacity,
											    radius: point.radius
							});
						break;
						case 'polygon':
							if(!point.borderColor) point.borderColor = '#38f';
							if(!point.backgroundColor) point.backgroundColor = '#38f';
							if(!point.opacity) point.opacity = 0.5;
							if(!point.radius) point.radius = 500;
							var currentPoint = L.polygon(point.points, {
											    color: point.borderColor,
											    fillColor: point.backgroundColor,
											    fillOpacity: point.opacity,
											    radius: point.radius
							});
						break;
					}
					markers.addLayer(currentPoint);
					if(point.label) currentPoint.bindPopup(point.label);
					currentPoint.id = k;

					if(point.id) currentPoint.id = point.id ;

					mapPoints[currentPoint.id] = currentPoint;
					//currentPoint.addTo(map);
				}
				map.addLayer(markers);
				input.data('points',mapPoints);
			break;

			case 'contact':
				//autocomplete classique
				input.component_autocomplete('contact',{
					edit : 'core_contact_by_uid',
					autocomplete : 'core_contact_autocomplete',
					skin  :  function(item){
						var html = '';
						var re = new RegExp(input.val(),"gi");
						name = item.label.replace(re, function (x) {
							return '<strong>'+x+'</strong>';
						});
						html += '<div class="'+slug+'-infos"><span>'+name+'</span>';
						if(item.job) html += '<div class="text-muted '+slug+'-job"><span>'+item.job+'</span>';
						html += '<div class="clear"></div>';
						return html;
					},
					data : function(){
						return {scope : input.attr('data-scope'),uid : input.attr('data-uid')? input.attr('data-uid').split(','):[] };
					}
				});
			break;

			case 'user':
				var userContainer;
				var userPicker;
				var pickerLi;

				if(!input.data("data-component")){
					userContainer = $('<div class="'+input.attr('class')+' data-type-user"><ul><li class="unfold"><i class="fas fa-ellipsis-h" title="Agrandir le champ" data-tooltip></i></li><li class="user-picker-li"><input type="text" '+(input.attr('placeholder')!=null?'placeholder="'+input.attr('placeholder')+'"':'')+'></li></ul></div>');

					input.before(userContainer);
					input.addClass('component-raw-value'); // utilisé par les filtres / etc.. pour dissocier la valeur brute du composant
					input.data("data-component", userContainer);
					if(input.attr("required")) userContainer.attr("required","");
					if(input.attr("disabled")) userContainer.attr("disabled","");
					if(input.attr("readonly")) userContainer.attr("readonly","");
				} else {
					userContainer = input.data("data-component");
				}
				userPicker = userContainer.find('input:eq(0)');
				pickerLi = userContainer.find('ul li.user-picker-li');
				if(userContainer.attr('disabled') || userContainer.attr('readonly'))
					pickerLi.find('input').attr('readonly','');
				userContainer.find('.user-picker-tag').remove();
				pickerLi.removeClass('hidden');
				var pickerFunctions = {
					//Récuperation des valeurs sélectionnées (objet et uid) en fonction des tags visuels présents
					getValues : function(userContainer){
						var tags = userContainer.find('ul .user-picker-tag');
						var values = {object:[],uid:[]};
						tags.each(function(i,element){
							if($(element).attr('data-uid') == '') return;
							var object = $(element).data();
							values['object'].push(object);
							values['uid'].push(object.uid);
						});
						return values;
					},
					checkComponentOverflow  : function(container){
						var overflow = container.get(0).scrollHeight > container.outerHeight();
						overflow ? container.addClass('overflowing') : container.removeClass('overflowing');
					},
					//Ajout d'un tag visuel et mise à jour de l'input brut en fonction de l'objet user fournis
					addTag : function(userContainer,user,input){
						pickerLi = userContainer.find('ul li.user-picker-li');
						if(userContainer.find('li[data-uid="'+user.uid+'"]').length>0) return;
						var closeBtn = userContainer.attr('disabled') || userContainer.attr('readonly') ? '' : '<i class="fa fa-times"></i>';
						var tag = $('<li class="user-picker-tag" data-entity="'+user.type+'" data-fullname="'+user.fullname+'" data-uid="'+user.uid+'"><div title="'+(user.type=='rank'?'Rang':'Utilisateur')+'">'+user.name+closeBtn+'</div></li>');
						pickerLi.before(tag);
						var values = pickerFunctions.getValues(userContainer);
						input.val(values['uid'].join(','));
						input.data('values',values['object']);

						if((values['uid'].length) == 1 && input.attr('data-multiple') == null){
							pickerLi.addClass('hidden');
						}else{
							pickerLi.removeClass('hidden');
						}

						pickerFunctions.checkComponentOverflow(userContainer);

						tag.find('i').click(function(){
							if(userContainer.attr('disabled') || userContainer.attr('readonly')) return;

							$(this).closest('.user-picker-tag').remove();
							var values = pickerFunctions.getValues(userContainer);
							input.val(values['uid'].join(','));
							input.data('values',values['object']);
							pickerLi = userContainer.find('ul li.user-picker-li');
							if((values['uid'].length-1) ==1 && input.attr('data-multiple') == null){
								pickerLi.addClass('hidden');
							}else{
								pickerLi.removeClass('hidden');
							}
							input.trigger('change');
							pickerFunctions.checkComponentOverflow(userContainer);
						});
						if(userContainer.find('ul li.user-picker-tag'))
							userContainer.find('ul li.user-picker-li input').removeAttr('placeholder');
					}
				}

				//Gestion des champs déja remplis au chargement de la page
				if(input.val() !=''){
					var uid = input.val();

					if(!window.componentQueue.user) window.componentQueue.user = {timeout : null,components : [],uids  :{} };
					clearTimeout(window.componentQueue.user.timeout);

					var uids = uid.split(',');
					for(var k in uids)
						window.componentQueue.user.uids[uids[k]] = 1;

					window.componentQueue.user.components.push({
						input : input,
						picker : userPicker,
						container : userContainer,
						values : uids
					});

					userContainer.find('ul').append('<li class="user-picker-loader"><i class="fas fa-spinner fa-pulse"></i> Chargement</li>');
					userPicker.addClass('hidden');
					window.componentQueue.user.timeout = setTimeout(function(){
						$.action({
							action : 'core_user_by_uid',
							uids : Object.keys(window.componentQueue.user.uids)
						},function(r){
							for(var key in window.componentQueue.user.components){
								var component = window.componentQueue.user.components[key];
								component.container.find('.user-picker-loader').remove();
								component.picker.removeClass('hidden');
								input = window.componentQueue.user.components[key].input;
								for(var i in component.values){
									var value = component.values[i];
									if(!r.users[value]) continue;
									pickerFunctions.addTag(component.container,r.users[value],input);
								}
								delete window.componentQueue.user.components[key];
							}
						});
					},50);
				}
				userContainer.find('.unfold').click(function(e){
					e.stopPropagation();
					var position = userContainer.position();
					var width = userContainer.outerWidth();
					userContainer
					.addClass('unfolded')
					.css({
						top: position.top+'px',
						width: width+'px',
						left: position.left+'px'
					});

					$(document).mousedown(function(e){
						if($(e.target).closest('div.data-type-user').length || !userContainer.hasClass('unfolded')) return;
						window.userPickerTimeout = userContainer.removeClass('unfolded')
							.css({
								top: 'inherit',
								width: 'inherit',
								left: 'inherit'
							});
						pickerFunctions.checkComponentOverflow(userContainer);
					});
				});
				//Sélectionne l'input d'auto-completion ou que l'on clique dans le composant
				userContainer.find('ul').click(function(e){
					if(input.attr("readonly") == "readonly") return;
					userPicker.focus();
					e.stopPropagation();
				});
				//Selectionne l'item dropdown actif lors de l'appui sur entrée
				userPicker.keyup(function(e){
					if(e.keyCode!=13 || input.is('[readonly]') || input.is('[disabled]')) return;
					if(input.attr("readonly") == "readonly") return;
					var active = $('.user-picker-li .dropdown-menu .active');
					if(active.length==0) return;
					active.trigger('click').trigger('change');
				});

				var types = ['user'];
				if(input.data('types') && input.data('types')!='')
					types = input.data('types').split(',');
				//aucompletion sur le nom des users / rangs
				userPicker.autocomplete({
					action : 'core_user_autocomplete',
					data : {
						types : types,
						scope : input.attr('data-scope')
					},
					skin : function(item){
						var html = '';
						var re = new RegExp(escape_regex_chars(userPicker.val()),"gi");

						name = item.name.replace(re, function (x) {
							return '<strong>'+x+'</strong>';
						});

						if(item.type=='user'){
							if(item.avatar) html += '<div class="user-logo"><img src="'+item.avatar+'" class="avatar-mini avatar-rounded"></div>';
							html += '<div class="user-infos"><span>'+name+'</span> <small class="text-muted">- Utilisateur (@'+item.id+')</small>';
							html += item.function ? '<br/><small>'+item.function+'</small></div>' : '</div>';
							html += '<div class="clear"></div>';
						}else{
							html += '<div class="rank-logo"><i class="far fa-address-card"></i></div>';
							html += '<div class="rank-infos"><span>'+name+'</span> <small class="text-muted">- Rang</small>';
							html += item.description ? '<br/><small>'+item.description+'</small></div>' : '</div>';
							html += '<div class="clear"></div>';
						}

						return html;
					},
					highlight : function(item){
						return item;
					},
					onClick : function(selected,element){
						clearTimeout(window.userPickerTimeout);

						userPicker.val('');
						pickerFunctions.addTag(userContainer,selected,input);
						input.trigger('click').trigger('change');
					},
					onBlur : function(selected){
						if(input.attr('data-force')!='false' && input.val() == '') userPicker.val('');
					}
				});
			break;

			/**
			 * data-labels  : tableau des libellés entre double quotes eg : ["Libellé 1","Libellé 2"]
			 * data-values	: taleau des valeurs eg : [12,13]
			 * data-colors	: taleau des couleurs entre double quotes eg : ["#cecece","#222222"]
			 */
			case 'doughnut':
				var data = input.data();

				if(data.height) $(input).attr('height',data.height);

				var myChart = new Chart(input.get(0).getContext('2d'), {
					type: 'doughnut',
					data: {
						labels: data.labels,
						datasets: [{
							data: data.values,
							backgroundColor: data.colors
						}]
					},
					options: {
						cutoutPercentage:80,
						legend: {
							display: (data.legend && data.legend===true)
						}
					}
				});
			break;
			/**
			 * data-labels  : tableau des libellés entre double quotes eg : ["Libellé1","Libellé 2"]
			 * data-values	: taleau des valeurs eg : [12,13]
			 * data-colors	: taleau des couleurs entre double quotes eg : ["#cecece","#222222"]
			 */
			case 'bar':
				var data = input.data();
				var myChart = new Chart(input.get(0).getContext('2d'), {
					type: 'bar',
					data: {
						labels: data.labels,
						datasets: [{
							label : input.html(),
							data: data.values,
							backgroundColor: data.colors
						}]
					},
					options: {
						scales: {
							xAxes: [{
								ticks: {
									beginAtZero:true,
									autoSkip:false,
									maxRotation:90,
									minRotation:80
								}
							}],
							yAxes: [{
								ticks: {
									beginAtZero:true,
									autoSkip:false
								}
							}]
						},
						legend: {
							display: (data.legend && data.legend==true)
						}
					}
				});
			break;

			/**
			 * data-labels  : tableau des 2 libellés courant et max entre double quotes eg : ["Atteint","A Atteindre"]
			 * data-values	: taleau des valeurs courant et max : [50,100] (donne une jauge allant de 0 à 100 remplie a 50%)
			 * data-colors	: taleau des 2 couleurs "remplis" et "non remplis" entre double quotes eg : ["#cecece","#222222"] (optionnel)
			 * data-unity	: unité à affher a coté des libellé min, max et courant
			 */
			case 'gauge':
				var data = input.data();


				Chart.pluginService.register({
			      beforeDraw: function(chart) {

			      		// Get ctx from string
				        var ctx = chart.chart.ctx;

			      		if(chart.config.options.elements.center){

				          // Get options from the center object in options
				          var centerConfig = chart.config.options.elements.center;
				          var sidePaddingCalculated = (centerConfig.sidePadding / 100) * (chart.innerRadius * 2);

				          // Get the width of the string and also the width of the element minus 10 to give it 5px side padding
				          var stringWidth = ctx.measureText(centerConfig.text).width;
				          var elementWidth = (chart.innerRadius * 2) - sidePaddingCalculated;
				          var centerWidth = stringWidth;
				          // Find out how much the font can grow in width.
				          var widthRatio = elementWidth / stringWidth;
				          var newFontSize = Math.floor(30 * widthRatio);
				          var elementHeight = (chart.innerRadius * 2);

				          // Pick a new font size so it will not be larger than the height of label.
				          var fontSizeToUse = Math.min(newFontSize, elementHeight, centerConfig.maxFontSize);

				          // Set font settings to draw it correctly.
				          ctx.textAlign = 'center';
				          ctx.textBaseline = 'middle';
				          var centerX = ((chart.chartArea.left + chart.chartArea.right) / 2);
				          var centerY = ((chart.chartArea.top + chart.chartArea.bottom) / 2) + 20;
				          ctx.font = "1.7em Arial";
				          ctx.fillStyle = centerConfig.color;
				          ctx.fillText(centerConfig.text, centerX, centerY);
			          }

			      	 if(chart.config.options.elements.minimum){

				          // Get options from the center object in options
				          var minimumConfig = chart.config.options.elements.minimum;
				          var sidePaddingCalculated = (minimumConfig.sidePadding / 100) * (chart.innerRadius * 2);

				          // Get the width of the string and also the width of the element minus 10 to give it 5px side padding
				          var stringWidth = ctx.measureText(minimumConfig.text).width;
				          var elementWidth = (chart.innerRadius * 2) - sidePaddingCalculated;

				          // Find out how much the font can grow in width.
				          var widthRatio = elementWidth / stringWidth;
				          var newFontSize = Math.floor(30 * widthRatio);
				          var elementHeight = (chart.innerRadius * 2);

				          // Pick a new font size so it will not be larger than the height of label.
				          var fontSizeToUse = Math.min(newFontSize, elementHeight, minimumConfig.maxFontSize);
				          // Set font settings to draw it correctly.
				          ctx.textAlign = 'center';
				          ctx.textBaseline = 'center';
				          var minimumX = chart.radiusLength +20;
				          var minimumY = chart.height - chart.offsetY - 8;
				          ctx.font = "1em Arial";
				          ctx.fillStyle = minimumConfig.color;
				          ctx.fillText(minimumConfig.text, minimumX, minimumY);
			          }

			          if(chart.config.options.elements.maximum){

				          // Get options from the center object in options
				          var maximumConfig = chart.config.options.elements.maximum;
				          var sidePaddingCalculated = (maximumConfig.sidePadding / 100) * (chart.innerRadius * 2);

				          // Get the width of the string and also the width of the element minus 10 to give it 5px side padding
				          var stringWidth = ctx.measureText(maximumConfig.text).width;
				          var elementWidth = (chart.innerRadius * 2) - sidePaddingCalculated;

				          // Find out how much the font can grow in width.
				          var widthRatio = elementWidth / stringWidth;
				          var newFontSize = Math.floor(30 * widthRatio);
				          var elementHeight = (chart.innerRadius * 2);

				          // Pick a new font size so it will not be larger than the height of label.
				          var fontSizeToUse = Math.min(newFontSize, elementHeight, maximumConfig.maxFontSize);
				          // Set font settings to draw it correctly.
				          ctx.textAlign = 'left';

				          ctx.textBaseline = 'center';
				          var maximumX = chart.width - chart.radiusLength - 15 - stringWidth;
				          var maximumY = chart.height - chart.offsetY - 8;
				          ctx.font = "1em Arial";
				          ctx.fillStyle = maximumConfig.color;
				          ctx.fillText(maximumConfig.text, maximumX, maximumY);
			          }
			      }
			    });


				if(data.height) $(input).attr('height',data.height);

				if (!data.colors){
					var doneColor = '#e74c3c';
					var percentage = data.values[0] * 100 / data.values[1];

					percentage = percentage > 100 ? 100 : percentage;

					//Calcul de la couleur en fonction du pourcentage de progression
					if(percentage<=20) doneColor = '#e74c3c';
					if(percentage>20 && percentage<=40) doneColor = '#e67e22';
					if(percentage>40 && percentage<=60) doneColor = '#2980b9';
					if(percentage>60 && percentage<=80) doneColor = '#16a085';
					if(percentage>80) doneColor = '#27ae60';

					//[couleur du fait,couleur du restant
					data.colors = [doneColor,'#bdc3c7'];
				}

				var myChart = new Chart(input.get(0).getContext('2d'), {
					type: 'doughnut',
					data: {
						labels: [data.labels[0],data.labels[1]],
						datasets: [{
							data: [percentage,100-percentage],
							backgroundColor: data.colors
						}],
						realDatasets : [data.values[0],data.values[1]]
					},
					options: {
						tooltips : {
							callbacks : {
								title: function(tooltipItem, data) {
						          return data['labels'][tooltipItem[0]['index']];
						        },
						        label: function(tooltipItem, data) {
						          return data['realDatasets'][tooltipItem['index']];
						        },
						        afterLabel: function(tooltipItem, data) {
						          return '('+data['datasets'][0]['data'][tooltipItem['index']]+'%)';

						        }
							}
						},
						elements: {
					      minimum: {
					        text: '0'+data.unity,
					        color: '#333333', // Default is #000000
					        sidePadding: 20, // Default is 20 (as a percentage)
					        minFontSize: 12, // Default is 20 (in px), set to false and text will not wrap.
					        lineHeight: 12 // Default is 25 (in px), used for when text wraps
					      },
					      center: {
					        text: data.values[0]+data.unity,
					        color: '#333333', // Default is #000000
					        sidePadding: 20, // Default is 20 (as a percentage)
					        minFontSize: 32, // Default is 20 (in px), set to false and text will not wrap.
					        lineHeight: 12 // Default is 25 (in px), used for when text wraps
					      },
					      maximum: {
					        text: data.values[1]+data.unity,
					        color: '#333333', // Default is #000000
					        sidePadding: 20, // Default is 20 (as a percentage)
					        minFontSize: 12, // Default is 20 (in px), set to false and text will not wrap.
					        lineHeight: 12 // Default is 25 (in px), used for when text wraps
					      }
					    },
						circumference: Math.PI ,
						rotation: -Math.PI ,
						cutoutPercentage:75,
						legend: {
							display: (data.legend && data.legend===true)
						}
					}
				});


			break;

			/**
			 * data-labels  : tableau des libellés entre double quotes eg : ["Libellé1","Libellé 2"]
			 * data-values	: taleau des valeurs eg : [12,13]
			 * data-color	: couleurs  eg : #cecece
			 */
			case 'line':
				var data = input.data();
				var myChart = new Chart(input.get(0).getContext('2d'), {
					type: 'line',

					data: {

						labels: data.labels,
						datasets: [{
							label : input.html(),
							data: data.values,
							borderColor: [data.color]
						}]
					},
					options: {

						scales: {
							xAxes: [{
								ticks: {
									beginAtZero:true,
									autoSkip:false,
									maxRotation:90,
									minRotation:80
								}
							}],
							yAxes: [{
								ticks: {
									beginAtZero:true,
									autoSkip:false
								}
							}]
						}
					}
				});
		 	break;

		 	/**
			 * data-table  : tableau dynamique js tri, recherche, pagination...
			 * data-pageLength : nombre de résultats à afficher
			 */
			case 'data-table':
				var data = input.data();
				var options = {
					pageLength: input.attr('data-pageLength') != null ? input.attr('data-pageLength') : 20,
					destroy: true,
					pagingType: "simple_numbers",
					fixedHeader: true,
					lengthMenu: [ [10, 20, 40, -1], [10, 20, 40, "Tous"] ],
			        language: {
			            url: "plugin/statistic/js/dataTables.fr.translation.json"
			        },
			        order: [], // Disable on load sort
			        ordering: 'true' // Manage orderable options
			    };

			   options = $.extend(options,data);
				input.DataTable(options);
		 	break;
			/**
			 * data-filter-country		: filtre par pays à rechercher séparés par virgule (format ISO 3 numériques, ex : FRA)
			 * data-filter-items		: nb de résultats à retourner
			 * data-filter-language		: langue dans laquelle retourner les résultats
			 *
			 * data-select-callback		: fonction de callback appelée lors de la sélection d'un élément
			 *
			 * data-filter-geocode		: retourne les infos complémentaires de l'élément sélectionné (latitude, longitude,etc...)
			 * data-geocode-callback	: fonction de callback appelée pour traiter les infos retournées via geocode
			 */
			case 'location':
				input.location({
					items: input.attr('data-filter-items'),
					language: input.attr('data-filter-language'),
					country: input.attr('data-filter-country'),
					geocode: input.attr('data-filter-geocode') != null ? true : false,
					select: function(location){
						if(input.attr('data-select-callback')!=null && input.attr('data-select-callback').length) {
							var select = input.attr('data-select-callback').replace(/[^a-z0-9]/i,'_');
							if(window[select] !=null) window[select](location);
						}
					},
					geocode: function(location){
						$.action({
							action: 'core_location_detail_search',
							locationId: location.locationId
						}, function(r){
							if(input.attr('data-geocode-callback')!=null && input.attr('data-geocode-callback').length) {
								var geocode = input.attr('data-geocode-callback').replace(/[^a-z0-9]/i,'_');
								if(window[geocode] !=null) window[geocode](r);
							}
						});
					}
				});

				var data = input.data();
				input.removeData();
				input.data({
					type : data.type,
					country : data.country,
					zip : data.zip,
					city : data.city,
					complement : data.complement,
					street : data.street
				});
			break;

			/**
			 * data-source			: tableau de clés / valeurs au format json base 64 encodé
			 * data-value 			: la valeur de l'entité à récup en base (clé de l'item)
			 */
			/*case 'list':
				var source = input.attr('data-source');
				if(source != '' && source != null && btoa(atob(source)) == source){
					input.html('');
					input.append('<option value=""> - </option>');
					var items = JSON.parse(atob(source));
					//boucle sur les entrées possibles de sources de données
					for(const [key, source] of Object.entries(items))
						//boucle sur les couples clés valeurs de la source de données
						for(const [index, value] of Object.entries(source))
							input.append('<option value="'+index+'" '+(index == input.attr('data-value') ? "selected" : '')+'>'+value+'</option>');
				}
			break;*/


			/**
			 * data-depth 			: nb de profondeur de liste (ex: 2, affichera 2 select au maximum), 1 par défaut
			 * data-slug 			: le slug de la liste mère à afficher, listes primaires par défaut
			 * data-value 			: la valeur de l'entité  à récup en base
			 * data-disable-label 	: cache le label de sous-liste si mentionné
			 * data-hierarchy		: si mentionné à false, ne récupère pas
			 * data-parent-id		: l'id de la liste parente associée
			 * data-output		: id/slug définit la valueur de sortie de l'input (slug de la liste ou id de la liste (defaut))
			 */
			case 'dictionary':
				var data = input.data();
				var slug = input.attr('data-slug') ? input.attr('data-slug') : "";
				var parentId = input.attr('data-parent-id') && input.attr('data-parent-id').length ? input.attr('data-parent-id') : "";
				//if (!slug.match(/^[a-z\d\-_]+$/i) && parentId == '') return;
				data.output = data.output ? data.output : 'id';


				$.action({
					action : 'core_dictionary_component_load',
					slug: slug,
					parentId : parentId,
					depth : data.depth,
					hierarchy : input.attr('data-hierarchy') == 'false' ? 0 : 1,
					value: input.attr('data-value')
				},function(r){
					var children = r.content && r.content.childs ? r.content.childs : r.content;
					input.attr('onchange', 'get_sub_dictionary(this, "'+input.attr('name')+'",'+1+');');
					input.html('');
					input.append('<option value=""> - </option>');

					$.each(children, function (index, value){


						if (value.selected) {
							input.append('<option value="'+value[data.output]+'" data-slug="'+value.slug+'" data-parent="'+value.parent+'" data-sublabel="'+value.sublistlabel+'" selected>'+value.label+'</option>');
							get_selected_values(input, value);
						}else{
							input.append('<option value="'+value[data.output]+'" data-slug="'+value.slug+'" data-parent="'+value.parent+'" data-sublabel="'+value.sublistlabel+'">'+value.label+'</option>');
						}
					});
				});
			break;
			/**
			 * data-label 	: le label affiché dans la zone
			 * data-delete 	: méthode de suppression de doc de l'entité
			 * data-save 	: méthode de sauvegarde de doc de l'entité (si mentionné, save automatique)
			 * data-readonly: Empeche l'ajout/suppression de documents
			 * data-allowed : les extensions de fichier acceptées
			 */
			case 'dropzone':
				console.warn('[DEPRECATED] : Dropzone doit être remplacé par le composant file');
				if(input.find('form').length != 0 || input.find('ul>li').length) break;
				if(!input.attr('data-action')) input.attr('data-action','action.php?action=core_temporary_file_upload');
				var readonly = input.attr('data-readonly') == "true" ? true : false;
				if(!input.get(0).hasAttribute('id')) input.attr('id',generate_uuid(10));
				var customTpl = input.find('> *:not(:visible)');
				var customActions = '';
				if(customTpl && customTpl.length){
					$.each(customTpl, function(i, action){
						if(i>2) return;
						var valCalc = readonly ? i*28+3 : i*28+25;
						customActions += $(action).removeClass('hidden').css('right', valCalc).get(0).outerHTML;
					});
				}
				var preview = '<li data-path="{{path}}" data-ext="{{ext}}" data-url="{{url}}" data-name="{{name}}" data-last-modification="{{lastModification}}">'+
					'<a {{#url}}href="{{url}}"{{/url}} target="_blank" title="{{name}}" class="text-decoration-none media">';
				preview += input.get(0).hasAttribute('data-preview') ? '<img style="margin: 0 5px;float: left;max-height:100px;max-width:80px;" src="{{url}}"/>' : '<i class="{{icon}} mx-1 align-self-top pt-1"></i>';
				preview += ' <span class="media-body">{{name}}{{lastModification}}</span></a>'+customActions+' <i class="fas fa-times pointer '+(input.attr('data-delete')?'':'hidden')+'" onclick="{{#temporary}}dropzone_delete_file(this);{{/temporary}}{{^temporary}}'+input.attr('data-delete')+'(this){{/temporary}}"></i><div class="clear"></div></li>';

				var valueFiles = input.html()!='' && is_json_string(input.text()) ? JSON.parse(input.text()) : [];

				input.html('');
				var allowed = input.attr('data-allowed');
				if(allowed) allowed = allowed.split(',');
				var save = input.attr('data-save');
				var size = input.attr('data-max-size');
				var maxFile = input.attr('data-max-files');
				maxFile = 100; //maxFile=='' || ! maxFile ? 0 : maxFile;

				input.upload({
					allowed : allowed,
					size : size == '' ? 0 : size,
					readonly: readonly,
					addData: function(){
					    return {index: input.attr('id')};
					},
					start: function(){
						isProcessing = true;
						if(maxFile!=0 &&  $('li:visible',files).length >=maxFile   ) return -1;
						preload.removeClass('hidden');
					},
					success: function(response){
						isProcessing = false;
						if(response.error){
							preload.addClass('hidden');
							$.message('error', response.error, 0);
							return;
						}
						if(response.previews.length && response.previews[0].name) {
							var inputTemp = $('#'+input.attr('id')+'_temporary');
							var currVal = inputTemp.val().length ? JSON.parse(inputTemp.val()) : [];

							for(var i in response.previews){
								files.append(Mustache.render(preview,response.previews[i]));
								currVal.push(response.previews[i]);
							}
							inputTemp.val(JSON.stringify(currVal));
							if(save) window[save](response.previews);
						}
						preload.addClass('hidden');
					},
					complete: function(){
						isProcessing = false;
						preload.addClass('hidden');
					},
					error: function(){
						isProcessing = false;
						preload.addClass('hidden');
					},
				});
				var preload = $('<div class="preload progress-bar progress-bar-striped progress-bar-animated hidden"></div>');
				input.append(preload);
				var files = $('<ul class="my-auto mx-0 w-100 pb-0"></ul>');
				input.append(files);


				var filesValues = $('<input type="hidden" name="'+input.attr('id')+'_temporary" id="'+input.attr('id')+'_temporary">');
				input.append(filesValues);

				for(var i in valueFiles) {
					files.append(Mustache.render(preview,valueFiles[i]));
					if(readonly) files.find('li > i.fa-times').remove();
				}
				if(!valueFiles.length && readonly)
					input.append('<div>Aucun document</div>');
			break;

			case 'file':

				if(!input.get(0).hasAttribute('data-id')) input.attr('data-id',generate_uuid(10));

				var droppedFiles = false;
				//Récuperation des data attributs de l'input
				var data = input.data();
				//extra données (eg.id de l'entité liée)
				if(input.attr('data-data')){
					try{
						data.data = input.attr('data-data') ? JSON.parse(input.attr('data-data')) : {};
					}catch(e){
						data.data = {};
					}
				}

				var options = $.extend({
					allStart : function(files){return true},
					allComplete : function(){},
					fileStart : function(file){return true},
					fileComplete : function(file){},
					fileError : function(file){},
					fileProgress : function(file){},
					over : 'file-hover',
					label : 'Faites glisser vos fichiers ici',
					data : {},
					limit : 0,
					readonly : false,
					onVisible : true
				},data);


				if(input.hasAttr('readonly')) options.readonly = true;
				var component = data.component;

				//initialisation du composant et liaison à l'input si pas déja fait
				if(!component){
					var component =  $($('#component-file-template').get(0).outerHTML);
					component.attr('class',input.attr('class')).removeClass('hidden').removeAttr('id');
					input.after(component).data('component',component);
					input.attr('data-multiple-values','true');
				}

				if(options.onVisible === true && !component.is(':visible')) break;

				var lineTpl = $('#component-file-template .upload-list').html();

				var removeLine = function(element){
					form.find('input[type="file"]').val('');
					if(element.attr('data-path')==''){
						element.remove();
						component.attr('data-file-number',uploadList.find('>li').length);
						return;
					}
					$.action({
						action: data.action,
						type: 'delete',
						path: atob(element.attr('data-path')),
						data: data.data
					},function(r){
						updateValue(input,{path:element.attr('data-path')},true);
						element.remove();
						component.attr('data-file-number',uploadList.find('>li').length);
					});
				}
				//Ajout d'une ligne fichier
				var addLine = function(tpl,data){
					var component =  input.data('component');
					if(data.path) data.path = btoa(data.path);
					var line = $(Mustache.render(tpl,data));
					if(options.buttons) line.find('.file-button').html($(options.buttons).html());
					component.find('.upload-list').append(line);

					//affichage du preview avec pré-chargement pour eviter le blink
					if(data.preview){
						$('<img/>').attr('src', data.preview).on('load', function() {
						   $(this).remove();
						   line.find('.file-preview').attr('style','background-image:url('+data.preview+')');
						   line.addClass('preview-loaded');
						});
					}else{
						line.addClass('no-preview');
					}

					line.find('.btn-delete').click(function(){
						if(!line.hasClass('.upload-error')){
							if(!confirm('Êtes-vous sûr de vouloir supprimer ce fichier ?')) return;
						}
						removeLine(line);
						input.trigger('change');
					});
					return line;
				}
				//Ajout d'un ensemble de fichiers
				var refreshFiles = function(tpl,input,data){
					var component =  input.data('component');
					component.find('.upload-list li').remove();
					for (var k in data.files) {
						addLine(tpl,data.files[k]);
					}
				}

				var updateValue = function(input,data,toDelete){
					var jsonValue = JSON.parse(input.val()?input.val():'[]');

					if(toDelete){
						//delete
						for(var k in jsonValue)
							if(btoa(jsonValue[k].path) == data.path) jsonValue.splice(k,1);
					}else{
						//add
						data.path = atob(data.path);
						jsonValue.push(data);
					}
					input.val(JSON.stringify(jsonValue));
				}

				if(input.hasAttr('required')) component.attr('required','');

				if(!options.data.id && $.urlParam('id')) options.data.id = $.urlParam('id');
				//Récuperation des extensions, de la taille, des fichiers existants
				$.action({
					action : options.action,
					type : 'search',
					data : options.data
				},function(r){
					if(r.options) options = $.extend(options,r.options);
					refreshFiles(lineTpl,input,{files : r.files});
					component.attr('data-file-number',r.files.length);
				});


				//formulaire d'envois
				var form = component.find('form');
				//input d'upload caché
				var fileInput = form.find('input[type="file"]');
				//drop & click zone
				var uploadZone = component.find('.upload-zone');
				//liste des fichiers
				var uploadList = component.find('.upload-list');

				form.attr('action','action.php?action='+options.action+'&type=upload');
				uploadZone.find('.file-upload-label').html(options.label);

				component.attr('data-file-number',0);
				if(options.readonly !== false) component.addClass('readonly');

				//test if d&d is enabled n browser
				var div = document.createElement('div');
				var dragAndDropEnabled = (('draggable' in div) || ('ondragstart' in div && 'ondrop' in div)) && 'FormData' in window && 'FileReader' in window;

				//set events
				if(options.readonly === false){
				uploadZone
						.off('click drag dragstart dragend dragenter dragleave drop dragover').on('click', function (e) {
						   fileInput.trigger('click');
						   e.preventDefault();
						   e.stopPropagation();
						})
						.on('drag dragstart dragend dragover dragenter dragleave drop', function (e) {
							e.preventDefault();
							e.stopPropagation();
						})
						.on('dragover dragenter', function () {
							form.addClass(options.hover);
						})
						.on('dragleave dragend drop', function () {
							form.removeClass(options.hover);
						})
						.on('drop', function (e) {
							droppedFiles = e.originalEvent.dataTransfer.files;
							form.trigger('submit');
						});
				}
				//si l'input file change (on a choisis un fichier) on lance la soumission du formulaire
				fileInput.off('change').on('change', function (e) {
					form.trigger('submit');
				});

				//sur soumission du formulaire
				form.off('submit').on('submit', function (e) {
					e.preventDefault();

					//fonctionnement browser modernes
					if (!dragAndDropEnabled) throw 'Drag & drop non fonctionnel, veuillez mettre à jour votre navigateur';
					var fileProcessed = [];

					//si pas de drop mode, on récuepre le mode click
					if(!droppedFiles) droppedFiles = $('input',form).get(0).files;


					var fileQueue = [];

					var allowedExtensions = options.extension ? options.extension.split(',') : [];

					$.each(droppedFiles, function (i, file) {
						//line data
						var lineData = {
							extension : file.name.split('.').pop(),
							label : file.name,
							sort : i
						};

						if(options.limit!=0 && i > options.limit) return;

						//dom element
						var fileLine = addLine(lineTpl,lineData);
						//ajout a la file d'upload
						fileQueue.push({
							file : file,
							data : lineData,
							element : fileLine
						});
					});

					droppedFiles = null;

					if(!options.allStart(fileQueue)) return;

					for(var k in fileQueue) {
						var file = fileQueue[k].file;

						fileQueue[k].element.attr('data-sort',k);

						try{

						if(allowedExtensions.length !=0  && allowedExtensions.indexOf(fileQueue[k].data.extension.toLowerCase()) === -1 )
							throw "Extension non permise, autorisé : "+allowedExtensions.join(', ');

						if(options.size && file.size > options.size)
							throw "Taille fichier "+file.size+" octets trop grande (max autorisé:"+options.size+" octets";


						var ajaxData = new FormData();
						ajaxData.append(input.attr('data-id'), file);
						ajaxData.append('index', input.attr('data-id'));
						ajaxData.append('sort', k);


						if(!options.fileStart(fileQueue[k])) continue;
						$.ajax({
							url: form.attr('action'),
							type: form.attr('method'),
							data: ajaxData,
							dataType: 'json',
							cache: false,
							contentType: false,
							processData: false,
							complete: function (data) {
								var response = data.responseJSON ? data.responseJSON : {error :data.responseText,sort:-1 };
								if(response.sort!=-1){
									var currentFile = fileQueue[response.sort];
									if(response.error) currentFile.element.addClass('upload-error').find('.error-message').html(response.error).attr('title',response.error);
									currentFile.element.addClass('upload-complete');
								}else{
									if(response.error) $.message('error',response.error);
								}
								fileQueue[response.sort] = true;
								var completed = 0;
								for(var u in fileQueue){
									if(fileQueue[u]===true) completed++;
								}
								if(fileQueue.length == completed){
									if(window[options.allComplete]) window[options.allComplete](response);
									input.trigger('change');
								}
							},
							success: function (data) {
								var currentFile = fileQueue[data.sort];
								options.fileComplete(currentFile);
								var newElement = addLine(lineTpl,data);
								currentFile.element.replaceWith(newElement);
								updateValue(input,data);
								var lines = uploadList.find('>li');
								if(options.limit != 0 && lines.length > options.limit){
									uploadList.find('>li').each(function(i,li){
										if(i < lines.length - options.limit ) $(li).remove();
									});
								}
								component.attr('data-file-number',lines.length);
								newElement.addClass('upload-success');
							},
							error: function (data) {
								var response = data.responseJSON ? data.responseJSON : {error :data.responseText,sort:-1 };
								if(response.sort!=-1){
									var currentFile = fileQueue[data.sort];
									options.fileError(currentFile);
									currentFile.element.addClass('upload-error');
									fileQueue[response.sort] = true;
								}else{
									$.message('error',response.error);
								}
							},
							xhr: function() {
								var xhr = new window.XMLHttpRequest();
								xhr.sort = k;

								xhr.upload.addEventListener("progress", function(evt){
									if (evt.lengthComputable) {
									var percentComplete = (evt.loaded / evt.total) * 100;
									percentComplete = Math.round(percentComplete * 100) / 100;
									currentFile = fileQueue[xhr.sort];
									options.fileProgress(fileQueue[xhr.sort],percentComplete);

									var progressBar = $('.progress-bar',currentFile.element)
										.css('width',percentComplete+'%');
									if(percentComplete >= 100) currentFile.element.addClass('upload-process');

									}
								}, false);
								return xhr;
							}
						});

						}catch(e){
							options.fileError(fileQueue[k],e);
							fileQueue[k].element.addClass('upload-error').find('.error-message').html(e).attr('title',e);
							fileQueue[k] = true;

							continue;
						}

					};

				});



			break;

			/*
			Composant table regroupant a terme toutes les options de "table avancée" :
			* les colonnes dynamiques
			* les multi select checkbox
			* les colonnes sortables
			* futures features tableau (ex: filtre sur les colonnes façon excel)
			ce composant est voué a être un composant core un fois rodé
			*/
			case 'search-table':


				var SearchTable = function(table,options){
					var table = $(table);
					table.data('searchTable',this);
					this.table = table;
					this.options = options;
					this.slug = options.slug;
					this.actionButton = $(this.options.checkboxAction);

					var object = this;
					//Gestion des checkbox
					var globalCheckBox = object.table.find('.table_checkbox_global');
					if(globalCheckBox.length==0 && object.options.checkboxAction){
						var globalCheckBox =
						object.table.find('thead > tr:eq(0)').prepend('<th class="align-middle text-center"><input type="checkbox" class="table_checkbox_global" data-type="checkbox"></th>');
						globalCheckBox = object.table.find('.table_checkbox_global');
						globalCheckBox.click(function(){
							object.table.find('tbody td:visible input[type="checkbox"]').click();
						});

						object.table.find('tbody > tr:eq(0)').prepend('<td class="align-middle text-center"><input class="table_checkbox_line" type="checkbox" data-type="checkbox"></td>');

				    	object.table.on('click','tr:visible input.table_checkbox_line',function(){

							var element = $(this);
							var checked = element.prop('checked');
							var tr = element.closest('tr');

							var id = tr.attr('data-id');
							if(!id) return;

							var ids = object.checkboxes();

							if(checked) {
								ids.push(id);
							}else{
								var i = ids.indexOf(id);
								if(i > -1) ids.splice(i, 1);
							}


							object.checkboxes(ids)
							object.refreshActionButton();
						});
				    }
				    if(object.table.attr('data-loaded')!=1){
						var search = window[options.entitySearch];
						 //Activation du tri colonne si au moins une en-tete contient le data-sortable
					    if(object.table.find('[data-sortable]').length>0){
						    object.table.sortable_table({
								onSort : function(){search({keepChecked:true})}
							});
						}
						//lancement de la recherche automatique
						if(search) search();
						object.table.attr('data-loaded',1);
					}
				}

				SearchTable.prototype.refreshActionButton = function(){
					var ids = this.checkboxes();
					this.actionButton.attr('data-count',ids.length).find('span.count').text(ids.length);
				};

				SearchTable.prototype.resetCheckbox = function(){
					this.checkboxes([]);
					this.refreshActionButton();
				};

				SearchTable.prototype.fillCheckbox = function(){
					var selected = this.checkboxes();
					for(var k in selected)
						$('tr:visible[data-id="'+selected[k]+'"] input.table_checkbox_line',this.table).prop('checked',true);
				};

				SearchTable.prototype.resetGlobalCheckbox = function(){
					this.table.find('.table_checkbox_global').prop('checked',false);
				};

				SearchTable.prototype.checkboxes = function(data){
					var tablesearch = localStorage.getItem('component.tablesearch.checked');
					tablesearch = !tablesearch ? {} : JSON.parse(tablesearch);
					if(!data)
						return tablesearch[this.slug] ? tablesearch[this.slug] : [];

					tablesearch[this.slug] = data;
					localStorage.setItem('component.tablesearch.checked', JSON.stringify(tablesearch));
				};

				var searchTable = new SearchTable(input,input.data());
			break;



			/**
			 * data-simple : Si "true" alors interface avec moins de boutons
			 * data-minimal: Si présent alors affichage simple en mode input inline
			 */
			case 'wysiwyg':
				var buttons = [
					['strong','em','underline','del'],
					['foreColor', 'backColor'],
					['fontfamily'],
					['fontsize'],
			 		['undo', 'redo'], // Only supported in Blink browsers
			 		['formatting'],
			 		['superscript', 'subscript'],
			 		['link'],
			 		['insertImage'],
			 		['table'],
			 		['justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull'],
			 		['unorderedList', 'orderedList'],
			 		['horizontalRule'],
			 		['removeformat'],
			 		['fullscreen'],
			 		['viewHTML']
		 		]
		 		if(input.attr('data-simple')!=null || input.attr('data-simple') == 'true'){
		 			buttons = [
			 			['strong','em','underline','del'],
			 			['foreColor', 'backColor'],
			 			['undo', 'redo'], // Only supported in Blink browsers
			 			['formatting'],
			 			['link'],
			 			['unorderedList', 'orderedList'],
			 			['insertImage']
		 			]
		 		}

		 		if(input.attr('data-buttons')!=null && input.attr('data-buttons')!='') {
		 			buttons = is_json_string(input.attr('data-buttons')) ? JSON.parse(input.attr('data-buttons')) : [ input.attr('data-buttons').split(',') ];
		 		}

		 		var defaultOptions = {
		 			btns: buttons,
		 			lang: 'fr',
		 			autogrow: input.attr('data-minimal') == null,
		 			semantic: false,
		 			plugins : {}
		 		};



		 		var data = input.data();
	 			defaultOptions.tagsToRemove= ['script', 'link'];

	 			var scriptAllow = input.get(0).hasAttribute('data-script-allow');
	 			if(scriptAllow) defaultOptions.tagsToRemove= [];

			 	// gestion des mentions
			 	defaultOptions.plugins.mention = {keys : {}};

			 	if(input.attr('data-mention-user') && input.attr('data-mention-user') != ''){
			 		//mention @
			   	defaultOptions.plugins.mention.keys['@'] = {
			   	 	load : function(data){

			   	 		data.event.preventDefault();
			   	 		document.execCommand('insertHTML', false, '<b class="mention-user-picker-container">@<input type="text" data-type="user" data-types="'+input.attr('data-mention-user')+'" class="mention-user-picker d-inline-block" value=""></b>');

			   	 		var inputContainer = $('.mention-user-picker-container');

			   	 		init_components(inputContainer);
			   	 		var picker = $('.mention-user-picker-container input');

			   	 		picker.focus();
			   	 		var timeout = null;

			   	 		picker.click(function(){
			   	 			clearTimeout(timeout);
			   	 			var value = $(this).val();
			   	 			inputContainer.remove();
			   	 			data.editor.focus();
			   	 			data.textarea.trumbowyg('restoreRange');
			   	 			document.execCommand('insertHTML', false, '<span class="data-mention-user" data-mention-type="user" data-mention-value="'+value+'">@'+value+'</span>&nbsp;');
			   	 			data.editor.trigger('keyup');
			   	 		}).blur(function(){

		   	 			//on supprime le composant si blur sans valeur (attente de 200 ms car le blur est trigger avant le click)
		   	 			timeout = setTimeout(
		   	 				function(){
			   	 				inputContainer.remove();
			   	 				data.editor.trigger('keyup');
			   	 			},200);
			   	 		});

			   	 		picker.keydown(function(e){
			   	 			var code  = e.keyCode || e.which;
			   	 			//Si appuis sur espace ça casse l'autocompletion
			   	 			if(code == 32){
			   	 				e.preventDefault();
			   	 				var value = picker.val();
			   	 				$('.mention-user-picker-container').remove();
			   	 				data.editor.focus();
			   	 				data.textarea.trumbowyg('restoreRange');
			   	 				document.execCommand('insertHTML', false, '@'+value+'&nbsp;');
			   	 				data.editor.trigger('keyup');
			   	 			}
			   	 		});
			   	 	}
			   	};
				}

				if(input.attr('data-mention-object') && input.attr('data-mention-object') != ''){
					//mention #
				    defaultOptions.plugins.mention.keys['#'] = {
				    	load : function(data){
				    		data.event.preventDefault();
				    		document.execCommand('insertHTML', false, '<b class="mention-object-picker-container">#<input type="text" class="mention-object-picker" value=""></b>');

				    		var picker = $('.mention-object-picker');
				    		picker.focus();
				    		picker.keydown(function(e){
				    			var code  = e.keyCode || e.which;
				    			//Si appuis sur espace ça casse l'autocompletion
				    			if(code == 32){
				    				e.preventDefault();
				    				var value = picker.val();
				    				$('.mention-object-picker-container').remove();
				    				data.editor.focus();
				    				data.textarea.trumbowyg('restoreRange');
				    				document.execCommand('insertHTML', false, '#'+value+'&nbsp;');
				    				data.editor.trigger('keyup');
				    			}
				    		});

				    		var timeout = null;
				    		picker.autocomplete({
								action : input.attr('data-mention-object'),
								skin : function(item){
									var html = '';
									var re = new RegExp(picker.val(),"gi");
									name = item.name.replace(re, function (x) {
										return '<strong>'+x+'</strong>';
									});
									html += '<div class="pointer"><span>'+name+'</span>';
									html += '<div class="clear"></div>';
									return html;
								},
								highlight : function(item){
									return item;
								},
								onClick : function(selected,element){
									clearTimeout(timeout);
									picker.val('');
									$('.mention-object-picker-container').remove();
									data.textarea.trumbowyg('restoreRange');
									slug = selected.slug ? selected.slug : selected.name;
				    				document.execCommand('insertHTML', false, '<span class="data-mention-object" data-mention-type="object" title="'+selected.name+'" data-mention-value="'+selected.id+'">#'+slug+'</span>&nbsp;');
				    				data.editor.trigger('keyup');
								},
								onBlur : function(selected){
									if(input.val() == '') picker.val('');
									timeout = setTimeout(function(){
					    				$('.mention-object-picker-container').remove();
					    				data.editor.trigger('keyup');
					    			},200);
								}
							});
				    	}
				   };
				}


			    var options = $.extend(defaultOptions,data);
			    options = $.extend(options,  input.data());

			    var obj = input.trumbowyg(options);
			    //fix pour accepter les tags non sémantiques dans la gestion du highlight de bouttons
			    trumb = input.data('trumbowyg');
			    trumb.tagToButton['b'] = 'strong';
			    trumb.tagToButton['i'] = 'em';
			    trumb.tagToButton['u'] = 'underline';
			    input.data('trumbowyg',trumb);
			    obj.data('trumbowyg',trumb);

			    obj.on('tbwblur', function(){ input.trigger('blur'); });
			    obj.on('tbwchange', function(){ input.trigger('keypress'); });

			    var trumbowygBox = $(input).closest('div.trumbowyg-box');

			    trumbowygBox.addClass(input.attr('class')).removeClass('trumbowyg-textarea');
			    if(input.attr('required')) trumbowygBox.attr('required', true);
			    if(input.attr('readonly')) obj.trumbowyg('disable');

			      if(input.val()!='')
			    	input.trumbowyg('html', input.val());

			    if(input.attr('data-minimal') != null){
			        trumbowygBox.addClass('trumbowyg-minimal');
			        var btnPane = trumbowygBox.find('.trumbowyg-button-pane');
			        var editor = trumbowygBox.find('.trumbowyg-editor');

			        btnPane.addClass('hidden');
			        editor.removeAttr('style').keydown(function(e) {
			            $(this).find('*').removeProp('font-size');

			            switch (e.keyCode) {
			                case 13:
			                    return false;
			                break;
			                default:
			                    return true;
			                break;
			            }
			        }).on('paste',function(e){
			            e.preventDefault();
			            e.stopImmediatePropagation();
			            var text = (e.originalEvent || e).clipboardData.getData('text/plain');
			            document.execCommand("insertHTML", false, text);
			        });
			    }
		    break;
		    /**
		     * Permet la recherche combinatoire multi critères.
		     * Voir filter.component.js pour plus d'infos.
		     */
			case 'filter':
			 	var box = new FilterBox(input,input.data());
			break;

			/**
		    * Ouvre une popup de definition des droits ciblée sur une entité, une section, un id
		    	data-scope : section / partie de code ou de module concerné par le droit (defaut : core)
				data-uid : Id spécifique d'entité concerné par le droit (defaut : $.urlParam('id'))
				data-edit : Afficher la case edition (defaut : true)
				data-read : Afficher la case lecture (defaut : true)
				data-recursive : Afficher la case récursif (defaut : false)
				data-configure : Afficher la case configuration (defaut : false)
				data-firm : Scoper sur un établissement, cache la selection d'établissement si définit (defaut : null)
				data-saveAction : Définir une action de save custom du droit (defaut : core_right_save)
				data-deleteAction :  Définir une action de delete custom du droit (defaut : core_right_delete)
		    */
			case 'right':
			 	input.off('click').click(function(){


				 	var data = $.extend({
						scope : 'core',
						uid : $.urlParam('id'),
						edit : true,
						read : true,
						recursive : false,
						configure : false,
						firm : null,
						saveAction : 'core_right_save',
						deleteAction : 'core_right_delete'
					},input.data());

				 	core_right_edit(data);
			 	});
			break;
			/**
			 * data-table (ex : #contacts) : id de la table de résultat liée
			 * data-value (ex: 20) : valeur initiale d'item par page si rien dans le local storage
			 * data-step (ex: 5) : valeur du pas entre chaque proposition du nb d'item par page
			 * data-items (ex: 10,25,50,150) : liste des items proposés pour la préférence de pagination séparés par virgule
			 * data-max-item (ex: 50) : valeur maximum possible d'item par pages
			 */
			case 'pagination-preference':
				var pagination = localStorage.getItem('pagination') ? JSON.parse(localStorage.getItem('pagination')) : {};
				var table = $(input.attr('data-table'));
				var container = input.data('container');
				var items = input.data('items');
				var uid = ($.urlParam('module')?$.urlParam('module'):'main')+'-'+($.urlParam('page')?$.urlParam('page') :'main')+'-'+table.attr('id');

				if(!container){
					var finalItems = [];
					if(!items) {
						var step = parseInt(input.attr('data-step'));
						for(var i=parseInt(input.attr('data-max-item')), j=1; i>=j; i-=(step?step:10))
							finalItems.push({number:i});
					} else {
						var items = items.split(',');
						for(var i=0; i<items.length; i++)
							finalItems.push({number:items[i]});
					}

					var initial = pagination[uid] ? pagination[uid] : input.attr('data-value');
					var container = $(Mustache.render($('#pagination-preference-template').html(),{initial:initial, items:finalItems}));
					input.append(container);
					input.data('container',container);
				} else {
					container = $(container);
				}

				container.find('.dropdown-item').click(function(){
					var button = $(this);
					var toggle = button.parent().parent().find('.dropdown-toggle');
					toggle.text(button.text());
					pagination[uid] = button.text();
					localStorage.setItem('pagination',JSON.stringify(pagination));
					if(window[table.attr('data-column-search')]) window[table.attr('data-column-search')]();
				});
			break;
			/**
			 * data-toggle-event : Pour le moment, que "hover", de base au click
			 * data-show-strength: Si indiqué, affiche la barre de force du mot de passe renseigné dans l'input
			 * data-length	 	 : Si indiqué, le generateur de mot de passe se cantonnera à la longueur spécifiée
			 * data-forbidden	 : Si indiqué (sous forme de string), le generateur de mot de passe n'utilisera pas ces caractères
			 * data-generator	 : Si indiqué, affiche une icône pour générer un mdp sécurisé
			 */
			case 'password':
				if(input.closest('.password-field').length) return;
				input.attr('type', 'password');
				input.addClass('component-raw-value'); // utilisé par les filtres / etc.. pour dissocier la valeur brute du composant
				input.wrap(function() {return '<div class="data-type-password password-field"></div>';});
				input.data('data-component',input.parent());
				var container = input.parent('.password-field');
				container.append('<i class="fas fa-eye-slash noPrint password-toggler" title="Afficher/Masquer le mot de passe"></i>');

				container.find('i.password-toggler').attr((input.attr('data-toggle-event') == 'hover')?{
					'onmouseover': 'toggle_password(this);',
					'onmouseleave': 'toggle_password(this);'
				}:{'onclick': 'toggle_password(this);'});

				if(input.attr('data-generator') !== undefined) {
					container.append('<i class="fas fa-shield-alt noPrint password-generator" title="Générer un mot de passe"></i>')
					container.find('i.password-generator').on('click', function(){
						if(input.val()!='' && !confirm('Attention, un mot de passe est déjà défini.\nVoulez-vous quand même en générer un nouveau ?')) return;

						//Longueur attendue (12 par défaut)
						var length = input.attr('data-length') !== undefined && input.attr('data-length').length ? parseInt(input.attr('data-length')) : 12;
						//Regex norme ANSSI
						var strong = new RegExp('^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[=/\()%ยง!@#$%^&*])(?=.{'+length+',})');
						var forbidden = input.attr('data-forbidden') !== undefined && input.attr('data-forbidden').length ? input.attr('data-forbidden') : '';

						do {
							password = generate_password(length, forbidden);
						} while(!strong.test(password))

						input.val(password).trigger('change');
						if(input.attr('data-show-strength') !== undefined)
							check_password_strength(input);
					});

				 	function generate_password(length,forbidden){
				 		var charset = get_characters_set();
				 		var length = length ? length : 12+get_random_int(6);
				 		var forbidden = forbidden!=null ? forbidden : '';
				 		var password = "";

				 		for(var i=0; i<forbidden.length; i++){
					 		var index = charset.indexOf(forbidden[i]);
				 		    if(index > -1) charset.splice(index, 1);
				 		}
				 		for (var i=0; i<length; ++i)
				 			password += charset[get_random_int(charset.length)];
				 		return password;
				 	}
				}

				if(input.attr('data-show-strength') !== undefined) {
					input.parent('.password-field').append('<div class="strength-lines"><div class="line"></div><div class="line"></div><div class="line"></div></div>');

					// Strength validation on keyup-event
					input.on("keyup mouseup", function(e) {
						check_password_strength($(this));
					});

					//Check password strength
					function check_password_strength(input) {
						var value = $(input).val();
						var container = $(input).closest('.password-field');
						$(".line", container).removeClass("strength-low strength-medium strength-hard").addClass("strength-transparent");
						if(!value.length) return;

						var strongRegex = new RegExp('^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[=/\()%ยง!@#$%^&*])(?=.{12,})'),
							mediumRegex = new RegExp('^(((?=.*[a-z])(?=.*[A-Z]))|((?=.*[a-z])(?=.*[0-9]))|((?=.*[A-Z])(?=.*[0-9])))(?=.{6,})');

						if(strongRegex.test(value)) {
							$(".line", container).removeClass("strength-transparent").addClass("strength-hard");
						} else if (mediumRegex.test(value)) {
							$(".line:not(:last-of-type)", container).removeClass("strength-transparent").addClass("strength-medium");
						} else {
							$(".line:nth-child(1)", container).removeClass("strength-transparent").addClass("strength-low");
						}
					}
				}
			break;

			case 'url':
				if(input.closest('.url-field').length) return;

				var container = $('<div class="data-type-url url-field '+(!input[0].hasAttribute("data-no-lock") ? 'haslock' : '')+'">'+(!input[0].hasAttribute("data-no-lock")?'<i class="fas fa-lock url-icon"></i> ':'')+
					(!input[0].hasAttribute("data-no-link")?'<i class="fas fa-globe noPrint url-link" title="Afficher le site internet"></i>':'')+'</div>');
				input.after(container);
				container.prepend(input.detach());
				input.data('data-component',container);
				input.addClass('component-raw-value'); // utilisé par les filtres / etc.. pour dissocier la valeur brute du composant
				var link = $('.url-link',container);
				var icon = $('.url-icon',container);
				var keyActions = function(){
					var value = input.val();
					if(!value){
						link.addClass('hidden');
					}else{
						link.removeClass('hidden');
					}
					if(value.match('https://')){
						icon.removeClass('text-muted').addClass('text-success').attr('title','Site sécurisé');
					}else{
						icon.removeClass('text-success').addClass('text-muted').attr('title','Site non sécurisé');
					}
				}
				keyActions();

				input.keyup(keyActions);

				var scheme = input.attr('data-default-scheme');
				if(scheme){
					input.blur(function(){
						if(input.val() && !input.val().match('https?://')){
							input.val(scheme+'://'+input.val());
							keyActions();
						}
					});
				}

				container.find('i.url-link').click(function(){
					window.open(input.val());
				});


			break;
			case 'icon':
				input.addClass('hidden');
				input.next('.component-icon').remove();

				var data = {
					value : input.val(),
					choices : []
				};

				var icons = $.fontAwesome();
				var line = [];
				for(var key in icons){
					if(line.length==15){
						data.choices.push(line);
						line = [];
					}
					line.push(icons[key].icon);
				}

				var selector = input.data('data-component');
				if(!selector){
					var selector = $(Mustache.render($('.component-icon.hidden').get(0).outerHTML,data));
					selector.addClass(input.attr('class'));
					selector.removeClass('hidden form-control');

					input.data('data-component',selector);
					input.after(selector);
				}

				selector.on('show.bs.dropdown', function () {

					if(input.is('[readonly]') || input.is('[disabled]')){
						selector.find('.dropdown-menu').addClass('hidden');
					}else{
						selector.find('.dropdown-menu').removeClass('hidden');
					}
					setTimeout(function(){selector.find('input.form-control').focus();},0);
				})

				selector.find('.dropdown-icon-item').click(function(){
					selector.find('.dropdown-icon-item').removeClass('hidden');
					selector.find('input.form-control').val('');
					var icon = $(this).attr('data-icon');
					input.val(icon);
					selector.find('.dropdown-toggle i').attr('class',icon);
					input.trigger('change');
				});
				selector.find('input.form-control').keyup(function(){
					var value = $(this).val();
					$('.dropdown-icon-item i',selector).each(function(i,iconElement){
						iconElement = $(iconElement);
						parent = iconElement.parent();
						iconElement.attr('class').indexOf(value)!=-1 ? parent.removeClass('hidden') : parent.addClass('hidden');
					});
				});

				if(input.hasAttr('required')) selector.attr('required','');
				if(input.hasAttr('readonly')) selector.attr('readonly','');
			break;
			/**
			 * data-title (*) : Le titre du modal
			 * data-loaded (*): fonction callback appelée auprès le chargement du modal (bien pour setter des actions custom sur les boutons dans le modal par exemple)
			 * data-params 	: Les paramètres utilisés pour l'appel du callback
			 * data-action (*) : Appel de la modal par action et non par ajax
			 * data-precall : fonction appelée avant le chargement du modal (méthode de check sur l'UI par exemple)
			 * Pour customiser l'icône du quickform, il suffit de placer le contenu que l'on veut dans la div de data-type="quickform"
			 */
			case 'quickform':
				if(input.find('span').length) return;
				if(input.attr('disabled')) return;

				var cbLoaded = input.attr('data-loaded');
				// var params = input.attr('data-params') ? input.attr('data-params').split(',') : [];
				var params = input.attr('data-params') ? JSON.parse(input.attr('data-params')) : [];
				var preCb = input.attr('data-precall') ? input.attr('data-precall') : '';

				if(input.find('> *').length){
					input.find('> *').wrap('<span class="tooltip-warning">');
				} else {
					input.append('<span class="tooltip-warning"><i title="Création rapide" class="fas fa-plus-square"></i></span');
				}

				$(document).ready(function(e){
			 		$('span.tooltip-warning').tooltip({
			 			track: true,
			 			tooltipClass: 'quickform-tooltip',
			 		});
				 	input.on('click', function(e){
				 		if(preCb.length && !window[preCb]()) return;

			 			$.action({
			 				action: input.attr('data-action'),
			 				data: params
			 			}, function(r){
			 				var modal = $('#quickform-modal');
			 				var title = (r.title && r.title.length ? r.title : (input.attr('data-title')!=null ? input.attr('data-title') : 'Création rapide :'));

			 				$('#quickform-modal-label', modal).text(title);
			 				$('.modal-body', modal).html('').append(r.content);
			 				$('.modal-footer > div:first-of-type', modal).nextAll().remove();

			 				modal.clear();
			 				$('form', modal).attr('data-id','');

			 				if(cbLoaded) window[cbLoaded](params);
			 				modal.modal('show');
			 				init_components('#quickform-modal');
			 			});
			 		});
			 	});
			break;
			case 'checkbox':
				//3 cas possibles :
				//	- input sans rien, data-type="checkbox" --> on fait tout
				//	- input avec coquille sans data-uuid,	--> on génère simplement un data-uuid et maj valeur
				//	- input avec coquille et data-uuid 		--> on maj simplement sa valeur

				if(input.get(0).hasAttribute('data-uuid')) return;
				if(input.attr('type') != 'checkbox') input.attr('type', 'checkbox');

				if(!input.closest('label.check-component').length) {
					var labelBox = $('<label class="check-component"></label>');
					var checkbox = $('<div class="box"></div>');
					var id = input.attr('id');
					var title = input.attr('title');
					var customClass = input.attr('data-class');

					if(id) labelBox.attr('for', id);
					if(title) labelBox.attr('title', title);
					if(input.attr('class')) labelBox.addClass(input.attr('class'));
                    if(customClass) labelBox.addClass(customClass);
					if(input.prop('disabled') == true) labelBox.attr('disabled', true);
					input.removeAttr('title data-class').wrap(labelBox);
					input.after(checkbox);
				}
				var uuid = generate_uuid(15);
				input.attr('data-uuid', uuid);
				input.addClass('component-raw-value'); // utilisé par les filtres / etc.. pour dissocier la valeur brute du composant

				if(input.hasAttr('required')) input.closest('label.check-component').attr('required','');
				if(input.hasAttr('readonly')) input.closest('label.check-component').attr('readonly','');


				if(input.hasAttr('readonly')) input.closest('label.check-component').click(function(e){ e.preventDefault()});

				if(input.get(0).hasAttribute('id')) return;
				input.closest('label.check-component').off('click','div.box').on('click','div.box',function(e){


					if(input.hasAttr('readonly')) return e.preventDefault();
					var checkboxInput = $('input[data-uuid="'+uuid+'"]');
					checkboxInput.prop('checked', checkboxInput.prop('checked'));
				});
			break;
			/**
			 * data-label : Libellé affiché à côté de l'input radio
			 * name (*)	  : Le nom du groupe dont fait partie l'input radio
			 */
			case 'radio':
				if(input.get(0).hasAttribute('data-uuid')) return;
				if(input.attr('type') != 'radio') input.attr('type', 'radio');

				var uuid = generate_uuid(15);
				var id = input.get(0).hasAttribute('id')? input.attr('id') : '';
				var title = input.get(0).hasAttribute('title') ? input.attr('title') : '';
				var label = input.attr('data-label');

				var labelBox = $('<label data-uid="'+uuid+'"></label>');
				labelBox.attr({
					'for': id,
					'title': title
				});
				if(input.prop('disabled') == true) labelBox.attr('disabled', true);
				input.addClass('component-raw-value'); // utilisé par les filtres / etc.. pour dissocier la valeur brute du composant
				input.addClass('radio-component').removeAttr('title data-label').attr('data-uuid', uuid);
				input.after(labelBox);
				if(label) labelBox.after('<label for="'+id+'">'+label+'</label>');

				if(input.attr('id') && input.attr('id').length) return;

				labelBox.click(function(e){
					var radioInput = $('input[data-uuid="'+uuid+'"]');
					var name = radioInput.attr('name');
					$('input[name="'+name+'"]').prop('checked', false).removeAttr('checked');
					if(radioInput.prop('disabled') != true) radioInput.prop('checked', true);
					$(input).change();
				});
			break;
			/**
			 * data-action : l'action php pour récupérer l'UI de la card
			 * data-parameters : paramètres à passer avec l'action json encodés
			 */
			case 'card':
				if(!input.is(':visible')) return;
				var action = input.attr('data-action');
				if(!action) {
					console.warn('CARD COMPONENT: Need "data-action" to get card content');
					return;
				}
				var showDelay = input.attr('data-show-delay') ? input.attr('data-show-delay') : 250;
				var hideDelay = input.attr('data-hide-delay') ? input.attr('data-hide-delay') : 600;

				input.addClass('card-component-container').attr('data-uuid', generate_uuid());

				var timeout;
				input.off('mouseenter').mouseenter(function(event){
					var e = event.target || event.relatedTarget;
			        if (e.parentNode != this && e != this) return;
					event.stopImmediatePropagation();
					event.stopPropagation();

					$('*[data-type="card"]').find('.card-component')
						.addClass('card-component-hide')
						.removeClass('card-component-hover')
						.one('webkitAnimationEnd oanimationend msAnimationEnd animationend', function(e) {
							var card = $(this);
							if(card.closest('.card-component-container').attr('data-uuid') == input.attr('data-uuid')) return;
							card.closest('.wrapper').remove();
						});

					var parameters = input.attr('data-parameters');
					var data = parameters!=null && parameters.length ? JSON.parse(parameters) : {};
					data.action = action;

					timeout = setTimeout(function(){
						var currInput = input;
						if(!currInput.find('.card-component').length) {
							$.action(data, function(r){
								if(!r.content) return;
								//On utilise un wrapper pour gérer les overflows "out of the box"
								var card = $(r.content);
								var topOffset = currInput.offset().top;
								currInput.append(card);

								var position = {};
								var cardWidth = card.outerWidth();
								card.wrap($('<div class="wrapper"></div>'));
								card.addClass('card-component card-component-hover');

								if($('body').width() < (currInput.offset().left + cardWidth))
									position['right'] = cardWidth;
								else
									position['left'] = 0;

								//Gestion des overflows pour positionnement de la card
								var cardHeight = card.outerHeight();
								var htmlHeight = $('html').height();
								if(htmlHeight-100 < (topOffset + cardHeight)){
									var spaceBottom = htmlHeight-topOffset;
									var height = 0;
									if(spaceBottom > topOffset){
										if(cardHeight > spaceBottom) height = (spaceBottom-100);
									} else {
										position['top'] = -cardHeight;
										if(cardHeight > topOffset){
											height = (topOffset-100);
											position['top'] = -(topOffset-100);
										}
									}
									if(height!=0){
										card.css({
											height: height+'px',
											'overflow-y': 'auto',
											'overflow-x': 'hidden'
										});
									}
								}
								currInput.find('.wrapper').css(position);
								init_components(currInput.find('.wrapper'));
							});
						} else {
							var card = $(currInput.find('.card-component'));
				    		card.addClass('card-component-hover').removeClass('card-component-hide');
						}
					    clearTimeout(currInput.data('tOutbox'));
			        }, showDelay);
				});

				input.add(input.find('.card-component')).off('mouseleave').mouseleave(function(e){
					// e.stopImmediatePropagation();
					// e.stopPropagation();

					var input = $(this),
						card = input.find('.card-component'),
						tOutbox = setTimeout(function(){
							if(!$('.card-component:hover').length) {
								card.addClass('card-component-hide').removeClass('card-component-hover');
								card.one('webkitAnimationEnd oanimationend msAnimationEnd animationend', function(e) {
									$(this).closest('.wrapper').remove();
							  	});
							}
						}, hideDelay);
					//Clear du timeout d'apparition
					clearTimeout(timeout);
					//Set du l'id de timeout, permet de clear ce trigger si la souris revient sur l'input
					input.data('tOutbox', tOutbox);
				});
			break;

			case 'choice':
				if(input.get(0).tagName !='INPUT') return console.warn('Choice autorisé sur input uniquement');
				var data =  input.data();
				var container;
				if(!input.data("data-component")){
					container = $('<div class="'+input.attr('class')+' data-type-choice"></div>');
					input.before(container);
					input.data("data-component", container);
					input.addClass('component-raw-value'); // utilisé par les filtres / etc.. pour dissocier la valeur brute du composant
				}
				container = input.data("data-component");
				//récuperation du name servant d'id de groupe pour les radio
				var name = input.attr('name');
				if(!name) name = data.name;
				if(!name) name = input.attr('id');
				if(!name) name = Math.floor(Math.random() * 100);

				//rafraichissement des options possibles
				if(!data.values) return;
				html = '';
				for(var k in data.values){
					html+='<label class="pointer">'
					html+= '<input type="radio" data-type="radio" name="'+name+'" value="'+k+'">';
					html+= ' '+data.values[k]+'</label> ';
				}
				container.html(html);
				$('input',container).change(function(){
					input.val($(this).val());
					input.change();
				});
				init_components(container);

				//supprime les component-raw-value généré par l'utilisation des data-type = radio
				//pour les filtres on ne veut que l'input du choice
				container.find('.component-raw-value').removeClass('component-raw-value');

				//set de la valeur si définie dans l'input
				if(input.val()){
					$('input',container).prop('checked',false);
					$('input[value="'+input.val()+'"]',container).prop('checked',true);
				}

				if(input.hasAttr("required")) container.attr('required','');

			break;

			case 'jsontable':
				if(input.get(0).tagName !='INPUT') return;
				var data =  input.data();
				if(data.format!='key-value' && data.format!='multiple-values') return console.warn('seul les formats key-value et multiple-values sont implémentés sur le composant jsontable');
				var container;
				if(!input.data("data-component")){
					container = $('<table class="table table-striped '+input.attr('class')+' data-type-json">\
						<thead><tr></tr></thead><tbody></tbody>\
						</table>');
					input.before(container);
					input.data("data-component", container);
					input.addClass('component-raw-value'); // utilisé par les filtres / etc.. pour dissocier la valeur brute du composant
				}
				container = input.data("data-component");



				var head = $('thead tr',container);
				var body = $('tbody',container);

				var refreshTable = function(){


					head.find('th:not(.option-head)').remove();
					body.find('tr').remove();
					var html = '';

					if(data.format == 'key-value') data.columns = {"key":"Clé","value":"Valeur"};

					for(var k in data.columns){
						html+= '<th data-key="'+k+'">'+data.columns[k]+'</th>';
					}
					html+= '<th></th>';
					head.prepend(html);
					var values = {};
					try{
						values = input.val();
					 	values = JSON.parse(input.val());
					}catch(e){
						values = {};
					}


					var columnLength = Object.keys(data.columns).length+1;

					body.append('<tr><td colspan="'+columnLength+'" class="center pointer btn-add-line"><i class="fas fa-plus text-success "></i> <span class="text-muted font-weight-bold">AJOUTER</span></td></tr>');

					for(var k in values){
						line = values[k];
						if(data.format == 'key-value') line = {key:k,value:values[k]};
						addLine(line,data.columns);
					}

					$('.btn-add-line',container).click(function(){
						addLine(null,data.columns);
					});

					if(input.hasAttr('required')) container.attr('required','');

				}

				var addLine = function(data,header){
					if(!data){
						for(var k in header)header[k] = '';
						data = header;
					}

					var html = '<tr class="line">';
					for(var key in data)
						html += '<td><input type="text" class="form-control" data-key="'+key+'" value="'+data[key]+'"></td>';


					html +='<td><i class="fas fa-trash text-muted pointer btn-remove"></i></td>';
					html += '</tr>';
					var line =  $(html);


					body.find('tr:last-child').before(line);
					$('.btn-remove',line).click(function(){
						removeLine($(this).closest('tr'));
					});
					$('input',line).change(function(){
						refreshInput();
						input.change();
					});
					refreshInput();
					input.change();
				}
				var removeLine = function(element){
					$(element).remove();
					refreshInput();
					input.change();
				}

				var refreshInput = function(){

					var json = data.format=='multiple-values' ? [] : {};
					body.find('tr.line').each(function(){
						var tr = $(this);
						if(data.format=='key-value'){
							json[tr.find('td:eq(0) input').val()] = tr.find('td:eq(1) input').val();
						}else if(data.format=='multiple-values'){
							var line = {};
							tr.find('td').each(function(){
								var td = $(this);
								var inputValue = td.find('input');
								if(inputValue.length==0) return;
								line[inputValue.attr('data-key')] = inputValue.val();
							});

							json.push(line);
						}

					});
					input.val(JSON.stringify(json));
				}
				refreshTable();
				if(input.hasAttr('readonly')){
					container.attr('readonly','');
					container.find('tbody input').attr('readonly','');
				}
			break;

			case 'entitypicker':
				var component = input.data('data-component');
				var modal = $('#entitypicker-modal');
				if(!component){
					var component = $('<div class="'+input.attr('class')+' data-type-entitypicker"><i class="fas fa-database"></i> <span></span>  <i class="entitypicker-clear fas fa-times text-muted"></i></div>');
					input.data('data-component',component);
					input.after(component);

					if(modal.length!=0) modal.remove();

					modal = $('<div class="modal fade entitypicker-modal" id="entitypicker-modal" aria-hidden="true">\
							  <div class="modal-dialog modal-xl"> \
							    <div class="modal-content"> \
							      <div class="modal-header"> \
							        <h5 class="modal-title">Sélection d\'une entitée</h5>\
							        <button type="button" class="close" data-dismiss="modal" aria-label="Fermer">\
							          <span aria-hidden="true">&times;</span>\
							        </button>\
							      </div>\
							      <div class="modal-body">\
								      <div class="row">\
									      <div class="col-md-6 plugin-list-container">\
										      <ul class="plugin-list list-group">\
												<li class="hidden pointer plugin list-group-item {{className}}" data-label="{{name}}" data-path="{{folder.path}}"><i class="far fa-folder"></i> <span>{{name}} <small class="text-muted">{{description}}</small></span></li>\
											  </ul>\
									      </div>\
									      <div class="col-md-6">\
										      <ul class="entity-list list-group">\
												<li class="hidden entity list-group-item {{className}} pointer" data-label="{{label}}" data-path="{{path}}"><i class="fas fa-border-all"></i> <span>{{label}}</span></li>\
											  </ul>\
									      </div>\
								      </div>\
							      </div>\
							      <div class="modal-footer">\
							        <button type="button" class="btn btn-primary  btn-select">Sélectionner</button>\
							      </div>\
							    </div>\
							  </div>\
							</div>');
					$('body').append(modal);
				}

				modal = $('#entitypicker-modal');

				var searchEntities = function(){
					var plugin = $('.plugin.active',modal).attr('data-path');
					if(!plugin) return;
					$('.entity-list').fill({
						action:'core_entity_search',
						plugin : plugin
					},function(){
						$('.entity-list .entity').click(function(){
							input.val($(this).attr('data-path'));
							refreshValue();
							modal.modal('hide');
							input.change();
						});
					});
				};

				var refreshValue = function(){
					var value = input.val();
					if(!value) return $('>span',component).html('');
					$.action({
						action : 'core_entity_edit',
						path : value
					},function(response){
						$('>span',component).html('<span class="text-muted">'+response.plugin.name+'</span> <i class="fas fa-chevron-right"></i> '+response.label);
					});
				}

				$(component).off('click').click(function(){
					modal.modal('show');
					$('.plugin-list',modal).fill({action:'core_plugin_search',includeCore : true},function(){
						$('.plugin',modal).click(function(){
							$('.plugin',modal).removeClass('active');
							$(this).addClass('active');
							searchEntities();
						});
					});
				});

				$('.entitypicker-clear',component).click(function(event){
					event.stopPropagation();
					input.val('');

					refreshValue();
				});

				refreshValue();
			break;

			case 'filepicker':
				if(input.get(0).tagName !='INPUT') return;
				var data =  input.data();

				var container;

				var refreshComponent = function (){
					var folders = input.val().split('/');
					var html = '';
					for (var i in folders) {
						var folder = folders[i];
						if(!folder) continue;
						html += '<li><i class="fas fa-slash text-muted""></i><span class="pathlabel" '+(input.hasAttr('readonly') || !data.editable ?'':' contenteditable="true"')+' >'+folder+'</span></li>';
					}
					container.find('>ul').html(html);

					$('.pathlabel',container).click(function(event){
						event.stopPropagation();
						if(!input.hasAttr('readonly')) $('.filepicker-browse',container).click();
					}).keyup(function(){
						if(input.hasAttr('readonly') || !data.editable) return;
						var folders = input.val().split('/');
						folders[$(this).closest('li').index()] = $(this).text();
						input.val(folders.join('/'));
					});

				}

				if(!input.data("data-component")){
					container = $('<div class="'+input.attr('class')+' data-type-filepicker"><i class="far fa-folder"></i><ul></ul> <i class="filepicker-browse far fa-window-restore text-muted"></i> <i class="filepicker-clear fas fa-times text-muted"></i></div>');
					input.before(container);
					input.data("data-component", container);
					input.addClass('component-raw-value'); // utilisé par les filtres / etc.. pour dissocier la valeur brute du composant

					$('.filepicker-clear',container).click(function(event){
						event.stopPropagation();
						input.val('');
						refreshComponent();
						input.change();
					});

					container.click(function(){

							if(input.hasAttr('readonly')) return;

							$('#filepicker-modal').remove();

							var modal = $('<div class="modal fade filepicker-modal" id="filepicker-modal" aria-hidden="true">\
									  <div class="modal-dialog"> \
									    <div class="modal-content"> \
									      <div class="modal-header"> \
									        <h5 class="modal-title">Sélection d\'un élement</h5>\
									        <button type="button" class="close" data-dismiss="modal" aria-label="Fermer">\
									          <span aria-hidden="true">&times;</span>\
									        </button>\
									      </div>\
									      <div class="modal-body">\
									      <ul class="tree-folders folders-container">\
											<li class="hidden folder {{className}}" data-label="{{label}}" data-path="{{path}}"><i class="far fa-folder"></i> <span>{{label}}</span><ul class="folders-container"></ul></li>\
										  </ul>\
									      </div>\
									      <div class="modal-footer">\
									        <button type="button" class="btn btn-primary  btn-select">Sélectionner</button>\
									      </div>\
									    </div>\
									  </div>\
									</div>');
							$('body').append(modal);


							$('.tree-folders',modal).on('click','li',function(event){
								var li = $(this);

								if(li.hasClass('folder-focused')){
									var parent = li.parent().parent();
									treeSearch(parent.attr('data-path') == null ? '.' : parent.attr('data-path'));
								}else{
									treeSearch(li.attr('data-path'));
								}
								if(event) event.stopPropagation();
							});

							$('.btn-select',modal).click(function(){

								var path = $('.folder-focused',modal).attr('data-path');

								if(!path) return $.message('error','Aucun chemin n\'est sélectionné');
								input.val(path);

								refreshComponent();
								modal.modal('hide');
								input.change();
							});



						var treeSearch = function(folderPath){

							$.action({
								action: 'core_file_search',
								keyword : '',
								//sort : sort,
								root :data.root,
								folder : folderPath ? folderPath : ''
							},function(r){
								var tree = $('ul.tree-folders',modal);
								$('li:not(:eq(0))',tree).remove();
								var tpl = $('li:not(:visible)',tree).get(0).outerHTML;
						  		tree.find('li:visible').remove();

					  			var selected = folderPath ? folderPath : '' ;
					  			if(selected.length>=2 && selected.substring(0,2) == './') selected = selected.substring(2);
					  			for(var k in r.tree){
						  			var path = r.tree[k];
						  			var parts = path.split('/');
						  			var recipient =  tree.parent();
						  			for(var i in parts){
						  				var part = parts[i];
						  				var path = parts.slice(0,parseInt(i)+1).join('/');
						  				var element = $('> .folders-container > li[data-path="'+path+'"]',recipient);
						  				if(element.length ==0){
						  					var classes = '';
						  					if(selected==path) classes+= ' folder-focused ';
						  					if(selected && (selected.indexOf(path+'/')!==-1 || selected==path) ) classes+= ' folder-open ';
						  					var row = {
						  						path : path,
						  						label : part,
						  						className : classes
						  					};
						  					var element = $(Mustache.render(tpl,row));
						  					element.removeClass('hidden');
						  					recipient.find('> .folders-container').append(element);
						  				}
						  				recipient = element;
						  			}
						  		}


							});
						}

						treeSearch(input.val());
						modal.modal('show');
					});
				}else{
					container = input.data("data-component");
				}

				if(input.hasAttr('readonly')){
					container.attr('readonly','');
				}else{
					container.removeAttr('readonly');
				}

				if(input.hasAttr('required')) container.attr('required','');

				if(input.val()) refreshComponent();


			break;
			/**
			 * data-dictionary : Le slug du dictionary à utiliser
			 */
			case 'dictionary-table':
				if(input.hasClass('component-dictionary-table')) return;
				if(!input.attr('data-dictionary') || !input.attr('data-dictionary').length) return;
				var tpl = $('.component-dictionary-table').get(0).outerHTML;
				var inputData = input.data();

				$.action({
					action: 'core_dictionary_component_load',
					slug : input.attr('data-dictionary')
				},function(r){
					if(!r.content) {
						input.append('<div class="alert alert-warning mb-0"><i class="fas fa-exclamation-triangle mr-2"></i>Liste spécifiée inexistante</div>');
						return;
					}
				 	var list = $(Mustache.render(tpl,{label:'{{label}}', slug:'{{slug}}', id:'{{id}}',parent:r.content}));
				 	input.append(list);
				 	list.removeClass('hidden');

				 	dictionary_table_refresh(list);

					//Save
					input.on('click','thead .btn-success',function(){
						var line = $(this).closest('tr');
						var data = {
							action:   inputData.saveAction ? inputData.saveAction : 'core_dictionary_table_save',
							label: line.find('input.list-label').val(),
							id: list.attr('data-id'),
							list: list.attr('data-dictionary')
						};
						if(list.find('input.list-slug')) data.slug = $('input.list-slug', list).val();

						$.action(data, function(r){
							list.attr('data-id', '');
							line.find('input').val('');
							$.message('success','Enregistré');
							dictionary_table_refresh(list);
						});
					});

					//Suppression
					input.on('click','tbody tr .btn-danger',function(){
						if(!confirm('Êtes-vous sûr de vouloir supprimer cet élément de liste ?')) return;
						var line = $(this).closest('tr');
						$.action({
							action:  inputData.deleteAction ? inputData.deleteAction : 'core_dictionary_delete',
							id: line.attr('data-id')
						},function(r){
							line.remove();
							line.closest('.edit-field').val('');
							$.message('info', 'Élément de liste supprimé');
						});
					});

					//Édition
					input.on('click','tbody tr .btn-edit',function(){
						var line = $(this).closest('tr');
						$.action({
							action:  inputData.editAction ? inputData.editAction : 'core_dictionary_edit',
							id: line.attr('data-id')
						},function(r){
							list.find('input.list-label').val(r.label);
							if(list.find('input.list-slug')) $('input.list-slug', list).val(r.slug);
							list.attr('data-id',r.id);
						});
					});
				});
			break;
			//Permet aux plugins d'ajouter leurs composants
			//via la fonction init_components_nomcomposant(input);
			default:
				var type = input.attr('data-type').replace(/[^a-z0-9]/i,'_');
				if(window['init_components_'+type] !=null) window['init_components_'+type](input);
			break;
		}
	});

	init_tooltips();
}


/** TOOLTIPS **/
function init_tooltips(selector){
	//Clean des tooltip qui restent en suspend sans hover de la souris
	$('div.tooltip[role="tooltip"]').tooltip('dispose');

	var selected = selector ? $('[data-tooltip]',selector) : '[data-tooltip]';
	$(selected).each(function(){
		var element = $(this);
		var options =  {html:true};

		if(element.attr('data-placement')) options.placement = element.attr('data-placement');

		element.tooltip('dispose');
		element.tooltip(options);
	});
}