Technical description of the Story Page
The Story page handles the story viewer and the editor, the latter being provided by a JavaScript library called CKEditor. There are many editor libraries but this one is among the most comprehensive, and is free.
The Story module is an EasyCoder script kept in a table in the database. When the page loads, a call is made to the REST server to fetch the Story script, which is then compiled and initialized, ready for use. Every time the script receives a "trigger" it displays either the story viewer or the editor and manages interactions with each of them.
The script starts by naming itself. Then it names the variables it expects to import from the calling module, and then declares all the other variables it will need locally.
script Stories import div ParentDiv and variable Record and variable MyEmail div ContainerDiv div EditorDiv div TextFieldsDiv div TitleDiv div TagsDiv div StoryDiv div AuthorDiv div ButtonsDiv h3 TitleHeader label TitleLabel label TagsLabel label Label input TitleInput input TagsInput span Status span Text img DeleteIcon img FolderIcon img SaveIcon img ViewIcon a EditorLink a AuthorLink a TagLink a Link module FileManager variable ID variable Author variable Email variable Latitude variable Longitude variable Zoom variable Title variable Tags variable Story variable TextBoxWidth variable Script variable FileManagerRunning variable Message variable Record2 variable SavedTitle variable SavedTags variable SavedStory variable TagCount variable N variable Flag
The main container for this part of the page is created, giving the named element as its parent. It's initially set to be invisible, with a border, a margin and some padding.
create ContainerDiv in ParentDiv set the style of ContainerDiv to `display:none;border:1px solid lightgray;margin-top:0.5em;padding:0.5em`
The script will only ever be sent one type of message, so there's no need for it to be JSON-formatted. The message tells the script to hide the main container and all its contents.
on message begin if the message is `hide` begin set style `display` of ContainerDiv to `none` end end
That's all the initialization done, so the script waits for a trigger and signals that it's ready. Scripts block while they are loading, to avoid multiple requests interfering with each other, so since JavaScript is single-threaded they should aim to load as quickly as possible. Fortunately, scripts only have to be compiled once per session. For the Story script this takes typically 50ms or so the first time the user clicks a pin on the map; not enough for most people to notice. The 'set ready' command signals that both the load and the compilation are complete and the parent script may continue.
on trigger go to ShowStory set ready stop
The imported record is a JSON structure containing everything about the pin except its tags and the story itself. The script extracts all the fields into local variables, then retrieves the remaining information with a separate REST call.
The reason it's done this way is that when tags are returned from a search there may be up to 20 of them, this being the maximum number the map will display at one time to avoid crowding. Since the user probably won't click all of them before moving the map there's no sense in retrieving the bulky story and the relatively time-consuming tag search for all 20; instead, this is done here when a specific pin has been selected.
The tags arrive as a JSON array; at the end f this block this array is turned into a comma-delimited list suitable for display.
ShowStory: put property `id` of Record into ID put property `email` of Record into Email put property `latitude` of Record into Latitude put property `longitude` of Record into Longitude put property `zoom` of Record into Zoom put decode property `title` of Record into Title rest get Record2 from `_/ec_markers/story/` cat ID or begin alert `The data for this pin failed to load. Please try again.` cat newline cat newline cat `The reported error message was:` cat newline cat the error end put property `author` of Record2 into Author put property `tags` of Record2 into Tags put decode property `story` of Record2 into Story put Title into SavedTitle put Story into SavedStory put the json count of Tags into TagCount put `` into SavedTags clear Flag put 0 into N while N is less than TagCount begin if Flag put SavedTags cat `,` into SavedTags put SavedTags cat element N of Tags into SavedTags set Flag add 1 to N end
The main container is now made visible...
set style `display` of ContainerDiv to `block`
... and a test is made to see if this pin belongs to the current user. If it does, control is transferred to the editor; otherwise it continues with the viewer.
if MyEmail is Email go to Editor
The viewer starts by clearing the container in case it may be holding a previous pin story. It then checks the ownership of the pin (because the viewer can be called from the editor) and if necessary sets up a hyperlink to return to the editor.
View: clear ContainerDiv if MyEmail is Email begin create EditorLink in ContainerDiv set the style of EditorLink to `float:right` set the content of EditorLink to `Go to Editor` on click EditorLink go to Editor end
Above every story is a line displaying the name of the story author, which can be clicked to display pins from just that author. When the name is clicked a JSON message is built and sent to the parent (the home page) for action.
create TitleHeader in ContainerDiv set the style of TitleHeader to `color:blue` set the content of TitleHeader to Title create AuthorDiv in ContainerDiv set the style of AuthorDiv to `font-size:80%` set the content of AuthorDiv to `Author: ` create AuthorLink in AuthorDiv set the style of AuthorLink to `font-size:1em` set the content of AuthorLink to Author set attribute `title` of AuthorLink to `Show only pins from this author` on click AuthorLink begin put `{}` into Message json set property `request` of Message to `select` json set property `select` of Message to `author` json set property `author` of Message to Author json set property `email` of Message to Email send Message to parent end
After the author, another line displays all the tags for this pin. These are also clickable to select all pins with a given tag. This code is similar to that above for the author, except there can be any number of tags so a loop is needed. Since we are creating a clickable link for each tag we set the TagLink variable to have the right number of elements before we start to iterate the tags. The expression 'element N of Tags' extracts a single value from a JSON array; this is then set as the content of the corresponding element in the TagLink (array) variable.
You will see that although TagLink is iterated through all its elements there's only 1 'on click'. This is because when an event is set on a variable it is set on all the array elements at once. The index for the array will be set to the clicked element, so the correct action is performed.
put the json count of Tags into TagCount if TagCount is greater than 0 begin create TagsDiv in ContainerDiv set the style of TagsDiv to `margin-bottom:0.5em;font-size:80%` set the content of TagsDiv to `Tags: ` set the elements of TagLink to TagCount clear Flag put 0 into N while N is less than TagCount begin index TagLink to N if Flag begin create Text in TagsDiv set the content of Text to `, ` end create TagLink in TagsDiv set the style of TagLink to `font-size:1em` set the content of TagLink to element N of Tags set attribute `title` of TagLink to `Show only pins with this tag` set Flag add 1 to N end on click TagLink begin put `{}` into Message json set property `request` of Message to `select` json set property `select` of Message to `tag` json set property `tag` of Message to the content of TagLink json set property `email` of Message to Email send Message to parent end end
After all that it's very simple to set up the story itself. We create a new DIV for it and set its content to whatever returned from the database; a block of HTML markup. And that's done with the viewer.
create StoryDiv in ContainerDiv set the content of StoryDiv to Story stop
The editor is a little more complicated. As with the viewer, we clear the container, then we create an Editor Div to hold the content, followed by a ButtonsDiv to hold the 4 graphic buttons that float to the right at the top of the editor.
Part of that button group is a status label we use to indicate that a save operation was successful. This is created first, and followed by the Delete, Folder, Save and View icons. It should be pretty easy to see how this works, as the code is just mirroring what happens in HTML. One note; you might ask why each icon is embedded in a link, even though it's the icon that is clickable. The answer is that unless this is done the cursor remains as a pointer when you hover over the button. Wrapping it in a link makes the hand cursor icon appear on hover.
All the buttons except View transfer to a script label when clicked; View is done inline. (There's no particular reason for this.) View retrieves the values of each of the alterable fields and checks if any have changed since the last save. If so it pops up a warning and refuses to go to the viewer.
One more field is added, showing the zoom value for the pin, that is, the zoom at the time it was created. Zoom values range from 0 (the whole planet) to 22 (close enough to see what people are wearing).
Editor: clear ContainerDiv create EditorDiv in ContainerDiv create ButtonsDiv in EditorDiv set the style of ButtonsDiv to `float:right;text-align:right` create Status in ButtonsDiv set the style of Status to `color:green;padding-right:1em;margin-bottom:1em` create Link in ButtonsDiv create DeleteIcon in Link set the style of DeleteIcon to `width:25px;margin-right:10px` set attribute `src` of DeleteIcon to `http://hotm.easycoder.software/resources/system/delete.png` set attribute `title` of DeleteIcon to `Delete this pin` on click DeleteIcon go to Delete create FolderIcon in Link set the style of FolderIcon to `width:25px;margin-right:10px` set attribute `src` of FolderIcon to `http://hotm.easycoder.software/resources/system/media.png` set attribute `title` of FolderIcon to `Image manager` on click FolderIcon go to FileMan create Link in ButtonsDiv create SaveIcon in Link set the style of SaveIcon to `width:25px;margin-right:10px` set attribute `src` of SaveIcon to `http://hotm.easycoder.software/resources/system/save.png` set attribute `title` of SaveIcon to `Save changes` on click SaveIcon go to Save create Link in ButtonsDiv create ViewIcon in Link set the style of ViewIcon to `width:25px` set attribute `src` of ViewIcon to `http://hotm.easycoder.software/resources/system/binoculars.png` set attribute `title` of ViewIcon to `View article` on click ViewIcon begin put the text of TitleInput into Title put the text of TagsInput into Tags replace `'` with `` in Tags replace `"` with `` in Tags ckeditor get Story from StoryDiv if Title is not SavedTitle go to NotSaved if Tags is not SavedTags go to NotSaved if Story is not SavedStory go to NotSaved go to View end create Label in ButtonsDiv set the content of Label to `Z:` cat Zoom
Next we create the editable fields above the main story editor. These should need no explanation as they just describe HTML fields.
create TextFieldsDiv in EditorDiv put 40 into TextBoxWidth create TitleDiv in TextFieldsDiv set the style of TitleDiv to `display:flex;height:1.5em;margin:0.2em 0 0.5em 0` create TitleLabel in TitleDiv set the style of TitleLabel to `width:7em;padding-top:0.2em` set the content of TitleLabel to `Title:` create TitleInput in TitleDiv set the size of TitleInput to TextBoxWidth set the content of TitleInput to Title create TagsDiv in TextFieldsDiv set the style of TagsDiv to `display:flex;height:1.5em;margin-bottom:0.5em` create TagsLabel in TagsDiv set the style of TagsLabel to `width:7em;padding-top:0.2em` set the content of TagsLabel to `Tags:` create TagsInput in TagsDiv set the size of TagsInput to TextBoxWidth put the json count of Tags into TagCount set the content of TagsInput to SavedTags
And finally the story itself; a DIV with a CKEditor component attached. For an unknown reason it's best to wait before we actualy load the story itself into the editor; half a second seems to do the job most of the time. A 'tick' is 10ms.
create StoryDiv in ContainerDiv ckeditor attach to StoryDiv wait 50 ticks ckeditor set StoryDiv to Story stop
Here we throw up a warning when an action is taken without saving the data:
NotSaved: alert `The data is not saved.` stop
This is where the File Manager gets loaded. Its script is retrieved from the database and then run, after checking that this hasn't already been done.
FileMan: if not FileManagerRunning begin rest get Script from `ec_scripts/name/fileman` or begin alert `Failed to load the file manager script. Please try again.` cat newline cat newline cat `The reported error message was:` cat newline cat the error end run Script as FileManager set FileManagerRunning end
The next part deals with the browser 'Back' button, which some users click even if there's an explicit Close button on the page. The code isn't complex but it may not be obvious what is happening.
The File Manager runs as a new entry in the browser history, but doesn't rewrite the page URL so you don't see any change. When the user clicks the 'Close' button in the File Manager, its script invokes 'browser back', which causes a 'restore' event to arrive here. The File Manager has no way of telling this has happened because the user may have clicked the browser 'Back' button, which has the same effect.
If you don't use code of this kind, then although the Close button may work fine, when the user clicks Back they will leave this web page completely. Not what is intended in most cases.
The 'on restore' command traps the browser 'back' event so we can tell the File Manager to close itself:
on restore close FileManager
In order for the above mechanism to work we have to 'set' the browser history before triggering the File Manager. That tells the browser where to return to when the user leaves the File Manager.
history set trigger FileManager stop
To save our data we build a JSON data object with all the necessary information and send it to the server. Then we copy the current field values into the ones used to detect when the document has changed.
Save: put the text of TitleInput into Title put the text of TagsInput into Tags replace `'` with `` in Tags replace `"` with `` in Tags ckeditor get Story from StoryDiv put `{}` into Record json set property `id` of Record to ID json set property `title` of Record to encode Title json set property `tags` of Record to lowercase encode Tags json set property `story` of Record to encode Story rest post Record to `_/ec_markers/update/` cat ID or begin alert `The save failed. Please try again.` cat newline cat newline cat `The reported error message was:` cat newline cat the error end
put Title into SavedTitle put Tags into SavedTags put Story into SavedStory set the content of Status to `Story saved` wait 3 set the content of Status to `` stop
Finally, we can delete a pin, after confirming it's what the user really wanted.
Delete: if confirm `Please confirm you want to delete marker '` cat property `title` of Record cat `'` begin put `{}` into Record json set property `id` of Record to ID json set property `email` of Record to Email rest post Record to `_/ec_markers/delete` or begin alert `The delete failed. Please try again.` cat newline cat newline cat `The reported error message was:` cat newline cat the error end clear ContainerDiv put `{}` into Message json set property `request` of Message to `refresh` send Message to parent end stop