app_fw.js

/**
 * Contiene las funciones para simplificar el uso de algunos componentes de framework 7. 
 * Además de la inicialización automática del objeto app
 * @see <a href="https://framework7.io/" target="_blank">Documentación Framework 7</a>
 * @module Fw7_App
 */

var urlSrv = window.location.origin;
var mobile = false;

/** 
 * Indica si se está usando un dispositivo móvil
 * @type {boolean}*/
var isMobile = {
    Android: function () {
        return navigator.userAgent.match(/Android/i);
    },
    BlackBerry: function () {
        return navigator.userAgent.match(/BlackBerry/i);
    },
    iOS: function () {
        return navigator.userAgent.match(/iPhone|iPad|iPod/i);
    },
    Opera: function () {
        return navigator.userAgent.match(/Opera Mini/i);
    },
    Windows: function () {
        return navigator.userAgent.match(/IEMobile/i);
    },
    any: function () {
        return (isMobile.Android() || isMobile.BlackBerry() || isMobile.iOS() || isMobile.Opera() || isMobile.Windows());
    }
};
if (isMobile.any()) mobile = true;

var pickersF7 = {};
var autocomplete = {};
var autocompleteProps = {};

// Dom7
var $$ = Dom7;

/** 
 * Objeto de la aplicación framework 7, tiene unas configuraciones por defecto, 
 * el texto de los botones de alerta, el formato del calendario, mensaje para smart select, entre otros
 * @type {object} */
var app = new Framework7({
    root: '#form1', // App root element
    id: 'io.framework7.formBPMCo', // App bundle ID
    name: '', // App name
    theme: 'auto', // Automatic theme detection

    dialog: {
        buttonOk: 'ACEPTAR',
        buttonCancel: 'CANCELAR'
    },

    navbar: {
        hideOnPageScroll: false,
        iosCenterTitle: false
    },

    calendar: {
        monthNames: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'],
        monthNamesShort: ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic'],
        dayNames: ['Lunes', 'Martes', 'Miercoles', 'Jueves', 'Viernes', 'Sabado', 'Domingo'],
        dayNamesShort: ['Dom', 'Lun', 'Mar', 'Mie', 'Jue', 'Vie', 'Sab'],
        touchMove: true,
        animate: true,
        closeOnSelect: true,
        dateFormat: 'dd/mm/yyyy'
    },

	smartSelect: {
		appendSearchbarNotFound: 'No se encontraron resultados',
        closeOnSelect: true
    }
});

var mainView = app.views.create('.view-main', {
    url: '/'
});

/**
*Se encarga de inicializar un calendario para los celulares, trabaja como los combos en IOS
*@param {string} id id del campo que tendrá el picker
*@param {function} funcionCallBack Función que se dispara al seleccionar la fecha
*@param {Array} objValues Arreglo con el año inicial y el año final
*@param {function} fnChange Función que se dispara al cambiar el día, o el mes o el año
*@example fechaPicker('t8', ValidarViajesEnFecha, [2018, 2019]);
*/
function fechaPicker(id, funcionCallBack, objValues, fnChange) {
    var today = new Date();
    today.setMonth(today.getMonth() + 1);

    var pickerInline = app.picker.create({
        inputEl: '#' + id,
        rotateEffect: true,
        renderToolbar: function () {
            return '<div class="toolbar">' +
                '<div class="toolbar-inner">' +
                '<div class="right">' +
                '<a href="#" class="link sheet-close popover-close">Aceptar</a>' +
                '</div>' +
                '</div>' +
                '</div>';
        },
        value: [today.getDate(), today.getMonth(), today.getFullYear()],
        formatValue: function (values) {
            return formatFecha(values[0]) + '/' + formatFecha(values[1]) + '/' + values[2];
        },
        cols: [
            // Days
            {
                values: (function () {
                    if (objValues == null || objValues.dias == null || objValues.dias.length == 0) {
                        var arr = [];
                        for (var i = 1; i <= 31; i++) { arr.push(i); }
                        return arr;
                    }

                    return objValues.dias;
                })(),
            },
            // Months
            {
                values: (function () {
                    if (objValues == null || objValues.meses == null || objValues.meses.length == 0) {
                        var arr = [];
                        for (var i = 1; i <= 12; i++) { arr.push(i); }
                        return arr;
                    }

                    return objValues.meses;
                })(),
                displayValues: (function () {
                    var arr = ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'];
                    if (objValues == null || objValues.meses == null || objValues.meses.length == 0) {
                        return arr;
                    } else {
                        var arrN = [];
                        for (var i = 0; i < objValues.meses.length; i++) {
                            arrN.push(arr[objValues.meses[i] - 1]);
                        }

                        return arrN;
                    }
                })(),
                textAlign: 'left'
            },
            // Years
            {
                values: (function () {
                    if (objValues == null || objValues.anios == null || objValues.anios.length == 0) {
                        var arr = [];
                        for (var i = 1950; i <= 2030; i++) { arr.push(i); }
                        return arr;
                    }

                    return objValues.anios;
                })(),
            }
        ],
        on: {
            open: function (picker) {
                setPickerDate(picker);

                picker.$el.find('.popover-close').on('click', function () {
                    if (funcionCallBack != null && funcionCallBack != '') {
                        funcionCallBack();
                    }
                });
            },
            change: function (picker, values, displayValues) {
                var daysInMonth = new Date(picker.value[2], picker.value[1] * 1, 0).getDate();
                if (values[0] > daysInMonth) {
                    picker.cols[0].setValue(daysInMonth);
                }

                if (fnChange != null) {
                    fnChange.apply(picker, [picker.$inputEl]);
                }
            },
        }
    });

    pickersF7[id] = pickerInline;
}

/**
*Pone la fecha actual en el picker para móviles {@link fechaPicker}
*@param {object} picker objeto picker creado en la función 
*@param {function} funcionCallBack Función que se dispara al seleccionar la fecha
*/
function setPickerDate(picker) {
    if (getFecha) {
        if (picker.cols.length == 0) { return }
        var vFecha = (picker.$inputEl.val() == '') ? getAhora(true) : picker.$inputEl.val();

        vFecha = getFecha(vFecha);
        picker.cols[0].setValue(vFecha.getDate());
        picker.cols[1].setValue(vFecha.getMonth() + 1);
        picker.cols[2].setValue(vFecha.getFullYear());
    } else {
        console.error('Falta incluir el script de calendario.js');
    }
}

function formatFecha(num) {
    num = parseInt(num);

    if (num <= 9)
        return '0' + num;

    return num.toString();
}

/**
*Genera una sugerencia simple que carga los datos bajo el campo de texto
*@param {string} input id del input que será sugenrecia
*@param {string} metodo Ruta del método en el backEnd para el llamad ajax
*@param {string} placeHolder Nombre que aparecerá antes de escribir
*@param {string} campoValor El nombre del campo que se tomará como valor con los datos que vienen del servidor
*@param {function} funcionCallBack Función que se invocará al seleccioar una opción de la sugerencia, 
*devuelve el objeto completo con los datos seleccionados
*@param {string} campoMostrar El nombre del campo que vienen del servidor y se mostrará en la lista
*@param {function} fnRender Función que se invoca antes de renderizar el listado
*@param {string} params concatenación de los valores de parametros para la consulta al backEnd, el orden del campo a consultar 
*@example 
    autocompleteFw('t1', 'e_EFG.ajax_p100.getHotel', 'Hoteles', 'pvNombre', function (value) {
        if (value.length == 1 && value[0] != undefined) {
            _$('#' + input).val(value[0].pvNombre);
        }
    }, 'pvNombre', null, 'pais|{0}|sociedad');
*@deprecated usar la función autocompleteFw2
*{@link autocompleteFw2}
*/
function autocompleteFw(input, metodo, placeHolder, campoValor, funcionCallBack, campoMostrar, fnRender, params) {
    var texto = (campoMostrar == null || campoValor == '') ? campoValor : campoMostrar;
    var autocompleteTypeahead = app.autocomplete.create({
        inputEl: '#' + input,
        openIn: 'dropdown',
        preloader: true, //enable preloader
        highlightMatches: true,
        notFoundText: 'No se encontraron resultados',
        /* If we set valueProperty to "id" then input value on select will be set according to this property */
        valueProperty: campoValor, //object's "value" property name
        textProperty: texto, //object's "text" property name
        typeahead: true,
        dropdownPlaceholderText: placeHolder,
        source: function (query, render) {
            var autocomplete = this;
            var results = [];
            if (query.length === 0) {
                render(results);
                return;
            }
            // Show Preloader
            autocomplete.preloaderShow();

            var parametros = query;

            if (params != null)
                parametros = params.join('|').format(query);

            appRequest(metodo, parametros, function (data) {
                // Hide Preoloader
                autocomplete.preloaderHide();

                if (fnRender != null)
                    data = fnRender.apply(this, [data, query]);

                // Render items by passing array with result items
                render(data);
            });
        },
        on: {
            autocompleteClosed: function (value) {
                if (funcionCallBack != null && funcionCallBack != '')
                    funcionCallBack(value.value);
            },
        },
    });
}

/**
*Genera una sugerencia simple que carga los datos bajo el campo de texto
*@param {string} input id del input que será sugenrecia
*@param {string} metodo Ruta del método en el backEnd para el llamad ajax
*@param {string} placeHolder Nombre que aparecerá antes de escribir
*@param {string} campoValor El nombre del campo que se tomará como valor con los datos que vienen del servidor
*@param {function|object} funcionCallBack Función que se invocará al seleccioar una opción de la sugerencia, 
*o un objeto donde se definen las funciones de open, close, autocompleteClosed, change, render
*devuelve el objeto completo con los datos seleccionados
*@param {string} campoMostrar El nombre del campo que vienen del servidor y se mostrará en la lista
*@param {function} fnRender Función que se invoca antes de renderizar el listado
*@param {string} params concatenación de los valores de parametros para la consulta al backEnd, el orden del campo a consultar
*@param {Array} dataLocal Si se desea cargar con valores locales la sugerencia
*@example 
    autocompleteFw2('t1', 'e_EFG.ajax_p100.getHotel', 'Hoteles', 'pvNombre', function (value) {
        if (value.length == 1 && value[0] != undefined) {
            _$('#' + input).val(value[0].pvNombre);
        }
    }, 'pvNombre', null, 'pais|{0}|sociedad', null);
*/
function autocompleteFw2(id, metodo, placeHolder, campoValor, funcionCallBack, campoMostrar, fnRender, params, dataLocal) {
    if (dataLocal == null) dataLocal = [];
    var funcionesSug = { 'open': null, 'autocompleteClosed': null, 'close': null, 'change': null, 'render': null };

    if (funcionCallBack != null && funcionCallBack != '' && typeof funcionCallBack == 'object') {
        funcionesSug.open = funcionCallBack.open;
        funcionesSug.autocompleteClosed = funcionCallBack.autocompleteClosed;
        funcionesSug.close = funcionCallBack.close;
        funcionesSug.change = funcionCallBack.change;
        funcionesSug.render = funcionCallBack.render;
    }

    if (funcionCallBack != null && funcionCallBack != '' && typeof funcionCallBack == 'function')
        funcionesSug.autocompleteClosed = funcionCallBack;

    if (fnRender != null && fnRender != '' && typeof fnRender == 'function')
        funcionesSug.render = fnRender;
    
    var texto = (campoMostrar == null) ? campoValor : campoMostrar;
    if (campoMostrar == null)
        campoMostrar = campoValor;

    var autocompleteTypeahead = app.autocomplete.create({
        openerEl: '#_' + id,
        openIn: 'popup',
        preloader: true,
        notFoundText: 'No se encontraron resultados',
        prms: params,
        valueProperty: campoValor, //object's "value" property name
        textProperty: texto, //object's "text" property name
        closeOnSelect: true,
        searchbarPlaceholder: placeHolder,
        source: function (query, render) {
            var autocomplete = this;
            var results = [];
            if (dataLocal.length == 0 && query.length === 0) {
                render(results);
                return;
            }

            if (dataLocal.length > 0) {
                if (query.length > 0)
                    results = dataLocal.filter(x => x[campoMostrar].toLowerCase().indexOf(query.toLowerCase()) != -1);

                render(results);
                return;
            }

            // Show Preloader
            autocomplete.preloaderShow();

            var parametros = query;

            if (params != null)
                parametros = params.join('|').format(query);

            // Do Ajax request to Autocomplete data
            appRequest(metodo, parametros, function (data) {
                // Hide Preoloader
                autocomplete.preloaderHide();

                if (funcionesSug.render != null)
                    data = funcionesSug.render.apply(this, [data, query]);

                // Render items by passing array with result items
                render(data);
            });
        },
        on: {
            open: function (el) {
                var sug = this;
                if (dataLocal.length > 0 && sug.value.length == 0) {
                    sug.$el.find('input').val('a');
                    sug.$el.find('input').trigger('change');
                }

                setTimeout(function () { sug.$el.find('input').focus();}, 500);
                verCerrarIframe();

                if (funcionesSug.open != null)
                    funcionesSug.open.apply(this, [el]);
            },

            autocompleteClosed: function (data) {
                if (funcionesSug.autocompleteClosed != null)
                    funcionesSug.autocompleteClosed.apply(this, [data.value]);
            },

            close: function (el) {
                verCerrarIframe();

                if (funcionesSug.close != null)
                    funcionesSug.close.apply(this, [el]);
            },

            change: function (data) {
                if (funcionesSug.change != null)
                    funcionesSug.change.apply(this, [data]);
            }
        },
    });
    autocomplete[id] = autocompleteTypeahead;
    autocompleteProps[id] = { 'metodo': metodo, 'params': params, 'campoValor': campoValor};
}

function appRequest(metodo, parametros, fnSuccess){
    app.request({
        url: `${urlSrv}/API/APIV1.asmx/DT2JSON2`,
        method: 'POST',
        contentType: 'application/x-www-form-urlencoded',
        dataType: 'json',
        data: {
            method: metodo,
            parameters: parametros
        },
        success: fnSuccess
    });
}

/**
*Cargar un valor por defecto en la sugerencia
*@param {string} id id del campo que es sugerencia ya inicializada
*@param {string} valor valor que se quiere poner en la sugerencia, debe ser un valor válido según el backend
*@example setValueAutocompleteFw2('t1', 'Hotel Estelar')
*/
function setValueAutocompleteFw2(id, valor) {
    if (autocompleteProps[id] != null) {
        var parametros = valor;

        if (autocompleteProps[id].params != null)
            parametros = autocompleteProps[id].params.join('|').format(valor);

        var metodo = autocompleteProps[id].metodo;

        appRequest(metodo, parametros, function (data) {
            if (data != null && data.length > 0) {
                var campoVal = autocompleteProps[id].campoValor;
                var objDataSug = data.find(x => x[campoVal] == valor);

                if (objDataSug != null)
                    autocomplete[id].value.push(objDataSug);
            }
        });
    }
}

/**
*Se invoca automáticamente para que los links funcionen de manera correcta, pero deben tener un nodo 
*padre con la clase <b>fw7Link</b>
*/
function processLinks() {
    var links = document.querySelectorAll('.fw7Link a');

    for (var i = 0; i < links.length; i++) {
        links[i].classList.add('link');
        links[i].classList.add('external');
    }
}

processLinks();

function verCerrarIframe() {
    try {
        var modals = document.querySelectorAll('.modal-in');
        var display = (modals.length > 0) ? 'none' : '';
        window.parent.document.getElementById('closeButton').style.display = display;
    } catch (E) { }
}

$('.popup').on('popup:open', function (e, popup) {
    verCerrarIframe();
});

$('.popup').on('popup:close', function (e, popup) {
    verCerrarIframe();
});

/**
*Se encarga de evitar un error cuando los smartSelects están en modales
*/
function f7SmartSelectFix() {
    if (app) {
        var f7SmartS = _$('.smart-select');
        f7SmartS.each(function () {
            var ssLink = _$(this);
            var s = ssLink.find('select');
            ssLink.addClass(s.prop('id'));

            var data = ssLink.data();
            var ss = app.smartSelect.create(app.utils.extend({
                el: this
            }, data));

            ss.view = mainView;
            ss.off('open');
            ss.off('close');

			ss.on('open', function () { verCerrarIframe(); });
            ss.on('close', function () {
                if (_$(this.selectEl).prop('multiple') != null && _$(this.selectEl).prop('multiple')) {
                    var arrV = _$(this.selectEl).val();

                    if (arrV[0] == '') {
                        arrV.splice(0, 1);
                        _$(this.selectEl).val(arrV);
                    }
                }

                verCerrarIframe();
            });
        });
    } else {
        console.error('No existe F7 app inicializada');
    }
}

/**
*Se encarga de cargar los smart select con los textos de los campos ocultos
*/
function f7SmartSelectSetVal() {
    var f7SmartS = _$('.smart-select');
    f7SmartS.each(function () {
        if (_$(this).hasClass('noFixVal'))
            return;

        var ssLink = _$(this);
        var txS = _$(ssLink).parent().find('input[type="hidden"]').val();
        txS = (txS == null) ? '' : txS;

        if (txS == '') {
            txS = _$(ssLink).parent().find('select option:selected').text();

            if (txS != '' && txS.toUpperCase() != 'SELECCIONE')
                _$(ssLink).parent().find('input[type="hidden"]').val(txS);
        }

        var select = _$(ssLink).parent().find('select');

        var ss = app.smartSelect.get(ssLink);
        if (ss != null) {
            if (select.val() == null || select.val().toString() == '')
                txS = '';

            app.smartSelect.get(ssLink).setValue('');

            if (ss.multiple)
                app.smartSelect.get(ssLink).setValue(txS.split(','));
            else
                app.smartSelect.get(ssLink).setValue(txS);
        }
    });
}

/**
*Se encarga de bloquear todos los smartSelect
*@param {boolean} bloq true para bloquear, falso para desbloquear 
*/
function f7SmartSelectBloq(bloq) {
    if (bloq) {
        var f7SmartS = _$('.smart-select');
        f7SmartS.each(function () {
            _$('<div class="ssBloq"></div>').insertBefore(this);
        });
    } else {
        _$('.ssBloq').remove();
    }
}

/** 
*Refresca el valor de un smartSelect
*@param {string} cual id jQuery del combo
*@param {string} valor el valor que va a quedar en el combo
*@param {string} idVal id jQuery del campo asociado que tiene el texto
*@param {boolean} noChange true si desea que no se invoque el change del combo, false o null para invocarlo
*@example f7SetSelect('#s3', 'PJ', '#_ts3', true);
*/
function f7SetSelect(cual, valor, idVal, noChange) {
    _$(cual).val(valor);

    if (noChange == null)
        _$(cual).trigger('change');

    if (idVal == null)
        idVal = cual + '_';

    app.smartSelect.get(cual.replace('#', '.')).setValue(_$(idVal).val());
}

/**
*Se encarga de bloquear los smartSelect indicados 
*@param {boolean} bloq true para bloquear, falso para desbloquear
*@param {Array} arrIds arreglo con los ids de los combos a bloquear o desbloquear
*@example f7SmartSelectBloqForIds(true, ['s2','s3','s4']);
*/
function f7SmartSelectBloqForIds(bloq, arrIds) {
    for (var i = 0; i < arrIds.length; i++) {
        var ss = _$('#' + arrIds[i]).parents('.smart-select');

        if (bloq)
            _$('<div class="ssBloq"></div>').insertBefore(ss[0]);
        else
            ss.parent().find('.ssBloq').remove();
    }
}