filter.component.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838
  1. /*
  2. @author valentin.carruesco
  3. == options pour la balise générale <select>
  4. * data-hide-filters: Si renseigné, masque la recherche,
  5. * data-default: permet de renseigner des filtres par défaut à la recherche (au format attendu -> voir fonction php filters_set()),
  6. * data-label: Libellé affiché dans la partie gauche de la recherche simple,
  7. * data-slug : Si spécifié, la recherche devient enregistrable pour une réutilisation ultérieure,
  8. * data-right-scope : Correspond à la section (scope) de droit
  9. * data-configure : défini si l'utilisateur peut modifier de manière globale les recherches utilisateurs
  10. * data-only-advanced : Si l'attribut est présent, cache la recherche simple et ouvre par defaut la recherche avancée
  11. * data-autosearch (default: true) : si défini a false, ne lancera pas la fonction data-function automatiquement en fin de chargement du composant
  12. * data-urlsearch (default: true) : si défini à false, n'utilisera pas l'url pour récupérer ou définir des filtres
  13. * data-show-url-advanced (default: true) : si défini à false, ne dépliera pas automatiquement la recherche avancée si il y en a une dans l'url
  14. * data-select-from-url (default: false) : si défini à true, selectionne le filtre des raccourcis correspondant à l'URL
  15. * data-user-shortcut : Si renseigné, affichera les recherches enregistrées de l'utilisateur connecté
  16. * data-global-shortcut : Si renseigné, affichera les recherches globale enregistrées pour tous les utilisateurs
  17. == options pour les balises <option>
  18. * data-operator-delete : supprime l'affiche des operateurs par défaut spécifiés en json (ex : data-operator-delete='["in","not in"]')
  19. * data-operator-view : customise l'affichage pour un opérateur donné (ex : data-operator-view='{"in":{"view":"tag"}}' )
  20. nb : La touche shift enfoncée lors de l'indentation d'un filtre forcera la création d'un nouveau groupe au lieu de chercher une fusion de groupe.
  21. nb 2: ce composant fonctionne avec les templates de type de filtres situés dans la page footer, exemple de type de filtre
  22. nb 3: les templates subcolumn référencent le plugin workflow (@TODO: à commenter par VC)
  23. <div class="filter-value-block" data-value-type="dictionary" data-value-selector=".filter-value:last-child">
  24. <select class="form-control filter-operator border-0 text-primary">
  25. <option value="=">Égal</option>
  26. <option value="!=">Différent</option>
  27. <option value="IS NULL" data-values="0">Non Renseigné</option>
  28. <option value="IS NOT NULL" data-values="0">Renseigné</option>
  29. </select>
  30. <select data-template="dictionary" data-slug="{{slug}}" data-depth="{{depth}}" class="form-control filter-value" data-disable-label></select>
  31. </div>
  32. data-value-type="" : permet de définir le data-type si la donnée est un composant
  33. data-value-selector : selecteur permetant de choisir le(s) inputs représeant la valeur réele du filtre (le selecteur est scopé sur la ligne du filtre)
  34. data-values="0" : sur l'operateur, cela permet de multiplier ou anihiler le champs de valeurs (0: supression, 1(default) un champs de valeur , n : n champs d valeur )
  35. */
  36. var FilterBox = function(input,options){
  37. var input = $(input);
  38. var object = input.data('component-object');
  39. this.input = input;
  40. this.searches = {};
  41. //Premiere création du composant
  42. if(object == null){
  43. object = this;
  44. object.shift = false;
  45. //permet le forcing de création d'un nouveau groupe lors de l'appuis sur maj
  46. $(document).keydown(function(e) {
  47. if((e.which | e.keyCode) != 16) return;
  48. object.shift = true;
  49. }).keyup(function(e) {
  50. if((e.which | e.keyCode) != 16) return;
  51. object.shift = false;
  52. });
  53. object.data = options;
  54. object.slug = input.attr('data-slug');
  55. if(object.slug != null) $('.btn-search-save',object.box).removeClass('hidden');
  56. input.addClass('hidden');
  57. object.box = $('.advanced-search-box.hidden').clone().removeClass('hidden');
  58. object.box.attr('data-configure',input.attr('data-configure'));
  59. object.box.attr('data-right-scope',input.attr('data-right-scope'));
  60. object.box.attr('data-global-shortcut',input.attr('data-global-shortcut'));
  61. object.box.attr('data-user-shortcut',input.attr('data-user-shortcut'));
  62. object.box.attr('data-select-from-url',input.attr('data-select-from-url'));
  63. input.after(object.box);
  64. object.columns = [];
  65. object.tpl = $('.criterias .condition.hidden',object.box).get(0).outerHTML;
  66. input.data('component-object',object);
  67. object.columns.push({label : ' - Choix filtre - ',value:''});
  68. $('option',input).each(function(i,element){
  69. object.columns.push({
  70. label : $(element).text(),
  71. type : $(element).attr('data-filter-type'),
  72. value : $(element).val()
  73. });
  74. });
  75. if(object.columns.length<=1){
  76. $('.options', object.box).addClass('hidden');
  77. $('.simple-search .input-group-append', object.box).removeClass('hidden');
  78. }
  79. object.box.on('change','.filter-column',function(){
  80. var condition = $(this).closest('.condition');
  81. object.filter_refresh(condition,true);
  82. }).on('change','.filter-operator',function(){
  83. var condition = $(this).closest('.condition');
  84. object.filter_refresh(condition);
  85. }).on('click','.filter-option .btn-add',function(){
  86. var button = $(this);
  87. var condition = button.closest('.condition');
  88. object.filter_add(condition);
  89. }).on('click','.filter-option .btn-duplicate',function(){
  90. var button = $(this);
  91. var condition = button.closest('.condition');
  92. object.filter_duplicate(condition);
  93. }).on('keydown', function(e){
  94. if((e.key != '+' && e.key != '=') || !e.ctrlKey) return;
  95. e.preventDefault();
  96. var condition = $('.criterias>.condition:last-of-type:not(.hidden)');
  97. object.filter_add(condition);
  98. }).on('keyup click','.filter-keyword',function(){
  99. // RESET KEYWORD
  100. object.filter_clear_button();
  101. }).on('mouseover','li.condition',function(event){
  102. event.stopPropagation();
  103. event.preventDefault();
  104. $(this).addClass('hover');
  105. }).on('mouseout','li.condition',function(event){
  106. event.stopPropagation();
  107. event.preventDefault();
  108. $(this).removeClass('hover');
  109. //Indentation d'un groupe
  110. }).on('click',' li.condition .btn-indent',function(){
  111. var line = $(this).closest('li');
  112. //si un groupe existe AVANT la condition, on déplace celle ci en FIN de ce groupe
  113. if(!object.shift && line.prev('.condition').find('>ul.group').length!=0){
  114. group = line.prev('.condition').find('>ul.group')
  115. group.append(line.detach());
  116. //si un groupe existe APRES la condition, on déplace celle ci en DEBUT de ce groupe
  117. }else if(!object.shift && line.next('.condition').find('>ul.group').length!=0){
  118. group = line.next('.condition').find('>ul.group');
  119. group.prepend(line.detach());
  120. //si aucun groupe a proximité, on créé un nouveau groupe
  121. }else{
  122. //tweak js car jquery ne capte pas bien les selected sur les clones
  123. line.find(':selected').attr('selected','selected');
  124. var newline = line.clone();
  125. var newGroup = $('<ul class="group"></ul>');
  126. newline.prepend(newGroup);
  127. newline.find('.filter-column,.filter-operator,.filter-value').remove();
  128. line.after(newline);
  129. newGroup.append(line.detach());
  130. group = newGroup;
  131. }
  132. $(group).sortable({
  133. axis : 'y',
  134. handle: ".btn-move",
  135. });
  136. }).on('click',' li.condition .btn-unindent',function(){
  137. var line = $(this).closest('li.condition');
  138. var parent = line.closest('ul.group');
  139. if(parent.hasClass('criterias')) return;
  140. //en fonction de la position de l'item dans son group on définit si on le déplace apres ou avant le groupe d'ou on le sort.
  141. var middleGroup = Math.trunc($("li.condition",parent).length / 2);
  142. var index = $("li.condition",parent).index(line) +1 ;
  143. var parentLine = parent.parent();
  144. var current = line.detach();
  145. if(index <= middleGroup){
  146. parentLine.before(current);
  147. }else{
  148. parentLine.after(current);
  149. }
  150. //Supression auto du groupe si plus aucun condition à l'interieur
  151. if(parent.find('.condition').length==0) parent.parent().remove();
  152. }).on('click',' li .btn-delete',function(){
  153. var line = $(this).closest('li.condition');
  154. object.core_filter_delete(line);
  155. }).on('click','.btn-search',function(){
  156. object.search();
  157. }).on('keypress','.filter-keyword, .filter-value input',function(e){
  158. if(e.key != "Enter") return;
  159. object.search();
  160. //affichage du formulaire de label sur l'enregistrement d'une recherche
  161. }).on('click','.btn-search-save',function(e){
  162. e.preventDefault();
  163. e.stopPropagation();
  164. object.core_filter_save_edit();
  165. //Enregistrement de la recherche courante
  166. }).on('click','.btn-search-save-submit',function(e){
  167. e.preventDefault();
  168. e.stopPropagation();
  169. object.core_filter_save();
  170. //Définition d'une recherche pré enregistrée en global ou pas
  171. }).on('click','.btn-search-global',function(e){
  172. e.preventDefault();
  173. e.stopPropagation();
  174. object.core_filter_global_save($(this).closest('.btn-search-load'));
  175. //Suppression d'une recherche pré enregistrée
  176. }).on('click','.btn-search-load > .btn-search-delete',function(e){
  177. if(!confirm('Êtes-vous sûr de supprimer cette recherche définitivement ?')) return;
  178. e.preventDefault();
  179. e.stopPropagation();
  180. object.filter_search_remove($(this).closest('.btn-search-load').attr('data-uid'));
  181. //lancement d'une recherche pré enregistrée
  182. }).on('click','.btn-search-load',function(e){
  183. object.filter_search_execute($(this).attr('data-uid'));
  184. }).on('click','.btn-search-clean',function(){
  185. $.urlParam('filters','');
  186. $('.filter-keyword',object.box).val('');
  187. object.filter_clear_button();
  188. $('.condition:not(.hidden)',object.box).each(function(i,element){
  189. object.core_filter_delete($(element));
  190. });
  191. $('.condition',object.box).removeClass('error');
  192. });
  193. //ajout de la premiere condition
  194. object.filter_add();
  195. $('.advanced-button-search',object.box).click(function(){
  196. object.box.toggleClass('advanced');
  197. });
  198. //Preset des filtres depuis l'url si défini
  199. var filters = $.urlParam('filters');
  200. if(filters && filters!='' && (input.attr('data-urlsearch')==null || input.attr('data-urlsearch')=="true")){
  201. filters = JSON.parse(atob(filters));
  202. } else if (input.attr('data-default') && input.attr('data-default').length){
  203. //Preset des filtres depuis l'attribut data-default
  204. filters = JSON.parse(input.attr('data-default'));
  205. //Récupération du type de filtre pour la colonne ciblée
  206. $.each(filters.a, function(i, filter){
  207. if(filter.c==null || !filter.c.length || (filter.t!=null && filter.t.length)) return;
  208. var option = $('option[value="'+filter.c+'"]', object.input);
  209. if(!option.length){
  210. delete filters.a[i];
  211. return;
  212. }
  213. filters.a[i]['t'] = option.attr('data-filter-type');
  214. filters.a[i]['o'] = filter.o.toLowerCase();
  215. });
  216. } else {
  217. filters = "";
  218. }
  219. if(filters){
  220. filters.keyword = filters.k;
  221. filters.advanced = filters.a;
  222. delete filters.a;
  223. delete filters.k;
  224. filters.advanced = object.filter_rename_keys(filters.advanced,{j:'join',o:'operator',v:'value',t:'type',c:'column',s:'subcolumn',g:'group'});
  225. if(filters.keyword && filters.keyword.length) $('.filter-keyword',object.box).val(filters.keyword);
  226. if(filters.advanced.length>0){
  227. if(input.attr('data-show-url-advanced')==null || input.attr('data-show-url-advanced')=="true") $(object.box).addClass('advanced');
  228. object.filters(filters.advanced);
  229. object.box.find('.condition:not(.hidden):eq(0)').remove();
  230. }
  231. }
  232. if(object.slug!=null && object.slug!='') object.filter_search_load(function(){});
  233. object.load_callback();
  234. $('#search-clear',object.box).click(function(){
  235. $('.filter-keyword',object.box).val('').focus();
  236. object.filter_clear_button();
  237. object.search();
  238. });
  239. }else{
  240. //Rafraichissement et/ou récuperation du composant
  241. //on reload les données de l'input aux cas ou elles aient changées
  242. object.data = input.data();
  243. }
  244. if(object.data && object.data.onlyAdvanced!=null){
  245. $('.simple-search,.advanced-button-search',object.box).addClass('hidden');
  246. object.box.addClass('advanced');
  247. }
  248. if(object.data && object.data.label != null && object.data.label.length) $('.simple-search .data-search-label',object.box).html(object.data.label);
  249. if(object.data && object.data.hideFilters != null) setTimeout(function(){object.box.addClass('hidden');}, 0);
  250. Object.assign(this, object);
  251. }
  252. FilterBox.prototype.load_callback = function(){
  253. if(this.data && this.data.function && window[this.data.function] && (!this.data.hasOwnProperty('autosearch') || this.data.autosearch==true))
  254. window[this.data.function]();
  255. };
  256. //Suppression d'une recherche
  257. FilterBox.prototype.filter_search_remove = function(uid){
  258. var object = this;
  259. $.action({
  260. action: 'core_filter_delete',
  261. slug: object.slug,
  262. uid: uid,
  263. global: $('.btn-search-load[data-uid="'+uid+'"]',object.box).attr('data-global')
  264. },function(response){
  265. if(response.message != null)
  266. $.message('info',response.message);
  267. object.filter_search_load();
  268. });
  269. };
  270. FilterBox.prototype.filter_clear_button = function(){
  271. var object = this;
  272. var keyword = $('.filter-keyword',object.box);
  273. var searchLabel = keyword.prev('.input-group-prepend');
  274. var clear = $('#search-clear',object.box);
  275. //On se base sur la valeur left car en fct de recherche
  276. //simple/avancée le right n'est pas fiable à 100%
  277. if(keyword.val().length){
  278. clear.removeClass('hidden');
  279. setTimeout(function(){
  280. clear.css({
  281. left: (searchLabel.outerWidth() + keyword.outerWidth() - 20),
  282. opacity: 1
  283. });
  284. }, 50);
  285. } else {
  286. clear.css({opacity: 0});
  287. setTimeout(function(){
  288. clear.addClass('hidden');
  289. }, 150);
  290. }
  291. };
  292. //Enregistrement d'une recherche (edition nom + options)
  293. FilterBox.prototype.core_filter_save_edit = function(){
  294. var object = this;
  295. //var data = object.filters();
  296. var button = $('.btn-search-save',object.box);
  297. var text = button.find('> span');
  298. var form = button.find('> div');
  299. var input = form.find('input');
  300. text.addClass('hidden');
  301. form.removeClass('hidden');
  302. input.focus();
  303. input.off('keydown').on('keydown', function(e){
  304. if(e.key=="Enter") $('.btn-search-save-submit', object.box).trigger('click');
  305. });
  306. };
  307. //Passage d'une recherche en global / privé
  308. FilterBox.prototype.core_filter_global_save = function(element){
  309. var object = this;
  310. var uid = element.attr('data-uid');
  311. var state = element.attr('data-global') == '1';
  312. state = state ? 0:1 ;
  313. $.action({
  314. action: 'core_filter_global_save',
  315. slug: object.slug,
  316. uid: uid,
  317. state: state,
  318. rightSection: object.data.rightSection
  319. },function(response){
  320. element.attr('data-global',state);
  321. });
  322. };
  323. //Enregistrement d'une recherche
  324. FilterBox.prototype.core_filter_save = function(){
  325. var object = this;
  326. var data = object.filters();
  327. if(!data.keyword.length && !data.advanced.length) {
  328. $.message('warning', "Vous ne pouvez pas enregistrer une recherche vide");
  329. return;
  330. }
  331. var button = $('.btn-search-save',object.box);
  332. var text = button.find('> span');
  333. var form = button.find('> div');
  334. var input = form.find('input');
  335. text.removeClass('hidden');
  336. form.addClass('hidden');
  337. var label = input.val();
  338. input.val('');
  339. $.action({
  340. action : 'core_filter_save',
  341. slug : object.slug,
  342. label : label,
  343. filters : data
  344. },function(response){
  345. if(response.message != null)
  346. $.message('info',response.message);
  347. object.filter_search_load();
  348. });
  349. };
  350. //Rafraichissement des données et de la structure d'un filtre
  351. FilterBox.prototype.filter_refresh = function(condition, refreshOperator, data){
  352. var column = $('.filter-column select',condition);
  353. if(data && data.column) column.val(data.column);
  354. var type = $('option:selected',column).attr('data-filter-type');
  355. var dataAttributes = this.input.find('option[value="'+column.val()+'"]').data();
  356. var operator = $('>.filter-operator',condition);
  357. var operatorSelect = $('select',operator);
  358. if(column.val()==""){
  359. $('.filter-operator,.filter-value',condition).html('');
  360. return;
  361. }
  362. if(!type) return;
  363. //Rafraichissement OU création de l'opérateur en fonction de la colonne ciblée
  364. if(operator.length==0 || refreshOperator){
  365. var operatorTemplateNode = $('.filter-value-block[data-value-type="'+type+'"] > .filter-operator');
  366. if(operatorTemplateNode.length == 0){
  367. operatorTemplateNode = $('.filter-value-block[data-value-type="text"] > .filter-operator');
  368. console.warn("Le filtre de type "+type+" n'est pas défini, remplacé par un filtre text");
  369. }
  370. var operatorSelect = $(operatorTemplateNode.get(0).outerHTML);
  371. //On supprime les opérateurs spécifiés comme data-operator-delete si présents
  372. if(dataAttributes.operatorDelete){
  373. for(var i in dataAttributes.operatorDelete)
  374. operatorSelect.find('option[value="'+dataAttributes.operatorDelete[i]+'"]').remove();
  375. }
  376. //On normalise en lowercase les opérateurs
  377. $('option', operatorSelect).val(function(){
  378. return this.value.toLowerCase();
  379. });
  380. condition.find('> .filter-operator').html(operatorSelect);
  381. }
  382. if(data && data.operator) operatorSelect.val(data.operator);
  383. if(data && data.join) $('>.filter-join',condition).val(data.join);
  384. //Gestion des ous-colonnes (subcolumns)
  385. var subHtml = $('.filter-value-block[data-value-type="'+type+'"] .filter-subfield').html();
  386. condition.find('.filter-subcolumn').html(subHtml ? subHtml : '');
  387. if(data && data.subcolumn){
  388. for(var k in data.subcolumn)
  389. condition.find('.filter-subcolumn [data-id="'+k+'"]').val(data.subcolumn[k]);
  390. }
  391. condition.find('.filter-value').html('');
  392. //Types d'affichage possibles pour l'opérateur ciblé
  393. var optionSelect = operatorSelect.find('option:selected');
  394. var defaultView = optionSelect.attr('data-default-view');
  395. defaultView = JSON.parse(atob_unicode(defaultView));
  396. //On prend par defaut l'affichage n. 0
  397. var valueType = defaultView;
  398. //Si un affichage custom est spécifié, on le mets en place
  399. if(dataAttributes.operatorView && dataAttributes.operatorView[optionSelect.attr('value')]){
  400. valueType = dataAttributes.operatorView[optionSelect.attr('value')];
  401. //if(allowedViews.indexOf(valueType) === -1) console.warn("l'affichage "+valueType+" n'est pas conçu pour fonctionner avec l'opérateur "+optionSelect.attr('value'));
  402. }
  403. var typeBloc = $('.available-field-type[data-field-type="'+valueType.view+'"]');
  404. if(typeBloc.length==0){
  405. //Si une valeur doit être affichée (pas le cas pour "est défini" ...) mais que le type de cet affichage n'est pas en filtre, on warn
  406. //if(defaultView.length!=0 && allowedViews[0]!='') console.warn('Le type '+type+' est appellé dans un filtre mais n\'est pas disponible en tant que filtre');
  407. return;
  408. }
  409. var value = typeBloc.html();
  410. value = value.replace('data-template','data-type');
  411. var repeat = $('option:selected',operator).attr('data-values');
  412. repeat = repeat==null || repeat == '' ? 1 : repeat;
  413. for(i=0;i<repeat;i++){
  414. var tpl = Mustache.render(value,dataAttributes);
  415. tpl = tpl.replace(/\[\[/ig,'{{').replace(/\]\]/ig,'}}');
  416. var valueInput = $(tpl);
  417. condition.find('span.filter-value').append(valueInput);
  418. //Si le type de champ est une liste on la remplit avec le datasource
  419. if(dataAttributes.filterSource || dataAttributes.values){
  420. var source = dataAttributes.filterSource ? dataAttributes.filterSource : dataAttributes.values;
  421. var options = '<option value=""> - </option>';
  422. for(var k in source)
  423. options += '<option value="'+k+'">'+source[k]+'</option>';
  424. valueInput.append(options);
  425. }
  426. if(dataAttributes.data) valueInput.attr('data-data',JSON.stringify(dataAttributes.data));
  427. if(dataAttributes.values) valueInput.attr('data-values',JSON.stringify(dataAttributes.values));
  428. //Si on n'a qu'un champ à fill ET qu'on a plusieurs valeurs disponibles ET qu'on a un value-separator défini, on retravaille le champ des valeurs
  429. //Besoin de faire ça car pour les filtres qui ont plusieurs valeurs dans 1 seul champ, on split leur valeurs lors de la recherche, il faut donc les join quand on les remet
  430. if(repeat==1 && (data && data.value && data.value.length>1) && defaultView['value-separator']){
  431. values = data.value.join(defaultView['value-separator']);
  432. data.value = [];
  433. data.value[i] = values;
  434. }
  435. if(data && data.value!=null) valueInput.val(data.value[i]).attr('data-value',data.value[i]).trigger('change');
  436. }
  437. init_components(condition);
  438. }
  439. //Lance la recherche (via le bouton)
  440. FilterBox.prototype.search = function(){
  441. var object = this;
  442. var filters = object.filters();
  443. if(filters.advanced.length>0 || filters.keyword!=''){
  444. filters.k = filters.keyword;
  445. filters.a = filters.advanced;
  446. delete filters.advanced;
  447. delete filters.keyword;
  448. filters.a = object.filter_rename_keys(filters.a,{join:'j',operator:'o',value:'v',type:'t',column:'c',subcolumn:'s',group:'g'});
  449. filters = JSON.stringify(filters);
  450. if(!this.data.hasOwnProperty('urlsearch') || this.data.urlsearch==true)
  451. $.urlParam('filters',btoa_unicode(filters));
  452. }else{
  453. $.urlParam('filters','');
  454. }
  455. window[object.data.function]();
  456. if(object.data.callback) window[object.data.callback]();
  457. }
  458. //Renommage des clés des filtres (permet de compresser la chaine base64 encodée en url)
  459. FilterBox.prototype.filter_rename_keys = function(filters,mapping){
  460. object = this;
  461. var newFilters = [];
  462. for(var k in filters){
  463. newFilters[k] = {};
  464. var keys = Object.keys(filters[k]);
  465. for(var i in keys){
  466. var key = keys[i];
  467. if(mapping[key] === null) continue;
  468. newFilters[k][mapping[key]] = filters[k][key];
  469. }
  470. if(newFilters[k].g) newFilters[k].g = object.filter_rename_keys(newFilters[k].g,mapping);
  471. if(newFilters[k].group) newFilters[k].group = object.filter_rename_keys(newFilters[k].group,mapping);
  472. }
  473. return newFilters;
  474. }
  475. //Définition ou récuperation d'un tableau de filtres
  476. FilterBox.prototype.filters = function(values){
  477. var object = this;
  478. if(values){
  479. object.filter_recursive_set($('.criterias', object.box),values);
  480. return;
  481. }
  482. filters = object.filter_recursive_get($('.criterias', object.box));
  483. return {
  484. keyword : $('.filter-keyword',object.box).val(),
  485. advanced : filters
  486. };
  487. }
  488. //Récupération des datas de filtre d'une ligne de filtre
  489. FilterBox.prototype.filter_get_line = function(element){
  490. var object = this;
  491. var line = {};
  492. line.type = element.find('> .filter-column select option:selected').attr('data-filter-type');
  493. var typeData = $('.filter-value-block[data-value-type="'+line.type+'"]').data();
  494. line.column = element.find('> .filter-column select').val();
  495. if(element.find('> .filter-subcolumn [data-id]').length){
  496. line.subcolumn = {};
  497. element.find('> .filter-subcolumn [data-id]').each(function(){
  498. var input = $(this);
  499. line.subcolumn[input.attr('data-id')] = input.val();
  500. });
  501. }
  502. if(line.column == null || line.column == ''){
  503. object.core_filter_delete(element);
  504. return;
  505. } else {
  506. line.operator = element.find('> .filter-operator select').val();
  507. line.value = [];
  508. if(typeData.valueSelector){
  509. var valueElements = element.find('> .filter-value '+typeData.valueSelector);
  510. //Récuperation des valeurs brute sur les composants
  511. }else if(element.find('> .filter-value .component-raw-value').length!=0){
  512. var valueElements = element.find('> .filter-value .component-raw-value');
  513. }else{
  514. var valueElements = element.find('> .filter-value > .filter-value');
  515. }
  516. var dataAttributes = object.input.find('option[value="'+line.column+'"]').data();
  517. var options = [];
  518. var optionSelect = element.find('.filter-operator option:selected');
  519. //Types d'affichage possibles pour l'opérateur ciblé
  520. var defaultView = optionSelect.attr('data-default-view');
  521. options = JSON.parse(atob_unicode(defaultView));
  522. //Récupère les options de filtres qui peuvent alterer la valeur comme value-separator
  523. if(dataAttributes.operatorView && dataAttributes.operatorView[line.operator]){
  524. valueType = element.find('.filter-value .component-raw-value').attr('data-type');
  525. if(valueType == dataAttributes.operatorView[line.operator].view){
  526. if(dataAttributes.operatorView[line.operator].options) options = dataAttributes.operatorView[line.operator].options;
  527. }
  528. }
  529. valueElements.each(function(u,input){
  530. if(options['value-separator']){
  531. var values = $(input).val().split(options['value-separator']);
  532. for(var k in values)
  533. line.value.push(values[k]);
  534. }else{
  535. var value = $(input).val();
  536. if($(input).attr('type')=='checkbox') value = $(input).prop('checked') ?1:0;
  537. line.value.push(value);
  538. }
  539. });
  540. if(!element.is(':last-child')) line.join = element.find('> .filter-join').val();
  541. }
  542. return line;
  543. }
  544. //Définition des filtres dpeuis un objet values de manière récursive
  545. FilterBox.prototype.filter_recursive_set = function(parent,values){
  546. var object = this;
  547. var filters = [];
  548. for(var key in values){
  549. condition = object.filter_add(null, values[key], parent);
  550. if(values[key].group){
  551. var newGroup = $('<ul class="group"></ul>');
  552. condition.find('.filter-column').after(newGroup);
  553. condition.find('.filter-column').remove();
  554. condition.find('.filter-operator').remove();
  555. condition.find('.filter-value').remove();
  556. if(values[key].join && values[key].join!='') condition.find('>.filter-join').val(values[key].join);
  557. condition.prepend(newGroup);
  558. object.filter_recursive_set(newGroup, values[key].group);
  559. }
  560. }
  561. return filters;
  562. }
  563. //Récuperation des filtres définis sur l'UI dans un objet et de manière récursive
  564. FilterBox.prototype.filter_recursive_get = function(parent){
  565. var object = this;
  566. var filters = [];
  567. $(parent).find('> .condition:not(.hidden)').each(function(i,element){
  568. var filter = {};
  569. var element = $(element);
  570. if(element.find('> .group').length > 0){
  571. //.advanced-search-box li.condition:last-child > .filter-join
  572. if(!element.is(':last-child')) filter.join = element.find('> .filter-join').val();
  573. filter.group = object.filter_recursive_get(element.find('> .group'));
  574. } else {
  575. filter = object.filter_get_line(element);
  576. if(!filter) return;
  577. }
  578. if(!filter.group || filter.group.length) filters.push(filter);
  579. });
  580. return filters;
  581. }
  582. //Ajout d'un filtre visuel (vide ou remplis avec l'obj data) dans un parent (optionnel) ou après un element (optionnel)
  583. //si pas de parent ou d'élements défini, le filtre s'ajoute au dernier niveau.
  584. FilterBox.prototype.filter_add = function(element, data, parent){
  585. var object = this;
  586. if(!data) data = {};
  587. data.columns = object.columns;
  588. var condition = $(Mustache.render(object.tpl, data));
  589. condition.removeClass('hidden');
  590. if(element){
  591. element.after(condition);
  592. }else if(parent){
  593. parent.append(condition);
  594. }else{
  595. $('.criterias',object.box).append(condition);
  596. }
  597. if(data) object.filter_refresh(condition, true, data);
  598. $(condition.parent()).sortable({
  599. axis : 'y',
  600. handle: ".btn-move",
  601. });
  602. return condition;
  603. }
  604. //Duplication d'un filtre visuel (ligne ou groupe) dans un parent (optionnel) ou après un element (optionnel)
  605. //si pas de parent ou d'élement défini, le filtre s'ajoute au dernier niveau.
  606. FilterBox.prototype.filter_duplicate = function(element, parent){
  607. var object = this;
  608. //Tweak JS car jQuery ne capte pas bien les selected sur les clones
  609. element.find(':selected').attr('selected','selected');
  610. //On clone la ligne ou le groupe
  611. var condition = element.clone(true).removeClass('hover');
  612. //On la wrappe dans un wrapper temporaire pour récupérer les datas
  613. var wrapper = $('<div class="temp-filter-wrapper"></div>');
  614. wrapper.append(condition);
  615. //On récupère les datas
  616. filters = object.filter_recursive_get(wrapper);
  617. //On set à nouveau les datas
  618. object.filter_recursive_set(element.parent(), filters);
  619. //On supprime les attributs selected
  620. $('.criterias', object.box).find(':selected').removeAttr('selected');
  621. }
  622. //Execution d'une recherche enregistrées
  623. FilterBox.prototype.filter_search_execute = function(uid,showFilters,callback){
  624. var object = this;
  625. var search = object.searches[uid];
  626. $.urlParam('filters','');
  627. $('.condition:not(.hidden)',object.box).each(function(i,element){
  628. object.core_filter_delete($(element));
  629. });
  630. $('.condition',object.box).removeClass('error');
  631. $('.filter-keyword',object.box).val(search.filters.keyword);
  632. if(showFilters) object.box.addClass('advanced');
  633. object.filter_recursive_set(null,search.filters.advanced);
  634. object.search();
  635. if (callback) callback();
  636. //object.load_callback();
  637. }
  638. //Recherche des recherches enregistrées
  639. FilterBox.prototype.filter_search_load = function(callback){
  640. var object = this;
  641. $.action({
  642. action : 'core_filter_search',
  643. slug : object.slug,
  644. },function(response){
  645. var savedSearch = $('.saved-search-container');
  646. var tpl = $('template.filter-saved-search',object.box).html();
  647. $('.btn-search-load',object.box).remove();
  648. response.filters.length==0 ? savedSearch.addClass('hidden') : savedSearch.removeClass('hidden');
  649. var rows = [];
  650. for(var i in response.filters){
  651. var search = response.filters[i];
  652. if(!search.label) continue;
  653. var line = Mustache.render(tpl,search);
  654. savedSearch.append(line);
  655. rows.push(search);
  656. }
  657. object.searches = response.filters;
  658. if(object.box.attr('data-user-shortcut')!='' || object.box.attr('data-global-shortcut')!=''){
  659. var userShortcuts = object.box.attr('data-user-shortcut') != ''
  660. ? $(object.box.attr('data-user-shortcut'))
  661. : null;
  662. var globalShortcuts = object.box.attr('data-global-shortcut') != ''
  663. ? $(object.box.attr('data-global-shortcut'))
  664. : null;
  665. if(rows.length!=0 && rows.filter(row => row.global == 0) && userShortcuts) {
  666. userShortcuts.find('.has-shortcut').removeClass('hidden');
  667. } else {
  668. userShortcuts.find('.has-shortcut').addClass('hidden');
  669. }
  670. if(rows.length!=0 && rows.filter(row => row.global == 1) && globalShortcuts) {
  671. globalShortcuts.find('.has-shortcut').removeClass('hidden');
  672. } else {
  673. globalShortcuts.find('.has-shortcut').addClass('hidden');
  674. }
  675. //Ajout des recherches enregistrées sur les zones de raccourcis (globaux et user)
  676. if(userShortcuts.is(globalShortcuts)) {
  677. userShortcuts.find('>ul').addLine(rows);
  678. } else {
  679. if(userShortcuts) userShortcuts.find('>ul').addLine(rows.filter(row => row.global == 0));
  680. if(globalShortcuts) globalShortcuts.find('>ul').addLine(rows.filter(row => row.global == 1));
  681. }
  682. if(object.slug && $.urlParam('filters') && object.box.attr('data-select-from-url')=="true"){
  683. var uid = object.filter_select();
  684. if(uid){
  685. userShortcuts.find('div[uid="'+uid+'"]').addClass('active');
  686. globalShortcuts.find('div[uid="'+uid+'"]').addClass('active');
  687. }
  688. }
  689. }
  690. if(callback) callback();
  691. });
  692. };
  693. //Supression d'un filtre et de ses parents vides.
  694. FilterBox.prototype.core_filter_delete = function(line){
  695. var object = this;
  696. var group = line.closest('ul.group');
  697. //Suppression des groupes avec 1 seule ligne
  698. if(line.closest('ul.group').find('>li.condition:not(.hidden)').length==1){
  699. object.filter_add(line.find('ul.group')?line:null);
  700. line.remove();
  701. return;
  702. }
  703. if($('.criterias > .condition:not(.hidden)',object.box).length>1 || line.siblings('.condition:not(.hidden)').length>0) line.remove();
  704. //Suppression ascendante récursive des groupes vides après suppression de la ligne
  705. while(1){
  706. if(!group || group.length == 0 || group.find('.condition').length!=0) break;
  707. oldgroup = group.parent().parent();
  708. group.parent().remove();
  709. group = oldgroup;
  710. }
  711. }
  712. //Selection d'un filtre raccourci si equivalent au filtre de l'URL
  713. FilterBox.prototype.filter_select = function(){
  714. var object = this;
  715. var urlFilters = $.urlParam('filters');
  716. urlFilters = JSON.parse(atob_unicode(urlFilters));
  717. if(urlFilters && urlFilters!=''){
  718. urlFilters.keyword = urlFilters.k;
  719. urlFilters.advanced = urlFilters.a;
  720. delete urlFilters.a;
  721. delete urlFilters.k;
  722. urlFilters.advanced = object.filter_rename_keys(urlFilters.advanced,{j:'join',o:'operator',v:'value',t:'type',c:'column',s:'subcolumn',g:'group'});
  723. }
  724. var slug = false;
  725. Object.keys(object.searches).forEach(function(key){
  726. var search = object.searches[key]['filters'];
  727. if(JSON.stringify(search) == JSON.stringify(urlFilters)) slug = key;
  728. });
  729. return slug;
  730. }