How to build a Web App

Part 3 - Implementing Stories

In Part 1 of this series I described how to set up the environment for programming web apps in WordPress and in Part 2 we built an anagram finder library in JavaScript. Now we'll put everything together as a web app.

There'a fundamental question to ask when building a web app, one that applies in many other areas of programming, and that's "Which tools do I use?". Browsers are rather unusual in that there's only one programming language they understand - JavaScript.  So the first option is to use JavaScript with your favorite framework (or with none at all).

There are other tools available, mainly ones that take code written in other languages (such as Python) and translate them into JavaScript. Clever though this is, the problem I can see with it is that in the browser you're debugging code that wasn't written by humans, so it will inevitably be hard to read. But if you like it, go with it.

My favored option is to use a higher-level scripting language which is itself written in JavaScript. These are so far rare, though this may well change as language writers come to realize how powerful JavaScript is, powerful enough to allow a more complex layer to exist on top of it and still give good performance. The only such language I know of is EasyCoder, so that's the one I'll pick. It makes the code much easier to follow and doesn't exclude people who have a hard time with JavaScript.

EasyCoder is a scripting plugin for WordPress. The EasyCoder website has plenty of examples of how to use the standard product to build interactive content, but some things (such as finding anagrams) are too complex for scripts to handle and this is where JavaScript and React skills come into play. Basically you create self-contained components and give them a well-defined API, as we've done with the anagram finder, then write an EasyCoder plugin that extends the language to include special commands and values that make use of the API. That's what we'll do.

Web App Stories

Let's start by defining the web app we want to create. We'll keep it simple otherwise the principles will get lost in the detail. The anagram finder, implemented as a general-purpose JavaScript library component, takes a line of text and looks for sets of dictionary words that use the same letters. At the level of the web app we're not concerned how it works; what we want to do is implement the user stories that specify how this web app appears to its users.

In a previous article I wrote about Story Driven Design. Stories are written by customers who want a website built. They are not themselves coding experts but they know what they want and how to describe it. Here we have a pretty simple web app so the stories should be simple too. Here's what they look like:

"The page presents a text field in which to type a line of text, a button labeled Run and another labeled Clear."

"When the app starts, the text field holds the text typed into it the last time it was used (if this is not the first time)."

"When the user clicks Run the program calls the anagram finder to look for dictionary words that together use up all the letters in the text given. The label of the button changes to Stop."

"When Stop is clicked, the search stops. The button label changes to Continue."

"When Continue is clicked, the search resumes."

"When Clear is clicked the list of results is cleared."

"The page displays the list of anagrams as they are found, in a panel below the text and buttons, sorted alphabetically."

and so on.

Stories of this kind form the basis of a contract between the customer and the programmer and represent something the delivered product can be tested against. The stories themselves are rarely cast in stone; they frequently undergo changes in the light of experience and feedback from users. By contrast, the algorithms embedded in the anagram finder module are likely to be fixed for all time. So it makes little sense to combine both these items into the same coding structure.

EasyCoder is designed to express stories in code that visibly corresponds to the original stories and that can be read by the domain expert that wrote them. When changes are needed it's much easier to do it at this level than when it's buried in a huge mass of JavaScript and React code that only a skilled programmer can read.

The EasyCoder script

Because we're building the app as a WordPress page we don't have access to the root of the DOM tree (the <body>), so to provide somewhere for the app to live we need to add a <div> to the page, as follows:

<div id="anagrams"></div>

The EasyCoder script also lives in the page,  in a special <pre> block:

<pre id="easycoder-script">
...
</pre>

When the page has loaded, the EasyCoder plugin looks for the <pre> block, compiles its contents and runs them.

The stories tell us we need an input box for the user to type some text. We'll also have some buttons and a panel to hold the anagrams as they arrive. Here's the complete script:

  require `http://cors.io/?https://raw.githubusercontent.com/gtanyware/EasyCoder/master/demo/words.js`
  require `http://cors.io/?https://raw.githubusercontent.com/gtanyware/EasyCoder/master/demo/anagrams.js`

  h2 Title
  div Root
  div InputDiv
  div ResultsDiv
  div ResultDiv
  div Label
  div Padding
  input Text
  button RunButton
  button ClearButton
  variable Anagrams
  variable Line
  variable Words
  variable Index
  variable Running
  variable Results
  variable Keys

  attach Root to `anagrams`
  if mobile set the style of Root to `width:100%`
  else set the style of Root to `width:1024px;margin:1em auto`

  create Title in Root
  set the style of Title to `text-align:center`
  set the content of Title to `Anagram Finder`

  create InputDiv in Root
  if mobile set the style of InputDiv to `display:flex;margin: 0.5em`
  else set the style of InputDiv to `display:flex;margin-top:1em`

  create Padding in InputDiv
  set the style of Padding to `flex:2`

  create Text in InputDiv
  set the style of Text to `flex:76`
  get Line from storage as `anagram-text`
  set the text of Text to Line

  create Padding in InputDiv
  set the style of Padding to `flex:2`

  create RunButton in InputDiv
  set the style of RunButton to `flex:10`
  set the text of RunButton to `Run`
  on click RunButton go to Run

  create Padding in InputDiv
  set the style of Padding to `flex:2`

  create ClearButton in InputDiv
  set the style of ClearButton to `flex:10`
  set the text of ClearButton to `Clear`
  on click ClearButton
  begin
    clear ResultsDiv
    clear Results
  end

  create Label in Root
  set the style of Label to `margin: 0.5em 0.5em 0 0.5em`
  set the content of Label to `No anagrams found (yet):`

  create ResultsDiv in Root
  set the style of ResultsDiv to `margin: 0.5em 0.5em 0 0.5em`

  stop

Run:
  json set Results to object
Continue:
  put Text into storage as `anagram-text`
  set the text of RunButton to `Stop`
  on click RunButton go to Stop
  set Running
  while Running
  begin
    put anagrams of Text into Anagrams
    if property `status` of Anagrams is `found`
    begin
      put property `words` of Anagrams into Words
      json sort Words
      put empty into Line
      put 0 into Index
      while Index is less than the json count of Words
      begin
        put Line cat element Index of Words cat ` ` into Line
        add 1 to Index
      end
      if property Line of Results is empty
      begin
        set property Line of Results to true
        put the json keys of Results into Keys
        json sort Keys
        set the content of Label to the json count of Keys cat ` anagrams found:`
        clear ResultsDiv
        put 0 into Index
        while Index is less than the json count of Keys
        begin
          create ResultDiv in ResultsDiv
          set the content of ResultDiv to element Index of Keys
          add 1 to Index
        end
      end
    end
    wait 2 ticks
  end
  stop

Stop:
  clear Running
  set the text of RunButton to `Continue`
  on click RunButton go to Continue

Now for how it works. Right at the top we have a couple of require lines. These load JavaScript files and insert them into the HEAD of the document. One is the anagram finder component we built in Part 2 of this series; the other is a dictionary - a list of words. It looks like this:

>const EasyCoder_words = [
`aah`,
`aardvark`,
`abacus`,
`abacuses`,
`abalone`,
...

and continues for another 33,000 or so lines. If you substitute a list of French, German or Spanish words it will work just as well but in the chosen language.

Then there follows a list of variables. Many of these are types that correspond to DOM elements; the rest are plain variables to hold numbers or text.

We start by attaching the Root variable to the <div> we set up earlier. From now on, anything we do with or to Root will act on the div itself. We  then test if the app is running in a smartphone browser, and apply suitable styling. As you can see, styles can be applied inline. Purists who object to this way of working are free to set a class attribute on each element and create a separate stylesheet, but it's far simpler for scripted elements to have styles set as they are created.

There then follows a set of commands that set up the display, item by item. In EasyCoder, when we create DOM elements we have to specify a parent element that already exists. I've put the components of the display into their own <div> and called it InputDiv. All the rest go straight into Root. I'm hoping that even readers who are not programmers will find it easy to follow - that's the way it's intended to be. At the end the program stops and wait for something to happen.

The InputDiv uses a flex display. I find it helps when distributing components to use percentages that add up to 100. There also a line in there that takes a value from storage an puts it into the text field. This is using browser storage, that allows you to persist data between visits to a site.

Along the way there was an on click command to detect when the user clicks the Run button. Control then transfers to Run:; the colon signifies a program label.

When the click occurs, frst there the rather odd-looking command `json set Results to object`. This just initializes the variable to be a JavaScript object (not an array).

The script repurposes the Run button so we can use it to stop the run, then enters a while loop, where there's the line

`put anagrams of Text into Anagrams`

Here we have a problem, Houston. This syntax is unknown to EasyCoder so we're going to need a plugin to handle it. I'll explain how that's done shortly, but for now I'll carry on describing the code.

When we ask the anagram finder to look for anagrams it comes back with a list of words and a success/fail flag. We're only interested where the search succeeded, so if it failed the the whole of the rest of the code is skipped and we ask it to try again. It's most important that we put a short delay in, though, otherwise the computer will surely overheat and lock up, both at the same time. This is highly undesirable and sometimes the only cure is to hit the reset button. The delay here is 20ms, long enough for it to cool a little.

When the search succeeds we take the list of words, sort them into alphabetical order and put them all on one line with a space between each one. Then we check the Resultsarray to see if the phrase we have was already there. If not, we add it to the array, sort that and write it to the results panel in the display, with a line at the top saying how many anagrams were found.

You'll see the word json occurring quite frequently. This is because the handling of JSON-formatted strings isn't standard EasyCoder functionality and is done by a plugin. To avoid the syntax becoming ambiguous, the lesser-used JSON features are prefixed by the word json.

Now let's deal with that plugin we need.

Writing an EasyCoder plugin

It's just possible that the required anagram finder functionality could be programmed using script, but it would be very slow and it's not really what the language is for. As with Google Maps, it's better to do it as a standard component and just import it into our page.

In English, unfamiliar objects are given names whose meanings we agree on, so an object that might be described as "big metal bird with wheels that carries people" gets to be called an Airplane. Sometimes the name starts out only having a meaning to a small group of people, so Light Amplification by Stimulated Emission of Radiation began as LASER, then when it became understood by the public in general it changed to just a plain laser. It had entered the dictionary.

Similarly, when big chunks of script are needed to perform a defined task it's better to replace them with a new word in the language. Most computer languages do this with functions, but EasyCoder has a way to make the new item join the rest of the language in a completely seamless manner, by adding it to a plugin. This is what we'll do here.

An EasyCoder plugin has a standard form. There's a range of them in the plugins folder of the EasyCoder installation, including a  dummy one called dummy.js that's sometimes useful as a starting point for a new one. You can also see all the files on our GitHub repository. We're only using part of the functionality provided by a plugin so I'll just show what the new one looks like. It's called anagrams.js and it's also in the same folder.

const EasyCoder_Anagrams = {

  value: {

    compile: (compiler) => {
      if (compiler.tokenIs(`anagrams`)) {
        if (compiler.nextTokenIs(`of`)) {
          const value = compiler.getNextValue();
          return {
            domain: `anagrams`,
            type: `getAnagrams`,
            value
           };
        }
        return null;
      }
    },

    get: (program, value) => {
      switch (value.type) {
        case `getAnagrams`:
        return {
          type: `constant`,
          numeric: false,
          content: JSON.stringify(Anagrams.getAnagrams(program.getValue(value.value),
            EasyCoder_words))
        };
      }
      return null;
    }
  },

  getHandler: () => {}
};

There are two parts to the value handler; one to compile the incoming script and the other to run the compiled program.  These are compile() and get() respectively. The syntax we want to handle is the expression anagrams of Text. This is not a command; it's a value.  Most plugins handle commands, values and conditions but here we only have values The main compiler has a number of helper functions that we can use to compile the new item, some of which are used in the code above.

The incoming script is in the form of a stream of tokens (words). Walking through it, the code first checks if the current token is anagrams and if so, if the one after that is of. After that we go and fetch a "value", which here is a variable but might be any sequence of instructions that returns a string or a number. Many of the functions advance the token pointer so we don't need to do it manually. The value isn't a simple number; it's a specification of how to get a number, which allows variables or constants to be used as appropriate. Evaluation is not done until runtime because some values, such as the time or the size of a window, aren't known until then.

Once we've extracted all the meaning we construct a small object that names the domain (package), a "type" that identifies what function is being performed and the value. This data returns to the main compiler where it's stored in the compiled program.

At runtime, the runtime execution unit will at some point come across the compiled value, look up which package to call and what action is needed and call get(program, value)with a reference to the program and the value object we created in the compiler.  So in the runtime handler - the get() function - we can now look for our anagrams.

The real work of extracting hyperlinks is done by getAnagrams(), a function in the external JavaScript library we wrote in Part 2, that we call with text we typed into the box. The library is completely independent of EasyCoder; in larger projects it may well be a React component but here it is vanilla JavaScript.

As I mentioned above, evaluation of values is done as late as possible, so up to this point all we've been passing about is the name of the variable holding the text. There's an internal symbol table that points to the variable itself and the data it contains. The text is retrieved using program.getValue() before it's sent to the library function.

The reason why EasyCoder can claim to handle almost any level of complexity or size of website is because someone else is doing all the real work. EasyCoder itself only has to worry about language syntax and looking after data; complex calculations and algorithms are done by library JavaScript modules. Because of this I don't have to explain here how anagrams are found as it really has nothing to do with the way this website is built.

Adding the new plugin to EasyCoder

There's just one more step here; to tell EasyCoder about the new plugin. When EasyCoder is installed into WordPress it sets up a folder called easycoder at the root of the WordPress installation. In here it places a couple of files, one of which, plugins.js, controls which plugins are available to be loaded. The relevant part of the default file looks like this:


const EasyCoder_Plugins = {

  getGlobalPlugins: (timestamp, path, setPluginCount, getPlugin, addPlugin) => {

  console.log(`${Date.now() - timestamp} ms: Load plugins`);

  setPluginCount(2); // *** IMPORTANT *** the number of plugins you will be adding

  getPlugin(`browser`,
    `${window.location.origin}${path}/wp-content/plugins/easycoder/plugins/browser.js`,
    function () {
      addPlugin(`browser`, EasyCoder_Browser);
    });

  getPlugin(`json`,
    `${window.location.origin}${path}/wp-content/plugins/easycoder/plugins/json.js`,
    function () {
      addPlugin(`json`, EasyCoder_Json);
    });

  }
};

exports = {
  EasyCoder_Plugins
};

In this case, 2 plugins are loaded globally (the rest of the file deals with loading plugins on demand, which doesn't concern us here). We need to add our link checker to this list, so first we'll decide where to put it. In the same folder seems as good a place as any, so the script now looks like this:


const EasyCoder_Plugins = {

  getGlobalPlugins: (timestamp, path, setPluginCount, getPlugin, addPlugin) => {

  console.log(`${Date.now() - timestamp} ms: Load plugins`);

  setPluginCount(3); // *** IMPORTANT *** the number of plugins you will be adding

  getPlugin(`browser`,
    `${window.location.origin}${path}/wp-content/plugins/easycoder/plugins/browser.js`,
    function () {
      addPlugin(`browser`, EasyCoder_Browser);
    });

  getPlugin(`json`,
    `${window.location.origin}${path}/wp-content/plugins/easycoder/plugins/json.js`,
    function () {
      addPlugin(`json`, EasyCoder_Json);
    });

  getPlugin(`anagrams`,
    `${window.location.origin}${path}/easycoder/anagrams.js`,
    function () {
      addPlugin(`json`, EasyCoder_Anagrams);
    });

  }
};

exports = {
  EasyCoder_Plugins
};

Our new plugin will now be loaded when EasyCoder starts up and will be ready to do its job.

And that's the end of this series of articles. Thank you for staying with me; I hope you enjoyed the read. If you have any questions I will be pleased to help in any way I can, both here and at the EasyCoder website where you'll find a link to a Slack channel. My mission is to make websites easier to build and to maintain, and I believe that putting high-level UI coding in a form like EasyCoder and heavy functionality into downloadable components is the best way to go forward.