Initial test version of NearMe feature add-on
authorChuck Scott <cscott@gaslightmedia.com>
Tue, 13 Dec 2016 18:36:36 +0000 (13:36 -0500)
committerChuck Scott <cscott@gaslightmedia.com>
Tue, 13 Dec 2016 18:36:36 +0000 (13:36 -0500)
models/admin/ajax/getPOI.php [new file with mode: 0644]
models/front/nearme/index.php [new file with mode: 0644]
setup/shortcodes.php
setup/validActions.php
views/front/footer.html [new file with mode: 0644]
views/front/nearme/header.html [new file with mode: 0644]
views/front/nearme/index.html [new file with mode: 0644]

diff --git a/models/admin/ajax/getPOI.php b/models/admin/ajax/getPOI.php
new file mode 100644 (file)
index 0000000..5146114
--- /dev/null
@@ -0,0 +1,276 @@
+<?php
+
+/**
+ * Gaslight Media Members Database
+ * Get Points of Interest (POI) for NearMe by AJAX
+ *
+ * PHP version 5.5
+ *
+ * @category glmWordPressPlugin
+ * @package  glmMembersDatabase
+ * @author   Chuck Scott <cscott@gaslightmedia.com>
+ * @license  http://www.gaslightmedia.com Gaslightmedia
+ * @version  0.1
+ */
+
+
+// Load Members Data Class
+require_once GLM_MEMBERS_PLUGIN_CLASS_PATH.'/data/dataMembers.php';
+
+/*
+ * This class exports the currently selected members list
+ * to a printable HTML file, to a CSV file, or otherwise.
+ */
+class GlmMembersAdmin_ajax_getPOI extends GlmDataMembers
+{
+
+    /**
+     * WordPress Database Object
+     *
+     * @var $wpdb
+     * @access public
+     */
+    public $wpdb;
+    /**
+     * Plugin Configuration Data
+     *
+     * @var $config
+     * @access public
+     */
+    public $config;
+
+    /*
+     * Constructor
+     *
+     * This contructor sets up this model. At this time that only includes
+     * storing away the WordPress data object.
+     *
+     * @return object Class object
+     *
+     */
+    public function __construct ($wpdb, $config)
+    {
+
+        // Save WordPress Database object
+        $this->wpdb = $wpdb;
+
+        // Save plugin configuration object
+        $this->config = $config;
+
+        parent::__construct(false, false);
+
+    }
+
+    /*
+     * Perform Model Action
+     *
+     * This modelAction takes an AJAX request to bump a member detail page views
+     * counter in the database for the current day, week and month and updates
+     * the database accordingly.
+     *
+     * This model action does not return, it simply does it's work then calls die();
+     *
+     * @param $actionData
+     *
+     * Echos JSON string as response and does not return
+     */
+    public function modelAction ($actionData = false)
+    {
+
+        $ret = false;
+        $poiAreas = false;      // False means to search anywhere
+
+        // Get any search string
+        $searchText = html_entity_decode( filter_input(INPUT_GET, 'searchText', FILTER_SANITIZE_STRING), ENT_QUOTES | ENT_XML1);
+
+        $request = filter_input(INPUT_GET, 'request', FILTER_SANITIZE_STRING);
+
+        // Get the target LAT/LON area
+        $latMax = filter_input(INPUT_GET, 'latMax', FILTER_VALIDATE_FLOAT);
+        $latMin = filter_input(INPUT_GET, 'latMin', FILTER_VALIDATE_FLOAT);
+        $lonMax = filter_input(INPUT_GET, 'lonMax', FILTER_VALIDATE_FLOAT);
+        $lonMin = filter_input(INPUT_GET, 'lonMin', FILTER_VALIDATE_FLOAT);
+
+
+        // If this is not an "anywhere" search, then build the lat/lon areas
+        switch($request) {
+
+            case 'onMap';
+
+                // Check for good LAT/LON data
+                if ($latMax && $latMin && $lonMax && $lonMin) {
+
+                    // Get the previous target LAT/LON area
+                    $lastLatMax = filter_input(INPUT_GET, 'lastLatMax', FILTER_VALIDATE_FLOAT);
+                    $lastLatMin = filter_input(INPUT_GET, 'lastLatMin', FILTER_VALIDATE_FLOAT);
+                    $lastLonMax = filter_input(INPUT_GET, 'lastLonMax', FILTER_VALIDATE_FLOAT);
+                    $lastLonMin = filter_input(INPUT_GET, 'lastLonMin', FILTER_VALIDATE_FLOAT);
+
+                    // If this is the first load, request all POIs within map bounds
+                    if ($lastLatMax == false) {
+
+                        // Add entire map as area to update
+                        $poiAreas[] = array(
+                            'area'   => 'Entire Map',
+                            'latMax' => $latMax,
+                            'latMin' => $latMin,
+                            'lonMax' => $lonMax,
+                            'lonMin' => $lonMin
+                        );
+
+                    // Otherwise request only those POIs in the new map areas
+                    } else {
+
+                        // Check for good last LAT/LON data
+                        if ($lastLatMax && $lastLatMin && $lastLonMax && $lastLonMin) {
+
+                            // Check if there's new area at the top of the map
+                            if ($latMax > $lastLatMax) {
+                                $poiAreas[] = array(
+                                    'area'   => 'New at top',
+                                    'latMax' => $latMax,
+                                    'latMin' => $lastLatMax,
+                                    'lonMax' => $lonMax,
+                                    'lonMin' => $lonMin
+                                );
+                            }
+
+                            // Check if there's new area at the bottom of the map
+                            if ($latMin < $lastLatMin) {
+                                $poiAreas[] = array(
+                                    'area'   => 'New at bottom',
+                                    'latMax' => $lastLatMin,
+                                    'latMin' => $latMin,
+                                    'lonMax' => $lonMax,
+                                    'lonMin' => $lonMin
+                                );
+                            }
+
+                            // Check if there's new area at the left of the map
+                            if ($lonMin < $lastLonMin) {
+                                $poiAreas[] = array(
+                                    'area'   => 'New at left',
+                                    'latMax' => $latMax,
+                                    'latMin' => $latMin,
+                                    'lonMax' => $lastLonMin,
+                                    'lonMin' => $lonMin
+                                );
+                            }
+
+                            // Check if there's new area at the right of the map
+                            if ($lonMax > $lastLonMax) {
+                                $poiAreas[] = array(
+                                    'area'   => 'New at right',
+                                    'latMax' => $latMax,
+                                    'latMin' => $latMin,
+                                    'lonMax' => $lonMax,
+                                    'lonMin' => $lastLonMax
+                                );
+                            }
+
+                        }
+
+                    }
+
+                }
+
+                break;
+
+            case 'anywhere':
+                break;
+
+            default:
+                $request = 'onMap';
+                break;
+
+        }
+
+        /*
+         * Get map items
+         *
+         * (NOTE that while this is for a front-end feature, that feature gets
+         *  data via AJAX, which is processed by the admin controller.)
+         *
+         * The array supplied is in the following standardized format. This format is
+         * used for all generic map items aggregated from multiple sources.
+         *
+         *  'request'       What type of request is being made - Request only
+         *  'filter'        A string filter to use to limit results - Request only
+         *  'area'          A set of lat/lon areas to get results for (if request = 'onMap') - Request only
+         *                      If false then get all results without regard as to where they are.
+         *  'sources'       An array of source information for what type of item and which add-on - Each source adds one entry here
+         *  'mapItems'      Array of new map items to return to NearMe - Each source adds one or more map items here
+         *
+         * array(
+         *      'request' => {'onMap', 'anywhere'}
+         *      'filter' => {some search string}
+         *      'area' => array(
+         *          // First area to search
+         *          array(
+         *              'latMax' => {lat at North end of area},
+         *              'latMin' => {lat at South end of area},
+         *              'lonMax' => {lon at East end of area},
+         *              'lonMin' => {lon at West end of area}
+         *          ),
+         *          // Second area to search
+         *          array(
+         *              'latMax' => {lat at North end of area},
+         *              'latMin' => {lat at South end of area},
+         *              'lonMax' => {lon at East end of area},
+         *              'lonMin' => {lon at West end of area}
+         *          ),
+         *          // Additional areas
+         *      ),
+         *      'sources' = array(
+         *          {addOn Slug} => array(
+         *              'addOn' => {addOn Slug},
+         *              'type'  => {Name to use for the source - Needs to match the 'type' element in map items added by the addOn )
+         *          ),
+         *          // Additional sources
+         *      ),
+         *      'mapItems' => array(
+         *          array(
+         *              'type'        => {type of item, i.e. 'member', 'event', ...},
+         *              'id'          => {ID of item for reference},
+         *              'lat'         => {Latitude},
+         *              'lon'         => {Longitude},
+         *              'name'        => {Name of item},
+         *              'loc_name'    => {Name of Location},
+         *              'addr1'       => {Address line 1 of location},
+         *              'addr2'       => {Address line 2 of location},
+         *              'city'        => {City of location},
+         *              'state'       => {State code of location},
+         *              'zip'         => {Postal Code of location},
+         *              'phone'       => {Contact phone number},
+         *              'email'       => {Contact E-Mail address},
+         *              'url'         => {URL of item},
+         *              'region'      => {Region name, if available},
+         *              'categories'  => {array of categories - i.e. array( 0 = array( id => {id}, name => {name}), ... )
+         *              'descr'       => {Description},
+         *              'short_descr' => {Short Description},
+         *              'detail_page' => {URL of detail page, if available},
+         *              'dates'       => {Text stating date, dates, or date range},
+         *              'times'       => {Text stating time, times, or time range}
+         *          )
+         *      )
+         * )
+         */
+        $poiData = apply_filters(
+            'glm-hook-list-map-items-by-latlon',
+            array(
+                'request' => $request,
+                'filter' => $searchText,
+                'area'   => $poiAreas,
+                'sources'  => array(),
+                'mapItems' => array()
+            )
+        );
+
+        header('Content-type:application/json;charset=utf-8', true);
+        echo json_encode($poiData);
+
+        wp_die();
+
+    }
+
+}
diff --git a/models/front/nearme/index.php b/models/front/nearme/index.php
new file mode 100644 (file)
index 0000000..00b6def
--- /dev/null
@@ -0,0 +1,195 @@
+<?php
+/**
+ * Gaslight Media Members Database
+ * NearMe Page
+ *
+ * PHP version 5.5
+ *
+ * @category glmWordPressPlugin
+ * @package  glmMembersDatabase
+ * @author   Chuck Scott <cscott@gaslightmedia.com>
+ * @license  http://www.gaslightmedia.com Gaslightmedia
+ * @version  0.1
+ */
+
+/*
+ * This class performs the work for displaying members packages.
+ */
+class GlmMembersFront_nearme_index
+{
+
+    /**
+     * WordPress Database Object
+     *
+     * @var $wpdb
+     * @access public
+     */
+    public $wpdb;
+    /**
+     * Plugin Configuration Data
+     *
+     * @var $config
+     * @access public
+     */
+    public $config;
+
+    /*
+     * Constructor
+     *
+     * This contructor sets up this model. At this time that only includes
+     * storing away the WordPress data object.
+     *
+     * @return object Class object
+     *
+     */
+    public function __construct ($wpdb, $config)
+    {
+
+        // Save WordPress Database object
+        $this->wpdb = $wpdb;
+
+        // Save plugin configuration object
+        $this->config = $config;
+
+        // Enqueue the Marker Clusterer Script
+        wp_register_script(
+            'glm-members-admin-google-maps-marker-clusterer',
+            GLM_MEMBERS_PLUGIN_URL . 'js/googleMapsMarkerClusterer/markerclustererplus.js',
+            array(
+                    'jquery'
+            ),
+            GLM_MEMBERS_PLUGIN_VERSION
+        );
+        wp_enqueue_script('glm-members-admin-google-maps-marker-clusterer', false, array('jquery'), false, true);
+
+    }
+
+    /*
+     * Perform Model Action
+     *
+     * This method does the work for this model and returns any resulting data
+     *
+     * @return array Status and data array
+     *
+     * 'status'
+     *
+     * True if successfull and false if there was a fatal failure.
+     *
+     * 'menuItemRedirect'
+     *
+     * If not false, provides a menu item the controller should
+     * execute after this one. Normally if this is used, there would also be a
+     * modelRedirect value supplied as well.
+     *
+     * 'modelRedirect'
+     *
+     * If not false, provides an action the controller should execute after
+     * this one.
+     *
+     * 'view'
+     *
+     * A suggested view name that the contoller should use instead of the
+     * default view for this model or false to indicate that the default view
+     * should be used.
+     *
+     * 'data'
+     *
+     * Data that the model is returning for use in merging with the view to
+     * produce output.
+     *
+     */
+    public function modelAction ($actionData = false)
+    {
+
+        $status = true;
+        $pointsOfInterest = array();
+        $havePOI = false;          // Have Points of Interest
+
+        // Set default map center
+        $mapCenterLat = $this->config['settings']['maps_default_lat'];
+        $mapCenterLon = $this->config['settings']['maps_default_lon'];
+
+        // Set default reference location
+        $refLat = $mapCenterLat;
+        $refLon = $mapCenterLon;
+
+        // Set default map zoom
+        $mapZoom = 14;          // Need to add this to management ******
+
+        // Check for user cookie - Cookie will be set by ajax request from map so we can save the last user map status - Overides defaults
+        if (isset($_COOKIE) && isset($_COOKIE[GLM_MEMBERS_NEARME_PLUGIN_SLUG])) {
+            $cookie = $_COOKIE[GLM_MEMBERS_NEARME_PLUGIN_SLUG];
+            $mapCenterLat = $cookie['mapCenterLat'];
+            $mapCenterLon = $cookie['mapCenterLon'];
+            $refLat = $cookie['refLat'];
+            $refLon = $cookie['refLon'];
+            $mapZoom = $cookie['mapZoom'];
+        }
+
+        // Check for specified map center, reference location, and zoom - Overides cookies and defaults
+        $mapCenterLat = $this->checkMapRequest('mapCenterLat', $mapCenterLat);
+        $mapCenterLon = $this->checkMapRequest('mapCenterLon', $mapCenterLon);
+        $refLat = $this->checkMapRequest('refLat', $refLat);
+        $refLon = $this->checkMapRequest('refLon', $refLon);
+        $mapZoom = $this->checkMapRequest('mapZoom', $mapZoom);
+
+        $view = 'index.html';
+
+        // Compile template data
+        $templateData = array(
+            'siteBaseUrl' => GLM_MEMBERS_SITE_BASE_URL,
+            'pointsOfInterest' => $pointsOfInterest,
+            'havePOI' => $havePOI,
+            'mapCenterLat' => $mapCenterLat,
+            'mapCenterLon' => $mapCenterLon,
+            'refLat' => $refLat,
+            'refLon' => $refLon,
+            'mapZoom' => $mapZoom
+        );
+
+        // Return status, suggested view, and data to controller - also return any modified settings
+        return array(
+            'status' => $status,
+            'menuItemRedirect' => false,
+            'modelRedirect' => false,
+            'view' => 'front/nearme/'.$view,
+            'data' => $templateData
+        );
+
+    }
+
+    /*
+     * Check for a submitted value for map defaults
+     *
+     * @param $param string Name of the request parameter
+     * @param $default float/integer Default value to return if none was sumbitted
+     *
+     * @return float/integer Value to use
+     */
+    private function checkMapRequest ($param, $default)
+    {
+        // If there's no request array, return default
+        if (!isset($_REQUEST)) {
+            return $default;
+        }
+
+        // If the request parameter doesn't exist, return default
+        if (!isset($_REQUEST[$param])) {
+            return $default;
+        }
+
+        // If it's not a valid number, return default
+        $val = ($_REQUEST[$param] - 0);
+        if ($val == 0) {
+            return $default;
+        }
+
+        // Return the parameter value
+        return $val;
+
+    }
+
+
+}
+
+?>
\ No newline at end of file
index 2206529..3403201 100644 (file)
@@ -48,7 +48,7 @@
  * The following is an explanation of this array.
  *
  * array(
- *      '{shortcode-slug} => array(
+ *      '{shortcode-slug}' => array(
  *          'plugin' => '{plugin (add-on) slug}',       // Identifies which plugin is providing the short-code
  *          'menu' => '{menu name}',                    // Menu name in this context is simply where to find the action
  *          'action' => '{shortcode action name},       // Action used to execute this shortcode
@@ -62,6 +62,7 @@
  *
  * Shortcode descriptions sample
  *
+ *      <tr><th>Shortcode</th><th>Attribute</th><th>Description</th></tr>
  *      <tr>
  *           <th>[glm-members-sample-shortcode]</th>
  *           <td>&nbsp;</td>
  */
 
 $glmMembersNearMeShortcodes = array(
+    'glm-members-nearme' => array(
+        'plugin' => GLM_MEMBERS_NEARME_PLUGIN_SLUG,       // Identifies which plugin is providing the short-code
+        'menu' => 'nearme',                               // Menu name in this context is simply where to find the action
+        'action' => 'index',                              // Action used to execute this shortcode
+        'table' => false,                                 // Database table where default attribute values are stored
+        'attributes' => array(                            // An array of all shortcode attributes (options)
+        )
+    )
 );
 
-$glmMembersNearMeShortcodesDescription = '';
+$glmMembersNearMeShortcodesDescription = '
+    <tr><th>Shortcode</th><th>Attribute</th><th>Description</th></tr>
+    <tr>
+         <th>[glm-members-nearme]</th>
+         <td>&nbsp;</td>
+         <td width="50%">
+             Displays the NearMe search map and associated information.
+         </td>
+    </tr>
+';
 
index 9e4eee3..05af50d 100644 (file)
 
 $glmMembersNearMeAddOnValidActions = array(
     'adminActions' => array(
+        'ajax' => array(
+            'getPOI' => GLM_MEMBERS_NEARME_PLUGIN_SLUG
+        )
     ),
     'frontActions' => array(
+        'nearme' => array(
+            'index' => GLM_MEMBERS_NEARME_PLUGIN_SLUG
+        )
     )
 );
 
diff --git a/views/front/footer.html b/views/front/footer.html
new file mode 100644 (file)
index 0000000..75ce31a
--- /dev/null
@@ -0,0 +1,4 @@
+
+    </div> <!-- / admin content area -->
+  
+</div> <!-- / wrap -->
\ No newline at end of file
diff --git a/views/front/nearme/header.html b/views/front/nearme/header.html
new file mode 100644 (file)
index 0000000..4eaacea
--- /dev/null
@@ -0,0 +1,3 @@
+<div class="wrap">
+    <div id="glm-member-db-front-container" class="glm-member-front-container">
+    
\ No newline at end of file
diff --git a/views/front/nearme/index.html b/views/front/nearme/index.html
new file mode 100644 (file)
index 0000000..a296762
--- /dev/null
@@ -0,0 +1,871 @@
+{include file='front/nearme/header.html'}
+
+    <script src="http://maps.googleapis.com/maps/api/js?&key={$settings.google_maps_api_key}"></script>
+    <script type="text/javascript">var enableDraggable = true;</script>
+
+    <div>
+        Display only items matching this text:&nbsp;&nbsp;&nbsp;&nbsp;
+        
+        <a id="glmNearMeSearchAnywhere" class="button button-secondary glm-member-button-small">Search All Areas</a> 
+        <input id="glmNearMeSearchText" type="text">
+    </div>
+    
+    <p><!-- just adding space here temporarily --></p>
+    
+    <div style="height: 2em;">
+        <b>My Location:</b> 
+            <input id="glmMyLocationFollowMe" class="glmMapMyLocationType" type="radio" name="myLocationType" value="track" checked="checked"> Follow Me
+            <input id="glmMyLocationSet" class="glmMapMyLocationType" type="radio" name="myLocationType" value="set"> Manual Map Selection
+        &nbsp;&nbsp;
+        <a id="glmMapSetMyLocation" class="button button-secondary glm-member-button-small glm-hidden">Set My Location Here</a>&nbsp;
+        <a id="glmMapGoToMyLocation" class="button button-secondary glm-member-button-small">Return to My Location</a>&nbsp;
+        <a id="glmMapBack" class="button button-secondary glm-member-button-small glm-hidden">Back to previous map location</a>&nbsp;
+        <span id="glmMapReload" class="glm-hidden glm-right">Loading ...</span>
+    </div>
+    <div id="glm-nearmeMap" class="glm-map-tall">(map loads here)</div>
+    <div id="glm-mapLegend" style="margin: .4em;">
+        <b>Show:</b>&nbsp;&nbsp;
+    </div>
+    <div>
+        My Location <img src="{$assetsUrl}/MapIcons/standard/woman.png"> (drag to change)
+        <span id="glm-mapClusterLegend" class="glm-right">
+            Clustered locations - Click to view: 
+            <img style="width: 26px; height: auto;" src="{$baseUrl}/js/googleMapsMarkerClusterer/images/m1.png">
+            <img style="width: 28px; height: auto;" src="{$baseUrl}/js/googleMapsMarkerClusterer/images/m2.png">
+            <img style="width: 33px; height: auto;" src="{$baseUrl}/js/googleMapsMarkerClusterer/images/m3.png">
+            <img style="width: 39px; height: auto;" src="{$baseUrl}/js/googleMapsMarkerClusterer/images/m4.png">
+            <img style="width: 45px; height: auto;" src="{$baseUrl}/js/googleMapsMarkerClusterer/images/m5.png">
+        </span>
+    </div>
+    <div id="nearMe-detail"><!-- Detail for map item goes here when clicked. --></div>
+    <div id="nearMe-favorites">
+        <h4>My Favorites</h4>
+        <div>
+            <!-List of favorites goes here -->
+        </div>
+    </div>
+    
+    <div id="nearMe-mouseover-template" class="glm-hidden">
+        { name } - Click for more
+    </div>
+        
+    <script type="text/javascript">
+        jQuery(document).ready(function($) {
+            
+            /*
+             * Google Maps
+             *  API reference: https://developers.google.com/maps/documentation/javascript/reference
+             */
+    
+            // Settings
+            var doDetailSection = false;        // Show detail section below map
+            var doPOIListSection = false;       // Show a list of map points of interest below map
+            var defZoom = {$mapZoom};           // Default zoom for starting up or when returning to selected location
+            var doMapBubble = true;             // Show detail on map bubbles in map
+            var clusterGridSize = 30;           // Area in pixels over which clustering may take place. Smaller number = more smaller clusters.
+            var clusterMaxZoom = 14;            // Maximum map zoom where clustering may take place. Higher number is smaller map area. Set to 1 to effectively dissable clustering.
+            var clusterMinSize = 3;             // Minimum number of markers that may be in a cluster.
+            var mapLat = {$mapCenterLat};       // Set default map Lat - Default for the current Website
+            var mapLon = {$mapCenterLon};       // Set default map Lon - Default for the current Website       
+            var refLat = {$refLat};             // Default location for My Location marker - Lat
+            var refLon = {$refLon};             // Default location for My Location marker - Lon
+            var maxAnywhereZoom = 14;           // Max zoom in when doing anywhere search
+            var boundsTimeout = 1500;           // Time to wait after a map move or zoom before trying to load new POIs in milliseconds
+            var trackPositionInterval = 60000;  // Time interval for getting user's current geolocation - 1 Min
+            var highAccuracyPoisition = false;  // Request high-accuracy user geolocation from user's device
+            var postionTimeout = 15000;         // Maximum amount of time we'll wait for geolocation result - 15 Sec
+            var geolocationFailCount = 2;       // Allow geolocation to fail this many times before switching to manual location settings ('set')
+            var trackToConsole = false;          // Send debug/progress messages to developers console (Firefox)
+            
+            // Set default - Need to make this configurable
+            var mapLocation = new google.maps.LatLng(mapLat, mapLon);
+            var refLocation = new google.maps.LatLng(refLat, refLon);
+            var map;
+            var geocoder;
+            var marker;
+            var latMax = false;
+            var latMin = false;
+            var lonMax = false;
+            var lonMin = false;
+            var lastLatMax = false;
+            var lastLatMin = false;
+            var lastLonMax = false;
+            var lastLonMin = false;
+            var boundsDelayTimer = false;
+            var markers = [];               // Object with marker objects attached by key name
+            var markerId = 1;
+            var markerClusterer = false;
+            var myMarker = false;
+            var myCurrentLocation = false;
+            var canReadUserLocation = true;
+            var savedMapView = [];
+            var mapViewStack = [];
+            var curZoom = false;
+            var curCenter = false;
+            var poiTypes = [];
+            var dontPushPos = false;
+            var mapBubble = false;
+            var mapTitle = false;
+            var categories = [];
+            var availPushPins = [
+                'standard/blue.png',
+                'standard/green.png',
+                'standard/lightblue.png',
+                'standard/orange.png',
+                'standard/pink.png',
+                'standard/purple.png',
+                'standard/red.png',
+                'standard/yellow.png'
+            ]
+            var assignedPushPins = [];
+            var delQueue = [];
+            var dragging = false;
+            var trackingType = 'track';
+            var trackingTimer;
+            var getLocationIngoredTimer;
+            var geolocationFailCounter = 0;
+            var stopGeolocation = false;
+                        
+            function initMap() {
+                
+                if (trackToConsole) { console.log('GLM NearMe: initMap()'); }
+                
+                map = new google.maps.Map(document.getElementById('glm-nearmeMap'), {  
+                    zoom: defZoom,  
+                    disableDefaultUI: false,   
+                    mapTypeId: google.maps.MapTypeId.MAP  
+                });
+                var geocoder = new google.maps.Geocoder();
+
+                markerClusterer = new MarkerClusterer(map, [], {
+                    imagePath: '{$baseUrl}/js/googleMapsMarkerClusterer/images/m',
+                    gridSize: clusterGridSize,
+                    maxZoom: clusterMaxZoom,
+                    minimumClusterSize: clusterMinSize
+                });
+
+                // Create a moveable marker for our currently saved reference location
+                myMarker = new google.maps.Marker({  
+                    map: map,  
+                    position: refLocation,
+                    draggable: enableDraggable,
+                    animation: google.maps.Animation.DROP,  
+                    icon: '{$assetsUrl}/MapIcons/standard/woman.png'
+                });
+                
+                // Get initial user location, but wait a bit for things to get seup.
+                google.maps.event.addListenerOnce(map, 'idle', function(){
+                    window.setTimeout(getUserLocation, 1000);
+
+                    // Start with map centered on reference location
+                    map.setCenter(mapLocation);
+
+                    // Save the initail map position
+                    curZoom = map.getZoom();
+                    curCenter = map.getCenter();
+
+                });
+                
+                google.maps.event.addListener(myMarker, 'dragend', function(event) {
+// Save My location stuff here 
+                });
+
+                // Add lister to capture map position and zoom before dragging
+                map.addListener('dragstart', function() {
+                    
+                    if (trackToConsole) { console.log('GLM NearMe: Drag map started'); }
+                    
+                    // Close map bubble if used
+                    closeMapBubble();
+                    
+                });
+                
+                // On first load get all POIs for current bounds - Don't push position 
+                dontPushPos = true;
+                checkNewPOIs(boundsTimeout * 2);    // Give things a bit more time to be ready before gettign POIs the first time.
+                             
+                // Add a click listener for this marker
+                myMarker.addListener('mouseover', function() {
+                    
+                    if (trackToConsole) { console.log('GLM NearMe: myMarker mouseover'); }
+                    
+                    if (dragging) {
+                        return;
+                    }
+                    closeMapBubble();
+                    closeMapTitle();
+                    mapTitle = new google.maps.InfoWindow({
+                        content: 'My Selected Location'
+                    });
+                    mapTitle.open(map, this);
+                        
+                });
+                
+                myMarker.addListener('mouseout', function() {
+                    
+                    if (trackToConsole) { console.log('GLM NearMe: myMarker mouseout'); }
+                    
+                    if (dragging) {
+                        return;
+                    }
+                    mapTitle.close();
+                    checkNewPOIs();
+                });
+
+                myMarker.addListener('dragstart', function() {
+                    
+                    if (trackToConsole) { console.log('GLM NearMe: myMaker dragstart'); }
+                    
+                    dragging = true;
+                    mapTitle.close();
+                    window.clearTimeout(boundsDelayTimer);
+                });
+
+                myMarker.addListener('dragend', function() {
+                    
+                    if (trackToConsole) { console.log('GLM NearMe: myMarker dragend'); }
+                    
+                    dragging = false;
+                    checkNewPOIs();
+                });
+
+                // Request update for points of interest when map dragging stops.
+                map.addListener('dragend', function() {
+                    
+                    if (trackToConsole) { console.log('GLM NearMe: map dragend'); }
+                    
+                    checkNewPOIs();
+                });
+                
+                // Request update for points of interest when map zoom changes.
+                map.addListener('zoom_changed', function() {
+                    
+                    if (trackToConsole) { console.log('GLM NearMe: map zoom changed'); }
+                    
+                    // Close map bubble if used
+                    closeMapBubble();
+                    
+                    checkNewPOIs();
+                });
+                
+            }
+
+            // Watch for Reference Map Position request by user
+            $('#glmMapGoToMyLocation').on('click', function() {
+                
+                if (trackToConsole) { console.log('GLM NearMe: User GoToMyLocation clicked'); }
+                
+                goToPosition(myMarker.getPosition());
+                return false;
+            });
+
+            function goToPosition(position) {
+                map.setCenter(position);
+                map.setZoom(defZoom)
+                checkNewPOIs();
+            }
+            
+            
+            // Watch for request to set My Selected Location to center of map
+            $('#glmMapSetMyLocation').on('click', function() {
+                
+                if (trackToConsole) { console.log('GLM NearMe: User SetMyLocation clicked'); }
+                
+                myMarker.setPosition(map.getCenter());
+                return false;
+            });
+            
+            // Watch for Previous Map Position request by user
+            $('#glmMapBack').on('click', function() {
+                
+                if (trackToConsole) { console.log('GLM NearMe: User MapBack clicked'); }
+                
+                // If there's any previous map positions on the stack
+                if (mapViewStack.length) {                
+                    
+                    if (trackToConsole) { console.log('GLM NearMe: Moving to previous map position'); }
+                    
+                    dontPushPos = true;
+                    
+                    // Get the previons position data
+                    var poppedPos = mapViewStack.pop();
+                    
+                    // Set the map to the popped position
+                    map.panTo(poppedPos.center);
+                    map.setZoom(poppedPos.zoom);
+
+                    // If there's nothing left on the stack, remove back button
+                    if (!mapViewStack.length) {
+                        
+                        if (trackToConsole) { console.log('GLM NearMe: No previous map postion'); }
+                        
+                        $('#glmMapBack').addClass('glm-hidden');
+                    }
+
+                    checkNewPOIs();
+
+                }
+                
+                return false;
+            });
+            
+            // Redo search when Search Anywhere button is clicked.
+            $('#glmNearMeSearchAnywhere').click('change', function() {
+                getBoundsPOIs(true, true);
+            });
+
+            // Trigger text search for POIs when search text is changed
+            $('#glmNearMeSearchText').catchEnter().on('enterkey', function() {
+                getBoundsPOIs(true);
+            });
+
+            // Schedule a check for new POIs
+            function checkNewPOIs(timeout = boundsTimeout) {
+
+                if (trackToConsole) { console.log('GLM NearMe: checkNewPOIs()'); }
+                
+                // Reset the delay timer to make sure nothing happens from an earlier dragend
+                window.clearTimeout(boundsDelayTimer);
+                
+                // Set timer to get POIs
+                boundsDelayTimer = window.setTimeout(getBoundsPOIs, timeout);
+
+            }
+            
+            /* 
+             *  Get new bounds for map and check for POI changes
+             *  
+             *  type        'onMap' or 'anywhere'
+             *  searchText  A string that should match any returned results
+             *  resetPOIs   true to clear all POIs and reload them all for current map bounds
+             *                  Not needed for type 'anywhere' - full reset is assumned
+             */
+            function getBoundsPOIs(resetPOIs = false, anywhere = false) {
+
+                if (trackToConsole) { console.log('GLM NearMe: getBoundsPOIs() '); }
+                
+                var searchText = $('#glmNearMeSearchText').val();
+                var request = 'onMap';
+                if (anywhere) {
+                    request = 'anywhere';
+                    
+                    // If we're currently in "Follow Me" mode, switch to manual
+                    if (trackingType == 'track') {
+                        setMyLocationToManual();
+                    }
+                    
+                    // Then turn off checked
+                    $('#glmNearMeSearchAnywhere').attr("checked", false);
+                }
+
+                
+               // Get current map view data
+                mapBounds = map.getBounds();
+                ne = mapBounds.getNorthEast();
+                sw = mapBounds.getSouthWest();
+                latMax = ne.lat();
+                latMin = sw.lat();
+                lonMax = ne.lng();
+                lonMin = sw.lng();
+                
+                // If "reset" requested, clear last position values
+                if (resetPOIs) {
+                    lastLatMax = false;
+                    lastLatMin = false;
+                    lastLonMax = false;
+                    lastLonMin = false;
+                }
+                
+                // Check if there's been no movement - Don't do anything.
+                if ( latMax == lastLatMax && latMin == lastLatMin && lonMax == lastLonMax && lonMin == lastLonMin ) {
+                    
+                    if (trackToConsole) { console.log('GLM NearMe: No map change, aborting getBoundsPOIs()'); }
+                    
+                    hideLoadingMsg();
+                    return;
+                }
+             
+                showLoadingMsg();
+                
+                // If there is a previous map position, push that on the stack - unless we've been told not to
+                if (dontPushPos) {
+                    dontPushPos = false;
+                } else {
+                    
+                    // Push the previous position on the stack
+                    mapViewStack.push({
+                        zoom: curZoom,
+                        center:  curCenter
+                    });
+                    
+                    // Show the back button
+                    $('#glmMapBack').removeClass('glm-hidden');
+                    
+                    // Clear the don't push on stack flag
+                    dontPushPos = false;
+                }
+                
+                // Request any POIs in new areas of the map
+                $.getJSON(
+                    '{$ajaxUrl}?action=glm_members_admin_ajax&glm_action=getPOI&request='+request+'&latMax='+latMax+'&latMin='+latMin+'&lonMax='+lonMax+'&lonMin='+lonMin+'&lastLatMax='+lastLatMax+'&lastLatMin='+lastLatMin+'&lastLonMax='+lastLonMax+'&lastLonMin='+lastLonMin,
+                    'searchText='+searchText,
+                    function( newPOIs ) {
+                        
+                        if (trackToConsole) { console.log('GLM NearMe: Requesting newPOIs'); }
+                        
+                        // If resetPOIs is requested, clear all clusterer markers, all map markers, and our own markers array.
+                        if (resetPOIs) {
+                            
+                            markerClusterer.clearMarkers();
+                            markerClusterer.repaint();
+                            
+//                            map.markers = [];
+                            markers = [];
+//                            markerClusterer.redraw();
+                            
+                            
+                        // Otherwise remove only markers that are off our map
+                        } else {
+                            
+                            // Delete the POIs that are off our map
+                            for (key in markers) {
+    
+                                // If reset is requested or this marker is not within our map bounds. remove the marker and delete it
+                                if (markers[key].getPosition().lat() > latMax || markers[key].getPosition().lat() < latMin || 
+                                    markers[key].getPosition().lng() > lonMax || markers[key].getPosition().lng() < lonMin) {
+                                   
+                                    // Add this marker to the delete queue
+                                    delQueue.push(key);
+                                }
+    
+                            }                        
+    
+                            if (trackToConsole) { console.log('GLM NearMe: '+Object.keys(delQueue).length+' markers in delete queue'); }
+
+                            // Remove markers in the delete queue from the clusterer and markers array - can't do this in loop above
+                            if (Object.keys(delQueue).length) {
+                                $.each( delQueue, function(key, value) {               
+                                    
+                                    // Remove the marker from the clusterer
+                                    markerClusterer.removeMarker(markers[value]);
+                                        
+                                    // Remove the marker from the map
+                                    markers[value].setMap(null);
+                                    
+                                    // Delete our marker
+                                    delete markers[value];
+                                    
+                                });
+                            }
+                            delQueue = [];  // Clear delete queue 
+                            
+                        }
+                        
+                        if (trackToConsole) { console.log('GLM NearMe: Processing '+Object.keys(newPOIs.sources).length+' POI sources'); }
+                        
+                        // Add all sources (members, events, ...) to the assignedPushPins array
+                        $.each( newPOIs.sources, function(key, value) {
+
+                            if (!assignedPushPins[value.type]) {
+                                
+                                if (trackToConsole) { console.log('GLM NearMe: Adding POI type '+value.type); }
+                                        
+                                // Pull a new pushpin off the available stack and assign that to this type
+                                pushPin = availPushPins.pop();
+                                pushPinData = { pinFile: pushPin, pinOn: true };
+                                assignedPushPins[value.type] = pushPinData;
+
+                                // Add the new pushpin to the map legend
+                                $('#glm-mapLegend').append('<input data-type="'+value.type+'" class="glm-type-select" type="checkbox" checked><img src="{$assetsUrl}/MapIcons/'+pushPin+'"> '+value.type+'&nbsp;&nbsp;&nbsp;&nbsp;');
+                                
+                            }               
+                        });
+
+                        // Re-bind click on ping type for when a pin type is turned on or off by the user
+                        // Note that .upbind().on() prevents the event from being bound multiple times from multiple map moves.
+                        $('.glm-type-select').unbind('click').on('click', function() {
+                            
+                            // Get the type and current visibility state
+                            pinType = $(this).attr('data-type');
+                            pinVisible = $(this).prop('checked');        
+                            
+                            if (trackToConsole) { console.log('Pin type "'+pinType+' set to '+pinVisible); }
+                            
+                            // Save visibility to the specified assignedPushPins element
+                            assignedPushPins[pinType].pinOn = pinVisible;
+                            
+                            // loop through all current markers and set visibility for any of that type
+                            for (key in markers) {   
+                                if (markers[key].type == pinType) {
+                                    
+                                    // Set marker visibility
+                                    markers[key].setVisible(pinVisible);
+                                
+                                    // If not visible, remove from clusterer. If visible, add back to clusterer
+                                    if (pinVisible) {
+                                        markerClusterer.addMarker(markers[key]);
+                                    } else {
+                                        markerClusterer.removeMarker(markers[key]);
+                                    }
+                                }
+                                
+                            }
+
+                        });
+                        
+                        // If we have new POIs
+                        if (newPOIs.mapItems.length > 0) {
+
+                            if (trackToConsole) { console.log('GLM NearMe: Processing '+Object.keys(newPOIs.mapItems).length+' new POIs'); }                            
+
+                            $.each( newPOIs.mapItems, function(key, value) {
+
+                                var markerKey = value.type+'_'+value.id+'_'+value.lat+'_'+value.lon;
+                                
+                                // Create marker - add some other information we'll need for each marker
+                                value.position = new google.maps.LatLng(value.lat, value.lon);
+                                marker = new google.maps.Marker({
+                                    position:  value.position,
+                                    map:       map,
+                                    icon:      '{$assetsUrl}/MapIcons/' + assignedPushPins[value.type].pinFile,
+                                    type:      value.type,
+                                    visible:   assignedPushPins[value.type].pinOn,
+                                    id:        value.type+value.id
+                                })
+                                
+                                // Add a click listener for this marker
+                                marker.addListener('mouseover', function() {
+                                    
+                                        template = $('#nearMe-mouseover-template').html();
+                                        template = template.replace(/{ name }/g, value.name);
+                                        closeMapBubble();
+                                        closeMapTitle();
+                                        
+                                        mapTitle = new google.maps.InfoWindow({
+                                            content: template
+                                        });
+                                        
+                                        mapTitle.open(map, this);
+                                });
+                                
+                                marker.addListener('mouseout', function() {
+                                    mapTitle.close();
+                                });
+               
+                                // Add a click listener for this marker
+                                marker.addListener('click', function() {
+                             
+                                    
+                                    var infotext = '<h4 style="white-space: nowrap;">'+value.name+'</h4>';
+                                    var stateComma = '';
+                                    
+                                    if (value.short_descr) {
+                                        infotext += '<p>'+value.short_descr+'</p>';
+                                    }
+                                    if (value.loc_name) {
+                                        infotext += '<p>'+value.name+'</p>';
+                                    }
+                                    if (value.addr1) {
+                                        infotext += value.addr1+'<br>';
+                                    }
+                                    if (value.addr2) {
+                                        infotext += value.addr2+'<br>';
+                                    }
+                                    if (value.city) {
+                                        infotext += value.city;
+                                        stateComma = ', ';
+                                    }
+                                    if (value.state) {
+                                        infotext += stateComma+value.state;
+                                    }
+                                    if (value.zip) {
+                                        infotext += ' '+value.zip;
+                                    }
+                                    if (value.city+value.state+value.zip) {
+                                        infotext += '<br>';
+                                    }
+                                    if (value.phone) {
+                                        infotext += '<b>Phone: </b>'+value.phone+'<br>';
+                                    }
+                                    if (value.email) {
+                                        infotext += '<b>E-Mail: </b><a href="mailto:'+value.email+'">'+value.email+'</a><br>';
+                                    }
+                                    if (value.region) {
+                                        infotext += '<b>Region: </b>'+value.region+'<br>';
+                                    }
+                                    if (value.url) {
+                                        infotext += '<a href="'+value.url+'">Website<br>';
+                                    }
+                                    if (value.detail_page) {
+                                        infotext += '<a href="'+value.detail_page+'">More Information</a></<br>';
+                                    }
+                                    if (value.dates) {
+                                        infotext += '<b>Dates: </b>'+value.dates+'<br>';
+                                    }
+                                    if (value.times) {
+                                        infotext += '<b>Times: </b>'+value.times+'<br>';
+                                    }
+
+
+                                    if (doDetailSection) {
+                                        $('#nearMe-detail').html(infotext);
+                                    }
+                                    
+                                    if (doMapBubble) {
+    
+                                        // Close any other existing bubble or title.
+                                        closeMapBubble();
+                                        closeMapTitle();
+                                        
+                                        // Show a map info bubble for this marker
+                                        mapBubble = new google.maps.InfoWindow({
+                                            content: infotext
+                                        });
+                                        mapBubble.open(map, this);
+
+                                        // Check for new POIs in case the map moved when displaying the map bubble
+                                        checkNewPOIs();
+                                        
+                                    }
+                                    
+                                });
+
+                                // Save this marker to our markers array                                
+                                markers[marker.id] = marker;
+
+                                // Also if this marker type is turned on, add marker to Clusterer
+                                if (assignedPushPins[marker.type].pinOn) {
+                                    markerClusterer.addMarker(marker);
+                                }
+
+                            });
+                        }
+
+                        // If there's a request to find things anywhere
+                        if (request == 'anywhere' && Object.keys(markers).length) {
+                            
+                            allBounds  = new google.maps.LatLngBounds();
+                            
+                            // Find the total extent of all current markers.
+                            for (key in markers) {
+                                allBounds.extend(markers[key].getPosition());
+                            }
+                            
+                            map.fitBounds(allBounds);
+                            
+                            // Enforce minimum zoom
+                            var zoom = map.getZoom();
+                            if (zoom > maxAnywhereZoom) {
+                                map.setZoom(maxAnywhereZoom);                        
+                            }
+
+                            
+                        }
+
+                        // Remove the loading message
+                        hideLoadingMsg();
+                        
+                    }
+                );
+                
+                // Save current bounds as last bounds
+                lastLatMax = latMax;
+                lastLatMin = latMin;
+                lastLonMax = lonMax;
+                lastLonMin = lonMin;
+
+                // Save the current center position and zoom to push on stack before next move
+                curZoom = map.getZoom();
+                curCenter = map.getCenter();
+
+            }
+
+            // Handle tracking type selection
+            $('.glmMapMyLocationType').on('change', function() {
+                
+                trackingType = $(this).val();
+                switch (trackingType) {
+                    case 'track':
+                        setMyLocationToTrack();
+                        break;
+                    case 'set':
+                        setMyLocationToManual();
+                        break;
+                
+                }
+
+            });
+            
+            // Try HTML5 geolocation to get user's current location
+            function getUserLocation() {
+                
+                if (trackToConsole) { console.log('GLM NearMe: getUserLocation()'); }
+
+                if (!canReadUserLocation) {
+                    if (trackToConsole) { console.log('GLM NearMe: Aborting getUserLocation() - canReadUserLocation = false'); }
+                    return;    
+                }
+                
+                stopGeolocation = false;
+                clearGeolocationTimers();
+                
+                // Set timer to catch when user ignores postion request or selects Not Now or geolocation not available
+                getLocationIngoredTimer = window.setTimeout( function() {
+                    setMyLocationToManual();
+                    return;
+                }, postionTimeout);
+                
+                
+                // If we can get the user's location
+                if (navigator.geolocation) {
+                    
+                    if (trackToConsole) { console.log('GLM NearMe: Processing Geolocation'); }
+                    
+                    // Get the user's location
+                    navigator.geolocation.getCurrentPosition(function(position) {
+                        
+                        if (trackToConsole) { console.log('GLM NearMe: Have geolocation as '+position.coords.latitude+', '+position.coords.longitude); }
+                        
+                        if (stopGeolocation) {
+                            
+                            if (trackToConsole) { console.log('GLM NearMe: geolocation stopped'); }
+                            
+                            clearGeolocationTimers();
+                            return;
+                        }
+                        
+                        // Save their current location
+                        myCurrentLocation = {
+                              lat: position.coords.latitude,
+                              lng: position.coords.longitude
+                        };
+
+                        // Set return to this function at the defined interval
+                        trackingTimer = window.setTimeout(getUserLocation, trackPositionInterval);
+
+                        // Say that we are able to read the location and set things up to continue
+                        canReadUserLocation = true;
+                        window.clearTimeout(getLocationIngoredTimer);
+                        geolocationFailCounter = 0;
+                        
+                        // Set My Location to new lat/lon
+                        myMarker.setPosition(myCurrentLocation);
+                        
+                        // If current location is not on map, recenter map on location
+                        if (!map.getBounds().contains(myMarker.getPosition())) {
+                            map.setCenter(myCurrentLocation);    
+                            getBoundsPOIs();
+                        }
+        
+                    // If we can't get the location, then switch to manual
+                    }, 
+                    function(err) {
+
+                        if (trackToConsole) { console.log('GLM NearMe: Geolocation error = ' + err.code + ': ' + err.message); }                        
+
+                        if (stopGeolocation) {
+                            
+                            if (trackToConsole) { console.log('GLM NearMe: geolocation stopped'); }
+                            
+                            clearGeolocationTimers();
+                            return;
+                        }
+                        
+                        // Bump fail counter and check if we've reached the end of our patience.
+                        if (++geolocationFailCounter == geolocationFailCount) {
+                            
+                            if (trackToConsole) { console.log('GLM NearMe: Geolocation fail counter exceeded'); }
+                            
+                            setMyLocationToManual();    
+                        } else {
+                            
+                            if (trackToConsole) { console.log('GLM NearMe: Geolocation fail #'+(geolocationFailCounter-1)); }
+                            
+                            // Start the get location timer again                            window.clearTimeout(trackingTimer);
+                            trackingTimer = window.setTimeout(getUserLocation, trackPositionInterval);
+                        }
+                        
+                    }, 
+                    {
+                        enableHighAccuracy: highAccuracyPoisition,
+                        timeout:            postionTimeout,
+                        maximumAge:         trackPositionInterval - 100     // Allows cached position resulting from other requests during interval
+                        
+                    });
+                    
+                }
+            }
+
+            function setMyLocationToTrack() {
+                
+                stopGeolocation = false;
+                canReadUserLocation = true;
+                getUserLocation();
+                $('#glmMapSetMyLocation').addClass('glm-hidden');
+                
+            }
+
+            function clearGeolocationTimers() {
+                window.clearTimeout(trackingTimer);
+                window.clearTimeout(getLocationIngoredTimer);
+            }
+            
+            function setMyLocationToManual() {
+                
+                if (trackToConsole) { console.log('GLM NearMe: My Location set to manual'); }
+                
+                stopGeolocation = true;
+                clearGeolocationTimers();
+
+                // Dissable tracking and change tracking radio buttons to 'set'
+                canReadUserLocation = false;
+                trackingType = 'set';
+                $('#glmMyLocationSet').prop('checked', true); 
+
+                // Let user know that we're doing this.
+                alert('Your "My Location" setting has been changed to "Manual Map Selection".'+"\n\n"
+                       +'You either selected "Manual Map Selection" or your are doing a search with "Anywhere" selected.'+"\n\n"
+                       +'You may use the "Set My Location Here" button to select the center of the current map as your "My Location" and return to that location as desired.'+"\n\n"
+                       +'You may return to automatically tracking your location by selecting "Follow Me" at any time.');
+                
+                $('#glmMapSetMyLocation').removeClass('glm-hidden');
+
+            }
+
+            function showLoadingMsg() {
+                $('#glmMapReload').removeClass('glm-hidden');
+            }
+            function hideLoadingMsg() {
+                $('#glmMapReload').addClass('glm-hidden');
+            }
+            
+            function closeMapBubble() {
+                if (mapBubble) {
+                    mapBubble.close();
+                }
+            }
+            function closeMapTitle() {
+                if (mapTitle) {
+                    mapTitle.close();
+                }
+            }
+
+            // Get it all started
+            initMap();
+            
+        }); // jquery
+        
+        (function($) { $.fn.catchEnter = function(sel) {  
+            return this.each(function() { 
+                $(this).on('keyup',sel,function(e){
+                    if(e.keyCode == 13)
+                      $(this).trigger("enterkey");
+                })
+            });  
+        };
+        })(jQuery);
+        
+    </script>
+
+{include file='front/footer.html'}    
\ No newline at end of file