/* μlogger * * Copyright(C) 2017 Bartek Fabiszewski (www.fabiszewski.net) * * This is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ window.onload = function () { uLogger.initUI(); uLogger.loadMapAPI(); }; /** @namespace */ var uLogger = window.uLogger || {}; (function (ns) { "use strict"; /** * @namespace * @alias uLogger.mapAPI */ var mapAPI = ns.mapAPI || {}; /** * @namespace * @alias uLogger.ui */ var ui = ns.ui || {}; /** @type {uLogger.config} */ ns.config = ns.config || {}; /** @type {uLogger.admin} */ ns.admin = ns.admin || {}; /** @type {uLogger.lang} */ ns.lang = ns.lang || {}; /** @type {number} userId */ ns.trackId = ns.trackId || -1; /** @type {number} trackId */ ns.userId = ns.userId || -1; var factor_kmh, unit_kmh, factor_m, unit_m, factor_km, unit_km; var latest = 0; var live = 0; var chart = null; var altitudes = {}; var loadTime = 0; var auto; var savedBounds = null; /** @type {?uLogger.mapAPI.api} */ var map = null; initUnits(); /** * Initialize UI elements */ function initUI() { /** @type {HTMLElement} */ ui.menu = document.getElementById('menu'); /** @type {?HTMLElement} */ ui.menuHead = document.getElementById('menu_head'); /** @type {?HTMLElement} */ ui.userDropdown = document.getElementById('user_dropdown'); /** @type {?HTMLElement} */ ui.menuPass = document.getElementById('menu_pass'); // noinspection JSValidateTypes /** @type {?HTMLSelectElement} */ ui.userSelect = function () { var list = document.getElementsByName('user'); if (list.length) { return list[0]; } return null; }(); // noinspection JSValidateTypes /** @type {HTMLSelectElement} */ ui.trackSelect = document.getElementsByName('track')[0]; // noinspection JSValidateTypes /** @type {HTMLSelectElement} */ ui.api = document.getElementsByName('api')[0]; // noinspection JSValidateTypes /** @type {HTMLSelectElement} */ ui.lang = document.getElementsByName('lang')[0]; // noinspection JSValidateTypes /** @type {HTMLSelectElement} */ ui.units = document.getElementsByName('units')[0]; /** @type {HTMLElement} */ ui.chart = document.getElementById('chart'); /** @type {HTMLElement} */ ui.chartClose = document.getElementById('chart_close'); /** @type {HTMLElement} */ ui.bottom = document.getElementById('bottom'); /** @type {HTMLElement} */ ui.altitudes = document.getElementById('altitudes'); /** @type {HTMLElement} */ ui.main = document.getElementById('main'); /** @type {HTMLElement} */ ui.menuClose = document.getElementById('menu-close'); /** @type {HTMLElement} */ ui.track = document.getElementById('track'); /** @type {HTMLElement} */ ui.trackTitle = ui.track ? ui.track.getElementsByClassName('menutitle')[0] : null; /** @type {HTMLElement} */ ui.import = document.getElementById('import'); /** @type {HTMLElement} */ ui.importTitle = ui.import ? ui.import.getElementsByClassName('menutitle')[0] : null; /** @type {HTMLElement} */ ui.summary = document.getElementById('summary'); /** @type {HTMLElement} */ ui.latest = document.getElementById('latest'); /** @type {HTMLElement} */ ui.autoReload = document.getElementById('auto_reload'); /** @type {HTMLElement} */ ui.forceReload = document.getElementById('force_reload'); /** @type {HTMLElement} */ ui.auto = document.getElementById('auto'); /** @type {HTMLElement} */ ui.setTime = document.getElementById('set_time'); /** @type {HTMLElement} */ ui.exportKml = document.getElementById('export_kml'); /** @type {HTMLElement} */ ui.exportGpx = document.getElementById('export_gpx'); /** @type {?HTMLElement} */ ui.inputFile = document.getElementById('inputFile'); /** @type {HTMLElement} */ ui.importGpx = document.getElementById('import_gpx'); /** @type {?HTMLElement} */ ui.addUser = document.getElementById('adduser'); /** @type {?HTMLElement} */ ui.editUser = document.getElementById('edituser'); /** @type {?HTMLElement} */ ui.editTrack = document.getElementById('edittrack'); /** @type {HTMLElement} */ ui.map = document.getElementById('map-canvas'); /** @type {HTMLElement} */ ui.head = document.getElementsByTagName('head')[0]; if (ui.menuHead) { ui.menuHead.onclick = userMenu; } if (ui.menuPass) { ui.menuPass.onclick = ns.changePass; } if (ui.userSelect) { ui.userSelect.onchange = selectUser; } ui.trackSelect.onchange = selectTrack; ui.latest.onchange = toggleLatest; ui.autoReload.onchange = autoReload; ui.setTime.onclick = setTime; ui.forceReload.onclick = reload; ui.altitudes.onclick = toggleChart; ui.api.onchange = function () { loadMapAPI(ui.api.options[ui.api.selectedIndex].value); }; ui.lang.onchange = function () { setLang(ui.lang.options[ui.lang.selectedIndex].value); }; ui.units.onchange = function () { setUnits(ui.units.options[ui.units.selectedIndex].value); }; ui.exportKml.onclick = function () { exportFile('kml'); }; ui.exportGpx.onclick = function () { exportFile('gpx'); }; if (ui.inputFile) { ui.inputFile.onchange = importFile; ui.importGpx.onclick = function () { ui.inputFile.click(); }; } if (ui.addUser) { ui.addUser.onclick = ns.admin.addUser; } if (ui.editUser) { ui.editUser.onclick = ns.admin.editUser; } if (ui.editTrack) { ui.editTrack.onclick = ns.editTrack; } ui.menuClose.onclick = toggleMenu; ui.chartClose.onclick = hideChart; } /** * Initialize units based on settings */ function initUnits() { if (ns.config.units === 'imperial') { factor_kmh = 0.62; // to mph unit_kmh = 'mph'; factor_m = 3.28; // to feet unit_m = 'ft'; factor_km = 0.62; // to miles unit_km = 'mi'; } else if (ns.config.units === 'nautical') { factor_kmh = 0.54; // to knots unit_kmh = 'kt'; factor_m = 1; // meters unit_m = 'm'; factor_km = 0.54; // to nautical miles unit_km = 'nm'; } else { factor_kmh = 1; unit_kmh = 'km/h'; factor_m = 1; unit_m = 'm'; factor_km = 1; unit_km = 'km'; } } /** * Display altitudes chart */ function displayChart() { ui.bottom.style.display = 'block'; if (chart) { google.visualization.events.removeAllListeners(chart); } var data = new google.visualization.DataTable(); data.addColumn('number', 'id'); data.addColumn('number', ns.lang.strings['altitude']); for (var id in altitudes) { if (altitudes.hasOwnProperty(id)) { data.addRow([parseInt(id) + 1, Math.round((altitudes[id] * factor_m))]); } } var options = { title: ns.lang.strings['altitude'] + ' (' + unit_m + ')', hAxis: {textPosition: 'none'}, legend: {position: 'none'} }; chart = new google.visualization.LineChart(ui.chart); chart.draw(data, options); map.addChartEvent(chart, data); } /** * Update altitudes chart if visible */ function updateChart() { if (isChartVisible()) { chart.clearChart(); displayChart(); } } /** * Is chart visible * @returns {boolean} */ function isChartVisible() { return ui.bottom.style.display === 'block'; } /** * Hide chart */ function hideChart() { chart.clearChart(); ui.bottom.style.display = 'none'; } /** * Toggle chart visibility */ function toggleChart() { if (isChartVisible()) { hideChart(); } else if (Object.keys(altitudes).length > 1) { displayChart(); } } /** * Show position on chart * @param {number} id Position id */ function chartShowPosition(id) { if (isChartVisible()) { var index = 0; for (var key in altitudes) { if (altitudes.hasOwnProperty(key) && parseInt(key) === id) { chart.setSelection([{row: index, column: null}]); break; } index++; } } } /** * Toggle chart link visibility */ function toggleChartLink() { var link = ui.altitudes; if (Object.keys(altitudes).length > 1) { link.style.visibility = 'visible'; } else { link.style.visibility = 'hidden'; } } /** * Toggle side menu */ function toggleMenu() { if (ui.menuClose.innerHTML === '»') { ui.menu.style.width = '0'; ui.main.style.marginRight = '0'; ui.menuClose.style.right = '0'; ui.menuClose.innerHTML = '«'; } else { ui.menu.style.width = '165px'; ui.main.style.marginRight = '165px'; ui.menuClose.style.right = '165px'; ui.menuClose.innerHTML = '»'; } map.updateSize(); } /** * Load track from database * @param {number} userid User id * @param {number} trackid Track id * @param update */ function loadTrack(userid, trackid, update) { if (trackid < 0) { return; } if (latest === 1) { trackid = 0; } ns.get('utils/getpositions.php', { trackid: trackid, userid: userid, last: latest }, { loader: ui.trackTitle, success: function (xml) { var positions = xml.getElementsByTagName('position'); if (positions.length > 0) { map.clearMap(); altitudes = {}; map.displayTrack(positions, update); toggleChartLink(); } }, fail: function () { alert(ns.lang.strings['actionfailure']); } }); } /** * Load all users positions from database */ function loadLastPositionAllUsers() { ns.get('utils/getpositions.php', { last: latest }, { loader: ui.trackTitle, success: function (xml) { map.clearMap(); var positions = xml.getElementsByTagName('position'); var posLen = positions.length; var timestampMax = 0; for (var i = 0; i < posLen; i++) { var p = parsePosition(positions[i], i); // set marker map.setMarker(p, i, posLen); if (p.timestamp > timestampMax) { timestampMax = p.timestamp; } } map.zoomToExtent(); updateSummary(timestampMax); }, fail: function () { alert(ns.lang.strings['actionfailure']); } }); } /** * Position * @typedef {Object} uLogger.Position * @property {number} latitude * @property {number} longitude * @property {?number} altitude * @property {?number} speed * @property {?number} bearing * @property {?number} accuracy * @property {?string} provider * @property {?string} comment * @property {string} username * @property {string} trackname * @property {string} tid * @property {number} timestamp * @property {number} distance * @property {number} seconds * @property {number} totalMeters * @property {number} totalSeconds * */ /** * Parse XML element to Position object * @param {Element} xmlPos XML position element * @param {number} id Position ID * @returns {uLogger.Position} Position */ function parsePosition(xmlPos, id) { // read data var position = {}; position.latitude = getNodeAsFloat(xmlPos, 'latitude'); position.longitude = getNodeAsFloat(xmlPos, 'longitude'); position.altitude = getNodeAsInt(xmlPos, 'altitude'); // may be null if (position.altitude != null) { // save altitudes for chart altitudes[id] = position.altitude; } position.speed = getNodeAsInt(xmlPos, 'speed'); // may be null position.bearing = getNodeAsInt(xmlPos, 'bearing'); // may be null position.accuracy = getNodeAsInt(xmlPos, 'accuracy'); // may be null position.provider = getNode(xmlPos, 'provider'); // may be null position.comments = getNode(xmlPos, 'comments'); // may be null position.username = getNode(xmlPos, 'username'); position.trackname = getNode(xmlPos, 'trackname'); position.tid = getNode(xmlPos, 'trackid'); position.timestamp = getNodeAsInt(xmlPos, 'timestamp'); position.distance = getNodeAsInt(xmlPos, 'distance'); position.seconds = getNodeAsInt(xmlPos, 'seconds'); return position; } /** * Get popup html * @param {uLogger.Position} pos Position * @param {number} id Position ID * @param {number} count Positions count * @returns {string} */ function getPopupHtml(pos, id, count) { var date = '–––'; var time = '–––'; if (pos.timestamp > 0) { var d = new Date(pos.timestamp * 1000); date = d.getFullYear() + '-' + ('0' + (d.getMonth() + 1)).slice(-2) + '-' + ('0' + d.getDate()).slice(-2); time = d.toTimeString(); var offset; if ((offset = time.indexOf(' ')) >= 0) { time = time.substr(0, offset) + ' ' + time.substr(offset + 1) + ''; } } var provider = ''; if (pos.provider === 'gps') { provider = ' (' + ns.lang.strings['gps'] + ')'; } else if (pos.provider === 'network') { provider = ' (' + ns.lang.strings['network'] + ')'; } var stats = ''; if (latest === 0) { stats = '
' + '' + ns.lang.strings['track'] + '
' + '' + ns.lang.strings['ttime'] + ' ' + pos.totalSeconds.toHMS() + '
' + '' + ns.lang.strings['aspeed'] + ' ' + ((pos.totalSeconds > 0) ? ((pos.totalMeters / pos.totalSeconds).toKmH() * factor_kmh).toFixed() : 0) + ' ' + unit_kmh + '
' + '' + ns.lang.strings['tdistance'] + ' ' + (pos.totalMeters.toKm() * factor_km).toFixed(2) + ' ' + unit_km + '
' + '
'; } return ''; } /** * Export to file * @param {string} type File type */ function exportFile(type) { var url = 'utils/export.php?type=' + type + '&userid=' + ns.userId + '&trackid=' + ns.trackId; window.location.assign(url); } /** * Import GPX file */ function importFile() { var form = this.parentElement; var sizeMax = form.elements['MAX_FILE_SIZE'].value; if (this.files && this.files.length === 1 && this.files[0].size > sizeMax) { alert(sprintf(ns.lang.strings['isizefailure'], sizeMax)); return; } ns.post('utils/import.php', form, { loader: ui.importTitle, success: function (xml) { var root = xml.getElementsByTagName('root'); var trackId = getNodeAsInt(root[0], 'trackid'); var trackCnt = getNodeAsInt(root[0], 'trackcnt'); getTracks(ns.userId, trackId); if (trackCnt > 1) { alert(sprintf(ns.lang.strings['imultiple'], trackCnt)); } }, fail: function (message) { alert(ns.lang.strings['actionfailure'] + '\n' + message); } }); } /** * Update track summary * @param {number} timestamp * @param {number=} d Total distance (m) * @param {number=} s Total time (s) */ function updateSummary(timestamp, d, s) { if (latest === 0) { ui.summary.innerHTML = '' + '
' + ns.lang.strings['tdistance'] + ' ' + (d.toKm() * factor_km).toFixed(2) + ' ' + unit_km + '
' + '
' + ns.lang.strings['ttime'] + ' ' + s.toHMS() + '
'; } else { var today = new Date(); var date = new Date(timestamp * 1000); var dateString = ''; if (date.toDateString() !== today.toDateString()) { dateString += date.getFullYear() + '-' + ('0' + (date.getMonth() + 1)).slice(-2) + '-' + ('0' + date.getDate()).slice(-2); dateString += '
'; } var timeString = date.toTimeString(); var offset; if ((offset = timeString.indexOf(' ')) >= 0) { timeString = timeString.substr(0, offset) + ' ' + timeString.substr(offset + 1) + ''; } ui.summary.innerHTML = '' + dateString + timeString; } } /** * Get value of first XML child node with given name * @param {Document|Element} root Root element * @param {string} name Node name * @returns {string|null} Node value or null if not found */ function getNode(root, name) { var el = root.getElementsByTagName(name); if (el.length) { var children = el[0].childNodes; if (children.length) { return children[0].nodeValue; } } return null; } /** * Get value of first XML child node with given name * @param {Document|Element} root Root element * @param {string} name Node name * @returns {number|null} Node value or null if not found */ function getNodeAsFloat(root, name) { var str = getNode(root, name); if (str != null) { return parseFloat(str); } return null; } /** * Get value of first XML child node with given name * @param {Document|Element} root Root element * @param {string} name Node name * @returns {number|null} Node value or null if not found */ function getNodeAsInt(root, name) { var str = getNode(root, name); if (str != null) { return parseInt(str); } return null; } // seconds to (d) H:M:S Number.prototype.toHMS = function () { var s = this; var d = Math.floor(s / 86400); var h = Math.floor((s % 86400) / 3600); var m = Math.floor(((s % 86400) % 3600) / 60); s = ((s % 86400) % 3600) % 60; return ((d > 0) ? (d + ' d ') : '') + (('00' + h).slice(-2)) + ':' + (('00' + m).slice(-2)) + ':' + (('00' + s).slice(-2)) + ''; }; // meters to km Number.prototype.toKm = function () { return Math.round(this / 10) / 100; }; // m/s to km/h Number.prototype.toKmH = function () { return Math.round(this * 3600 / 10) / 100; }; /** * On latest checbox toggle */ function toggleLatest() { if (latest === 0) { if (!hasAllUsers() && ui.userSelect && ui.userSelect.length > 2) { ui.userSelect.options.add(new Option('- ' + ns.lang.strings['allusers'] + ' -', 'all'), ui.userSelect.options[1]); } latest = 1; loadTrack(ns.userId, 0, 1); } else { if (hasAllUsers()) { ui.userSelect.selectedIndex = 0; ui.userSelect.remove(1); } latest = 0; loadTrack(ns.userId, ns.trackId, 1); } } /** * Set track * @param {number} trackId */ function setTrack(trackId) { ui.trackSelect.value = trackId.toString(); } /** * On track select */ function selectTrack() { var el = ui.trackSelect; if (el.selectedIndex >= 0) { ns.trackId = parseInt(el.options[el.selectedIndex].value); } else { ns.trackId = 0; } ui.latest.checked = false; if (latest === 1) { toggleLatest(); } loadTrack(ns.userId, ns.trackId, 1); } /** * On user select */ function selectUser() { ns.userId = parseInt(ui.userSelect.options[ui.userSelect.selectedIndex].value); if (isSelectedAllUsers()) { clearOptions(ui.trackSelect); loadLastPositionAllUsers(); } else { getTracks(ns.userId); } } /** * Get track list from database * @param {number} userid Default user ID * @param {number=} trackid Default track ID */ function getTracks(userid, trackid) { ns.get('utils/gettracks.php', { userid: userid }, { loader: ui.trackTitle, success: function (xml) { clearOptions(ui.trackSelect); var tracks = xml.getElementsByTagName('track'); if (tracks.length > 0) { fillOptions(xml, userid, trackid); } else { map.clearMap(); } }, fail: function () { alert(ns.lang.strings['actionfailure']); } }); } /** * Fill track select options * @param {Document} xml XML document * @param {number=} uid Default user ID * @param {number=} tid Default track ID */ function fillOptions(xml, uid, tid) { var tracks = xml.getElementsByTagName('track'); var trackLen = tracks.length; for (var i = 0; i < trackLen; i++) { var trackid = getNode(tracks[i], 'trackid'); var trackname = getNode(tracks[i], 'trackname'); var option = document.createElement('option'); option.value = trackid; option.innerHTML = htmlEncode(trackname); ui.trackSelect.appendChild(option); } var defaultTrack = tid || getNodeAsInt(tracks[0], 'trackid'); var defaultUser = uid || ns.userId; loadTrack(defaultUser, defaultTrack, 1); } /** * Clear select options * @param {HTMLSelectElement} el */ function clearOptions(el) { if (el.options) { while (el.options.length) { el.remove(0); } } } /** * Reload track */ function reload() { if (isSelectedAllUsers()) { loadLastPositionAllUsers(); } else { loadTrack(ns.userId, ns.trackId, 0); } } /** * Toggle auto-reload */ function autoReload() { if (live === 0) { live = 1; if (isSelectedAllUsers()) { auto = setInterval(function () { loadLastPositionAllUsers(); }, uLogger.config.interval * 1000); } else { auto = setInterval(function () { loadTrack(ns.userId, ns.trackId, 0); }, uLogger.config.interval * 1000); } } else { live = 0; clearInterval(auto); } } /** * Is selected option for all users position * @returns {boolean} */ function isSelectedAllUsers() { var usersSelect = document.getElementsByName('user')[0]; return usersSelect[usersSelect.selectedIndex].value === 'all'; } /** * Is 'all users' option present in user select * @returns {boolean} */ function hasAllUsers() { return ui.userSelect && ui.userSelect.length > 2 && ui.userSelect.options[1].value === 'all'; } /** * Set new interval from user dialog */ function setTime() { var i = parseInt(prompt(ns.lang.strings['newinterval'])); if (!isNaN(i) && i !== ns.config.interval) { ns.config.interval = i; ui.auto.innerHTML = ns.config.interval.toString(); // if live tracking on, reload with new interval if (live === 1) { live = 0; clearInterval(auto); autoReload(); } // save current state as default setCookie('interval', ns.config.interval, 30); } } /** * Dynamic change of map api * @param {string=} api API name */ function loadMapAPI(api) { if (api) { ns.config.mapapi = api; try { savedBounds = map.getBounds(); } catch (e) { savedBounds = null; } map.cleanup(); } if (ns.config.mapapi === 'gmaps') { map = mapAPI.gmaps; } else { map = mapAPI.ol; } waitAndInit(); } /** * Try to initialize map engine */ function waitAndInit() { // wait till main api loads if (loadTime > 10000) { loadTime = 0; alert(sprintf(ns.lang.strings['apifailure'], ns.config.mapapi)); return; } try { map.init(); } catch (e) { setTimeout(function () { loadTime += 50; waitAndInit(); }, 50); return; } loadTime = 0; var update = 1; if (savedBounds) { map.zoomToBounds(savedBounds); update = 0; } if (latest && isSelectedAllUsers()) { loadLastPositionAllUsers(); } else { loadTrack(ns.userId, ns.trackId, update); } // save current api as default setCookie('api', ns.config.mapapi, 30); } /** * Add script tag * @param {string} url attribute * @param {string} id attribute */ function addScript(url, id) { if (id && document.getElementById(id)) { return; } var tag = document.createElement('script'); tag.type = 'text/javascript'; tag.src = url; if (id) { tag.id = id; } ui.head.appendChild(tag); } /** * Add link tag with type css * @param {string} url attribute * @param {string} id attribute */ function addCss(url, id) { if (id && document.getElementById(id)) { return; } var tag = document.createElement('link'); tag.type = 'text/css'; tag.rel = 'stylesheet'; tag.href = url; if (id) { tag.id = id; } ui.head.appendChild(tag); } /** * Remove HTML element * @param {string} id Element ID */ function removeElementById(id) { var tag = document.getElementById(id); if (tag && tag.parentNode) { tag.parentNode.removeChild(tag); } } /** * Clear map canvas */ function clearMapCanvas() { ui.map.innerHTML = ''; } /** * Set cookie * @param {string} name * @param {(string|number)} value * @param {number=} days */ function setCookie(name, value, days) { var expires = ''; if (days) { var date = new Date(); date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); expires = '; expires=' + date.toUTCString(); } document.cookie = 'ulogger_' + name + '=' + value + expires + '; path=/'; } /** * Set language * @param {string} lang Language code */ function setLang(lang) { setCookie('lang', lang, 30); location.reload(); } /** * Set units * @param {string} unit New units */ function setUnits(unit) { ns.config.units = unit; setCookie('units', unit, 30); location.reload(); } /** * Show modal dialog * @param {string} contentHTML */ ui.showModal = function (contentHTML) { var div = document.createElement('div'); div.setAttribute('id', 'modal'); div.innerHTML = ''; document.body.appendChild(div); var modalBody = document.getElementById('modal-body'); modalBody.innerHTML = contentHTML; }; /** * Remove modal dialog */ ui.removeModal = function () { document.body.removeChild(document.getElementById('modal')); }; /** * Toggle user menu visibility */ function userMenu() { if (ui.userDropdown.classList.contains('show')) { ui.userDropdown.classList.remove('show'); } else { ui.userDropdown.classList.add('show'); window.addEventListener('click', removeOnClick, true); } } /** * Click listener callback to hide user menu * @param {MouseEvent} e */ function removeOnClick(e) { // noinspection JSUnresolvedVariable var parent = e.target.parentElement; ui.userDropdown.classList.remove('show'); window.removeEventListener('click', removeOnClick, true); if (!parent.classList.contains('dropdown')) { e.stopPropagation(); } } /** * sprintf, naive approach, only %s, %d supported * @param {string} fmt String * @param {...(string|number)=} params Optional parameters * @returns {string} */ function sprintf(fmt, params) { // eslint-disable-line no-unused-vars var args = Array.prototype.slice.call(arguments); var format = args.shift(); var i = 0; return format.replace(/%%|%s|%d/g, function (match) { if (match === '%%') { return '%'; } return (typeof args[i] != 'undefined') ? args[i++] : match; }); } /** * Encode string for HTML * @param {string} s * @returns {string} */ function htmlEncode(s) { return s.replace(/&/g, '&') .replace(/"/g, '"') .replace(/'/g, ''') .replace(//g, '>'); } /** * Convert hex string and opacity to an rgba string * @param {string} hex * @param {number} opacity * @returns {string} */ function hexToRGBA(hex, opacity) { return 'rgba(' + (hex = hex.replace('#', '')) .match(new RegExp('(.{' + hex.length / 3 + '})', 'g')) .map(function (l) { return parseInt(hex.length % 2 ? l + l : l, 16) }) .concat(opacity || 1).join(',') + ')'; } /** * Ajax request failure callback * @callback failCallback * @param {string=} message Error message */ /** * Ajax request success callback * @callback successCallback * @param {Document=} xml XML response */ /** * Perform POST HTTP request * @alias ajax */ function post(url, data, options) { var params = options || {}; params.method = 'POST'; return ajax(url, data, params); } /** * Perform GET HTTP request * @alias ajax */ function get(url, data, options) { var params = options || {}; params.method = 'GET'; return ajax(url, data, params); } /** * Perform ajax HTTP request * @param {string} url Request URL * @param {Object|HTMLFormElement} [data] Optional request parameters: key/value pairs or form element * @param {Object} [options] Optional options * @param {successCallback} [options.success] Optional on success callback * @param {failCallback} [options.fail] Optional on fail callback * @param {string} [options.method='GET'] Optional query method, default 'GET' * @param {HTMLElement} [options.loader] Optional element to animate during loading */ function ajax(url, data, options) { data = data || {}; options = options || {}; var method = options.method || 'GET'; var loader = options.loader; var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function () { if (xhr.readyState === 4) { var message = ''; var error = true; if (xhr.status === 200) { var xml = xhr.responseXML; if (xml) { var root = xml.getElementsByTagName('root'); if (root.length && ns.getNode(root[0], 'error') !== '1') { if (options.success && typeof options.success === 'function') { xml = xml || {}; options.success(xml); } error = false; } else if (root.length) { var errorMsg = ns.getNode(root[0], 'message'); if (errorMsg) { message = errorMsg; } } } } if (error) { if (options.fail && typeof options.fail === 'function') { options.fail(message); } } } if (loader) { removeLoader(loader); } }; var body = null; if (data instanceof HTMLFormElement) { body = new FormData(data); method = 'POST'; } else { var params = []; for (var key in data) { if (data.hasOwnProperty(key)) { params.push(key + '=' + encodeURIComponent(data[key])); } } body = params.join('&'); body = body.replace(/%20/g, '+'); } if (method === 'GET' && params.length) { url += "?" + body; body = null; } xhr.open(method, url, true); if (method === 'POST' && !(data instanceof HTMLFormElement)) { xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); } xhr.send(body); if (loader) { setLoader(loader); } function setLoader(el) { var s = el.textContent || el.innerText; var newHTML = ''; for (var i = 0, len = s.length; i < len; i++) { newHTML += '' + s.charAt(i) + ''; } el.innerHTML = newHTML; } function removeLoader(el) { el.innerHTML = el.textContent || el.innerText; } } ns.ui = ui; ns.mapAPI = mapAPI; ns.initUI = initUI; ns.loadMapAPI = loadMapAPI; ns.userMenu = userMenu; ns.selectUser = selectUser; ns.selectTrack = selectTrack; ns.toggleChart = toggleChart; ns.toggleLatest = toggleLatest; ns.toggleMenu = toggleMenu; ns.autoReload = autoReload; ns.setTime = setTime; ns.reload = reload; ns.exportFile = exportFile; ns.importFile = importFile; ns.htmlEncode = htmlEncode; ns.getNode = getNode; ns.addCss = addCss; ns.hexToRGBA = hexToRGBA; ns.getPopupHtml = getPopupHtml; ns.removeElementById = removeElementById; ns.clearMapCanvas = clearMapCanvas; ns.parsePosition = parsePosition; ns.updateSummary = updateSummary; ns.setTrack = setTrack; ns.updateChart = updateChart; ns.isChartVisible = isChartVisible; ns.chartShowPosition = chartShowPosition; ns.addScript = addScript; ns.sprintf = sprintf; ns.post = post; ns.get = get; Object.defineProperty(ns, 'map', {get: function() { return map; }}); ns.isLatest = function () { return latest === 1; }; })(uLogger);