component.js 117 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218
  1. /* COMPOSANT */
  2. function init_components(selector){
  3. //Suppression apr defaut des légendes pour les graphiques
  4. if(window.Chart) Chart.defaults.global.legend.display = false;
  5. var selected = selector ? $('[data-type]',selector) : '[data-type]';
  6. $(selected).each(function(i,input){
  7. var input = $(input);
  8. switch($(input).attr('data-type')){
  9. /**
  10. * data-format : Supporte les formats dd/mm/yyyy ou yyyy/mm/dd avec séparateur "/" ou "-"
  11. * 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)
  12. * 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)
  13. * data-workdays : si attribut est présent, seuls les jours ouvrés sont sélectionnables
  14. */
  15. case 'date':
  16. //Les séparateurs doivent être similaires entre les éléments de la chaîne
  17. var dateFormatRegex = /^(dd|yy)([\/|-])(mm)\2(dd|yy)$/;
  18. var dateFormat = input.attr('data-format') != undefined && input.attr('data-format').match(dateFormatRegex) ? input.attr('data-format').match(dateFormatRegex)[0] : "dd/mm/yy";
  19. var begin = input.attr('data-begin');
  20. begin = (begin != undefined && (begin.match(/-?\d+/) || begin.match(/^(\d{2}(?:\d{2})?)\/(\d{2})\/(\d{2}(?:\d{2})?)$/))) ? begin : null ;
  21. var end = input.attr('data-end');
  22. end = (end != undefined && (end.match(/-?\d+/) || end.match(/^(\d{2}(?:\d{2})?)[-|\/](\d{2})[-|\/](\d{2}(?:\d{2})?)$/))) ? end : null ;
  23. input.removeClass('hasDatepicker');
  24. input.date({
  25. placeholder: input.attr('placeholder') != undefined ? input.attr('placeholder') : "jj/mm/aaaa",
  26. dateFormat: dateFormat,
  27. beginDate: begin,
  28. endDate: end,
  29. workdays: input.attr('data-workdays') != null
  30. }).click(function(event) {
  31. $(this).select();
  32. });
  33. break;
  34. /**
  35. * data-format : Supporte les formats dd/mm/yyyy ou yyyy/mm/dd avec séparateur "/" ou "-"
  36. * data-step : L'intervalle entre 2 valeurs en minutes
  37. */
  38. case 'hour':
  39. var timeFormatRegex = /^(H):(i)$/;
  40. var timeFormat = input.attr('data-format') != undefined && input.attr('data-format').match(timeFormatRegex) ? input.attr('data-format').match(timeFormatRegex)[0] : "H:i";
  41. var step = input.attr('data-step');
  42. input.hour({
  43. placeholder: input.attr('placeholder') != undefined ? input.attr('placeholder') : "hh:mm",
  44. timeFormat: timeFormat,
  45. step: is_numeric(step) && step>0 ? step : 1
  46. }).click(function(event){
  47. $(this).select();
  48. });
  49. break;
  50. case 'decimal':
  51. input.off('keydown').on('keydown',function(e){
  52. switch(e.key){
  53. case ',':
  54. //on interdit la saisie si la chaine contient déja un . ou une ,
  55. if(input.val().match(/[\.,]/i)) return false;
  56. //remplace les , par les . pour chiffre valide en db
  57. input.val(input.val()+'.');
  58. return false;
  59. break;
  60. case '.':
  61. //on interdit la saisie si la chaine contient déja un . ou une ,
  62. if(input.val().match(/[\.,]/i)) return false;
  63. break;
  64. case 'Backspace':
  65. case 'ArrowRight':
  66. case 'ArrowLeft':
  67. case 'Delete':
  68. case 'Insert':
  69. case '-':
  70. return true
  71. break;
  72. default:
  73. //autorise le coller ctrl+v
  74. if(e.key == 'v' && e.ctrlKey ) return true;
  75. //dégage les caractères différents de .,0123456789
  76. if(!e.key.match(/[\-0-9,\.]/i)) return false;
  77. break;
  78. }
  79. return true;
  80. });
  81. //supprime les caracteres pourris sur un coller
  82. input.off('keyup blur').on('keyup blur',function(e){
  83. var value = input.val().replace(/[^0-9\-,.]/ig,'');
  84. //on remplace les "," par les "."
  85. var value = value.replace(/,/ig,'.');
  86. //on s'assure qu'il n'existe pas plusieurs "."
  87. var splits = value.split('.');
  88. if(splits.length > 1){
  89. var newValue = '';
  90. for(var i in splits)
  91. newValue+= (i!=splits.length-1 ? '' : '.')+splits[i];
  92. value = newValue;
  93. }
  94. //On s'assure que le - est en début ou n'existe pas
  95. if(value.indexOf('-') !== -1 && value.indexOf('-')!==0 ) value = value.replace('-','');
  96. input.val(value);
  97. });
  98. break;
  99. case 'color':
  100. input.colorInput();
  101. break;
  102. case 'history':
  103. var data = input.data();
  104. if(isNaN(input.attr('data-uid'))) break;
  105. input.off();
  106. if(data.showImportant){
  107. if(!window.componentQueue['history']) window.componentQueue['history'] = {timeout : null,scopes :{} };
  108. clearTimeout(window.componentQueue['history'].timeout);
  109. if(!window.componentQueue['history'].scopes[data.scope]) window.componentQueue['history'].scopes[data.scope] = [];
  110. window.componentQueue['history'].scopes[data.scope].push(data.uid);
  111. window.componentQueue['history'].timeout = setTimeout(function(){
  112. $.action({
  113. action : 'history_search',
  114. scopes : window.componentQueue['history'].scopes,
  115. showImportant : true
  116. },function(r){
  117. var tpl = $('#history-notification-tpl').html();
  118. for(var k in r.rows){
  119. var row = r.rows[k];
  120. var html = Mustache.render(tpl, {number: row.number});
  121. inputs = $('[data-scope="'+row.scope+'"][data-uid="'+row.uid+'"][data-type="history"][data-show-important="true"]');
  122. inputs.addClass('position-relative').append(html);
  123. if(row.number == 0) inputs.find('.history-notification').addClass('hidden');
  124. init_components(inputs);
  125. }
  126. });
  127. window.componentQueue['history'] = {timeout : null,scopes :{} };
  128. },50);
  129. }
  130. input.click(function(event){
  131. var data = input.data();
  132. var panel = $('.history-panel');
  133. var uid;
  134. if(panel.hasClass('hidden')) panel.removeClass('hidden');
  135. panel.toggleClass('fold').one('webkitAnimationEnd oanimationend msAnimationEnd animationend', function(e) {
  136. var currentPanel = $(this);
  137. if(!currentPanel.hasClass('fold')) return;
  138. currentPanel.addClass('hidden');
  139. });
  140. if(panel.hasClass('fold')) return;
  141. if(input.attr('data-uid')) panel.attr('data-uid',input.attr('data-uid'));
  142. if(data.scope) panel.attr('data-scope',data.scope);
  143. history_search(function(){
  144. //Redimensionnement du panel historique
  145. setTimeout(function(){
  146. uid = $('.history-panel').panelResize({direction : 'left'});
  147. }, 100);
  148. });
  149. event.stopPropagation();
  150. //Fermeture du panel au click en dehors du composant
  151. $(window).click(function(e) {
  152. var target = $(e.target);
  153. var panel = target.closest('.history-panel');
  154. var handler = target.closest('.panel-resize-handler');
  155. var targetInPanel = panel.length || handler.length;
  156. //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
  157. if(target.hasClass('history-panel') || target.hasClass('comments') || !targetInPanel){
  158. $('li.comment:not(.hidden)').each(function(){
  159. if($(this).find('.history-content .trumbowyg-editor-visible').length && $(this).find('.history-content .trumbowyg-editor').text().length)
  160. history_save(this);
  161. });
  162. }
  163. if(targetInPanel) return;
  164. $('.history-panel').addClass('fold');
  165. $('#'+uid).remove();
  166. });
  167. });
  168. break;
  169. /**
  170. * data-regex : regex de vérification (defaut : ^[0-9]{10}$, les espaces sont trimés pour vérification)
  171. * data-spacing : espace automatiquement les chiffre si collés (défaut true)
  172. * data-empty-field : vide le champs si invalide (défaut false)
  173. * data-invalid-class : définit la classe a ajouter si invalide (défaut .invalid-value)
  174. * data-blur : si défini, trigger le blur indiqué après validation du champ (le onblur classique trigger avant)
  175. * data-type-only : empeche l'utilisateur de taper autre caractere que définis dans cette expression (defaut : [0-9\+\s])
  176. */
  177. case 'phone':
  178. input.phone(input.data());
  179. break;
  180. /**
  181. * data-delete : définit la fonction JS qui sera appelée au clic sur la croix pour supprimer l'image
  182. * data-save : définit la fonction JS qui sera appelée après le changement de l'image
  183. * data-default-src : chemin vers l'image par défaut à utiliser
  184. */
  185. case 'image':
  186. console.warn('[DEPRECATED] : Image doit être remplacé par le composant file');
  187. if(input.hasClass('hidden')) return;
  188. if(input.closest('.type-image-bloc').length!=0) break;
  189. input.attr('type', 'file');
  190. input.wrap( "<div class='type-image-bloc'></div>");
  191. var save = input.attr('data-save');
  192. var src = ($(input).attr('value')!='') ? $(input).attr('value') : 'img/default-image.png';
  193. src += src.indexOf('?')!=-1 ? '&' : '?';
  194. src += 't='+(Math.random()*1000);
  195. var thumbnail = $('<img src="'+src+'" >');
  196. 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>';
  197. input.before(thumbnail);
  198. if(thumbnail.attr('src').indexOf('default-') === -1) thumbnail.before(deleteBtn);
  199. input.addClass('noPrint');
  200. input.change(function(){
  201. var reader = new FileReader();
  202. reader.onload = function (e) {
  203. thumbnail.attr('src', e.target.result);
  204. thumbnail.before(deleteBtn);
  205. }
  206. reader.readAsDataURL(input.get(0).files[0]);
  207. if(save) window[save](input);
  208. });
  209. break;
  210. //Sélection de valeurs multiples
  211. case 'checkbox-list':
  212. var data = input.data();
  213. if(!data.depth) data.depth = 1;
  214. var liClasses = '';
  215. var isDropDown = data.display == 'dropdown';
  216. //Si défini à false => bloque le choix multi niveau
  217. var multiLevelSelect = data.multiLevelSelect;
  218. var initValues = function(){
  219. ul.find('li input').prop("checked",false);
  220. ul.find('li[data-folded="0"]').attr('data-folded','1');
  221. if(input.val() ){
  222. var checkeds = input.val().split(',')
  223. for(var i in checkeds){
  224. var checkbox = ul.find('li[data-value="'+checkeds[i]+'"] input').eq(0);
  225. checkbox.prop("checked",true);
  226. checkbox.closest('ul').parents().attr('data-folded','0');
  227. }
  228. refreshLabel();
  229. }
  230. }
  231. var refreshLabel = function(){
  232. if(!isDropDown) return;
  233. var checkedLabels = [];
  234. ul.find('>li input:checked').each(function(){
  235. checkedLabels.push($(this).closest('li').find('label').eq(0).text());
  236. });
  237. var button = container.find('.dropdown-toggle');
  238. button.html(checkedLabels.length>0 ? checkedLabels.join(',') : data.button);
  239. $('.checkbox-list-counter',container).text(checkedLabels.length);
  240. }
  241. var printChilds = function(childs,level){
  242. if(!level) level = 1;
  243. html = '';
  244. if(level==1){
  245. 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"> ';
  246. html += '<i class="far fa-square fa-stack-2x"></i><i class="fas fa-check-double fa-stack-1x"></i></span></small></li>';
  247. }
  248. for(var k in childs){
  249. var child = childs[k];
  250. var hasChilds = child.childs && child.childs.length!=0;
  251. if(!data.multiLevelSelect && level == 1 && hasChilds && (child.childs.length < data.depth)) continue;
  252. html += '<li class="'+liClasses+' user-select-none" data-folded="1" data-value="'+child.id+'"> ';
  253. if(level == data.depth){
  254. html += '<label><input data-type="checkbox" type="checkbox"> '+child.label+'</label>';
  255. }else{
  256. if(hasChilds || multiLevelSelect){
  257. html += '<span class="dropdown-element">'
  258. html += hasChilds ? '<i class="fas fa-chevron-right fa-fw checkbox-toggle-fold"></i> <label class="label-group">' : '<label>';
  259. if(level == data.depth || multiLevelSelect) html += '<input data-type="checkbox" type="checkbox"> ';
  260. html += child.label+'</label>';
  261. if(hasChilds){
  262. html += '<span class="fa-stack checkbox-list-check-all hide" title="Tout (dé)cocher"> ';
  263. html += '<i class="far fa-square fa-stack-2x"></i> ';
  264. html += '<i class="fas fa-check-double fa-stack-1x"></i></span>';
  265. }
  266. html += '</span> ';
  267. }
  268. }
  269. if(hasChilds) html += '<ul>'+printChilds(child.childs,level+1)+'</ul>';
  270. html += '</li>';
  271. }
  272. return html;
  273. }
  274. if(!input.data("data-component")){
  275. container = $('<div class="'+(isDropDown?'dropdown':'')+' '+input.attr('class')+' data-type-checkbox-list"><ul class="'+(isDropDown?'dropdown-menu':'')+'"></ul></div>');
  276. if(isDropDown){
  277. data.button = !data.button ? 'Choix': data.button;
  278. container.append('<div class="dropdown-toggle" type="button" data-toggle="dropdown" >'+data.button+'</div>');
  279. liClasses += ' dropdown-item ';
  280. }
  281. input.before(container);
  282. input.data("data-component", container);
  283. input.addClass('component-raw-value'); // utilisé par les filtres / etc.. pour dissocier la valeur brute du composant
  284. if(input.attr("required")) container.attr("required", "");
  285. if(input.attr("disabled")) container.attr("disabled", "");
  286. if(input.attr("readonly")) container.attr("readonly", "");
  287. var ul = container.find('>ul');
  288. ul.on('click','>li',function(e){
  289. e.stopPropagation();
  290. }).on('click','>li input',function(e){
  291. e.stopPropagation();
  292. var checked = [];
  293. ul.find('>li input:checked').each(function(){
  294. checked.push($(this).closest('li').attr('data-value'));
  295. });
  296. input.val(checked.join(','));
  297. refreshLabel();
  298. input.trigger('change');
  299. });
  300. }else{
  301. container = input.data("data-component");
  302. var ul = container.find('>ul');
  303. }
  304. var html = '';
  305. data.values = input.attr('data-values');
  306. if(data.values){
  307. data.values = JSON.parse(input.attr('data-values'));
  308. for(var k in data.values)
  309. if(typeof(data.values[k]) === "string") data.values[k] = {id: k, label: data.values[k]};
  310. html += printChilds(data.values);
  311. }
  312. ul.html(html);
  313. ul.find('[data-type="checkbox"]').removeClass('component-raw-value');
  314. var initCheckbox = function(ul, callback){
  315. init_components(ul);
  316. ul.find('[data-type="checkbox"]').removeClass('component-raw-value');
  317. ul.find('.checkbox-toggle-fold').click(function(){
  318. var li = $(this).closest('li');
  319. li.attr('data-folded',li.attr('data-folded')=='1'?0:1);
  320. });
  321. $('.dropdown-element').mouseover(function(){
  322. $(this).find('.checkbox-list-check-all').eq(0).removeClass('hide');
  323. });
  324. $('.dropdown-element').mouseout(function(){
  325. $(this).find('.checkbox-list-check-all').eq(0).addClass('hide');
  326. });
  327. container.find('.checkbox-list-check-all').click(function(event){
  328. event.stopPropagation();
  329. var element = $(this);
  330. var parentContainer = element.parent().parent();
  331. //cas du checkbox-all racine
  332. if(parentContainer.hasClass('checkbox-list-header'))
  333. parentContainer = parentContainer.parent();
  334. parentContainer.find('input[type="checkbox"]').click();
  335. var li = element.closest('li');
  336. li.attr('data-folded',0);
  337. if(multiLevelSelect) li.find('li').attr('data-folded',0);
  338. });
  339. if(callback!=null) callback();
  340. }
  341. if(data.slug){
  342. if(!data.depth) data.depth = 1;
  343. $.action({
  344. action: 'load_dictionnary_component',
  345. slug: data.slug,
  346. depth: data.depth,
  347. multiLevelSelect: data.multiLevelSelect,
  348. },function(response){
  349. html = '';
  350. if(response.content.childs && response.content.childs.length!=0){
  351. html += printChilds(response.content.childs);
  352. }else{
  353. html += '<li class="text-muted text-center"><small><i class="fas fa-exclamation-circle"></i> Liste vide ou introuvable</small></li>';
  354. }
  355. ul.append(html);
  356. initCheckbox(ul, initValues);
  357. });
  358. }else{
  359. initCheckbox(ul, initValues);
  360. }
  361. break;
  362. //Selection de tag
  363. case 'tag':
  364. var container;
  365. var picker;
  366. var pickerLi;
  367. if(!input.data("data-component")){
  368. var placeholder = input.attr("placeholder") != undefined ? input.attr("placeholder") : "";
  369. container = $('<div class="'+input.attr('class')+' data-type-tag"><ul><li class="tag-picker-li"><input placeholder="'+placeholder+'" type="text"></li></ul></div>');
  370. input.before(container);
  371. input.addClass('component-raw-value'); // utilisé par les filtres / etc.. pour dissocier la valeur brute du composant
  372. input.data("data-component", container);
  373. if(input.attr("required")) container.attr("required","");
  374. if(input.attr("disabled")) container.attr("disabled","");
  375. if(input.attr("readonly")) container.attr("readonly","");
  376. } else {
  377. container = input.data("data-component");
  378. }
  379. picker = container.find('input:eq(0)');
  380. pickerLi = container.find('ul li.tag-picker-li');
  381. if(container.attr('disabled') || container.attr('readonly'))
  382. pickerLi.find('input').attr('readonly','');
  383. container.find('.tag-picker-tag').remove();
  384. pickerLi.removeClass('hidden');
  385. var methods = {
  386. //Récuperation des valeurs sélectionnées (objet et id) en fonction des tags visuels présents
  387. values : function(container){
  388. var tags = container.find('ul .tag-picker-tag');
  389. var values = {object:[],id:[]};
  390. tags.each(function(i,element){
  391. if($(element).attr('data-value') == '') return;
  392. var data = $(element).data();
  393. values['object'].push(data);
  394. values['id'].push(data.value);
  395. });
  396. return values;
  397. },
  398. remove : function(tag){
  399. tag.remove();
  400. var values = methods.values(container);
  401. var plainValue = [];
  402. for(var k in values.object){
  403. plainValue.push(values.object[k].value);
  404. }
  405. input.val(plainValue.length!=0? plainValue.join(',') :'');
  406. input.data('values',values['object']);
  407. if((values['id'].length-1) ==1 && input.attr('data-multiple') == null){
  408. pickerLi.addClass('hidden');
  409. }else{
  410. pickerLi.removeClass('hidden');
  411. }
  412. //on trigger le change
  413. input.trigger('change');
  414. },
  415. //Ajout d'un tag visuel et mise à jour de l'input brut en fonction de l'objet fournis
  416. add : function(container,tag){
  417. pickerLi = container.find('ul li.tag-picker-li');
  418. if( container.find('li[data-id="'+tag.id+'"]').length>0) return;
  419. var closeBtn = input.is('[readonly]') || input.is('[disabled]') ? '' : '<i class="fa fa-times btn-remove"></i>';
  420. var tag = $('<li class="tag-picker-tag" data-value="'+tag.value+'"><div>'+tag.label+closeBtn+'</div></li>');
  421. pickerLi.before(tag);
  422. var values = methods.values(container);
  423. input.val(values['id'].join(','));
  424. input.data('values',values['object']);
  425. if((values['id'].length) == 1 && input.attr('data-multiple') == null){
  426. pickerLi.addClass('hidden');
  427. }else{
  428. pickerLi.removeClass('hidden');
  429. }
  430. tag.find('.btn-remove').click(function(){
  431. if(input.is('[readonly]') || input.is('[disabled]')) return;
  432. methods.remove($(this).closest('.tag-picker-tag'));
  433. });
  434. }
  435. }
  436. //Gestion des champs déja remplis au chargement de la page
  437. if(input.val() !=''){
  438. var id = input.val();
  439. //picker.addClass('hidden');
  440. var tags = id.split(',');
  441. if(input.data('dictionnarySlug') && input.data("data-autocomplete-value") != 'label'){
  442. $.action({
  443. action : 'tag_list_by_id',
  444. id : id
  445. },function(response){
  446. for(var key in response.tags){
  447. if(response.tags[key] == '') continue;
  448. methods.add( container,{label:response.tags[key].label,value:response.tags[key].id});
  449. }
  450. });
  451. }else{
  452. for(var key in tags){
  453. if(tags[key] == '') continue;
  454. methods.add( container,{label:tags[key],value:tags[key]});
  455. }
  456. }
  457. }
  458. picker.off('blur').blur(function(e){
  459. if(window.tagBlur) clearTimeout(window.tagBlur);
  460. window.tagBlur = setTimeout(function(){
  461. var value = picker.val();
  462. if(value.trim() == '') return;
  463. methods.add(container,{label:value,value:value});
  464. picker.val('');
  465. $('.typeahead.dropdown-menu').hide();
  466. },500);
  467. });
  468. if(!input.data('autocomplete') && input.data('dictionnarySlug'))
  469. input.data('autocomplete','tag_list_autocomplete');
  470. if(input.data('autocomplete')){
  471. var dictionnarySlug = input.data('dictionnarySlug');
  472. $(picker).autocomplete({
  473. action: input.data('autocomplete'),
  474. selectEqual : false, //evite le select auto du tag test quand on veux taper test2
  475. suggest : true,
  476. dynamicData : function(){
  477. return dictionnarySlug ? { parent : dictionnarySlug} : {}
  478. },
  479. onClick : function(selected,element){
  480. if(window.tagBlur) clearTimeout(window.tagBlur);
  481. methods.add(container,{label:selected.name,value: (input.data("data-autocomplete-value") == 'label' ? selected.name : selected.id) });
  482. //on vide l'input de saisie
  483. picker.val('');
  484. //on trigger le change
  485. input.trigger('change');
  486. },
  487. });
  488. }
  489. picker.off('keydown').keydown(function(e){
  490. var validationChars = ['Enter',' ',',','Tab'];
  491. if(e.key == 'Backspace' && picker.val() == ''){
  492. methods.remove($('.tag-picker-tag:last',container));
  493. return;
  494. }
  495. //si appui sur Entrée, virgule ou espace ou tab on valide le tag
  496. if( validationChars.indexOf(e.key) === -1 || input.is('[readonly]') || input.is('[disabled]') ) return;
  497. var value = picker.val();
  498. if(value.trim() == '') return;
  499. //on cache l'automcomplete si present
  500. $('.typeahead.dropdown-menu').hide();
  501. //on ajoute le tag visuel
  502. methods.add(container,{label:value,value:value});
  503. //on vide l'input de saisie
  504. picker.val('');
  505. //on trigger le change
  506. input.trigger('change');
  507. return false;
  508. });
  509. break;
  510. //composant permettant les listes sous
  511. //form de dropdown avec icones et couleurs
  512. case 'dropdown-select':
  513. var container;
  514. if(!input.data("data-component")){
  515. container = $('<div class="'+input.attr('class')+' data-type-dropdown-select"> \
  516. <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> \
  517. <div class="dropdown-menu w-100 p-0 text-center"></div> \
  518. </div>');
  519. input.addClass('component-raw-value'); // utilisé par les filtres / etc.. pour dissocier la valeur brute du composant
  520. input.before(container);
  521. input.data("data-component", container);
  522. if(input.attr("required")) container.attr("required","");
  523. var button = container.find('.dropdown-toggle-button');
  524. } else {
  525. container = input.data("data-component");
  526. var button = container.find('.dropdown-toggle-button');
  527. }
  528. container.find('.dropdown-menu').html('');
  529. var html = '';
  530. $.each(input.get(0).options, function(i,element){
  531. var option = $(element);
  532. var classes = option.attr('class') ? option.attr('class') : '';
  533. var icon = option.attr("data-icon") ? '<i class="'+option.attr("data-icon")+'"></i> ': '';
  534. var title = option.attr("data-title") ? 'title="'+option.attr("data-title")+'"': '';
  535. var backColor = option.get(0).style.getPropertyValue('background-color') ? option.get(0).style.getPropertyValue('background-color') : '#ffffff';
  536. var fontColor = color_light(backColor) < 50 ? '#ffffff' : '#333333' ;
  537. html += '<a class="dropdown-item pointer '+classes+'" '+title+' data-value="'+option.val()+'" style="background-color:'+backColor+';color:'+fontColor+'">'+icon+option.html()+'</a>';
  538. });
  539. container.find('.dropdown-menu').append(html);
  540. var changeMenu = function(container,menu){
  541. $('.dropdown-menu a',container).removeClass('active');
  542. var item = $('.dropdown-menu a[data-value="'+menu+'"]', container);
  543. var backColor = item.css('background-color') ? item.css('background-color') : '#ffffff' ;
  544. var fontColor = color_light(backColor) < 50 ? '#ffffff' : '#333333' ;
  545. container.find('.dropdown-toggle-button').html(item.html()).css({
  546. backgroundColor:backColor,
  547. color:fontColor
  548. });
  549. item.addClass('active');
  550. };
  551. $('.dropdown-menu a',container).click(function(){
  552. if(input.attr("readonly") == 'readonly') return;
  553. var item = $(this);
  554. changeMenu(container,$(this).attr('data-value'));
  555. input.val(item.attr('data-value')).trigger('change');
  556. });
  557. if(input[0].hasAttribute('data-no-toggle')) button.removeClass('dropdown-toggle');
  558. var state = input.attr('data-value') != null ? input.attr('data-value') : input.val();
  559. if(state == '') return;
  560. var item = $('.dropdown-menu a[data-value="'+state+'"]', container);
  561. //Utilisation background-color pour fonctionner sous FF
  562. var backColor = item.get(0) && item.get(0).style.getPropertyValue('background-color') ? item.get(0).style.getPropertyValue('background-color') : '#ffffff' ;
  563. var fontColor = color_light(backColor) < 50 ? '#ffffff' : '#333333' ;
  564. button.html(item.html()).css({
  565. backgroundColor:backColor,
  566. color:fontColor
  567. });
  568. item.addClass('active');
  569. input.val(item.attr('data-value'));
  570. input.change(function(){
  571. changeMenu(container,input.val());
  572. });
  573. break;
  574. case 'firm':
  575. //autocomplete classique
  576. input.component_autocomplete('firm');
  577. break;
  578. case 'map':
  579. //on recréé le container a chaque fois car leaflet
  580. //ne sais pas refresh sans tout recharger (a verifier)
  581. var container = $('<div></div>');
  582. container.attr('class',input.attr('class'));
  583. input.data('container',container);
  584. input.before(container);
  585. if(container.height() <1){
  586. container.css('height',(input.height() <1 ? '300px' : input.height()+'px'));
  587. container.css('width',(input.width() <1 ? '300px' : input.width()+'px'));
  588. }
  589. var data = input.data();
  590. var json = input.get(0).innerHtml;
  591. if(!json) json = '[]';
  592. var points = JSON.parse(json);
  593. var existingMap = L.DomUtil.get(container.get(0));
  594. if(existingMap != null)
  595. existingMap._leaflet_id = null;
  596. var map = L.map(container.get(0));
  597. map.invalidateSize();
  598. //si une latitude/longitude pour la vue global est définie on l'utilise
  599. if(data.latitude && data.longitude){
  600. if(!data.zoom) data.zoom = 13;
  601. map.setView([data.longitude, data.latitude], data.zoom);
  602. //Sinon on se base sur le groupe de marqueurs proposés pour définie une vue englobante
  603. }else{
  604. var pointArray = [];
  605. if(points.length!=0){
  606. for(var k in points){
  607. var point = points[k];
  608. if(point.longitude && point.latitude)pointArray.push([point.latitude,point.longitude]);
  609. }
  610. var markerGroup = new L.LatLngBounds(pointArray);
  611. map.fitBounds(markerGroup);
  612. }
  613. }
  614. if(data.zoom) map.setZoom(data.zoom);
  615. L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
  616. attribution: ''
  617. }).addTo(map);
  618. /* var defaultIcon = L.icon({
  619. iconUrl: 'img/leaflet/marker-icon.png',
  620. shadowUrl: 'img/leaflet/marker-shadow.png',
  621. iconSize: [25, 41], // size of the icon
  622. shadowSize: [41, 41], // size of the shadow
  623. iconAnchor: [0, 0], // point of the icon which will correspond to marker's location
  624. shadowAnchor: [0, 0], // the same for the shadow
  625. popupAnchor: [10, 0] // point from which the popup should open relative to the iconAnchor
  626. });*/
  627. map.on('click', function(args){
  628. if(data.click && window[data.click]) window[data.click]({
  629. longitude : args.latlng.lng,
  630. latitude : args.latlng.lat,
  631. layerX : args.layerPoint.x,
  632. layerY : args.layerPoint.y,
  633. x : args.containerPoint.x,
  634. y : args.containerPoint.y,
  635. event : args.originalEvent,
  636. target : args.target,
  637. sourceTarget : args.sourceTarget
  638. });
  639. });
  640. var mapPoints = {};
  641. var markers = L.markerClusterGroup();
  642. for(var k in points){
  643. var point = points[k];
  644. if(!point.type) point.type = 'marker';
  645. switch(point.type){
  646. case 'marker':
  647. var currentPoint = L.marker([point.latitude,point.longitude]/*, {icon: defaultIcon}*/);
  648. break;
  649. case 'circle':
  650. if(!point.borderColor) point.borderColor = '#38f';
  651. if(!point.backgroundColor) point.backgroundColor = '#38f';
  652. if(!point.opacity) point.opacity = 0.5;
  653. if(!point.radius) point.radius = 500;
  654. var currentPoint = L.circle([point.latitude,point.longitude], {
  655. color: point.borderColor,
  656. fillColor: point.backgroundColor,
  657. fillOpacity: point.opacity,
  658. radius: point.radius
  659. });
  660. break;
  661. case 'polygon':
  662. if(!point.borderColor) point.borderColor = '#38f';
  663. if(!point.backgroundColor) point.backgroundColor = '#38f';
  664. if(!point.opacity) point.opacity = 0.5;
  665. if(!point.radius) point.radius = 500;
  666. var currentPoint = L.polygon(point.points, {
  667. color: point.borderColor,
  668. fillColor: point.backgroundColor,
  669. fillOpacity: point.opacity,
  670. radius: point.radius
  671. });
  672. break;
  673. }
  674. markers.addLayer(currentPoint);
  675. if(point.label) currentPoint.bindPopup(point.label);
  676. currentPoint.id = k;
  677. if(point.id) currentPoint.id = point.id ;
  678. mapPoints[currentPoint.id] = currentPoint;
  679. //currentPoint.addTo(map);
  680. }
  681. map.addLayer(markers);
  682. input.data('points',mapPoints);
  683. break;
  684. case 'contact':
  685. //autocomplete classique
  686. input.component_autocomplete('contact',{
  687. skin : function(item){
  688. var html = '';
  689. var re = new RegExp(input.val(),"gi");
  690. name = item.label.replace(re, function (x) {
  691. return '<strong>'+x+'</strong>';
  692. });
  693. html += '<div class="'+slug+'-infos"><span>'+name+'</span>';
  694. if(item.job) html += '<div class="text-muted '+slug+'-job"><span>'+item.job+'</span>';
  695. html += '<div class="clear"></div>';
  696. return html;
  697. },
  698. data : function(){
  699. return {scope : input.attr('data-scope'),uid : input.attr('data-uid')? input.attr('data-uid').split(','):[] };
  700. }
  701. });
  702. break;
  703. case 'user':
  704. var userContainer;
  705. var userPicker;
  706. var pickerLi;
  707. if(!input.data("data-component")){
  708. 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>');
  709. input.before(userContainer);
  710. input.addClass('component-raw-value'); // utilisé par les filtres / etc.. pour dissocier la valeur brute du composant
  711. input.data("data-component", userContainer);
  712. if(input.attr("required")) userContainer.attr("required","");
  713. if(input.attr("disabled")) userContainer.attr("disabled","");
  714. if(input.attr("readonly")) userContainer.attr("readonly","");
  715. } else {
  716. userContainer = input.data("data-component");
  717. }
  718. userPicker = userContainer.find('input:eq(0)');
  719. pickerLi = userContainer.find('ul li.user-picker-li');
  720. if(userContainer.attr('disabled') || userContainer.attr('readonly'))
  721. pickerLi.find('input').attr('readonly','');
  722. userContainer.find('.user-picker-tag').remove();
  723. pickerLi.removeClass('hidden');
  724. var pickerFunctions = {
  725. //Récuperation des valeurs sélectionnées (objet et uid) en fonction des tags visuels présents
  726. getValues : function(userContainer){
  727. var tags = userContainer.find('ul .user-picker-tag');
  728. var values = {object:[],uid:[]};
  729. tags.each(function(i,element){
  730. if($(element).attr('data-uid') == '') return;
  731. var object = $(element).data();
  732. values['object'].push(object);
  733. values['uid'].push(object.uid);
  734. });
  735. return values;
  736. },
  737. checkComponentOverflow : function(container){
  738. var overflow = container.get(0).scrollHeight > container.outerHeight();
  739. overflow ? container.addClass('overflowing') : container.removeClass('overflowing');
  740. },
  741. //Ajout d'un tag visuel et mise à jour de l'input brut en fonction de l'objet user fournis
  742. addTag : function(userContainer,user,input){
  743. pickerLi = userContainer.find('ul li.user-picker-li');
  744. if(userContainer.find('li[data-uid="'+user.uid+'"]').length>0) return;
  745. var closeBtn = userContainer.attr('disabled') || userContainer.attr('readonly') ? '' : '<i class="fa fa-times"></i>';
  746. 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>');
  747. pickerLi.before(tag);
  748. var values = pickerFunctions.getValues(userContainer);
  749. input.val(values['uid'].join(','));
  750. input.data('values',values['object']);
  751. if((values['uid'].length) == 1 && input.attr('data-multiple') == null){
  752. pickerLi.addClass('hidden');
  753. }else{
  754. pickerLi.removeClass('hidden');
  755. }
  756. pickerFunctions.checkComponentOverflow(userContainer);
  757. tag.find('i').click(function(){
  758. if(userContainer.attr('disabled') || userContainer.attr('readonly')) return;
  759. $(this).closest('.user-picker-tag').remove();
  760. var values = pickerFunctions.getValues(userContainer);
  761. input.val(values['uid'].join(','));
  762. input.data('values',values['object']);
  763. pickerLi = userContainer.find('ul li.user-picker-li');
  764. if((values['uid'].length-1) ==1 && input.attr('data-multiple') == null){
  765. pickerLi.addClass('hidden');
  766. }else{
  767. pickerLi.removeClass('hidden');
  768. }
  769. input.trigger('change');
  770. pickerFunctions.checkComponentOverflow(userContainer);
  771. });
  772. if(userContainer.find('ul li.user-picker-tag'))
  773. userContainer.find('ul li.user-picker-li input').removeAttr('placeholder');
  774. }
  775. }
  776. //Gestion des champs déja remplis au chargement de la page
  777. if(input.val() !=''){
  778. var uid = input.val();
  779. if(!window.componentQueue.user) window.componentQueue.user = {timeout : null,components : [],uids :{} };
  780. clearTimeout(window.componentQueue.user.timeout);
  781. var uids = uid.split(',');
  782. for(var k in uids)
  783. window.componentQueue.user.uids[uids[k]] = 1;
  784. window.componentQueue.user.components.push({
  785. input : input,
  786. picker : userPicker,
  787. container : userContainer,
  788. values : uids
  789. });
  790. userContainer.find('ul').append('<li class="user-picker-loader"><i class="fas fa-spinner fa-pulse"></i> Chargement</li>');
  791. userPicker.addClass('hidden');
  792. window.componentQueue.user.timeout = setTimeout(function(){
  793. $.action({
  794. action : 'user_by_uid',
  795. uids : Object.keys(window.componentQueue.user.uids)
  796. },function(r){
  797. for(var key in window.componentQueue.user.components){
  798. var component = window.componentQueue.user.components[key];
  799. component.container.find('.user-picker-loader').remove();
  800. component.picker.removeClass('hidden');
  801. input = window.componentQueue.user.components[key].input;
  802. for(var i in component.values){
  803. var value = component.values[i];
  804. if(!r.users[value]) continue;
  805. pickerFunctions.addTag(component.container,r.users[value],input);
  806. }
  807. delete window.componentQueue.user.components[key];
  808. }
  809. });
  810. },50);
  811. }
  812. userContainer.find('.unfold').click(function(e){
  813. e.stopPropagation();
  814. var position = userContainer.position();
  815. var width = userContainer.outerWidth();
  816. userContainer
  817. .addClass('unfolded')
  818. .css({
  819. top: position.top+'px',
  820. width: width+'px',
  821. left: position.left+'px'
  822. });
  823. $(document).mousedown(function(e){
  824. if($(e.target).closest('div.data-type-user').length || !userContainer.hasClass('unfolded')) return;
  825. window.userPickerTimeout = userContainer.removeClass('unfolded')
  826. .css({
  827. top: 'inherit',
  828. width: 'inherit',
  829. left: 'inherit'
  830. });
  831. pickerFunctions.checkComponentOverflow(userContainer);
  832. });
  833. });
  834. //Sélectionne l'input d'auto-completion ou que l'on clique dans le composant
  835. userContainer.find('ul').click(function(e){
  836. if(input.attr("readonly") == "readonly") return;
  837. userPicker.focus();
  838. e.stopPropagation();
  839. });
  840. //Selectionne l'item dropdown actif lors de l'appui sur entrée
  841. userPicker.keyup(function(e){
  842. if(e.keyCode!=13 || input.is('[readonly]') || input.is('[disabled]')) return;
  843. if(input.attr("readonly") == "readonly") return;
  844. var active = $('.user-picker-li .dropdown-menu .active');
  845. if(active.length==0) return;
  846. active.trigger('click').trigger('change');
  847. });
  848. var types = ['user'];
  849. if(input.data('types') && input.data('types')!='')
  850. types = input.data('types').split(',');
  851. //aucompletion sur le nom des users / rangs
  852. userPicker.autocomplete({
  853. action : 'user_autocomplete',
  854. data : {
  855. types : types,
  856. scope : input.attr('data-scope')
  857. },
  858. skin : function(item){
  859. var html = '';
  860. var re = new RegExp(escape_regex_chars(userPicker.val()),"gi");
  861. name = item.name.replace(re, function (x) {
  862. return '<strong>'+x+'</strong>';
  863. });
  864. if(item.type=='user'){
  865. if(item.avatar) html += '<div class="user-logo"><img src="'+item.avatar+'" class="avatar-mini avatar-rounded"></div>';
  866. html += '<div class="user-infos"><span>'+name+'</span> <small class="text-muted">- Utilisateur (@'+item.id+')</small>';
  867. html += item.function ? '<br/><small>'+item.function+'</small></div>' : '</div>';
  868. html += '<div class="clear"></div>';
  869. }else{
  870. html += '<div class="rank-logo"><i class="far fa-address-card"></i></div>';
  871. html += '<div class="rank-infos"><span>'+name+'</span> <small class="text-muted">- Rang</small>';
  872. html += item.description ? '<br/><small>'+item.description+'</small></div>' : '</div>';
  873. html += '<div class="clear"></div>';
  874. }
  875. return html;
  876. },
  877. highlight : function(item){
  878. return item;
  879. },
  880. onClick : function(selected,element){
  881. clearTimeout(window.userPickerTimeout);
  882. userPicker.val('');
  883. pickerFunctions.addTag(userContainer,selected,input);
  884. input.trigger('click').trigger('change');
  885. },
  886. onBlur : function(selected){
  887. if(input.attr('data-force')!='false' && input.val() == '') userPicker.val('');
  888. }
  889. });
  890. break;
  891. /**
  892. * data-labels : tableau des libellés entre double quotes eg : ["Libellé 1","Libellé 2"]
  893. * data-values : taleau des valeurs eg : [12,13]
  894. * data-colors : taleau des couleurs entre double quotes eg : ["#cecece","#222222"]
  895. */
  896. case 'doughnut':
  897. var data = input.data();
  898. if(data.height) $(input).attr('height',data.height);
  899. var myChart = new Chart(input.get(0).getContext('2d'), {
  900. type: 'doughnut',
  901. data: {
  902. labels: data.labels,
  903. datasets: [{
  904. data: data.values,
  905. backgroundColor: data.colors
  906. }]
  907. },
  908. options: {
  909. cutoutPercentage:80,
  910. legend: {
  911. display: (data.legend && data.legend===true)
  912. }
  913. }
  914. });
  915. break;
  916. /**
  917. * data-labels : tableau des libellés entre double quotes eg : ["Libellé1","Libellé 2"]
  918. * data-values : taleau des valeurs eg : [12,13]
  919. * data-colors : taleau des couleurs entre double quotes eg : ["#cecece","#222222"]
  920. */
  921. case 'bar':
  922. var data = input.data();
  923. var myChart = new Chart(input.get(0).getContext('2d'), {
  924. type: 'bar',
  925. data: {
  926. labels: data.labels,
  927. datasets: [{
  928. label : input.html(),
  929. data: data.values,
  930. backgroundColor: data.colors
  931. }]
  932. },
  933. options: {
  934. scales: {
  935. xAxes: [{
  936. ticks: {
  937. beginAtZero:true,
  938. autoSkip:false,
  939. maxRotation:90,
  940. minRotation:80
  941. }
  942. }],
  943. yAxes: [{
  944. ticks: {
  945. beginAtZero:true,
  946. autoSkip:false
  947. }
  948. }]
  949. },
  950. legend: {
  951. display: (data.legend && data.legend==true)
  952. }
  953. }
  954. });
  955. break;
  956. /**
  957. * data-labels : tableau des 2 libellés courant et max entre double quotes eg : ["Atteint","A Atteindre"]
  958. * data-values : taleau des valeurs courant et max : [50,100] (donne une jauge allant de 0 à 100 remplie a 50%)
  959. * data-colors : taleau des 2 couleurs "remplis" et "non remplis" entre double quotes eg : ["#cecece","#222222"] (optionnel)
  960. * data-unity : unité à affher a coté des libellé min, max et courant
  961. */
  962. case 'gauge':
  963. var data = input.data();
  964. Chart.pluginService.register({
  965. beforeDraw: function(chart) {
  966. // Get ctx from string
  967. var ctx = chart.chart.ctx;
  968. if(chart.config.options.elements.center){
  969. // Get options from the center object in options
  970. var centerConfig = chart.config.options.elements.center;
  971. var sidePaddingCalculated = (centerConfig.sidePadding / 100) * (chart.innerRadius * 2);
  972. // Get the width of the string and also the width of the element minus 10 to give it 5px side padding
  973. var stringWidth = ctx.measureText(centerConfig.text).width;
  974. var elementWidth = (chart.innerRadius * 2) - sidePaddingCalculated;
  975. var centerWidth = stringWidth;
  976. // Find out how much the font can grow in width.
  977. var widthRatio = elementWidth / stringWidth;
  978. var newFontSize = Math.floor(30 * widthRatio);
  979. var elementHeight = (chart.innerRadius * 2);
  980. // Pick a new font size so it will not be larger than the height of label.
  981. var fontSizeToUse = Math.min(newFontSize, elementHeight, centerConfig.maxFontSize);
  982. // Set font settings to draw it correctly.
  983. ctx.textAlign = 'center';
  984. ctx.textBaseline = 'middle';
  985. var centerX = ((chart.chartArea.left + chart.chartArea.right) / 2);
  986. var centerY = ((chart.chartArea.top + chart.chartArea.bottom) / 2) + 20;
  987. ctx.font = "1.7em Arial";
  988. ctx.fillStyle = centerConfig.color;
  989. ctx.fillText(centerConfig.text, centerX, centerY);
  990. }
  991. if(chart.config.options.elements.minimum){
  992. // Get options from the center object in options
  993. var minimumConfig = chart.config.options.elements.minimum;
  994. var sidePaddingCalculated = (minimumConfig.sidePadding / 100) * (chart.innerRadius * 2);
  995. // Get the width of the string and also the width of the element minus 10 to give it 5px side padding
  996. var stringWidth = ctx.measureText(minimumConfig.text).width;
  997. var elementWidth = (chart.innerRadius * 2) - sidePaddingCalculated;
  998. // Find out how much the font can grow in width.
  999. var widthRatio = elementWidth / stringWidth;
  1000. var newFontSize = Math.floor(30 * widthRatio);
  1001. var elementHeight = (chart.innerRadius * 2);
  1002. // Pick a new font size so it will not be larger than the height of label.
  1003. var fontSizeToUse = Math.min(newFontSize, elementHeight, minimumConfig.maxFontSize);
  1004. // Set font settings to draw it correctly.
  1005. ctx.textAlign = 'center';
  1006. ctx.textBaseline = 'center';
  1007. var minimumX = chart.radiusLength +20;
  1008. var minimumY = chart.height - chart.offsetY - 8;
  1009. ctx.font = "1em Arial";
  1010. ctx.fillStyle = minimumConfig.color;
  1011. ctx.fillText(minimumConfig.text, minimumX, minimumY);
  1012. }
  1013. if(chart.config.options.elements.maximum){
  1014. // Get options from the center object in options
  1015. var maximumConfig = chart.config.options.elements.maximum;
  1016. var sidePaddingCalculated = (maximumConfig.sidePadding / 100) * (chart.innerRadius * 2);
  1017. // Get the width of the string and also the width of the element minus 10 to give it 5px side padding
  1018. var stringWidth = ctx.measureText(maximumConfig.text).width;
  1019. var elementWidth = (chart.innerRadius * 2) - sidePaddingCalculated;
  1020. // Find out how much the font can grow in width.
  1021. var widthRatio = elementWidth / stringWidth;
  1022. var newFontSize = Math.floor(30 * widthRatio);
  1023. var elementHeight = (chart.innerRadius * 2);
  1024. // Pick a new font size so it will not be larger than the height of label.
  1025. var fontSizeToUse = Math.min(newFontSize, elementHeight, maximumConfig.maxFontSize);
  1026. // Set font settings to draw it correctly.
  1027. ctx.textAlign = 'left';
  1028. ctx.textBaseline = 'center';
  1029. var maximumX = chart.width - chart.radiusLength - 15 - stringWidth;
  1030. var maximumY = chart.height - chart.offsetY - 8;
  1031. ctx.font = "1em Arial";
  1032. ctx.fillStyle = maximumConfig.color;
  1033. ctx.fillText(maximumConfig.text, maximumX, maximumY);
  1034. }
  1035. }
  1036. });
  1037. if(data.height) $(input).attr('height',data.height);
  1038. if (!data.colors){
  1039. var doneColor = '#e74c3c';
  1040. var percentage = data.values[0] * 100 / data.values[1];
  1041. percentage = percentage > 100 ? 100 : percentage;
  1042. //Calcul de la couleur en fonction du pourcentage de progression
  1043. if(percentage<=20) doneColor = '#e74c3c';
  1044. if(percentage>20 && percentage<=40) doneColor = '#e67e22';
  1045. if(percentage>40 && percentage<=60) doneColor = '#2980b9';
  1046. if(percentage>60 && percentage<=80) doneColor = '#16a085';
  1047. if(percentage>80) doneColor = '#27ae60';
  1048. //[couleur du fait,couleur du restant
  1049. data.colors = [doneColor,'#bdc3c7'];
  1050. }
  1051. var myChart = new Chart(input.get(0).getContext('2d'), {
  1052. type: 'doughnut',
  1053. data: {
  1054. labels: [data.labels[0],data.labels[1]],
  1055. datasets: [{
  1056. data: [percentage,100-percentage],
  1057. backgroundColor: data.colors
  1058. }],
  1059. realDatasets : [data.values[0],data.values[1]]
  1060. },
  1061. options: {
  1062. tooltips : {
  1063. callbacks : {
  1064. title: function(tooltipItem, data) {
  1065. return data['labels'][tooltipItem[0]['index']];
  1066. },
  1067. label: function(tooltipItem, data) {
  1068. return data['realDatasets'][tooltipItem['index']];
  1069. },
  1070. afterLabel: function(tooltipItem, data) {
  1071. return '('+data['datasets'][0]['data'][tooltipItem['index']]+'%)';
  1072. }
  1073. }
  1074. },
  1075. elements: {
  1076. minimum: {
  1077. text: '0'+data.unity,
  1078. color: '#333333', // Default is #000000
  1079. sidePadding: 20, // Default is 20 (as a percentage)
  1080. minFontSize: 12, // Default is 20 (in px), set to false and text will not wrap.
  1081. lineHeight: 12 // Default is 25 (in px), used for when text wraps
  1082. },
  1083. center: {
  1084. text: data.values[0]+data.unity,
  1085. color: '#333333', // Default is #000000
  1086. sidePadding: 20, // Default is 20 (as a percentage)
  1087. minFontSize: 32, // Default is 20 (in px), set to false and text will not wrap.
  1088. lineHeight: 12 // Default is 25 (in px), used for when text wraps
  1089. },
  1090. maximum: {
  1091. text: data.values[1]+data.unity,
  1092. color: '#333333', // Default is #000000
  1093. sidePadding: 20, // Default is 20 (as a percentage)
  1094. minFontSize: 12, // Default is 20 (in px), set to false and text will not wrap.
  1095. lineHeight: 12 // Default is 25 (in px), used for when text wraps
  1096. }
  1097. },
  1098. circumference: Math.PI ,
  1099. rotation: -Math.PI ,
  1100. cutoutPercentage:75,
  1101. legend: {
  1102. display: (data.legend && data.legend===true)
  1103. }
  1104. }
  1105. });
  1106. break;
  1107. /**
  1108. * data-labels : tableau des libellés entre double quotes eg : ["Libellé1","Libellé 2"]
  1109. * data-values : taleau des valeurs eg : [12,13]
  1110. * data-color : couleurs eg : #cecece
  1111. */
  1112. case 'line':
  1113. var data = input.data();
  1114. var myChart = new Chart(input.get(0).getContext('2d'), {
  1115. type: 'line',
  1116. data: {
  1117. labels: data.labels,
  1118. datasets: [{
  1119. label : input.html(),
  1120. data: data.values,
  1121. borderColor: [data.color]
  1122. }]
  1123. },
  1124. options: {
  1125. scales: {
  1126. xAxes: [{
  1127. ticks: {
  1128. beginAtZero:true,
  1129. autoSkip:false,
  1130. maxRotation:90,
  1131. minRotation:80
  1132. }
  1133. }],
  1134. yAxes: [{
  1135. ticks: {
  1136. beginAtZero:true,
  1137. autoSkip:false
  1138. }
  1139. }]
  1140. }
  1141. }
  1142. });
  1143. break;
  1144. /**
  1145. * data-table : tableau dynamique js tri, recherche, pagination...
  1146. * data-pageLength : nombre de résultats à afficher
  1147. */
  1148. case 'data-table':
  1149. var data = input.data();
  1150. var options = {
  1151. pageLength: input.attr('data-pageLength') != null ? input.attr('data-pageLength') : 20,
  1152. destroy: true,
  1153. pagingType: "simple_numbers",
  1154. fixedHeader: true,
  1155. lengthMenu: [ [10, 20, 40, -1], [10, 20, 40, "Tous"] ],
  1156. language: {
  1157. url: "plugin/statistic/js/dataTables.fr.translation.json"
  1158. },
  1159. order: [], // Disable on load sort
  1160. ordering: 'true' // Manage orderable options
  1161. };
  1162. options = $.extend(options,data);
  1163. input.DataTable(options);
  1164. break;
  1165. /**
  1166. * data-filter-country : filtre par pays à rechercher séparés par virgule (format ISO 3 numériques, ex : FRA)
  1167. * data-filter-items : nb de résultats à retourner
  1168. * data-filter-language : langue dans laquelle retourner les résultats
  1169. *
  1170. * data-select-callback : fonction de callback appelée lors de la sélection d'un élément
  1171. *
  1172. * data-filter-geocode : retourne les infos complémentaires de l'élément sélectionné (latitude, longitude,etc...)
  1173. * data-geocode-callback : fonction de callback appelée pour traiter les infos retournées via geocode
  1174. */
  1175. case 'location':
  1176. input.location({
  1177. items: input.attr('data-filter-items'),
  1178. language: input.attr('data-filter-language'),
  1179. country: input.attr('data-filter-country'),
  1180. geocode: input.attr('data-filter-geocode') != null ? true : false,
  1181. select: function(location){
  1182. if(input.attr('data-select-callback')!=null && input.attr('data-select-callback').length) {
  1183. var select = input.attr('data-select-callback').replace(/[^a-z0-9]/i,'_');
  1184. if(window[select] !=null) window[select](location);
  1185. }
  1186. },
  1187. geocode: function(location){
  1188. $.action({
  1189. action: 'location_detail_search',
  1190. locationId: location.locationId
  1191. }, function(r){
  1192. if(input.attr('data-geocode-callback')!=null && input.attr('data-geocode-callback').length) {
  1193. var geocode = input.attr('data-geocode-callback').replace(/[^a-z0-9]/i,'_');
  1194. if(window[geocode] !=null) window[geocode](r);
  1195. }
  1196. });
  1197. }
  1198. });
  1199. var data = input.data();
  1200. input.removeData();
  1201. input.data({
  1202. type : data.type,
  1203. country : data.country,
  1204. zip : data.zip,
  1205. city : data.city,
  1206. complement : data.complement,
  1207. street : data.street
  1208. });
  1209. break;
  1210. /**
  1211. * data-source : tableau de clés / valeurs au format json base 64 encodé
  1212. * data-value : la valeur de l'entité à récup en base (clé de l'item)
  1213. */
  1214. /*case 'list':
  1215. var source = input.attr('data-source');
  1216. if(source != '' && source != null && btoa(atob(source)) == source){
  1217. input.html('');
  1218. input.append('<option value=""> - </option>');
  1219. var items = JSON.parse(atob(source));
  1220. //boucle sur les entrées possibles de sources de données
  1221. for(const [key, source] of Object.entries(items))
  1222. //boucle sur les couples clés valeurs de la source de données
  1223. for(const [index, value] of Object.entries(source))
  1224. input.append('<option value="'+index+'" '+(index == input.attr('data-value') ? "selected" : '')+'>'+value+'</option>');
  1225. }
  1226. break;*/
  1227. /**
  1228. * data-depth : nb de profondeur de liste (ex: 2, affichera 2 select au maximum), 1 par défaut
  1229. * data-slug : le slug de la liste mère à afficher, listes primaires par défaut
  1230. * data-value : la valeur de l'entité à récup en base
  1231. * data-disable-label : cache le label de sous-liste si mentionné
  1232. * data-hierarchy : si mentionné à false, ne récupère pas
  1233. * data-parent-id : l'id de la liste parente associée
  1234. * data-output : id/slug définit la valueur de sortie de l'input (slug de la liste ou id de la liste (defaut))
  1235. */
  1236. case 'dictionnary':
  1237. var data = input.data();
  1238. var slug = input.attr('data-slug') ? input.attr('data-slug') : "";
  1239. var parentId = input.attr('data-parent-id') && input.attr('data-parent-id').length ? input.attr('data-parent-id') : "";
  1240. //if (!slug.match(/^[a-z\d\-_]+$/i) && parentId == '') return;
  1241. data.output = data.output ? data.output : 'id';
  1242. $.action({
  1243. action : 'load_dictionnary_component',
  1244. slug: slug,
  1245. parentId : parentId,
  1246. depth : data.depth,
  1247. hierarchy : input.attr('data-hierarchy') == 'false' ? 0 : 1,
  1248. value: input.attr('data-value')
  1249. },function(r){
  1250. var children = r.content && r.content.childs ? r.content.childs : r.content;
  1251. input.attr('onchange', 'get_sub_dictionnary(this, "'+input.attr('name')+'",'+1+');');
  1252. input.html('');
  1253. input.append('<option value=""> - </option>');
  1254. $.each(children, function (index, value){
  1255. if (value.selected) {
  1256. input.append('<option value="'+value[data.output]+'" data-slug="'+value.slug+'" data-parent="'+value.parent+'" data-sublabel="'+value.sublistlabel+'" selected>'+value.label+'</option>');
  1257. get_selected_values(input, value);
  1258. }else{
  1259. input.append('<option value="'+value[data.output]+'" data-slug="'+value.slug+'" data-parent="'+value.parent+'" data-sublabel="'+value.sublistlabel+'">'+value.label+'</option>');
  1260. }
  1261. });
  1262. });
  1263. break;
  1264. /**
  1265. * data-label : le label affiché dans la zone
  1266. * data-delete : méthode de suppression de doc de l'entité
  1267. * data-save : méthode de sauvegarde de doc de l'entité (si mentionné, save automatique)
  1268. * data-readonly: Empeche l'ajout/suppression de documents
  1269. * data-allowed : les extensions de fichier acceptées
  1270. */
  1271. case 'dropzone':
  1272. console.warn('[DEPRECATED] : Dropzone doit être remplacé par le composant file');
  1273. if(input.find('form').length != 0 || input.find('ul>li').length) break;
  1274. if(!input.attr('data-action')) input.attr('data-action','action.php?action=upload_temporary_file');
  1275. var readonly = input.attr('data-readonly') == "true" ? true : false;
  1276. if(!input.get(0).hasAttribute('id')) input.attr('id',generate_uuid(10));
  1277. var customTpl = input.find('> *:not(:visible)');
  1278. var customActions = '';
  1279. if(customTpl && customTpl.length){
  1280. $.each(customTpl, function(i, action){
  1281. if(i>2) return;
  1282. var valCalc = readonly ? i*28+3 : i*28+25;
  1283. customActions += $(action).removeClass('hidden').css('right', valCalc).get(0).outerHTML;
  1284. });
  1285. }
  1286. var preview = '<li data-path="{{path}}" data-ext="{{ext}}" data-url="{{url}}" data-name="{{name}}" data-last-modification="{{lastModification}}">'+
  1287. '<a {{#url}}href="{{url}}"{{/url}} target="_blank" title="{{name}}" class="text-decoration-none media">';
  1288. 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>';
  1289. 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>';
  1290. var valueFiles = input.html()!='' && is_json_string(input.text()) ? JSON.parse(input.text()) : [];
  1291. input.html('');
  1292. var allowed = input.attr('data-allowed');
  1293. if(allowed) allowed = allowed.split(',');
  1294. var save = input.attr('data-save');
  1295. var size = input.attr('data-max-size');
  1296. var maxFile = input.attr('data-max-files');
  1297. maxFile = 100; //maxFile=='' || ! maxFile ? 0 : maxFile;
  1298. input.upload({
  1299. allowed : allowed,
  1300. size : size == '' ? 0 : size,
  1301. readonly: readonly,
  1302. addData: function(){
  1303. return {index: input.attr('id')};
  1304. },
  1305. start: function(){
  1306. isProcessing = true;
  1307. if(maxFile!=0 && $('li:visible',files).length >=maxFile ) return -1;
  1308. preload.removeClass('hidden');
  1309. },
  1310. success: function(response){
  1311. isProcessing = false;
  1312. if(response.error){
  1313. preload.addClass('hidden');
  1314. $.message('error', response.error, 0);
  1315. return;
  1316. }
  1317. if(response.previews.length && response.previews[0].name) {
  1318. var inputTemp = $('#'+input.attr('id')+'_temporary');
  1319. var currVal = inputTemp.val().length ? JSON.parse(inputTemp.val()) : [];
  1320. for(var i in response.previews){
  1321. files.append(Mustache.render(preview,response.previews[i]));
  1322. currVal.push(response.previews[i]);
  1323. }
  1324. inputTemp.val(JSON.stringify(currVal));
  1325. if(save) window[save](response.previews);
  1326. }
  1327. preload.addClass('hidden');
  1328. },
  1329. complete: function(){
  1330. isProcessing = false;
  1331. preload.addClass('hidden');
  1332. },
  1333. error: function(){
  1334. isProcessing = false;
  1335. preload.addClass('hidden');
  1336. },
  1337. });
  1338. var preload = $('<div class="preload progress-bar progress-bar-striped progress-bar-animated hidden"></div>');
  1339. input.append(preload);
  1340. var files = $('<ul class="my-auto mx-0 w-100 pb-0"></ul>');
  1341. input.append(files);
  1342. var filesValues = $('<input type="hidden" name="'+input.attr('id')+'_temporary" id="'+input.attr('id')+'_temporary">');
  1343. input.append(filesValues);
  1344. for(var i in valueFiles) {
  1345. files.append(Mustache.render(preview,valueFiles[i]));
  1346. if(readonly) files.find('li > i.fa-times').remove();
  1347. }
  1348. if(!valueFiles.length && readonly)
  1349. input.append('<div>Aucun document</div>');
  1350. break;
  1351. case 'file':
  1352. if(!input.get(0).hasAttribute('data-id')) input.attr('data-id',generate_uuid(10));
  1353. var droppedFiles = false;
  1354. //Récuperation des data attributs de l'input
  1355. var data = input.data();
  1356. //extra données (eg.id de l'entité liée)
  1357. if(input.attr('data-data')){
  1358. try{
  1359. data.data = input.attr('data-data') ? JSON.parse(input.attr('data-data')) : {};
  1360. }catch(e){
  1361. data.data = {};
  1362. }
  1363. }
  1364. var options = $.extend({
  1365. allStart : function(files){return true},
  1366. allComplete : function(){},
  1367. fileStart : function(file){return true},
  1368. fileComplete : function(file){},
  1369. fileError : function(file){},
  1370. fileProgress : function(file){},
  1371. over : 'file-hover',
  1372. label : 'Faites glisser vos fichiers ici',
  1373. data : {},
  1374. limit : 0,
  1375. readonly : false,
  1376. onVisible : true
  1377. },data);
  1378. if(input.hasAttr('readonly')) options.readonly = true;
  1379. var component = data.component;
  1380. //initialisation du composant et liaison à l'input si pas déja fait
  1381. if(!component){
  1382. var component = $($('#component-file-template').get(0).outerHTML);
  1383. component.attr('class',input.attr('class')).removeClass('hidden').removeAttr('id');
  1384. input.after(component).data('component',component);
  1385. input.attr('data-multiple-values','true');
  1386. }
  1387. if(options.onVisible === true && !component.is(':visible')) break;
  1388. var lineTpl = $('#component-file-template .upload-list').html();
  1389. var removeLine = function(element){
  1390. form.find('input[type="file"]').val('');
  1391. if(element.attr('data-path')==''){
  1392. element.remove();
  1393. component.attr('data-file-number',uploadList.find('>li').length);
  1394. return;
  1395. }
  1396. $.action({
  1397. action: data.action,
  1398. type: 'delete',
  1399. path: atob(element.attr('data-path')),
  1400. data: data.data
  1401. },function(r){
  1402. updateValue(input,{path:element.attr('data-path')},true);
  1403. element.remove();
  1404. component.attr('data-file-number',uploadList.find('>li').length);
  1405. });
  1406. }
  1407. //Ajout d'une ligne fichier
  1408. var addLine = function(tpl,data){
  1409. var component = input.data('component');
  1410. if(data.path) data.path = btoa(data.path);
  1411. var line = $(Mustache.render(tpl,data));
  1412. if(options.buttons) line.find('.file-button').html($(options.buttons).html());
  1413. component.find('.upload-list').append(line);
  1414. //affichage du preview avec pré-chargement pour eviter le blink
  1415. if(data.preview){
  1416. $('<img/>').attr('src', data.preview).on('load', function() {
  1417. $(this).remove();
  1418. line.find('.file-preview').attr('style','background-image:url('+data.preview+')');
  1419. line.addClass('preview-loaded');
  1420. });
  1421. }else{
  1422. line.addClass('no-preview');
  1423. }
  1424. line.find('.btn-delete').click(function(){
  1425. if(!line.hasClass('.upload-error')){
  1426. if(!confirm('Êtes-vous sûr de vouloir supprimer ce fichier ?')) return;
  1427. }
  1428. removeLine(line);
  1429. input.trigger('change');
  1430. });
  1431. return line;
  1432. }
  1433. //Ajout d'un ensemble de fichiers
  1434. var refreshFiles = function(tpl,input,data){
  1435. var component = input.data('component');
  1436. component.find('.upload-list li').remove();
  1437. for (var k in data.files) {
  1438. addLine(tpl,data.files[k]);
  1439. }
  1440. }
  1441. var updateValue = function(input,data,toDelete){
  1442. var jsonValue = JSON.parse(input.val()?input.val():'[]');
  1443. if(toDelete){
  1444. //delete
  1445. for(var k in jsonValue)
  1446. if(btoa(jsonValue[k].path) == data.path) jsonValue.splice(k,1);
  1447. }else{
  1448. //add
  1449. data.path = atob(data.path);
  1450. jsonValue.push(data);
  1451. }
  1452. input.val(JSON.stringify(jsonValue));
  1453. }
  1454. if(input.hasAttr('required')) component.attr('required','');
  1455. if(!options.data.id && $.urlParam('id')) options.data.id = $.urlParam('id');
  1456. //Récuperation des extensions, de la taille, des fichiers existants
  1457. $.action({
  1458. action : options.action,
  1459. type : 'search',
  1460. data : options.data
  1461. },function(r){
  1462. if(r.options) options = $.extend(options,r.options);
  1463. refreshFiles(lineTpl,input,{files : r.files});
  1464. component.attr('data-file-number',r.files.length);
  1465. });
  1466. //formulaire d'envois
  1467. var form = component.find('form');
  1468. //input d'upload caché
  1469. var fileInput = form.find('input[type="file"]');
  1470. //drop & click zone
  1471. var uploadZone = component.find('.upload-zone');
  1472. //liste des fichiers
  1473. var uploadList = component.find('.upload-list');
  1474. form.attr('action','action.php?action='+options.action+'&type=upload');
  1475. uploadZone.find('.file-upload-label').html(options.label);
  1476. component.attr('data-file-number',0);
  1477. if(options.readonly !== false) component.addClass('readonly');
  1478. //test if d&d is enabled n browser
  1479. var div = document.createElement('div');
  1480. var dragAndDropEnabled = (('draggable' in div) || ('ondragstart' in div && 'ondrop' in div)) && 'FormData' in window && 'FileReader' in window;
  1481. //set events
  1482. if(options.readonly === false){
  1483. uploadZone
  1484. .off('click drag dragstart dragend dragenter dragleave drop dragover').on('click', function (e) {
  1485. fileInput.trigger('click');
  1486. e.preventDefault();
  1487. e.stopPropagation();
  1488. })
  1489. .on('drag dragstart dragend dragover dragenter dragleave drop', function (e) {
  1490. e.preventDefault();
  1491. e.stopPropagation();
  1492. })
  1493. .on('dragover dragenter', function () {
  1494. form.addClass(options.hover);
  1495. })
  1496. .on('dragleave dragend drop', function () {
  1497. form.removeClass(options.hover);
  1498. })
  1499. .on('drop', function (e) {
  1500. droppedFiles = e.originalEvent.dataTransfer.files;
  1501. form.trigger('submit');
  1502. });
  1503. }
  1504. //si l'input file change (on a choisis un fichier) on lance la soumission du formulaire
  1505. fileInput.off('change').on('change', function (e) {
  1506. form.trigger('submit');
  1507. });
  1508. //sur soumission du formulaire
  1509. form.off('submit').on('submit', function (e) {
  1510. e.preventDefault();
  1511. //fonctionnement browser modernes
  1512. if (!dragAndDropEnabled) throw 'Drag & drop non fonctionnel, veuillez mettre à jour votre navigateur';
  1513. var fileProcessed = [];
  1514. //si pas de drop mode, on récuepre le mode click
  1515. if(!droppedFiles) droppedFiles = $('input',form).get(0).files;
  1516. var fileQueue = [];
  1517. var allowedExtensions = options.extension ? options.extension.split(',') : [];
  1518. $.each(droppedFiles, function (i, file) {
  1519. //line data
  1520. var lineData = {
  1521. extension : file.name.split('.').pop(),
  1522. label : file.name,
  1523. sort : i
  1524. };
  1525. if(options.limit!=0 && i > options.limit) return;
  1526. //dom element
  1527. var fileLine = addLine(lineTpl,lineData);
  1528. //ajout a la file d'upload
  1529. fileQueue.push({
  1530. file : file,
  1531. data : lineData,
  1532. element : fileLine
  1533. });
  1534. });
  1535. droppedFiles = null;
  1536. if(!options.allStart(fileQueue)) return;
  1537. for(var k in fileQueue) {
  1538. var file = fileQueue[k].file;
  1539. fileQueue[k].element.attr('data-sort',k);
  1540. try{
  1541. if(allowedExtensions.length !=0 && allowedExtensions.indexOf(fileQueue[k].data.extension.toLowerCase()) === -1 )
  1542. throw "Extension non permise, autorisé : "+allowedExtensions.join(', ');
  1543. if(options.size && file.size > options.size)
  1544. throw "Taille fichier "+file.size+" octets trop grande (max autorisé:"+options.size+" octets";
  1545. var ajaxData = new FormData();
  1546. ajaxData.append(input.attr('data-id'), file);
  1547. ajaxData.append('index', input.attr('data-id'));
  1548. ajaxData.append('sort', k);
  1549. if(!options.fileStart(fileQueue[k])) continue;
  1550. $.ajax({
  1551. url: form.attr('action'),
  1552. type: form.attr('method'),
  1553. data: ajaxData,
  1554. dataType: 'json',
  1555. cache: false,
  1556. contentType: false,
  1557. processData: false,
  1558. complete: function (data) {
  1559. var response = data.responseJSON ? data.responseJSON : {error :data.responseText,sort:-1 };
  1560. if(response.sort!=-1){
  1561. var currentFile = fileQueue[response.sort];
  1562. if(response.error) currentFile.element.addClass('upload-error').find('.error-message').html(response.error).attr('title',response.error);
  1563. currentFile.element.addClass('upload-complete');
  1564. }else{
  1565. if(response.error) $.message('error',response.error);
  1566. }
  1567. fileQueue[response.sort] = true;
  1568. var completed = 0;
  1569. for(var u in fileQueue){
  1570. if(fileQueue[u]===true) completed++;
  1571. }
  1572. if(fileQueue.length == completed){
  1573. if(window[options.allComplete]) window[options.allComplete](response);
  1574. input.trigger('change');
  1575. }
  1576. },
  1577. success: function (data) {
  1578. var currentFile = fileQueue[data.sort];
  1579. options.fileComplete(currentFile);
  1580. var newElement = addLine(lineTpl,data);
  1581. currentFile.element.replaceWith(newElement);
  1582. updateValue(input,data);
  1583. var lines = uploadList.find('>li');
  1584. if(options.limit != 0 && lines.length > options.limit){
  1585. uploadList.find('>li').each(function(i,li){
  1586. if(i < lines.length - options.limit ) $(li).remove();
  1587. });
  1588. }
  1589. component.attr('data-file-number',lines.length);
  1590. newElement.addClass('upload-success');
  1591. },
  1592. error: function (data) {
  1593. var response = data.responseJSON ? data.responseJSON : {error :data.responseText,sort:-1 };
  1594. if(response.sort!=-1){
  1595. var currentFile = fileQueue[data.sort];
  1596. options.fileError(currentFile);
  1597. currentFile.element.addClass('upload-error');
  1598. fileQueue[response.sort] = true;
  1599. }else{
  1600. $.message('error',response.error);
  1601. }
  1602. },
  1603. xhr: function() {
  1604. var xhr = new window.XMLHttpRequest();
  1605. xhr.sort = k;
  1606. xhr.upload.addEventListener("progress", function(evt){
  1607. if (evt.lengthComputable) {
  1608. var percentComplete = (evt.loaded / evt.total) * 100;
  1609. percentComplete = Math.round(percentComplete * 100) / 100;
  1610. currentFile = fileQueue[xhr.sort];
  1611. options.fileProgress(fileQueue[xhr.sort],percentComplete);
  1612. var progressBar = $('.progress-bar',currentFile.element)
  1613. .css('width',percentComplete+'%');
  1614. if(percentComplete >= 100) currentFile.element.addClass('upload-process');
  1615. }
  1616. }, false);
  1617. return xhr;
  1618. }
  1619. });
  1620. }catch(e){
  1621. options.fileError(fileQueue[k],e);
  1622. fileQueue[k].element.addClass('upload-error').find('.error-message').html(e).attr('title',e);
  1623. fileQueue[k] = true;
  1624. continue;
  1625. }
  1626. };
  1627. });
  1628. break;
  1629. /*
  1630. Composant table regroupant a terme toutes les options de "table avancée" :
  1631. * les colonnes dynamiques
  1632. * les multi select checkbox
  1633. * les colonnes sortables
  1634. * futures features tableau (ex: filtre sur les colonnes façon excel)
  1635. ce composant est voué a être un composant core un fois rodé
  1636. */
  1637. case 'search-table':
  1638. var SearchTable = function(table,options){
  1639. var table = $(table);
  1640. table.data('searchTable',this);
  1641. this.table = table;
  1642. this.options = options;
  1643. this.slug = options.slug;
  1644. this.actionButton = $(this.options.checkboxAction);
  1645. var object = this;
  1646. //Gestion des checkbox
  1647. var globalCheckBox = object.table.find('.table_checkbox_global');
  1648. if(globalCheckBox.length==0 && object.options.checkboxAction){
  1649. var globalCheckBox =
  1650. 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>');
  1651. globalCheckBox = object.table.find('.table_checkbox_global');
  1652. globalCheckBox.click(function(){
  1653. object.table.find('tbody td:visible input[type="checkbox"]').click();
  1654. });
  1655. 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>');
  1656. object.table.on('click','tr:visible input.table_checkbox_line',function(){
  1657. var element = $(this);
  1658. var checked = element.prop('checked');
  1659. var tr = element.closest('tr');
  1660. var id = tr.attr('data-id');
  1661. if(!id) return;
  1662. var ids = object.checkboxes();
  1663. if(checked) {
  1664. ids.push(id);
  1665. }else{
  1666. var i = ids.indexOf(id);
  1667. if(i > -1) ids.splice(i, 1);
  1668. }
  1669. object.checkboxes(ids)
  1670. object.refreshActionButton();
  1671. });
  1672. }
  1673. if(object.table.attr('data-loaded')!=1){
  1674. var search = window[options.entitySearch];
  1675. //Activation du tri colonne si au moins une en-tete contient le data-sortable
  1676. if(object.table.find('[data-sortable]').length>0){
  1677. object.table.sortable_table({
  1678. onSort : function(){search({keepChecked:true})}
  1679. });
  1680. }
  1681. //lancement de la recherche automatique
  1682. if(search) search();
  1683. object.table.attr('data-loaded',1);
  1684. }
  1685. }
  1686. SearchTable.prototype.refreshActionButton = function(){
  1687. var ids = this.checkboxes();
  1688. this.actionButton.attr('data-count',ids.length).find('span.count').text(ids.length);
  1689. };
  1690. SearchTable.prototype.resetCheckbox = function(){
  1691. this.checkboxes([]);
  1692. this.refreshActionButton();
  1693. };
  1694. SearchTable.prototype.fillCheckbox = function(){
  1695. var selected = this.checkboxes();
  1696. for(var k in selected)
  1697. $('tr:visible[data-id="'+selected[k]+'"] input.table_checkbox_line',this.table).prop('checked',true);
  1698. };
  1699. SearchTable.prototype.resetGlobalCheckbox = function(){
  1700. this.table.find('.table_checkbox_global').prop('checked',false);
  1701. };
  1702. SearchTable.prototype.checkboxes = function(data){
  1703. var tablesearch = localStorage.getItem('component.tablesearch.checked');
  1704. tablesearch = !tablesearch ? {} : JSON.parse(tablesearch);
  1705. if(!data)
  1706. return tablesearch[this.slug] ? tablesearch[this.slug] : [];
  1707. tablesearch[this.slug] = data;
  1708. localStorage.setItem('component.tablesearch.checked', JSON.stringify(tablesearch));
  1709. };
  1710. var searchTable = new SearchTable(input,input.data());
  1711. break;
  1712. /**
  1713. * data-simple : Si "true" alors interface avec moins de boutons
  1714. * data-minimal: Si présent alors affichage simple en mode input inline
  1715. */
  1716. case 'wysiwyg':
  1717. var buttons = [
  1718. ['strong','em','underline','del'],
  1719. ['foreColor', 'backColor'],
  1720. ['fontfamily'],
  1721. ['fontsize'],
  1722. ['undo', 'redo'], // Only supported in Blink browsers
  1723. ['formatting'],
  1724. ['superscript', 'subscript'],
  1725. ['link'],
  1726. ['insertImage'],
  1727. ['table'],
  1728. ['justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull'],
  1729. ['unorderedList', 'orderedList'],
  1730. ['horizontalRule'],
  1731. ['removeformat'],
  1732. ['fullscreen'],
  1733. ['viewHTML']
  1734. ]
  1735. if(input.attr('data-simple')!=null || input.attr('data-simple') == 'true'){
  1736. buttons = [
  1737. ['strong','em','underline','del'],
  1738. ['foreColor', 'backColor'],
  1739. ['undo', 'redo'], // Only supported in Blink browsers
  1740. ['formatting'],
  1741. ['link'],
  1742. ['unorderedList', 'orderedList'],
  1743. ['insertImage']
  1744. ]
  1745. }
  1746. if(input.attr('data-buttons')!=null && input.attr('data-buttons')!='') {
  1747. buttons = is_json_string(input.attr('data-buttons')) ? JSON.parse(input.attr('data-buttons')) : [ input.attr('data-buttons').split(',') ];
  1748. }
  1749. var defaultOptions = {
  1750. btns: buttons,
  1751. lang: 'fr',
  1752. autogrow: input.attr('data-minimal') == null,
  1753. semantic: false,
  1754. plugins : {}
  1755. };
  1756. var data = input.data();
  1757. defaultOptions.tagsToRemove= ['script', 'link'];
  1758. var scriptAllow = input.get(0).hasAttribute('data-script-allow');
  1759. if(scriptAllow) defaultOptions.tagsToRemove= [];
  1760. // gestion des mentions
  1761. defaultOptions.plugins.mention = {keys : {}};
  1762. if(input.attr('data-mention-user') && input.attr('data-mention-user') != ''){
  1763. //mention @
  1764. defaultOptions.plugins.mention.keys['@'] = {
  1765. load : function(data){
  1766. data.event.preventDefault();
  1767. 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>');
  1768. var inputContainer = $('.mention-user-picker-container');
  1769. init_components(inputContainer);
  1770. var picker = $('.mention-user-picker-container input');
  1771. picker.focus();
  1772. var timeout = null;
  1773. picker.click(function(){
  1774. clearTimeout(timeout);
  1775. var value = $(this).val();
  1776. inputContainer.remove();
  1777. data.editor.focus();
  1778. data.textarea.trumbowyg('restoreRange');
  1779. document.execCommand('insertHTML', false, '<span class="data-mention-user" data-mention-type="user" data-mention-value="'+value+'">@'+value+'</span>&nbsp;');
  1780. data.editor.trigger('keyup');
  1781. }).blur(function(){
  1782. //on supprime le composant si blur sans valeur (attente de 200 ms car le blur est trigger avant le click)
  1783. timeout = setTimeout(
  1784. function(){
  1785. inputContainer.remove();
  1786. data.editor.trigger('keyup');
  1787. },200);
  1788. });
  1789. picker.keydown(function(e){
  1790. var code = e.keyCode || e.which;
  1791. //Si appuis sur espace ça casse l'autocompletion
  1792. if(code == 32){
  1793. e.preventDefault();
  1794. var value = picker.val();
  1795. $('.mention-user-picker-container').remove();
  1796. data.editor.focus();
  1797. data.textarea.trumbowyg('restoreRange');
  1798. document.execCommand('insertHTML', false, '@'+value+'&nbsp;');
  1799. data.editor.trigger('keyup');
  1800. }
  1801. });
  1802. }
  1803. };
  1804. }
  1805. if(input.attr('data-mention-object') && input.attr('data-mention-object') != ''){
  1806. //mention #
  1807. defaultOptions.plugins.mention.keys['#'] = {
  1808. load : function(data){
  1809. data.event.preventDefault();
  1810. document.execCommand('insertHTML', false, '<b class="mention-object-picker-container">#<input type="text" class="mention-object-picker" value=""></b>');
  1811. var picker = $('.mention-object-picker');
  1812. picker.focus();
  1813. picker.keydown(function(e){
  1814. var code = e.keyCode || e.which;
  1815. //Si appuis sur espace ça casse l'autocompletion
  1816. if(code == 32){
  1817. e.preventDefault();
  1818. var value = picker.val();
  1819. $('.mention-object-picker-container').remove();
  1820. data.editor.focus();
  1821. data.textarea.trumbowyg('restoreRange');
  1822. document.execCommand('insertHTML', false, '#'+value+'&nbsp;');
  1823. data.editor.trigger('keyup');
  1824. }
  1825. });
  1826. var timeout = null;
  1827. picker.autocomplete({
  1828. action : input.attr('data-mention-object'),
  1829. skin : function(item){
  1830. var html = '';
  1831. var re = new RegExp(picker.val(),"gi");
  1832. name = item.name.replace(re, function (x) {
  1833. return '<strong>'+x+'</strong>';
  1834. });
  1835. html += '<div class="pointer"><span>'+name+'</span>';
  1836. html += '<div class="clear"></div>';
  1837. return html;
  1838. },
  1839. highlight : function(item){
  1840. return item;
  1841. },
  1842. onClick : function(selected,element){
  1843. clearTimeout(timeout);
  1844. picker.val('');
  1845. $('.mention-object-picker-container').remove();
  1846. data.textarea.trumbowyg('restoreRange');
  1847. slug = selected.slug ? selected.slug : selected.name;
  1848. document.execCommand('insertHTML', false, '<span class="data-mention-object" data-mention-type="object" title="'+selected.name+'" data-mention-value="'+selected.id+'">#'+slug+'</span>&nbsp;');
  1849. data.editor.trigger('keyup');
  1850. },
  1851. onBlur : function(selected){
  1852. if(input.val() == '') picker.val('');
  1853. timeout = setTimeout(function(){
  1854. $('.mention-object-picker-container').remove();
  1855. data.editor.trigger('keyup');
  1856. },200);
  1857. }
  1858. });
  1859. }
  1860. };
  1861. }
  1862. var options = $.extend(defaultOptions,data);
  1863. options = $.extend(options, input.data());
  1864. var obj = input.trumbowyg(options);
  1865. //fix pour accepter les tags non sémantiques dans la gestion du highlight de bouttons
  1866. trumb = input.data('trumbowyg');
  1867. trumb.tagToButton['b'] = 'strong';
  1868. trumb.tagToButton['i'] = 'em';
  1869. trumb.tagToButton['u'] = 'underline';
  1870. input.data('trumbowyg',trumb);
  1871. obj.data('trumbowyg',trumb);
  1872. obj.on('tbwblur', function(){ input.trigger('blur'); });
  1873. obj.on('tbwchange', function(){ input.trigger('keypress'); });
  1874. var trumbowygBox = $(input).closest('div.trumbowyg-box');
  1875. trumbowygBox.addClass(input.attr('class')).removeClass('trumbowyg-textarea');
  1876. if(input.attr('required')) trumbowygBox.attr('required', true);
  1877. if(input.attr('readonly')) obj.trumbowyg('disable');
  1878. if(input.val()!='')
  1879. input.trumbowyg('html', input.val());
  1880. if(input.attr('data-minimal') != null){
  1881. trumbowygBox.addClass('trumbowyg-minimal');
  1882. var btnPane = trumbowygBox.find('.trumbowyg-button-pane');
  1883. var editor = trumbowygBox.find('.trumbowyg-editor');
  1884. btnPane.addClass('hidden');
  1885. editor.removeAttr('style').keydown(function(e) {
  1886. $(this).find('*').removeProp('font-size');
  1887. switch (e.keyCode) {
  1888. case 13:
  1889. return false;
  1890. break;
  1891. default:
  1892. return true;
  1893. break;
  1894. }
  1895. }).on('paste',function(e){
  1896. e.preventDefault();
  1897. e.stopImmediatePropagation();
  1898. var text = (e.originalEvent || e).clipboardData.getData('text/plain');
  1899. document.execCommand("insertHTML", false, text);
  1900. });
  1901. }
  1902. break;
  1903. /**
  1904. * Permet la recherche combinatoire multi critères.
  1905. * Voir filter.component.js pour plus d'infos.
  1906. */
  1907. case 'filter':
  1908. var box = new FilterBox(input,input.data());
  1909. break;
  1910. /**
  1911. * Ouvre une popup de definition des droits ciblée sur une entité, une section, un id
  1912. data-scope : section / partie de code ou de module concerné par le droit (defaut : core)
  1913. data-uid : Id spécifique d'entité concerné par le droit (defaut : $.urlParam('id'))
  1914. data-edit : Afficher la case edition (defaut : true)
  1915. data-read : Afficher la case lecture (defaut : true)
  1916. data-recursive : Afficher la case récursif (defaut : false)
  1917. data-configure : Afficher la case configuration (defaut : false)
  1918. data-firm : Scoper sur un établissement, cache la selection d'établissement si définit (defaut : null)
  1919. data-saveAction : Définir une action de save custom du droit (defaut : core_right_save)
  1920. data-deleteAction : Définir une action de delete custom du droit (defaut : core_right_delete)
  1921. */
  1922. case 'right':
  1923. input.off('click').click(function(){
  1924. var data = $.extend({
  1925. scope : 'core',
  1926. uid : $.urlParam('id'),
  1927. edit : true,
  1928. read : true,
  1929. recursive : false,
  1930. configure : false,
  1931. firm : null,
  1932. saveAction : 'core_right_save',
  1933. deleteAction : 'core_right_delete'
  1934. },input.data());
  1935. core_right_edit(data);
  1936. });
  1937. break;
  1938. /**
  1939. * data-table (ex : #contacts) : id de la table de résultat liée
  1940. * data-value (ex: 20) : valeur initiale d'item par page si rien dans le local storage
  1941. * data-step (ex: 5) : valeur du pas entre chaque proposition du nb d'item par page
  1942. * data-items (ex: 10,25,50,150) : liste des items proposés pour la préférence de pagination séparés par virgule
  1943. * data-max-item (ex: 50) : valeur maximum possible d'item par pages
  1944. */
  1945. case 'pagination-preference':
  1946. var pagination = localStorage.getItem('pagination') ? JSON.parse(localStorage.getItem('pagination')) : {};
  1947. var table = $(input.attr('data-table'));
  1948. var container = input.data('container');
  1949. var items = input.data('items');
  1950. var uid = ($.urlParam('module')?$.urlParam('module'):'main')+'-'+($.urlParam('page')?$.urlParam('page') :'main')+'-'+table.attr('id');
  1951. if(!container){
  1952. var finalItems = [];
  1953. if(!items) {
  1954. var step = parseInt(input.attr('data-step'));
  1955. for(var i=parseInt(input.attr('data-max-item')), j=1; i>=j; i-=(step?step:10))
  1956. finalItems.push({number:i});
  1957. } else {
  1958. var items = items.split(',');
  1959. for(var i=0; i<items.length; i++)
  1960. finalItems.push({number:items[i]});
  1961. }
  1962. var initial = pagination[uid] ? pagination[uid] : input.attr('data-value');
  1963. var container = $(Mustache.render($('#pagination-preference-template').html(),{initial:initial, items:finalItems}));
  1964. input.append(container);
  1965. input.data('container',container);
  1966. } else {
  1967. container = $(container);
  1968. }
  1969. container.find('.dropdown-item').click(function(){
  1970. var button = $(this);
  1971. var toggle = button.parent().parent().find('.dropdown-toggle');
  1972. toggle.text(button.text());
  1973. pagination[uid] = button.text();
  1974. localStorage.setItem('pagination',JSON.stringify(pagination));
  1975. if(window[table.attr('data-column-search')]) window[table.attr('data-column-search')]();
  1976. });
  1977. break;
  1978. /**
  1979. * data-toggle-event : Pour le moment, que "hover", de base au click
  1980. * data-show-strength: Si indiqué, affiche la barre de force du mot de passe renseigné dans l'input
  1981. * data-length : Si indiqué, le generateur de mot de passe se cantonnera à la longueur spécifiée
  1982. * data-forbidden : Si indiqué (sous forme de string), le generateur de mot de passe n'utilisera pas ces caractères
  1983. * data-generator : Si indiqué, affiche une icône pour générer un mdp sécurisé
  1984. */
  1985. case 'password':
  1986. if(input.closest('.password-field').length) return;
  1987. input.attr('type', 'password');
  1988. input.addClass('component-raw-value'); // utilisé par les filtres / etc.. pour dissocier la valeur brute du composant
  1989. input.wrap(function() {return '<div class="data-type-password password-field"></div>';});
  1990. input.data('data-component',input.parent());
  1991. var container = input.parent('.password-field');
  1992. container.append('<i class="fas fa-eye-slash noPrint password-toggler" title="Afficher/Masquer le mot de passe"></i>');
  1993. container.find('i.password-toggler').attr((input.attr('data-toggle-event') == 'hover')?{
  1994. 'onmouseover': 'toggle_password(this);',
  1995. 'onmouseleave': 'toggle_password(this);'
  1996. }:{'onclick': 'toggle_password(this);'});
  1997. if(input.attr('data-generator') !== undefined) {
  1998. container.append('<i class="fas fa-shield-alt noPrint password-generator" title="Générer un mot de passe"></i>')
  1999. container.find('i.password-generator').on('click', function(){
  2000. 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;
  2001. //Longueur attendue (12 par défaut)
  2002. var length = input.attr('data-length') !== undefined && input.attr('data-length').length ? parseInt(input.attr('data-length')) : 12;
  2003. //Regex norme ANSSI
  2004. var strong = new RegExp('^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[=/\()%ยง!@#$%^&*])(?=.{'+length+',})');
  2005. var forbidden = input.attr('data-forbidden') !== undefined && input.attr('data-forbidden').length ? input.attr('data-forbidden') : '';
  2006. do {
  2007. password = generate_password(length, forbidden);
  2008. } while(!strong.test(password))
  2009. input.val(password).trigger('change');
  2010. if(input.attr('data-show-strength') !== undefined)
  2011. check_password_strength(input);
  2012. });
  2013. function generate_password(length,forbidden){
  2014. var charset = get_characters_set();
  2015. var length = length ? length : 12+get_random_int(6);
  2016. var forbidden = forbidden!=null ? forbidden : '';
  2017. var password = "";
  2018. for(var i=0; i<forbidden.length; i++){
  2019. var index = charset.indexOf(forbidden[i]);
  2020. if(index > -1) charset.splice(index, 1);
  2021. }
  2022. for (var i=0; i<length; ++i)
  2023. password += charset[get_random_int(charset.length)];
  2024. return password;
  2025. }
  2026. }
  2027. if(input.attr('data-show-strength') !== undefined) {
  2028. input.parent('.password-field').append('<div class="strength-lines"><div class="line"></div><div class="line"></div><div class="line"></div></div>');
  2029. // Strength validation on keyup-event
  2030. input.on("keyup mouseup", function(e) {
  2031. check_password_strength($(this));
  2032. });
  2033. //Check password strength
  2034. function check_password_strength(input) {
  2035. var value = $(input).val();
  2036. var container = $(input).closest('.password-field');
  2037. $(".line", container).removeClass("strength-low strength-medium strength-hard").addClass("strength-transparent");
  2038. if(!value.length) return;
  2039. var strongRegex = new RegExp('^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[=/\()%ยง!@#$%^&*])(?=.{12,})'),
  2040. mediumRegex = new RegExp('^(((?=.*[a-z])(?=.*[A-Z]))|((?=.*[a-z])(?=.*[0-9]))|((?=.*[A-Z])(?=.*[0-9])))(?=.{6,})');
  2041. if(strongRegex.test(value)) {
  2042. $(".line", container).removeClass("strength-transparent").addClass("strength-hard");
  2043. } else if (mediumRegex.test(value)) {
  2044. $(".line:not(:last-of-type)", container).removeClass("strength-transparent").addClass("strength-medium");
  2045. } else {
  2046. $(".line:nth-child(1)", container).removeClass("strength-transparent").addClass("strength-low");
  2047. }
  2048. }
  2049. }
  2050. break;
  2051. case 'url':
  2052. if(input.closest('.url-field').length) return;
  2053. 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> ':'')+
  2054. (!input[0].hasAttribute("data-no-link")?'<i class="fas fa-globe noPrint url-link" title="Afficher le site internet"></i>':'')+'</div>');
  2055. input.after(container);
  2056. container.prepend(input.detach());
  2057. input.data('data-component',container);
  2058. input.addClass('component-raw-value'); // utilisé par les filtres / etc.. pour dissocier la valeur brute du composant
  2059. var link = $('.url-link',container);
  2060. var icon = $('.url-icon',container);
  2061. var keyActions = function(){
  2062. var value = input.val();
  2063. if(!value){
  2064. link.addClass('hidden');
  2065. }else{
  2066. link.removeClass('hidden');
  2067. }
  2068. if(value.match('https://')){
  2069. icon.removeClass('text-muted').addClass('text-success').attr('title','Site sécurisé');
  2070. }else{
  2071. icon.removeClass('text-success').addClass('text-muted').attr('title','Site non sécurisé');
  2072. }
  2073. }
  2074. keyActions();
  2075. input.keyup(keyActions);
  2076. var scheme = input.attr('data-default-scheme');
  2077. if(scheme){
  2078. input.blur(function(){
  2079. if(input.val() && !input.val().match('https?://')){
  2080. input.val(scheme+'://'+input.val());
  2081. keyActions();
  2082. }
  2083. });
  2084. }
  2085. container.find('i.url-link').click(function(){
  2086. window.open(input.val());
  2087. });
  2088. break;
  2089. case 'icon':
  2090. input.addClass('hidden');
  2091. input.next('.component-icon').remove();
  2092. var data = {
  2093. value : input.val(),
  2094. choices : []
  2095. };
  2096. var icons = $.fontAwesome();
  2097. var line = [];
  2098. for(var key in icons){
  2099. if(line.length==15){
  2100. data.choices.push(line);
  2101. line = [];
  2102. }
  2103. line.push(icons[key].icon);
  2104. }
  2105. var selector = input.data('data-component');
  2106. if(!selector){
  2107. var selector = $(Mustache.render($('.component-icon.hidden').get(0).outerHTML,data));
  2108. selector.addClass(input.attr('class'));
  2109. selector.removeClass('hidden form-control');
  2110. input.data('data-component',selector);
  2111. input.after(selector);
  2112. }
  2113. selector.on('show.bs.dropdown', function () {
  2114. if(input.is('[readonly]') || input.is('[disabled]')){
  2115. selector.find('.dropdown-menu').addClass('hidden');
  2116. }else{
  2117. selector.find('.dropdown-menu').removeClass('hidden');
  2118. }
  2119. setTimeout(function(){selector.find('input.form-control').focus();},0);
  2120. })
  2121. selector.find('.dropdown-icon-item').click(function(){
  2122. selector.find('.dropdown-icon-item').removeClass('hidden');
  2123. selector.find('input.form-control').val('');
  2124. var icon = $(this).attr('data-icon');
  2125. input.val(icon);
  2126. selector.find('.dropdown-toggle i').attr('class',icon);
  2127. input.trigger('change');
  2128. });
  2129. selector.find('input.form-control').keyup(function(){
  2130. var value = $(this).val();
  2131. $('.dropdown-icon-item i',selector).each(function(i,iconElement){
  2132. iconElement = $(iconElement);
  2133. parent = iconElement.parent();
  2134. iconElement.attr('class').indexOf(value)!=-1 ? parent.removeClass('hidden') : parent.addClass('hidden');
  2135. });
  2136. });
  2137. if(input.hasAttr('required')) selector.attr('required','');
  2138. if(input.hasAttr('readonly')) selector.attr('readonly','');
  2139. break;
  2140. /**
  2141. * data-title (*) : Le titre du modal
  2142. * 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)
  2143. * data-params : Les paramètres utilisés pour l'appel du callback
  2144. * data-action (*) : Appel de la modal par action et non par ajax
  2145. * data-precall : fonction appelée avant le chargement du modal (méthode de check sur l'UI par exemple)
  2146. * Pour customiser l'icône du quickform, il suffit de placer le contenu que l'on veut dans la div de data-type="quickform"
  2147. */
  2148. case 'quickform':
  2149. if(input.find('span').length) return;
  2150. if(input.attr('disabled')) return;
  2151. var cbLoaded = input.attr('data-loaded');
  2152. // var params = input.attr('data-params') ? input.attr('data-params').split(',') : [];
  2153. var params = input.attr('data-params') ? JSON.parse(input.attr('data-params')) : [];
  2154. var preCb = input.attr('data-precall') ? input.attr('data-precall') : '';
  2155. if(input.find('> *').length){
  2156. input.find('> *').wrap('<span class="tooltip-warning">');
  2157. } else {
  2158. input.append('<span class="tooltip-warning"><i title="Création rapide" class="fas fa-plus-square"></i></span');
  2159. }
  2160. $(document).ready(function(e){
  2161. $('span.tooltip-warning').tooltip({
  2162. track: true,
  2163. tooltipClass: 'quickform-tooltip',
  2164. });
  2165. input.on('click', function(e){
  2166. if(preCb.length && !window[preCb]()) return;
  2167. $.action({
  2168. action: input.attr('data-action'),
  2169. data: params
  2170. }, function(r){
  2171. var modal = $('#quickform-modal');
  2172. var title = (r.title && r.title.length ? r.title : (input.attr('data-title')!=null ? input.attr('data-title') : 'Création rapide :'));
  2173. $('#quickform-modal-label', modal).text(title);
  2174. $('.modal-body', modal).html('').append(r.content);
  2175. $('.modal-footer > div:first-of-type', modal).nextAll().remove();
  2176. modal.clear();
  2177. $('form', modal).attr('data-id','');
  2178. if(cbLoaded) window[cbLoaded](params);
  2179. modal.modal('show');
  2180. init_components('#quickform-modal');
  2181. });
  2182. });
  2183. });
  2184. break;
  2185. case 'checkbox':
  2186. //3 cas possibles :
  2187. // - input sans rien, data-type="checkbox" --> on fait tout
  2188. // - input avec coquille sans data-uuid, --> on génère simplement un data-uuid et maj valeur
  2189. // - input avec coquille et data-uuid --> on maj simplement sa valeur
  2190. if(input.get(0).hasAttribute('data-uuid')) return;
  2191. if(input.attr('type') != 'checkbox') input.attr('type', 'checkbox');
  2192. if(!input.closest('label.check-component').length) {
  2193. var labelBox = $('<label class="check-component"></label>');
  2194. var checkbox = $('<div class="box"></div>');
  2195. var id = input.attr('id');
  2196. var title = input.attr('title');
  2197. var customClass = input.attr('data-class');
  2198. if(id) labelBox.attr('for', id);
  2199. if(title) labelBox.attr('title', title);
  2200. if(input.attr('class')) labelBox.addClass(input.attr('class'));
  2201. if(customClass) labelBox.addClass(customClass);
  2202. if(input.prop('disabled') == true) labelBox.attr('disabled', true);
  2203. input.removeAttr('title data-class').wrap(labelBox);
  2204. input.after(checkbox);
  2205. }
  2206. var uuid = generate_uuid(15);
  2207. input.attr('data-uuid', uuid);
  2208. input.addClass('component-raw-value'); // utilisé par les filtres / etc.. pour dissocier la valeur brute du composant
  2209. if(input.hasAttr('required')) input.closest('label.check-component').attr('required','');
  2210. if(input.hasAttr('readonly')) input.closest('label.check-component').attr('readonly','');
  2211. if(input.hasAttr('readonly')) input.closest('label.check-component').click(function(e){ e.preventDefault()});
  2212. if(input.get(0).hasAttribute('id')) return;
  2213. input.closest('label.check-component').off('click','div.box').on('click','div.box',function(e){
  2214. if(input.hasAttr('readonly')) return e.preventDefault();
  2215. var checkboxInput = $('input[data-uuid="'+uuid+'"]');
  2216. checkboxInput.prop('checked', checkboxInput.prop('checked'));
  2217. });
  2218. break;
  2219. /**
  2220. * data-label : Libellé affiché à côté de l'input radio
  2221. * name (*) : Le nom du groupe dont fait partie l'input radio
  2222. */
  2223. case 'radio':
  2224. if(input.get(0).hasAttribute('data-uuid')) return;
  2225. if(input.attr('type') != 'radio') input.attr('type', 'radio');
  2226. var uuid = generate_uuid(15);
  2227. var id = input.get(0).hasAttribute('id')? input.attr('id') : '';
  2228. var title = input.get(0).hasAttribute('title') ? input.attr('title') : '';
  2229. var label = input.attr('data-label');
  2230. var labelBox = $('<label data-uid="'+uuid+'"></label>');
  2231. labelBox.attr({
  2232. 'for': id,
  2233. 'title': title
  2234. });
  2235. if(input.prop('disabled') == true) labelBox.attr('disabled', true);
  2236. input.addClass('component-raw-value'); // utilisé par les filtres / etc.. pour dissocier la valeur brute du composant
  2237. input.addClass('radio-component').removeAttr('title data-label').attr('data-uuid', uuid);
  2238. input.after(labelBox);
  2239. if(label) labelBox.after('<label for="'+id+'">'+label+'</label>');
  2240. if(input.attr('id') && input.attr('id').length) return;
  2241. labelBox.click(function(e){
  2242. var radioInput = $('input[data-uuid="'+uuid+'"]');
  2243. var name = radioInput.attr('name');
  2244. $('input[name="'+name+'"]').prop('checked', false).removeAttr('checked');
  2245. if(radioInput.prop('disabled') != true) radioInput.prop('checked', true);
  2246. $(input).change();
  2247. });
  2248. break;
  2249. /**
  2250. * data-action : l'action php pour récupérer l'UI de la card
  2251. * data-parameters : paramètres à passer avec l'action json encodés
  2252. */
  2253. case 'card':
  2254. if(!input.is(':visible')) return;
  2255. var action = input.attr('data-action');
  2256. if(!action) {
  2257. console.warn('CARD COMPONENT: Need "data-action" to get card content');
  2258. return;
  2259. }
  2260. var showDelay = input.attr('data-show-delay') ? input.attr('data-show-delay') : 250;
  2261. var hideDelay = input.attr('data-hide-delay') ? input.attr('data-hide-delay') : 600;
  2262. input.addClass('card-component-container').attr('data-uuid', generate_uuid());
  2263. var timeout;
  2264. input.off('mouseenter').mouseenter(function(event){
  2265. var e = event.target || event.relatedTarget;
  2266. if (e.parentNode != this && e != this) return;
  2267. event.stopImmediatePropagation();
  2268. event.stopPropagation();
  2269. $('*[data-type="card"]').find('.card-component')
  2270. .addClass('card-component-hide')
  2271. .removeClass('card-component-hover')
  2272. .one('webkitAnimationEnd oanimationend msAnimationEnd animationend', function(e) {
  2273. var card = $(this);
  2274. if(card.closest('.card-component-container').attr('data-uuid') == input.attr('data-uuid')) return;
  2275. card.closest('.wrapper').remove();
  2276. });
  2277. var parameters = input.attr('data-parameters');
  2278. var data = parameters!=null && parameters.length ? JSON.parse(parameters) : {};
  2279. data.action = action;
  2280. timeout = setTimeout(function(){
  2281. var currInput = input;
  2282. if(!currInput.find('.card-component').length) {
  2283. $.action(data, function(r){
  2284. if(!r.content) return;
  2285. //On utilise un wrapper pour gérer les overflows "out of the box"
  2286. var card = $(r.content);
  2287. var topOffset = currInput.offset().top;
  2288. currInput.append(card);
  2289. var position = {};
  2290. var cardWidth = card.outerWidth();
  2291. card.wrap($('<div class="wrapper"></div>'));
  2292. card.addClass('card-component card-component-hover');
  2293. if($('body').width() < (currInput.offset().left + cardWidth))
  2294. position['right'] = cardWidth;
  2295. else
  2296. position['left'] = 0;
  2297. //Gestion des overflows pour positionnement de la card
  2298. var cardHeight = card.outerHeight();
  2299. var htmlHeight = $('html').height();
  2300. if(htmlHeight-100 < (topOffset + cardHeight)){
  2301. var spaceBottom = htmlHeight-topOffset;
  2302. var height = 0;
  2303. if(spaceBottom > topOffset){
  2304. if(cardHeight > spaceBottom) height = (spaceBottom-100);
  2305. } else {
  2306. position['top'] = -cardHeight;
  2307. if(cardHeight > topOffset){
  2308. height = (topOffset-100);
  2309. position['top'] = -(topOffset-100);
  2310. }
  2311. }
  2312. if(height!=0){
  2313. card.css({
  2314. height: height+'px',
  2315. 'overflow-y': 'auto',
  2316. 'overflow-x': 'hidden'
  2317. });
  2318. }
  2319. }
  2320. currInput.find('.wrapper').css(position);
  2321. init_components(currInput.find('.wrapper'));
  2322. });
  2323. } else {
  2324. var card = $(currInput.find('.card-component'));
  2325. card.addClass('card-component-hover').removeClass('card-component-hide');
  2326. }
  2327. clearTimeout(currInput.data('tOutbox'));
  2328. }, showDelay);
  2329. });
  2330. input.add(input.find('.card-component')).off('mouseleave').mouseleave(function(e){
  2331. // e.stopImmediatePropagation();
  2332. // e.stopPropagation();
  2333. var input = $(this),
  2334. card = input.find('.card-component'),
  2335. tOutbox = setTimeout(function(){
  2336. if(!$('.card-component:hover').length) {
  2337. card.addClass('card-component-hide').removeClass('card-component-hover');
  2338. card.one('webkitAnimationEnd oanimationend msAnimationEnd animationend', function(e) {
  2339. $(this).closest('.wrapper').remove();
  2340. });
  2341. }
  2342. }, hideDelay);
  2343. //Clear du timeout d'apparition
  2344. clearTimeout(timeout);
  2345. //Set du l'id de timeout, permet de clear ce trigger si la souris revient sur l'input
  2346. input.data('tOutbox', tOutbox);
  2347. });
  2348. break;
  2349. case 'choice':
  2350. if(input.get(0).tagName !='INPUT') return console.warn('Choice autorisé sur input uniquement');
  2351. var data = input.data();
  2352. var container;
  2353. if(!input.data("data-component")){
  2354. container = $('<div class="'+input.attr('class')+' data-type-choice"></div>');
  2355. input.before(container);
  2356. input.data("data-component", container);
  2357. input.addClass('component-raw-value'); // utilisé par les filtres / etc.. pour dissocier la valeur brute du composant
  2358. }
  2359. container = input.data("data-component");
  2360. //récuperation du name servant d'id de groupe pour les radio
  2361. var name = input.attr('name');
  2362. if(!name) name = data.name;
  2363. if(!name) name = input.attr('id');
  2364. if(!name) name = Math.floor(Math.random() * 100);
  2365. //rafraichissement des options possibles
  2366. if(!data.values) return;
  2367. html = '';
  2368. for(var k in data.values){
  2369. html+='<label class="pointer">'
  2370. html+= '<input type="radio" data-type="radio" name="'+name+'" value="'+k+'">';
  2371. html+= ' '+data.values[k]+'</label> ';
  2372. }
  2373. container.html(html);
  2374. $('input',container).change(function(){
  2375. input.val($(this).val());
  2376. input.change();
  2377. });
  2378. init_components(container);
  2379. //supprime les component-raw-value généré par l'utilisation des data-type = radio
  2380. //pour les filtres on ne veut que l'input du choice
  2381. container.find('.component-raw-value').removeClass('component-raw-value');
  2382. //set de la valeur si définie dans l'input
  2383. if(input.val()){
  2384. $('input',container).prop('checked',false);
  2385. $('input[value="'+input.val()+'"]',container).prop('checked',true);
  2386. }
  2387. if(input.hasAttr("required")) container.attr('required','');
  2388. break;
  2389. case 'jsontable':
  2390. if(input.get(0).tagName !='INPUT') return;
  2391. var data = input.data();
  2392. 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');
  2393. var container;
  2394. if(!input.data("data-component")){
  2395. container = $('<table class="table table-striped '+input.attr('class')+' data-type-json">\
  2396. <thead><tr></tr></thead><tbody></tbody>\
  2397. </table>');
  2398. input.before(container);
  2399. input.data("data-component", container);
  2400. input.addClass('component-raw-value'); // utilisé par les filtres / etc.. pour dissocier la valeur brute du composant
  2401. }
  2402. container = input.data("data-component");
  2403. var head = $('thead tr',container);
  2404. var body = $('tbody',container);
  2405. var refreshTable = function(){
  2406. head.find('th:not(.option-head)').remove();
  2407. body.find('tr').remove();
  2408. var html = '';
  2409. if(data.format == 'key-value') data.columns = {"key":"Clé","value":"Valeur"};
  2410. for(var k in data.columns){
  2411. html+= '<th data-key="'+k+'">'+data.columns[k]+'</th>';
  2412. }
  2413. html+= '<th></th>';
  2414. head.prepend(html);
  2415. var values = {};
  2416. try{
  2417. values = input.val();
  2418. values = JSON.parse(input.val());
  2419. }catch(e){
  2420. values = {};
  2421. }
  2422. var columnLength = Object.keys(data.columns).length+1;
  2423. 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>');
  2424. for(var k in values){
  2425. line = values[k];
  2426. if(data.format == 'key-value') line = {key:k,value:values[k]};
  2427. addLine(line,data.columns);
  2428. }
  2429. $('.btn-add-line',container).click(function(){
  2430. addLine(null,data.columns);
  2431. });
  2432. if(input.hasAttr('required')) container.attr('required','');
  2433. }
  2434. var addLine = function(data,header){
  2435. if(!data){
  2436. for(var k in header)header[k] = '';
  2437. data = header;
  2438. }
  2439. var html = '<tr class="line">';
  2440. for(var key in data)
  2441. html += '<td><input type="text" class="form-control" data-key="'+key+'" value="'+data[key]+'"></td>';
  2442. html +='<td><i class="fas fa-trash text-muted pointer btn-remove"></i></td>';
  2443. html += '</tr>';
  2444. var line = $(html);
  2445. body.find('tr:last-child').before(line);
  2446. $('.btn-remove',line).click(function(){
  2447. removeLine($(this).closest('tr'));
  2448. });
  2449. $('input',line).change(function(){
  2450. refreshInput();
  2451. input.change();
  2452. });
  2453. refreshInput();
  2454. input.change();
  2455. }
  2456. var removeLine = function(element){
  2457. $(element).remove();
  2458. refreshInput();
  2459. input.change();
  2460. }
  2461. var refreshInput = function(){
  2462. var json = data.format=='multiple-values' ? [] : {};
  2463. body.find('tr.line').each(function(){
  2464. var tr = $(this);
  2465. if(data.format=='key-value'){
  2466. json[tr.find('td:eq(0) input').val()] = tr.find('td:eq(1) input').val();
  2467. }else if(data.format=='multiple-values'){
  2468. var line = {};
  2469. tr.find('td').each(function(){
  2470. var td = $(this);
  2471. var inputValue = td.find('input');
  2472. if(inputValue.length==0) return;
  2473. line[inputValue.attr('data-key')] = inputValue.val();
  2474. });
  2475. json.push(line);
  2476. }
  2477. });
  2478. input.val(JSON.stringify(json));
  2479. }
  2480. refreshTable();
  2481. if(input.hasAttr('readonly')){
  2482. container.attr('readonly','');
  2483. container.find('tbody input').attr('readonly','');
  2484. }
  2485. break;
  2486. case 'entitypicker':
  2487. var component = input.data('data-component');
  2488. var modal = $('#entitypicker-modal');
  2489. if(!component){
  2490. 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>');
  2491. input.data('data-component',component);
  2492. input.after(component);
  2493. if(modal.length!=0) modal.remove();
  2494. modal = $('<div class="modal fade entitypicker-modal" id="entitypicker-modal" aria-hidden="true">\
  2495. <div class="modal-dialog modal-xl"> \
  2496. <div class="modal-content"> \
  2497. <div class="modal-header"> \
  2498. <h5 class="modal-title">Sélection d\'une entitée</h5>\
  2499. <button type="button" class="close" data-dismiss="modal" aria-label="Fermer">\
  2500. <span aria-hidden="true">&times;</span>\
  2501. </button>\
  2502. </div>\
  2503. <div class="modal-body">\
  2504. <div class="row">\
  2505. <div class="col-md-6 plugin-list-container">\
  2506. <ul class="plugin-list list-group">\
  2507. <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>\
  2508. </ul>\
  2509. </div>\
  2510. <div class="col-md-6">\
  2511. <ul class="entity-list list-group">\
  2512. <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>\
  2513. </ul>\
  2514. </div>\
  2515. </div>\
  2516. </div>\
  2517. <div class="modal-footer">\
  2518. <button type="button" class="btn btn-primary btn-select">Sélectionner</button>\
  2519. </div>\
  2520. </div>\
  2521. </div>\
  2522. </div>');
  2523. $('body').append(modal);
  2524. }
  2525. modal = $('#entitypicker-modal');
  2526. var searchEntities = function(){
  2527. var plugin = $('.plugin.active',modal).attr('data-path');
  2528. if(!plugin) return;
  2529. $('.entity-list').fill({
  2530. action:'search_entities',
  2531. plugin : plugin
  2532. },function(){
  2533. $('.entity-list .entity').click(function(){
  2534. input.val($(this).attr('data-path'));
  2535. refreshValue();
  2536. modal.modal('hide');
  2537. input.change();
  2538. });
  2539. });
  2540. };
  2541. var refreshValue = function(){
  2542. var value = input.val();
  2543. if(!value) return $('>span',component).html('');
  2544. $.action({
  2545. action : 'entity_edit',
  2546. path : value
  2547. },function(response){
  2548. $('>span',component).html('<span class="text-muted">'+response.plugin.name+'</span> <i class="fas fa-chevron-right"></i> '+response.label);
  2549. });
  2550. }
  2551. $(component).off('click').click(function(){
  2552. modal.modal('show');
  2553. $('.plugin-list',modal).fill({action:'search_plugin',includeCore : true},function(){
  2554. $('.plugin',modal).click(function(){
  2555. $('.plugin',modal).removeClass('active');
  2556. $(this).addClass('active');
  2557. searchEntities();
  2558. });
  2559. });
  2560. });
  2561. $('.entitypicker-clear',component).click(function(event){
  2562. event.stopPropagation();
  2563. input.val('');
  2564. refreshValue();
  2565. });
  2566. refreshValue();
  2567. break;
  2568. case 'filepicker':
  2569. if(input.get(0).tagName !='INPUT') return;
  2570. var data = input.data();
  2571. var container;
  2572. var refreshComponent = function (){
  2573. var folders = input.val().split('/');
  2574. var html = '';
  2575. for (var i in folders) {
  2576. var folder = folders[i];
  2577. if(!folder) continue;
  2578. html += '<li><i class="fas fa-slash text-muted""></i><span class="pathlabel" '+(input.hasAttr('readonly') || !data.editable ?'':' contenteditable="true"')+' >'+folder+'</span></li>';
  2579. }
  2580. container.find('>ul').html(html);
  2581. $('.pathlabel',container).click(function(event){
  2582. event.stopPropagation();
  2583. if(!input.hasAttr('readonly')) $('.filepicker-browse',container).click();
  2584. }).keyup(function(){
  2585. if(input.hasAttr('readonly') || !data.editable) return;
  2586. var folders = input.val().split('/');
  2587. folders[$(this).closest('li').index()] = $(this).text();
  2588. input.val(folders.join('/'));
  2589. });
  2590. }
  2591. if(!input.data("data-component")){
  2592. 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>');
  2593. input.before(container);
  2594. input.data("data-component", container);
  2595. input.addClass('component-raw-value'); // utilisé par les filtres / etc.. pour dissocier la valeur brute du composant
  2596. $('.filepicker-clear',container).click(function(event){
  2597. event.stopPropagation();
  2598. input.val('');
  2599. refreshComponent();
  2600. input.change();
  2601. });
  2602. container.click(function(){
  2603. if(input.hasAttr('readonly')) return;
  2604. $('#filepicker-modal').remove();
  2605. var modal = $('<div class="modal fade filepicker-modal" id="filepicker-modal" aria-hidden="true">\
  2606. <div class="modal-dialog"> \
  2607. <div class="modal-content"> \
  2608. <div class="modal-header"> \
  2609. <h5 class="modal-title">Sélection d\'un élement</h5>\
  2610. <button type="button" class="close" data-dismiss="modal" aria-label="Fermer">\
  2611. <span aria-hidden="true">&times;</span>\
  2612. </button>\
  2613. </div>\
  2614. <div class="modal-body">\
  2615. <ul class="tree-folders folders-container">\
  2616. <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>\
  2617. </ul>\
  2618. </div>\
  2619. <div class="modal-footer">\
  2620. <button type="button" class="btn btn-primary btn-select">Sélectionner</button>\
  2621. </div>\
  2622. </div>\
  2623. </div>\
  2624. </div>');
  2625. $('body').append(modal);
  2626. $('.tree-folders',modal).on('click','li',function(event){
  2627. var li = $(this);
  2628. if(li.hasClass('folder-focused')){
  2629. var parent = li.parent().parent();
  2630. treeSearch(parent.attr('data-path') == null ? '.' : parent.attr('data-path'));
  2631. }else{
  2632. treeSearch(li.attr('data-path'));
  2633. }
  2634. if(event) event.stopPropagation();
  2635. });
  2636. $('.btn-select',modal).click(function(){
  2637. var path = $('.folder-focused',modal).attr('data-path');
  2638. if(!path) return $.message('error','Aucun chemin n\'est sélectionné');
  2639. input.val(path);
  2640. refreshComponent();
  2641. modal.modal('hide');
  2642. input.change();
  2643. });
  2644. var treeSearch = function(folderPath){
  2645. $.action({
  2646. action: 'file_search',
  2647. keyword : '',
  2648. //sort : sort,
  2649. root :data.root,
  2650. folder : folderPath ? folderPath : ''
  2651. },function(r){
  2652. var tree = $('ul.tree-folders',modal);
  2653. $('li:not(:eq(0))',tree).remove();
  2654. var tpl = $('li:not(:visible)',tree).get(0).outerHTML;
  2655. tree.find('li:visible').remove();
  2656. var selected = folderPath ? folderPath : '' ;
  2657. if(selected.length>=2 && selected.substring(0,2) == './') selected = selected.substring(2);
  2658. for(var k in r.tree){
  2659. var path = r.tree[k];
  2660. var parts = path.split('/');
  2661. var recipient = tree.parent();
  2662. for(var i in parts){
  2663. var part = parts[i];
  2664. var path = parts.slice(0,parseInt(i)+1).join('/');
  2665. var element = $('> .folders-container > li[data-path="'+path+'"]',recipient);
  2666. if(element.length ==0){
  2667. var classes = '';
  2668. if(selected==path) classes+= ' folder-focused ';
  2669. if(selected && (selected.indexOf(path+'/')!==-1 || selected==path) ) classes+= ' folder-open ';
  2670. var row = {
  2671. path : path,
  2672. label : part,
  2673. className : classes
  2674. };
  2675. var element = $(Mustache.render(tpl,row));
  2676. element.removeClass('hidden');
  2677. recipient.find('> .folders-container').append(element);
  2678. }
  2679. recipient = element;
  2680. }
  2681. }
  2682. });
  2683. }
  2684. treeSearch(input.val());
  2685. modal.modal('show');
  2686. });
  2687. }else{
  2688. container = input.data("data-component");
  2689. }
  2690. if(input.hasAttr('readonly')){
  2691. container.attr('readonly','');
  2692. }else{
  2693. container.removeAttr('readonly');
  2694. }
  2695. if(input.hasAttr('required')) container.attr('required','');
  2696. if(input.val()) refreshComponent();
  2697. break;
  2698. /**
  2699. * data-dictionnary : Le slug du dictionnary à utiliser
  2700. */
  2701. case 'dictionnary-table':
  2702. if(input.hasClass('component-dictionnary-table')) return;
  2703. if(!input.attr('data-dictionnary') || !input.attr('data-dictionnary').length) return;
  2704. var tpl = $('.component-dictionnary-table').get(0).outerHTML;
  2705. var inputData = input.data();
  2706. $.action({
  2707. action: 'load_dictionnary_component',
  2708. slug : input.attr('data-dictionnary')
  2709. },function(r){
  2710. if(!r.content) {
  2711. input.append('<div class="alert alert-warning mb-0"><i class="fas fa-exclamation-triangle mr-2"></i>Liste spécifiée inexistante</div>');
  2712. return;
  2713. }
  2714. var list = $(Mustache.render(tpl,{label:'{{label}}', slug:'{{slug}}', id:'{{id}}',parent:r.content}));
  2715. input.append(list);
  2716. list.removeClass('hidden');
  2717. dictionnary_table_refresh(list);
  2718. //Save
  2719. input.on('click','thead .btn-success',function(){
  2720. var line = $(this).closest('tr');
  2721. var data = {
  2722. action: inputData.saveAction ? inputData.saveAction : 'dictionnary_table_save',
  2723. label: line.find('input.list-label').val(),
  2724. id: list.attr('data-id'),
  2725. list: list.attr('data-dictionnary')
  2726. };
  2727. if(list.find('input.list-slug')) data.slug = $('input.list-slug', list).val();
  2728. $.action(data, function(r){
  2729. list.attr('data-id', '');
  2730. line.find('input').val('');
  2731. $.message('success','Enregistré');
  2732. dictionnary_table_refresh(list);
  2733. });
  2734. });
  2735. //Suppression
  2736. input.on('click','tbody tr .btn-danger',function(){
  2737. if(!confirm('Êtes-vous sûr de vouloir supprimer cet élément de liste ?')) return;
  2738. var line = $(this).closest('tr');
  2739. $.action({
  2740. action: inputData.deleteAction ? inputData.deleteAction : 'delete_dictionnary',
  2741. id: line.attr('data-id')
  2742. },function(r){
  2743. line.remove();
  2744. line.closest('.edit-field').val('');
  2745. $.message('info', 'Élément de liste supprimé');
  2746. });
  2747. });
  2748. //Édition
  2749. input.on('click','tbody tr .btn-edit',function(){
  2750. var line = $(this).closest('tr');
  2751. $.action({
  2752. action: inputData.editAction ? inputData.editAction : 'edit_dictionnary',
  2753. id: line.attr('data-id')
  2754. },function(r){
  2755. list.find('input.list-label').val(r.label);
  2756. if(list.find('input.list-slug')) $('input.list-slug', list).val(r.slug);
  2757. list.attr('data-id',r.id);
  2758. });
  2759. });
  2760. });
  2761. break;
  2762. //Permet aux plugins d'ajouter leurs composants
  2763. //via la fonction init_components_nomcomposant(input);
  2764. default:
  2765. var type = input.attr('data-type').replace(/[^a-z0-9]/i,'_');
  2766. if(window['init_components_'+type] !=null) window['init_components_'+type](input);
  2767. break;
  2768. }
  2769. });
  2770. init_tooltips();
  2771. }
  2772. /** TOOLTIPS **/
  2773. function init_tooltips(selector){
  2774. //Clean des tooltip qui restent en suspend sans hover de la souris
  2775. $('div.tooltip[role="tooltip"]').tooltip('dispose');
  2776. var selected = selector ? $('[data-tooltip]',selector) : '[data-tooltip]';
  2777. $(selected).each(function(){
  2778. var element = $(this);
  2779. var options = {html:true};
  2780. if(element.attr('data-placement')) options.placement = element.attr('data-placement');
  2781. element.tooltip('dispose');
  2782. element.tooltip(options);
  2783. });
  2784. }