From 50e4e13d120af6785459886e8f2293cce5024b5d Mon Sep 17 00:00:00 2001 From: Alexey Zakharenkov Date: Fri, 21 Jun 2019 15:25:15 +0300 Subject: [PATCH] Improve render's code; remove 3d party library for ajax --- render/js/lib/xhr.js | 250 ------------------------------------------- render/js/metro.js | 157 +++++++++++++-------------- render/render.css | 16 +++ render/render.html | 22 +--- 4 files changed, 95 insertions(+), 350 deletions(-) delete mode 100644 render/js/lib/xhr.js create mode 100644 render/render.css diff --git a/render/js/lib/xhr.js b/render/js/lib/xhr.js deleted file mode 100644 index 144671b..0000000 --- a/render/js/lib/xhr.js +++ /dev/null @@ -1,250 +0,0 @@ -/** - * XHR.js - A vanilla javascript wrapper for the XMLHttpRequest object. - * @version 0.1.1 - * @author George Raptis (https://github.com/georapbox) - * - * The MIT License (MIT) - * - * Copyright (c) 2014 George Raptis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -(function (name, context, definition) { - if (typeof module !== 'undefined' && module.exports) { - module.exports = definition(); - } else if (typeof define === 'function' && define.amd) { - define(definition); - } else { - context[name] = definition(); - } -}('XHR', this, function () { - var helpers = { - extend: function() { - 'use strict'; - for (var i = 1, l = arguments.length; i < l; i += 1) { - for (var key in arguments[i]) { - if (arguments[i].hasOwnProperty(key)) { - if (arguments[i][key] && arguments[i][key].constructor && arguments[i][key].constructor === Object) { - arguments[0][key] = arguments[0][key] || {}; - helpers.extend(arguments[0][key], arguments[i][key]); - } else { - arguments[0][key] = arguments[i][key]; - } - } - } - } - return arguments[0]; - }, - - encodeUrl: function (url) { - var domain = url.substring(0, url.indexOf('?') + 1), - search = url.substring(url.indexOf('?') + 1), - vars = search ? search.split('&') : [], - varsLen = vars.length, - encodedUrl = domain, - pair, - i; - - for (i = 0; i < varsLen; i += 1) { - pair = vars[i].split('='); - encodedUrl += encodeURIComponent(pair[0]) + '=' + encodeURIComponent(pair[1]) + '&'; - } - - encodedUrl = encodedUrl.substring(0, encodedUrl.length - 1); - return encodedUrl; - }, - - serialize: function (form) { - var parts = [], - field = null, - i, - len, - j, - optLen, - option, - optValue; - - for (i = 0, len = form.elements.length; i < len; i += 1) { - field = form.elements[i]; - - switch (field.type) { - case 'select-one': - case 'select-multiple': - if (field.name.length) { - for (j = 0, optLen = field.options.length; j < optLen; j += 1) { - option = field.options[j]; - - if (option.selected) { - optValue = ''; - - if (option.hasAttribute) { - optValue = (option.hasAttribute('value') ? option.value : option.text); - } else { - optValue = (option.attributes.value.specified ? option.value : option.text); - } - - parts.push(encodeURIComponent(field.name) + '=' + encodeURIComponent(optValue)); - } - } - } - break; - case undefined: // fieldset - case 'file': // file input - case 'submit': // submit button - case 'reset': // reset button - case 'button': // custom button - break; - case 'radio': // radio button - case 'checkbox': // checkbox - if (!field.checked) { - break; - } - /* falls through */ - default: - // Don't include form fields without names. - if (field.name.length) { - parts.push(encodeURIComponent(field.name) + '=' + encodeURIComponent(field.value)); - } - } - } - return parts.join('&'); - } - }; - - var XHR = function (options) { - var that = this, - defaults, - xhr, - i, - customHeadersLen, - customHeadersItem; - - // Define default options. - defaults = { - method: 'get', // Type of request. - url: '', // Request url (relative path). - async: true, // Defines if request is asynchronous or not. - serialize: false, // Defines if forms data sent in a POST request should be serialized. - data: null, // Data to be sent as the body of the request. Default is "null" for browser compatibility issues. - contentType: 'application/x-www-form-urlencoded', // Sets the Content Type of the request. - responseType: 'xml', - customHeaders: [], // Set custom request headers. Default value is empty array. - success: function () {}, // Callback function to handle success. - error: function () {} // Callback function to handle errors. - }; - - // Extend the default options with user's specified ones. - options = helpers.extend({}, defaults, options); - - that.method = options.method; - that.url = options.url; - that.async = options.async; - that.serialize = options.serialize; - that.data = options.data; - that.contentType = options.contentType; - that.responseType = options.responseType; - that.customHeaders = options.customHeaders; - that.success = options.success; - that.error = options.error; - that.progressEvent = {}; - - customHeadersLen = that.customHeaders.length; - - // Create a new XMLHttpRequest. - xhr = new XMLHttpRequest(); - - // onreadystatechange event - xhr.onreadystatechange = function () { - // if request is completed, handle success or error states. - if (xhr.readyState === 4) { - if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) { - that.success(xhr.status, xhr.responseText, xhr.responseXML, xhr.statusText); - } else { - that.error(xhr.status, xhr.responseText, xhr.responseXML, xhr.statusText); - } - } - }; - - // onprogress event - xhr.onprogress = function (event) { - if (event.lengthComputable){ - that.progressEvent = { - bubbles: event.bubbles, - cancelable: event.cancelable, - currentTarget: event.currentTarget, - defaultPrevented: event.defaultPrevented, - eventPhase: event.eventPhase, - explicitOriginalTarget: event.explicitOriginalTarget, - isTrusted: event.isTrusted, - lengthComputable: event.lengthComputable, - loaded: event.loaded, - originalTarget: event.originalTarget, - target: event.target, - timeStamp: event.timeStamp, - total: event.total, - type: event.type - }; - - return that.progressEvent; - } - }; - - // Encode URL in case of a "GET" request. - if (that.method === 'get') { - that.url = helpers.encodeUrl(that.url); - } - - // Prepare the request to be sent. - xhr.open(that.method, that.url, that.async); - - // Set "Content-Type". - if (that.contentType !== false) { - xhr.setRequestHeader('Content-Type', that.contentType); - } - - // Set custom headers. - if (customHeadersLen > 0) { - for (i = 0; i < customHeadersLen; i += 1) { - customHeadersItem = that.customHeaders[i]; - - if (typeof customHeadersItem === 'object') { - for (var prop in customHeadersItem) { - if (customHeadersItem.hasOwnProperty(prop)) { - xhr.setRequestHeader(prop, customHeadersItem[prop]); - } - } - } else { - throw new Error('Property "customHeader" expects an array of objects for value.'); - } - } - } - - // Serialize form if option set to "true". - if (that.serialize === true) { - that.data = helpers.serialize(that.data); - } - - // Send data. - xhr.send(that.data); - - return that; - }; - - return XHR; -})); diff --git a/render/js/metro.js b/render/js/metro.js index f11c1c9..d80ebf4 100644 --- a/render/js/metro.js +++ b/render/js/metro.js @@ -1,5 +1,26 @@ -const OSM_URL = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'; -const OSM_ATTRIB = '© OpenStreetMap contributors'; +function slugify(name) { + return name.toLowerCase() + .replace(/ /g, '_') + .replace(/[^a-z0-9_-]+/g, ''); +} + +function ajax(url, onSuccess, onError) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, true); // the third argument is true by default, it means that request will be async + xhr.onload = function(e) { + var responseText = e.target.responseText; + if (xhr.status === 200) { + onSuccess(responseText); + } else if (onError) { + onError(responseText, xhr.status); + } + }; + xhr.send(); +} + + +var OSM_URL = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'; +var OSM_ATTRIB = '© OpenStreetMap contributors'; var osm_layer = L.tileLayer(OSM_URL, { maxZoom: 18, @@ -11,19 +32,12 @@ var initialLocation = [55.7510888, 37.7642849]; var map = L.map('map').setView(initialLocation, 15).addLayer(osm_layer); -L.marker(initialLocation) -.addTo(map) -.bindPopup('Choose a city from the list at the top-right corner!') -.openPopup(); +var hint = L.marker(initialLocation) + .addTo(map) + .bindPopup('Choose a city from the list at the top-right corner!') + .openPopup(); -function slugify(name) { - name = name.toLowerCase(); - name = name.replace(/ /g, '_'); - name = name.replace(/[^a-z0-9_-]+/g, ''); - return name; -} - // Inspired by http://ahalota.github.io/Leaflet.CountrySelect L.CitySelect = L.Control.extend({ options: { @@ -33,43 +47,37 @@ L.CitySelect = L.Control.extend({ onAdd: function(map) { this.div = L.DomUtil.create('div'); this.select = L.DomUtil.create('select', 'leaflet-countryselect', this.div); + this.select.onmousedown = L.DomEvent.stopPropagation; var that = this; - var xhr = new XHR({ - method: 'get', - url: 'cities.txt?', - async: true, - data: {}, - serialize: false, - success: function (status, responseText, responseXML, statusText) { - if (status == 200 && responseText) { - var cities = responseText.split("\n"); - cities = cities.filter(city => city.length).sort(); - - var content = ''; + ajax('cities.txt', + function(responseText) { + var cities = responseText.split("\n"); + cities = cities.filter(function(str) { + return str.length > 0; + }) + .sort(); - if (that.options.title.length > 0) { - content += ''; - } + var content = ''; + var title = that.options.title; - for (var i = 0; i < cities.length; ++i){ - city_name = cities[i].split(',')[0]; - content += ''; - } - - that.select.innerHTML = content; + if (title && title.length) { + content += ''; } - }, - error: function (status, responseText, responseXML, statusText) { - console.log('Request was unsuccessful: ' + status + ', ' + statusText); - } - }); - this.select.onmousedown = L.DomEvent.stopPropagation; + for (var i = 0; i < cities.length; i++) { + city_name = cities[i].split(',')[0]; + content += ''; + } + + that.select.innerHTML = content; + } + ); + return this.div; }, on: function(type, handler) { - if (type == 'change') { + if (type === 'change') { this.onChange = handler; L.DomEvent.addListener(this.select, 'change', this._onChange, this); } else { @@ -91,50 +99,39 @@ L.citySelect = function(id, options) { var selector = L.citySelect({position: 'topright'}).addTo(map); selector.on('change', function(e) { - if (e.cityName === 'City') + if (e.cityName === selector.options.title) return; - var cityName = slugify(e.cityName); + var cityName = e.cityName; - var xhr = new XHR({ - method: 'get', - url: cityName + '.geojson?', - async: true, - responseType: 'json', - data: {}, - serialize: false, - success: function (status, responseText, responseXML, statusText) { - if (status == 200 && responseText) { - - var json = JSON.parse(responseText); - var newCity = L.geoJSON(json, { - style: function(feature) { - if ('stroke' in feature.properties) - return {color: feature.properties.stroke}; - }, - pointToLayer: function (feature, latlng) { - return L.circleMarker(latlng, { - color: feature.properties['marker-color'], - //line-width: 1, - //weight: 1, - radius: 4 - }); - } - }); - - if (map.previousCity != null) { - map.removeLayer(map.previousCity); + ajax(slugify(cityName) + '.geojson', + function (responseText) { + var json = JSON.parse(responseText); + var newCity = L.geoJSON(json, { + style: function(feature) { + if ('stroke' in feature.properties) + return {color: feature.properties.stroke}; + }, + pointToLayer: function (feature, latlng) { + return L.circleMarker(latlng, { + color: feature.properties['marker-color'], + //line-width: 1, + //weight: 1, + radius: 4 + }); } - map.previousCity = newCity; - - map.addLayer(newCity); - map.fitBounds(newCity.getBounds()); + }); + if (map.previousCity !== undefined) { + map.removeLayer(map.previousCity); } + map.previousCity = newCity; - }, - error: function (status, responseText, responseXML, statusText) { - console.log('Request was unsuccessful: ' + status + ', ' + statusText); - } - }); + map.addLayer(newCity); + map.fitBounds(newCity.getBounds()); + }, + function (statusText, status) { + alert("Cannot fetch city data for " + cityName + ".\nError code: " + status); + } + ); }); diff --git a/render/render.css b/render/render.css new file mode 100644 index 0000000..2d738e0 --- /dev/null +++ b/render/render.css @@ -0,0 +1,16 @@ +html, body +{ + height: 100%; + min-height: 100%; + margin: 0; + padding: 0; +} + +#map { + height: 100%; + min-height: 100%; +} + +.leaflet-countryselect { + width: 200px; +} \ No newline at end of file diff --git a/render/render.html b/render/render.html index 0652eaa..5e8a232 100644 --- a/render/render.html +++ b/render/render.html @@ -1,32 +1,14 @@ + - - +
-