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
http://hotm.easycoder.software/?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. Although not essential, it's best 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