// Avoid `console` errors in browsers that lack a console.
(function() {
	var method;
	var noop = function () {};
	var methods = [
		'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error',
		'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log',
		'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd',
		'timeline', 'timelineEnd', 'timeStamp', 'trace', 'warn'
	];
	var length = methods.length;
	var console = (window.console = window.console || {});

	while (length--) {
		method = methods[length];

		// Only stub undefined methods.
		if (!console[method]) {
			console[method] = noop;
		}
	}
}());

//Affiche un message 'message' de type 'type' pendant 'timeout' secondes and
$.message = function (type,message,timeout,addClass){
	message = message.replace(/<\/?script|iframe|object[^>]*>/gim,'');
	var types = {
		error: 'ERREUR',
		warning: 'ATTENTION',
		info: 'INFORMATION',
	}
	var data = {
		type: type,
		content: message,
		timeout: timeout,
		progress: true,
		addClass:addClass
	};
	if(types[type]) data.title = types[type];
	$.toast(data);
}

//appelle un hook js
$.callHook = function (hook,args){
	if(!window.hooks) window.hooks = {};
	if(!window.hooks[hook]) window.hooks[hook] = [];
	for(var k in window.hooks[hook]){
		var method =  window.hooks[hook][k];
		method(args);
	}
}
//Définis un hook js
$.addHook = function (hook,callback){
	if(!window.hooks) window.hooks = {};
	if(!window.hooks[hook]) window.hooks[hook] = [];
	window.hooks[hook].push(callback);
}


//Permet les notifications types toast sans dépendance de librairie/css/html
$.toast = function (options) {
	var o = $.extend({
		title: null,
		content: '',
		type: 'info',
		timeout: 3000,
		addClass: ''
	}, options);

	var toastFunctions = {
		hideToast: function(toast, timeout){
			setTimeout(function(){
				toast.addClass('hidden');
				setTimeout(function(){
					toast.remove();
				},100);
			}, timeout);
		},
		updateProgress: function(){
			var percentage = ((progress.hideEta - (new Date().getTime())) / progress.maxHideTime) * 100;
			progressBar.width(percentage + '%');
		}
	};
	var progress = {
        toastInterval: null,
        hideEta: null,
        maxHideTime: null
    };

	if($('.toastContainer').length == 0) $('body').append('<div class="toastContainer"></div>');
	var popupContainer = $('.toastContainer');
	var popup = $('<div class="noPrint toast toast-'+o.type+' '+o.addClass+' media hidden"><i class="toastIcon align-self-start fa"></i><div class="toastContent media-body"><div class="toastTitle"><span class="m-auto d-inline-block"></span><i class="toastRemove fas fa-times hidden" onclick="var toast = $(this).closest(\'div.toast.media\'); toast.addClass(\'hidden\');setTimeout(function(){toast.remove();},100);";></i></div><div class="toastMessage"></div></div></div>');

	popupContainer.append(popup);

	if(o.title) $('.toastTitle > span', popup).html(o.title);
	$('.toastMessage', popup).html(o.content);

	//Apparition du toast
	setTimeout(function(){ popup.removeClass('hidden'); }, 50);

    var progressBar = $('<div class="toastProgress"></div>');
	clearTimeout(progress.toastInterval);

	//Cache le toast en fonction d'un timeout ou non
	if(o.timeout > 0) {
		if(o.progress) {
			toastInterval = setTimeout(function(){toastFunctions.hideToast(popup);}, o.timeout);
			progress.maxHideTime = parseFloat(o.timeout);
			progress.hideEta = new Date().getTime() + progress.maxHideTime;
			popup.append(progressBar);
			progress.toastInterval = setInterval(toastFunctions.updateProgress, 10);
		}

		//Au terme du timeout, on cache le toast
		toastFunctions.hideToast(popup, o.timeout);
	} else {
		popup.find('.toastRemove').removeClass('hidden');
	}
}

//Permet l'utilisation du json dans les local storage
$.storage = function(key,value){
	var json = localStorage.getItem(key);
	json = !json ? {} : JSON.parse(json);
	if(!value) return json;
	localStorage.setItem(key,JSON.stringify(value));
}

$.page = function(element){
	var path = window.location.pathname.split('/') ;
	path = path[path.length-1];
	path = path.replace('.php','');

	return path;
}

$.getForm = function(element){
	return $(element).getForm();
}

$.setForm = function(element,data){
	var o = {};
	var obj = $(element);
	$('input,select,textarea',obj).each(function(i,element){
		var input =  $(element);
		var id = element.id!=null && element.id!="" ? element.id : '';
		if(!id && input.attr('data-id')) id =  input.attr('data-id');

		if(!id || data[id]==null) return;

		if(input.attr("type")=='checkbox' || input.attr("type")=='radio'){
			input.prop("checked",data[id]==1 || data[id]=='true' ?true:false);
		} else {
			input.val(data[id]);
		}

	});
	return o;
}

//Gestion des tableaux pour le formData
$.form_data =  function(formData, key,values){
	var hasFile = false;
	if(values instanceof File) hasFile = true;

    if(typeof values == 'object' && !(values instanceof File)){
        for(subkey in values)
			if($.form_data(formData,key + '[' + subkey + ']',values[subkey])) hasFile = true;
    }else{
        formData.append(key, values);
    }
    return hasFile;
}

$.action = function(data,success,error,progress) {
	var formData = new FormData();
	var defaultSuccess = data.defaultSuccess != undefined ? data.defaultSuccess : true;
	var async = data.async != undefined  ? data.async  : true ;
	// if(data.defaultSuccess !== undefined) delete data.defaultSuccess;

	//hasFile determine si un fichier uploadé se trouve dans le tableau des data
	// Si c'est le cas, il envois les données en multipart, sinon il les envoie en json classique.
	var hasFile = false;
	$.each(data, function(key, value){
		if($.form_data(formData,key, value) == true) hasFile = true;
	});

	var request = {
		url : 'action.php',
		method : 'POST',
		async :async,
		success: function(response){
			$('.btn-preloader').removeClass('btn-preloader');

			//Debug des erreur php non catchées
			if(window.location.host=='127.0.0.1'){
				var debugRegex = /Erreur : <\/strong><span>(.*)-  <small style="opacity:0.5;">(.*\.php) L([0-9]*)/gm;
				while ((m = debugRegex.exec(response)) !== null) {
				    if (m.index === debugRegex.lastIndex) debugRegex.lastIndex++;
				    var path = m[2].split('/');
				    path = path.slice(-3).join('/');

				    var filePath = m[2].replace(/\//ig,'\\');
				    var link = 'w2d://'+btoa(JSON.stringify({action:'alias',alias:'sublime',arguments:filePath+'":'+m[3]}));
				    var html = '<a style="color:inherit;text-decoration:none;" href="'+link+'">'+path+'<br><strong>L'+m[3]+'</strong><br>'+m[1]+'<br/><small>('+data.action+')</small></a>';
					$.message('warning',html,0);
				}
			}

			if(response && !response.error){
				if(success!=null)success(response);
			} else {
				if(response.errorCode && response.errorCode=='401') core_logout();
				var errorMessage = '';

				if(response.file){
					var filePath = response.file.replace(/\//ig,'\\');
				    var link = 'w2d://'+btoa(JSON.stringify({action:'alias',alias:'sublime',arguments:filePath+'":'+response.line}));
				    errorMessage += '<a style="color:inherit;text-decoration:none;" href="'+link+'">'+response.file.replace('/var/www/html/','')+'<br><strong>L'+response.line+'</strong></a>';
				}

				errorMessage += "<br>"+response.error;

				if(response.trace) errorMessage += "<i class='fab fa-stack-overflow d-block pointer m-1' title=\"Voir la trace complête\" onclick=\"$(this).next('div').toggleClass('hidden')\"></i><div class='hidden' style='font-size:10px;max-height:200px;overflow:auto;text-align:left;'>"+response.trace+"</div>";
				if(defaultSuccess) $.message('error',errorMessage,0);
				if(error!=null) error(response);
			}
		},
		error : function(response){
			$('.btn-preloader').removeClass('btn-preloader');
			if(response.status == 200 && $.localhost() ){
				$('body').append('<div class="debugFrame" style="box-shadow: 0px 0px 10px rgba(0,0,0,0.5);z-index: 30001;padding: 5px;position: fixed;left: 0;top: 0;width: 40%;min-height: 100%;background: rgba(242,245,255, 0.9);overflow-y: auto;height: 100%; word-break: break-word;"><h5 class="text-center">ACTION DEBUG</h5> <i style="position:absolute; right:10px; top:10px;font-size:1.5em;" onclick="$(this).parent().remove()" class="fas fa-times pointer"></i>'+response.responseText+'</div>');
			} else {
				if(response.readyState == 0 && error==null) return;
				if(error!=null){
					if(response.errorCode && response.errorCode=='401') core_logout();
					error(response);
				}else{
					$.message('error','Erreur indefinie, merci de contacter un administrateur',0);
				}
			}
		},
		xhr: function() {
			var xhr = new window.XMLHttpRequest();
			if(data.downloadResponse) xhr.responseType = 'blob';
			xhr.upload.addEventListener("progress", function(evt){
				if (evt.lengthComputable) {
				var percentComplete = (evt.loaded / evt.total) * 100;
				percentComplete = Math.round(percentComplete * 100) / 100;
				if(progress) progress(percentComplete,'upload');
				}
			}, false);

			xhr.addEventListener("progress", function(evt){
				if (evt.lengthComputable) {
				var percentComplete = evt.loaded / evt.total;
				if(progress) progress(percentComplete,'download');
				}
			}, false);
			xhr.addEventListener('readystatechange', function(e) {
				if(xhr.readyState == 4  && xhr.status == 200) {
					if(data.downloadResponse){
						var disposition = xhr.getResponseHeader('content-disposition');
						var matches = /"([^"]*)"/.exec(disposition);
        				var filename = (matches != null && matches[1] ? matches[1] : 'file');
				        var blob = new Blob([xhr.response], { type: xhr.response.type });
				        var link = document.createElement('a');
				        link.href = window.URL.createObjectURL(blob);
				        link.download = filename;
				        $('body').append(link);
				        link.click();
				        link.remove();
				        window.URL.revokeObjectURL(link);
			        }
				}
			});
			return xhr;
		}
	};
	if(!hasFile){
		request.data = data;
	}else{
		request.data = formData;
		request.processData = false;
		request.contentType = false;
	}
	$.ajax(request);
}

$.localhost = function(){
	return (document.location.hostname=='127.0.0.1' || document.location.hostname=='localhost');
}


$.hashData = function(name){
	var page = window.location.hash.substring(1);
	page += "&"+window.location.search.substring(1);

	data = {};
	if(page!='' && page!= null){
		options = page.split('&');
		var data = {};
		for(var key in options){
			infos = options[key].split('=');
			data[infos[0]] = infos[1];
		}
	}
	if(name == null) return data;
	if(typeof name === "object"){
		data = name;
		hashstring = '';
		for(var key in data)
			hashstring+= "&"+key+"="+data[key];
		hashstring = hashstring.substring(1);
		window.location.hash = hashstring;
		return;
	}

	return typeof data[name] == "undefined" ? '':data[name];
}


$.urlParam = function (name,value,forceValue) {
	var parameters = window.location.href.match(/[\\?&]([^&#]*)=([^&#]*)/g);
	var data = {};
	for (var key in parameters) {
		var couple = parameters[key].substring(1, parameters[key].length).split('=');
		data[couple[0]] = couple[1];
	}
	if(name == null) return data;
	if (value == null && !forceValue)
		return data[name] ? data[name] : null;
	if (value !== false) data[name] = ''+value;
	var url = '?';
	for (var key in data) {

		if (value == false  && !forceValue && key == name) continue;
		url += key + '=' + data[key]+'&';
	}
	window.history.pushState('', document.title, url.substring(0, url.length-1));
}


$.fn.extend({
	fromJson : function(data){
	   $.setForm(this,data);
	   return $(this);
	},
	toJson : function(){
	   return $.getForm(this);
	},
	hasAttr : function(attr){
		if(this.length==0) return false;
		return this.get(0).hasAttribute(attr)
	},
	fitText: function(ratio, options) {
	 	var compressor = ratio || 1,
	 	    settings = $.extend({
	 	      'minFontSize' : Number.NEGATIVE_INFINITY,
	 	      'maxFontSize' : Number.POSITIVE_INFINITY
	      }, options);

	 	return this.each(function(){
		    var element = $(this);
			var resizer = function() {
				element.css('font-size', Math.max(Math.min(element.width() / (compressor*10), parseFloat(settings.maxFontSize)), parseFloat(settings.minFontSize)));
			};
		    resizer();

	 		$(window).on('resize.fittext orientationchange.fittext', resizer);
	 	});
	},
	getForm: function(){
		var o = {};
		var obj = $(this);
		for(var key in obj.data()){
			if(key!="action" &&  key != "id") continue;
			o[key] = obj.attr('data-'+key);
		}

		$('input,select,textarea',obj).each(function(i,element){
			var element = $(element);
			var id = element.attr('id');

			if(!id) id = element.attr('data-id');
			if(!id && element.attr("type")=='radio') id = element.attr('name');
			if(!id) return;

			if(element.attr("type")=='checkbox'){
				o[id] = (element.is(':checked')?1:0);

			}else if(element.attr("type")=='radio' && element.is(':checked')){
				o[id] = element.val();

			} else if(element.attr("type")=='file'){
				if(!element[0].files.length) return;

				if(element[0].files.length == 1){
					o[id] = element[0].files[0];
				} else {
					//Attention, le passage de multiples fichiers
					//entraine une modification de la structure du
					//tableau $_FILES dans le php (voir fonction normalize_php_files())
					o[id] = {};
					for(var i=0; i<element[0].files.length; ++i)
						o[id][i] = element[0].files[i];
				}
			} else {
				//permet le set d emultiples valeurs sur un seul input
				if(element.attr('data-multiple-values') == 'true'){
					try{
						o[id] = element.val()!= '' ? JSON.parse(element.val()) : [];
					}catch(e){
						o[id] = [];
					}
				}else{
					o[id] = element.val();
				}
			}

		});
		return o;
	},

	upload: function (options) {
		//var options = $.extend(defaults, options);
		return this.each(function () {
			var o = options;

			var droppedFiles = false;
			var div = $(this);
			var data = div.data();
			data.html =!div.attr('data-label') ? '': div.attr('data-label');
			data.label = data.html;
			div.html('');
			var o = $.extend(o, data);

			if(o.readonly == false){
				var form = $('<form class="box" method="post" action="' + o.action + '" enctype="multipart/form-data"><input type="file" name="'+(o.fileName?o.fileName:div.attr('id'))+'[]"  multiple /></form>');
				div.append(form);

				var input = form.find('input[type="file"]');
				var zone = $('<div class="pointer text-center">' + o.label + '</div>');
				div.append(zone);

				//test if dnd 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 elements styles
				input.attr('style', "width: 0.1px;height: 0.1px;opacity: 0;overflow: hidden; position: absolute;z-index: -1");

				//set events
				zone.on('click', function (e) {
					   form.find('input[type="file"]').trigger('click');
					   e.preventDefault();
					   e.stopPropagation();
					})
					.on('drag dragstart dragend dragover dragenter dragleave drop', function (e) {
						e.preventDefault();
						e.stopPropagation();
					})
					.on('dragover dragenter', function () {
						if (o.hover) form.addClass(o.hover);
					})
					.on('dragleave dragend drop', function () {
						if (o.hover) form.removeClass(o.hover);
					})
					.on('drop', function (e) {
						droppedFiles = e.originalEvent.dataTransfer.files;

						form.trigger('submit');
					});

				input.on('change', function (e) {
					form.trigger('submit');
				});

				form.on('submit', function (e) {
					e.preventDefault();
					if (o.start){
						var result = o.start();
						if(result == -1) return;
					}
					if (dragAndDropEnabled) {
						var ajaxData = new FormData();

						if (droppedFiles) {
							$.each(droppedFiles, function (i, file) {
								var ext = file.name.split('.');
								ext = ext.pop();

								if(o.allowed && $.inArray(ext.toLowerCase(),o.allowed.split(','))===-1){
									$.message('error','Extension fichier '+ext+' non permise (autorisé:'+o.allowed+')',0);
									isProcessing = false;
									return;
								}
								if(o.size && file.size > o.size){
									$.message('error','Taille fichier '+file.size+' octets trop grande (autorisé:'+o.size+' octets)',0);
									isProcessing = false;
									return;
								}
								ajaxData.append(input.attr('name'), file);
							});
						}else{
							ajaxData = new FormData(form.get(0));

							for(var key in $('input',form).get(0).files){
								var file = $('input',form).get(0).files[key];
								if(file.name==null || typeof file !='object') continue;
								var ext = file.name.split('.');

								ext = ext.pop();

								if(o.allowed && $.inArray(ext.toLowerCase(),o.allowed.split(','))===-1){
									$.message('error','Extension fichier '+ext+' non permise (autorisé:'+o.allowed+')',0);
									$('input',form).val();
									isProcessing = false;
									return;
								}
								if(o.size && file.size > o.size){
									$.message('error','Taille fichier '+file.size+' octets trop grande (autorisé:'+o.size+' octets)',0);
									$('input',form).val();
									isProcessing = false;
									return;
								}
							}
						}

						if (o.addData){
							var addionnalData = o.addData();
							for(var k in addionnalData)
								ajaxData.append(k, addionnalData[k]);
						}
						droppedFiles = null;

						$.ajax({
							url: form.attr('action'),
							type: form.attr('method'),
							data: ajaxData,
							dataType: 'json',
							cache: false,
							contentType: false,
							processData: false,
							complete: function (data) {
								if (o.complete) o.complete(data.responseJSON);
							},
							success: function (data) {
								if (o.success) o.success(data);
							},
							error: function (data) {
								if (o.error) o.error(data);
							}
						});
					} else {
						var iframeName = 'uploadiframe' + new Date().getTime();
						iframe = $('<iframe name="' + iframeName + '" style="display: none;"></iframe>');

						$('body').append(iframe);
						form.attr('target', iframeName);

						iframe.one('load', function () {
							var data = JSON.parse(iframe.contents().find('body').text());
							if (!data.success) alert(data.error);
							form.removeAttr('target');
							iframe.remove();
							 if (o.complete) o.complete();
						});
					}
				});
			}
		});
	},

	shiftCheckboxes: function() {
        var instance = this;

        this.on('click', function(e) {
            var checkbox = $(e.target);
            if(e.shiftKey && instance.last) {
                var from = instance.index(instance.last);
                var to = instance.index(checkbox);

                instance.slice(Math.min(from, to), Math.max(from, to) + 1)
                    .filter(':not(:disabled)')
                    .prop('checked', checkbox.prop('checked'))
                    .trigger('change');
            }
            instance.last = checkbox;
        });
    },

    clear: function(){
    	return this.each(function() {
    		var obj = $(this);
    		obj.find('input,textarea').not(':input[type=checkbox], :input[type=radio]').val('')
    		obj.find('input').prop('checked',false).prop('selected',false);
    		obj.find('select').prop('selectedIndex',0);

    		obj.find('*[contenteditable="true"]').text('');
    	});
    },

    //Rends le formulaire et ses inputs en lecture seule uniquement ou pas en fonction du booleen donné
    readonly: function(enableReadOnly){
    	return this.each(function() {
    		var obj = $(this);
    		if(enableReadOnly){
	    		obj.find('input,textarea,select').attr('readonly','readonly');
	    		obj.find('textarea[data-type="wysiwyg"]').trumbowyg('disable');

	    		obj.find('input[data-type],select[data-type],textarea[data-type]').each(function(){
	    			var input = $(this);
	    			var component = input.data("data-component");
	    			if(input.attr('data-type') == 'wysiwyg'){
	    				component = input.data('trumbowyg').$box;
	    				input.data('trumbowyg').$ed.attr('contenteditable',false);
	    			}
	    			if(component) component.attr('readonly','readonly');
	    		});


	    	}else{
	    		obj.find('input,textarea,select').removeAttr('readonly');
	    		obj.find('textarea[data-type="wysiwyg"]').trumbowyg('enable');
	    		obj.find('input[data-type],select[data-type],textarea[data-type]').each(function(){
	    			var input = $(this);
	    			var component = input.data("data-component");
	    			if(input.attr('data-type') == 'wysiwyg'){
	    				component = input.data('trumbowyg').$box;
	    				input.data('trumbowyg').$ed.attr('contenteditable',true);
	    			}
	    			if(component) component.removeAttr('readonly');
	    		});
	    	}
    	});
    },

    //@TODO:
    //	- Voir pour fusionner avec $.fill
    //	- Voir pour respecter la sémantique (addLine supprime les anciennes lignes...)
	addLine: function (rows){
		return this.each(function() {

			var obj = $(this);
			var model = null;
			var container = null;

			if(obj.prop("tagName") == 'UL'){
				container = obj;
				model = container.find('li:first-child');
				container.find('li:visible').remove();
			}else if(obj.prop("tagName") == 'TABLE'){
				container = obj.find('tbody');
				model = container.find('tr:first-child');
				container.find('tr:visible').remove();
			}else{
				container = obj;
				childName = container.children().get(0).nodeName;
				model = container.find(childName+':first-child');
				container.find(childName+':visible:not(.nofill)').remove();
			}
			var tpl = model.get(0).outerHTML;

			//fix jquery backslahes break
			tpl = tpl.replace(/{{##/g,'{{/').replace(/{{\/(.*)}}=""/g,'{{/$1}}');

			//fix images url not found on template
			tpl = tpl.replace(/(<img\s[^>]*\s)(data-src)/g,'$1src');

			for(var key in rows){
				var line = $(Mustache.render(tpl,rows[key]));
				container.append(line);
				line.removeClass('hidden');
			}


		});
	},

	//Retourne un node jquery visible à partir d'un
	//modèle de template invisible + données mustache
	template: function(data){
		return $(Mustache.render($(this).get(0).outerHTML,data)).removeClass('hidden');
	},
	//Permet le resize d'un panel vertical
	panelResize : function(options){
		var obj = $(this);
		var o = $.extend({
			handlerClass : 'panel-resize-handler',
			hoverClass : 'panel-resize-handler-hover',
			handlerWidth : 10,
			direction : 'right',
			minWidth : 30,
			maxWidth : 700,
			update : function(data){}
		}, options);

		var handlerId = 'handler-'+generate_uuid(10);
		var handler = $('<div id="'+handlerId+'" class="'+o.handlerClass+'" style="cursor:col-resize;opacity:0.1;position:fixed;z-index:1000;height:'+obj.outerHeight()+'px;width:'+o.handlerWidth+'px;"></div>');
		var timeout = null;
		var left;
		if(o.direction == 'right'){
			left = obj.offset().left + obj.outerWidth() - (o.handlerWidth/2);
		}else if (o.direction == 'left'){
			left = obj.offset().left - (o.handlerWidth/2);
		}

		handler.css({
			left : left,
			top : obj.offset().top
		});

		handler.hover(function(){
			clearTimeout(timeout);
			handler.addClass(o.hoverClass);
		},function(){
			timeout = setTimeout(function(){
				handler.removeClass(o.hoverClass);
			},300);
		});

		if(!window.handlerActive) window.handlerActive = {};
		if(!window.handlerPosition) window.handlerPosition = {};

		handler.mousedown(function(){
			window.handlerPosition[handlerId] = handler.offset().left;
			window.handlerPanelWidth = obj.outerWidth();
			window.handlerActive[handlerId] = true;
		});

		$(document).mouseup(function(){
			window.handlerActive[handlerId] = false;
			var left;
			if(o.direction == 'right'){
				left = obj.offset().left + obj.outerWidth() - (o.handlerWidth/2);
			}else if (o.direction == 'left'){
				left = obj.offset().left-(o.handlerWidth/2);
			}

			handler.css({left : left});

			o.update({
				element : obj,
				handler : handler,
				width : obj.outerWidth()
			});
		});

		$(document).mousemove(function( event ) {
		 	if(!window.handlerActive[handlerId])  return;

		 	var newPosition = (event.pageX-2);
		 	var negativeWidth = window.handlerPosition[handlerId] - newPosition;
		 	var newWidth = window.handlerPanelWidth- (negativeWidth * (o.direction== 'right'?1:-1) );
		 	if(newWidth < o.minWidth || newWidth <1 || newWidth > o.maxWidth ){
		 		return;
		 	}
		 	handler.css({
		 		left : newPosition+'px'
		 	});

		 	obj.css({
		 		width : newWidth+'px',
		 		maxWidth : newWidth+'px'
		 	});
		});
		obj.append(handler);
		return handlerId;
	},

	fill: function (option,callback,progress){
		return this.each(function() {
			var obj = $(this);
			var model = null;
			var container = null;
			option = $.extend({
				differential : false,
				showing : function(item,key,data){
					//permet la personnalisation de l'apparition des lignes (removeClass('hidden') par defaut)
					item.removeClass('hidden');
				}
			},option);

			var preloader = null;
			if(option.preloader){
				var preloader = $(option.preloader);
				preloader.css('position','absolute');
				preloader.css('left',(obj.offset().left+obj.width()/2)+'px');
				preloader.css('top',(obj.offset().top+30)+'px');
				$('body').append(preloader);
			}

			var paginationPreferenceUid = ($.urlParam('module')?$.urlParam('module'):'main')+'-'+($.urlParam('page')?$.urlParam('page') :'main')+'-'+obj.attr('id');
			var paginationPreference = $.storage('pagination');

			if(paginationPreference[paginationPreferenceUid]){
				//si une valeur est présente dans le localstorage on la prend
				option.itemPerPage = paginationPreference[paginationPreferenceUid];
			}else{
				//si pas de valeur en local storage mais une valeur par defaut, on la prend
				var paginationPreference = $('[data-type="pagination-preference"][data-table="#'+obj.attr('id')+'"]');
				if(paginationPreference.length >0) option.itemPerPage = paginationPreference.attr('data-value');
			}

			//Permet l'affichage d'un élément HTML si aucun résultat ne ressort
			if(option.empty) $(option.empty+':not(.hidden)').remove();

			if(obj.prop("tagName") == 'UL'){
				container = obj;
				model = container.find('li:first-child');
				if(!option.export && !option.differential) container.children('li:not(.hidden)').remove();
			} else if(obj.prop("tagName") == 'TABLE'){
				container = $('>tbody',obj);
				model = container.find('tr:first-child');
				if(!option.export && !option.differential) container.children('tr:not(.hidden)').remove();

				/** début colonnes dynamiques **/
				var slug = obj.attr('data-slug');
				if(slug){
					//Refresh du dropdown de colonnes
					var dynamic_table_refresh_menu = function(table){
						var picker = $('.dynamic-table-picker .dropdown-menu',table);
						picker.html('');

						var columns = dynamic_table_columns(table,true);
						for(var i in columns.deleted){
							var th = columns.deleted[i];
							if(!th.hasClass('hidden')) continue;
							picker.append('<div class="dropdown-item px-2 pointer" data-slug="'+th.attr('data-available')+'" title="Ajouter la colonne"><i class="text-muted fas fa-sm fa-angle-right mr-1"></i>'+th.text()+'</div>');
						};
						if(!picker.html().length) picker.append('<div class="dropdown-item px-2 text-muted user-select-none">Aucune colonne disponible</div>');
					};
					//Enregistrement de l'affichage des colonnes
					var dynamic_table_save = function(table,callback){
						var columns = dynamic_table_columns(table);
						if($('#loginHeader').attr('data-connected') == ''){
							var localColumns = $.storage('component.tablesearch.columns');
							localColumns[table.attr('data-slug')] = columns.added;
							$.storage('component.tablesearch.columns',localColumns);
							if(callback) callback();
						}else{
							$.action({
								action : 'core_table_colum_save',
								slug : table.attr('data-slug'),
								added : columns.added
							},function(){
								if(callback) callback();
							});
						}
					}
					//Récupération de l'état des colonnes affichées/masquées
					var dynamic_table_columns = function(table,getElement){
						table = $(table);
						var columns = {added:[],deleted:[]};
						$('th[data-available]',table).each(function(){
							var th = $(this);
							var thSlug = th.attr('data-available');
							if(th.hasClass('hidden')) {
								columns.deleted.push(!getElement ? thSlug : th);
							}else{
								columns.added.push(!getElement ? thSlug : th);
							}
						});
						return columns;
					}

					option.columns = dynamic_table_columns(obj);
					var plusButton = $('.dynamic-table-picker',obj);
					if(!plusButton.length){
						plusButton = $('<div class="dropdown dynamic-table-picker">\
							<div data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="text-right">\
							<i class="fas fa-plus text-success dynamic-table-add" title="Ajouter une colonne" data-tooltip></i></div>\
							<div class="dropdown-menu dropdown-menu-right"></div></div>');

						$('thead tr:eq(0) th:last-child',obj).append(plusButton);
						$('thead tr:eq(0) th[data-available]',obj).append('<i class="fas fa-times text-danger dynamic-table-delete ml-2" title="Supprimer la colonne" data-tooltip></i>');

						//Gestion events
						$('.dynamic-table-delete',obj).click(function(){
							element = $(this).closest('th');
							var th = $('>thead th[data-available="'+element.attr('data-available')+'"]',obj);
							th.addClass('hidden');
							$('> tbody > tr',obj).each(function(){
								$('> td:eq('+th.index()+')',this).addClass('hidden');
							});
							dynamic_table_refresh_menu(obj);
							dynamic_table_save(obj);
						});
						$(plusButton).off('click').on('click','.dropdown-item[data-slug]',function(){
							element = $(this);
							var th = $('thead th[data-available="'+element.attr('data-slug')+'"]',obj);
							th.removeClass('hidden');
							$('tbody tr',obj).each(function(){
								$('td:eq('+th.index()+')',this).removeClass('hidden');
							});
							dynamic_table_refresh_menu(obj);
							dynamic_table_save(obj,function(){
								if(obj.attr('data-column-search')) window[obj.attr('data-column-search')]();
							});
						});
					}

					var addDynamicColumns = function(added){
						$('th[data-available]',obj).each(function(){
				  			var th = $(this);
				  			var td = $('tbody tr td:eq('+th.index()+')',obj);
							if(added.indexOf(th.attr('data-available')) !== -1) {
								th.removeClass('hidden');
								td.removeClass('hidden');
								return;
							}
				  			th.addClass('hidden');
				  			td.addClass('hidden');
			  			});
			  			dynamic_table_refresh_menu(obj);
					}

					if($('#loginHeader').attr('data-connected') == ''){
						var localColumns = $.storage('component.tablesearch.columns');
						var added = localColumns[slug] ? localColumns[slug] : [];
						addDynamicColumns(added);
					}else{
						$.action({
							action : 'core_table_colum_search',
							async : false,
						},function(preferences){
							plusButton.addClass('btn-preloader');
							var added = preferences[slug] ? preferences[slug] : [];
							addDynamicColumns(added);
						});
					}
				}
				/** fin colonnes dynamiques **/

			} else if(obj.prop("tagName") == "SELECT"){
				container = obj;
				model = container.find('option[value*="{{"]');
				if(model.length==0) model = $('<option class="hidden" value="{{value}}">{{label}}</option>');
				if(!option.export && !option.differential) container.find('option:not([value*="{{"])').remove();
			} else{
				container = obj;
				childName = container.children().get(0).nodeName;
				model = container.find(childName+':first-child');
				if(!option.export && !option.differential) container.find(childName+':visible:not(.nofill)').remove();
			}
			var tpl = model.get(0).outerHTML;

			//fix jquery backslashes break
			tpl = tpl.replace(/{{##/g,'{{/').replace(/{{\/(.*)}}=""/g,'{{/$1}}');

			//fix images url not found on template
			tpl = tpl.replace(/(<img\s[^>]*\s?)(data-src)/g,'$1src');

			var pagination =
				obj.attr('data-pagination')!=null && $(obj.attr('data-pagination')).length ?
					$(obj.attr('data-pagination')) :
					(obj.nextAll('.pagination').length ?
						obj.nextAll('.pagination') :
						obj.parent().nextAll('.pagination')
					);

			if(pagination.length!=0) {
				var activePage = $('li.active', pagination);
				if($.urlParam('p')==null || $.urlParam('p') <= 0 || activePage.length > 0)
					option.page = activePage.attr('data-value');
				else
					option.page = parseInt($.urlParam('p') - 1);
			}
			tpl = tpl.replace(/(data-style)/g, 'style');

			if(option.export) option.downloadResponse = true;

			//on clone l'objet option pour ne transmettre que des datas utiles
			data = $.extend({},option);
			delete data.showing;
			delete data.templating;

			$.action(data,function(r){
				//On ne gere la pagination et l'affichage tableau que si on est pas en mode export
				if(!option.export){
					if(option.differential){
						//suppression des élements dom qui ne sont plus en db
						$('>[data-id]:visible',container).each(function(i,line){
							var line = $(line);

							toDelete = true;
							for(var key in r.rows){
								if(line.attr('data-id') == r.rows[key].id){
									toDelete = false;
									break;
								}
							}
							if(toDelete) line.remove();
						});
					}
					//var activeIds = [];
					for(var key in r.rows){

						var line;
						var data = r.rows[key];
						var lineTpl = tpl;
						if(option.templating) lineTpl = option.templating(data,line,tpl);

						if(!option.differential){
							line = $(Mustache.render(lineTpl,data));
							container.append(line);
							option.showing(line,key,data);
						}else{
							//activeIds.push(data.id);
							var existing = $('> [data-id="'+data.id+'"]',container);
							//existe en data et pas dans le dom : ajout
							if(existing.length == 0){
								line = $(Mustache.render(lineTpl,data));
								line.attr('data-update-tag',data.updated);

								if(key==0){
									container.append(line);
								}else{
									var previousIndex = key-1;
									var previous = $('>[data-id]:visible',container).eq(previousIndex);
									previous.after(line);
								}
								option.showing(line,key,data);
							}else{
								//existe en data et dans le dom et pas de modification : on passe au suivant
								if(existing.attr('data-update-tag') == data.updated){
									continue;
								//existe en data et dans le dom mais a été modifié  : on remplace
								}else{
									line = $(Mustache.render(lineTpl,data));
									line.attr('data-update-tag',data.updated);
									existing.after(line);
									existing.remove();
									option.showing(line,key,data);
								}
							}
						}
					}

					if(!r.rows || !r.rows.length && option.empty) obj.after($(option.empty).removeClass('hidden'));

					if(option.differential){
						var existingDom = [];
						for(var key in r.rows){
							var data = r.rows[key];
							var line = $('>[data-id="'+r.rows[key].id+'"]',container);

							var index = line.index() -1;
							if(key != index){
								var prevIndex = (key-1);
								if(prevIndex<0){
									container.prepend(line.detach());
								}else{
									var prevElement = $('>[data-id]:visible()',container).eq(prevIndex);
									prevElement.after(line.detach());
								}
							}
						}
					}

					if(r.pagination){
						$('.page-item-previous,.page-item-next').remove();
						r.pagination.pages = Math.ceil(r.pagination.pages);
						var previewNumber = pagination.attr('data-range') != null && pagination.attr('data-range').length ? parseInt(pagination.attr('data-range')) : 5;
						previewNumber = previewNumber < 2 ? 2 : previewNumber;
						//Possible values: simple, numbers, full
						var controls = pagination.attr('data-control') != null && pagination.attr('data-control').length ? pagination.attr('data-control') : "full";

						var template = pagination.find('li:not(:visible)');
						if(template.length>0 ){

							template = template.get(0).outerHTML;
							$('li:not(:eq(0))',pagination).remove();
							var current = parseInt(r.pagination.current);
							var previousPages = current-previewNumber;
							var nextPages = current+previewNumber;

							var dropDownTpl = '<div data-toggle="dropdown"><i class="fas fa-ellipsis-h pointer"></i></div><div class="dropdown-menu pagination-select">{{#choices}}<span class="dropdown-item">{{.}}</span>{{/choices}}</div>';

							for(i=0;i<r.pagination.pages;i++){
								var li = $(Mustache.render(template,{value:i,label:i+1}));

								if(
									(previousPages < i && i <  nextPages)
									|| i==0 || i==r.pagination.pages-1
								){
									li.removeClass('hidden');
									if(i==current) li.addClass('active');
								}else{
									if(i==1 || i==r.pagination.pages-2){
										var dotli = li.clone()
										var start = i==1 ?  2: current+previewNumber+1;
										var end = i==1 ? (current+1)-previewNumber : r.pagination.pages-1;

										var choices = [];
										for(u=start; u<end+1;u++)
											choices.push(u);
										rightDropDown = $(Mustache.render(dropDownTpl,{choices:choices}));

										dotli.removeClass('hidden')
										.attr('title','Voir les pages '+(start)+' à '+(end))
										.removeAttr('onclick')
										.css('position','relative')
										.addClass('page-link').html(rightDropDown);
										pagination.append(dotli);
									}
								}
								pagination.append(li);
							}
							if($('li.active',pagination).length) $.urlParam('p', parseInt($('li.active',pagination).attr('data-value')) + 1);

							if(current!=0){
								var prev = $('<li class="page-item page-item-previous"><span class="page-link" ><span><i class="fas fa-angle-left"></i></span></span></li>');
								pagination.prepend(prev);
								prev.click(function(e){
									if ($(e.target).closest('span.page-link').hasClass('disabled')) return;
									pagination.find('li[data-value="'+(current-1)+'"]').trigger('click');
								});
							}
							if(current<r.pagination.pages-1){
								var next = $('<li class="page-item page-item-next"><span class="page-link" ><span><i class="fas fa-angle-right"></i></span></span></li>');
								pagination.append(next);
								next.click(function(e){
									if ($(e.target).closest('span.page-link').hasClass('disabled')) return;
									pagination.find('li[data-value="'+(current+1)+'"]').trigger('click');
								});
							}

							//Affiche uniquement les flèches précédent / suivant de contrôle de la pagination
							if(controls == "simple") pagination.find('li[data-value]:not(.hidden)').addClass('hidden');
							//Affiche uniquement les n° de page de contrôle de la pagination
							if(controls == "numbers") pagination.find('li:not([data-value]):not(.hidden)').addClass('hidden');

							var visiblePageCount = pagination.find('.page-item:not(.hidden)').length;
							if(visiblePageCount>1){
								pagination.find('.page-item:not(.hidden):first .page-link').css('border-radius','0.25rem 0 0 0.25rem');
								pagination.find('.page-item:not(.hidden):last .page-link').css('border-radius','0 0.25rem 0.25rem 0');
							}else{
								pagination.find('.page-item:not(.hidden):first .page-link').css('border-radius','0.25rem 0.25rem 0.25rem 0.25rem');
							}

							$('.pagination-select span',pagination).click(function(){
								var selectedPage = (parseInt($(this).text())-1);
								pagination.find('li[data-value="'+selectedPage+'"]').trigger('click');
							});
						}
					}
				}
				if(preloader) preloader.remove();
				if(callback!=null)callback(r);
			},null,function(percent,type){
				if(progress) progress(percent,type);
			});
        });
	},

	enter: function (option){
        return this.each(function() {
			var obj = $(this);
			obj.keydown(function(event){
			if(event.keyCode == 13){
				option();
				return false;
			}
			});

        });
	},
	sortable_table: function (option){
		if(option=='get'){
			var obj = $(this);
			var response = {};
			obj.find('thead th[data-sortable]').each(function(i,th){
				var th = $(th);

				if(th.attr('data-sort') && th.attr('data-sort')!=""){
					response = {
						sort : th.attr('data-sort'),
						sortable : th.attr('data-sortable')
					};
				}
			});
			return response;
		}
        return this.each(function() {
			var obj = $(this);
			obj.find('thead th[data-sortable]').click(function(e){
				if($(e.target).hasClass('dynamic-table-delete')) return;
				var th = $(this);
				var data = th.data();
				if(!data.sort || data.sort==''){
					data.sort = 'asc'
				}else if(data.sort == 'asc'){
					data.sort ='desc'
				}else{
					data.sort = '';
				}
				obj.find('thead th').removeClass('sort-asc').removeClass('sort-desc').removeAttr('data-sort');
				if(data.sort!='') th.addClass('sort-'+data.sort);
				th.attr('data-sort',data.sort);
				if(option.onSort) option.onSort();
			});

		});
	},
	autocomplete: function(o){
		return this.each(function() {
			var obj = $(this);
			if(o == 'off') return obj.typeahead('destroy');
			obj.attr('autocomplete','new-password');
			var option = obj.data();
			option = $.extend(option,o);
			if(!option.delay) option.delay = 1;
			var timeout = null;
			obj.typeahead({
				items: (o.items) ? o.items : 5,
				minLength:  o.suggest ? 0 : (o.minLength ? o.minLength : 2) ,
				fitToElement: false,
				autoSelect : false,
				selectOnBlur : false,
				displayText : function(item){
					return (o.skin) ? o.skin(item,obj) : item.name || item;
				},
				source: function(keyword, response){
					if (timeout) clearTimeout(timeout) ;
					timeout = setTimeout(function() {
						if(o.onKeypress) o.onKeypress(obj);
						if(o.dynamicData) o.data = $.extend(o.data,o.dynamicData());
						if(o.data) o.data.limit = (o.items) ? o.items : 5;
						$.action({
							action: option.action,
							keyword: obj.val(),
							data: o.data
						},function(r){
							if(r.rows != null) response(r.rows);
						});
					},option.delay);
				},
				highlighter: function(item){
					if(o.highlight) {
						return o.highlight(item)
					}
					else {
						var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&')
					    return item.replace(new RegExp('('+query+')', 'ig'), function($1, match){
					        return '<strong>'+match+'</strong>';
					    });
					}
				},
				matcher: function(r){
					if(obj.val() == r.name && o.selectEqual!==false)
						if(o.onClick)
							o.onClick(r,obj);
					return '<div>'+r.name+'</div>';
				},
				afterSelect: function(item) {

					obj.data('selected',true);
					if(o.onClick) o.onClick(item,obj);
					obj.trigger("change");
				}
			}).blur(function(){
				if(o.onBlur) o.onBlur(obj);
			});

			if(o.suggest){
				obj.off("click").on("click", function () {
					if(typeof o.suggest === 'function' && !o.suggest()) return;
				    ev = $.Event("keydown");
				    ev.keyCode = ev.which = 40;
				    $(this).trigger(ev);
				    return true
				});
			}

			obj.data('typeahead').next =  function (event) {
	            var active = this.$menu.find('.active').removeClass('active');
	            var next = active.next();
	            if (!next.length) {
	                next = $(this.$menu.find($(this.options.item || this.theme.item).prop('tagName'))[0]);
	            }
	            while (next.hasClass('divider') || next.hasClass('dropdown-header')) {
	                next = next.next();
	            }
	            next.addClass('active');
	            var newVal = this.updater(next.data('value'));
	            this.$element.val(newVal.name || newVal.label || newVal);
        	};
        	obj.data('typeahead').prev =  function (event) {
				var active = this.$menu.find('.active').removeClass('active');
	            var prev = active.prev();
	            if (!prev.length){
	                prev = this.$menu.find($(this.options.item || this.theme.item).prop('tagName')).last();
	            }
	            while (prev.hasClass('divider') || prev.hasClass('dropdown-header')) {
	                prev = prev.prev();
	            }
	            prev.addClass('active');
	            var newVal = this.updater(prev.data('value'));
	            if (this.changeInputOnMove) this.$element.val(newVal.name || newVal.label || newVal);
        	};
		});
	},
	location: function (options){
		return this.each(function() {
			var obj = $(this);
			var o = $.extend({},options);

			obj.off('keyup').on('keyup', function(event) {
				if(!$(this).val().length) return;
			});

			obj.autocomplete({
				action: 'core_location_search',
				delay : 300,
				force: (o.force ? o.force : false),
				items: (o.items ? o.items : 8),
				data: {
					maxresults: (o.items ? o.items : 8),
					language: (o.language ? o.language : "fr"),
					country: (o.country ? o.country : ""),
				},
				skin : function(item){
					var html = '<small class="text-muted"><i class="fas fa-map-marker-alt d-inline-block mr-2 text-muted"></i>';
					var re = new RegExp(obj.val(),"gi");
					name = item.label.replace(re, function (x) {
					    return '<strong class="text-primary">'+x+'</strong>';
					});
					html += name+'</small>';

					return html;
				},
				highlight : function(item){
					return item;
				},
				onClick : function(selected,element){
					obj.val(selected.label);
					if(!is_empty_obj(selected.address)) {
						var infos = {
							number: selected.address.houseNumber ? selected.address.houseNumber : '',
							street: selected.address.street ? selected.address.street : '',
							district: selected.address.district ? selected.address.district : '',
							city: selected.address.city ? selected.address.city : '',
							county: selected.address.county ? selected.address.county : '',
							state: selected.address.state ? selected.address.state : '',
							country: selected.address.country ? selected.address.country : '',
							zip: selected.address.postalCode ? selected.address.postalCode : '',
						};
						infos.address = (infos.street && infos.street.length && infos.number && infos.number.length) ? infos.number+' '+infos.street : (infos.street ? infos.street : '');

						obj.removeData();
						obj.data(infos);

						if(o.select) o.select(infos);
						if(o.geocode) o.geocode(selected);
					}
				},
				onBlur : function(selected,element){

				},
				onCancel : function(element){
					obj.val('');
				}
			});
		});
	},
	colorInput: function (options) {
		return this.each(function () {
			var o = $.extend({
				choices :  [
				 	'#34495e','#9b59b6','#3498db','#2ecc71','#1abc9c',
				 	'#95a5a6','#ecf0f1','#e74c3c','#e67e22','#f1c40f',
				 	'#d35400','#55E6C1','#EAB543','#F8EFBA','#FD7272',
				 	'#3B3B98','#B33771','#6c5ce7','#0984e3','#00cec9',
				 	'#fd79a8','#d63031','#a29bfe','#55efc4'
			 	]
			},options);
			var input = $(this);
			input.hide();

			var component = input.data('data-component');
			if(component){
				//reload
			}else{
				var component = $('<div class="component-color d-flex form-control" title="Ouvrir la palette"><div class="component-color-thumb"></div><div class="component-color-picker hidden"></div><input type="color" class="component-color-palette"></div>');
				//load
				input.data('data-component',component);
				input.after(component);
				component.append(input.detach());
				init_tooltips(component);
			}

			var picker = $('.component-color-picker',component);
			var palette = $('.component-color-palette',component);
			var thumb = $('.component-color-thumb',component);
			thumb.css('backgroundColor',input.val());
			var html = '<ul>';
			for(var k in o.choices){
				html += '<li><div class="color-choice" data-color="'+o.choices[k]+'" style="background-color:'+o.choices[k]+'"></div></li>';
			}
			html += '<li><div class="other-color" title="Autre couleur"><i class="fas fa-palette"></i></div></li>';
			html += '</ul>';
			picker.html(html);
			$('.other-color',component).click( function(){
				palette.click();
			});
			palette.change(function(){
				input.val(palette.val()).change();
				picker.addClass('hidden');
			});
			input.change(function(){
				thumb.css('backgroundColor',input.val());
			});
			component.click(function(e){
				e.stopPropagation();
				if(input.is('[readonly]') || input.is('[disabled]')) return;
				if($(e.target).closest('.component-color-picker').length || $(e.target).closest('.component-color-palette').length) return;

				picker.removeClass('active');
				setTimeout(function(){

					picker.removeClass('hidden');
					setTimeout(function(){picker.addClass('active')},50);
				},150);
			});
			$('.color-choice',picker).click(function(e){
				e.stopPropagation();

				picker.removeClass('active');
				setTimeout(function(){picker.addClass('hidden')},200);
				input.val($(this).attr('data-color')).change();
			});
			$(document).click(function() {
				picker.removeClass('active');
				setTimeout(function(){picker.addClass('hidden')},200);
			});

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


		});
	},
	date: function (options){
		return this.each(function(){
			var obj = $(this);
			obj.on('paste', function(e){
				is_valid_date(e.originalEvent.clipboardData.getData('Text')) ? obj.val("") : e.preventDefault();
			});
			//Jours ouvrés
			var minDate = options.beginDate;
			var beforeShowDay = '';
			if(options.workdays){
				var minDate = new Date();
				var daysToAdd = [0, 0, 2, 2, 2, 2, 1];
				minDate.setDate(minDate.getDate()+parseInt(options.beginDate)+daysToAdd[minDate.getDay()]);
				beforeShowDay = $.datepicker.noWeekends;
			}

			obj.datepicker({
				dateFormat: options.dateFormat,
				dayNames: ["Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"],
				dayNamesMin: ["Di", "Lu", "Ma", "Me", "Je", "Ve", "Sa"],
				dayNamesShort: ["Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam"],
				monthNames: ["Janvier","Février","Mars","Avril","Mai","Juin","Juillet","Aout","Septembre","Octobre","Novembre","Décembre"],
				monthNamesShort: ["Jan","Fév","Mars","Avr","Mai","Juin","Juil","Aout","Sept","Oct","Nov","Déc"],
				firstDay: 1,
				minDate: minDate,
				beforeShowDay: beforeShowDay,
				maxDate: options.endDate,
				changeMonth: true,
				yearRange: "-100:+30",
				changeYear: true,
				onSelect: function(dateText, inst){
					obj.trigger("blur");
					obj.trigger("change");
				},
				beforeShow: function(){
					if(obj.is('[readonly]')) return false;
				}
			}).keypress(function(event){
				if(event.key=='/') event.preventDefault();
				var length = obj.val().length;
				if(length == 2 || length == 5) obj.val(obj.val()+'/');
			}).blur(function(event){
				obj.removeClass('border border-danger');
				if(obj.val()=='') return;
				var segments = obj.val().split('/');
				if(segments.length!=3) return;
				if(segments[0] > 31 || segments[1] > 12) obj.addClass('border border-danger');
			}).attr('maxlength','10');

			obj.attr('placeholder',options.placeholder);
			obj.attr('pattern',"^(\\d{2}(?:\\d{2})?)\/(\\d{2})\/(\\d{2}(?:\\d{2})?)$");
			obj.attr('title','Format '+options.dateFormat);
		});
	},
	phone: function (options){
		return this.each(function(){
			var obj = $(this);

			options = $.extend({
				regex: '^[0-9]{10}$',
				emptyField: false,
				spacing: true,
				typeOnly: '[0-9\+\s]',
				invalidClass: 'invalid-value',
			},options);

			if(options.typeOnly){
				obj.keydown(function(event){
					if((event.ctrlKey && ['v','a','c','x'].indexOf(event.key) !== -1) || ['Backspace','ArrowLeft','ArrowRight',' '].indexOf(event.key) !== -1) return true;

					var regexType = RegExp(options.typeOnly, 'g');
					return regexType.test(event.key);
				});
			}

			obj.blur(function(event){
				var value = obj.val()
					.trim()
					.replace(/\s/ig,'')
					.replace(/\+33/ig,'0');
				if(value=='') return obj.removeClass(options.invalidClass);;
				var regex = RegExp(options.regex, 'g');

				if(!regex.test(value)){
					obj.addClass(options.invalidClass);
					if(options.emptyField) value = '';
				} else {
					if(options.spacing) value = value.match(/.{1,2}/g).join(' ');
					obj.val(value);
					obj.removeClass(options.invalidClass);
				}
				if(obj.data('blur')) eval(obj.data('blur'));
			});
		});
	},
	hour: function (options){
		return this.each(function() {
			var obj = $(this);
			obj.on('paste', function(e){
				is_valid_hour(e.originalEvent.clipboardData.getData('Text')) ? obj.val("") : e.preventDefault();
			});

			obj.timepicker({
				scrollDefault: 'now',
				timeFormat: options.timeFormat,
				step: options.step,
				onSelect: function(timeText, inst) {
					obj.trigger("blur");
					obj.trigger("change");
				}
			}).keydown(function(e) {
				//Si c'est pas un chiffre ou :
				if(!input_number_control(e)) return false;

				//Si on efface et qu'on est après les deux points, on les supprime et le chiffre qui précède aussi
				if(e.key=='Backspace' && obj.val().substr(obj.val().length-1)==':'){
					e.preventDefault();
					obj.val(obj.val().substring(0, obj.val().length-2));

				//Premier digit avant les deux points supérieur à 2, on ajoute un 0 devant
				} else if((obj.val().length==0 || get_selected_text(obj).length == obj.val().length) && e.key>2) {
					e.preventDefault();
					obj.val('0'+e.key+':');

				//Gestion des deux points entre heures et minutes
				} else if(obj.val().length==1 && e.key.match(/[0-9:]/)) {
					e.preventDefault();
					obj.val(obj.val()+e.key+':');

				//Si le 1er digit après les deux points est supérieur à 5, on bloque
				} else if(obj.val().length==3 && e.key>5 && get_selected_text(obj).length<2) {
					e.preventDefault();
				}
			}).blur(function(event){
				obj.removeClass('border border-danger')
				if(obj.val()=='') return;
				var segments = obj.val().split(':');
				if(!segments[1]){
					obj.val(obj.val()+'00');
					segments[1] = '00';
				}
				if(segments.length!=2)return;
				if(segments[0] > 23 || segments[1] > 59) obj.addClass('border border-danger');
			}).attr('maxlength','5');

			obj.attr("placeholder",options.placeholder);
			obj.attr('title',"Format hh:mm");
		});
	},
	raiseNumber: function(from,to,time) {
		return this.each(function(){
			var obj = $(this);
			obj.text(from);

			time = time!=null ? time : 100;
			var ratio = (from<=to) ? to/time : from/time;

			var increment = Math.round(ratio)<1 ? 1 : Math.round(ratio);
			increment = (from<=to) ? increment : increment*-1;

			var updateCount = function(obj, increment){
				var count = parseInt(obj.text());
				if((from<=to && count<to) || (from>to && count>=to)) {
					obj.text(count + increment);
					setTimeout(function(){updateCount(obj,increment);}, 1);
				} else {
					obj.text(to);
				}
			};
			updateCount(obj,increment);
		});
	},
	component_autocomplete : function(slug,options){

		if(!slug) $.message('error','Slug non définis pour un composant autocomplete');
		var options = $.extend({
			edit : slug+'_by_uid',
			autocomplete : slug+'_autocomplete',
			force : true,
			nullChars : ['0'],
			data : function(){
				return {};
			},
			editData : function(){
				return {};
			}
		},options);

		return this.each(function(){
			var input = $(this);
			var container;
			if(!input.data("data-component")){
				container = $('<input class="'+input.attr('class')+' data-type-'+slug.replace(/_/ig,'-')+'" type="text" '+(input.attr('placeholder')!=null?'placeholder="'+input.attr('placeholder')+'"':'')+'>');
				input.before(container);
				input.addClass('component-raw-value hidden'); // 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");
			}

			if(!options.skin){
				options.skin  =  function(item){
					var html = '';
					var re = new RegExp(container.val(),"gi");
					name = item.label.replace(re, function (x) {
						return '<strong>'+x+'</strong>';
					});
					html += '<div class="'+slug+'-infos"><span>'+name+'</span>';
					html += '<div class="clear"></div>';
					return html;
				}
			}

			if(!options.onClick){
				options.onClick = function(selected,element){
					input.val(selected.id);
					container.val(selected.label);
					input.trigger('click').trigger('change');
				}
			}

			if(!options.onLoad){
				options.onLoad = function(component,item){
					component.container.val(item.label);
				}
			}

			//Gestion des champs déja remplis au chargement de la page
			if(input.val() !='' && options.nullChars.indexOf(input.val())==-1){
				var uid = input.val();
				if(!window.componentQueue[slug]) window.componentQueue[slug] = {timeout : null,components : [],uids  :{} };
				clearTimeout(window.componentQueue[slug].timeout);

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

				window.componentQueue[slug].components.push({
					input : input,
					container : container,
					values : uids
				});

				container.val('Chargement...');

				window.componentQueue[slug].timeout = setTimeout(function(){
					$.action({
						action : options.edit,
						data : options.editData(slug),
						items : Object.keys(window.componentQueue[slug].uids)
					},function(r){
						if(window.componentQueue[slug]){
							for(var key in window.componentQueue[slug].components){
								var component = window.componentQueue[slug].components[key];
								input = window.componentQueue[slug].components[key].input;
								for(var i in component.values){
									var value = component.values[i];
									if(!r.items[value]) continue;
									options.onLoad(component,r.items[value]);
									//component.container.val(r.items[value].label);
								}
							}
							delete window.componentQueue[slug];
						}
					});
				},50);
			}

			container.keyup(function(e){
				if (e.ctrlKey ||
					e.key == "Control" ||
					e.key == "ArrowUp" ||
					e.key == "ArrowDown" ||
					e.key == "ArrowLeft" ||
					e.key == "ArrowRight" ||
					e.key == "Shift" ||
					e.key == "CapsLock" ||
					e.key == "Tab" ||
					e.key == "Alt" ||
					e.key == "Home" ||
					e.key == "End")
						return;

				input.val('');
			});

			//autocompletion
			container.autocomplete({
				action : options.autocomplete,
				suggest : function(){ return  !input.is('[readonly]') && !input.is('[disabled]')},
				dynamicData : options.data,
				skin : options.skin,
				highlight : function(item){
					return item;
				},
				onClick : function(selected,element){
					container.val(selected.label);
					options.onClick(selected,element);
				},
				onBlur : function(selected){

					if((input.attr('data-force')!='false' || options.force) && input.val() == ''){
						container.val('');
						input.trigger('click').trigger('change');
					}
					input.trigger('blur');
				}
			});
		});
	},
    //Chargement des images en dynamique au scroll
	//Par défaut check sur scroll de window sinon sur celui du parent indiqué
    lazyLoad : function(parent, callback) {
		var obj = this;
		var parent = parent!=null ? $(parent) : $(window);

		this.lazyload = function(elem, parent, callback){
			var viewportHeight = parent.height();
			if(!elem.filter(':not([data-lazy=""])').length) return;

			elem.filter(':not([data-lazy=""])').each(function() {
				var element = $(this);
	        	var elemOffsetTop = element.offset().top;
				//Si parent est $(window) on récupère la valeur du scrollTop, sinon on prend la valeur du offsetTop du parent
				var viewportOffset = viewportHeight + ((parent.get(0)===parent.get(0).window) ? parent.scrollTop() : parent.offset().top);

				//Check si l'image se situe (en px) à 1/3 de sa taille ou moins du bas du viewport du parent pour le requêter
				if(elemOffsetTop - element.height()/3 < viewportOffset && element.attr('data-lazy')){
					var srcImg = element.attr('data-lazy');
					element.removeAttr('data-lazy');

					//Check pour ajouter attribut src (tag IMG) ou mettre en background-image (tag autre)
					element.is('img') ? element.attr({src: srcImg}) : element.css({"background-image": 'url('+srcImg+')'});

					if(callback!=null) {
						elementEvent = !element.is('img') ? $('<img/>').attr('src', srcImg) : element;

						elementEvent.one("load", function() {
							if(!element.is('img')) $(this).remove();
							callback(element);
						}).each(function() {
							if(this.complete) $(this).trigger('load');
						});
					}
				}
		    });
		}
		//Initialisation
		this.lazyload(obj,parent,callback);

		//Appel qd parent scroll
	    parent.off('scroll').on('scroll', function(){
	    	if(window.scrollTimer) clearTimeout(window.scrollTimer);
	    	//Ajout timeout pour éviter de trigger à
	    	//chaque scroll, on attend que le user stabilise
	    	window.scrollTimer = setTimeout(function(){
	    		obj.lazyload(obj,parent,callback);
	    	}, 25);
		});

		//Appel sur resize
	    $(window).resize(function(){
	    	if(window.scrollTimer) clearTimeout(window.scrollTimer);

	    	window.scrollTimer = setTimeout(function(){
	    		obj.lazyload(obj,parent,callback);
	    	}, 25);
		});
	}
});
