APIs

Show:
/**
 Add block view is a UI component which allows selection and insertion of a GPS location view coords
 @class AddBlockLocationView
 @constructor
 @return {object} instantiated AddBlockLocationView
 **/
define(['jquery', 'backbone', 'StackView', 'ScreenTemplateFactory', 'bootbox', 'async'], function ($, Backbone, StackView, ScreenTemplateFactory, Bootbox, async) {

    /** SERVICES **/
    BB.SERVICES.ADD_BLOCK_LOCATION_VIEW = 'googleMapsLocationView';
    BB.SERVICES.ADD_BLOCK_LOCATION_SCENE_VIEW = 'googleMapsLocationSceneView';

    var AddBlockLocationView = BB.View.extend({

        /**
         Constructor
         @method initialize
         **/
        initialize: function (options) {
            var self = this;
            self.m_map;
            self.m_mapPoints = [];
            self.m_placement = options.placement;
            self.m_loadedMaps = false;
            self.m_markerOnClick = false;
            self.m_mapData = {points: []};
            self._setSimulationMode(false);

            // Clone the AddBlockTemplate
            var e = $(Elements.ADD_BLOCK_LOCATION_TEMPLATE).clone();
            $(self.options.el).append(e).fadeIn();

            var id = _.uniqueId('slideLocSim');
            $(Elements.CLASS_LOCATION_SIMULATION_MODE, self.el).attr('id', id);
            $('label', self.el).attr('for', BB.lib.unhash(id));
            self._listenModeChange('#' + id);

            $(e).show();
            self.el = self.$el[0];

            $(self.el).find('#prev').on('click', function () {
                self._goBack();
                return false;
            });

            self.listenTo(self.options.stackView, BB.EVENTS.SELECTED_STACK_VIEW, function (e) {
                if (e == self)
                    self._render();
            });

            self._listenStationRefresh();
        },

        /**
         Listen to mode change between simulation mode and real mode
         @method _listenModeChange
         **/
        _listenModeChange: function (i_id) {
            var self = this;
            self.sliderInput = function () {
                var mode = $(i_id).prop('checked');
                self._setSimulationMode(mode);
                if (mode) {
                    $(Elements.CLASS_LOCATION_SIMULATION_PROPS, self.el).slideDown();
                } else {
                    $(Elements.CLASS_LOCATION_SIMULATION_PROPS, self.el).slideUp();
                }
            };
            $(i_id, self.el).on('change', self.sliderInput);
        },

        _setSimulationMode: function (i_mode) {
            var self = this;
            self.m_simulationMode = i_mode;
        },

        _getSimulationMode: function () {
            var self = this;
            return self.m_simulationMode;
        },

        /**
         Listen stations refresh so we rebuild list of available station in the drop down selection
         @method _listenStationRefresh
         **/
        _listenStationRefresh: function () {
            var self = this;
            $(Elements.CLASS_LOCATION_SIMULATION_PROPS, self.el).find('button').on('click', function () {
                self._loadStationList();
            });
        },

        /**
         Init the google map module. We also create a class for _mapPoint which when it gets instantiated
         internally holds a reference to it's own coordinates as well as Marker and Circle.
         Once we do a new on _mapPoint we insert it into an array of m_mapPoints so we can hold
         a reference to all points in a map (used for example when we want to clear the map so
         we can cycle through the points and remove them).
         @method _initMap
         **/
        _initMap: function () {
            var self = this;
            Number.prototype.toRad = function () {
                return this * Math.PI / 180;
            };
            Number.prototype.toDeg = function () {
                return this * 180 / Math.PI;
            };
            self._mapPoint = function (latLng, radius, mapPoints, map, that) {
                var self = this;
                self.$el;
                self.circle;
                self.marker;
                self.latLng = latLng;

                self.remove = function () {
                    // remove circle
                    self.circle.setMap(null);
                    // remove marker
                    self.marker.setMap(null);
                    // remove UI
                    // self.$el.remove();
                };

                // Draw the circle
                self.circle = new google.maps.Circle({
                    center: latLng,
                    radius: radius * 1000,       // Convert to meters
                    fillColor: '#FF0000',
                    fillOpacity: 0.2,
                    map: map,
                    clickable: true,
                    editable: false
                });

                google.maps.event.addListener(self.circle, 'click', function (event) {
                    var lat = event.latLng.lat();
                    var lng = event.latLng.lng();
                    var inst;
                    console.log('location map is in ' + that.options.placement);
                    if (that.options.placement == BB.CONSTS.PLACEMENT_SCENE) {
                        inst = BB.comBroker.getService(BB.SERVICES.ADD_BLOCK_LOCATION_SCENE_VIEW);
                    } else {
                        inst = BB.comBroker.getService(BB.SERVICES.ADD_BLOCK_LOCATION_VIEW);
                    }
                    if (inst._getSimulationMode()) {
                        console.log('within range ' + lat + ' ' + lng);
                        inst._simulateEvent(lat, lng, true);
                    }

                });

                // Show marker at circle center
                self.marker = new google.maps.Marker({
                    position: latLng,
                    map: map
                });

                // UI
                /*
                 var tmpl = document.getElementById('map-point');
                 document.getElementById("map-points").appendChild(tmpl.content.cloneNode(true));
                 self.$el = $('#map-points li:last');

                 self.$el.find('.p-center').html(this.circle.getCenter().toUrlValue());

                 self.$el.find('.remove').click(function () {
                 self.remove();
                 mapPoints.splice(mapPoints.indexOf(self), 1);
                 });

                 // radius slider
                 $('#map-points li:last .radius-slider').slider({
                 formatter: function (value) {
                 return 'Current value: ' + value;
                 }
                 }).on('change', function (event) {
                 self.circle.setRadius(event.value.newValue);
                 });

                 self.$el.find('.radius-slider').on('change', function (e) {
                 var a = $(e.target).val();
                 self.circle.setRadius(Number(a));
                 });

                 // pan to point
                 self.$el.click(function () {
                 map.panTo(self.circle.getCenter());

                 $('#map-points li').css('background-color', '#FFF');
                 $(this).css('background-color', '#ACF19A');
                 });
                 */
            };
            self._createMap();
        },

        /**
         Get all pointData (deprecated)
         @method _pointData
         **/
        _pointData: function () {
            var self = this;
            var data = {
                points: []
            };
            for (var i = 0; i < self.m_mapPoints.length; ++i) {
                var point = self.m_mapPoints[i];
                var center = point.circle.getCenter();
                data.points.push({
                    center: {lat: center.lat(), lng: center.lng()},
                    radius: parseInt(point.circle.getRadius().toString()) / 1000
                });
            }
            return data;
        },

        /**
         Create the google map and listen to corresponding events such map clicks (not within a circle or marker)
         as well as the Search box find input etc
         @method _createMap
         **/
        _createMap: function () {
            var self = this;
            google.maps.LatLng.prototype.destinationPoint = function (brng, dist) {
                dist = dist / 6371;
                brng = brng.toRad();

                var lat1 = this.lat().toRad(), lon1 = this.lng().toRad();

                var lat2 = Math.asin(Math.sin(lat1) * Math.cos(dist) +
                    Math.cos(lat1) * Math.sin(dist) * Math.cos(brng));

                var lon2 = lon1 + Math.atan2(Math.sin(brng) * Math.sin(dist) *
                        Math.cos(lat1),
                        Math.cos(dist) - Math.sin(lat1) *
                        Math.sin(lat2));

                if (isNaN(lat2) || isNaN(lon2)) return null;

                return new google.maps.LatLng(lat2.toDeg(), lon2.toDeg());
            };

            var pointA = new google.maps.LatLng(34.155260, -118.787163);   // Circle center
            var radius = 1; // 10km

            var mapOpt = {
                mapTypeId: google.maps.MapTypeId.TERRAIN,
                center: pointA,
                zoom: 10
            };
            var map = $('.map', self.el);
            self.m_map = new google.maps.Map(map[0], mapOpt);

            // Create the search box and link it to the UI element.
            //var input = $('#pac-input', self.el)[0];
            var c = $('.inputPlacement', self.el);
            $(c).append('<input class="pac-input" class="controls" type="text" placeholder="Search Box">');
            var input = $(c).find('input')[0];
            var searchBox = new google.maps.places.SearchBox(input);
            self.m_map.controls[google.maps.ControlPosition.TOP_LEFT].push(input);

            // Bias the SearchBox results towards current map's viewport.
            self.m_map.addListener('bounds_changed', function () {
                searchBox.setBounds(self.m_map.getBounds());
            });

            var markers = [];
            // Listen for the event fired when the user selects a prediction and retrieve details for location
            searchBox.addListener('places_changed', function () {
                var places = searchBox.getPlaces();

                if (places.length == 0) {
                    return;
                }

                // Clear out the old markers.
                markers.forEach(function (marker) {
                    marker.setMap(null);
                });
                markers = [];

                // For each place, get the icon, name and location.
                var bounds = new google.maps.LatLngBounds();
                places.forEach(function (place) {
                    var icon = {
                        url: place.icon,
                        size: new google.maps.Size(71, 71),
                        origin: new google.maps.Point(0, 0),
                        anchor: new google.maps.Point(17, 34),
                        scaledSize: new google.maps.Size(25, 25)
                    };

                    // Create a marker for each place.
                    markers.push(new google.maps.Marker({
                        map: self.m_map,
                        icon: icon,
                        title: place.name,
                        position: place.geometry.location
                    }));

                    if (place.geometry.viewport) {
                        // Only geocodes have viewport.
                        bounds.union(place.geometry.viewport);
                    } else {
                        bounds.extend(place.geometry.location);
                    }
                });
                self.m_map.fitBounds(bounds);
            });

            google.maps.event.addListener(self.m_map, 'click', function (event) {
                var lat = event.latLng.lat();
                var lng = event.latLng.lng();
                if (self._getSimulationMode()) {
                    console.log('out of range ' + lat + ' ' + lng);
                    self._simulateEvent(lat, lng, false);
                    return;
                }
                if (self.m_markerOnClick) {
                    self.addPoint(event.latLng, 0.10);
                    self.m_markerOnClick = false;
                    BB.comBroker.fire(BB.EVENTS.ADD_LOCATION_POINT, self, null, {lat: lat, lng: lng});
                }
            });
        },

        /**
         Build lists of components, resources and scenes (respectively showing what's needed per placement mode)
         Once an LI is selected proper event fired to announce block is added.
         @method _render
         @return undefined
         **/
        _render: function () {
            var self = this;
            if (self.m_loadedMaps) {
                self.loadJson();
                return;
            }
            require(['async!https://maps.googleapis.com/maps/api/js?libraries=places'], function (e) {
                self._initMap();
                self._loadStationList();
                google.maps.LatLng.prototype.destinationPoint = function (brng, dist) {
                    dist = dist / 6371;
                    brng = brng.toRad();

                    var lat1 = this.lat().toRad(), lon1 = this.lng().toRad();

                    var lat2 = Math.asin(Math.sin(lat1) * Math.cos(dist) +
                        Math.cos(lat1) * Math.sin(dist) * Math.cos(brng));

                    var lon2 = lon1 + Math.atan2(Math.sin(brng) * Math.sin(dist) *
                            Math.cos(lat1),
                            Math.cos(dist) - Math.sin(lat1) *
                            Math.sin(lat2));

                    if (isNaN(lat2) || isNaN(lon2)) return null;

                    return new google.maps.LatLng(lat2.toDeg(), lon2.toDeg());
                };
                self.m_loadedMaps = true;
                self.loadJson();
                return;
            });

            /*
             $('.log-data').click(function () {
             console.log(JSON.stringify(self._pointData()));
             });
             $('.clear-map').click(function () {
             self._clearMap();
             });
             */
        },

        /**
         Clear the entire map of markers and circles by iterating over the m_mapPoints array
         @method _clearMap
         **/
        _clearMap: function () {
            var self = this;
            for (var i = 0; i < self.m_mapPoints.length; ++i) {
                var point = self.m_mapPoints[i];
                point.remove();
            }
            self.m_mapPoints = [];
        },

        /**
         Simulate a trigger event of GPS coordinates by user clicks within the google map
         @method _simulateEvent
         @param {Number} lat
         @param {Number} lng
         @param {Boolean} inRange true if clicked within a marked circle radius
         **/
        _simulateEvent: function (lat, lng, inRange) {
            var self = this;
            var selected = $(Elements.CLASS_LOCATION_SIMULATION_PROPS, self.el).find('select').eq(0).find('option:selected');
            var postMode = $(Elements.CLASS_LOCATION_SIMULATION_PROPS, self.el).find('select').eq(1).find('option:selected').attr('value');
            var msg = (postMode == 'local') ? 'click link to send post...' : 'sending post...';
            var id = $(selected).attr('data-stationid');
            var ip = $(selected).attr('data-ip');
            var stationRecord = BB.Pepper.getStationRecord(id);
            var port = stationRecord.lan_server_port;
            var $messages = $(Elements.CLASS_LOCATION_SIMULATION_PROPS, self.el).find('h5');
            if (inRange) {
                $messages.css({color: 'green'});
            } else {
                $messages.css({color: 'red'});
            }
            var url = BB.Pepper.sendLocalEventGPS(postMode, lat, lng, id, ip, port, function (e) {
                console.log(e);
            });
            $messages.eq(0).text(msg);
            $messages.eq(1).text(lng);
            $messages.eq(2).text(lat);
            $messages.eq(3).text(url);
            $messages.eq(3).off('click');
            if (postMode=="local"){
                $messages.eq(3).on('click', function(){
                    window.open(url, '_blank');
                });
            }
        },

        /**
         Load and refresh the station list so we can pull station id for simulation
         @method _loadStationList
         **/
        _loadStationList: function () {
            var self = this;
            var userData = pepper.getUserData();
            var url = window.g_protocol + userData.domain + '/WebService/getStatus.ashx?user=' + userData.userName + '&password=' + userData.userPass + '&callback=?';
            var select = $(Elements.CLASS_LOCATION_SIMULATION_PROPS, self.el).find('select').eq(0);
            $(select).children().remove();
            $.getJSON(url, function (data) {
                var s64 = data['ret'];
                var str = $.base64.decode(s64);
                var xml = $.parseXML(str);
                $(xml).find('Station').each(function (key, value) {
                    var stationID = $(value).attr('id');
                    var stationName = $(value).attr('name');
                    var stationPort = $(value).attr('localPort') || 9999;
                    var stationIp = $(value).attr('localAddress');
                    var buff = '<option data-ip="' + stationIp + '" data-stationid="' + stationID + '">' + stationName + '</option>'
                    $(select).append(buff);
                });
            });
        },

        /**
         Go back after selection
         @method _goBack
         **/
        _goBack: function () {
            var self = this;
            switch (self.options.placement) {
                case BB.CONSTS.PLACEMENT_CHANNEL:
                {
                    self.options.stackView.slideToPage(self.options.from, 'left');
                    break;
                }
                case BB.CONSTS.PLACEMENT_SCENE:
                {
                    self.m_sceneSliderView = BB.comBroker.getService(BB.SERVICES['SCENE_SLIDER_VIEW']);
                    self.m_sceneSliderView.slideToPage(Elements.SCENE_SLIDER_ELEMENT_VIEW, 'left');
                    break;
                }
                case BB.CONSTS.PLACEMENT_LISTS:
                {
                    self.options.stackView.slideToPage(self.options.from, 'left');
                    break;
                }
            }
            self.m_markerOnClick = false;
            BB.comBroker.fire(BB.EVENTS.ADD_LOCATION_POINT, self);
        },

        /**
         Load and populate the map fro json data, keep in mind data needs to be available from previous method call fills up m_mapData
         @method loadJson
         **/
        loadJson: function () {
            var self = this;
            if (!self.m_loadedMaps)
                return;
            self._clearMap();
            //var data = JSON.parse(str);
            var points = self.m_mapData.points;
            for (var i = 0; i < points.length; ++i) {
                var point = points[i];
                var center = new google.maps.LatLng(point.center.lat, point.center.lng);
                var radius = point.radius;
                self.addPoint(center, radius);
            }
        },

        /**
         Select current view which will animate page loading
         @method selectView
         @params {Object} i_mapData load map data
         @params {Boolean} i_markerOnClick if true, we allow a single click to add a new marker in map
         **/
        selectView: function (i_markerOnClick) {
            var self = this;
            self.m_markerOnClick = i_markerOnClick;
            self.options.stackView.slideToPage(self, 'right');
        },

        /**
         Set current map data (we dont actaully render it yet, just get it ready)
         @method selectView
         @params {Object} i_mapData load map data
         **/
        setData: function (i_mapData) {
            var self = this;
            self.m_mapData = i_mapData;
        },

        /**
         Deselect current view which will animate page unloading
         @method selectView
         **/
        deSelectView: function () {
            var self = this;
            self._goBack();
        },

        /**
         Add a new point to the map (a point is constructed of marker and circle radius and inserted into m_mapPoints)
         @method addPoint
         @param {Number} latLng
         @param {Number} radius
         @param {Boolean} notCenter
         **/
        addPoint: function (latLng, radius, notCenter) {
            var self = this;
            if (notCenter)
                latLng = new google.maps.LatLng(latLng.H, latLng.L);
            radius = radius || 0.10;
            var newPoint = new self._mapPoint(latLng, radius, self.m_mapPoints, self.m_map, self);
            self.m_mapPoints.push(newPoint);
        },

        /**
         Animate google maps to give position
         @method panToPoint
         @param {Number} lat
         @param {Number} lng
         **/
        panToPoint: function (lat, lng) {
            var self = this;
            if (!self.m_map)
                return;
            var center = new google.maps.LatLng(lat, lng);
            self.m_map.panTo(center);
        }
    });

    return AddBlockLocationView;
});