FoxInCloud supports Google Map in both desktop and Web mode, with a high level of progam and/or user interaction. This post shows how, with very simple code, your application can provide a great map user experience on any device: desktop, browser on desktop or hand-held device.

(installed with V 2.21.1 scheduled 2016-05-01).

1- Add an Internet Explorer activeX object to the form

  ADD OBJECT oleIE AS ficOLEIE WITH ; && ficOLEIE as awOLEIE of aw.vcx
    Top = 5, ;
    Left = 10, ;
    Height = 315, ;
    Width = 745, ;
    ZOrderSet = 11, ;
    Anchor = 15, ;
    Name = "oleIE"

  PROCEDURE oleIE.Init
    && 2016-04-20 thn -- {en} desktop mode: to use Google Maps in this control, you need to set Internet Explorer Compatibility:
    && 2016-04-20 thn -- {fr} mode desktop : pour utiliser Google Maps dans ce contrôle, vous devez régler la compatibilité Internet Explorer :

    && https://weblog.west-wind.com/posts/2011/May/21/Web-Browser-Control-Specifying-the-IE-Version
  ENDPROC

2- Implement a Google Map in Web mode

When generating HTML, this objects instructs the HTML/CSS/JS generator to add JavaScript that creates a Google Map in Web mode:

  PROCEDURE oleIE.wcHTMLgen
    LPARAMETERS toHTMLgen AS awHTMLgen OF awHTML.prg, tlInnerHTML && {en} doc in Parent Code {fr} doc dans le code parent

    local cScript as String

    && {en} The following JavaScript:
    && {en} - instantiates a Google Maps API object and stores a reference in a property added to the form's HTML element
    && {en} - implements the 'center_changed' event that fires when user moves the map using the mouse:
    && {en}   event handler writes the new lattitude and longitude to the spinners in the page
    && {en} notes:
    && {en} - $() is a shortcut to document.getElementById()
    && {en} - cLitteralJS() (modify command abTxt) turns any VFP value into a JavaScript literal
    && {en} - [test ? valueTrue : valueFalse] is the JavaScript equivalent for Iif()

    && {fr} Ce JavaScript :
    && {fr} - instancie un objet Google Maps API et stocke sa référence dans une propriété ajoutée à l'élément HTML du formulaire
    && {fr} - implémente l'événement 'center_changed' déclenché lorsque l'utilisateur déplace la carte avec la souris
    && {fr}   ce code écrit les nouvelles latitude et longitude dans les toupies présentes sur la page HTML
    && {fr} notes :
    && {fr} - $() est un raccourci (alias) de document.getElementById()
    && {fr} - cLitteralJS() (modify command abTxt) convertit toute valeur VFP en un littéral JavaScript
    && {fr} - [test ? valueTrue : valueFalse] est l'équivalent JavaScript de Iif()

    && https://developers.google.com/maps/documentation/javascript/reference

    text to cScript noshow textmerge flags 1 pretext 15
      $('<<m.thisForm.wcID>>').googleMap = new google.maps.Map(
        $('<<m.this.wcID>>')
      , {
          center: {lat: <<cLitteralJS(thisForm.spnLat.Value)>>, lng: <<cLitteralJS(thisForm.spnLng.Value)>>}
        , zoom: 8
        }
      );
      $('<<m.thisForm.wcID>>').googleMap.addListener('center_changed', function(){
        var googleMap = $('<<m.thisForm.wcID>>').googleMap
        , LatLng = googleMap.getCenter()
        , zoom = googleMap.getZoom()
        , dec = zoom > 12
          ? 5
          : (zoom > 10
            ? 4
            : (zoom > 8
              ? 3
              : (zoom > 6
                ? 2
                : (zoom > 4
                  ? 1
                  : 0
                )
              )
            )
          )
        ;
        $('<<thisForm.spnLat.wcID>>').valueSet(LatLng.lat().toFixed(dec) + '°');
        $('<<thisForm.spnLng.wcID>>').valueSet(LatLng.lng().toFixed(dec) + '°');
      });
    endtext

    m.toHTMLgen.cScriptJSadd(m.cScript)
  ENDPROC

3- Load the Google Map JavaScript resource

Of course, we need to instruct the server, namely the process object, to generate a HTML instruction that loads the Google Map JavaScript whenever necessary:

* -------------------------------------------
PROTECTED FUNCTION cawJSinc && of xxxProcess
LPARAMETERS ;
  tcJSAdd; && [''] {en} Application, current form[, and custom] {fr} JS files URLs (UTF-8 encoded) {fr} URL des fichiers JS de l'application et du formulaire courant (encodés en UTF-8)
, toForm as awFrm of aw.vcx; && {en} Reference to form && {fr} Référence au formulaire && 2016-04-20 thn -- {en} {FiC V 2.21.1-beta.0} added paramter
, tcForm; && {en} .Name of form {fr} Nom du formulaire && 2016-04-20 thn -- {en} {FiC V 2.21.1-beta.0} added parameter

local result

result = DoDefault(m.tcJSAdd)

if InList(Lower(m.tcForm), Lower('index.scx'), Lower('googleMap.scx'))
  result = '<script src="https://maps.googleapis.com/maps/api/js"></script>' + m.result
endif

return m.result
endfunc

4- Add control over the map location

We also add 2 spinners and a commandButton to control where the map is centered:

  ADD OBJECT spnLat AS ficSpn WITH ; && ficSpn as awSpn of aw.vcx
    Anchor = 12, ;
    Height = 24, ;
    InputMask = "999.99°", ;
    KeyboardHighValue = 180, ;
    KeyboardLowValue = -180, ;
    Left = 680, ;
    SpinnerHighValue = 180.00, ;
    SpinnerLowValue = -180.00, ;
    Top = 345, ;
    Width = 75, ;
    ZOrderSet = 6, ;
    Value = 48.79, ; && initial map lattitude
    Name = "spnLat"

  ADD OBJECT spnLng AS ficSpn WITH ; && ficSpn as awSpn of aw.vcx
    Anchor = 12, ;
    Height = 24, ;
    InputMask = "999.99°", ;
    KeyboardHighValue = 180, ;
    KeyboardLowValue = -180, ;
    Left = 680, ;
    SpinnerHighValue = 180.00, ;
    SpinnerLowValue = -180.00, ;
    Top = 398, ;
    Width = 75, ;
    ZOrderSet = 8, ;
    Value = 2.21, ; && initial map longitude
    Name = "spnLng"

  ADD OBJECT cmdSet AS ficCmd WITH ; && ficCmd as awCmd of aw.vcx
    Top = 430, ;
    Left = 680, ;
    Height = 24, ;
    Width = 75, ;
    Anchor = 12, ;
    Caption = "Set", ;
    ZOrderSet = 10, ;
    Name = "cmdSet"

As usual, the .click() method of the button is implemented for both desktop and web mode, all code (VFP and JavaScript) in the same method:

  PROCEDURE cmdSet.Click
    if thisForm.wlHTMLgen && FoxInCloud adaptation
      return
    endif

    local oAJAX as awAJAX of awServer.prg;
    , cURL as String;

    * ==============================
    if wlAJAX(@m.oAJAX) && Web mode
    * ==============================

      && tutoServer.prg > tutoProcess.cawJSinc() {en} loads the Google Map Script {fr} charge le script Google Map
      && https://developers.google.com/maps/documentation/javascript/reference

      && {en} act upon Google Maps API object stored in form HTML element
      && {en} notes:
      && {en} - FoxInCloud Application Server executes this code when user clicks this button in the browser
      && {en} - oAJAX is a reference to the FoxInCloud AJAX object processing the request
      && {en} - oAJAX.cScriptJSadd_() adds some JavaScript instruction to the AJAX response
      && {en} - $() is a shortcut to document.getElementById()
      && {en} - cLitteralJS() (modify command abTxt) turns any VFP value into a JavaScript literal

      && {fr} agir sur l'objet Google Maps API dont l'élément HTML du formulaire garde une référence
      && {fr} notes :
      && {fr} - Le serveur d'application FoxInCloud exécute ce code lorsque l'utilisateur clique le bouton dans le navigateur
      && {fr} - oAJAX est une référence à l'Objet AJAX FoxInCloud traitant la requête
      && {fr} - oAJAX.cScriptJSadd_() ajoute des instructions JavaScript à la réponse AJAX
      && {fr} - $() est un raccourci (alias) de document.getElementById()
      && {fr} - cLitteralJS() (modify command abTxt) convertit toute valeur VFP en un littéral JavaScript

      m.oAJAX.cScriptJSadd_(Textmerge([$('<<wcID(thisForm)>>').googleMap.setCenter({lat:<<cLitteralJS(Cast(m.thisForm.spnLat.Value as N(6,2)))>>, lng:<<cLitteralJS(Cast(thisForm.spnLng.Value as N(6,2)))>>});]))

    * ==============================
    else && LAN / desktop mode
    * ==============================

      && {en} change URL on Internet Explorer activeX
      && {fr} mettre à jour l'URL de l'Internet Explorer activeX

      cURL = Evl(thisForm.oleIE.object.LocationURL, 'https://www.google.com/maps/@48.8000,2.2000,10z')
      cURL = GetWordNum(m.cURL, 1, '@');
        + '@' + cL(thisForm.spnLat.Value); && {en} Cast() depends on Set("Point"), cL() works around this {fr} Cast() dépend de Set("Point"), cL() s'en affranchit
        + ',' + cL(thisForm.spnLng.Value); && modify command abTxt
        + ',' + GetWordNum(GetWordNum(m.cURL, 2, '@'), 3, ',');
        + ''

      thisForm.oleIE.object.navigate2(m.cURL) && <==

    endif
  ENDPROC

5- Initialize map location at form.Init()

We make sure that form inits to some location (default or parameter)

  PROCEDURE Init
    lparameters lat, lng

    DoDefault()

    if thisForm.wlInitFirst && FoxInCloud adaptation
      return
    endif

    if Pcount() > 0
      this.spnLat.Value = Cast(m.lat as N(4,1))
      this.spnLng.Value = Cast(m.lng as N(4,1))
    endif

    this.cmdSet.Click

6- Update spinners with the current map location

Whenever user moves the map using the mouse, the lattitude and longitude spinners reflect where map is currently centered

  PROCEDURE oleIE.NavigateComplete2
    *** ActiveX Control Event ***
    LPARAMETERS pdisp, url

    && {en} fires in desktop mode only
    && {fr} ne se déclenche qu'en mode desktop

    url = GetWordNum(m.url, 2, '@') && eg 'https://www.google.com/maps/@48.8000,2.2000,10z' > '48.8000,2.2000,10z'

    thisform.spnLat.Value = Cast(GetWordNum(m.url, 1, ',') as N(6,2))
    thisform.spnLng.Value = Cast(GetWordNum(m.url, 2, ',') as N(6,2))

  PROCEDURE oleIE.wcHTMLgen
    LPARAMETERS toHTMLgen AS awHTMLgen OF awHTML.prg, tlInnerHTML && {en} doc in Parent Code {fr} doc dans le code parent

    text to cScript noshow textmerge flags 1 pretext 15 && for Web mode
      $('<<m.thisForm.wcID>>').googleMap.addListener('center_changed', function(){
        var googleMap = $('<<m.thisForm.wcID>>').googleMap
        , LatLng = googleMap.getCenter()
        , zoom = googleMap.getZoom()
        , dec = zoom > 12
          ? 5
          : (zoom > 10
            ? 4
            : (zoom > 8
              ? 3
              : (zoom > 6
                ? 2
                : (zoom > 4
                  ? 1
                  : 0
                )
              )
            )
          )
        ;
        $('<<thisForm.spnLat.wcID>>').valueSet(LatLng.lat().toFixed(dec) + '°');
        $('<<thisForm.spnLng.wcID>>').valueSet(LatLng.lng().toFixed(dec) + '°');
      });
    endtext

    m.toHTMLgen.cScriptJSadd(m.cScript)
  ENDPROC

7- Test!

On-line test