<!doctype html>
<html>
<head>
<title>Traccar Manager</title>
<link rel="stylesheet" type="text/css" href="http://cdn.sencha.io/ext-4.1.0-gpl/resources/css/ext-all.css" />
<script type="text/javascript" src="http://cdn.sencha.io/ext-4.1.0-gpl/ext-all.js"></script>

<!-- check for new version: https://raw.github.com/VinylFox/ExtJS.ux.GMapPanel/master/src/GMapPanel3.js -->

<script type="text/javascript">
Ext.ns('Ext.ux');
/**
 * @class Ext.ux.GMapPanel
 * @extends Ext.Panel
 * @author Shea Frederick
 */
Ext.define('Ext.ux.GMapPanel', {

    extend: 'Ext.panel.Panel',

    alias: 'widget.gmappanel',

    requires: ['Ext.window.MessageBox'],
    /**
     * @cfg {Boolean} border
     * Defaults to <tt>false</tt>.  See {@link Ext.Panel}.<code>{@link Ext.Panel#border border}</code>.
     */
    border: false,

    /**
     * @cfg {Array} respErrors
     * An array of msg/code pairs.
     */
    respErrors: [{
            code: 'UNKNOWN_ERROR',
            msg: 'A geocoding or directions request could not be successfully processed, yet the exact reason for the failure is not known.'
        },{
            code: 'ERROR',
            msg: 'There was a problem contacting the Google servers.'
        },{
            code: 'ZERO_RESULTS',
            msg: 'The request did not encounter any errors but returns zero results.'
        },{
            code: 'INVALID_REQUEST',
            msg: 'This request was invalid.'
        },{
            code: 'REQUEST_DENIED',
            msg: 'The webpage is not allowed to use the geocoder for some reason.'
        },{
            code: 'OVER_QUERY_LIMIT',
            msg: 'The webpage has gone over the requests limit in too short a period of time.'
    }],
    /**
     * @cfg {Array} locationTypes
     * An array of msg/code/level pairs.
     */
    locationTypes: [{
            level: 4,
            code: 'ROOFTOP',
            msg: 'The returned result is a precise geocode for which we have location information accurate down to street address precision.'
        },{
            level: 3,
            code: 'RANGE_INTERPOLATED',
            msg: 'The returned result reflects an approximation (usually on a road) interpolated between two precise points (such as intersections). Interpolated results are generally returned when rooftop geocodes are unavailable for a street address.'
        },{
            level: 2,
            code: 'GEOMETRIC_CENTER',
            msg: 'The returned result is the geometric center of a result such as a polyline (for example, a street) or polygon (region).'
        },{
            level: 1,
            code: 'APPROXIMATE',
            msg: 'The returned result is approximate.'
    }],
    /**
     * @cfg {String} respErrorTitle
     * Defaults to <tt>'Error'</tt>.
     */
    respErrorTitle : 'Error',
    /**
     * @cfg {String} geoErrorMsgUnable
     * Defaults to <tt>'Unable to Locate the Address you provided'</tt>.
     */
    geoErrorMsgUnable : 'Unable to Locate the Address you provided',
    /**
     * @cfg {String} geoErrorTitle
     * Defaults to <tt>'Address Location Error'</tt>.
     */
    geoErrorTitle : 'Address Location Error',
    /**
     * @cfg {String} geoErrorMsgAccuracy
     * Defaults to <tt>'The address provided has a low accuracy.<br><br>{0} Accuracy.'</tt>.
     * <div class="mdetail-params"><ul>
     * <li><b><code>ROOFTOP</code></b> : <div class="sub-desc"><p>
     * The returned result is a precise geocode for which we have location information accurate down to street address precision.
     * </p></div></li>
     * <li><b><code>RANGE_INTERPOLATED</code></b> : <div class="sub-desc"><p>
     * The returned result reflects an approximation (usually on a road) interpolated between two precise points (such as intersections). Interpolated results are generally returned when rooftop geocodes are unavailable for a street address.
     * </p></div></li>
     * <li><b><code>GEOMETRIC_CENTER</code></b> : <div class="sub-desc"><p>
     * The returned result is the geometric center of a result such as a polyline (for example, a street) or polygon (region).
     * </p></div></li>
     * <li><b><code>APPROXIMATE</code></b> : <div class="sub-desc"><p>
     * The returned result is approximate.
     * </p></div></li>
     * </ul></div>
     */
    geoErrorMsgAccuracy : 'The address provided has a low accuracy.<br><br>"{0}" Accuracy.<br><br>{1}',
    /**
     * @cfg {String} gmapType
     * The type of map to display, generic options available are: 'map', 'panorama'.
     * Defaults to <tt>'map'</tt>.
     * More specific maps can be used by specifying the google map type:
     * <div class="mdetail-params"><ul>
     * <li><b><code>G_NORMAL_MAP</code></b> : <div class="sub-desc"><p>
     * Displays the default road map view
     * </p></div></li>
     * <li><b><code>G_SATELLITE_MAP</code></b> : <div class="sub-desc"><p>
     * Displays Google Earth satellite images
     * </p></div></li>
     * <li><b><code>G_HYBRID_MAP</code></b> : <div class="sub-desc"><p>
     * Displays a mixture of normal and satellite views
     * </p></div></li>
     * <li><b><code>G_DEFAULT_MAP_TYPES</code></b> : <div class="sub-desc"><p>
     * Contains an array of the above three types, useful for iterative processing.
     * </p></div></li>
     * <li><b><code>G_PHYSICAL_MAP</code></b> : <div class="sub-desc"><p>
     * Displays a physical map based on terrain information.
     * </p></div></li>
     * <li><b><code>G_MOON_ELEVATION_MAP</code></b> : <div class="sub-desc"><p>
     * Displays a shaded terrain map of the surface of the Moon, color-coded by altitude.
     * </p></div></li>
     * <li><b><code>G_MOON_VISIBLE_MAP</code></b> : <div class="sub-desc"><p>
     * Displays photographic imagery taken from orbit around the moon.
     * </p></div></li>
     * <li><b><code>G_MARS_ELEVATION_MAP</code></b> : <div class="sub-desc"><p>
     * Displays a shaded terrain map of the surface of Mars, color-coded by altitude.
     * </p></div></li>
     * <li><b><code>G_MARS_VISIBLE_MAP</code></b> : <div class="sub-desc"><p>
     * Displays photographs taken from orbit around Mars.
     * </p></div></li>
     * <li><b><code>G_MARS_INFRARED_MAP</code></b> : <div class="sub-desc"><p>
     * Displays a shaded infrared map of the surface of Mars, where warmer areas appear brighter and colder areas appear darker.
     * </p></div></li>
     * <li><b><code>G_SKY_VISIBLE_MAP</code></b> : <div class="sub-desc"><p>
     * Displays a mosaic of the sky, as seen from Earth, covering the full celestial sphere.
     * </p></div></li>
     * </ul></div>
     * Sample usage:
     * <pre><code>
     * gmapType: G_MOON_VISIBLE_MAP
     * </code></pre>
     */
    gmapType : 'map',
    /**
     * @cfg {Object} setCenter
     * The initial center location of the map. The map needs to be centered before it can be used.
     * A marker is not required to be specified.
     * More markers can be added to the map using the <code>{@link #markers}</code> array.
     * For example:
     * <pre><code>
setCenter: {
    geoCodeAddr: '4 Yawkey Way, Boston, MA, 02215-3409, USA',
    marker: {title: 'Fenway Park'}
},

// or just specify lat/long
setCenter: {
    lat: 42.345573,
    lng: -71.098326
}
     * </code></pre>
     */
    /**
     * @cfg {Number} zoomLevel
     * The zoom level to initialize the map at, generally between 1 (whole planet) and 40 (street).
     * Also used as the zoom level for panoramas, zero specifies no zoom at all.
     * Defaults to <tt>3</tt>.
     */
    zoomLevel: 14,
    /**
     * @cfg {Number} yaw
     * The Yaw, or rotational direction of the users perspective in degrees. Only applies to panoramas.
     * Defaults to <tt>180</tt>.
     */
    yaw: 180,
    /**
     * @cfg {Number} pitch
     * The pitch, or vertical direction of the users perspective in degrees.
     * Defaults to <tt>0</tt> (straight ahead). Valid values are between +90 (straight up) and -90 (straight down).
     */
    pitch: 0,
    /**
     * @cfg {Boolean} displayGeoErrors
     * True to display geocoding errors to the end user via a message box.
     * Defaults to <tt>false</tt>.
     */
    displayGeoErrors: false,
    /**
     * @cfg {Boolean} minGeoAccuracy
     * The level to display an accuracy error below. Defaults to <tt>ROOFTOP</tt>. For additional information
     * see <a href="http://code.google.com/apis/maps/documentation/reference.html#GGeoAddressAccuracy">here</a>.
     */
    minGeoAccuracy: 'ROOFTOP',
    /**
     * @cfg {Array} mapConfOpts
     * Array of strings representing configuration methods to call, a full list can be found
     * <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2">here</a>.
     * For example:
     * <pre><code>
     * mapConfOpts: ['enableScrollWheelZoom','enableDoubleClickZoom','enableDragging'],
     * </code></pre>
     */
    /**
     * @cfg {Array} mapControls
     * Array of strings representing map controls to initialize, a full list can be found
     * <a href="http://code.google.com/apis/maps/documentation/reference.html#GControlImpl">here</a>.
     * For example:
     * <pre><code>
     * mapControls: ['GSmallMapControl','GMapTypeControl','NonExistantControl']
     * </code></pre>
     */
    /**
     * @cfg {Array} markers
     * Markers may be added to the map. Instead of specifying <code>lat</code>/<code>lng</code>,
     * geocoding can be specified via a <code>geoCodeAddr</code> string.
     * For example:
     * <pre><code>
markers: [{
    //lat: 42.339641,
    //lng: -71.094224,
    // instead of lat/lng:
    geoCodeAddr: '465 Huntington Avenue, Boston, MA, 02215-5597, USA',
    marker: {title: 'Boston Museum of Fine Arts'},
    listeners: {
        click: function(e){
            Ext.Msg.alert('Its fine', 'and its art.');
        }
    }
},{
    lat: 42.339419,
    lng: -71.09077,
    marker: {title: 'Northeastern University'}
}]
     * </code></pre>
     */
    // private
    mapDefined: false,
    // private
    mapDefinedGMap: false,
    initComponent : function(){

        this.addEvents(
            /**
             * @event mapready
             * Fires when the map is ready for interaction
             * @param {GMapPanel} this
             * @param {GMap} map
             */
            'mapready',
            /**
             * @event apiready
             * Fires when the Google Maps API is loaded
             */
            'apiready'
        );

        Ext.applyIf(this,{
          markers: [],
          cache: {
              marker: [],
              polyline: [],
              infowindow: []
          }
        });

        Ext.ux.GMapPanel.superclass.initComponent.call(this);

        if (window.google && window.google.maps){
          this.on('afterrender', this.apiReady, this);
        }else{
          window.gmapapiready = Ext.Function.bind(this.apiReady,this);
          this.buildScriptTag('http://maps.google.com/maps/api/js?sensor=false&callback=gmapapiready');
        }

    },
    apiReady : function(){

        if (this.rendered){

          Ext.defer(function(){
              if (this.gmapType === 'map'){
                  this.gmap = new google.maps.Map(this.getEl().dom, {zoom:this.zoomLevel,mapTypeId: google.maps.MapTypeId.ROADMAP});
                  this.mapDefined = true;
                  this.mapDefinedGMap = true;
              }

              if (this.gmapType === 'panorama'){
                  this.gmap = new GStreetviewPanorama(this.getEl().dom);
                  this.mapDefined = true;
              }

              if (!this.mapDefined && this.gmapType){
                 this.gmap = new google.maps.Map(this.getEl().dom, {zoom:this.zoomLevel,mapTypeId: google.maps.MapTypeId.ROADMAP});
                 this.gmap.setMapTypeId(this.gmapType);
                 this.mapDefined = true;
                 this.mapDefinedGMap = true;
              }

              google.maps.event.addListenerOnce(this.getMap(), 'tilesloaded', Ext.Function.bind(this.onMapReady, this));
              google.maps.event.addListener(this.getMap(), 'dragend', Ext.Function.bind(this.dragEnd, this));


              if (typeof this.setCenter === 'object') {
                  if (typeof this.setCenter.geoCodeAddr === 'string'){
                      this.geoCodeLookup(this.setCenter.geoCodeAddr, this.setCenter.marker, false, true, this.setCenter.listeners);
                  }else{
                      if (this.gmapType === 'map'){
                          var point = new google.maps.LatLng(this.setCenter.lat,this.setCenter.lng);
                          this.getMap().setCenter(point, this.zoomLevel);
                          this.lastCenter = point;
                      }
                      if (typeof this.setCenter.marker === 'object' && typeof point === 'object') {
                          this.addMarker(point, this.setCenter.marker, this.setCenter.marker.clear);
                      }
                  }
                  if (this.gmapType === 'panorama'){
                      this.getMap().setLocationAndPOV(new google.maps.LatLng(this.setCenter.lat,this.setCenter.lng), {yaw: this.yaw, pitch: this.pitch, zoom: this.zoomLevel});
                  }
              }
        }, 200,this); // Ext.defer

        }else{
          this.on('afterrender', this.apiReady, this);
        }
    },
    // private
    afterRender : function(){

        var wh = this.ownerCt.getSize();
        Ext.applyIf(this, wh);

        Ext.ux.GMapPanel.superclass.afterRender.call(this);

    },
    // private
    buildScriptTag: function(filename, callback) {
        var script  = document.createElement('script'),
        head        = document.getElementsByTagName("head")[0];
        script.type = "text/javascript";
        script.src  = filename;

        return head.appendChild(script);
    },
    // private
    onMapReady : function(){

        this.addMapControls();
        this.addOptions();

        this.addMarkers(this.markers);
        this.addMapListeners();

        this.fireEvent('mapready', this, this.getMap());
        return this;
    },
    // private
    addMapListeners : function () {
      	if (this.maplisteners){
      		Ext.iterate(this.maplisteners, function(key,val){
      			google.maps.event.addListener(this.getMap(), key, Ext.Function.bind(val,this));
      		},this);
      	}
    },
    // private
    onResize : function(w, h){

        Ext.ux.GMapPanel.superclass.onResize.call(this, w, h);

        // check for the existance of the google map in case the onResize fires too early
        if (typeof this.getMap() == 'object') {
            google.maps.event.trigger(this.getMap(), 'resize');
            if (this.lastCenter){
              this.getMap().setCenter(this.lastCenter, this.zoomLevel);
            }
        }

    },
    // private
    setSize : function(width, height, animate){

        Ext.ux.GMapPanel.superclass.setSize.call(this, width, height, animate);

        // check for the existance of the google map in case setSize is called too early
        if (Ext.isObject(this.getMap())) {
            google.maps.event.trigger(this.getMap(), 'resize');
            if (this.lastCenter){
              this.getMap().setCenter(this.lastCenter, this.zoomLevel);
            }
        }

    },
    // private
    dragEnd: function(){
      this.lastCenter = this.getMap().getCenter();
    },
    /**
     * Returns the current google map which can be used to call Google Maps API specific handlers.
     * @return {GMap} this
     */
    getMap : function(){

        return this.gmap;

    },
    /**
     * Returns the maps center as a GLatLng object
     * @return {GLatLng} this
     */
    getCenter : function(){

        return this.getMap().getCenter();

    },
    /**
     * Returns the maps center as a simple object
     * @return {Object} this has lat and lng properties only
     */
    getCenterLatLng : function(){

        var ll = this.getCenter();
        return {lat: ll.lat(), lng: ll.lng()};

    },
    /**
     * Creates markers from the array that is passed in. Each marker must consist of at least
     * <code>lat</code> and <code>lng</code> properties or a <code>geoCodeAddr</code>.
     * @param {Array} markers an array of marker objects
     */
    addMarkers : function(markers) {
        if (Ext.isArray(markers)){
            for (var i = 0; i < markers.length; i++) {
                if (markers[i]) {
                    if (typeof markers[i].geoCodeAddr == 'string') {
                        this.geoCodeLookup(markers[i].geoCodeAddr, markers[i].marker, false, markers[i].setCenter, markers[i].listeners);
                    } else {
                        var mkr_point = new google.maps.LatLng(markers[i].lat, markers[i].lng);
                        this.addMarker(mkr_point, markers[i].marker, false, markers[i].setCenter, markers[i].listeners);
                    }
                }
            }
        }

    },
    /**
     * Creates a single marker.
     * @param {Object} point a GLatLng point
     * @param {Object} marker a marker object consisting of at least lat and lng
     * @param {Boolean} clear clear other markers before creating this marker
     * @param {Boolean} center true to center the map on this marker
     * @param {Object} listeners a listeners config
     */
    addMarker : function(point, marker, clear, center, listeners){

        Ext.applyIf(marker,{});

        if (clear === true){
            this.clearMarkers();
        }
        if (center === true) {
            this.getMap().setCenter(point, this.zoomLevel)
            this.lastCenter = point;
        }

        var mark = new google.maps.Marker(Ext.apply(marker, {
            position: point
        }));

        if (marker.infoWindow){
            this.createInfoWindow(marker.infoWindow, point, mark);
        }

        this.cache.marker.push(mark);
        mark.setMap(this.getMap());

        if (typeof listeners === 'object'){
            for (evt in listeners) {
                google.maps.event.addListener(mark, evt, listeners[evt]);
            }
        }

        return mark;

    },
    /**
     * Creates a single polyline.
     * @param {Array} points an array of polyline points
     * @param {Object} linestyle an object defining the line style to use
     */
    addPolyline : function(points, linestyle){

        var plinepnts = new google.maps.MVCArray, pline, linestyle = linestyle ? linestyle : {
            strokeColor: '#FF0000',
            strokeOpacity: 1.0,
            strokeWeight: 2
        };

        Ext.each(points, function(point){
            plinepnts.push(new google.maps.LatLng(point.lat, point.lng));
        }, this);

        var pline = new google.maps.Polyline(Ext.apply({
          path: plinepnts
        },linestyle));

        this.cache.polyline.push(pline);

        pline.setMap(this.getMap());

    },
    /**
     * Creates an Info Window.
     * @param {Object} inwin an Info Window configuration
     * @param {GLatLng} point the point to show the Info Window at
     * @param {GMarker} marker a marker to attach the Info Window to
     */
    createInfoWindow : function(inwin, point, marker){

        var me = this, infoWindow = new google.maps.InfoWindow({
            content: inwin.content,
            position: point
        });

        if (marker) {
            google.maps.event.addListener(marker, 'click', function(){
                me.hideAllInfoWindows();
                infoWindow.open(me.getMap());
            });
        }

        this.cache.infowindow.push(infoWindow);

        return infoWindow;

    },
    // private
    hideAllInfoWindows : function(){
        for (var i = 0; i < this.cache.infowindow.length; i++) {
            this.cache.infowindow[i].close();
        }
    },
    // private
    clearMarkers : function(){

        this.hideAllInfoWindows();
        this.hideMarkers();

    },
    // private
    hideMarkers : function(){
        Ext.each(this.cache.marker, function(mrk){
            mrk.setMap(null);
        });
    },
    // private
    showMarkers : function(){
        Ext.each(this.cache.marker, function(mrk){
            mrk.setMap(this.getMap());
        },this);
    },
    // private
    addMapControls : function(){

        if (this.gmapType === 'map') {
            if (Ext.isArray(this.mapControls)) {
                for(i=0;i<this.mapControls.length;i++){
                    //this.addMapControl(this.mapControls[i]);
                }
            }else if(typeof this.mapControls === 'string'){
                //this.addMapControl(this.mapControls);
            }else if(typeof this.mapControls === 'object'){
                //this.getMap().add_control(this.mapControls);
            }
        }

    },
    /**
     * Adds a GMap control to the map.
     * @param {String} mc a string representation of the control to be instantiated.
     */
    addMapControl : function(mc){

        var mcf = window[mc];
        if (typeof mcf === 'function') {
            //this.getMap().addControl(new mcf());
        }

    },
    // private
    addOptions : function(){

        if (Ext.isArray(this.mapConfOpts)) {
            var mc;
            for(i=0;i<this.mapConfOpts.length;i++){
                //this.addOption(this.mapConfOpts[i]);
            }
        }else if(typeof this.mapConfOpts === 'string'){
            //this.addOption(this.mapConfOpts);
        }

    },
    /**
     * Adds a GMap option to the map.
     * @param {String} mo a string representation of the option to be instantiated.
     */
    addOption : function(mo){

        var mof = this.getMap()[mo];
        if (typeof mof === 'function') {
            this.getMap()[mo]();
        }

    },
    /**
     * Looks up and address and optionally add a marker, center the map to this location, or
     * clear other markers. Sample usage:
     * <pre><code>
buttons: [
    {
        text: 'Fenway Park',
        handler: function(){
            var addr = '4 Yawkey Way, Boston, MA, 02215-3409, USA';
            Ext.getCmp('my_map').geoCodeLookup(addr, undefined, false, true, undefined);
        }
    },{
        text: 'Zoom Fenway Park',
        handler: function(){
            Ext.getCmp('my_map').zoomLevel = 19;
            var addr = '4 Yawkey Way, Boston, MA, 02215-3409, USA';
            Ext.getCmp('my_map').geoCodeLookup(addr, undefined, false, true, undefined);
        }
    },{
        text: 'Low Accuracy',
        handler: function(){
            Ext.getCmp('my_map').geoCodeLookup('Paris, France', undefined, false, true, undefined);
        }
    },{

        text: 'Bogus Address',
        handler: function(){
            var addr = 'Some Fake, Address, For, Errors';
            Ext.getCmp('my_map').geoCodeLookup(addr, undefined, false, true, undefined);
        }
    }
]
     * </code></pre>
     * @param {String} addr the address to lookup.
     * @param {Object} marker the marker to add (optional).
     * @param {Boolean} clear clear other markers before creating this marker
     * @param {Boolean} center true to set this point as the center of the map.
     * @param {Object} listeners a listeners config
     */
    geoCodeLookup : function(addr, marker, clear, center, listeners) {

        if (!this.geocoder) {
            this.geocoder = new google.maps.Geocoder();
        }
        this.geocoder.geocode({
    			address: addr
    		}, Ext.Function.bind(this.addAddressToMap, this, [addr, marker, clear, center, listeners], true));

    },
  	// private
  	centerOnClientLocation : function(){
  		this.getClientLocation(function(loc){
  			var point = new google.maps.LatLng(loc.latitude,loc.longitude);
        this.getMap().setCenter(point, this.zoomLevel);
        this.lastCenter = point;
  		});
  	},
  	// private
  	getClientLocation : function(fn, errorFn){
  		if (!errorFn) {
          errorFn = Ext.emptyFn;
      }
  		if (!this.clientGeo) {
  			this.clientGeo = google.gears.factory.create('beta.geolocation');
  		}
  		geo.getCurrentPosition(Ext.Function.bind(fn, this), errorFn);
  	},
    // private
    addAddressToMap : function(response, status, addr, marker, clear, center, listeners){
        if (!response || status !== 'OK') {
            this.respErrorMsg(status);
        }else{
            var place = response[0].geometry.location,
			          accuracy = this.getLocationTypeInfo(response[0].geometry.location_type,'level'),
			          reqAccuracy = this.getLocationTypeInfo(this.minGeoAccuracy,'level');
            if (accuracy === 0) {
                this.geoErrorMsg(this.geoErrorTitle, this.geoErrorMsgUnable);
            }else{
                if (accuracy < reqAccuracy) {
                    this.geoErrorMsg(this.geoErrorTitle, Ext.String.format(this.geoErrorMsgAccuracy, response[0].geometry.location_type, this.getLocationTypeInfo(response[0].geometry.location_type,'msg')));
                }else{
                    point = new google.maps.LatLng(place.lat(),place.lng());
                    if (center){
                        this.getMap().setCenter(point, this.zoomLevel);
                        this.lastCenter = point;
                    }
                    if (typeof marker === 'object') {
                        if (!marker.title){
                            marker.title = response.formatted_address;
                        }
                        var mkr = this.addMarker(point, marker, clear, false, listeners);
                        if (marker.callback){
                          marker.callback.call(this, mkr, point);
                        }
                    }
                }
            }
        }

    },
    // private
    geoErrorMsg : function(title,msg){
        if (this.displayGeoErrors) {
            Ext.MessageBox.alert(title,msg);
        }
    },
    // private
    respErrorMsg : function(code){
        Ext.each(this.respErrors, function(obj){
            if (code == obj.code){
                Ext.MessageBox.alert(this.respErrorTitle, obj.msg);
            }
        }, this);
    },
    // private
    getLocationTypeInfo: function(location_type,property){
      var val = 0;
      Ext.each(this.locationTypes, function(itm){
        if (itm.code === location_type){
          val = itm[property];
        }
      });
      return val;
    }
});
</script>

<script type="text/javascript">
Ext.onReady(function() {

    Ext.define('Device', {
        extend: 'Ext.data.Model',
        fields: [
            {name: 'id', type: 'int'},
            {name: 'imei',type: 'string'}
        ]
    });

    Ext.define('Position', {
        extend: 'Ext.data.Model',
        fields: [
            {name: 'device_id', type: 'int'},
            {name: 'time', type: 'string'},
            {name: 'valid', type: 'boolean'},
            {name: 'latitude', type: 'float'},
            {name: 'longitude', type: 'float'},
            {name: 'speed', type: 'float'},
            {name: 'course', type: 'float'},
            {name: 'power', type: 'float'}
        ]
    });

    var devicesUrl = 'devices.json';
    var positionsUrl = 'positions.json';

    var devices = Ext.create('Ext.data.Store', {
        id: 'devices',
        model: 'Device',
        fields: ['id', 'imei'],
        autoSync: true,
        proxy: {
            type: 'ajax',
            api: {
                create: devicesUrl + '?action=create',
                read: devicesUrl,
                update: devicesUrl + '?action=update',
                destroy: devicesUrl + '?action=destroy'
            },
            reader: {
                type: 'json',
                root: 'results'
            }
        }
    });

    var positions = Ext.create('Ext.data.Store', {
        id: 'positions',
        model: 'Position',
        fields: [
            'device_id',
            'time',
            'valid',
            'latitude',
            'longitude',
            'speed',
            'course',
            'power'
        ],
        proxy: {
            type: 'ajax',
            url: positionsUrl,
            reader: {
                type: 'json',
                root: 'results'
            }
        }
    });
    
    var cityCarousel = [
        {lat: 59.95, lng: 30.3}, // Saint Petersburg
        {lat: 40.664167, lng: -73.938611}, // New York
        {lat: -36.840417, lng: 174.739869}, // Auckland
        {lat: 48.8567, lng: 2.3508}, // Paris, France
        {lat: 10.65, lng: -71.633333}, // Maracaibo
        {lat: 35.689506, lng: 139.6917}, // Tokyo
        {lat: 1.3, lng: 103.8}, // Singapore
        {lat: 22.278333, lng: 114.158889}, // Hong Kong
        {lat: 10.246944, lng: -67.596111}, // Maracay
        {lat: 51.507222, lng: -0.1275}, // London, UK
        {lat: 55.681200, lng: 12.486777} // Copenhagen, Denmark
    ];

    var map = Ext.create('Ext.ux.GMapPanel', {
        id: 'gmap',
        setCenter: cityCarousel[Math.floor((Math.random() * cityCarousel.length))],
        zoomLevel: 11
    });

    var devicesPanel = Ext.create('Ext.grid.Panel', {
        title: 'Devices',
        region: 'west',
        split: true,
        width: 200,
        margins: {top: 5, bottom: 0, right: 0, left: 5},

        sortableColumns: false,
        enableColumnHide: false,

        store: devices,
        tbar: [
            {
                id: 'device_update',
                text: 'Update',
                handler : function() {
                    devices.load();
                }
            },
            {
                id: 'device_add',
                text: 'Add',
                handler : function() {
                    Ext.Msg.prompt('Add', 'Device IMEI:', function(btn, text) {
                        if (btn == 'ok') {
                            devices.add({imei: text});
                        }
                    });
                }
            },
            {
                id: 'device_remove',
                text: 'Remove',
                disabled: true,
                handler : function() {
                    Ext.Msg.confirm('Remove', 'Are you sure to remove item?', function(btn) {
                        if (btn == 'yes') {
                            devices.remove(devicesPanel.getSelectionModel().getLastSelected());
                        }
                    });
                }
            },
            {
                id: 'device_edit',
                text: 'Edit',
                disabled: true,
                handler : function() {
                    Ext.Msg.prompt('Edit', 'Device IMEI:', function(btn, text) {
                        if (btn == 'ok') {
                            devicesPanel.getSelectionModel().getLastSelected().set('imei', text);
                        }
                    }, this, false, devicesPanel.getSelectionModel().getLastSelected().get('imei'));
                }
            }
        ],
        columns: [
            {header: 'Id',  dataIndex: 'id', width: 30},
            {header: 'IMEI', dataIndex: 'imei', flex: 1}
        ],
        listeners: {
            selectionchange: function(sender, selected, eOpts) {
                if (selected.length != 0) {
                    Ext.getCmp('device_remove').enable();
                    Ext.getCmp('device_edit').enable();

                    positions.getProxy().url = positionsUrl + '?deviceId=' +
                        devicesPanel.getSelectionModel().getLastSelected().get('id');
                    positions.load();
                    Ext.getCmp('position_update').enable();
                } else {
                    Ext.getCmp('position_update').disable();
                    positions.getProxy().url = positionsUrl;
                    positions.load();

                    Ext.getCmp('device_edit').disable();
                    Ext.getCmp('device_remove').disable();
                }
            }
        }
    });

    var positionsPanel = Ext.create('Ext.grid.Panel', {
        title: 'Positions',
        region: 'south',
        split: true,
        height: 250,
        margins: {top: 0, bottom: 5, right: 5, left: 5},

        sortableColumns: false,
        enableColumnHide: false,

        store: positions,
        tbar: [
            {
                id: 'position_update',
                text: 'Update',
                disabled: true,
                handler : function() {
                    positions.load();
                }
            }
        ],
        columns: [
            {header: 'Device Id', dataIndex: 'device_id'},
            {
                header: 'Time',
                dataIndex: 'time',
                flex: 1
                //renderer: Ext.util.Format.dateRenderer('Y-m-d H:i:s')
            },
            {header: 'Valid', dataIndex: 'valid'},
            {header: 'Latitude', dataIndex: 'latitude'},
            {header: 'Longitude', dataIndex: 'longitude'},
            {header: 'Speed', dataIndex: 'speed'},
            {header: 'Course', dataIndex: 'course'},
            {header: 'Power', dataIndex: 'power'}
        ],
        listeners: {
            selectionchange: function(sender, selected, eOpts) {
                if (selected.length != 0) {
                    var lat = positionsPanel.getSelectionModel().getLastSelected().get('latitude');
                    var lng = positionsPanel.getSelectionModel().getLastSelected().get('longitude');
                    var point = new google.maps.LatLng(lat, lng);
                    map.addMarker(point, {lat: lat, lng: lng}, true, true);
                } else {
                    map.clearMarkers(); // private?
                }
            }
        }
    });

    var mapPanel = Ext.create('Ext.panel.Panel', {
        title: 'Map',
        region: 'center',
        margins: {top: 5, bottom: 0, right: 5, left: 0},

        layout: 'fit',
        items: [map]
    });

    Ext.create('Ext.container.Viewport', {
        renderTo: Ext.getBody(),
        layout: 'border',
        items: [devicesPanel, positionsPanel, mapPanel]
    });

    devices.load();
});
</script>
</head>
<body></body>
</html>