ar-poncho 2.0.308 → 2.0.310

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/.github/workflows/build-poncho.yml +15 -32
  2. package/dist/css/device-breadcrumb.css +1 -1
  3. package/dist/css/icono-arg.css +110 -8
  4. package/dist/css/poncho-map.css +1 -1
  5. package/dist/css/poncho.css +2 -2
  6. package/dist/css/poncho.min.css +2 -2
  7. package/dist/css/poncho_mobile.css +2 -2
  8. package/dist/fonts/{icono-arg_a6cc69d556789499dda37d7617476d6b.ttf → icono-arg_62584f1d69ebd710fc7e1ea3cfa4bceb.eot} +0 -0
  9. package/dist/fonts/{icono-arg_a6cc69d556789499dda37d7617476d6b.svg → icono-arg_62584f1d69ebd710fc7e1ea3cfa4bceb.svg} +440 -32
  10. package/dist/fonts/{icono-arg_a6cc69d556789499dda37d7617476d6b.eot → icono-arg_62584f1d69ebd710fc7e1ea3cfa4bceb.ttf} +0 -0
  11. package/dist/fonts/icono-arg_62584f1d69ebd710fc7e1ea3cfa4bceb.woff +0 -0
  12. package/dist/fonts/icono-arg_62584f1d69ebd710fc7e1ea3cfa4bceb.woff2 +0 -0
  13. package/dist/js/device-breadcrumb.js +1 -1
  14. package/dist/js/mapa-argentina.js +1 -1
  15. package/dist/js/poncho.js +2650 -1016
  16. package/dist/js/poncho.min.js +1 -1
  17. package/dist/js/showdown-extensions.js +2 -2
  18. package/dist/jsons/icono-arg.json +7603 -0
  19. package/gulpfile.js +10 -15
  20. package/package-lock.json +14866 -10143
  21. package/package.json +59 -49
  22. package/dist/css/argentina.css +0 -7
  23. package/dist/fonts/icono-arg_a6cc69d556789499dda37d7617476d6b.woff +0 -0
  24. package/dist/fonts/icono-arg_a6cc69d556789499dda37d7617476d6b.woff2 +0 -0
  25. package/test/color.test.js +0 -16
  26. package/test/gapi-sheet-data.test.js +0 -17
  27. package/test/html.test.js +0 -18
  28. package/test/poncho-gapi-legacy.test.js +0 -7
  29. package/test/resources/response.js +0 -56
  30. package/test/string.test.js +0 -20
package/dist/js/poncho.js CHANGED
@@ -314,11 +314,25 @@ const ponchoColorDefinitions = color => {
314
314
  * getColor("celeste")
315
315
  * @returns {string} Color en formato hexadecimal.
316
316
  */
317
- const ponchoColor = color => ponchoColorDefinitions(color)?.color || color;
317
+ const ponchoColor = color => {
318
+ const defaultColor = "#99999";
319
+
320
+ if (typeof color !== "string") {
321
+ console.warn(`Invalid color provided. Using default: ${defaultColor}`);
322
+ return defaultColor;
323
+ }
324
+
325
+ const definition = ponchoColorDefinitions(color);
326
+ if (definition) {
327
+ return definition.color || defaultColor;
328
+ }
329
+
330
+ return defaultColor;
331
+ };
318
332
 
319
333
 
320
334
  /**
321
- * Conversor de hex a binary
335
+ * Hace un refactor del número hexa
322
336
  *
323
337
  * @param {string} value Valor hexadecimal
324
338
  * @returns {string}
@@ -330,10 +344,12 @@ const cleanUpHex = value => {
330
344
  .trim()
331
345
  .toUpperCase();
332
346
 
333
- if(hex.length == 3){
347
+ if (hex.length < 3 || hex.length > 6){
348
+ return false;
349
+ } else if(hex.length == 3){
334
350
  hex = Array.from(hex).map(a => a.repeat(2)).join("");
335
351
  }
336
- return hex;
352
+ return `#${hex}`;
337
353
  };
338
354
 
339
355
 
@@ -354,7 +370,7 @@ const cleanUpHex = value => {
354
370
  * findByHex("#f79525");
355
371
  * @returns {object} Objecto con la defición del color
356
372
  */
357
- const findPonchoColorByHex = value => ponchoColorDefinitionsList.find(f => {
373
+ const ponchoColorByHex = value => ponchoColorDefinitionsList.find(f => {
358
374
  const colorToFind = cleanUpHex(value);
359
375
  const colorToCompare = cleanUpHex(f.color);
360
376
  if(colorToFind == colorToCompare){
@@ -363,8 +379,11 @@ const findPonchoColorByHex = value => ponchoColorDefinitionsList.find(f => {
363
379
  return false;
364
380
  });
365
381
 
366
-
367
- /* module.exports REMOVED */
382
+ if (typeof exports !== "undefined") {
383
+ module.exports = {
384
+ ponchoColorDefinitionsList,
385
+ ponchoColorDefinitions, ponchoColor, ponchoColorByHex, cleanUpHex};
386
+ }
368
387
 
369
388
  /**
370
389
  * Fetch data
@@ -376,17 +395,19 @@ const findPonchoColorByHex = value => ponchoColorDefinitionsList.find(f => {
376
395
  * });
377
396
  * ```
378
397
  */
379
- async function fetch_json(url, method="GET"){
380
- const response = await fetch(
381
- url,
382
- {
383
- method: method,
384
- headers: {
385
- "Accept": "application/json",
386
- "Content-Type": "application/json"
387
- }
388
- }
389
- );
398
+ async function fetch_json(uri, options={}) {
399
+
400
+ let defaultOptions = {
401
+ method: "GET",
402
+ headers: {
403
+ "Accept": "application/json",
404
+ "Content-Type": "application/json"
405
+ },
406
+ redirect: "follow"
407
+ };
408
+ let opts = Object.assign({}, defaultOptions, options);
409
+ const response = await fetch(uri, opts);
410
+
390
411
  if (!response.ok) {
391
412
  throw new Error(`HTTP error! status: ${response.status}`);
392
413
  }
@@ -436,935 +457,1744 @@ const slugify = (string) =>{
436
457
  + "rrsssssttuuuuuuuuuwxyyzzz------";
437
458
  const p = new RegExp(a.split("").join("|"), "g");
438
459
 
439
- return string.toString().toLowerCase()
440
- .replace(/\s+/g, "-")
441
- .replace(p, c => b.charAt(a.indexOf(c)))
442
- .replace(/&/g, "-and-")
443
- .replace(/[^\w\-]+/g, "")
444
- .replace(/\-\-+/g, "-")
445
- .replace(/^-+/, "")
446
- .replace(/-+$/, "");
447
- };
460
+ return string.toString().toLowerCase()
461
+ .replace(/\s+/g, "-")
462
+ .replace(p, c => b.charAt(a.indexOf(c)))
463
+ .replace(/&/g, "-and-")
464
+ .replace(/[^\w\-]+/g, "")
465
+ .replace(/\-\-+/g, "-")
466
+ .replace(/^-+/, "")
467
+ .replace(/-+$/, "");
468
+ };
469
+
470
+
471
+ if (typeof exports !== "undefined") {
472
+ module.exports = {slugify, replaceSpecialChars};
473
+ }
474
+
475
+ /**
476
+ * Impide que se impriman etiquetas HTML.
477
+ *
478
+ * @summary Impide que se impriman etiquetas HTML exceptuando aquellas
479
+ * asignadas en el parámetro exclude.
480
+ * @param {string} str Cadena de texto a remplazar.
481
+ * @param {object} exclude Etiquetas que deben preservarse.
482
+ * @example
483
+ * // returns &lt;h1&gt;Hello world!&lt;/h1&gt; <a href="#">Link</a>
484
+ * secureHTML('<h1>Hello world!</h1> <a href="#">Link</a>', ["a"])
485
+ *
486
+ * @returns {string} Texto remplazado.
487
+ */
488
+ const secureHTML = (str, exclude=[]) => {
489
+ if(exclude.some(e => e === "*")){
490
+ return str;
491
+ }
492
+
493
+ let replaceString = str.toString()
494
+ .replace(/</g, "&lt;")
495
+ .replace(/>/g, "&gt;");
496
+
497
+ // let replaceString = str.toString()
498
+ // .replace(/<(?=[a-zA-Z])([^<>]*)>/gm, "&lt;$1&gt;")
499
+ // .replace(/<\/(?=[a-zA-Z])([^<>]*)>/gm, "&lt;/$1&gt;");
500
+
501
+
502
+ if(exclude.length > 0){
503
+ const regexStart = new RegExp(
504
+ "&lt;(" + exclude.join("|") + ")(.*?)&gt;", "g");
505
+ const regexEnd = new RegExp(
506
+ "&lt;\/(" + exclude.join("|") + ")(.*?)&gt;", "g");
507
+
508
+ return replaceString
509
+ .replace(regexStart, "<$1$2>")
510
+ .replace(regexEnd, "</$1>");
511
+ }
512
+ return replaceString;
513
+ };
514
+
515
+
516
+
517
+ if (typeof exports !== "undefined") {
518
+ module.exports = {secureHTML};
519
+ }
520
+
521
+
522
+ function flattenNestedObjects(entries) {
523
+ return entries.map(entry => {
524
+ return flattenObject(entry, "");
525
+ });
526
+ }
527
+
528
+ function flattenObject(obj, prefix) {
529
+ const flattened = {};
530
+ for (const key in obj) {
531
+ const value = obj[key];
532
+ const newKey = (prefix ? `${prefix}__${key}` : key);
533
+
534
+ if (typeof value === "object" && value !== null) {
535
+ Object.assign(flattened, flattenObject(value, newKey));
536
+ } else {
537
+ flattened[newKey] = value;
538
+ }
539
+ }
540
+ return flattened;
541
+ }
542
+
543
+ /**
544
+ *
545
+ */
546
+ ponchoTableLegacyPatch = () => {
547
+ document
548
+ .querySelectorAll("select[id=ponchoTableFiltro]")
549
+ .forEach(element => {
550
+ // const node = element.closest(".form-group");
551
+ const node = element.parentElement;
552
+ const newElement = document.createElement("div");
553
+ newElement.id = "ponchoTableFiltro";
554
+ newElement.classList.add("row");
555
+ node.parentElement.appendChild(newElement);
556
+ node.remove();
557
+ });
558
+ };
559
+
560
+
561
+ function ponchoTable(opt) {
562
+ ponchoTableLegacyPatch();
563
+ return ponchoTableDependant(opt);
564
+ }
565
+
566
+
567
+ /**
568
+ * Agenda
569
+ *
570
+ * @summary Agenda de eventos basada en PonchoTable donde se agrupan las
571
+ * entradas por fecha de inicio, fecha de fin, y categoría.
572
+ * @author Agustín Bouillet <bouilleta@jefatura.gob.ar>
573
+ * @requires jQuery, dataTables
574
+ * @see https://github.com/argob/poncho/tree/master/src/js/poncho-table
575
+ *
576
+ *
577
+ * MIT License
578
+ *
579
+ * Copyright (c) 2024 Argentina.gob.ar
580
+ *
581
+ * Permission is hereby granted, free of charge, to any person
582
+ * obtaining a copy of this software and associated documentation
583
+ * files (the "Software"), to deal in the Software without restriction,
584
+ * including without limitation the rightsto use, copy, modify, merge,
585
+ * publish, distribute, sublicense, and/or sell copies of the Software,
586
+ * and to permit persons to whom the Software is furnished to do so,
587
+ * subject to the following conditions:
588
+ *
589
+ * The above copyright notice and this permission notice shall be
590
+ * included in all copies or substantial portions of the Software.
591
+ *
592
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
593
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
594
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
595
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
596
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
597
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
598
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
599
+ * SOFTWARE.
600
+ */
601
+ class PonchoAgenda {
602
+
603
+ DATE_REGEX = /^([1-9]|0[1-9]|[1-2][0-9]|3[0-1])\/([1-9]|0[1-9]|1[0-2])\/([1-9][0-9]{3})$/;
604
+
605
+ constructor(options={}){
606
+ options.headers = this._refactorHeaders(options);
607
+ options.headersOrder = this._refactorHeadersOrder(options);
608
+
609
+ // Global Options
610
+ this.opts = Object.assign({}, this.defaults, options);
611
+
612
+ this.categoryTitleClassList = this.opts.categoryTitleClassList;
613
+ this.itemContClassList = this.opts.itemContClassList;
614
+ this.itemClassList = this.opts.itemClassList;
615
+ this.groupCategory = this.opts.groupCategory;
616
+ this.dateSeparator = this.opts.dateSeparator;
617
+ this.startDateId = this.opts.startDateId;
618
+ this.endDateId = this.opts.endDateId;
619
+ this.timeId = this.opts.timeId;
620
+
621
+ this.descriptionId = this.opts.descriptionId;
622
+ this.criteriaOneId = this.opts.criteriaOneId;
623
+ this.criteriaTwoId = this.opts.criteriaTwoId;
624
+ this.criteriaThreeId = this.opts.criteriaThreeId;
625
+ }
626
+
627
+
628
+ /**
629
+ * Opciones por defecto
630
+ */
631
+ defaults = {
632
+ allowedTags: [
633
+ "strong","span", "dl", "dt", "dd", "img", "em","button", "button",
634
+ "p", "div", "h3", "ul", "li", "time", "a", "h1"],
635
+
636
+ criteriaOneId: "destinatarios",
637
+ criteriaThreeId: "destacado",
638
+ criteriaTwoId: "url",
639
+ descriptionId: "descripcion",
640
+ categoryTitleClassList: ["h6", "text-secondary"],
641
+ itemContClassList: ["list-unstyled"],
642
+ itemClassList: ["m-b-2"],
643
+ dateSeparator: "/",
644
+ filterStatus: {
645
+ header: "Estado",
646
+ nextDates: "Próximas",
647
+ pastDates: "Anteriores",
648
+ },
649
+ endDateId: "hasta",
650
+ groupCategory: "filtro-ministerio",
651
+ rangeLabel: "Fechas",
652
+ startDateId: "desde",
653
+ timeId: "horario",
654
+ };
655
+
656
+
657
+ /**
658
+ * Agrega los indices range y filtro-status al al array si no existieran.
659
+ *
660
+ * @param {object} options Opciones para ponchoTabla y Agenda
661
+ * @returns {object}
662
+ */
663
+ _refactorHeadersOrder = options => {
664
+ if(options.hasOwnProperty("headersOrder") &&
665
+ options.headersOrder.length > 0){
666
+ let order = options.headersOrder;
667
+ for(const i of ["range", "filtro-status"]){
668
+ if(!options.headersOrder.includes(i)){
669
+ options.headersOrder.push(i);
670
+ }
671
+ }
672
+ return order;
673
+ }
674
+ return [];
675
+ };
676
+
677
+ /**
678
+ * Mapea los headers.
679
+ *
680
+ * @return {string} key Key del item.
681
+ */
682
+ _header = (key) => {
683
+ return (this.opts.headers.hasOwnProperty(key) ?
684
+ this.opts.headers[key] : key);
685
+ };
686
+
687
+ /**
688
+ * Refactor de headers
689
+ *
690
+ * @summary Agrega los headers de range y filterheader a los
691
+ * asignados en el JSON.
692
+ * @param {object} options Opciones para ponchoTabla y Agenda
693
+ * @returns {object}
694
+ */
695
+ _refactorHeaders = options => {
696
+ let labelStatus = this.defaults.filterStatus.header;
697
+ if(options?.filterStatus?.header){
698
+ labelStatus = options.filterStatus.header;
699
+ }
700
+
701
+ let rangeLabel = this.defaults.rangeLabel;
702
+ if(options?.rangeLabel){
703
+ rangeLabel = options.rangeLabel;
704
+ }
705
+
706
+ const headers = {
707
+ ...{ "range": rangeLabel},
708
+ ...options.headers,
709
+ ...{"filtro-status": labelStatus}
710
+ };
711
+
712
+ return headers;
713
+ }
714
+
715
+
716
+ /**
717
+ * Showdown habilitado.
718
+ *
719
+ * Verifica si la librería _showdown_ está disponible.
720
+ * @returns {boolean}
721
+ */
722
+ _isMarkdownEnable = () => {
723
+ if(typeof showdown !== "undefined" &&
724
+ showdown.hasOwnProperty("Converter")){
725
+ return true;
726
+ }
727
+ return false;
728
+ };
729
+
730
+
731
+ /**
732
+ * Opciones para markdown
733
+ * @returns {object}
734
+ */
735
+ _markdownOptions = () => {
736
+ if(this._isMarkdownEnable()){
737
+ if(this.opts.hasOwnProperty("markdownOptions") &&
738
+ typeof this.opts.markdownOptions === "object"){
739
+ return this.opts.markdownOptions;
740
+ }
741
+ }
742
+ return {};
743
+ };
744
+
745
+
746
+ /**
747
+ * Convierte un string a markdown
748
+ *
749
+ * @param {string} str Cadena de texto a convertir
750
+ * @returns {string}
751
+ */
752
+ _markdownConverter = str => {
753
+ if(this._isMarkdownEnable()){
754
+ const converter = new showdown.Converter(this._markdownOptions());
755
+ return converter.makeHtml(str);
756
+ }
757
+ return str;
758
+ };
759
+
760
+
761
+ /**
762
+ * Fecha pasada
763
+ *
764
+ * @param {string} fecha Fecha a evaluar
765
+ * @returns {boolean}
766
+ */
767
+ _isPastDate = fecha => {
768
+ if(!this._isValidDateFormat(fecha)){
769
+ console.error(`La fecha no tiene un formato válido: ${fecha}`);
770
+ return false;
771
+ }
772
+
773
+ const dateToEvaluate = this._dateParser(fecha).date.getTime();
774
+ const current = this._currentDate().date.getTime();
775
+ return current > dateToEvaluate;
776
+ }
777
+
778
+
779
+ /**
780
+ * Formato para fecha y hora
781
+ *
782
+ * @param {objecct} date Fecha como objeto {day, month, year}
783
+ * @param {object} time Tiempo como objeto {hours, minutes, seconds}
784
+ * @returns {string}
785
+ */
786
+ _dateTimeFormat = (date, time=false) => {
787
+ const {day, month, year} = date;
788
+ const dateFormat = [day, month, year].join(this.dateSeparator);
789
+ let timeFormat = "";
790
+ if(time){
791
+ timeFormat = [hours, minutes].join(":");
792
+ }
793
+ return dateFormat + timeFormat;
794
+ };
795
+
796
+
797
+ /**
798
+ * Fecha al momento de ejecutarse el script.
799
+ *
800
+ * @returns {object} Retorna un objeto con: el día, mes, año y el
801
+ * objeto Date en fecha.
802
+ */
803
+ _currentDate = () => {
804
+ const today = new Date();
805
+ const year = today.getFullYear();
806
+ const month = today.getMonth() + 1;
807
+ const day = today.getDate();
808
+ const format = [
809
+ this._pad(day),
810
+ this._pad(month),
811
+ year].join(this.dateSeparator);
812
+
813
+ return {...this._dateParser(format), ...{format}};
814
+ }
815
+
816
+ /**
817
+ * Rellena con ceros a la izquierda
818
+ *
819
+ * @param {string|int} num Numero a rellenar con ceros.
820
+ * @param {int} counter Cantidad total de caracteres.
821
+ * @returns {string}
822
+ */
823
+ _pad = (num, counter=2) => num.toString().padStart(counter, "0");
824
+
825
+
826
+ /**
827
+ * Parsea una fecha.
828
+ *
829
+ * @param {string} date Fecha en formato dd/mm/yyyy.
830
+ * @param {string} time Tiempo en formato hh:mm:ss
831
+ * @example
832
+ * // {
833
+ * // day: '09',
834
+ * // month: '05',
835
+ * // year: '2012',
836
+ * // hours: '00',
837
+ * // minutes: '00',
838
+ * // date: Wed May 09 2012 00:00:00 GMT-0300...
839
+ * // }
840
+ * this._dateParser("09/05/2012")
841
+ * @returns {object|boolean}
842
+ */
843
+ _dateParser = (date, time="00:00:00") => {
844
+ if(!this._isValidDateFormat(date)){
845
+ console.error(`Formato de fecha incorrecto: ${date}`);
846
+ return;
847
+ }
848
+ const regex = this.DATE_REGEX;
849
+ const result = regex.exec(date);
850
+ const [, day, month, year] = result;
851
+ const objectDate = new Date(`${year}-${month}-${day} ${time}`);
852
+
853
+ return {
854
+ day: this._pad(day),
855
+ month: this._pad(month),
856
+ year,
857
+ hours: this._pad(objectDate.getHours()),
858
+ minutes: this._pad(objectDate.getMinutes()),
859
+ "date": objectDate
860
+ }
861
+ }
862
+
863
+
864
+ /**
865
+ * Valida el formato de la fecha.
866
+ * @summary El formato de fecha aceptado es: dd/mm/yyyy.
867
+ * Al momento de escribir este documento, no hay otro habilitado.
868
+ * @example
869
+ * // true
870
+ * this._isValidDateFormat("09/05/2012")
871
+ *
872
+ * // false
873
+ * this._isValidDateFormat("09/10/15")
874
+ * @param {string} str Fecha en formato dd/mm/yyyy.
875
+ * @returns {boolean}
876
+ */
877
+ _isValidDateFormat = str => {
878
+ const regex = this.DATE_REGEX;
879
+ const result = regex.exec(str);
880
+
881
+ return (result !== null ? true : false);
882
+ }
883
+
884
+
885
+ /**
886
+ * Agrupa contenidos por fecha y la categoría asignada.
887
+ *
888
+ * @param {object} datos JSON a procesar
889
+ * @returns {object}
890
+ */
891
+ _groupByFingerprintAndCategory = (datos) => {
892
+ const agrupados = {};
893
+
894
+ for (const dato of datos) {
895
+ const categoria = dato[this.groupCategory];
896
+ const {fingerprint} = dato;
897
+ if (!agrupados[fingerprint]) {
898
+ agrupados[fingerprint] = {};
899
+ }
900
+ if (!agrupados[fingerprint][categoria]) {
901
+ agrupados[fingerprint][categoria] = [];
902
+ }
903
+ agrupados[fingerprint][categoria].push(dato);
904
+ }
905
+
906
+ return agrupados;
907
+ }
908
+
909
+
910
+ /**
911
+ * Rearmo el JSON para agregar filtros.
912
+ *
913
+ * @param {object} jsonData
914
+ * @returns {object}
915
+ */
916
+ _refactorEntries = jsonData => {
917
+ if(!jsonData){
918
+ console.error("No se puede recorrer el script")
919
+ }
920
+
921
+ let entries = [];
922
+ jsonData.forEach(element => {
923
+ let desde = element[this.startDateId];
924
+ let hasta = element[this.endDateId];
925
+ // Si la columna `hasta` viene vacía le copio los datos de `desde`.
926
+ hasta = (hasta.trim() === "" ? desde : hasta);
927
+
928
+ const {pastDates, nextDates} = this.opts.filterStatus;
929
+ const estado = (this._isPastDate(hasta) ? pastDates : nextDates);
930
+ // dates
931
+ const startDate = this._dateParser(desde);
932
+ const endDate = this._dateParser(hasta);
933
+ const startDateTime = startDate.date.getTime();
934
+ const endDateTime = endDate.date.getTime();
935
+ const fingerprint = [startDateTime, endDateTime].join("_");
936
+
937
+ let range = this._dateTimeFormat(startDate);
938
+ if(startDateTime != endDateTime){
939
+ range = `Del ${this._dateTimeFormat(startDate)} al `
940
+ + `${this._dateTimeFormat(endDate)}`;
941
+ }
942
+
943
+ // refactor entry
944
+ const entry = {
945
+ ...element,
946
+ ...{
947
+ "range": range,
948
+ "filtro-status": estado,
949
+ fingerprint,
950
+ desde,
951
+ hasta,
952
+ }
953
+ };
954
+ entries.push(entry);
955
+ });
956
+
957
+ return entries;
958
+ };
959
+
960
+
961
+ /**
962
+ * Compone el template para el item de la agenda
963
+ *
964
+ * @param {string} description Descriptión del item de la agenda.
965
+ * @param {string} date Fecha formato dd/mm/yyyy
966
+ * @param {string} time Horario en formato hh:mm:ss
967
+ * @returns {object}
968
+ */
969
+ itemTemplate = (description, destinatarios, url,
970
+ destacados, date, time) => {
971
+ const itemContainer = document.createElement("dl");
972
+
973
+ // time
974
+ let timeElement;
975
+ if(time){
976
+ const datetime = this._dateParser(date, time);
977
+ timeElement = document.createElement("time");
978
+ timeElement.setAttribute("datetime", datetime.date.toISOString());
979
+ timeElement.textContent = `${datetime.hours}:`
980
+ + `${datetime.minutes}hs.`;
981
+ } else {
982
+ timeElement = document.createElement("span");
983
+ timeElement.textContent = "--:--";
984
+ }
985
+
986
+ const data = [
987
+ // Térm, definition, screenreader, dtoff, className
988
+ [
989
+ "Descripción",
990
+ this._markdownConverter(description),
991
+ true, true, "description"],
992
+ [
993
+ this._header(this.criteriaOneId),
994
+ this._markdownConverter(destinatarios),
995
+ false, true, "criteria-one"],
996
+ [
997
+ this._header(this.criteriaThreeId),
998
+ this._markdownConverter(destacados),
999
+ false, true, "criteria-three"],
1000
+ [
1001
+ this._header(this.criteriaTwoId),
1002
+ this._markdownConverter(url),
1003
+ false, true, "criteria-two"],
1004
+ [
1005
+ this._header(this.timeId),
1006
+ timeElement.outerHTML,
1007
+ false, true, "time"],
1008
+ ];
1009
+
1010
+ data.forEach( elem => {
1011
+ const [term, definition, srOnly, dtOff, className] = elem;
1012
+ if(!definition){
1013
+ return;
1014
+ }
1015
+
1016
+ const dt = document.createElement("dt");
1017
+ dt.textContent = term;
1018
+ dt.classList.add("agenda-item__dt", `agenda-item__dt-${className}`);
1019
+ if(srOnly){
1020
+ dt.classList.add("sr-only");
1021
+ }
1022
+
1023
+ const dd = document.createElement("dd");
1024
+ dd.textContent = definition;
1025
+ dd.classList.add("agenda-item__dd", `agenda-item__dd-${className}`);
1026
+
1027
+ if(dtOff){
1028
+ itemContainer.appendChild(dt);
1029
+ }
1030
+ itemContainer.appendChild(dd);
1031
+ });
1032
+
1033
+ if(this.itemClassList.some(f=>f)){
1034
+ itemContainer.classList.add("agenda-item", ...this.itemClassList);
1035
+ }
1036
+
1037
+ return itemContainer;
1038
+ };
1039
+
1040
+
1041
+ /**
1042
+ * Reagrupa las entradas dejando, por fecha, las entradas de la categoría.
1043
+ *
1044
+ * @param {object} entries
1045
+ * @returns {object}
1046
+ */
1047
+ _groupedEntries = entries => {
1048
+ let collect = [];
1049
+ // Nivel mismas fechas
1050
+ Object.values(entries).forEach(ele => {
1051
+ var entry;
1052
+
1053
+ // Nivel ministerio
1054
+ // Cada iteración es un ministerio.
1055
+ Object.values(ele).forEach((element) => {
1056
+ var block = "";
1057
+ var title = "";
1058
+
1059
+ const itemsContainer = document.createElement("div");
1060
+ if(this.itemContClassList.some(f=>f)){
1061
+ itemsContainer.classList.add(...this.itemContClassList);
1062
+ }
1063
+
1064
+ // Nivel items por ministerio
1065
+ element.forEach(a => {
1066
+ entry = a;
1067
+ if(title != entry[this.groupCategory]){
1068
+ title = entry[this.groupCategory];
1069
+
1070
+ const titleElement = document.createElement("p");
1071
+ if(this.categoryTitleClassList.some(f=>f)){
1072
+ titleElement.classList.add(
1073
+ ...this.categoryTitleClassList);
1074
+ titleElement.textContent = title;
1075
+ itemsContainer.appendChild(titleElement);
1076
+ }
1077
+ }
1078
+
1079
+ const item = this.itemTemplate(
1080
+ a.descripcion, a.destinatarios, a.url,
1081
+ a.destacados, a.desde, a.horario);
1082
+ itemsContainer.appendChild(item);
1083
+ });
1084
+
1085
+ block += itemsContainer.outerHTML;
1086
+ delete entry.fingerprint;
1087
+ let customData={};
1088
+
1089
+ customData[this.descriptionId] = block;
1090
+ collect.push( {...entry, ...customData} );
1091
+ });
1092
+ });
1093
+
1094
+ return collect;
1095
+ };
1096
+
1097
+
1098
+ /**
1099
+ * Valida si poncho tabla está importado
1100
+ * @returns {boolean}
1101
+ */
1102
+ _ponchoTableExists = () => {
1103
+ if(typeof ponchoTable !== "undefined"){
1104
+ return true;
1105
+ }
1106
+ return false;
1107
+ };
1108
+
1109
+
1110
+ /**
1111
+ * Imprime la tabla ponchoTable
1112
+ *
1113
+ * @returns {undefined}
1114
+ */
1115
+ render = () => {
1116
+ if(!this.opts.hasOwnProperty("jsonData")){
1117
+ console.error(
1118
+ "¡Hay un error en los datos pasados "
1119
+ + "a la función `PonchoAgenda`!");
1120
+ return;
1121
+ }
1122
+
1123
+ const refactorEntries = this._refactorEntries(this.opts.jsonData);
1124
+ const groupedByDateAndCategory =
1125
+ this._groupByFingerprintAndCategory(refactorEntries);
1126
+ this.opts.jsonData = this._groupedEntries(groupedByDateAndCategory);
1127
+
1128
+ if(this._ponchoTableExists()){
1129
+ ponchoTable( this.opts );
1130
+ }
1131
+ };
1132
+ };
1133
+
1134
+
1135
+ if (typeof exports !== "undefined") {
1136
+ module.exports = PonchoAgenda;
1137
+ }
1138
+
1139
+ /**
1140
+ * PONCHO TABLE
1141
+ *
1142
+ * @summary PonchoTable con filtros dependientes
1143
+ *
1144
+ * @author Agustín Bouillet <bouilleta@jefatura.gob.ar>
1145
+ * @requires jQuery
1146
+ * @see https://github.com/argob/poncho/blob/master/src/demo/poncho-maps/readme-poncho-maps.md
1147
+ * @see https://datatables.net
1148
+ *
1149
+ *
1150
+ * MIT License
1151
+ *
1152
+ * Copyright (c) 2022 Argentina.gob.ar
1153
+ *
1154
+ * Permission is hereby granted, free of charge, to any person
1155
+ * obtaining a copy of this software and associated documentation
1156
+ * files (the "Software"), to deal in the Software without restriction,
1157
+ * including without limitation the rightsto use, copy, modify, merge,
1158
+ * publish, distribute, sublicense, and/or sell copies of the Software,
1159
+ * and to permit persons to whom the Software is furnished to do so,
1160
+ * subject to the following conditions:
1161
+ *
1162
+ * The above copyright notice and this permission notice shall be
1163
+ * included in all copies or substantial portions of the Software.
1164
+ *
1165
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1166
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1167
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
1168
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
1169
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
1170
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
1171
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1172
+ * SOFTWARE.
1173
+ */
1174
+ const ponchoTableDependant = opt => {
1175
+ var gapi_data;
1176
+ var filtersList = [];
1177
+ var wizard = (opt.hasOwnProperty("wizard") && opt.wizard ?
1178
+ true : false);
1179
+ var emptyLabel = (opt.hasOwnProperty("emptyLabel") && opt.emptyLabel ?
1180
+ opt.emptyLabel : "Todos");
1181
+ var filtro = {};
1182
+ var orderFilter = (opt.hasOwnProperty("orderFilter") && opt.orderFilter ?
1183
+ true : false);
1184
+ var asFilter = {};
1185
+ var allowedTags = ["*"];
1186
+ let markdownOptions = {
1187
+ "tables": true,
1188
+ "simpleLineBreaks": true,
1189
+ "extensions": [
1190
+ "details",
1191
+ "images",
1192
+ "alerts",
1193
+ "numbers",
1194
+ "ejes",
1195
+ "button",
1196
+ "target",
1197
+ "bootstrap-tables",
1198
+ "video"
1199
+ ]
1200
+ };
1201
+
1202
+ // Loader
1203
+ document.querySelector("#ponchoTable").classList.add("state-loading");
1204
+
1205
+ if (jQuery.fn.DataTable.isDataTable("#ponchoTable")) {
1206
+ jQuery("#ponchoTable").DataTable().destroy();
1207
+ }
1208
+
1209
+
1210
+ /**
1211
+ * Ordena alfanuméricamente
1212
+ * @example
1213
+ * // ["Arroz", "zorro"]
1214
+ * ["zorro", "Arroz"].sort(_sortAlphaNumeric)
1215
+ * @return {object}
1216
+ */
1217
+ const sortAlphaNumeric = (a, b) => {
1218
+ return a.toString().localeCompare(b.toString(), "es", {numeric: true});
1219
+ };
1220
+
1221
+
1222
+ /**
1223
+ * De acuerdo a las opciones del usuario, ordena el listado o lo deja
1224
+ * en la secuencia en la que llega.
1225
+ *
1226
+ * @summary Alias de sortAlphaNumeric
1227
+ * @param {object} a
1228
+ * @param {object} b
1229
+ * @returns {object}
1230
+ */
1231
+ const _sortAlphaNumeric = (a, b) => (orderFilter ?
1232
+ sortAlphaNumeric(a, b) : null);
1233
+
1234
+
1235
+ /**
1236
+ * Resultados únicos
1237
+ *
1238
+ * @param {object} list Array del que se quiere obtener
1239
+ * resultados únicos.
1240
+ * @returns {object}
1241
+ */
1242
+ const distinct = list => [... new Set(list)];
1243
+
1244
+
1245
+ /**
1246
+ * Select option
1247
+ *
1248
+ * @summary Crea un tag _option_ para un _select_.
1249
+ * @param {integer} parent Índice según el listado de filtros.
1250
+ * @param {string} label Valor para el label o texto visible.
1251
+ * @param {string} value Valor para el attributo _value_.
1252
+ * @param {boolean} selected Si el item debe mostrarse seleccionado.
1253
+ * @return {object}
1254
+ */
1255
+ const _optionSelect = (parent=0, label, value, selected=false) => {
1256
+ var select_option = document.createElement("option");
1257
+ select_option.value = value.toString().trim();
1258
+ select_option.dataset.column = parent;
1259
+ select_option.textContent = label.toString().trim();
1260
+ if(selected){
1261
+ select_option.setAttribute("selected", "selected");
1262
+ }
1263
+ return select_option;
1264
+ };
1265
+
1266
+
1267
+ /**
1268
+ * Prepara el término para ser buscado por REGEX
1269
+ *
1270
+ * @summary Escapa caracteres especiales utilizados en la sintáxis
1271
+ * de expresiones regulares.
1272
+ * @see replaceSpecialChars() en poncho.min.js
1273
+ * @param {string} term Término a buscar.
1274
+ * @example
1275
+ * // return Simbrón \(3\.180\)
1276
+ * _searchTerm("Simbrón (3.180)")
1277
+ * @return {string}
1278
+ */
1279
+ const _searchTerm = (term="") => {
1280
+ return term.toString().replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1281
+ };
1282
+
1283
+
1284
+ /**
1285
+ * Evita un valor negativo
1286
+ */
1287
+ const _parentElement = value => (value <= 0 ? 0 : value);
1288
+
1289
+
1290
+ /**
1291
+ * Retorna los valores de los filtros
1292
+ */
1293
+ const _filterValues = () => {
1294
+ return [...document.querySelectorAll("[data-filter]")]
1295
+ .map(e => e.value);
1296
+ };
1297
+
1298
+
1299
+ /**
1300
+ * Showdown habilitado.
1301
+ *
1302
+ * Verifica si la librería _showdown_ está disponible.
1303
+ * @returns {boolean}
1304
+ */
1305
+ const _isMarkdownEnable = () => {
1306
+ if(typeof showdown !== "undefined" &&
1307
+ showdown.hasOwnProperty("Converter")){
1308
+ return true;
1309
+ }
1310
+ return false;
1311
+ };
1312
+
1313
+
1314
+ /**
1315
+ * Verifica si las extensiones showdown están definidas.
1316
+ *
1317
+ * @param {object} extensions
1318
+ * @returns {boolean}
1319
+ */
1320
+ const _isShowdownExtensionEnable = () => {
1321
+ const markdownOptions = _markdownOptions();
1322
+ const r = markdownOptions.extensions.every(e => {
1323
+ try {
1324
+ showdown.extension(e);
1325
+ return true;
1326
+ } catch (error) {
1327
+ return false;
1328
+ }
1329
+ });
1330
+ return r;
1331
+ };
1332
+
1333
+
1334
+ /**
1335
+ * Opciones para el componente showdonwjs
1336
+ *
1337
+ * @summary Si el usuario asigno opciones y extensiones, las usa; de otro
1338
+ * modo, usa las que están por defecto.
1339
+ * @returns {object}
1340
+ */
1341
+ const _markdownOptions = () => {
1342
+ if(opt.hasOwnProperty("markdownOptions") &&
1343
+ opt.markdownOptions === "object"){
1344
+ return opt.markdownOptions;
1345
+ }
1346
+ return markdownOptions;
1347
+ };
1348
+
1349
+
1350
+ /**
1351
+ * Convierte un string con sintaxis markdown
1352
+ * @param {stirng} str Cadena de texto a convertir
1353
+ * @returns {string}
1354
+ */
1355
+ const _markdownConvert = str => {
1356
+ if( typeof str !== "string" ){
1357
+ return;
1358
+ }
1359
+ if( !_isMarkdownEnable() ){
1360
+ return str;
1361
+ }
1362
+
1363
+ let converter;
1364
+ if(_isShowdownExtensionEnable()){
1365
+ converter = new showdown.Converter( _markdownOptions() );
1366
+ return converter.makeHtml(str);
1367
+ }
1368
+
1369
+ converter = new showdown.Converter();
1370
+ return converter.makeHtml(str);
1371
+ };
1372
+
1373
+
1374
+ /**
1375
+ * Botón poncho
1376
+ *
1377
+ * @summary Imprime un botón bootstrap.
1378
+ * @param {string} label Label para el botón.
1379
+ * @param {string} value Href para el botón
1380
+ * @return {undefined}
1381
+ */
1382
+ const button = (label, value) => {
1383
+ const btn = document.createElement("a");
1384
+ btn.setAttribute("aria-label", label);
1385
+ btn.classList.add(
1386
+ "btn", "btn-primary", "btn-sm", "margin-btn");
1387
+ btn.target = "_blank";
1388
+ btn.href = value;
1389
+ btn.textContent = label;
1390
+ btn.setAttribute("rel", "noopener noreferrer");
1391
+ return btn.outerHTML;
1392
+ };
1393
+
1394
+
1395
+ /**
1396
+ * Formato de fecha
1397
+ *
1398
+ * @summary Agrega una etiqueta datetime para mejorar la indexación
1399
+ * y el ordenamiento.
1400
+ * @return {undefined}
1401
+ */
1402
+ const tdDate = value => {
1403
+ const dateSplit = value.split("/");
1404
+ const finalDateIso = new Date(
1405
+ dateSplit[2], dateSplit[1] - 1, dateSplit[0]
1406
+ );
1407
+
1408
+ const datetime = finalDateIso.toISOString().split("T")[0];
1409
+
1410
+ const hiddenSpan = document.createElement("span");
1411
+ hiddenSpan.style.display = "none";
1412
+ hiddenSpan.textContent = datetime;
1413
+
1414
+ const time = document.createElement("time");
1415
+ time.setAttribute("datetime", datetime);
1416
+ time.textContent = value;
1417
+
1418
+ return hiddenSpan.outerHTML + time.outerHTML;
1419
+ };
1420
+
1421
+
1422
+ /**
1423
+ * Imprime los filtros
1424
+ *
1425
+ * @param {object} gapi_data Objeto con la información separada del
1426
+ * documento Google Sheet
1427
+ */
1428
+ const _createFilters = gapi_data => {
1429
+ // Contenedor
1430
+ const tableFiltroCont = document.querySelector("#ponchoTableFiltro");
1431
+ tableFiltroCont.innerHTML = "";
1432
+
1433
+ // Imprime cada uno de los filtros
1434
+ Object.keys(filtro).forEach((f, key) => {
1435
+ const columna = filtro[f][0].columna ? filtro[f][0].columna : 0;
1436
+ const list_filter = filtro[f]
1437
+ .map(e => e.value)
1438
+ .sort(_sortAlphaNumeric);
1439
+
1440
+ const tplCol = document.createElement("div");
1441
+
1442
+ if(opt.hasOwnProperty("filterClassList")){
1443
+ const classList = (typeof opt.filterClassList === "string" ?
1444
+ opt.filterClassList.split(" ") : opt.filterClassList);
1445
+ tplCol.classList.add(...classList);
1446
+ } else {
1447
+ const cols = Math.floor(12 / Object.keys(filtro).length);
1448
+ tplCol.classList.add("col-sm-12", `col-md-${cols}`);
1449
+ }
1450
+ tplCol.dataset.index = key;
1451
+ tplCol.dataset.filterName = f;
1452
+
1453
+ // If wizzard
1454
+ if(wizard && key > 0){
1455
+ tplCol.style.display = "none";
1456
+ }
1457
+
1458
+ const tplForm = document.createElement("div");
1459
+ tplForm.className = "form-group";
1460
+
1461
+ const formLabel = document.createElement("label");
1462
+ formLabel.setAttribute("for", f);
1463
+ formLabel.textContent = gapi_data.headers[`filtro-${f}`];
1464
+
1465
+ const select = document.createElement("select");
1466
+ select.classList.add("form-control");
1467
+ select.dataset.filter = 1;
1468
+ select.name = f;
1469
+ select.id = f;
1470
+ select.appendChild(_optionSelect(columna, emptyLabel, "", true));
1471
+ list_filter.forEach(item => {
1472
+ if(!item){
1473
+ return;
1474
+ }
1475
+ select.appendChild(_optionSelect(columna, item, item, false));
1476
+ });
1477
+
1478
+ tplForm.appendChild(formLabel);
1479
+ tplForm.appendChild(select);
1480
+ tplCol.appendChild(tplForm);
1481
+ tableFiltroCont.appendChild(tplCol);
1482
+ // }
1483
+ });
1484
+ };
1485
+
1486
+
1487
+ /**
1488
+ * Imprime la tabla
1489
+ *
1490
+ * @param {object} gapi_data Objeto con la información separada del
1491
+ * documento Google Sheet
1492
+ */
1493
+ const _createTable = gapi_data => {
1494
+ // Table thead > th
1495
+ const thead = document.querySelector("#ponchoTable thead");
1496
+ thead.innerHTML = "";
1497
+
1498
+ const theadTr = document.createElement("tr");
1499
+ Object.keys(gapi_data.headers).forEach((header, key) => {
1500
+ const th = document.createElement("th");
1501
+ th.textContent = gapi_data.headers[header];
1502
+ th.setAttribute("scope", "col");
1503
+ theadTr.appendChild(th);
1504
+ });
1505
+ thead.appendChild(theadTr);
1506
+
1507
+ // Table caption
1508
+ const tableCaption = document.querySelector("#ponchoTable caption");
1509
+ tableCaption.innerHTML = "";
1510
+ tableCaption.textContent = opt.tituloTabla;
1511
+
1512
+ // Tbody instance
1513
+ const tableTbody = document.querySelector("#ponchoTable tbody");
1514
+ tableTbody.innerHTML = "";
1515
+
1516
+ // CONTENIDO FILAS
1517
+ gapi_data.entries.forEach((entry, key) => {
1518
+
1519
+ if(!Object.values(entry).some(f => String(f).trim())){
1520
+ return;
1521
+ }
1522
+
1523
+ // si se desea modificar la entrada desde opciones
1524
+ entry = (typeof opt.customEntry === "function" &&
1525
+ opt.customEntry !== null ? opt.customEntry(entry) : entry);
1526
+
1527
+ // Inserta el row.
1528
+ const tbodyRow = tableTbody.insertRow();
1529
+ tbodyRow.id = "id_" + key;
1530
+
1531
+ // Recorro cada uno de los títulos
1532
+ Object.keys(gapi_data.headers).forEach(header => {
1533
+ let filas = entry[header];
1534
+
1535
+ if (header.startsWith("btn-") && filas != "") {
1536
+ const label = header.replace("btn-", "").replace("-", " ");
1537
+ filas = button(label, filas);
1538
+ } else if (header.startsWith("fecha-") && filas != "") {
1539
+ filas = tdDate(filas);
1540
+ }
1541
+
1542
+ const cell = tbodyRow.insertCell();
1543
+ cell.dataset.title = gapi_data.headers[header];
1544
+ if (filas == ""){
1545
+ cell.className = "hidden-xs";
1546
+ }
1547
+
1548
+ // Si showdown está incluido lo uso
1549
+ // @todo Usar showdown fuera de la función. Usarlo en options.
1550
+ let allowed_tags = (opt.hasOwnProperty("allowedTags") ?
1551
+ opt.allowedTags : allowedTags);
1552
+
1553
+ // Las etiquetas `<a>` y `<time>` junto con `<span>`, están
1554
+ // permitidas si existen los prefijos _btn-_ y _fecha-_
1555
+ // respectivamente.
1556
+ if(header.startsWith("btn-") && filas != ""){
1557
+ allowed_tags = [...allowed_tags, "a"];
1558
+ } else if(header.startsWith("fecha-") && filas != ""){
1559
+ allowed_tags = [...allowed_tags, "span", "time"];
1560
+ }
1561
+
1562
+ const cleannedText = secureHTML(filas, allowed_tags);
1563
+ if(_isMarkdownEnable()){
1564
+ cell.innerHTML = _markdownConvert(cleannedText);
1565
+ } else {
1566
+ cell.innerHTML = cleannedText;
1567
+ }
1568
+ });
1569
+ });
1570
+ };
1571
+
1572
+
1573
+ /**
1574
+ * Matriz filtro
1575
+ *
1576
+ * @summary Reune los filtros y por cada uno de ellos guarda los
1577
+ * datos —únicos—, de esa entrada.
1578
+ * @param {object} gapi_data Objeto con la información separada del
1579
+ * documento Google Sheet
1580
+ * @example
1581
+ * {
1582
+ * nombre_filtro : [
1583
+ * {
1584
+ * columna: 0,
1585
+ * value: "elemento"
1586
+ * },
1587
+ * ...
1588
+ * ]
1589
+ * }
1590
+ * @return {object}
1591
+ */
1592
+ const flterMatrix = (gapi_data, filtersList) => {
1593
+ let filters = {};
1594
+ filtersList.forEach((filter, key) => {
1595
+ let entiresByFilter = [];
1596
+ if(asFilter.hasOwnProperty(filtersList[key])){
1597
+ entiresByFilter = asFilter[filtersList[key]];
1598
+ } else {
1599
+ entiresByFilter = gapi_data.entries.map(entry => entry[filter]);
1600
+ }
1601
+
1602
+ const uniqueEntries = distinct(entiresByFilter);
1603
+ uniqueEntries.sort(_sortAlphaNumeric);
1604
+ filter = filter.replace("filtro-", "");
1605
+ filters[filter] = [];
1606
+ uniqueEntries.forEach(entry => {
1607
+ filters[filter].push({"columna": key, "value": entry});
1608
+ });
1609
+ });
1610
+ return filters;
1611
+ };
1612
+
1613
+
1614
+ /* HELPERS FILTRO DEPENDIENTE */
1615
+ /**
1616
+ * Valida los parents
1617
+ *
1618
+ * @param {integer} parent Índice (filtro) seleccionado.
1619
+ * @return {boolean}
1620
+ */
1621
+ const _validateSteps = (parent, entry, label, values) => {
1622
+ // Verifico que por cada entrada el valor(label), se
1623
+ // encuentre en cada uno de los parents.
1624
+ // El bucle termina cuando llega al índice seleccionado.
1625
+ const range = [...Array(_parentElement(parent + 1)).keys()];
1626
+ const results = range.map(i => {
1627
+ // Chequeo si el valor del select es igual al parent o
1628
+ // si en su defecto, está vacío.
1629
+ if(
1630
+ (
1631
+ (entry[filtersList[_parentElement(parent-1)]] ==
1632
+ values[_parentElement(parent-1)]) &&
1633
+ (entry[filtersList[_parentElement(parent)]] == label)
1634
+ ) || values[_parentElement(parent-1)] == "")
1635
+ {
1636
+ return true;
1637
+ }
1638
+ return false;
1639
+ });
1640
+ return results.every(e => e);
1641
+ };
1642
+
1643
+
1644
+ /**
1645
+ * Trae todos los elementos de un filtro en base a su parent.
1646
+ *
1647
+ * @param {integer} parent Indice de filtro seleccionado.
1648
+ * @param {integer} children Indice del hijo del seleccionado.
1649
+ * @param {string} label value del filtro seleccionado.
1650
+ * @return {object} Listado de elementos únicos para el select.
1651
+ */
1652
+ const _allFromParent = (parent, children, label) => {
1653
+ const filterList = gapi_data.entries.flatMap(e => {
1654
+ const evaluatedEntry = e[filtersList[_parentElement(children)]];
1655
+ if(e[filtersList[_parentElement(parent)]] == label || label == ""){
1656
+ if(_isCustomFilter(children, filtro)){
1657
+ const customFilters = _customFilter(children, filtro)
1658
+ .filter(e => {
1659
+ return _toCompareString(evaluatedEntry)
1660
+ .includes(_toCompareString(e));
1661
+ });
1662
+ return customFilters;
1663
+ }
1664
+ return evaluatedEntry;
1665
+ }
1666
+ return false;
1667
+
1668
+ }).filter(f => f);
1669
+
1670
+ const uniqueList = distinct(filterList);
1671
+ uniqueList.sort(_sortAlphaNumeric);
1672
+ return uniqueList;
1673
+ };
1674
+
1675
+
1676
+ /**
1677
+ * Prepara un string para una comparación case sensitive y sin
1678
+ * caracteres especiales.
1679
+ * @param {string} value Valor a comparar.
1680
+ * @returns {boolean}
1681
+ */
1682
+ const _toCompareString = value => replaceSpecialChars(value.toLowerCase());
1683
+
1684
+
1685
+ /**
1686
+ * Lista los valores que deben ir en un filtro según su parent.
1687
+ *
1688
+ * @param {integer} parent Indice de filtro seleccionado.
1689
+ * @param {string} label value del filtro seleccionado.
1690
+ * @param {integer} children Indice del hijo del seleccionado.
1691
+ */
1692
+ const _filterOptionList = (parent, children, label) => {
1693
+ children = (children == filtersList.length ? children - 1 : children);
1694
+ const values = _filterValues();
1695
+
1696
+ // Recorro todas las entradas del JSON
1697
+ const items = gapi_data.entries.flatMap(entry => {
1698
+ const range = _validateSteps(parent, entry, label, values);
1699
+ if(
1700
+ (entry[filtersList[_parentElement(children - 1)]] == label) &&
1701
+ (range)){
1702
+ const evaluatedEntry = entry[filtersList[_parentElement(children)]];
1703
+ if(_isCustomFilter(children, filtro)){
1704
+ const customFilters = _customFilter(children, filtro)
1705
+ .filter(e => {
1706
+ return _toCompareString(evaluatedEntry)
1707
+ .includes(_toCompareString(e));
1708
+ });
1709
+ return customFilters;
1710
+ } else {
1711
+ return evaluatedEntry;
1712
+ }
1713
+
1714
+ }
1715
+ return;
1716
+ }).filter(f => f);
1717
+
1718
+ const uniqueList = distinct(items);
1719
+ uniqueList.sort(_sortAlphaNumeric);
1720
+ return uniqueList;
1721
+ };
1722
+
1723
+
1724
+ /**
1725
+ * Tiene filtros personalizados
1726
+ * @param {integer} key Indice de filtro
1727
+ * @returns {boolean}
1728
+ */
1729
+ const _isCustomFilter = key => {
1730
+ const filtersKeys = Object.keys(filtro);
1731
+ if(asFilter.hasOwnProperty(`filtro-${filtersKeys[key]}`)){
1732
+ return true
1733
+ }
1734
+ return false;
1735
+ };
1736
+
1737
+
1738
+ /**
1739
+ * Listado de filtros personalizado
1740
+ * @param {integer} key Indice de filtro
1741
+ * @returns {object}
1742
+ */
1743
+ const _customFilter = key => {
1744
+ const filtersKeys = Object.keys(filtro);
1745
+ if(asFilter.hasOwnProperty(`filtro-${filtersKeys[key]}`)){
1746
+ return asFilter[`filtro-${filtersKeys[key]}`];
1747
+ }
1748
+ return [];
1749
+ };
1750
+
1751
+
1752
+ /**
1753
+ * Filtra select hijos en base a un item del padre.
1754
+ *
1755
+ * @param {integer} filterIndex Índice de filtro o número de filtro.
1756
+ * @param {string} label Label del indice seleccionado
1757
+ * @return {void}
1758
+ */
1759
+ const _dependantFilters = (filterIndex, label) => {
1760
+ const filtros = Object.keys(filtro);
1761
+ const filterValues = _filterValues();
1762
+ // Redibujo los _option_ por cada `select` (filtro).
1763
+ // Hago un `for()` iniciando en el hijo de filterIndex.
1764
+ for(let i = filterIndex + 1; i <= filtros.length; i++){
1765
+ if(filtros.length == i ){
1766
+ break;
1767
+ }
1768
+ let itemList = _filterOptionList(filterIndex, i, label);
1769
+ if(itemList.length == 0){
1770
+ itemList = _allFromParent(filterIndex, i, label);
1771
+ }
1772
+ const select = document.querySelector(`#${filtros[i]}`);
1773
+ select.innerHTML = "";
1774
+
1775
+ select.appendChild(_optionSelect(i, emptyLabel, "", true));
1776
+ itemList.forEach(e => {
1777
+ if(!e.trim()){
1778
+ return;
1779
+ }
1780
+ // Mantengo el filtro del hijo si existe en el
1781
+ // listado filtrado.
1782
+ let checked = (filterValues[i] == e ? true : false);
1783
+ select.appendChild(_optionSelect(i, e, e, checked));
1784
+ });
1785
+ }
1786
+ };
1787
+
1788
+
1789
+ /**
1790
+ * Asigna selectores al contenedor de los filtros.
1791
+ * @returns {undefined}
1792
+ */
1793
+ const _filtersContainerClassList = () =>{
1794
+ if(opt.hasOwnProperty("filterContClassList") && opt.filterContClassList){
1795
+ const fc = document.getElementById("ponchoTableFiltroCont");
1796
+ fc.removeAttribute("class");
1797
+ fc.classList.add(...opt.filterContClassList);
1798
+ }
1799
+ };
1800
+
1801
+
1802
+ /**
1803
+ * Asigna selectores al contenedor del buscador.
1804
+ * @returns {undefined}
1805
+ */
1806
+ const _searchContainerClassList = () =>{
1807
+ if(opt.hasOwnProperty("searchContClassList") && opt.searchContClassList){
1808
+ const element = document.getElementById("ponchoTableSearchCont");
1809
+ element.removeAttribute("class");
1810
+ element.classList.add(...opt.searchContClassList)
1811
+ }
1812
+ };
1813
+
1814
+
1815
+ /**
1816
+ * Si la URL tiene un valor por _hash_ lo obtiene considerandolo su id.
1817
+ * @returns {void}
1818
+ */
1819
+ const hasHash = () => {
1820
+ let hash = window.location.hash.replace("#", "");
1821
+ return hash || false;
1822
+ };
1823
+
1824
+
1825
+ /**
1826
+ * Visualización de la tabla
1827
+ *
1828
+ * @param {boolean} visibility Oculta y muestra la tabla.
1829
+ * @returns {undefined}
1830
+ */
1831
+ _hideTable = (visibility=true) => {
1832
+ const display = (visibility ? "none" : "block");
1833
+ const reverseDisplay = (visibility ? "block" : "none");
1834
+ document
1835
+ .querySelectorAll(
1836
+ `[data-visible-as-table="true"],#ponchoTable_wrapper`)
1837
+ .forEach(element => element.style.display = display);
1838
+
1839
+ document
1840
+ .querySelectorAll(`[data-visible-as-table="false"]`)
1841
+ .forEach(element => element.style.display = reverseDisplay);
1842
+ };
1843
+
1844
+
1845
+ /**
1846
+ * Inicializa DataTable() y modifica elementos para adaptarlos a
1847
+ * GoogleSheets y requerimientos de ArGob.
1848
+ */
1849
+ const initDataTable = () => {
1850
+ const searchType = jQuery.fn.DataTable.ext.type.search;
1851
+ searchType.string = data => {
1852
+ return (!data ?
1853
+ "" :
1854
+ (typeof data === "string" ?
1855
+ replaceSpecialChars(data) :
1856
+ data));
1857
+ };
1858
+
1859
+ searchType.html = data => {
1860
+ return (!data ?
1861
+ "" :
1862
+ (typeof data === "string" ?
1863
+ replaceSpecialChars(data.replace( /<.*?>/g, "")) :
1864
+ data));
1865
+ };
1866
+
1867
+ /**
1868
+ * Instacia DataTable()
1869
+ */
1870
+ let tabla = jQuery("#ponchoTable").DataTable({
1871
+ "initComplete" : (settings, json) => {
1872
+ if(wizard){
1873
+ _hideTable();
1874
+ }
1875
+ },
1876
+ "lengthChange": false,
1877
+ "autoWidth": false,
1878
+ "pageLength": opt.cantidadItems,
1879
+ "columnDefs": [
1880
+ { "type": "html-num", "targets": opt.tipoNumero },
1881
+ { "targets": opt.ocultarColumnas, "visible": false }
1882
+ ],
1883
+ "ordering": opt.orden,
1884
+ "order": [
1885
+ [opt.ordenColumna - 1, opt.ordenTipo]
1886
+ ],
1887
+ "dom": "<\"row\"<\"col-sm-6\"l><\"col-sm-6\"f>>" +
1888
+ "<\"row\"<\"col-sm-12\"i>>" +
1889
+ "<\"row\"<\"col-sm-12\"tr>>" +
1890
+ "<\"row\"<\"col-md-offset-3 col-md-6 "
1891
+ + "col-sm-offset-2 col-sm-8\"p>>",
1892
+ "language": {
1893
+ "sProcessing": "Procesando...",
1894
+ "sLengthMenu": "Mostrar _MENU_ registros",
1895
+ "sZeroRecords": "No se encontraron resultados",
1896
+ "sEmptyTable": "Ningún dato disponible en esta tabla",
1897
+ "sInfo": "_TOTAL_ resultados",
1898
+ "sInfoEmpty": "No hay resultados",
1899
+ //"sInfoFiltered": "(filtrado de un total de _MAX_ registros)",
1900
+ "sInfoFiltered": "",
1901
+ "sInfoPostFix": "",
1902
+ "sSearch": "Buscar:",
1903
+ "sUrl": "",
1904
+ "sInfoThousands": ".",
1905
+ "sLoadingRecords": "Cargando...",
1906
+ "oPaginate": {
1907
+ "sFirst": "<<",
1908
+ "sLast": ">>",
1909
+ "sNext": ">",
1910
+ "sPrevious": "<"
1911
+ },
1912
+ "oAria": {
1913
+ "sSortAscending":
1914
+ ": Activar para ordenar la columna "
1915
+ + "de manera ascendente",
1916
+ "sSortDescending":
1917
+ ": Activar para ordenar la columna de "
1918
+ + "manera descendente",
1919
+ "paginate": {
1920
+ "first": "Ir a la primera página",
1921
+ "previous": "Ir a la página anterior",
1922
+ "next": "Ir a la página siguiente",
1923
+ "last": "Ir a la última página"
1924
+ }
1925
+ }
1926
+ }
1927
+ });
1928
+
1929
+ /**
1930
+ * Buscador por palabra
1931
+ * @summary Ejecuta la búsqueda en cada keyup.
1932
+ */
1933
+ jQuery("#ponchoTableSearch").keyup(function() {
1934
+ tabla
1935
+ .search(jQuery.fn.DataTable.ext.type.search.string(this.value))
1936
+ .draw();
1937
+ });
1938
+
1939
+
1940
+ // REMUEVE LOS FILTROS
1941
+ jQuery("#ponchoTable_filter").parent().parent().remove();
1942
+
1943
+ // MUESTRA FILTRO PERSONALIZADO
1944
+ const ponchoTableOption =
1945
+ document.querySelectorAll("#ponchoTableFiltro option");
1946
+ if (ponchoTableOption.length > 1) {
1947
+ document
1948
+ .querySelector("#ponchoTableFiltroCont")
1949
+ .style.display = "block";
1950
+ }
1951
+
1952
+
1953
+ /**
1954
+ * Valida si un componente select tiene options con value.
1955
+ *
1956
+ * @summary El objeto de éste método es evitar traer selects que tengan
1957
+ * options vacíos.
1958
+ * @param {string} selector Selector del elemento select
1959
+ * @returns {boolean}
1960
+ */
1961
+ const _selectHasValues = selector => {
1962
+ const options = document.querySelectorAll(`${selector} option`);
1963
+ const result = Object.values(options).map(m => m.value).some(s => s);
1964
+ return result;
1965
+ }
1966
+
1967
+
1968
+ /**
1969
+ * Modo wizard para los filtros.
1970
+ *
1971
+ * @param {object} filters Listado de filtros.
1972
+ * @param {interger} column Indice de columna.
1973
+ * @param {string} valFilter Value del select
1974
+ * @returns {undefined}
1975
+ */
1976
+ const _wizardFilters = (filters, column=0, valFilter="") => {
1977
+ let tableStatus = false;
1978
+
1979
+ filters.forEach((filter, key) => {
1980
+ const selectHasValues = _selectHasValues(`#${filter}`);
1981
+ let displayStatus = "none";
1982
+
1983
+ if(selectHasValues && valFilter && key <= column + 1){
1984
+ displayStatus = "block";
1985
+
1986
+ } else if(selectHasValues && !valFilter && key <= column + 1){
1987
+ const nextFilter = document
1988
+ .querySelectorAll(`#${filters[column + 1]}`)
1989
+ nextFilter.forEach(element => element.innerHTML = "");
1990
+ displayStatus = "block";
1991
+ tableStatus = false;
1992
+ }
1993
+
1994
+ const currentFilter = document
1995
+ .querySelectorAll(`[data-filter-name="${filter}"]`)
1996
+ currentFilter
1997
+ .forEach(element => element.style.display = displayStatus);
1998
+ });
1999
+
2000
+ if(
2001
+ _selectHasValues(`#${filters[column]}`) &&
2002
+ valFilter &&
2003
+ !_selectHasValues(`#${filters[column + 1]}`)
2004
+ ){
2005
+ tableStatus = true;
2006
+ }
2007
+
2008
+
2009
+ if(tableStatus){
2010
+ _hideTable(false);
2011
+ } else {
2012
+ _hideTable();
2013
+ }
2014
+ };
2015
+
2016
+
2017
+ /**
2018
+ * Filtro en el change de cada select (filtro).
2019
+ *
2020
+ * @summary Por por cada interacción con un filtro, obtiene el índice de
2021
+ * columna y lo pasa con el valor del select a _dependantFilters(). Ésta
2022
+ * funciión redibuja los filtros en de forma dependiente según el valor
2023
+ * de la elección.
2024
+ * @see replaceSpecialChars() on poncho.min.js
2025
+ * @return {undefined}
2026
+ */
2027
+ jQuery("select[data-filter]").change(function() {
2028
+ const column = jQuery(this).find("option:selected").data("column");
2029
+ const valFilter = jQuery(this).find("option:selected").val();
2030
+
2031
+ _dependantFilters(column, valFilter);
2032
+
2033
+ // Restablece los datos en la tabla
2034
+ tabla.columns().search("").columns().search("").draw();
2035
+
2036
+ const filters = Object.keys(filtro);
2037
+ const filterValues = _filterValues();
2038
+ const filterIndex = filter => {
2039
+ return Object
2040
+ .keys(gapi_data.headers)
2041
+ .indexOf(`filtro-${filter}`);
2042
+ };
448
2043
 
2044
+ filterValues.forEach((f, k) => {
2045
+ const columnIndex = filterIndex(filters[k]);
2046
+ const term = _searchTerm(filterValues[k]);
2047
+ const cleanTerm = _searchTerm(
2048
+ replaceSpecialChars(filterValues[k]));
449
2049
 
450
- /* module.exports REMOVED */
2050
+ if(_isCustomFilter(k, filtro)){
2051
+ tabla.columns(columnIndex)
2052
+ .search(_toCompareString(filterValues[k]));
2053
+ } else {
2054
+ tabla
2055
+ .columns(columnIndex)
2056
+ .search(
2057
+ (filterValues[k] ? `^(${term}|${cleanTerm})$` : ""),
2058
+ true, false, true
2059
+ );
2060
+ }
2061
+ });
2062
+ tabla.draw();
2063
+ if(wizard){
2064
+ _wizardFilters(filters, column, valFilter);
2065
+ }
2066
+ });
451
2067
 
452
- /**
453
- * Impide que se impriman etiquetas HTML.
454
- *
455
- * @summary Impide que se impriman etiquetas HTML exceptuando aquellas
456
- * asignadas en el parámetro exclude.
457
- * @param {string} str Cadena de texto a remplazar.
458
- * @param {object} exclude Etiquetas que deben preservarse.
459
- * @example
460
- * // returns &lt;h1&gt;Hello world!&lt;/h1&gt; <a href="#">Link</a>
461
- * secureHTML('<h1>Hello world!</h1> <a href="#">Link</a>', ["a"])
462
- *
463
- * @returns {string} Texto remplazado.
464
- */
465
- const secureHTML = (str, exclude=[]) => {
466
- if(exclude.some(e => e === "*")){
467
- return str;
468
- }
469
2068
 
470
- let replaceString = str.toString()
471
- .replace(/</g, "&lt;")
472
- .replace(/>/g, "&gt;");
2069
+ // Si está habilitada la búsqueda por hash.
2070
+ if(opt.hasOwnProperty("hash") && opt.hash){
2071
+ const term = hasHash();
2072
+ const searchTerm = (term ? decodeURIComponent(term) : "");
2073
+ const element = document.querySelectorAll("#ponchoTableSearch");
2074
+ element.forEach(ele => {
2075
+ ele.value = searchTerm;
2076
+ tabla
2077
+ .search(jQuery.fn.DataTable.ext.type.search.string(searchTerm))
2078
+ .draw();
2079
+ });
2080
+ }
2081
+ } // end initDataTable
473
2082
 
474
- // let replaceString = str.toString()
475
- // .replace(/<(?=[a-zA-Z])([^<>]*)>/gm, "&lt;$1&gt;")
476
- // .replace(/<\/(?=[a-zA-Z])([^<>]*)>/gm, "&lt;/$1&gt;");
477
2083
 
2084
+ /**
2085
+ * Permite definir el orden de los headers.
2086
+ * @param {*} headers {object}
2087
+ * @param {*} order
2088
+ * @returns
2089
+ */
2090
+ const _headersOrder = (headers) => {
2091
+ if(opt.hasOwnProperty("headersOrder") && opt.headersOrder.length > 0){
2092
+ let refactorHeaders = {};
2093
+ for(i of opt.headersOrder){
2094
+ if( headers.hasOwnProperty(i) ){
2095
+ refactorHeaders[i] = headers[i];
2096
+ }
2097
+ }
2098
+ return refactorHeaders;
2099
+ }
2100
+ return headers;
2101
+ };
478
2102
 
479
- if(exclude.length > 0){
480
- const regexStart = new RegExp(
481
- "&lt;(" + exclude.join("|") + ")(.*?)&gt;", "g");
482
- const regexEnd = new RegExp(
483
- "&lt;\/(" + exclude.join("|") + ")(.*?)&gt;", "g");
484
2103
 
485
- return replaceString
486
- .replace(regexStart, "<$1$2>")
487
- .replace(regexEnd, "</$1>");
488
- }
489
- return replaceString;
490
- };
2104
+ /**
2105
+ * Imprime DataTable
2106
+ *
2107
+ * @param {object} data JSON data
2108
+ */
2109
+ const render = data => {
2110
+ // Defino la variable global
2111
+ gapi_data = data;
2112
+ // Defino las entradas
2113
+ gapi_data.entries = (
2114
+ typeof opt.refactorEntries === "function" &&
2115
+ opt.refactorEntries !== null ?
2116
+ opt.refactorEntries(gapi_data.entries) : gapi_data.entries
2117
+ );
2118
+ // Defino los headers que se van a utilizar
2119
+ gapi_data.headers = (opt.hasOwnProperty("headers") && opt.headers ?
2120
+ opt.headers : gapi_data.headers);
491
2121
 
2122
+ gapi_data.headers = _headersOrder(gapi_data.headers, opt.headersOrder);
492
2123
 
2124
+ // Listado de filtros
2125
+ filtersList = Object
2126
+ .keys(gapi_data.headers)
2127
+ .filter(e => e.startsWith("filtro-"));
493
2128
 
494
- /* module.exports REMOVED */
2129
+ asFilter = (opt.asFilter ? opt.asFilter(gapi_data.entries) : {});
2130
+ filtro = flterMatrix(gapi_data, filtersList);
495
2131
 
496
- /**
497
- *
498
- */
499
- ponchoTableLegacyPatch = () => {
500
- document
501
- .querySelectorAll("select[id=ponchoTableFiltro]")
502
- .forEach(element => {
503
- // const node = element.closest(".form-group");
504
- const node = element.parentElement;
505
- // Creo un contenedor
506
- const newElement = document.createElement("div");
507
- newElement.id = "ponchoTableFiltro";
508
- newElement.classList.add("row");
509
- node.parentElement.appendChild(newElement);
510
- // Borro el viejo elemento
511
- node.remove();
512
- });
513
- };
2132
+ _filtersContainerClassList();
2133
+ _searchContainerClassList();
2134
+ _createTable(gapi_data);
2135
+ _createFilters(gapi_data);
514
2136
 
515
- function ponchoTable(opt) {
516
- ponchoTableLegacyPatch();
517
- return ponchoTableDependant(opt);
518
- }
2137
+ document.querySelector("#ponchoTableSearchCont")
2138
+ .style.display = "block";
2139
+ document.querySelector("#ponchoTable")
2140
+ .classList.remove("state-loading");
519
2141
 
2142
+ initDataTable();
2143
+ };
520
2144
 
521
- /**
522
- * PONCHO TABLE
523
- *
524
- * @summary PonchoTable con filtros dependientes
525
- *
526
- * @author Agustín Bouillet <bouilleta@jefatura.gob.ar>
527
- * @requires jQuery
528
- * @see https://github.com/argob/poncho/blob/master/src/demo/poncho-maps/readme-poncho-maps.md
529
- * @see https://datatables.net
530
- *
531
- *
532
- * MIT License
533
- *
534
- * Copyright (c) 2022 Argentina.gob.ar
535
- *
536
- * Permission is hereby granted, free of charge, to any person
537
- * obtaining a copy of this software and associated documentation
538
- * files (the "Software"), to deal in the Software without restriction,
539
- * including without limitation the rightsto use, copy, modify, merge,
540
- * publish, distribute, sublicense, and/or sell copies of the Software,
541
- * and to permit persons to whom the Software is furnished to do so,
542
- * subject to the following conditions:
543
- *
544
- * The above copyright notice and this permission notice shall be
545
- * included in all copies or substantial portions of the Software.
546
- *
547
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
548
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
549
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
550
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
551
- * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
552
- * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
553
- * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
554
- * SOFTWARE.
555
- */
556
- const ponchoTableDependant = opt => {
557
- var gapi_data;
558
- var filtersList = [];
559
- var filtro = {};
560
- var orderFilter = (opt.hasOwnProperty("orderFilter") && opt.orderFilter ?
561
- true : false);
562
- var asFilter = {};
563
- var allowedTags = ["*"];
564
- let markdownOptions = {
565
- "tables": true,
566
- "simpleLineBreaks": true,
567
- "extensions": [
568
- 'details',
569
- 'images',
570
- 'alerts',
571
- 'numbers',
572
- 'ejes',
573
- 'button',
574
- 'target',
575
- 'bootstrap-tables',
576
- 'video'
577
- ]
578
- };
579
-
580
- // Loader
581
- document.querySelector("#ponchoTable").classList.add("state-loading");
582
-
583
- if (jQuery.fn.DataTable.isDataTable("#ponchoTable")) {
584
- jQuery("#ponchoTable").DataTable().destroy();
585
- }
586
2145
 
587
- /**
588
- * Ordena alfanuméricamente
589
- * @example
590
- * // ["Arroz", "zorro"]
591
- * ["zorro", "Arroz"].sort(_sortAlphaNumeric)
592
- * @return {object}
593
- */
594
- const sortAlphaNumeric = (a, b) => {
595
- return a.toString().localeCompare(b.toString(), "es", {numeric: true});
596
- };
597
-
598
- /**
599
- * De acuerdo a las opciones del usuario, ordena el listado o lo deja
600
- * en la secuencia en la que llega.
601
- *
602
- * @summary Alias de sortAlphaNumeric
603
- * @param {object} a
604
- * @param {object} b
605
- * @returns {object}
606
- */
607
- const _sortAlphaNumeric = (a, b) => (orderFilter ?
608
- sortAlphaNumeric(a, b) : null);
609
-
610
- /**
611
- * Resultados únicos
612
- *
613
- * @param {object} list Array del que se quiere obtener
614
- * resultados únicos.
615
- * @returns {object}
616
- */
617
- const distinct = list => [... new Set(list)];
618
-
619
- /**
620
- * Select option
621
- *
622
- * @summary Crea un tag _option_ para un _select_.
623
- * @param {integer} parent Índice según el listado de filtros.
624
- * @param {string} label Valor para el label o texto visible.
625
- * @param {string} value Valor para el attributo _value_.
626
- * @param {boolean} selected Si el item debe mostrarse seleccionado.
627
- * @return {object}
628
- */
629
- const _optionSelect = (parent=0, label, value, selected=false) => {
630
- var select_option = document.createElement("option");
631
- select_option.value = value.toString().trim();
632
- select_option.dataset.column = parent;
633
- select_option.textContent = label.toString().trim();
634
- if(selected){
635
- select_option.setAttribute("selected", "selected");
636
- }
637
- return select_option;
638
- };
639
-
640
- /**
641
- * Prepara el término para ser buscado por REGEX
642
- *
643
- * @summary Escapa caracteres especiales utilizados en la sintáxis
644
- * de expresiones regulares.
645
- * @see replaceSpecialChars() en poncho.min.js
646
- * @param {string} term Término a buscar.
647
- * @example
648
- * // return Simbrón \(3\.180\)
649
- * _searchTerm("Simbrón (3.180)")
650
- * @return {string}
651
- */
652
- const _searchTerm = term => {
653
- return term.toString().replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
654
- };
655
-
656
- /**
657
- * Evita un valor negativo
658
- */
659
- const _parentElement = value => (value <= 0 ? 0 : value);
660
-
661
- /**
662
- * Retorna los valores de los filtros
663
- */
664
- const _filterValues = () => {
665
- return [...document.querySelectorAll("[data-filter]")].map(e => e.value);
666
- };
667
-
668
- /**
669
- * Showdown habilitado.
670
- *
671
- * Verifica si la librería _showdown_ está disponible.
672
- * @returns {boolean}
673
- */
674
- const _isMarkdownEnable = () => {
675
- if(typeof showdown !== "undefined" &&
676
- showdown.hasOwnProperty("Converter")){
677
- return true;
678
- }
679
- return false;
680
- };
681
-
682
- /**
683
- * Verifica si las extensiones showdown están definidas.
684
- *
685
- * @param {object} extensions
686
- * @returns {boolean}
687
- */
688
- const _isShowdownExtensionEnable = extensions =>
689
- extensions.every(e => {
690
- try {
691
- showdown.extension(e);
692
- return true;
693
- } catch (error) {
694
- return false;
695
- }
696
- });
2146
+ /**
2147
+ * Obtiene el sheet e inicializa la tabla y filtros.
2148
+ *
2149
+ * @param {string} url URL del dataset JSON
2150
+ */
2151
+ const getSheetValues = url => {
2152
+ jQuery.getJSON(url, function(data){
2153
+ const gapi = new GapiSheetData();
2154
+ gapi_data = gapi.json_data(data);
697
2155
 
698
- /**
699
- * Botón poncho
700
- *
701
- * @summary Imprime un botón bootstrap.
702
- * @param {string} label Label para el botón.
703
- * @param {string} value Href para el botón
704
- * @return {undefined}
705
- */
706
- const button = (label, value) => {
707
- const btn = document.createElement("a");
708
- btn.setAttribute("aria-label", label);
709
- btn.classList.add(
710
- "btn", "btn-primary", "btn-sm", "margin-btn");
711
- btn.target = "_blank";
712
- btn.href = value;
713
- btn.textContent = label;
714
- btn.setAttribute("rel", "noopener noreferrer");
715
- return btn.outerHTML;
716
- };
717
-
718
- /**
719
- * Formato de fecha
720
- *
721
- * @summary Agrega una etiqueta datetime para mejorar la indexación
722
- * y el ordenamiento.
723
- * @return {undefined}
724
- */
725
- const tdDate = value => {
726
- const dateSplit = value.split("/");
727
- const finalDateIso = new Date(
728
- dateSplit[2], dateSplit[1] - 1, dateSplit[0]
729
- );
730
-
731
- const hiddenSpan = document.createElement("span");
732
- hiddenSpan.style.display = "none";
733
- hiddenSpan.textContent = finalDateIso.toISOString().split('T')[0];
734
- return hiddenSpan.outerHTML + value;
735
- };
736
-
737
- /**
738
- * Imprime los filtros
739
- *
740
- * @param {object} gapi_data Objeto con la información separada del
741
- * documento Google Sheet
742
- */
743
- const _createFilters = gapi_data => {
744
- // Contenedor
745
- const tableFiltroCont = document.querySelector("#ponchoTableFiltro");
746
- tableFiltroCont.innerHTML = "";
747
-
748
- // Imprime cada uno de los filtros
749
- for (f in filtro){
750
- const columna = filtro[f][0].columna ? filtro[f][0].columna : 0;
751
- const list_filter = filtro[f]
752
- .map(e => e.value)
753
- .sort(_sortAlphaNumeric);
754
-
755
- const tplCol = document.createElement("div");
756
-
757
- if(opt.hasOwnProperty("filterClassList")){
758
- const classList = (typeof opt.filterClassList === "string" ?
759
- opt.filterClassList.split(" ") : opt.filterClassList);
760
- tplCol.classList.add(...classList);
761
- } else {
762
- const cols = Math.floor(12 / Object.keys(filtro).length);
763
- tplCol.classList.add("col-sm-12", `col-md-${cols}`);
764
- }
765
-
766
- const tplForm = document.createElement("div");
767
- tplForm.className = "form-group";
768
-
769
- const formLabel = document.createElement("label");
770
- formLabel.setAttribute("for", f);
771
- formLabel.textContent = gapi_data.headers[`filtro-${f}`];
772
-
773
- const select = document.createElement("select");
774
- select.classList.add("form-control");
775
- select.dataset.filter = 1;
776
- select.name = f;
777
- select.id = f;
778
- select.appendChild(_optionSelect(columna, "Todos", "", true));
779
- list_filter.forEach(item => {
780
- if(!item){
781
- return;
782
- }
783
- select.appendChild(_optionSelect(columna, item, item, false));
784
- });
785
-
786
- tplForm.appendChild(formLabel);
787
- tplForm.appendChild(select);
788
- tplCol.appendChild(tplForm);
789
- tableFiltroCont.appendChild(tplCol);
790
- }
791
- };
792
-
793
- /**
794
- * Imprime la tabla
795
- *
796
- * @param {object} gapi_data Objeto con la información separada del
797
- * documento Google Sheet
798
- */
799
- const _createTable = gapi_data => {
800
- // Table thead > th
801
- const thead = document.querySelector("#ponchoTable thead");
802
- thead.innerHTML = "";
803
-
804
- const theadTr = document.createElement("tr");
805
- Object.keys(gapi_data.headers).forEach((header, key) => {
806
- const th = document.createElement("th");
807
- th.textContent = gapi_data.headers[header];
808
- th.setAttribute("scope", "col");
809
- theadTr.appendChild(th);
810
- });
811
- thead.appendChild(theadTr);
812
-
813
- // Table caption
814
- const tableCaption = document.querySelector("#ponchoTable caption");
815
- tableCaption.innerHTML = "";
816
- tableCaption.textContent = opt.tituloTabla;
817
-
818
- // Tbody instance
819
- const tableTbody = document.querySelector("#ponchoTable tbody");
820
- tableTbody.innerHTML = "";
821
-
822
- // CONTENIDO FILAS
823
- gapi_data.entries.forEach((entry, key) => {
824
-
825
- if(!Object.values(entry).some(f => String(f).trim())){
826
- return;
827
- }
828
-
829
- // si se desea modificar la entrada desde opciones
830
- entry = (typeof opt.customEntry === "function" &&
831
- opt.customEntry !== null ? opt.customEntry(entry) : entry);
832
-
833
- // Inserta el row.
834
- const tbodyRow = tableTbody.insertRow();
835
- tbodyRow.id = "id_" + key;
836
-
837
- // Verifico sin las extensiones showdown existen
838
- let showdownOptions;
839
- if(_isMarkdownEnable()){
840
- const registeredOptions = (opt.hasOwnProperty("markdownOptions") ?
841
- opt.markdownOptions : markdownOptions);
842
- showdownOptions = (_isShowdownExtensionEnable(
843
- registeredOptions.extensions) ? registeredOptions : {});
844
- }
845
-
846
- // Recorro cada uno de los títulos
847
- Object.keys(gapi_data.headers).forEach(header => {
848
- filas = entry[header];
849
-
850
- if (header.startsWith("btn-") && filas != "") {
851
- filas = button(
852
- header.replace("btn-", "").replace("-", " "), filas
853
- );
854
- } else if (header.startsWith("fecha-") && filas != "") {
855
- filas = tdDate(filas);
856
- }
857
-
858
- const cell = tbodyRow.insertCell();
859
- cell.dataset.title = gapi_data.headers[header];
860
- if (filas == ""){
861
- cell.className = "hidden-xs";
862
- }
863
-
864
- // Si showdown está incluido lo uso
865
- // @todo Usar showdown fuera de la función. Usarlo en options.
866
- let allowed_tags = (opt.hasOwnProperty("allowedTags") ?
867
- opt.allowedTags : allowedTags);
868
-
869
- // Anchor como filtro está permitido por defecto.
870
- allowed_tags = ( header.startsWith("btn-") && filas != "" ?
871
- [...allowed_tags, "a"] : allowed_tags);
872
-
873
- const cleannedText = secureHTML(filas, allowed_tags);
874
- if(_isMarkdownEnable()){
875
- const converter = new showdown.Converter(showdownOptions);
876
- cell.innerHTML = converter.makeHtml(cleannedText);
877
- } else {
878
- cell.innerHTML = cleannedText;
879
- }
880
- });
881
- });
882
- };
883
-
884
- /**
885
- * Matriz filtro
886
- *
887
- * @summary Reune los filtros y por cada uno de ellos guarda los
888
- * datos —únicos—, de esa entrada.
889
- * @param {object} gapi_data Objeto con la información separada del
890
- * documento Google Sheet
891
- * @example
892
- * {
893
- * nombre_filtro : [
894
- * {
895
- * columna: 0,
896
- * value: "elemento"
897
- * },
898
- * ...
899
- * ]
900
- * }
901
- * @return {object}
902
- */
903
- const flterMatrix = (gapi_data, filtersList) => {
904
- let filters = {};
905
- filtersList.forEach((filter, key) => {
906
- let entiresByFilter = [];
907
- if(asFilter.hasOwnProperty(filtersList[key])){
908
- entiresByFilter = asFilter[filtersList[key]];
909
- } else {
910
- entiresByFilter = gapi_data.entries.map(entry => entry[filter]);
911
- }
912
-
913
- const uniqueEntries = distinct(entiresByFilter);
914
- uniqueEntries.sort(_sortAlphaNumeric);
915
- filter = filter.replace("filtro-", "");
916
- filters[filter] = [];
917
- uniqueEntries.forEach(entry => {
918
- filters[filter].push({"columna": key, "value": entry});
919
- });
920
- });
921
- return filters;
922
- };
923
-
924
- /* HELPERS FILTRO DEPENDIENTE */
925
- /**
926
- * Valida los parents
927
- *
928
- * @param {integer} parent Índice (filtro) seleccionado.
929
- * @return {boolean}
930
- */
931
- const _validateSteps = (parent, entry, label, values) => {
932
- // Verifico que por cada entrada el valor(label), se
933
- // encuentre en cada uno de los parents.
934
- // El bucle termina cuando llega al índice seleccionado.
935
- const range = [...Array(_parentElement(parent + 1)).keys()];
936
- const results = range.map(i => {
937
- // Chequeo si el valor del select es igual al parent o
938
- // si en su defecto, está vacío.
939
- if(
940
- (
941
- (entry[filtersList[_parentElement(parent-1)]] ==
942
- values[_parentElement(parent-1)]) &&
943
- (entry[filtersList[_parentElement(parent)]] == label)
944
- ) || values[_parentElement(parent-1)] == "")
945
- {
946
- return true;
947
- }
948
- return false;
949
- });
950
- return results.every(e => e);
951
- };
952
-
953
- /**
954
- * Trae todos los elementos de un filtro en base a su parent.
955
- *
956
- * @param {integer} parent Indice de filtro seleccionado.
957
- * @param {integer} children Indice del hijo del seleccionado.
958
- * @param {string} label value del filtro seleccionado.
959
- * @return {object} Listado de elementos únicos para el select.
960
- */
961
- const _allFromParent = (parent, children, label) => {
962
- const filterList = gapi_data.entries.flatMap(e => {
963
- const evaluatedEntry = e[filtersList[_parentElement(children)]];
964
- if(e[filtersList[_parentElement(parent)]] == label || label == ""){
965
- if(_isCustomFilter(children, filtro)){
966
- const customFilters = _customFilter(children, filtro)
967
- .filter(e => {
968
- return _toCompareString(evaluatedEntry)
969
- .includes(_toCompareString(e));
970
- });
971
- return customFilters;
972
- }
973
- return evaluatedEntry;
974
- }
975
- return false;
976
-
977
- }).filter(f => f);
978
-
979
- const uniqueList = distinct(filterList);
980
- uniqueList.sort(_sortAlphaNumeric);
981
- return uniqueList;
982
- };
983
-
984
- /**
985
- * Prepara un string para una comparación case sensitive y sin
986
- * caracteres especiales.
987
- * @param {string} value Valor a comparar.
988
- * @returns {boolean}
989
- */
990
- const _toCompareString = value => replaceSpecialChars(value.toLowerCase());
991
-
992
- /**
993
- * Lista los valores que deben ir en un filtro según su parent.
994
- *
995
- * @param {integer} parent Indice de filtro seleccionado.
996
- * @param {string} label value del filtro seleccionado.
997
- * @param {integer} children Indice del hijo del seleccionado.
998
- */
999
- const _filterOptionList = (parent, children, label) => {
1000
- children = (children == filtersList.length ? children - 1 : children);
1001
- const values = _filterValues();
1002
-
1003
- // Recorro todas las entradas del JSON
1004
- const items = gapi_data.entries.flatMap(entry => {
1005
- const range = _validateSteps(parent, entry, label, values);
1006
- if(
1007
- (entry[filtersList[_parentElement(children - 1)]] == label) &&
1008
- (range)){
1009
- const evaluatedEntry = entry[filtersList[_parentElement(children)]];
1010
- if(_isCustomFilter(children, filtro)){
1011
- const customFilters = _customFilter(children, filtro)
1012
- .filter(e => {
1013
- return _toCompareString(evaluatedEntry)
1014
- .includes(_toCompareString(e));
1015
- });
1016
- return customFilters;
1017
- } else {
1018
- return evaluatedEntry;
1019
- }
2156
+ render(gapi_data);
2157
+ }); // end async
2158
+ };
2159
+
2160
+
2161
+ /**
2162
+ * Obtiene el sheet por número de hoja y nombre del spread.
2163
+ *
2164
+ * @param {integer} sheetNumber Número de hoja sin iniciar en 0.
2165
+ */
2166
+ const getSheetName = sheetNumber => {
2167
+ const gapi = new GapiSheetData();
2168
+ const uriApi = [
2169
+ "https://sheets.googleapis.com/v4/spreadsheets/",
2170
+ opt.idSpread, "/?alt=json&key=",
2171
+ gapi.gapi_key].join("");
2172
+
2173
+ jQuery.getJSON(uriApi, function function_name(response) {
2174
+ var sheetName = response.sheets[sheetNumber - 1].properties.title;
2175
+ const uriSheet = gapi.url(sheetName, opt.idSpread);
2176
+ getSheetValues(uriSheet);
2177
+ });
2178
+ };
1020
2179
 
1021
- }
1022
- return;
1023
- }).filter(f => f);
1024
-
1025
- const uniqueList = distinct(items);
1026
- uniqueList.sort(_sortAlphaNumeric);
1027
- return uniqueList;
1028
- };
1029
-
1030
- /**
1031
- * Tiene filtros personalizados
1032
- * @param {integer} key Indice de filtro
1033
- * @returns {boolean}
1034
- */
1035
- const _isCustomFilter = key => {
1036
- const filtersKeys = Object.keys(filtro);
1037
- if(asFilter.hasOwnProperty(`filtro-${filtersKeys[key]}`)){
1038
- return true
1039
- }
1040
- return false;
1041
- };
1042
-
1043
- /**
1044
- * Listado de filtros personalizado
1045
- * @param {integer} key Indice de filtro
1046
- * @returns {object}
1047
- */
1048
- const _customFilter = key => {
1049
- const filtersKeys = Object.keys(filtro);
1050
- if(asFilter.hasOwnProperty(`filtro-${filtersKeys[key]}`)){
1051
- return asFilter[`filtro-${filtersKeys[key]}`];
1052
- }
1053
- return [];
1054
- };
1055
-
1056
- /**
1057
- * Filtra select hijos en base a un item del padre.
1058
- *
1059
- * @param {integer} filterIndex Índice de filtro o número de filtro.
1060
- * @param {string} label Label del indice seleccionado
1061
- * @return {void}
1062
- */
1063
- const _dependantFilters = (filterIndex, label) => {
1064
- const filtros = Object.keys(filtro);
1065
- const filterValues = _filterValues();
1066
- // Redibujo los _option_ por cada `select` (filtro).
1067
- // Hago un `for()` iniciando en el hijo de filterIndex.
1068
- for(let i = filterIndex + 1; i <= filtros.length; i++){
1069
- if(filtros.length == i ){
1070
- break;
1071
- }
1072
- let itemList = _filterOptionList(filterIndex, i, label);
1073
- if(itemList.length == 0){
1074
- itemList = _allFromParent(filterIndex, i, label);
1075
- }
1076
- const select = document.querySelector(`#${filtros[i]}`);
1077
- select.innerHTML = "";
1078
-
1079
- select.appendChild(_optionSelect(i, "Todos", "", true));
1080
- itemList.forEach(e => {
1081
- if(!e.trim()){
1082
- return;
1083
- }
1084
- // Mantengo el filtro del hijo si existe en el
1085
- // listado filtrado.
1086
- let checked = (filterValues[i] == e ? true : false);
1087
- select.appendChild(_optionSelect(i, e, e, checked));
1088
- });
1089
- }
1090
- };
1091
-
1092
- /**
1093
- * Asigna selectores al contenedor de los filtros.
1094
- * @returns {undefined}
1095
- */
1096
- const _filtersContainerClassList = () =>{
1097
- if(opt.hasOwnProperty("filterContClassList") && opt.filterContClassList){
1098
- const fc = document.getElementById("ponchoTableFiltroCont");
1099
- fc.removeAttribute("class");
1100
- fc.classList.add(...opt.filterContClassList);
1101
- }
1102
- };
1103
-
1104
- /**
1105
- * Asigna selectores al contenedor del buscador.
1106
- * @returns {undefined}
1107
- */
1108
- const _searchContainerClassList = () =>{
1109
- if(opt.hasOwnProperty("searchContClassList") && opt.searchContClassList){
1110
- const element = document.getElementById("ponchoTableSearchCont");
1111
- element.removeAttribute("class");
1112
- element.classList.add(...opt.searchContClassList)
2180
+ // Según el caso en optiones.
2181
+ if (opt.jsonData){
2182
+ const headers = Object.fromEntries(
2183
+ Object.keys(opt.jsonData[0]).map(e => [e,e])
2184
+ );
2185
+
2186
+ const data = {entries: opt.jsonData, headers};
2187
+ render(data);
2188
+ } else if (opt.jsonUrl) {
2189
+ getSheetValues(opt.jsonUrl);
2190
+ } else if(opt.hojaNombre && opt.idSpread){
2191
+ const url = new GapiSheetData().url(opt.hojaNombre, opt.idSpread);
2192
+ getSheetValues(url);
2193
+ } else if(opt.hojaNumero && opt.idSpread){
2194
+ getSheetName(opt.hojaNumero);
2195
+ } else {
2196
+ throw "¡Error! No hay datos suficientes para crear la tabla.";
1113
2197
  }
1114
- };
1115
-
1116
- /**
1117
- * Si la URL tiene un valor por _hash_ lo obtiene considerandolo su id.
1118
- * @returns {void}
1119
- */
1120
- const hasHash = () => {
1121
- let hash = window.location.hash.replace("#", "");
1122
- return hash || false;
1123
- };
1124
-
1125
- /**
1126
- * Inicializa DataTable() y modifica elementos para adaptarlos a
1127
- * GoogleSheets y requerimientos de ArGob.
1128
- */
1129
- const initDataTable = () => {
1130
- const searchType = jQuery.fn.DataTable.ext.type.search;
1131
- searchType.string = data => {
1132
- return (!data ?
1133
- "" :
1134
- (typeof data === "string" ?
1135
- replaceSpecialChars(data) :
1136
- data));
1137
- };
1138
-
1139
- searchType.html = data => {
1140
- return (!data ?
1141
- "" :
1142
- (typeof data === "string" ?
1143
- replaceSpecialChars(data.replace( /<.*?>/g, "")) :
1144
- data));
1145
- };
1146
-
1147
- /**
1148
- * Instacia DataTable()
1149
- */
1150
- let tabla = jQuery("#ponchoTable").DataTable({
1151
- "lengthChange": false,
1152
- "autoWidth": false,
1153
- "pageLength": opt.cantidadItems,
1154
- "columnDefs": [
1155
- { "type": "html-num", "targets": opt.tipoNumero },
1156
- { "targets": opt.ocultarColumnas, "visible": false }
1157
- ],
1158
- "ordering": opt.orden,
1159
- "order": [
1160
- [opt.ordenColumna - 1, opt.ordenTipo]
1161
- ],
1162
- "dom": "<'row'<'col-sm-6'l><'col-sm-6'f>>" +
1163
- "<'row'<'col-sm-12'i>>" +
1164
- "<'row'<'col-sm-12'tr>>" +
1165
- "<'row'<'col-md-offset-3 col-md-6 col-sm-offset-2 col-sm-8'p>>",
1166
- "language": {
1167
- "sProcessing": "Procesando...",
1168
- "sLengthMenu": "Mostrar _MENU_ registros",
1169
- "sZeroRecords": "No se encontraron resultados",
1170
- "sEmptyTable": "Ningún dato disponible en esta tabla",
1171
- "sInfo": "_TOTAL_ resultados",
1172
- "sInfoEmpty": "No hay resultados",
1173
- //"sInfoFiltered": "(filtrado de un total de _MAX_ registros)",
1174
- "sInfoFiltered": "",
1175
- "sInfoPostFix": "",
1176
- "sSearch": "Buscar:",
1177
- "sUrl": "",
1178
- "sInfoThousands": ".",
1179
- "sLoadingRecords": "Cargando...",
1180
-
1181
- "oPaginate": {
1182
- "sFirst": "<<",
1183
- "sLast": ">>",
1184
- "sNext": ">",
1185
- "sPrevious": "<"
1186
- },
1187
- "oAria": {
1188
- "sSortAscending":
1189
- ": Activar para ordenar la columna de manera ascendente",
1190
- "sSortDescending":
1191
- ": Activar para ordenar la columna de manera descendente",
1192
- "paginate": {
1193
- "first": 'Ir a la primera página',
1194
- "previous": 'Ir a la página anterior',
1195
- "next": 'Ir a la página siguiente',
1196
- "last": 'Ir a la última página'
1197
- }
1198
- }
1199
- }
1200
- });
1201
-
1202
- /**
1203
- * Buscador por palabra
1204
- * @summary Ejecuta la búsqueda en cada keyup.
1205
- */
1206
- jQuery("#ponchoTableSearch").keyup(function() {
1207
- tabla
1208
- .search(jQuery.fn.DataTable.ext.type.search.string(this.value))
1209
- .draw();
1210
- });
1211
-
1212
- // REMUEVE LOS FILTROS
1213
- jQuery("#ponchoTable_filter").parent().parent().remove();
1214
-
1215
- // MUESTRA FILTRO PERSONALIZADO
1216
- const ponchoTableOption = document.querySelectorAll("#ponchoTableFiltro option");
1217
- if (ponchoTableOption.length > 1) {
1218
- document.querySelector("#ponchoTableFiltroCont").style.display = "block";
1219
- }
1220
-
1221
- /**
1222
- * Filtro en el change de cada select (filtro).
1223
- *
1224
- * @summary Por por cada interacción con un filtro, obtiene el índice de
1225
- * columna y lo pasa con el valor del select a _dependantFilters(). Ésta
1226
- * funciión redibuja los filtros en de forma dependiente según el valor
1227
- * de la elección.
1228
- * @see replaceSpecialChars() on poncho.min.js
1229
- * @return {undefined}
1230
- */
1231
- jQuery("select[data-filter]").change(function() {
1232
- const column = jQuery(this).find("option:selected").data("column");
1233
- const valFilter = jQuery(this).find("option:selected").val();
1234
-
1235
- _dependantFilters(column, valFilter);
1236
-
1237
- // Restablece los datos en la tabla
1238
- tabla.columns().search("").columns().search("").draw();
1239
-
1240
- const filters = Object.keys(filtro);
1241
- const filterValues = _filterValues();
1242
- const filterIndex = filter => {
1243
- return Object
1244
- .keys(gapi_data.headers)
1245
- .indexOf(`filtro-${filter}`);
1246
- };
1247
-
1248
- filterValues.forEach((f, k) => {
1249
- const columnIndex = filterIndex(filters[k]);
1250
- const term = _searchTerm(filterValues[k]);
1251
- const cleanTerm = _searchTerm(
1252
- replaceSpecialChars(filterValues[k]));
1253
- if(_isCustomFilter(k, filtro)){
1254
- tabla.columns(columnIndex)
1255
- .search(_toCompareString(filterValues[k]));
1256
- } else {
1257
- tabla
1258
- .columns(columnIndex)
1259
- .search(
1260
- (filterValues[k] ? `^(${term}|${cleanTerm})$` : ""),
1261
- true, false, true
1262
- );
1263
- }
1264
- });
1265
- tabla.draw();
1266
- });
1267
-
1268
- // Si está habilitada la búsqueda por hash.
1269
- if(opt.hasOwnProperty("hash") && opt.hash){
1270
- const term = hasHash();
1271
- const searchTerm = (term ? decodeURIComponent(term) : "");
1272
- const element = document.querySelector("#ponchoTableSearch");
1273
- element.value = searchTerm;
1274
- tabla
1275
- .search(jQuery.fn.DataTable.ext.type.search.string(searchTerm))
1276
- .draw();
1277
-
1278
- }
1279
- } // end initDataTable
1280
-
1281
- /**
1282
- * Imprime DataTable
1283
- *
1284
- * @param {object} data JSON data
1285
- */
1286
- const render = data => {
1287
- // Defino la variable global
1288
- gapi_data = data;
1289
- // Defino las entradas
1290
- gapi_data.entries = (
1291
- typeof opt.refactorEntries === "function" &&
1292
- opt.refactorEntries !== null ?
1293
- opt.refactorEntries(gapi_data.entries) : gapi_data.entries
1294
- );
1295
- // Defino los headers que se van a utilizar
1296
- gapi_data.headers = (opt.hasOwnProperty("headers") && opt.headers ?
1297
- opt.headers : gapi_data.headers);
1298
- // Listado de filtros
1299
- filtersList = Object
1300
- .keys(gapi_data.headers)
1301
- .filter(e => e.startsWith("filtro-"));
1302
-
1303
- asFilter = (opt.asFilter ? opt.asFilter(gapi_data.entries) : {});
1304
- filtro = flterMatrix(gapi_data, filtersList);
1305
-
1306
- _filtersContainerClassList();
1307
- _searchContainerClassList();
1308
- _createTable(gapi_data);
1309
- _createFilters(gapi_data);
1310
-
1311
- document.querySelector("#ponchoTableSearchCont")
1312
- .style.display = "block";
1313
- document.querySelector("#ponchoTable")
1314
- .classList.remove("state-loading");
1315
- initDataTable();
1316
- };
1317
-
1318
- /**
1319
- * Obtiene el sheet e inicializa la tabla y filtros.
1320
- *
1321
- * @param {string} url URL del dataset JSON
1322
- */
1323
- const getSheetValues = url => {
1324
- jQuery.getJSON(url, function(data){
1325
- const gapi = new GapiSheetData();
1326
- gapi_data = gapi.json_data(data);
1327
-
1328
- render(gapi_data);
1329
- }); // end async
1330
- };
1331
-
1332
- /**
1333
- * Obtiene el sheet por número de hoja y nombre del spread.
1334
- *
1335
- * @param {integer} sheetNumber Número de hoja sin iniciar en 0.
1336
- */
1337
- const getSheetName = sheetNumber => {
1338
- const gapi = new GapiSheetData();
1339
- const uriApi = [
1340
- 'https://sheets.googleapis.com/v4/spreadsheets/',
1341
- opt.idSpread, '/?alt=json&key=',
1342
- gapi.gapi_key].join("");
1343
-
1344
- jQuery.getJSON(uriApi, function function_name(response) {
1345
- var sheetName = response.sheets[sheetNumber - 1].properties.title;
1346
- const uriSheet = gapi.url(sheetName, opt.idSpread);
1347
- getSheetValues(uriSheet);
1348
- });
1349
- };
1350
-
1351
- // Según el caso en optiones.
1352
- if (opt.jsonData){
1353
- const headers = Object.fromEntries(
1354
- Object.keys(opt.jsonData[0]).map(e => [e,e])
1355
- );
1356
- const data = {entries: opt.jsonData, headers: headers};
1357
- render(data);
1358
- } else if (opt.jsonUrl) {
1359
- getSheetValues(opt.jsonUrl);
1360
- } else if(opt.hojaNombre && opt.idSpread){
1361
- const url = new GapiSheetData().url(opt.hojaNombre, opt.idSpread);
1362
- getSheetValues(url);
1363
- } else if(opt.hojaNumero && opt.idSpread){
1364
- getSheetName(opt.hojaNumero);
1365
- } else {
1366
- throw "¡Error! No hay datos suficientes para crear la tabla.";
1367
- }
1368
2198
 
1369
2199
  };
1370
2200
 
@@ -2878,6 +3708,14 @@ function ponchoChart(opt) {
2878
3708
  */
2879
3709
  const gapi_legacy = (response) => {
2880
3710
 
3711
+ if (!response || !response.values || response.values.length === 0) {
3712
+ throw new TypeError("Invalid response format");
3713
+ }
3714
+
3715
+ if (!Array.isArray(response.values) || !Array.isArray(response.values[0])) {
3716
+ throw new TypeError("Invalid response format: values should be arrays");
3717
+ }
3718
+
2881
3719
  const keys = response.values[0];
2882
3720
  const regex = / |\/|_/ig;
2883
3721
  let entry = [];
@@ -2897,7 +3735,9 @@ const gapi_legacy = (response) => {
2897
3735
  };
2898
3736
 
2899
3737
 
2900
- /* module.exports REMOVED */
3738
+ if (typeof exports !== "undefined") {
3739
+ module.exports = gapi_legacy;
3740
+ }
2901
3741
 
2902
3742
  /**
2903
3743
  * PONCHO MAP
@@ -2944,6 +3784,7 @@ class PonchoMap {
2944
3784
  no_info: false,
2945
3785
  title: false,
2946
3786
  id: "id",
3787
+ id_mixing: [],
2947
3788
  template: false,
2948
3789
  template_structure: {
2949
3790
  // lead: [],
@@ -2959,11 +3800,27 @@ class PonchoMap {
2959
3800
  definition_tag: "dd",
2960
3801
  term_classlist: ["h6", "m-b-0"],
2961
3802
  term_tag: "dt",
2962
- title_classlist: ["h4","text-primary","m-t-0"]
3803
+ title_classlist: ["h4","pm-color-primary","m-t-0"]
2963
3804
  },
3805
+ fit_bounds_onevent: true,
2964
3806
  allowed_tags: [],
2965
3807
  template_innerhtml: false,
2966
3808
  template_markdown: false,
3809
+ theme_ui: false,
3810
+ theme_map: false,
3811
+ theme_tool: true,
3812
+ map_opacity: 1,
3813
+ map_background: "#DDD",
3814
+ theme: "default",
3815
+ default_themes: [
3816
+ ["default", "Original"],
3817
+ ["contrast", "Alto contraste"],
3818
+ ["dark", "Oscuro"],
3819
+ ["grayscale", "Gris"],
3820
+ // ["sepia", "Sepia"],
3821
+ // ["blue", "Azul"],
3822
+ ["relax", "Relax"]
3823
+ ],
2967
3824
  markdown_options: {
2968
3825
  extensions :[
2969
3826
  "details",
@@ -2994,28 +3851,38 @@ class PonchoMap {
2994
3851
  map_anchor_zoom: 16,
2995
3852
  map_zoom: 4,
2996
3853
  min_zoom: 2,
3854
+ map_init_options: {
3855
+ // zoomSnap: .2,
3856
+ // zoomControl: false,
3857
+ // scrollWheelZoom: false,
3858
+ // touchZoom: false,
3859
+ // doubleClickZoom: false,
3860
+ // scrollWheelZoom: false,
3861
+ // boxZoom: false,
3862
+ // dragging:false
3863
+ },
2997
3864
  reset_zoom: true,
2998
3865
  latitud: "latitud",
2999
3866
  longitud: "longitud",
3000
3867
  marker: "azul",
3001
3868
  tooltip: false,
3002
3869
  tooltip_options:{
3003
- // permanent: false,
3870
+ permanent: false,
3004
3871
  className: "leaflet-tooltip-own",
3005
3872
  direction: "auto",
3006
- // offset: [15,0],
3007
- // sticky: true,sdf
3008
- // opacity: 1,
3873
+ offset: [13,-18],
3874
+ sticky: false,
3875
+ opacity: 1,
3009
3876
  },
3010
3877
  marker_cluster_options: {
3011
- spiderfyOnMaxZoom: false,
3878
+ spiderfyOnMaxZoom: true,
3012
3879
  showCoverageOnHover: false,
3013
3880
  zoomToBoundsOnClick: true,
3014
- maxClusterRadius: 45,
3015
- spiderfyDistanceMultiplier: 3,
3881
+ maxClusterRadius: 30,
3882
+ spiderfyDistanceMultiplier: 0.5,
3016
3883
  spiderLegPolylineOptions: {
3017
3884
  weight: 1,
3018
- color: "#666",
3885
+ color: "#666666",
3019
3886
  opacity: 0.5,
3020
3887
  "fill-opacity": 0.5
3021
3888
  }
@@ -3045,7 +3912,9 @@ class PonchoMap {
3045
3912
  this.header_icons = opts.header_icons;
3046
3913
  this.hash = opts.hash;
3047
3914
  this.scroll = opts.scroll;
3915
+ this.fit_bounds_onevent = opts.fit_bounds_onevent;
3048
3916
  this.map_view = opts.map_view;
3917
+ this.map_init_options = opts.map_init_options;
3049
3918
  this.anchor_delay = opts.anchor_delay;
3050
3919
  this.map_zoom = opts.map_zoom;
3051
3920
  this.min_zoom = opts.min_zoom;
@@ -3055,7 +3924,16 @@ class PonchoMap {
3055
3924
  this.marker_cluster_options = opts.marker_cluster_options;
3056
3925
  this.marker_color = opts.marker;
3057
3926
  this.id = opts.id;
3927
+ this.id_mixing = opts.id_mixing;
3058
3928
  this.title = opts.title;
3929
+ this.map_background = opts.map_background;
3930
+ this.theme = opts.theme;
3931
+ this.theme_tool = opts.theme_tool;
3932
+ this.default_themes = opts.default_themes;
3933
+ this.temes_not_visibles = [["transparent", "Transparent"]];
3934
+ this.theme_ui = opts.theme_ui;
3935
+ this.theme_map = opts.theme_map;
3936
+ this.map_opacity = opts.map_opacity;
3059
3937
  this.latitude = opts.latitud;
3060
3938
  this.longitude = opts.longitud;
3061
3939
  this.slider = opts.slider;
@@ -3067,6 +3945,7 @@ class PonchoMap {
3067
3945
  this.scope_sufix = `--${this.scope}`;
3068
3946
  this.content_selector = (opts.content_selector ?
3069
3947
  opts.content_selector : `.js-content${this.scope_sufix}`);
3948
+ this.content_outside = (this.content_selector !== `.js-content${this.scope_sufix}` ? true : false);
3070
3949
  this.data = this.formatInput(data);
3071
3950
  this.geometryTypes = [
3072
3951
  "Point",
@@ -3087,9 +3966,13 @@ class PonchoMap {
3087
3966
  this.geojson;
3088
3967
 
3089
3968
  // OSM
3090
- this.map = new L.map(this.map_selector, {renderer:L.svg()}
3969
+ this.map = new L.map(this.map_selector, {
3970
+ renderer: L.svg(),
3971
+ ...this.map_init_options
3972
+ }
3091
3973
  ).setView(this.map_view, this.map_zoom);
3092
- this.titleLayer = new L.tileLayer("https://mapa-ign.argentina.gob.ar/osm/{z}/{x}/{-y}.png",{
3974
+ this.titleLayer = new L.tileLayer("https://mapa-ign.argentina.gob.ar/osm/{z}/{x}/{-y}.png",
3975
+ {
3093
3976
  attribution: ("Contribuidores: "
3094
3977
  + "<a href=\"https://www.ign.gob.ar/AreaServicios/Argenmap/Introduccion\" "
3095
3978
  + "target=\"_blank\">"
@@ -3102,6 +3985,243 @@ class PonchoMap {
3102
3985
  this.ponchoLoaderTimeout;
3103
3986
  }
3104
3987
 
3988
+
3989
+ //
3990
+ // TEMA PARA EL MAPA
3991
+ //
3992
+
3993
+ /**
3994
+ * Modifica la opacidad del mapa
3995
+ * @param {double|string} value Número _(double)_, o porcentaje de valor
3996
+ * @example
3997
+ * mapOpacity(0.5)
3998
+ * mapOpacity("50%")
3999
+ * @returns {undefined}
4000
+ */
4001
+ mapOpacity = (value=false) => {
4002
+ const opacity = (value ? value : this.map_opacity);
4003
+ document
4004
+ .querySelectorAll(
4005
+ `${this.scope_selector} .leaflet-pane .leaflet-tile-pane`)
4006
+ .forEach(e => e.style.opacity=opacity);
4007
+ }
4008
+
4009
+
4010
+ /**
4011
+ * Define el color de fondo para el mapa.
4012
+ * @param {string} value Color en hexadecimal o cualquier sistema de color
4013
+ * aceptado por html.
4014
+ * @example
4015
+ * // #ffcc00
4016
+ * // Se representará como: style="background-color:#ffcc00;"
4017
+ * mapBackgroundColor("#ffcc00")
4018
+ * @returns {undefined}
4019
+ */
4020
+ mapBackgroundColor = (value=false) => {
4021
+ const color = (value ? value : this.map_background);
4022
+ document
4023
+ .querySelectorAll(`${this.scope_selector} .leaflet-container`)
4024
+ .forEach(e => e.style.backgroundColor = color);
4025
+ };
4026
+
4027
+
4028
+ /**
4029
+ * Crea el menú para cambiar de temas
4030
+ * @returns {undefined}
4031
+ */
4032
+ _menuTheme = () => {
4033
+ if(!this.theme_tool){
4034
+ return;
4035
+ }
4036
+ document
4037
+ .querySelectorAll(`#themes-tool${this.scope_sufix}`)
4038
+ .forEach(ele => ele.remove());
4039
+
4040
+ const navContainer = document.createElement("ul");
4041
+ navContainer.classList.add(
4042
+ "pm-list-unstyled", "pm-list",
4043
+ "pm-tools",
4044
+ `js-themes-tool${this.scope_sufix}`
4045
+ );
4046
+
4047
+
4048
+ const item = document.createElement("li");
4049
+ item.setAttribute("tabindex", "-1");
4050
+ item.dataset.toggle="true";
4051
+
4052
+ const icon = document.createElement("i");
4053
+ icon.setAttribute("aria-hidden", "true");
4054
+ icon.classList.add("pmi", "pmi-adjust");
4055
+
4056
+ const button = document.createElement("button");
4057
+ button.title = "Cambiar tema";
4058
+ button.tabIndex = "0";
4059
+ button.classList.add("pm-btn", "pm-btn-rounded-circle");
4060
+ button.appendChild(icon);
4061
+ button.setAttribute("role", "button");
4062
+ button.setAttribute("aria-label", "Abre el panel de temas");
4063
+
4064
+ const list = document.createElement("ul");
4065
+ list.classList.add(
4066
+ "pm-container", "pm-list", "pm-list-unstyled",
4067
+ "pm-p-1", "pm-caret", "pm-caret-b", "pm-toggle");
4068
+
4069
+ // Botón para restablecer el mapa
4070
+ const restart = document.createElement("button");
4071
+ restart.textContent = "Restablecer";
4072
+ restart.classList.add("pm-item-link", "js-reset-theme");
4073
+ const li = document.createElement("li");
4074
+ li.classList.add("pm-item-separator");
4075
+ li.appendChild(restart);
4076
+ list.appendChild(li);
4077
+
4078
+ this.default_themes.map(m => m[0]).forEach((value, key) => {
4079
+ const buttonTheme = document.createElement("button");
4080
+ buttonTheme.dataset.theme = value;
4081
+ buttonTheme.textContent = this.default_themes[key][1];
4082
+ buttonTheme.classList.add("js-set-theme", "pm-item-link");
4083
+
4084
+ const li = document.createElement("li");
4085
+ li.appendChild(buttonTheme);
4086
+
4087
+ list.appendChild(li);
4088
+ });
4089
+
4090
+ item.appendChild(button);
4091
+ item.appendChild(list);
4092
+ navContainer.appendChild(item)
4093
+
4094
+ const element = document.querySelectorAll(this.scope_selector);
4095
+ element.forEach(e => e.appendChild(navContainer));
4096
+
4097
+ // listeners
4098
+ document
4099
+ .querySelectorAll(".js-reset-theme")
4100
+ .forEach(ele => ele.addEventListener(
4101
+ "click", () => {
4102
+ localStorage.removeItem("mapTheme");
4103
+ this._removeThemes();
4104
+ this._setThemes();
4105
+ })
4106
+ );
4107
+
4108
+ document
4109
+ .querySelectorAll(".js-set-theme")
4110
+ .forEach(ele => ele.addEventListener(
4111
+ "click", () => {
4112
+ const th = ele.dataset.theme;
4113
+ this.useTheme(th);
4114
+ localStorage.setItem("mapTheme", th);
4115
+ })
4116
+ );
4117
+ };
4118
+
4119
+
4120
+ /**
4121
+ * Compone los estilos según la elección del usuario.
4122
+ * @param {string} theme Nombre del tema
4123
+ * @param {object} prefix Lista de prefijos. ui o map
4124
+ * @returns {object} Array con los estilos definidos.
4125
+ */
4126
+ _setThemeStyles = (theme=false, prefix=["ui", "map"]) => prefix.map(m => {
4127
+ return (["ui", "map"].includes(m) ? `${m}-${theme}` : false);
4128
+ });
4129
+
4130
+
4131
+ /**
4132
+ * Remueve los estilos de tema
4133
+ * @param {object} prefix Lista de prefijos. ui o map
4134
+ * @returns {undefined}
4135
+ */
4136
+ _removeThemes = (prefix=["ui", "map"]) => {
4137
+ const element = document.querySelectorAll(this.scope_selector);
4138
+ element.forEach(ele => {
4139
+ [
4140
+ ...this.default_themes,
4141
+ ...this.temes_not_visibles
4142
+ ].map(m => m[0]).forEach(th => {
4143
+ ele.classList.remove(...this._setThemeStyles(th, prefix));
4144
+ });
4145
+ });
4146
+ };
4147
+
4148
+
4149
+ /**
4150
+ * Agrega un tema en el mapa e interfase.
4151
+ * @param {string} theme Nombre del tema
4152
+ * @param {object} prefix Lista de prefijos. ui o map
4153
+ * @returns {undefined}
4154
+ */
4155
+ _setTheme = (theme, prefix) => {
4156
+ const element = document.querySelectorAll(this.scope_selector);
4157
+ this._removeThemes(prefix);
4158
+ element.forEach(ele => {
4159
+ ele.classList.add( ...this._setThemeStyles(theme, prefix) );
4160
+ });
4161
+ }
4162
+
4163
+
4164
+ /**
4165
+ * Permite setear un tema completo
4166
+ * @param {string} theme Nombre del tema
4167
+ * @returns {undefined}
4168
+ */
4169
+ useTheme = (theme = false) => {
4170
+ const useTheme = (theme ? theme : this.theme);
4171
+ this._setTheme(useTheme, ["ui", "map"]);
4172
+ }
4173
+
4174
+
4175
+ /**
4176
+ * Setea un tema para el mapa
4177
+ * @param {string} theme Nombre del tema
4178
+ * @returns {undefined}
4179
+ */
4180
+ useMapTheme = theme => this._setTheme(theme, ["map"]);
4181
+
4182
+
4183
+ /**
4184
+ * Setea un tema para el mapa
4185
+ * @param {string} theme Nombre del tema
4186
+ * @returns {undefined}
4187
+ */
4188
+ useUiTheme = theme => this._setTheme(theme, ["ui"]);
4189
+
4190
+
4191
+ /**
4192
+ * Setea el tema elegido por el usuario o el que lleva por defecto.
4193
+ * @returns {undefined}
4194
+ */
4195
+ _setThemes = () => {
4196
+ if(localStorage.hasOwnProperty("mapTheme")){
4197
+ this._setTheme(localStorage.getItem("mapTheme"), ["ui", "map"]);
4198
+ return;
4199
+ }
4200
+
4201
+ if(!this.theme_ui && !this.theme_map){
4202
+ this.useTheme();
4203
+ return;
4204
+ }
4205
+
4206
+ if(this.theme_ui){
4207
+ this._setTheme(this.theme_ui, ["ui"]);
4208
+ }
4209
+
4210
+ if(this.theme_map){
4211
+ this._setTheme(this.theme_map, ["map"]);
4212
+ }
4213
+ }
4214
+
4215
+
4216
+ /**
4217
+ * Alias de sluglify
4218
+ *
4219
+ * @param {string} val Texto a formatear
4220
+ * @returns {string}
4221
+ */
4222
+ _slugify = val => slugify(val);
4223
+
4224
+
3105
4225
  /**
3106
4226
  * Es un geoJSON
3107
4227
  *
@@ -3117,6 +4237,7 @@ class PonchoMap {
3117
4237
  return false;
3118
4238
  };
3119
4239
 
4240
+
3120
4241
  /**
3121
4242
  * JSON input
3122
4243
  *
@@ -3126,6 +4247,7 @@ class PonchoMap {
3126
4247
  return this.data.features;
3127
4248
  }
3128
4249
 
4250
+
3129
4251
  /**
3130
4252
  * Retrona las entradas en formato geoJSON
3131
4253
  */
@@ -3185,7 +4307,7 @@ class PonchoMap {
3185
4307
  );
3186
4308
 
3187
4309
  const title = document.createElement("h2");
3188
- title.classList.add("h6", "title", "sr-only");
4310
+ title.classList.add("h6", "title", "pm-visually-hidden");
3189
4311
  title.textContent = "¡Se produjo un error!";
3190
4312
 
3191
4313
  container.appendChild(mapIcon);
@@ -3282,6 +4404,42 @@ class PonchoMap {
3282
4404
  };
3283
4405
 
3284
4406
 
4407
+ /**
4408
+ * El indice id_mixing ¿Está siendo usado?
4409
+ * @returns {boolean}
4410
+ */
4411
+ _isIdMixing = () => (Array.isArray(this.id_mixing) &&
4412
+ this.id_mixing > 0 || typeof this.id_mixing === 'function');
4413
+
4414
+
4415
+ /**
4416
+ * Array con valores a concatenar en el id.
4417
+ *
4418
+ * @summary Dependiendo de la opción que haya elegido el usario, retorna
4419
+ * una array de valors pasado por funcion o _array object_.
4420
+ * @param {object} entry Objeto con una entrada del geoJson
4421
+ * @returns {object} Array con los valores a concatenar.
4422
+ */
4423
+ _idMixing = entry => {
4424
+ if(!this._isIdMixing()){
4425
+ return;
4426
+ }
4427
+
4428
+ if(typeof this.id_mixing === "function"){
4429
+ return this.id_mixing(this, entry).join('');
4430
+ }
4431
+
4432
+ const values = this.id_mixing.map(val => {
4433
+ if(entry.properties[val]){
4434
+ return entry.properties[val].toString();
4435
+ }
4436
+ return val;
4437
+ });
4438
+
4439
+ return this._slugify(values.join("-"));
4440
+ }
4441
+
4442
+
3285
4443
  /**
3286
4444
  * Crea una entrada ID autonumerada si no posee una.
3287
4445
  *
@@ -3293,21 +4451,30 @@ class PonchoMap {
3293
4451
  * @return {object}
3294
4452
  */
3295
4453
  _setIdIfNotExists = (entries) => {
3296
- const has_id = entries.features
3297
- .filter((_,k) => k===0)
3298
- .every(e => e.properties.hasOwnProperty("id"));
3299
-
3300
- if(!has_id){
3301
- const new_entries = entries.features.map(
3302
- (v,k) => {
3303
- const auto_id = k + 1;
3304
- const use_title = (this.title && v.properties[this.title] ?
3305
- `-${slugify(v.properties[this.title])}` : "");
3306
- v.properties.id = auto_id + use_title;
3307
- return v;
3308
- });
3309
- entries.features = new_entries;
4454
+ const hasId = entries.features
4455
+ .filter((_, k) => k === 0)
4456
+ .every(e => e.properties.hasOwnProperty("id"));
4457
+
4458
+ // Si no se configuró id_mixing y el json tiene id.
4459
+ if(!this._isIdMixing() && hasId){
4460
+ return entries;
3310
4461
  }
4462
+
4463
+ const newEntries = entries.features.map((entry, k) => {
4464
+ if(this._isIdMixing()){
4465
+ // Procesa la opción de id_mixing
4466
+ entry.properties.id = this._idMixing(entry);
4467
+ } else {
4468
+ // Genera un ID automático
4469
+ const autoId = k + 1;
4470
+ const useTitle = (this.title && entry.properties[this.title] ?
4471
+ this._slugify(entry.properties[this.title]) : "");
4472
+ entry.properties.id = [autoId, useTitle].filter(f=>f).join('-');
4473
+ }
4474
+
4475
+ return entry;
4476
+ });
4477
+ entries.features = newEntries;
3311
4478
  return entries;
3312
4479
  };
3313
4480
 
@@ -3319,9 +4486,16 @@ class PonchoMap {
3319
4486
  * @return {undefined}
3320
4487
  */
3321
4488
  addHash = (value) => {
3322
- if(!this.hash || this.no_info){
3323
- return null;
4489
+
4490
+ if (typeof value !== "string" && !value) {
4491
+ console.error('Invalid value provided to update hash');
4492
+ return;
4493
+ }
4494
+
4495
+ if (!this.hash || this.no_info) {
4496
+ return;
3324
4497
  }
4498
+
3325
4499
  window.location.hash = `#${value}`;
3326
4500
  };
3327
4501
 
@@ -3333,9 +4507,16 @@ class PonchoMap {
3333
4507
  * @return {object}
3334
4508
  */
3335
4509
  entry = (id) => {
3336
- return this.entries.find(e => e.properties[this.id] == id);
4510
+ return this.entries.find(e => {
4511
+ if(e?.properties && e.properties[this.id] === id &&
4512
+ e.properties?.["pm-interactive"] !== "n"){
4513
+ return true;
4514
+ }
4515
+ return false;
4516
+ });
3337
4517
  }
3338
4518
 
4519
+
3339
4520
  /**
3340
4521
  * Busca un término en un listado de entradas.
3341
4522
  *
@@ -3355,6 +4536,7 @@ class PonchoMap {
3355
4536
  return entries;
3356
4537
  };
3357
4538
 
4539
+
3358
4540
  /**
3359
4541
  * Busca un término en cada uno de los indices de una entrada.
3360
4542
  */
@@ -3597,7 +4779,7 @@ class PonchoMap {
3597
4779
  * Crea el bloque html para el slider.
3598
4780
  */
3599
4781
  _renderSlider = () => {
3600
- if(!this.render_slider){
4782
+ if(!this.render_slider || this.content_outside){
3601
4783
  return;
3602
4784
  } else if(this.no_info){
3603
4785
  return;
@@ -3612,7 +4794,7 @@ class PonchoMap {
3612
4794
  close_button.title = "Cerrar panel";
3613
4795
  close_button.setAttribute("role", "button");
3614
4796
  close_button.setAttribute("aria-label", "Cerrar panel de información");
3615
- close_button.innerHTML = "<span class=\"sr-only\">Cerrar</span>✕";
4797
+ close_button.innerHTML = "<span class=\"pm-visually-hidden\">Cerrar</span>✕";
3616
4798
 
3617
4799
  const anchor = document.createElement("a");
3618
4800
 
@@ -3632,7 +4814,7 @@ class PonchoMap {
3632
4814
  container.setAttribute("role", "region");
3633
4815
  container.setAttribute("aria-live", "polite");
3634
4816
  container.setAttribute("aria-label", "Panel de información");
3635
- container.classList.add("slider",`js-slider${this.scope_sufix}`);
4817
+ container.classList.add("pm-container", "slider",`js-slider${this.scope_sufix}`);
3636
4818
  container.id = `slider${this.scope_sufix}`;
3637
4819
  container.appendChild(close_button);
3638
4820
  container.appendChild(anchor);
@@ -3643,14 +4825,26 @@ class PonchoMap {
3643
4825
  };
3644
4826
 
3645
4827
 
4828
+ _removeTooltips = () => {
4829
+ let _this = this;
4830
+ this.map.eachLayer(function(layer) {
4831
+ if(layer.options.pane === "tooltipPane") {
4832
+ layer.removeFrom(_this.map);
4833
+ }
4834
+ });
4835
+ }
4836
+
4837
+
3646
4838
  /**
3647
4839
  * Proyecta el slider y hace zoom en el marker.
3648
4840
  */
3649
- _showSlider = (layer, feature) => {
4841
+ _showSlider = layer => {
4842
+ this._removeTooltips();
4843
+
3650
4844
  if(layer.hasOwnProperty("_latlng")){
3651
4845
  this.map.setView(layer._latlng, this.map_anchor_zoom);
3652
- } else {
3653
- this.map.fitBounds(layer.getBounds());
4846
+ } else if(this.fit_bounds_onevent) {
4847
+ this.map.fitBounds(layer.getBounds().pad(0.005));
3654
4848
  }
3655
4849
  layer.fireEvent("click");
3656
4850
  };
@@ -3665,7 +4859,7 @@ class PonchoMap {
3665
4859
  layer.openPopup();
3666
4860
  });
3667
4861
  } else {
3668
- this.map.fitBounds(layer.getBounds());
4862
+ this.map.fitBounds(layer.getBounds().pad(0.005));
3669
4863
  layer.openPopup();
3670
4864
  }
3671
4865
  };
@@ -3703,26 +4897,33 @@ class PonchoMap {
3703
4897
 
3704
4898
  /**
3705
4899
  * Muestra un marker
4900
+ *
3706
4901
  * @param {string|integer} id Valor identificador del marker.
4902
+ * @returns {undefined}
3707
4903
  */
3708
- gotoEntry = (id) => {
4904
+ gotoEntry = id => {
3709
4905
  const entry = this.entry(id);
3710
4906
  const setAction = (layer, id, entry) => {
4907
+
3711
4908
  if(!layer.options.hasOwnProperty("id")){
3712
4909
  return;
3713
4910
  }
4911
+
3714
4912
  if(layer.options.id == id){
3715
4913
  this._setSelectedMarker(id, layer);
4914
+
3716
4915
  if(this.hash){
3717
4916
  this.addHash(id);
3718
4917
  }
3719
- if(this.slider && this.hash){
4918
+
4919
+ if(this.slider){
3720
4920
  this._showSlider(layer, entry);
3721
4921
  } else {
3722
4922
  this._showPopup(layer);
3723
4923
  }
3724
4924
  }
3725
4925
  };
4926
+
3726
4927
  this.markers.eachLayer(layer => setAction(layer, id, entry));
3727
4928
  this.map.eachLayer(layer => {
3728
4929
  if(layer.hasOwnProperty("feature") &&
@@ -3739,20 +4940,22 @@ class PonchoMap {
3739
4940
  * que evaluar el tipo de objeto geoJSON.
3740
4941
  * @param {object} layer
3741
4942
  */
3742
- _setClickeable = (layer) => {
4943
+ _setClickeable = layer => {
3743
4944
  layer.on("keypress click", (e) => {
3744
4945
  document
3745
4946
  .querySelectorAll(".marker--active")
3746
4947
  .forEach(e => e.classList.remove("marker--active"));
3747
4948
 
3748
- ["_icon", "_path"].forEach(ele => {
4949
+
4950
+ ["_icon", "_path"].forEach(ele => {
3749
4951
  if(!e.sourceTarget.hasOwnProperty(ele)){
3750
4952
  return;
3751
4953
  }
3752
4954
  e.sourceTarget[ele].classList.add("marker--active");
3753
4955
  });
4956
+
3754
4957
  const content = this.entries.find(e => {
3755
- return e.properties[this.id]==layer.options.id;
4958
+ return (e?.properties && e.properties[this.id]===layer.options.id);
3756
4959
  });
3757
4960
  this.setContent(content.properties);
3758
4961
  });
@@ -3764,7 +4967,7 @@ class PonchoMap {
3764
4967
  * @param {object} layer Objeto Feature GeoJSON.
3765
4968
  * @returns {boolean}
3766
4969
  */
3767
- isFeature = (layer) => {
4970
+ isFeature = layer => {
3768
4971
  if(!layer.hasOwnProperty("feature")){
3769
4972
  return false;
3770
4973
  }
@@ -3784,6 +4987,8 @@ class PonchoMap {
3784
4987
  layer.feature.geometry.type == "Point" ||
3785
4988
  layer.feature.geometry.type == "MultiPoint"){
3786
4989
  return;
4990
+ } else if(layer?.feature?.properties["pm-interactive"] == "n"){
4991
+ return;
3787
4992
  }
3788
4993
  this._setClickeable(layer);
3789
4994
  });
@@ -4034,6 +5239,10 @@ class PonchoMap {
4034
5239
  key = false, css = "small", style = false
4035
5240
  } = this.template_structure.lead;
4036
5241
 
5242
+ if(!entry[key].trim()){
5243
+ return;
5244
+ }
5245
+
4037
5246
  const p = document.createElement("p");
4038
5247
  p.textContent = entry[key];
4039
5248
  // Style definitions
@@ -4182,7 +5391,7 @@ class PonchoMap {
4182
5391
  */
4183
5392
  fitBounds = () => {
4184
5393
  try {
4185
- this.map.fitBounds(this.geojson.getBounds());
5394
+ this.map.fitBounds(this.geojson.getBounds().pad(0.005));
4186
5395
  } catch (error) {
4187
5396
  console.error(error);
4188
5397
  }
@@ -4198,6 +5407,9 @@ class PonchoMap {
4198
5407
  return;
4199
5408
  }
4200
5409
  // función a evaluar. Busca y remueve un botón de reset si existiera.
5410
+ // if( document.querySelector(`.leaflet-control-zoom-reset`) ){
5411
+ // return;
5412
+ // }
4201
5413
  document.querySelectorAll(
4202
5414
  `.js-reset-view${this.scope_sufix}`).forEach(e => e.remove());
4203
5415
 
@@ -4206,7 +5418,7 @@ class PonchoMap {
4206
5418
  .forEach(ele => {
4207
5419
 
4208
5420
  const icon = document.createElement("i");
4209
- icon.classList.add("fa", "fa-expand");
5421
+ icon.classList.add("pmi", "pmi-expand");
4210
5422
  icon.setAttribute("aria-hidden","true");
4211
5423
 
4212
5424
  const button = document.createElement("a");
@@ -4416,15 +5628,23 @@ class PonchoMap {
4416
5628
  ele[key].setAttribute(
4417
5629
  "aria-label", ele?.feature?.properties?.[this.title]
4418
5630
  );
4419
- ele[key].setAttribute("role", "button");
5631
+
4420
5632
  ele[key].setAttribute("tabindex", 0);
4421
5633
  ele[key].dataset.entryId = ele?.feature?.properties?.[this.id];
5634
+
5635
+ if(ele?.feature?.properties?.["pm-interactive"] == "n"){
5636
+ ele[key].dataset.interactive = "n";
5637
+ ele[key].setAttribute("role", "graphics-symbol");
5638
+ } else {
5639
+ ele[key].setAttribute("role", "button");
5640
+ }
4422
5641
  ele[key].dataset.leafletId = ele._leaflet_id;
4423
5642
  };
4424
5643
 
4425
5644
  this.map.eachLayer(e => setAttributes(e, "_path"));
4426
5645
  };
4427
5646
 
5647
+
4428
5648
  /**
4429
5649
  * Anclas de salto
4430
5650
  *
@@ -4449,11 +5669,22 @@ class PonchoMap {
4449
5669
  ["role", "region"],
4450
5670
  ]
4451
5671
  ],
5672
+ [
5673
+ `.js-themes-tool${this.scope_sufix}`,
5674
+ `themes-tool${this.scope_sufix}`,
5675
+ [
5676
+ ["aria-label", "Herramienta para cambiar de tema visual"],
5677
+ ["role", "region"],
5678
+ ]
5679
+ ],
4452
5680
  ];
4453
5681
  anchors.forEach(anchor => {
4454
- const element = document.querySelector(anchor[0]);
4455
- element.id = anchor[1];
4456
- anchor[2].forEach(e => element.setAttribute(e[0], e[1]));
5682
+ const element = document.querySelectorAll(anchor[0]);
5683
+ element.forEach( ele => {
5684
+ ele.id = anchor[1]
5685
+ anchor[2].forEach(e => ele.setAttribute(e[0], e[1]));
5686
+ } );
5687
+
4457
5688
  });
4458
5689
  return anchors;
4459
5690
  };
@@ -4477,7 +5708,7 @@ class PonchoMap {
4477
5708
  */
4478
5709
  _accesibleMenu = () => {
4479
5710
  // Remuevo instancias anteriores si existieran.
4480
- document.querySelectorAll(`${this.scope_selector} .accesible-nav`)
5711
+ document.querySelectorAll(`${this.scope_selector} .pm-accesible-nav`)
4481
5712
  .forEach(e => e.remove());
4482
5713
 
4483
5714
  // Anclas que se deben crear.
@@ -4497,7 +5728,11 @@ class PonchoMap {
4497
5728
  {
4498
5729
  text: "Ir al panel de zoom",
4499
5730
  anchor: `#${anchors[1][1]}`
4500
- }
5731
+ },
5732
+ {
5733
+ text: "Cambiar de tema",
5734
+ anchor: `#${anchors[2][1]}`
5735
+ },
4501
5736
  ]
4502
5737
  values = [
4503
5738
  ...values,
@@ -4509,21 +5744,24 @@ class PonchoMap {
4509
5744
  // Imprimo los enlaces
4510
5745
  const icon = document.createElement("i");
4511
5746
  icon.classList.add(
4512
- "icono-arg-sitios-accesibles",
5747
+ "pmi",
5748
+ "pmi-universal-access",
4513
5749
  "accesible-nav__icon"
4514
5750
  );
4515
5751
  icon.setAttribute("aria-hidden", "true");
4516
5752
 
4517
5753
  const nav = document.createElement("div");
4518
- nav.classList.add("accesible-nav", "top");
4519
- nav.id = `accesible-nav${this.scope_sufix}`;
5754
+ nav.classList.add("pm-accesible-nav", "top", "pm-list");
5755
+ nav.id = `pm-accesible-nav${this.scope_sufix}`;
4520
5756
  nav.setAttribute("aria-label", "Menú para el mapa");
4521
5757
  nav.setAttribute("role", "navigation");
4522
5758
  nav.tabIndex=0;
4523
5759
 
4524
5760
  const ul = document.createElement("ul");
5761
+ ul.classList.add("pm-list-unstyled");
4525
5762
  values.forEach((link, index) => {
4526
5763
  const a = document.createElement("a");
5764
+ a.classList.add("pm-item-link", "pm-accesible")
4527
5765
  a.textContent = link.text;
4528
5766
  a.tabIndex = 0;
4529
5767
  a.href = link.anchor;
@@ -4542,11 +5780,12 @@ class PonchoMap {
4542
5780
  // enlace de retorno
4543
5781
  const back_to_nav = document.createElement("a");
4544
5782
  back_to_nav.textContent = "Ir a la navegación del mapa";
4545
- back_to_nav.href = `#accesible-nav${this.scope_sufix}`;
5783
+ back_to_nav.classList.add("pm-item-link", "pm-accesible");
5784
+ back_to_nav.href = `#pm-accesible-nav${this.scope_sufix}`;
4546
5785
  back_to_nav.id = `accesible-return-nav${this.scope_sufix}`;
4547
5786
 
4548
5787
  const return_nav = document.createElement("div");
4549
- return_nav.classList.add("accesible-nav", "bottom");
5788
+ return_nav.classList.add("pm-accesible-nav", "bottom");
4550
5789
  return_nav.appendChild(icon.cloneNode(true));
4551
5790
  return_nav.appendChild(back_to_nav);
4552
5791
 
@@ -4600,7 +5839,8 @@ class PonchoMap {
4600
5839
  render = () => {
4601
5840
  this._hiddenSearchInput();
4602
5841
  this._resetViewButton();
4603
-
5842
+ this._setThemes();
5843
+
4604
5844
  this.titleLayer.addTo(this.map);
4605
5845
  this.markersMap(this.entries);
4606
5846
  this._selectedMarker();
@@ -4623,6 +5863,8 @@ class PonchoMap {
4623
5863
  setTimeout(this.gotoHashedEntry, this.anchor_delay);
4624
5864
  this._setFetureAttributes();
4625
5865
  this._accesibleMenu();
5866
+ this.mapOpacity();
5867
+ this.mapBackgroundColor();
4626
5868
  };
4627
5869
  };
4628
5870
  // end class
@@ -4924,12 +6166,13 @@ class PonchoMapFilter extends PonchoMap {
4924
6166
 
4925
6167
  const poncho_map_height = filter_container.offsetParent.offsetHeight;
4926
6168
  // Valor tomado de la hoja de estilos
4927
- const container_position_distance = this._cssVarComputedDistance() * 2;
6169
+ const container_position_distance = this._cssVarComputedDistance() * 3;
4928
6170
  const filters_height = poncho_map_height
4929
6171
  - filter_container.offsetTop
4930
6172
  - filter_button.offsetHeight
4931
6173
  - container_position_distance;
4932
6174
 
6175
+
4933
6176
  const pos = document
4934
6177
  .querySelector(`.js-poncho-map-filters${this.scope_sufix}`);
4935
6178
  pos.style.maxHeight = `${filters_height}px`;
@@ -5026,19 +6269,33 @@ class PonchoMapFilter extends PonchoMap {
5026
6269
  * o, si se desean todos los checkbox desmarcados.
5027
6270
  * ["tipo", false]
5028
6271
  */
5029
- _fieldsToUse = (fields_items) => {
6272
+ _fieldsToUse = (fieldsItems) => {
5030
6273
  const {
5031
- fields: opt_fields = false,
5032
- field: opt_field = false} = fields_items;
5033
- if(!opt_fields && !opt_field){
6274
+ type = "checkbox",
6275
+ fields: optFields = false,
6276
+ field: optField = false} = fieldsItems;
6277
+
6278
+ if(!optFields && !optField){
5034
6279
  this.errorMessage(
5035
6280
  "Filters requiere el uso del atributo `field` o `fields`.",
5036
6281
  "warning"
5037
6282
  );
5038
6283
  }
5039
- const fields_to_use = (opt_fields ? opt_fields :
5040
- this._setFilter(opt_field));
5041
- return fields_to_use
6284
+ // Evito que a los radio se les asigne un valor checked.
6285
+ if (optField && type === "radio"){
6286
+ optField[1] = false;
6287
+ }
6288
+
6289
+ let fieldsToUse = (optFields ? optFields : this._setFilter(optField));
6290
+ // Hasta que se defina su uso, todos los radio tienen un item `Todos`.
6291
+ if(type === "radio" && optFields === false){
6292
+ const f = fieldsToUse.map(m => m[1]);
6293
+ fieldsToUse = [
6294
+ [fieldsToUse[0][0], "Todos", f, "checked"], ...fieldsToUse
6295
+ ];
6296
+ }
6297
+
6298
+ return fieldsToUse;
5042
6299
  };
5043
6300
 
5044
6301
 
@@ -5057,8 +6314,9 @@ class PonchoMapFilter extends PonchoMap {
5057
6314
  const field = fields_to_use[key];
5058
6315
  const input = document.createElement("input");
5059
6316
  input.type = (this.valid_fields.includes(fields_items.type) ?
5060
- fields_items.type : "checkbox");
6317
+ fields_items.type : "checkbox");
5061
6318
  input.id = `id__${field[0]}__${group}__${key}`;
6319
+
5062
6320
  if(fields_items.type == "radio"){
5063
6321
  input.name = `${field[0]}__${group}`;
5064
6322
  } else {
@@ -5099,15 +6357,15 @@ class PonchoMapFilter extends PonchoMap {
5099
6357
  _filterButton = () => {
5100
6358
  const filter_icon = document.createElement("i");
5101
6359
  filter_icon.setAttribute("aria-hidden", "true");
5102
- filter_icon.classList.add("fa", "fa-filter");
6360
+ filter_icon.classList.add("pmi", "pmi-filter");
5103
6361
 
5104
6362
  const button_text = document.createElement("span");
5105
6363
  button_text.textContent = "Abre o cierra el filtro de búsqueda";
5106
- button_text.classList.add("sr-only");
6364
+ button_text.classList.add("pm-visually-hidden");
5107
6365
 
5108
6366
  const button = document.createElement("button");
5109
6367
  button.classList.add(
5110
- "btn","btn-secondary","btn-filter",
6368
+ "pm-btn", "pm-btn-rounded-circle", "pm-my-1",
5111
6369
  `js-close-filter${this.scope_sufix}`
5112
6370
  );
5113
6371
  button.id = `filtrar-busqueda${this.scope_sufix}`
@@ -5134,7 +6392,7 @@ class PonchoMapFilter extends PonchoMap {
5134
6392
 
5135
6393
 
5136
6394
  /**
5137
- * Medida definida en la variable CSS --slider-distance
6395
+ * Medida definida en la variable CSS --pm-slider-distance
5138
6396
  *
5139
6397
  * @summary Esta medida puede ser variable según el estilo que se
5140
6398
  * quiera dar al mapa el diseñador.
@@ -5143,7 +6401,7 @@ class PonchoMapFilter extends PonchoMap {
5143
6401
  _cssVarComputedDistance = () => {
5144
6402
  const container = document.querySelector(".poncho-map");
5145
6403
  const computedDistance = getComputedStyle(container)
5146
- .getPropertyValue('--slider-distance');
6404
+ .getPropertyValue('--pm-slider-distance');
5147
6405
  const distance = parseInt(
5148
6406
  computedDistance.toString().replace(/[^0-9]*/gm, ""));
5149
6407
  return distance || 0;
@@ -5181,7 +6439,7 @@ class PonchoMapFilter extends PonchoMap {
5181
6439
  close_button.title = "Cerrar panel";
5182
6440
  close_button.setAttribute("role", "button");
5183
6441
  close_button.setAttribute("aria-label", "Cerrar panel de filtros");
5184
- close_button.innerHTML = "<span class=\"sr-only\">Cerrar </span>✕";
6442
+ close_button.innerHTML = "<span class=\"pm-visually-hidden\">Cerrar </span>✕";
5185
6443
 
5186
6444
 
5187
6445
  const form = document.createElement("form");
@@ -5191,7 +6449,10 @@ class PonchoMapFilter extends PonchoMap {
5191
6449
 
5192
6450
  const container = document.createElement("div");
5193
6451
  container.classList.add(
5194
- `js-poncho-map-filters${this.scope_sufix}`,"poncho-map-filters"
6452
+ `js-poncho-map-filters${this.scope_sufix}`,
6453
+ "pm-container",
6454
+ "poncho-map-filters",
6455
+ "pm-caret", "pm-caret-t",
5195
6456
  );
5196
6457
  container.setAttribute("role", "region");
5197
6458
  container.setAttribute("aria-live", "polite");
@@ -5206,7 +6467,6 @@ class PonchoMapFilter extends PonchoMap {
5206
6467
  const controlZoomSize = this._controlZoomSize();
5207
6468
  const styleTop = controlZoomSize.controlHeight
5208
6469
  + controlZoomSize.controlTop
5209
- + cssVarComputedDistance
5210
6470
  + "px";
5211
6471
 
5212
6472
  container.appendChild(form);
@@ -5252,18 +6512,18 @@ class PonchoMapFilter extends PonchoMap {
5252
6512
  * Crea los checkbox para los filtros.
5253
6513
  */
5254
6514
  _createFilters = (data) => {
5255
- // debugger
5256
6515
  const form_filters = document
5257
6516
  .querySelector(`.js-filters${this.scope_sufix}`);
5258
6517
 
5259
6518
  data.forEach((item, group) => {
5260
6519
  let legend = document.createElement("legend");
5261
6520
  legend.textContent = item.legend;
5262
- legend.classList.add("m-b-1", "text-primary", "h6")
6521
+ legend.classList.add("m-b-1", "color-primary", "h6")
5263
6522
 
5264
6523
  let fieldset = document.createElement("fieldset");
5265
6524
  fieldset.appendChild(legend);
5266
- if(item.hasOwnProperty("check_uncheck_all") && item.check_uncheck_all){
6525
+ if(item.hasOwnProperty("check_uncheck_all") &&
6526
+ item.check_uncheck_all && item?.type != "radio"){
5267
6527
  fieldset.appendChild(this._checkUncheckButtons(item));
5268
6528
  }
5269
6529
  fieldset.appendChild(this._fields(item, group));
@@ -5414,7 +6674,7 @@ class PonchoMapFilter extends PonchoMap {
5414
6674
  i.setAttribute("aria-hidden", "true");
5415
6675
 
5416
6676
  const span = document.createElement("span");
5417
- span.className = "sr-only";
6677
+ span.className = "pm-visually-hidden";
5418
6678
  span.style.fontWeight = "400";
5419
6679
  span.textContent = `${field[1]} elemento${plurals}.`;
5420
6680
 
@@ -5508,6 +6768,7 @@ class PonchoMapFilter extends PonchoMap {
5508
6768
  this._helpText(feed);
5509
6769
  this._resetSearch();
5510
6770
  this._clickToggleFilter();
6771
+
5511
6772
  if(this.slider){
5512
6773
  this._renderSlider();
5513
6774
  this._clickeableMarkers();
@@ -5518,6 +6779,7 @@ class PonchoMapFilter extends PonchoMap {
5518
6779
  if(this.hash){
5519
6780
  this._urlHash();
5520
6781
  }
6782
+
5521
6783
  this._setFetureAttributes();
5522
6784
  this._accesibleMenu();
5523
6785
  };
@@ -5596,6 +6858,9 @@ class PonchoMapFilter extends PonchoMap {
5596
6858
  this._hiddenSearchInput();
5597
6859
  this._resetViewButton();
5598
6860
 
6861
+ this._menuTheme();
6862
+ this._setThemes();
6863
+
5599
6864
  if(this.filters.length > 0){
5600
6865
  this._filterButton();
5601
6866
  this._filterContainer();
@@ -5619,6 +6884,8 @@ class PonchoMapFilter extends PonchoMap {
5619
6884
  if(this.filters_visible){
5620
6885
  this._filterContainerHeight();
5621
6886
  }
6887
+ this.mapOpacity();
6888
+ this.mapBackgroundColor();
5622
6889
  };
5623
6890
  };
5624
6891
  // end of class
@@ -5890,6 +7157,342 @@ class PonchoMapSearch {
5890
7157
  }
5891
7158
  };
5892
7159
 
7160
+ /**
7161
+ * PONCHO MAP FILTRO POR PROVINCIAS
7162
+ *
7163
+ * @summary Assets para configrar poncho map con un geoJSON de provincias.
7164
+ *
7165
+ * @author Agustín Bouillet <bouilleta@jefatura.gob.ar>
7166
+ *
7167
+ *
7168
+ *
7169
+ * MIT License
7170
+ *
7171
+ * Copyright (c) 2023 Argentina.gob.ar
7172
+ *
7173
+ * Permission is hereby granted, free of charge, to any person
7174
+ * obtaining a copy of this software and associated documentation
7175
+ * files (the "Software"), to deal in the Software without restriction,
7176
+ * including without limitation the rightsto use, copy, modify, merge,
7177
+ * publish, distribute, sublicense, and/or sell copies of the Software,
7178
+ * and to permit persons to whom the Software is furnished to do so,
7179
+ * subject to the following conditions:
7180
+ *
7181
+ * The above copyright notice and this permission notice shall be
7182
+ * included in all copies or substantial portions of the Software.
7183
+ *
7184
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
7185
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
7186
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
7187
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
7188
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
7189
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
7190
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
7191
+ * SOFTWARE.
7192
+ */
7193
+ const PONCHOMAP_GEOJSON_PROVINCES = "/profiles/argentinagobar/"
7194
+ + "themes/contrib/poncho/resources/jsons/"
7195
+ + "geo-provincias-argentinas.json";
7196
+
7197
+
7198
+ /**
7199
+ * Junta el geoJSON con el JSON de Google Sheet
7200
+ *
7201
+ * @summary Este objeto no puede estar dentro de la clase porque no se puede
7202
+ * utiliar `this` antes de `super()` en ES6.
7203
+ *
7204
+ * @param {object} geoProvinces GeoJSON
7205
+ * @param {object} entries JSON con entradas por provincia.
7206
+ * @returns {object}
7207
+ */
7208
+ const ponchoMapProvinceMergeData = (geoProvinces={}, entries={},
7209
+ provinceIndex="provincia") => {
7210
+
7211
+ if(!geoProvinces.hasOwnProperty("features")){
7212
+ throw new Error("Invalid data format");
7213
+ }
7214
+
7215
+ geoProvinces.features.forEach((feature, key) => {
7216
+ const jsonEntry = entries.find(entry =>
7217
+ (entry[provinceIndex] == feature.properties.fna ||
7218
+ entry[provinceIndex] == feature.properties.nam)
7219
+ );
7220
+ // Si no existe la provincia en el JSON, borra el feature.
7221
+ if(!jsonEntry && feature.properties.fna){
7222
+ delete geoProvinces.features[key];
7223
+ return;
7224
+ }
7225
+ // Si hay definido un key _color_, usa el color en el fill.
7226
+ if(jsonEntry?.color && !feature.properties["pm-type"]){
7227
+ geoProvinces
7228
+ .features[key]
7229
+ .properties.stroke = ponchoColor(jsonEntry.color);
7230
+ }
7231
+ // Remuevo la propiedad interactive del json para que no se interponga
7232
+ // con el valor del geoJSON.
7233
+ if(feature.properties["pm-interactive"] === "n" &&
7234
+ jsonEntry?.["pm-interactive"] !== "n"){
7235
+ delete jsonEntry["pm-interactive"];
7236
+ }
7237
+
7238
+ Object.assign(geoProvinces.features[key].properties, jsonEntry);
7239
+ });
7240
+ return geoProvinces;
7241
+ };
7242
+
7243
+
7244
+ /**
7245
+ * Remueve estilos toggle del select y el contenedor del mapa
7246
+ *
7247
+ * @summary Este objeto no puede estar dentro de la clase porque no se puede
7248
+ * utiliar `this` antes de `super()` en ES6.
7249
+ * @returns {undefined}
7250
+ */
7251
+ const ponchoMapProvinceCssStyles = flag => {
7252
+ if(flag){
7253
+ return;
7254
+ }
7255
+ const s = document.querySelectorAll(
7256
+ ".poncho-map-province__toggle-map,"
7257
+ + ".poncho-map-province__toggle-element"
7258
+ );
7259
+ s.forEach(element => {
7260
+ element.classList.remove(
7261
+ "poncho-map-province__toggle-map",
7262
+ "poncho-map-province__toggle-element"
7263
+ );
7264
+ });
7265
+ };
7266
+
7267
+
7268
+ class PonchoMapProvinces extends PonchoMapFilter {
7269
+ constructor(geoProvinces, entries, options){
7270
+
7271
+ const defaultOptions = {
7272
+ initial_entry: false,
7273
+ random_entry: false,
7274
+ overlay_image: true,
7275
+ overlay_image_bounds: [
7276
+ [-20.56830872133435, -44.91768177759874],
7277
+ [-55.861359445914566, -75.2246121480093]
7278
+ ],
7279
+ overlay_image_opacity: 0.8,
7280
+ overlay_image_url: "https://www.argentina.gob.ar/"
7281
+ + "sites/default/files/map-shadow.png",
7282
+ hide_select: true,
7283
+ province_index: "provincia",
7284
+ fit_bounds: true,
7285
+ // Sobreescribo opciones de PonchoMap
7286
+ map_view:[-40.47815508388363,-60.0045383246273],
7287
+ map_init_options: {
7288
+ zoomSnap: 0.2,
7289
+ zoomControl: true,
7290
+ doubleClickZoom: false,
7291
+ scrollWheelZoom: false,
7292
+ boxZoom: false
7293
+ },
7294
+ map_zoom: 4.4,
7295
+ tooltip_options: {
7296
+ permanent: false,
7297
+ className: "leaflet-tooltip-own",
7298
+ direction: "auto",
7299
+ offset: [0, -3],
7300
+ sticky: true,
7301
+ opacity: 1,
7302
+ },
7303
+ tooltip: true,
7304
+ slider: true
7305
+ };
7306
+ // Merge options
7307
+ let opts = Object.assign({}, defaultOptions, options);
7308
+
7309
+ ponchoMapProvinceCssStyles(opts.hide_select);
7310
+
7311
+ // PonchoMapFilter instance
7312
+ const mergedJSONs = ponchoMapProvinceMergeData(
7313
+ geoProvinces, entries, opts.province_index
7314
+ );
7315
+
7316
+ super(mergedJSONs, opts);
7317
+
7318
+ this.initialEntry = opts.initial_entry;
7319
+ this.randomEntry = opts.random_entry;
7320
+ this.overlayImage = opts.overlay_image;
7321
+ this.overlayImageUrl = opts.overlay_image_url;
7322
+ this.overlayImageBounds = opts.overlay_image_bounds;
7323
+ this.overlayImageOpacity = opts.overlay_image_opacity;
7324
+ this.mapView = opts.map_view;
7325
+ this.hideSelect = opts.hide_select;
7326
+ this.fitToBounds = opts.fit_bounds
7327
+ }
7328
+
7329
+
7330
+ /**
7331
+ * Ordena un array por uno de sus keys.
7332
+ * @param {object} obj Objeto a ordenar.
7333
+ * @param {integer} key Posición del array.
7334
+ * @param {object} obj Objeto ordenado.
7335
+ */
7336
+ sortObject = (obj, key=0) => obj.sort((a, b) => {
7337
+ const valA = a[key].toUpperCase();
7338
+ const valB = b[key].toUpperCase();
7339
+ if (valA > valB) {
7340
+ return 1;
7341
+ }
7342
+ if (valA < valB) {
7343
+ return -1;
7344
+ }
7345
+ return 0;
7346
+ });
7347
+
7348
+
7349
+ /**
7350
+ * Retorna un valor aleatório.
7351
+ * @param {object} list Listado de provincias
7352
+ * @returns {object}
7353
+ */
7354
+ _randomEntry = list => {
7355
+ return list[Math.floor(Math.random()*list.length)][0];
7356
+ };
7357
+
7358
+
7359
+ /**
7360
+ * Retorna un array con clave y valor de provincias argentinas
7361
+ * @param {object} geoJSON Objeto geoJSON con los features por provincia
7362
+ * @param {string} idKey Key del propertie que se usa como id.
7363
+ * @returns {object}
7364
+ */
7365
+ _provincesFromGeoJSON = (geoJSON, idKey) => {
7366
+ let prov = {};
7367
+ geoJSON.features.map(p => {
7368
+ const {
7369
+ name=false,
7370
+ "pm-interactive":pmInteractive=false} = p.properties;
7371
+
7372
+ if(pmInteractive === "n" || !name){
7373
+ return false;
7374
+ }
7375
+ prov[p.properties[idKey]] = name;
7376
+ }).filter(f => f);
7377
+
7378
+ let provincesToList = this.sortObject( Object.entries(prov), 1);
7379
+ return provincesToList;
7380
+ };
7381
+
7382
+
7383
+ /**
7384
+ * Imprime la región según las opciones de precedencia.
7385
+ * @param {string} prov Id de provincia
7386
+ * @returns {undefined}
7387
+ */
7388
+ _selectedEntry = prov => {
7389
+ const hash = window.location.hash.replace("#", "");
7390
+ let selected = "";
7391
+ if(hash){
7392
+ selected = hash;
7393
+ } else if(this.initialEntry){
7394
+ selected = this.initialEntry;
7395
+ } else if(this.randomEntry){
7396
+ selected = this._randomEntry(prov);
7397
+ }
7398
+ return selected;
7399
+ }
7400
+
7401
+
7402
+ /**
7403
+ * Crea los options para el select de provincias
7404
+ * @param {object} map
7405
+ * @returns {object}
7406
+ */
7407
+ _setSelectProvinces = map => {
7408
+ const hash = window.location.hash.replace("#", "");
7409
+ const prov = this._provincesFromGeoJSON(map.geoJSON, map.id);
7410
+ const selected = this._selectedEntry(prov);
7411
+
7412
+ // Creo los options
7413
+ const selectProvinces = document.getElementById("id_provincia");
7414
+ const optionsSelect = [["", "Seleccione una provincia"], ...prov];
7415
+ optionsSelect.forEach(province => {
7416
+ const option = document.createElement("option");
7417
+
7418
+ if(province[0] === selected){
7419
+ option.setAttribute("selected", "selected");
7420
+ }
7421
+ option.value = province[0];
7422
+ option.textContent = province[1];
7423
+ selectProvinces.appendChild(option);
7424
+ });
7425
+ return {object: selectProvinces, selected: selected};
7426
+ };
7427
+
7428
+
7429
+ /**
7430
+ * Selected option cuando selecciono un polígono
7431
+ * @param {object} map Objeto return ponchoMap
7432
+ */
7433
+ _selectedPolygon = map => {
7434
+ map.map.eachLayer(layer => {
7435
+ layer.on("keypress click", (e) => {
7436
+ if( e?.target?.feature?.properties ){
7437
+ const {id} = e.target.feature.properties;
7438
+ document.getElementById("id_provincia").value = id;
7439
+ }
7440
+ });
7441
+ })
7442
+ }
7443
+
7444
+
7445
+ /**
7446
+ * Crea el listener para la interacción del select con el mapa.
7447
+ * @param {object} map
7448
+ */
7449
+ _selectProvinces = map => {
7450
+ this._selectedPolygon(map);
7451
+
7452
+ // Arma el select con las provincias
7453
+ const selectProvinces = this._setSelectProvinces(map);
7454
+
7455
+ if(selectProvinces.selected){
7456
+ map.gotoEntry(selectProvinces.selected)
7457
+ }
7458
+
7459
+ // cambia los datos de la provincia
7460
+ selectProvinces.object.addEventListener("change", e => {
7461
+ map.gotoEntry(e.target.value);
7462
+ e.value = selectProvinces.selected
7463
+ });
7464
+ };
7465
+
7466
+
7467
+ /**
7468
+ * Implementa una imagen sobre el mapa
7469
+ * @returns {undefined}
7470
+ */
7471
+ _overlayImage = () => {
7472
+ if(!this.overlayImage){
7473
+ return;
7474
+ }
7475
+ L.imageOverlay(
7476
+ this.overlayImageUrl, this.overlayImageBounds,
7477
+ {opacity: this.overlayImageOpacity}
7478
+ ).addTo(this.map);
7479
+ }
7480
+
7481
+
7482
+ /**
7483
+ * imprime el mapa
7484
+ */
7485
+ renderProvinceMap = () =>{
7486
+ this._overlayImage();
7487
+ this.render(); // Imprime PonchoMapsFilter
7488
+ if(this.fitToBounds){
7489
+ this.fitBounds();
7490
+ }
7491
+ this._selectProvinces(this);
7492
+ };
7493
+ }
7494
+ // end class
7495
+
5893
7496
  /**
5894
7497
  * Helpers para manejar los json provenientes de Google Sheets.
5895
7498
  *
@@ -5998,7 +7601,9 @@ class GapiSheetData {
5998
7601
 
5999
7602
 
6000
7603
 
6001
- /* module.exports REMOVED */
7604
+ if (typeof exports !== "undefined") {
7605
+ module.exports = GapiSheetData;
7606
+ }
6002
7607
 
6003
7608
  /**
6004
7609
  * TRANSLATE
@@ -6034,10 +7639,9 @@ class GapiSheetData {
6034
7639
  */
6035
7640
  class TranslateHTML {
6036
7641
  ATTRIBUTES = [
6037
- "title", "placeholder", "alt", "value", "href", "src", "lang"
7642
+ "title", "placeholder", "alt", "value", "href", "src", "html.lang"
6038
7643
  ];
6039
7644
 
6040
-
6041
7645
  /**
6042
7646
  * @param {object} dictionary Objeto con diccionario de terminos
6043
7647
  * a traducir.
@@ -6045,11 +7649,33 @@ class TranslateHTML {
6045
7649
  * a traducir.
6046
7650
  */
6047
7651
  constructor(dictionary = [], attributes = []) {
6048
- this.dictionary = dictionary;
7652
+ this.dictionary = this.sortByLength(dictionary);
6049
7653
  this.attributes = (attributes.length ? attributes : this.ATTRIBUTES);
6050
7654
  }
6051
7655
 
6052
7656
 
7657
+ /**
7658
+ * Ordena los términos
7659
+ *
7660
+ * @summary Ordena el diccionario de mayor a menor según el total de
7661
+ * caracteres de cada término.
7662
+ * @param {object} obj Listado a ordenar
7663
+ * @returns {object} Listado ordenado
7664
+ */
7665
+ sortByLength = obj => {
7666
+ obj.sort((a, b) => {
7667
+ if (a[0].length > b[0].length) {
7668
+ return -1;
7669
+ } else if (a[0].length < b[0].length) {
7670
+ return 1;
7671
+ } else {
7672
+ return a[0] - b[0];
7673
+ }
7674
+ });
7675
+ return obj;
7676
+ };
7677
+
7678
+
6053
7679
  /**
6054
7680
  * Traduce atributos html
6055
7681
  *
@@ -6064,13 +7690,21 @@ class TranslateHTML {
6064
7690
  */
6065
7691
  translateAttributes = (dictionary=false) => {
6066
7692
  const dict = (dictionary ? dictionary : this.dictionary);
6067
- this.attributes.forEach((item) =>
6068
- dict.forEach((translate) =>
7693
+ this.attributes.forEach((item) => {
7694
+
7695
+ const attrDef = item.split(".").slice(-2);
7696
+ const tag = (attrDef.length === 2 ? attrDef[0] : "");
7697
+ const attr = (attrDef.length === 2 ? attrDef[1] : attrDef[0]);
7698
+
7699
+ dict.forEach(translate => {
7700
+
7701
+ console.log(`${tag}[${attr}='${translate[0]}']`, translate[1])
7702
+
6069
7703
  document
6070
- .querySelectorAll(`[${item}='${translate[0]}']`)
6071
- .forEach((t) => (t[item] = translate[1]))
6072
- )
6073
- );
7704
+ .querySelectorAll(`${tag}[${attr}='${translate[0]}']`)
7705
+ .forEach(t => (t[attr] = translate[1]));
7706
+ });
7707
+ });
6074
7708
  };
6075
7709
 
6076
7710