# Advanced Google Map Widget

> Embedded Google Map with 8 map types (basic, multiple marker, static, polyline, polygon, overlay, with routes, panorama) and 2 theme groups (Google standard 5 themes + Snazzy Maps 9 themes).

**Class file:** `includes/Elements/Google_Map.php` (1,611 lines)
**Slug:** `adv-google-map` (widget id `eael-adv-google-map`) ⚠ slug uses `adv-` not `advanced-`
**Public docs:** <https://essential-addons.com/elementor/docs/google-map/>
**Pro-shared:** Pro-only. **External integration: Google Maps JS API**.

## Overview

Embeds a Google Map driven by the Maps JavaScript API. User configures the API key once in EA settings (`eael_save_google_map_api` option), then can place multiple Google Map widgets on a page — each gets its own map type, markers, route, and theme. Themes come from `includes/Elements/advance-gmap-themes.php` data file (14 presets across 2 groups). Heaviest widget asset-wise — Maps JS is loaded once per page via `Enqueue::before_enqueue_scripts`, not via `config.php` deps.

## Pro vs Lite

Pro-only. No Lite counterpart.

## File Map

| File | Role |
| --- | --- |
| `includes/Elements/Google_Map.php` | Widget class — controls + render |
| `includes/Elements/advance-gmap-themes.php` | **Data file** — returns 14 JSON theme styles (gstandard: 5, snazzymaps: 9) |
| `src/css/view/adv-google-map.scss` → `assets/front-end/css/view/adv-google-map.min.css` | Layout / pin / info-window styling |
| `src/js/view/adv-google-map.js` → `assets/front-end/js/view/adv-google-map.min.js` | Map init using `google.maps.*` API |
| `assets/front-end/js/lib-view/gmap/gmap.min.js` | gmap utility wrapper (lib) |
| `includes/Traits/Enqueue.php` `before_enqueue_scripts()` | **Runtime enqueue** of `https://maps.googleapis.com/maps/api/js?key=<KEY>&callback=Function.prototype` |
| `config.php` entry `'adv-google-map'` | self CSS + lib JS (gmap) + self JS — but NOT the Maps JS itself |

## Architecture

- **API key is site-wide, not per-widget** — stored in WP option `eael_save_google_map_api` (set in EA settings page, not in widget controls). Pro reads it at runtime in `Enqueue::before_enqueue_scripts` and concatenates into the Maps JS URL.
- **Runtime enqueue gated on widget presence** — `Enqueue::before_enqueue_scripts` checks `in_array('adv-google-map', $widgets) && '' != get_option('eael_save_google_map_api')` before enqueuing Maps JS. No widget on page → no Maps JS. No API key set → no Maps JS (widget will fail silently in editor).
- **8 map types in one widget** — bar / line / area pattern; render branches on `eael_google_map_type` value. Each type has its own set of controls (markers for `marker`, route waypoints for `routes`, polygon coordinates for `polygon`, etc.).
- **Themes are data, not controls** — themes live in `advance-gmap-themes.php` and are loaded as JSON into the map options. Two theme groups: `gstandard` (Google's official 5: standard / silver / retro / dark / night / aubergine) and `snazzymaps` (9 community themes: simple / colorful / complex / dark / greyscale / light / monochrome / nolabels / twotone).
- **API key concatenated into URL with no `urlencode`** in `Enqueue.php:11`. API keys are alphanumeric so practical risk is low; tighten when touched.
- **`callback=Function.prototype` parameter** is a no-op callback — used so Maps JS doesn't throw, even though Pro's init runs through standard Elementor `frontend/element_ready` lifecycle instead of the callback.

## Render Output

```html
<div class="eael-google-map-wrap">
  <div class="eael-google-map"
       id="eael-google-map-{element-id}"
       data-map-type="basic|marker|static|polyline|polygon|overlay|routes|panorama"
       data-map-options='{ ...JSON... }'
       data-map-zoom="14"
       data-map-lat="..."
       data-map-lng="..."
       data-map-theme="silver">
  </div>
</div>
```

The `data-map-options` payload is built server-side from settings, including the theme JSON pulled from `advance-gmap-themes.php`. JS reads the attributes and calls `new google.maps.Map(container, options)`.

## Controls Reference

| Control id | Tab → Section | Type | Purpose |
| --- | --- | --- | --- |
| `eael_google_map_type` | Content → Map | SELECT | `basic` / `marker` / `static` / `polyline` / `polygon` / `overlay` / `routes` / `panorama` |
| `eael_google_map_address_type` | Content → Location | CHOOSE | `address` (string) / `coordinates` (lat,lng) |
| `eael_google_map_address` | Content → Location | TEXT | Address string (when type = address) |
| `eael_google_map_latitude` / `_longitude` | Content → Location | NUMBER | Coords (when type = coordinates) |
| `eael_google_map_zoom` | Content → Map | SLIDER | Initial zoom (1–22) |
| `eael_google_map_theme_group` | Style → Theme | SELECT | `gstandard` / `snazzymaps` |
| `eael_google_map_theme` | Style → Theme | SELECT | Theme within the selected group |
| `eael_google_map_markers` | Content → Markers | REPEATER | Marker rows (when type = marker) |
| `eael_google_map_route_origin` / `_destination` / `_waypoints` | Content → Route | various | Route configuration (when type = routes) |
| `eael_google_map_height` | Style → General | SLIDER | Map height (CSS) |

## Conditional Dependencies

```text
eael_google_map_type = 'marker'
  └── shows eael_google_map_markers repeater
  └── hides route / polygon / polyline / overlay controls

eael_google_map_type = 'routes'
  └── shows route origin, destination, waypoints, travel mode controls

eael_google_map_type = 'polygon' | 'polyline'
  └── shows coordinates repeater + fill/stroke style controls

eael_google_map_type = 'panorama'
  └── shows pano-specific heading/pitch controls

eael_google_map_address_type = 'coordinates'
  └── shows lat/lng inputs, hides address input
```

## JavaScript Lifecycle

`src/js/view/adv-google-map.js`:

```js
var GoogleMapHandler = function( $scope, $ ) {
    var $map = $scope.find( '.eael-google-map' );
    if ( ! $map.length ) return;
    if ( typeof google === 'undefined' || ! google.maps ) {
        console.warn( 'EA Google Map: Google Maps API not loaded. Check the API key.' );
        return;
    }

    var type = $map.data( 'map-type' );
    var options = JSON.parse( $map.data( 'map-options' ) );
    var map = new google.maps.Map( $map[0], options );

    // Type-specific init: add markers, draw route, etc.
};

jQuery( window ).on( 'elementor/frontend/init', function() {
    if ( eael.elementStatusCheck( 'eaelGoogleMapLoad' ) ) return false;
    elementorFrontend.hooks.addAction(
        'frontend/element_ready/eael-adv-google-map.default',
        GoogleMapHandler
    );
} );
```

## Hooks & Filters

### Elementor hooks consumed

Standard widget render. No extension-style hook registration.

### Pro / EA hooks emitted

None.

## Common Issues

| Symptom | Likely cause | Diagnose | Fix |
| --- | --- | --- | --- |
| Map shows "For development purposes only" overlay | Google API key not authorized for production / billing not enabled | Network: check the `maps/api/js` response | Enable billing on Google Cloud project; restrict API key to the site's referrer |
| Map area is blank | No API key set in EA settings | Verify `eael_save_google_map_api` option non-empty | Set the API key in EA settings |
| `google is not defined` console error | Maps JS not enqueued | DevTools Sources: `maps.googleapis.com` script present? | Verify EA settings has the API key AND the page actually has this widget |
| Multiple maps on a page break | Maps JS loaded once but multiple init calls race | Inspect console for `InvalidValueError` | Ensure `eael.elementStatusCheck` guard is in place — see `_patterns.md` |
| Theme not applying | Theme JSON in `advance-gmap-themes.php` is malformed | Check JSON validity | Theme strings are JSON-encoded inside PHP strings — quotes can escape badly when editing the data file |
| Editor preview shows map but blank on frontend | `is_edit_mode` branch in JS, OR Asset_Builder missing the page | Verify `Asset_Builder` detected this widget on the page | If widget is inside a template / popup / shortcode, see `asset-loading.md` § Asset_Builder detection limits |

## Known Limitations

- **API key is site-wide** — can't have per-page or per-environment keys via the widget
- **API key in URL on every page-load** — visible in HTML source. Google's referrer-restriction is the only mitigation
- **No `urlencode` on the API key concatenation** — tight when touched (API keys are alphanumeric so practical risk is low)
- **Maps JS is heavy (~120KB)** — every page with a Google Map widget pays the cost; no lazy-load
- **No fallback when Maps JS fails** — widget renders an empty div with no message to the visitor
- **`advance-gmap-themes.php` theme JSON is inline strings, not loaded from JSON files** — editing requires careful escape of inner quotes
- **No Snazzy Maps catalogue refresh** — themes are frozen at the version bundled in `advance-gmap-themes.php`. New Snazzy themes aren't auto-pulled.
- **Geocoding usage** for address-based maps is metered by Google — high-traffic sites with address-based maps may hit quota

## Cross-References

- Architecture: [`docs/architecture/external-api-integrations.md`](../architecture/external-api-integrations.md) — Maps JS / Snazzy data
- Architecture: [`docs/architecture/asset-loading.md`](../architecture/asset-loading.md) — runtime enqueue pattern
- Shared patterns: [`_patterns.md`](_patterns.md)
- Rule: [`.claude/rules/external-api-integrations.md`](../../.claude/rules/external-api-integrations.md)
