component.js 117 KB

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