Technical description of the Home Page

This is a block-by-block description of the home page script. If you need further information about specific code lines you can consult the documentation at EasyCoder (though the documentation at some times lags developments in the language).

All of the scripts for Here On The Map are kept in the EasyCoder repository on GitHub. This is not the usual way to manage a website but it allows anyone to examine our scripts, which will often have been updated since these notes were written. It makes the point well that EasyCoder scripts are just text files; you can keep them anywhere, load them at any time and run them on demand.

The loaded HTML is a boot page that defines a DIV for the EasyCoder tracer, which is only needed for debugging scripts. Then it defines the DIV that will contain the entire page. Finally there is a short boot script that gets the home page script from GitHub and runs it.

<div id="easycoder-tracer"></div>
<div id="ec-document"></div>
<pre id="easycoder-script">!
  variable Script
  rest get Script from `https://raw.githubusercontent.com/gtanyware/EasyCoder/master/`
    cat `scripts/hereonthemap/hereonthemap.easycoder` or
  begin
    alert `Failed to load the main script from GitHub. Please try again.` cat newline cat newline
      cat `The reported error message was:` cat newline cat the error
  end
  run Script
</pre>

 

The home page script starts by naming itself and declaring all the variables it will use. The name of the script is used during debugging and when error messages occur, as a guide to which script was running at the time.

Each variable is of a specific type. Some correspond exactly to DOM types and others are general-purpose variables that can hold a numeric or text value.

  script HereOnTheMap

  div Document
  div MapDiv
  div ViewingDiv
  div StoryDiv
  span ZoomSpan
  gmap Map
  marker Marker
  module UserModule
  module StoryModule
  a ViewAllLink
  variable Args
  variable RequestedID
  variable Revisit
  variable LoggedIn
  variable Script
  variable Latitude
  variable Longitude
  variable Zoom
  variable Type
  variable Position
  variable Title
  variable Email
  variable Story
  variable Tag
  variable Message
  variable Request
  variable Select
  variable SelectedName
  variable SelectedTag
  variable SelectedEmail
  variable Record
  variable Bounds
  variable Markers
  variable Counter
  variable Counting
  variable Index
  variable NMarkers
  variable Minus
  variable N
  variable State
  variable ID
  variable URL

 

The first action is to deal with a request for a specific pin, rather than just for the Home page. A specific URL is of the form

https://hereonthemap.com/?46

and may be followed by an SEO-friendly string, but we don't need that here. The parser gives us the part after the ? as a JSON object with 3 properties; 'protocol', 'domain' and 'args'. Here we are only interested in the last of these; the first part of it is the id of the pin so we save it in its own variable.

  json parse url the location as Args
  put property `arg` of Args into Request
  if Request is not empty
  begin
    put the position of `-` in Request into N
    if N is greater than 0
    begin
      put left N of Request into Request
    end
    put Request into RequestedID
  end

 

Next we check this is the first time the user has visited the site. We use browser storage to hold a flag that is set the first time the script is run on this browser. On the first visit only, the user is requested to choose whether to show the main About page or to continue with the Home page.

  get Revisit from storage as `revisit`
  if not Revisit
  begin
    put `yes` into storage as `revisit`
    if confirm
      `If you are new to this website you may find it best ` cat newline
      cat `to start by visiting our 'How to use' page.` cat newline
      cat ` If you would like to do that now, click OK; ` cat newline
      cat `otherwise click Cancel.` cat newline cat newline
      cat `You can go to the 'How to use' page at any time.`
    location `https://dev.hereonthemap.com/about`
  end

 

The current latitude and longitude, zoom and type (roadmap or satellite) are also held in browser local storage, allowing the page to resume at the location you last left it.

The script clears the boolean value LoggedIn. Variables do not have default values; they are undefined and must be assigned before they can be read.

The value Minus relates to how map pins stay visible as you zoom in and out.

  get Latitude from storage as `latitude`
  get Longitude from storage as `longitude`
  get Zoom from storage as `zoom`
  get Type from storage as `type`

  clear LoggedIn
  put 2 into Minus

 

If this is the first visit to the page these variables will be empty, so put in default values.

  if Latitude is empty
  begin
    put `0.0` into Latitude
    put Latitude into storage as `latitude`
  end
  if Longitude is empty
  begin
    put `0.0` into Longitude
    put Longitude into storage as `longitude`
  end
  if Zoom is empty
  begin
    put 2 into Zoom
    put Zoom into storage as `zoom`
  end
  if Type is empty
  begin
    put `roadmap` into Type
    put Type into storage as `type`
  end

 

The HTML for the page is generated by the script(s) and all hangs on a single empty element declared at the top of the boot page. Here we attach a "div" script variable to the element by giving its CSS id.

  attach Document to `ec-document`

 

The next part sets up the map. A DIV is created as a child of Document  and we give it inline style information. You are free to use CSS scripts or inline styles, as you prefer, but the former will require you to manually set a 'class' or 'id' attribute on the element in question. Next, the map itself is created. This is a special variable type that embeds all the Google Maps functionality. We set its API key; this is obtained by creating an account at Google and permits a large amount of map usage before any charges apply. We then set the position, zoom and type from the values we read earlier (or the default values). Finally, we make the map visible.

! Set up the map
  create MapDiv in Document
  set the style of MapDiv to `width:100vw;height:40vh`
  create Map in MapDiv
  set the key of Map to `AIzaSyDTWe5hoygkgkO96XRsEbrgi1Daty1uJvQ`
  set the latitude of Map to Latitude
  set the longitude of Map to Longitude
  set the zoom of Map to Zoom
  set the type of Map to Type
  show Map

 

The next part sets up action listeners on the map, to detect clicks, movement (dragging), zoom and selection of display type.

  on click Map
  begin
    put the click position of Map into Position
    if LoggedIn go to NewMarker
  end

  on move Map
  begin
    put the latitude of Map into storage as `latitude`
    put the longitude of Map into storage as `longitude`
    go to ReselectMarkers
  end

  on zoom Map
  begin
    put the zoom of Map into Zoom
    put Zoom into storage as `zoom`
    if ZoomSpan is defined
    begin
      set the content of ZoomSpan to `Z:` cat Zoom
      go to ReselectMarkers
    end
  end

  on type Map
  begin
    put the type of Map into storage as `type`
  end

 

Under the map is a single line of text that provides information about what the map is showing. Since we don't yet have anything to put in it we give it a display style of 'none' to keep it hidden.

Then we 'prime' the browser history. History management is a tricky area to understand so I'm not going to try to explain it. If you really want to know what's happening you'll have to get the EasyCoder plugin 'browser.js' from GitHub, look for anything to do with 'history' and compare it with whatever official documentation you can find. We use the property 'id' to identify which pin record is being accessed (zero being none of them) and 'script' to name the script that must be called when a browser 'back' event is fired. Beyond that it's all magic and witchcraft.

  create ViewingDiv in Document
  set the style of ViewingDiv to `display:none;margin-top:0.5em`
  history set state `{"id":0,"script":"HereOnTheMap"}`

 

The user management and story display features are each handled by supplementary scripts. As these are just text files they can be kept in a number of places. You can embed them in your page if you like, typically inside PRE blocks or even a regular DIV. A better place is in your own database, adding a table for them to the ones already used by WordPress. We keep the Here On The Map scripts on GitHub, in the EasyCoder repository; this is particularly convenient because they are then subject to version control and can easily be synchronized to our development computer and edited using our favorite code editor (Visual Studio). Or they can be edited directly on GitHub if you prefer.

We start by adding a DIV in which to put everything else below the map, then go and fetch the user script. Then we run the script and save its identity as UserModule. Nothing will be visible at this point; when child scripts run it's usual for them to initialise then stop and wait for a trigger to do their job.

The same thing is then done for the module that manages display and editing of the stories attached to the pins.

When a script is run you have the option of exporting some of your variables to it. These variables, which are marked as imports in the child script, are then shared by both modules, which can each read and write them. There are no in-built protections; as the programmer you are expected to know what you are doing.

! Set up the user and the story
  create StoryDiv in Document
  rest get Script from `https://raw.githubusercontent.com/gtanyware/EasyCoder/master/`
    cat `scripts/hereonthemap/users.easycoder` or
  begin
    alert `Failed to load the user script. Please try again.` cat newline cat newline
      cat `The reported error message was:` cat newline cat the error
  end
  run Script with StoryDiv as UserModule
  rest get Script from `https://raw.githubusercontent.com/gtanyware/EasyCoder/master/`
    cat `scripts/hereonthemap/stories.easycoder` or
  begin
    alert `Failed to load the stories script. Please try again.` cat newline cat newline
      cat `The reported error message was:` cat newline cat the error
  end
  run Script with StoryDiv and Record and Email as StoryModule

 

Scripts are able to communicate with each other by passing messages. It's often best - though not essential - to format these messages as JSON. Here, the primary functions are to refresh the map, to deal with login and logout and to detect when the user wants to select which pins appear on the map.

  on message
  begin
    put the message into Message
    put property `request` of Message into Request
    if Request is `refresh` go to ScanMarkers
    else if Request is `login`
    begin
      put property `email` of Message into Email
      attach ZoomSpan to property `zoom` of Message
      set LoggedIn
      go to ScanMarkers
    end
    else if Request is `logout`
    begin
      put `` into Email
      attach ZoomSpan to property `zoom` of Message
      send `hide` to StoryModule
      clear LoggedIn
      go to ScanMarkers
    end
    else if Request is `select`
    begin
      put property `select` of Message into Select
      if Select is `author`
      begin
        put property `author` of Message into SelectedName
        put property `email` of Message into SelectedEmail
        put `` into SelectedTag
        go to ScanMarkers
      end
      else if Select is `tag`
      begin
        put `` into SelectedName
        put `` into SelectedEmail
        put property `tag` of Message into SelectedTag
        go to ScanMarkers
      end
    end
  end

 

If a pin id was given in the URL of the request we now need to deal with it, by moving and zooming the map to center the pin, then we show the story for the pin. Note the use of 'fork', which sets off 'ShowStory' and then continues program execution at the next line. Programmers will be correct in assuming this is not proper multitasking, as JavaScript only has a single thread, but EasyCoder can make it look like more than one thing is happening at once.

  if RequestedID
  begin
    rest get Record from `_/ec_markers/id/` cat RequestedID or
      print `Failed to load the requested pin.  Error:` cat the error
    set the latitude of Map to property `latitude` of Record
    set the longitude of Map to property `longitude` of Record
    set the zoom of Map to property `zoom` of Record
    update Map
    fork to ShowStory
  end

 

The user module needs to run at startup, so here we trigger it just the once. It runs independently of the rest of the code, waiting for the user to log in or out. The story module will be triggered to refresh its data every time the user clicks on a pin.

  trigger UserModule

 

The next block may look intimidating but in fact it's mostly quite simple. Rather than explain it line by line I'll just pick out the more important points.

When the map is moved or zoomed we need to refresh the pins, showing only those within the bounds of the map. This can't really be done until the user stops moving or zooming so the code here waits for this to happen by looking for a period of inactivity. It then builds a JSON package based on the map bounds plus any other relevant information, and send this to the server for it to search the relevant database tables. Any existing pins (markers) on the map are removed and the script builds a new list, creating them on the map as it goes.

I need to comment here about arrays. In EasyCoder, every variable is an array and has a single element unless explicitly given more, using the 'set the elements' command you can see in this block. Each variable has an internal 'index' pointer that can be set to point to any given position in the array, and the variable is treated at all times as if it just has that one element. This technique avoids needing symbols to denote arrays. There is another feature (not used here) that lets you 'alias' a variable to any element of another variable. Just so you know.

Part of this block sets up the contents of the ViewingDiv that was created earlier. It does it in 2 stages; firstly it sets the content to a string, overwriting anything it previously held, then it creates a hyperlink in the same DIV, which has the effect of appending, not overwriting.

Also here is code to handle the provision of an SEO-friendly URL, by pushing a new value containing the title of the pin onto the browser history stack.

! Look for markers when the map moves or zooms
ScanMarkers:
  put 0 into Counter
  set Counting

LookForMarkers:
  while Counting
  begin
LookForMarkers2:
    if Counter is 0
    begin
      put the bounds of Map into Bounds
      if Bounds
      begin
        remove markers from Map
        set property `zoom` of Bounds to the zoom of Map
        set property `minus` of Bounds to Minus
        set property `mymail` of Bounds to Email
        if SelectedEmail
        begin
          set property `email` of Bounds to SelectedEmail
          set the content of ViewingDiv to `Viewing pins by "`
            cat SelectedName cat `"`
          create ViewAllLink in ViewingDiv
          set the style of ViewAllLink to `margin-left:1em`
          set the content of ViewAllLink to `View All`
          set attribute `title` of ViewAllLink to `View all pins`
          on click ViewAllLink
          begin
            put `` into SelectedName
            put `` into SelectedEmail
            go to ScanMarkers
          end
          set style `display` of ViewingDiv to `block`
        end
        else if SelectedTag
        begin
          set property `tag` of Bounds to SelectedTag
          set the content of ViewingDiv to `Viewing pins with tag "`
            cat SelectedTag cat `"`
          create ViewAllLink in ViewingDiv
          set the style of ViewAllLink to `margin-left:1em`
          set the content of ViewAllLink to `View All`
          set attribute `title` of ViewAllLink to `View all pins`
          on click ViewAllLink
          begin
            put `` into SelectedTag
            go to ScanMarkers
          end
          set style `display` of ViewingDiv to `block`
        end
        else set style `display` of ViewingDiv to `none`
        rest get Markers from `_/ec_markers/get/` cat Bounds or
        begin
          print `Failed to load the markers.  Error:` cat the error
          go to LookForMarkers2
        end
        put the json count of Markers into NMarkers
        set the elements of Record to NMarkers
        set the elements of Marker to NMarkers
        put 0 into Index
        while Index is less than NMarkers
        begin
          index Record to Index
          index Marker to Index
          put element Index of Markers into Record
          create Marker in Map
          put `` into Position
          set property `latitude` of Position to
            property `latitude` of Record
          set property `longitude` of Position to
            property `longitude` of Record
          set the position of Marker to Position
          set the title of Marker to property `title` of Record
          if property `email` of Record is Email
            set the color of Marker to `#00ff00`
          else set the color of Marker to `#ffdd00`
          add 1 to Index
        end
        clear Counting
        on click Marker
        begin
          put `` into SelectedName
          put `` into SelectedTag
          put `` into SelectedEmail
          index Record to the index of Marker
          go to ShowStory
        end
      end
      else wait 10 ticks
    end
    else
    begin
      take 1 from Counter
      wait 10 ticks
    end
  end
  stop

 

The next section handles 'restore' events, that occur when the browser 'back' button is clicked. Because many users use 'Back' as a primary means of navigation we make sure the browser retraces its steps through whichever pins have been clicked until it arrives back at an id of zero, which is the home page. It's fairly complex because of the need to retrigger the Story script for the event, giving it the correct record so it can show the viewer or editor as appropriate.

  on restore
  begin
    put the history state into State
    put property `id` of State into ID
    if ID
    begin
      put 0 into Index
      while Index is less than NMarkers
      begin
        index Record to Index
        if property `id` of Record is ID
        begin
          put `` into State
          set property `id` of State to ID
          set property `script` of State to `HereOnTheMap`
          put `?` cat ID cat `-` cat property `title` of Record into URL
          set property `url` of State to URL
          history set url URL state State
          trigger StoryModule
          stop
        end
        add 1 to Index
      end
    end
    send `hide` to StoryModule
  end
  stop

 

Here's a block that sets up a record for a pin, then triggers the story module to display it.

ShowStory:
  put property `id` of Record into ID
  put property `title` of Record into Title
  replace ` ` with `-` in Title
  replace `,` with `` in Title
  replace `;` with `` in Title
  put `` into State
  set property `id` of State to ID
  set property `script` of State to `HereOnTheMap`
  put `?` cat ID cat `-` cat Title into URL
  set property `url` of State to URL
  history push url URL state State
  trigger StoryModule
  stop

 

This block is called repeatedly while the map is being dragged or zoomed. It prevents the loop in the marker search above from timing out, ensuring that the map only redraws once all user activity has ceased. This avoids a succession of unnecessary calls to the server from being made.

ReselectMarkers:
  put 10 into Counter
  if not Counting
  begin
    set Counting
    go to LookForMarkers
  end
  stop

 

The final part of the script creates a new marker when a registered user clicks the map. As before, a JSON packet is built that holds all the needed information, then it's sent to the server.

Variables hold JSON data as plain strings, parsing and stringifying them as needed to perform setting and getting of properties. If you use the 'print' or 'alert' commands to display such a variable it will be shown with its contents pretty-printed since this is an action taken primarily for debugging purposes.

NewMarker:
  put prompt `Please supply a title for your new marker` with `title` into Title
  if Title is not empty
  begin
    create Marker in Map
    set the position of Marker to Position
    set the title of Marker to Title
    set the zoom of Map to Zoom
    put `` into Record
    set property `email` of Record to Email
    set property `latitude` of Record to property `latitude` of Position
    set property `longitude` of Record to property `longitude` of Position
    set property `zoom` of Record to Zoom
    set property `title` of Record to Title
    set property `story` of Record to ``
    rest post Record to `_/ec_markers/set` or
    begin
      alert `Failed to create a new pin. Please try again.` cat newline cat newline
        cat `The reported error message was:` cat newline cat the error
    end
    go to ScanMarkers
  end
  stop