(for which the anagram finder gave me I snort gilt eels. It's a lie, I tell you!)
In this article I'll describe how to code the anagram finder web app. As before, my target audience is people with levels of technical skill ranging from near-beginner to intermediate.
In a previous article (not in this series) I wrote about Story Driven Design. Stories are written by customers who want a website built. These people are usually not 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 the app:
and here are the stories that describe how it looks and functions:
"The app 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 is preloaded with the text it held 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 and 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 are what the delivered product will be verified against. The stories themselves are rarely cast in stone; they frequently undergo changes in the light of experience and feedback from users, but by contrast the anagram finder algorithm is likely to be fixed for all time. So it makes little sense to combine both these items into the same coding structure. Instead, we have two distinct programming paradigms:
There are few web pages so complex they can only be described using advanced computer code. Every one I can think of can be - and usually is - described in English, and it's very helpful if the code keeps its stories visible so the owners of products can look inside them and verify they do as expected.
At the very least you can regard the following as pseudo-code that expresses user stories in a verifiable form, even if you then choose to re-implement it with your favorite tools.
EasyCoder scripts live in the web page or are loaded from somewhere else. They are simply chunks of text that must be compiled by the EasyCoder plugin before they can do anything.
Because we're building the app as a WordPress page we don't have access to the root of the DOM tree (the
<body> element), so to provide somewhere for the app to live we need to add a
<div> to the page, as follows, with an id the script will look for and attach one of its own variables:
The EasyCoder script lives in a special
<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 EasyCoder script that corresponds to the stories:
h2 Title div Root div InputDiv div ResultsDiv div ResultDiv div Label div Padding input Text button RunButton button ClearButton variable Anagrams variable Phrase variable Words variable Index variable Running variable Results variable Keys ! Attach to the DOM element on the page attach Root to `anagrams` ! Styling is different for mobile if mobile set the style of Root to `width:100%` else set the style of Root to `width:100%;margin:1em` create Title in Root set the style of Title to `text-align:center` set the content of Title to `Anagram Finder` ! Create a separate DIV for all the form components 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` ! "Padding" is reused every time we want a separator create Padding in InputDiv set the style of Padding to `flex:2` create Text in InputDiv set the style of Text to `flex:76` ! Retrieve the text from last time get Phrase from storage as `anagram-text` set the text of Text to Phrase 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` 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 ! A label to go under the form create Label in Root set the style of Label to `margin: 0.5em 0.5em 0 0.5em` set the content of Label to `Loading a word list...` ! The DIV to hold all the found anagrams create ResultsDiv in Root set the style of ResultsDiv to `margin: 0.5em 0.5em 0 0.5em` ! Load the anagram finder JS file require `https://cors.io/?https://raw.githubusercontent.com/gtanyware/EasyCoder/master/demo/anagrams.js` ! Load a list of 33,300 English words require `https://cors.io/?https://raw.githubusercontent.com/gtanyware/EasyCoder/master/demo/words.js` set the content of Label to `No anagrams found (yet):` on click RunButton go to Run ! Wait for the user to do something stop ! When the user clicks Run... Run: put Text into storage as `anagram-text` ! Remember it for next time json set Results to object ! Make Results an empty JSON variable Continue: ! Reprogram the button set the text of RunButton to `Stop` on click RunButton begin clear Running ! Reprogram it again set the text of RunButton to `Continue` on click RunButton go to Continue end ! Set the running flag and keep going as long as it remains set set Running while Running begin ! This is where we call the anagram finder library module put anagrams of Text into Anagrams ! It returns JSON properties "status" and "words" if property `status` of Anagrams is `found` begin put property `words` of Anagrams into Words json sort Words ! Build a phrase with the words of this anagram put empty into Phrase put 0 into Index while Index is less than the json count of Words begin put Phrase cat element Index of Words cat ` ` into Phrase add 1 to Index end ! Check if this phrase is already a property of Results if property Phrase of Results is empty begin ! It isn't, so add it, with an arbitrary value (that we don't use) set property Phrase of Results to true ! Extract the phrases as an array put the json keys of Results into Keys json sort Keys ! Show how many results we have set the content of Label to the json count of Keys cat ` anagrams found:` ! Rebuild the list of phrases 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 ! Allow the CPU to cool off (critical!) wait 2 ticks end stop
Anyone who was around in the '80s and came across HyperCard on the early Macintosh may find this slightly familiar-looking. This is not a coincidence; EasyCoder was inspired by HyperTalk, the language inside that "insanely great" product that lives on in part as AppleScript. If there's a guiding principle it's that readability is best ensured by minimal use of symbols and maximum adherence to plain English syntax.
EasyCoder looks very different to modern computer languages, having few symbols and lacking concepts like block structuring and parameterization that are familiar to experts but which confuse everyone who isn't a programmer. Like SQL it sits in the middle ground between the machine and the human and is understandable by both.
Now for how it works. I've annotated it pretty heavily with comments (the exclamation marks); more than is generally regarded as necessary for good documentation, but this is the first time you'll have seen this syntax. The first half of the script sets up the screen, starting with a list of variables. The convention is for names to all start with capital letters, like they do in English. Many of these are types that correspond to DOM elements; the rest are plain variables to hold numbers or text.
The first action is to attach 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 series of commands that create the various parts of 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 text field and the buttons into their own
<div> and called it
InputDiv, then 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.
InputDiv uses a
flex display attribute and I find it helps when distributing components to use percentages that add up to 100. There's a line that takes a value from storage and puts it into the text field, and another that saves the current field value when the Run button is clicked. These use browser storage, that lets you persist data between visits to a site.
Having set up the screen elements we now have a couple of
HEAD of the document, and the strange URLs are needed to avoid cross-domain request refusals that exist to prevent browsers from accepting potentially dangerous executable code. One of these files is the anagram finder component we will be building in the next part of this series; the other is a dictionary - a list of words. It looks like this:
const EasyCoder_words = [ `a`, `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 (though I have no idea how to deal with accented characters). Word lists can be found on the Internet with a bit of hunting.
These two items can take a while to download, so the text of
Label is used to inform the user that something is happening.
At the end of the screen setup code there's an
on click command to detect when the user clicks the
Run button and transfer control to the program label
When it finishes setting up the program stops and wait for something to happen. When the click occurs, first there's the rather odd-looking command
The script reprograms 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
which is the syntax we'd like to use to call our anagram finder library, but here we have a problem. This syntax is unknown to EasyCoder so we're going to have to do something special to handle it. Before I say any more about that I'll just finish describing the code.
On the less frequent occasions where 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
Results object to see if the phrase we have was already there. If not, we add it as a property of that object, extract the object keys, sort them and write them to the results panel in the display, with a line at the top saying how many anagrams were found. Each anagram goes in its own
<div> on the off-chance we might want in the future to be able to click each one and do something with it.
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 language package. To avoid the syntax becoming ambiguous, the plugin prefixes lesser-used JSON features with the word
I promised I'd deal with the command to call the anagram finder module, which is unrecognized by standard EasyCoder. Other languages do this with functions but EasyCoder demands that all additions extend the language seamlessly. The language is comprised of plugin packages, each one handling a particular vocabulary and syntax, such as for Google Maps, SVG graphics or JSON, so this is a case for adding a new package, to implement a single command that calls the anagram finder. (Or you could add the command to an existing package, if you have one.)