The places subsystem
Introduction
During the Covid-19 lockdown of spring 2020 I reworked the entire place and other gazetteer pages. Previously the pages were autogenerated when needed, although there was some half-hearted serving of pages stored in /html/cached_gazetteer. There was a complicated system of caching for the data used to create the pages.
In the new system, there is maintained a set of complete HTML pages for all areas, features, places and waterways. These are served whenever needed. User preferences are managed by manipulating the pages using Javascript.
Note that if any significant changes are made to the plugins it will be necessary to delete all generated files for these to have any effect.
File structure
Just about everything is held in the "pages" directory at the top level. Below this is divided into:
- code - scripts and other files needed to create the page files
- generated - the generated HTML files. This is divided into 4 appropriate sub-directories for each type of page plus a "config" directory to hold a master page_plugins.hdf file which is generated from all the plugins by the file "prepare_plugins.can" in the script directory.
Under code are the following major directories:
- plugins - see below
- script - this contains programs to produce the generated page files and to produce the master configuration file
- templates - the .cst files used to make the framework of the generated pages
Scripts
There are four scripts - generate_xxx_html.can which make the four types of pages.
Plugins
These provide blocks of information for the ready-to-serve place, waterway, area and feature pages. The entire main-content part of the page is generated from plugins. Each plugin is used for any subset of the pages, depending on its configuration.
To create a plugin:
- add a directory with an appropriate name in /plugins/pages, for example /plugins/pages/nearest. This should be a single alphanumeric word - it is used as an identifier in code. Create the following files within the directory:
- a config.hdf file
- a code.can file which will be run when generating the page
- a template.cst file which generates the page
- other template files can be here as well if you want to break it down
- a javascript.js file which contains functions activated by users clicking on items in the plugin output, and any customisation code for the plugin (to adjust the output based on user options)
- other javascript files can be present here to be loaded dynamically.
each of these is further documented below.
code.can
This file should contain a function Plugin that takes two lookup parameters. The first is the parsed config.hdf file and the second is placeinfo for the place being built.
If it calls any sub functions or procedures, it should use the name of the plugin as a namespace - for example nearest$do_stuff
template.cst
This should access the plugin data and configuration using data.plugins.plugin-name.data and data.plugins.plugin-name.config
To call a javascript function from the generated HTML, do it using Page_Call_Plugin_JS('plugin-name','function name,parameters) This will load the plugin javascript file if needed and call the function.
javascript.js
Contains all JavaScript code in support of the plugin. Most functions should be called using Page_Call_Plugin_JS in response to user activity. All functions should be placed in an object derived namespace named after plugin_plugin-name - this means that a function definition looks like, for example plugin_photos.options_display = function() {
Can contains a function called personalise
This function will be called at page load if conditions met in config.hdf are met. This is to reconfigure the plugin display to user preferences (hiding some items for example).
Can contain a function called options_display
If the config for this plugin contains the "need_configuring" flag in the options block, then the JavaScript file will be loaded and this function called whenever the plugin is selected and the options relating to it displayed. It will also be called when any option containing the "has_dependencies" flag is altered. It should consist of a series of tests of option value that turn on and off, or set defaults, for other options.
config.hdf
All files should contain:
- rank – a number that has no direct meaning, but plugins are output ordered by the rank. Initially set up with power of two distances apart to maximise the space for new additions
- title – to be displayed in the "collapse" banner and when setting options
- text – explanation to be displayed when selecting in options
- showon – a list of which pages (area/feature/place/waterway) this plugin can be displayed on - all present as "yes" or "no"
- default_state - ("on" or "off"): should this be shown by default, or only when the user asks for it?
- an options block - this is documented below
- a "dynamic" block if the plugin has extra information to show to an active user (current stoppages, or whether this place has been visited for example) - see below
- any parameters specific to a plugin and used only in that code/template
config.hdf - the options block
This block defines all options that are shown on the preference page, and captured in sessions and user records.
- _needs_configuring - if "yes" then the display of some options are dependent on others. This triggers the loading and executing of plugin JavaScript to configure. The _ ensures the name doesn't clash with a future plugin
- a group called options - this contains a list of items, each defining an option
Each option should be named by the value that will be used elsewhere in the code. So, for example, a block called "photomode" in the "photos" plugin will be displayed on the preferences page as pageopt_option_photos_photomode, and will be stored in the options variable as pages.photos.photomode. The block of code for setting it on the preferences page will be called page_options_plugin_option and this is useful for showing/hiding individual options.
Because of this, option names must not contain underscores and must be valid identifiers otherwise (alphabetic strings recommended).
An option can contain the following fields:
- type - "select" or "checkbox" at the moment
- has_dependencies - this item controls the display of other items (see the JavaScript documentation for how to use this). If _needs_configuring is set at the options level, then at least one option must have dependencies. If any options have dependencies, then _needs_configuring must be set.
- hidden - do not display by default (so will be turned on when needed)
- default - must be present, the default value for the option
- label - the label text for the option
Option types has specific fields
- select:
- byindex - if present and "yes" then the value stored for the selector is the index, not the text
- values - a 0,1,2,3... listed set of values for the selector if not "byindex"
- values - a list of pairs of indices and texts for the selector
- checkbox:
- label - the label for the checkbox
A "comment" block will have each line printed at the bottom of the options page. This is useful to indicate, say, things that are controlled by other plugins (such as the layout of geograph photos)
Generic options
There are generic options that are shown for all plugins with an options menu. This makes it easier than calling up the main preferences screen. They are stored in the options variable as pages.
Dynamic information
To add dynamic information to a plugin:
- add a "dynamic" section to the config file, which contains "datacalled = [variable]"
- if the plugin template needs format options (say for dates) then set "formats = yes" inside the dynamic section
- add a file "dynamic_code.can" to the plugin directory. This should contain a function "plugin" which takes the same parameters as a normal plugin function. The data it returns will end up in data.[variable] as defined above
- add a file "dynamic_template.cst" which will render that data correctly
- ensure the normal template includes plugin_helpers.cst
- add a call to the normal template.cst call:dynamic_text(plu) where you want that text embedded.
