Resolving 'google is undefined' in Mango Server-Side Scripts

Tom Garrett9 min read
HMI / SCADAOther ManufacturerTroubleshooting
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

The map code fails with google is undefined because the google namespace does not exist in the browser when the returned inline script executes. The Maps API loader tag inside the server-side script's return string is never fetched and run before the map code that depends on it. The same markup pasted into a static HTML component renders correctly, so the map code is fine. The fault is where the code runs and the order in which it loads.

Execution context of the server-side script component

A server-side script component runs its body in the Mango server's JavaScript engine every time the bound point changes value. On the server it has the point value (value) and nothing else from the browser: no window, no document, and no way to load browser libraries. Its only product is a string. The view page receives that string through its periodic update mechanism and inserts it into the component's element.

That insertion step sets the timing. Browsers do not fetch or execute <script> elements inserted as HTML text. The Mango view page works around this for inline blocks by evaluating script content in component output. A minimal return of '<script type="text/javascript">alert('+ value +')</script>' fires correctly on the view page. That evaluation does not fetch an external src and wait for it to finish. The inline new google.maps.Map(...) runs at once, and the API is either never requested or arrives afterward.

The legacy loader URL used here, http://maps.google.com/maps/api/js?sensor=false, is a synchronous bootstrap. It pulls in the rest of the library with document.write, which works only while the page is still being parsed. When the tag is injected after page load, the bootstrap cannot complete even if the browser fetches it. This is why the loader must sit in content that exists when the page is first parsed, and why a static HTML component works.

Load-order limits and where to read them

One quantity decides this case: the value of typeof google at the instant the first map call executes in the browser. If it is 'undefined' at that instant, no fix to the map code helps. Read the related limits below before you change code.

Quantity Required state Where to read it
typeof google when map code runs 'object' Browser developer console on the view page
Maps loader request HTTP 200, requested during initial page parse Browser Network tab, filtered on maps
Helper .js file request HTTP 200, not 404 Network tab
Page scheme vs. loader scheme Same scheme (an http:// script on an https:// page is blocked as mixed content) Address bar and console warnings
Page type View page, not the view edit page Browser URL
Server-side script syntax No exception on evaluation Component output and the Mango log

Defects in the concatenated HTML string

Load order alone would stop the original script, but the return string also has defects that break it independently:

  • Unescaped quotes. Lines such as s +=" <meta http-equiv="content-type" ..." end the string literal at the second double quote, so the server engine hits a syntax error before any HTML is returned. Wrap the literal in single quotes, or escape each inner quote as \".
  • Malformed loader URL. src=" http://maps.google.com/maps/api/js?sensor=false\ " carries a leading space and a trailing backslash-space. Even if the tag loaded, the URL is not clean.
  • Nested document. Returning <!DOCTYPE html><html><head>...<body> inside a component's div is invalid. The browser discards the html, head, and body wrappers and keeps only fragments.
  • Array indexing. Marker positions must index the row, as in locations[i][1], locations[i][2], and locations[i][0].
  • Legacy options. navigationControl and NavigationControlStyle are v2-era options that the v3 API ignores. The sensor parameter is also legacy, and the current Maps JavaScript API requires an API key. Check Google's current loader documentation before you deploy.

Four ways to get GPS point values onto the map

The HTML component is not bound to a point, so it cannot receive live values. The server-side script is bound to a point, but it cannot load the library. Every workable design splits the work between the two.

Approach Bound to point value API loaded during page parse Observed result Verdict
Whole page in a static HTML component No Yes Map and markers render Static coordinates only
Whole page returned from a server-side script Yes No google is undefined Cannot work
Loader tag in an HTML component, full map build in a server-side script Yes Yes, if the HTML component is defined first Still google is undefined in this installation. The map is rebuilt on every value change. Fragile, depends on order
Helper functions in an external .js file loaded by the HTML component, with the server-side script returning a one-line call Yes Yes Helper call pattern (addBold test) confirmed on the view page in Mango 1.13.7. It errors on the edit page. Recommended
JSP view Yes, via page code Yes Suggested as better suited; not tested Fallback for complex maps

The helper-file pattern wins on two points. First, the map object is created once and survives every refresh, so each point update only moves a marker. Second, the server-side script output shrinks to a function call with numbers, which removes the quoting problem entirely.

Procedure for the helper-file map driven by server-side scripts

  1. Add an HTML component to the graphical view before any server-side script component. Give it the map container, the Maps loader, and the helper file, with no html, head, or body wrappers:
    <div id="map" style="width: 600px; height: 550px;"></div>
    <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
    <script type="text/javascript" src="/gpsmap.js"></script>
    Match the loader scheme to the scheme Mango is served on. Replace the loader URL with the current keyed form from Google's documentation.
  2. Add one server-side script component per GPS point, bound to that point. Return only a call to the helper:
    var p = String(value).split(',');
    var lat = parseFloat(p[0]), lng = parseFloat(p[1]);
    if (isNaN(lat) || isNaN(lng)) return '';
    return '<script type="text/javascript">gpsUpdate("position1",' + lat + ',' + lng + ');</script>';
    Use a distinct id string for each point (position1, position2, and so on) so that each component drives its own marker.
  3. Save the view and open it from the view page, not the edit page. The edit page does not evaluate component scripts the way the view page does, so helper calls there report the function as not found.

Encoding the GPS position in an HTTP Retriever alphanumeric point

Store latitude and longitude together in a single alphanumeric point, written for example as httpds?__device=gpslocA&__point=position1&__value=40.123456,52.123456. This keeps both coordinates in one sample with one timestamp. Two separate numeric points for latitude and longitude avoid string parsing, but they update independently. A server-side script bound to one of them then renders a marker from a new latitude and a stale longitude until the second update arrives.

The comma-separated string has to be parsed in the server-side script, so validate it there, before anything reaches the browser:

  • Reject values that parse to NaN. The script above returns an empty string in that case.
  • Reject latitude outside ±90 and longitude outside ±180. A swapped field order usually fails the latitude check first.
  • Keep the decimal point as the separator. A locale that writes decimals with commas collides with the field delimiter.

Symptoms versus causes for a blank view or undefined google

Symptom Cause Check / fix
google is undefined, whole page returned from the server-side script Loader tag in injected content is never loaded before the inline code runs Move the loader into the HTML component
google is undefined with the loader in the HTML component Script component renders before the HTML component, or the loader is injected after page parse Reorder components. Confirm in the Network tab that the loader loads on first page load. Add the retry guard.
Helper function not found, edit page only Edit page does not evaluate component scripts Test on the view page
Blank view, no console errors Helper file returns 404 (placed under WEB-INF or referenced by a relative path), map div missing or zero height, or empty script output Network tab status for the .js file. Inspect the #map element size. Log value.
No component output and a script error in Mango Unescaped double quotes in the return string Use single-quoted literals
Loader blocked in console http:// loader on an https:// Mango page Match the schemes

These are all logic and load-order faults, not server load problems. If the view renders once and then stops updating, look at the point's data source polling and value history before you look at the page.

Confirming live marker updates on the view page

  1. Open the view page and run typeof google and typeof gpsUpdate in the console. You should see 'object' and 'function'.
  2. Check that the map tile layer renders inside the 600 × 550 px container and that one marker appears per bound point.
  3. Push a new value through the HTTP Retriever URL with changed coordinates. The marker should move on the next view refresh without the map resetting zoom or center. A reset means the map is being rebuilt, which means the full map build is still inside the server-side script.
  4. Compare the marker position with the point's latest value and timestamp in the Mango point details. They should match to the precision entered.
  5. Send a malformed value, such as a single number. The marker should hold its last position and the console should stay clean.

FAQ

How do I load the Google Maps API in a Mango graphical view?

Put the loader <script src=...> tag in an HTML component that is defined before any server-side script component, so that it loads during the initial page parse. A loader tag inside a server-side script's returned string never loads in time.

How do I pass a Mango point value into JavaScript on the view page?

Bind a server-side script component to the point and return an inline block, such as '<script type="text/javascript">gpsUpdate("position1",' + lat + ',' + lng + ');</script>'. The view page evaluates the inline script each time the value changes.

How do I choose where to put a custom .js file for a Mango view?

Place it in webapps/ROOT and reference it with an absolute path such as /gpsmap.js, or host it on a CDN. Files under WEB-INF are never served to the browser and return 404.

How do I fix 'function not defined' errors on the Mango view edit page?

Test on the view page instead. The edit page does not evaluate component scripts the way the view page does, so helper calls fail there even when they work correctly on the view page.

How do I know when to escalate a Mango map view problem to support?

Escalate when typeof google and typeof gpsUpdate both return correctly on the view page and the helper file loads with HTTP 200, but server-side script output still never executes. At that point, contact Mango's official support channel with your exact Mango version, the component order, and the script bodies. If the map needs more than per-point marker updates, ask them about building it as a JSP view instead.

Back to blog