0

I'm new to leaflet and I came across this code I have to modify, but it's not a typical javascript structure that I've seen. What is this called and is this recommended practice?

The code:

var map = function() {
    var self = {
        config: {
            circleMarker: function(point, feature) {
                return new L.circleMarker(point, {
                    fillColor: "#DA3248",
                    fillOpacity: 0.8,
                    color: "white",
                    radius: 9,
                    className: "event-marker campaign-" + feature.properties.campaign_name +
                        " event-accessible-" + (feature.properties.is_accessible ? "y" : "n")
                });
            },
            Marker: function(point, feature) {
                return new L.circleMarker(point, {
                    fillColor: "#2B9CD9",
                    fillOpacity: 1.0,
                    color: "white",
                    radius: 9,
                    strokeWidth: 1,
                    className: "event-marker campaign-" + feature.properties.campaign_name + " event-accessible-" + (feature.properties.is_accessible ? "y" : "n")
                });
            },
            tileLayer: new L.tileLayer('https://{s}.tiles.mapbox.com/v4/mapbox.streets/{z}/{x}/{y}.png?access_token=' + MAPBOX_TOKEN, {
                attribution: '<a href="http://www.openstreetmap.org/copyright" target="_blank">&copy; OpenStreetMap contributors</a>'
            })
        },
        data: {},
        init: function() {
            self.init_map();
        },
        init_map: function() {
            self.map = new L.Map("map", {
                center: [37.8, -96.9],
                zoom: self.get_init_zoom(),
                tap: false
            }).addLayer(self.config.tileLayer);
            self.load_event_data();
            self.load_zip_codes();
            self.bind_events();
            self.handle_filter_params();
        },
        handle_filter_params: function() {
            var event_types = new URLSearchParams(window.location.search).get("event_types");
            if (event_types == null) {
                return;
            }
            event_types = event_types.split(',');
            var filters = document.querySelectorAll('#filter-type-list input[name="type[]"]');
            for (var i = 0; i < filters.length; i++) {
                var filter = filters[i];
                if (event_types.indexOf(filter.getAttribute('id')) == -1) {
                    filter.click();
                }
            }
        },
        bind_events: function() {
            document.getElementById('zipcode').addEventListener('keyup', self.handle_zipcode_keydown);
            document.getElementById('filter-type-list').addEventListener('click', self.handle_filter_type_click);
            document.getElementById('distance').addEventListener('change', self.handle_radius_change);
            self.map.on('moveend', self.filter_events_by_viewport);
            self.map._container.addEventListener('mouseover', self.handle_map_hover);
            self.map._container.addEventListener('mouseout', self.handle_map_mouseout);
        },
        is_mobile: function() {
            return window.navigator.userAgent.toLowerCase().indexOf('mobile') !== -1;
        },
        get_init_zoom: function() {
            if (!self.is_mobile()) {
                return 4;
            } else {
                return 3;
            }
        },
        filter_events_by_viewport: function() {
            if (self.map.getZoom() < 8) {
                self.clear_events_list();
            } else {
                var center = self.map.getCenter();
                var bounds = self.map.getBounds();
                var events = [];
                window.bounds = bounds;
                for (var i = 0; i < self.data.events.features.length; i++) {
                    var event = self.data.events.features[i];
                    var coords = event.geometry.coordinates;
                    var p = L.latLng(coords[1], coords[0]);
                    if (bounds.contains(p)) {
                        events.push(event);
                    }
                }
                events = self.sort_by_distance(events, center);
                self.data.filtered_events = events;
                self.populate_events_list();
            }
        },
        sort_by_distance: function(events, center) {
            for (var i = 0; i < events.length; i++) {
                var event = events[i];
                event.distance = center.distanceTo(L.latLng(event.geometry.coordinates[1], event.geometry.coordinates[0]));
            }
            events.sort(function(a, b) {
                return a.distance - b.distance
            });
            return events;
        },
        handle_filter_type_click: function() {
            if (event.target.tagName != 'INPUT') {
                return;
            }
            var events_list = document.getElementById('events-list');
            var event_accessible_id = 'event_accessible';
            var event_accessible_toggle_class = 'event-show-accessible';
            if (event.target.id == event_accessible_id) {
                self.map._container.classList.toggle(event_accessible_toggle_class);
                events_list.classList.toggle(event_accessible_toggle_class);
            } else if (event.target.checked) {
                self.map._container.classList.remove('hide-campaign-' + event.target.value);
                events_list.classList.remove('hide-campaign-' + event.target.value);
            } else {
                self.map._container.classList.add('hide-campaign-' + event.target.value);
                events_list.classList.add('hide-campaign-' + event.target.value);
            }
        },
        handle_radius_change: function(event) {
            if (event.target.tagName != 'SELECT') {
                return;
            }
            var value = event.target.options[event.target.selectedIndex].value;
            if (value == "5") {
                self.map.setZoom(12);
            } else if (value == "20") {
                self.map.setZoom(10);
            } else if (value == "50") {
                self.map.setZoom(9);
            } else if (value == "100") {
                self.map.setZoom(6);
            }
        },
        handle_zipcode_keydown: function(event) {
            if (event.target.value.length != 5) {
                return;
            }
            var latlng = self.data.zipcodes[event.target.value];
            if(latlng != undefined){
                self.map.setView(new L.LatLng(latlng[0], latlng[1]), 9, {
                    pan: true
                });
            }
        },
        load_event_data: function() {
            self.xhr('GET', events_url, function(xhr) {
                var newJson = JSON.parse(xhr.responseText);
                var oldJson = {};
                oldJson["type"]="FeatureCollection";
                oldJson["features"] = [];
                for (var i = 0; i < newJson.data.length; i++) {

                    if(newJson.data[i]['location'] != undefined) {
                        var address1="";
                        var address2="";
                        if(newJson.data[i]['location']['address_lines'] != undefined){
                           address1= newJson.data[i]['location']['address_lines'][0];
                           address2= newJson.data[i]['location']['address_lines'][1];
                        }
                        var city="";
                        if(newJson.data[i]['location']['locality'] != undefined){
                            city = newJson.data[i]['location']['locality'];
                        }
                        var postal_code="";
                        if(newJson.data[i]['location']['postal_code'] != undefined){
                            postal_code = newJson.data[i]['location']['postal_code'];
                        }
                        var region="";
                        if(newJson.data[i]['location']['region'] != undefined){
                            region = newJson.data[i]['location']['region'];
                        }
                        var start_time="";
                        var end_time="";
                        if(newJson.data[i]['timeslots'][0] != undefined){
                            var lengthOfTimeslots=newJson.data[i]['timeslots'];
                            start_time = newJson.data[i]['timeslots'][0]['start_date'];
                            if(newJson.data[i]['timeslots'][lengthOfTimeslots-1] != undefined && newJson.data[i]['timeslots'][lengthOfTimeslots-1]['end_date'] != undefined ){
                                end_time = newJson.data[i]['timeslots'][lengthOfTimeslots-1]['end_date'];
                            }else{
                                end_time = newJson.data[i]['timeslots'][0]['end_date'];
                            }
                        }
                        var features = {
                            "type": "Feature",
                            "properties": {
                                "id": newJson.data[i]['id'],
                                "campaign_name": newJson.data[i]['event_type'],
                                "title": newJson.data[i]['title'],
                                "starts_at": (new Date(start_time*1000)).toLocaleString("en-US", {timeZone: newJson.data[i]['timezone']}),
                                "ends_at": (new Date(end_time*1000)).toLocaleString("en-US", {timeZone: newJson.data[i]['timezone']}),
                                "is_accessible": true,
                                "status":"active",
                                "address1": address1,
                                "address2": address2,
                                "city": city,
                                "state": region,
                                "is_private":false,
                                "venue":"Private",
                                "starts_at_utc":(new Date(start_time*1000)).toUTCString(),
                                "ends_at_utc":(new Date(end_time*1000)).toUTCString(),
                                "zip": postal_code
                            },
                            "geometry": {
                                "type": "Point",
                                "coordinates": [
                                    newJson.data[i]['location']['location']['longitude'],
                                    newJson.data[i]['location']['location']['latitude']
                                ]
                            },
                        };
                        oldJson["features"].push(features);
                    }
                }

                self.data.events = oldJson;
                L.geoJson(self.data.events, {
                    pointToLayer: function(feature, latlng) {
                        if (['town-hall', 'rally-campaign'].indexOf(feature.properties.campaign_name) !== -1) {
                            return self.config.Marker(latlng, feature);
                        } else {
                            return self.config.circleMarker(latlng, feature);
                        }
                    },
                    onEachFeature: function(feature, layer) {
                        var tpl = document.getElementById('popup-template').innerHTML;
                        var event = feature.properties;
                        layer.bindPopup(eval("`" + tpl + "`"), {
                            className: 'event',
                            maxWidth: 260
                        });
                        layer.addTo(self.map);
                    }
                }).addTo(self.map);
            });
        },
        load_zip_codes: function() {
            self.xhr('GET', zipcodes_url, function(xhr) {
                self.data.zipcodes = JSON.parse(xhr.responseText);
            });
        },
        clear_events_list: function() {
            var events_list = document.getElementById('events-list');
            while (events_list.firstChild) {
                events_list.removeChild(events_list.firstChild);
            }
        },
        populate_events_list: function() {
            self.clear_events_list();
            var tpl = document.getElementById('event-template').innerHTML;
            var events_list_frag = document.createDocumentFragment();
            for (var i = 0; i < self.data.filtered_events.length; i++) {
                var event = self.data.filtered_events[i].properties;
                var item = document.createElement('li');
                item.setAttribute('class', 'event campaign-' + event.campaign_name + " event-accessible-" + (event.is_accessible ? "y" : "n"));
                item.innerHTML = eval("`" + tpl + "`");
                item.setAttribute('data-id', event.id);
                item.setAttribute('data-date', moment(event.starts_at_utc).format("X"));
                var coords = self.data.filtered_events[i]['geometry']['coordinates'];
                item.setAttribute('data-point', JSON.stringify(coords));
                events_list_frag.appendChild(item);
            }
            var events_list = document.getElementById('events-list');
            events_list.appendChild(events_list_frag);
        },
        xhr: function(method, url, callback, data) {
            if (typeof data == "undefined") {
                data = null;
            }
            var xhr = new XMLHttpRequest();
            xhr.open(method, url);
            xhr.send(data);
            xhr.onreadystatechange = function() {
                if (xhr.readyState == 4) {
                    callback(xhr);
                }
            }
        },
        signup_url: function(event) {
            var base = "https://www.mobilize.us/";
            return base + event.id;
        }
    };
    return self.init();
}();

What I'm really looking to do is add this code to the above, but since I'm unfamiliar with this structure, I'm not sure where to start:

lc = L.control.locate({
    strings: {
        title: "Show me where I am, yo!"
    }
}).addTo(map);

I've tried adding the code to the above at the very bottom of the massive spaghetti code but got the error: leaflet.js:2295 Uncaught TypeError: Cannot read property 'addLayer' of undefined

2nd attempt: I've also tried this:

init: function() {
            self.init_map();
            self.lc = L.control.locate({
    strings: {
        title: "Locate me"
    }
}).addTo(self.yangmap);
        },

but still the same error, it highlights this in the leaflet.js code:

addTo: function(t) {
            return t.addLayer(this), this
        },
6
  • it's just a function, it returns the result of self.init() ... i.e. undefined - perhaps your code needs to go inside at the end of the init: function and use self.map instead of map and self.lc = instead of lc = - though, the original code looks almost useless Commented Aug 3, 2019 at 0:39
  • I'm not sure why someone would subject themself to deal with such a mess.. Commented Aug 3, 2019 at 0:42
  • code looks fine to me, not sure why return self.init() rather than just self.init() at the end - the former makes it look like something useful would be returned. I've written code similar to the above, it has its place Commented Aug 3, 2019 at 0:44
  • I've put the code inside the init: function and edited the post with the details at the bottom, but still the same error.. Commented Aug 3, 2019 at 1:45
  • Why are you "looking to add that code"? Do you know what it does? Commented Aug 3, 2019 at 2:01

1 Answer 1

2

TL;DR: your 2nd attempt should have been:

init: function() {
  self.init_map();
  self.lc = L.control.locate({
    strings: {
      title: "Locate me"
    }
  }).addTo(self.map); // why yangmap?
},

"self.yangmap" would work if you replace ALL references of "self.map" by "self.yangmap".

What is this called and is this recommended practice?

  • The overall wrapper is an immediately invoked function expression (IIFE). It is used in JavaScript to avoid polluting a scope, while having a "private" scope within the IIFE body.
  • As pointed out by JaromandaX, because the "self.init" does not actually return anything, the IIFE return statement is misleading. The top "var map =" is pointless unfortunately.
  • the "var self =" assignment is a JavaScript workaround to avoid the ambiguity of "this" context when passing function references as callbacks (typically as event listeners). It is leveraged here also as the Object having the entire logic (next point)
  • the overall code organization is an attempt to stick with OOP in JavaScript, with clearly some conventions for consistency with other code (usage of config, data, init). There has been such a trend indeed, before popular frameworks like AngularJS offered different code structure, and above all before build engines (like webpack and Rollup) enabled modularizing the code while automatically wrapping modules in their own private scope.
  • you may still like such code style if you do not want to embark a full framework or a build step.
  • the "var self = this" trick is still quite widely used even when using frameworks and build engines, when the developer is unsure what "this" is.
Sign up to request clarification or add additional context in comments.

2 Comments

What would be the approach to break it up and have classical function? This looks like significant amount of work to break it up.
"This looks like significant amount of work to break it up": indeed. If you are not confident in JavaScript, better leave it as is. If it works, do not fix it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.