As an additional resource, a list of case studies is presented in this chapter.
These showcase all technologies in the Mavo ecosystem working together to produce high fidelity applications.
Some of them are included because they represent particularly common use cases,
others because they push the boundaries of what is possible with Mavo,
and others because they showcase interesting patterns.
This section includes case studies of a few typical CRUD applications,
some of very common mainstream use cases (blog, e-shop, portfolio),
and some more specialized (research group website, CSS WG Disposition of Comments).
These types of applications are exactly the type of applications that Mavo was designed for,
though some still showcase interesting patterns, such as integrations with third-party services.
This case study showcases how Mavo can be used to build multi-page applications,
and how it can integrate a third-party service (Disqus) to support comments.
This application consists of two HTML files: index.html and post/index.html, with 40 and 60 lines of HTML respectively.
It requires no JavaScript besides the edits needed to Disqus’ embed code (described below).
The index.html file lists all posts, with their metadata and an excerpt:
<divmv-app="blog"mv-plugins="markdown"mv-storage="https://github.com/mavoweb/mavo.io/blog/posts.json"><sectionid="[id]"mv-multipleproperty="post"><h2><metaproperty="id"mv-default="[ idify(title) ]"><ahref="/post/?post=[id]"property="title"mv-attribute="none"></a></h2><divproperty="excerpt"class="markdown">[ excerpt(content, words: 100) ]</div><footer>
Posted on <timeproperty="date"mv-default="[$today]"></time>
by <aproperty="author"mv-attribute="none"href="https://github.com/[author]"></a>•<ahref="https://mavo.io/post/?post=[id]#disqus_thread">Comments</a></footer></section></div>
An id is generated from each post based on the title, but can also be edited by the user.
Note that <meta> elements are Mavo’s recommended way for storing metadata that is not displayed to the end-user
(but become visible and editable in edit mode).
The post/index.html file displays a single post in full.
Its structure is very similar (except the content property is displayed in full).
The main difference the mv-path attribute in the app definition:
This defines a two-way data transformation that consists of subsetting the fetched data before it is rendered,
so that the Mavo app defined within only needs to handle a single post,
and then recombining the edited data back into the original data structure when saving.
This can be applied both to the entire application, or to individual properties, which can be useful when handling pre-existing data,
such as data fetched from an API.
To embed comments on a post, we use Disqus, a popular commenting service.
Disqus provides a JavaScript snippet that needs to be included on the page where comments are to be displayed.
As instructed by Disqus, this needs to be modified to communicate the page id and URL to Disqus.
The parts we had to add or modify are highlighted below:
<divid="disqus_thread"></div><script>vardisqus_config=function(){let id = Mavo.Functions.url("post");this.page.url ="https://mavo.io/blog/post/?post="+ id;this.page.identifier = id;};(function(){// DON'T EDIT BELOW THIS LINEvar d = document, s = d.createElement('script');
s.src ='https://mavo-blog.disqus.com/embed.js';
s.setAttribute('data-timestamp',+newDate());(d.head || d.body).appendChild(s);})();</script>
Disqus also provides JS code for displaying the number of comments for each post on the list page.
To make that work with Mavo, it needs to be wrapped in an event listener.
The code we added to Disqus’ snippet is highlighted below:
Currently, our blog posts don’t have very nice URLs, e.g. a blog post with an id of foo is accessed at /post/?post=foo.
Ideally, we’d want a URL such as /post/foo.
This can be done with Mavo, as its url() function handles both patterns, so we would not even need to modify our code.
However, to do that we would need to first route unknown (not found) URLs of that format to the post/index.html page.
Mavo cannot help with that, as it operates on the client side.
Most web servers can be configured to do this, but the code is not always straightforward code for novices to write.
For example, in the popular Apache web server, this can be done with a .htaccess file,
but it requires regular expressions:
RewriteEngine On
RewriteRule ^post/.*$ /post/index.html [L]
With other servers it can be simpler for common cases like this.
For example, on Netlify, a serverless web hosting provider, this can be done with a _redirects file:
In this example, a page is used to list recipes and a different page to display a single recipe.
Instead of a single content property for the entry content,
each recipe here contains a list of ingredients and a list of steps.
The recipe manager also showcases a current limitation of Mavo:
the lack of a primitive for data formatting.
Indeed, amounts need to be displayed as fractional numbers, such as 0.125,
rather than the far more common fractional numbers that recipes typically use, such as ⅛.
Here, a list of all invoices is displayed on one page, and each invoice is displayed on another.
Each invoice includes a list of services provided, with amounts for each, and a total sum.
The consultant’s information (company name, address, logo etc.) is defined once as root properties, and copied on every invoice.
This is a fully-functional e-shop application, which integrates with PayPal, a well-known payment provider
that provides an HTML integration for its API
through form submission with a predefined parameter schema.
This case study highlights several concepts of the Mavo development approach,
but is also an excellent example for the interoperability of HTML-based approaches.
Neither Mavo nor PayPal needed to know about each other, but because they both support HTML-based syntaxes,
they work together seamlessly, with no need for any plugin or integration.
While conceptually a single Mavo application, this is architected as two Mavo apps, each with different access control:
one for managing the list of products, and one for managing the shopping cart.
The former is viewable by everyone, but editable only by the e-shop administrator, and stored on a cloud service like GitHub:
<divmv-app="eshop"mv-storage="https://github.com/…/products.json"mv-bar="no-login"><articleproperty="product"mv-list-item><imgproperty="image"alt=""/><spanproperty="name"></span><buttonmv-action="add(product, cart.product)">Add to cart</button><spanproperty="amount"class="price"></span></article></div>
The latter is editable by everyone and stored locally:
<formmv-app="cart"mv-storage="local"mv-autosavemv-mode="edit"action="https://paypal.com/cgi-bin/webscr"method="POST"><table><thead><tr><th>Product</th><th>Quantity</th><th>Price</th><th>Subtotal</th></tr></thead><tbodymv-listmv-initial-items="0"mv-item-bar="delete"><trproperty="product"mv-list-item><thproperty="name"><spanclass="mv-ui mv-item-bar"></span><spanproperty="name"mv-editor-name="item_name_[$index + 1]"inert></span></th><td><inputtype="number"property="quantity"name="quantity_[$index + 1]"value="1"></td><tdproperty="amount"mv-editor-name="amount_[$index + 1]"inert></td><tdproperty="subtotal">[amount * quantity]</td></tr></tbody><tfoot><tr><thcolspan="3">Total:</th><td>[sum(subtotal)]</td></tr></tfoot></table><buttondisabled="[count(product) = 0]">Check out with PayPal</button><inputtype="hidden"name="cmd"value="_cart"><inputtype="hidden"name="upload"value="1"><inputtype="hidden"name="business"value="admin@fooshop.com"><inputtype="hidden"name="currency_code"value="USD"></form>
The entire application is also a form that submits to PayPal.
Any form elements with name attributes become part of the form submission, and communicate data to PayPal.
Hidden form elements communicate variables that do not need to be displayed in the UI.
To send the product name and price to PayPal without allowing them to be edited by the user,
we set a name on the generated editing element via mv-editor-name and added the HTML inert attribute to prevent the user from interacting with it and its descendants.
A simpler but more verbose solution would be to use more hidden inputs,
with formulas to copy the name and amount properties into them,
and mv-mode="read" so that no editing UI is generated.
The two apps interact with each other: the eshop app uses a data action to add products to the cart,
by copying them to the cart app’s storage.
Note that Formula2implicit scoping does not apply across apps: since the trees are disconnected,
the id of the other app (cart) had to be explicitly used.
But once we obtain a reference to that app’s data tree, implicit scoping works as expected.
This is not a bug or a limitation, but an intentional design decision.
We felt that the small usability benefit of being able to reference properties from other data trees is outweighed by the potential for confusion and mistakes,
since other Mavo applications may be very spatially disconnected from each other,
and since cross-app references are relatively uncommon, the extra verbosity is not a big issue.
By default, Mavo uses two modes: a read mode (the default) which is used for presenting data,
and an edit mode which is used for editing data in place.
However, certain applications, such as the shopping cart here only need one mode.
Sometimes that is only a read-only mode.
In these cases, the mv-mode attribute can be set to read to make the app (or parts of it) read-only.
However, there are use cases that benefit from Mavo’s editing controls, but do not need a read mode,
they are edit-only. These can be specified via mv-mode="edit".
The shopping cart is a good example of this:
we want certain editing functionality to be always there (such as deleting collection items or editing quantities),
certain editing functionality to never be there (such as adding new products).
Editability is fragmented spatially rather than temporally.
To keep this demo simple, the cart is persisted locally.
However most big e-shops support user accounts and then persist the cart across devices.
While it is trivial to change the cart app to use cloud storage instead of local storage,
this does not exactly give us the same pattern, unless that cloud storage allowed public writes and we were happy to store each guest’s cart in it.
But more likely, what we actually want is a hybrid:
local storage for guests, and cloud storage for authenticated users.
This is still possible but certainly not easy.
We can use formulas as the mv-storage value to use local storage for non-authenticated users and cloud storage for authenticated users,
but this introduces a bit of a chicken-and-egg problem:
how would the user log in to the cloud service, if there is no login UI because the app is using a local storage backend?
Since we can only have one storage backend per app,
we would need a third app that is always cloud-based, and only contains the login UI,
and take advantage of the fact that Mavo will synchronize user accounts across apps using the same type of backend (e.g. GitHub).
We can then use a formula to set the cart’s storage backend based on whether the user is logged in or not:
<formmv-app="cart"mv-storage="[if ($user or cart_login.$user, 'https://url/to/cloud', 'local')]">
This is still not perfect: if a user starts shopping as a guest and then logs in, their cart will be lost.
We can use mv-init="local" to address this, but that only works if the remote data is empty.
It remains an open question what the best primitive is for handling such cases in a general way.
The administrator gets the usual Mavo UI, with its promiment toolbar.
For the public, the toolbar is hidden, by hiding its login button (via mv-bar="no-login").
Since logged out users see no other controls, this hides the entire toolbar for them.
Instead, there is a discreet “Admin login” link in the footer:
<ahref="?login"class="mv-login">Admin login</a>
The link has class mv-login which makes it behave like a login button automatically (hidden for logged-in users, triggers login UI when clicked, etc.).
Every toolbar button has a corresponding class so that its functionality can be reused on custom UI elements even outside the toolbar.
The link target is ?login, which is a special URL that triggers the login UI for the first Mavo app in the page.
Such a link is available in every Mavo application (?login shows login UI for the first Mavo app in the page but a specific app id can be specified as the URL parameter’s value).
This allows for the login UI to be entirely hidden if the author desires.
Since cart.product is a collection that is only edited via a data action, showing functionality to add items to it is meaningless.
We can hide the add button using regular CSS:
[mv-app="cart"] .mv-add-product{display: none;}
To limit adding and reordering via the item bar, Mavo provides an mv-item-bar attribute that can be used similarly to mv-bar:
From a Mavo point of view, this is a very simple use case.
So simple in fact, it is included here as a case study on how such a high fidelity application
allowing images to be uploaded, pasted, or linked and edited in a number of ways can be built with such a small amount of code.
The entire page is 25 lines of HTML, with no JavaScript, of which only 8 are Mavo markup:
Individual pages for each painting could be easily added using a similar pattern as the blog case study (Section 8.1.1).
Note that reactive defaults crop up even in such a simple application:
the filename of the image is processed to create the default value for the name property,
a common pattern for these types of applications.
Images are resized by the browser to display thumbnails, but the full image is still downloaded.
It would be possible to handle thumbnail generation in Mavo via a plugin that generates thumbnails locally when uploading an image,
but due to the Web’s same origin policy [1, 2], this would not be possible for linked images.
The application consists of three Mavo apps, one for each section of the website: people, projects, and publications,
each reading and writing a different JSON file.
This is a design decision primarily to produce reusable JSON files that can also function as a basic data API,
and to keep their size manageable, but the website could have easily used the same file for all three sections.
As with the e-shop example, the Mavo toolbar is only visible for logged in users.
Logged out users simply see a discreet Log in to Github to edit data link in the footer of each section.
The people application includes a collection of people with their names, images, URLs, and other information:
<articleproperty="member"typeof="Person"mv-multiple><aproperty="url"><imgproperty="image"alt=""mv-uploads="../images/people"><h3property="name">Name</h3></a><!-- job title, social media handles, etc. --></article>
It then uses this to generate a <datalist> as a dynamic collection
over the names of all people in the group:
Then, on the collections that need to reference people,
the generated editing UI is linked to this <datalist>
to facilitate data entry via an (HTML-native) autocomplete widget.
A hidden computed property is then reactively populated with the selected person’s data.
For example, this is how the leader of a project is selected:
<footer>
Led by
<ahref="[leader.url]"><imgsrc="[leader.image]"alt=""><spanproperty="leader_name"mv-edit-list="member_list">Leader</span><metaproperty="leader"mv-value="people where name = leader_name"/></a></footer>
For publications, we only need to look up the author URL, so we can avoid defining the hidden property:
<pmv-listclass="authors"><aproperty="author"mv-attribute="none"href="[people.url where name = author]"mv-edit-list="member_list"></a></p>
This is essentially using the person’s name as a unique, immutable identifier, i.e. a primary key.
If there is no property that is a good candidate for this, a unique identifier can be generated using a formula (e.g. idify(name)),
and used as the default value of a hidden property, so that it can be edited by the user:
<metaproperty="id"mv-default="[ idify(name) ]"/>
Lookups would then need to be adjusted accordingly.
Many issues with this pattern stem from the fact that it is essentially a workaround
around the lack of real primary keys and foreign keys in Mavo.
Uniqueness of the property used as a key is not enforced.
It could be communicated to the user via form validation, but this would simply be informative,
it would not prevent them from saving corrupted data.
But a bigger issue is that this suffers from poor closeness of mapping,
breaking the declarativeness of general Mavo syntax.
Authors cannot express their intent with the language primitives,
so they have to express low-level steps for accomplishing their goal.
Dispositions of Comments is part of the process for advancing a W3C specification to the next stage,
by ensuring all issues raised against a draft specification by the community have been considered.
It was typically managed as plain text at the time.
This application was authored to improve this process (though as WG issues moved to GitHub Issues, it was later replaced by a convention based on GitHub labels).
This application showcases how filtering and sorting functionality can be implemented in Mavo,
but is also an excellent example of the kinds of data management applications that Mavo was designed for:
the long tail of use cases
that are too niche individually, yet vast in aggregate.
We can use a separate filters Mavo app to persist filters locally:
<asidemv-app="filters"mv-storage="local"><!-- Filtering UI --></aside><mainmv-appid="issues"mv-storage="https://github.com/w3c/csswg-drafts/[ url('spec') ]/issues-[ url('doc') ].yaml"><!-- List of issues --></main>
Each filter is a dynamic collection.
For filters on single-valued properties, such as status the grouping operator can be used:
<fieldsetmv-listproperty="status"mv-value="issues.issue by status"><legend>Filter by status</legend><labelmv-list-item><inputtype="checkbox"property="show"checked><spanproperty="status"mv-editor="#statuses"></span> ([ count($items) ])
</label></fieldset>
Note that while the status property in the dynamic collection is not editable (as statusFilter is a computed property),
we had to specify mv-editor="#statuses" which points to a <select> menu,
in order to get the same displayed text for each value.
Admittedly, the closeness of mapping for this could be improved…
Unfortunately, as described in the Formula2 chapter,
the grouping operator does not currently work well with multi-valued properties, such as issue tags.
For those, we need to use a lower level approach, essentially doing the grouping ourselves:
<fieldset><legend>Filter by tag</legend><labelproperty="tag"mv-list-itemmv-value="unique(issues.tag)"><inputtype="checkbox"property="show"checked><spanproperty="text">[tag]</span>
([ count(issue.tag = text) ])
</label></fieldset>
Then, to filter the issues based on the selected tags, we can define a hidden property as an immutable collection,
with a value for each filter:
<metaproperty="hidden"content="[ count(filters.status where show and id=status) ]"/><metaproperty="hidden"content="[ intersects(tag, filters.tag where show) ]"/>
And then use an aggregate disjunction of these properties to determine if each issue should be hidden:
One limitation of this approach has already been discussed above: the lack of a good way to group by multi-valued properties in Formula2.
But the bigger issue is that this approach is too low-level for such a common task.
This issue is discussed in more detail in the next chapter.
In this section, we present two applications that push the boundaries of what is possible with Mavo,
by using it to build GUIs that generate graphics.
Both of these showcase a pattern for creating heterogeneous collections,
as well as different ways to use Mavo to generate code in different languages: SVG in the first application,
JavaScript and LOGO in the second.
This is certainly not a typical CRUD application, but this SVG path demo showcases many interesting patterns.
It also makes a slightly different argument on interoperability:
HTML-based approaches are only only interoperable with HTML itself, but also any syntax that can be embedded in it, such as SVG.
This means that Mavo can be used to create interactive graphics and diagrams, or even — as this demo shows — entire editors.
The entire application is 126 lines of HTML, with no JavaScript.
The main data structure of this application is a collection of path segments (segment),
each item corresponding to a single SVG path segment command.
There are also root level properties to parameterize the
coordinate system (width and height) and style (fill, stroke, strokeWidth).
of the generateed graphic.
A computed pathsummary property within each item is used to generate the actual SVG path string for that segment:
Then, outside the collection, all these are combined into another computed property, containing the entire path,
which is also displayed to the end-user for copying:
The replace() is only there to make the path string more readable by collapsing sequences of spaces, and is not required
(10 is the number of times it should run on it own output).
Then, all that is needed to generate the download link is this:
The actual inline preview is a little longer,
as it also displays conveniences that improve feedback for the end-user,
such as a grid, and a marker to display the current position of the last point of the path:
This application showcases a common pattern for editing and storing heterogeneous collections,
i.e. collections where each item has different properties.
Typically, these have some overlap across different types of items,
otherwise there would be little reason to store them in the same collection.
Path segments are no exception:
they all have a type, a boolean absolute property,
and X and Y coordinates for their end point (except one: close path (z) segments do not take any coordinates).
Frequently in heterogeneous collections, there is a main property that determines the item type,
in this case the path segment type:
Then we can use declarative mv-if conditionals to show and hide the appropriate fields for each type:
<labelmv-if="type != v and type != z">
X <inputtype="number"property="x"/></label><labelmv-if="type != h and type != z">
Y <inputtype="number"property="y"/></label>
It is important to note that mv-if does not simply modify the HTML, like similarly namedj features in JS frameworks which treat HTML as purely a view.
It actually modifies the data that expressions see (but not the data that is stored, to avoid data loss).
While this demo highlights many of Mavo’s strengths, it also highlights one of its core limitations:
it cannot handle complex interactions, such as those needed by a visual graphics editor (e.g. dragging points to change the path).
How to enable such interactions within Mavo is an open question.
For this to be truly useful, it should also support editing existing paths.
Then authors could paste an existing path into the editor, edit it, and copy the result back out.
However, there is no way to write a Formula2 formula that parses (nontrivial) syntax.
End-user parser programming is a whole research area in itself, and is currently out of scope for Mavo.
This application fits squarely in the category of apps pushing the boundaries of what Mavo can do to the limit.
It is a visual programming environment for (a subset of) the LOGO programming language [3],
a graphic programming language designed for teaching programming to children.
It lets the user build a LOGO program by manipulating visual controls,
displays a reactive preview on the right,
and generates both LOGO code,
and JS code (using the Canvas API).
The user can not only tweak the parameters of commands,
but rearrange them and move them in and out of loops via drag & drop.
It is implemented as a collection of commands (instruction)
is progressively generating JavaScript code through Formula2 expressions,
and then rendered in an <iframe> to reactively draw the graphic on a <canvas> element,
automatically re-rendering the graphic as the commands change.
Beyond the novelty of the application itself,
it also showcases recursive collections via the mv-like attribute.
Recursive collections are collections that have at least one child using the same template as the parent.
A common use case is threaded discussion comments, where each post can have any number of replies as children,
which can also have their own replies and so on.
In this case instruction is a recursive collection,
as some commands (loops, conditions) can contain other commands.
It also showcases how metaprogramming can work in Mavo HTML,
by taking advantage of existing HTML primitives (<iframe srcdoc>) that allow rendering documents from arbitrary strings.
The entire application consists of approximately 200 lines of Mavo HTML, and its logic needs no JavaScript
(beyond the JS code it generates, as described below).
Of the 19 commands in the 1969 design of the LOGO language [3],
this application implements 16 (forward, backwards, right, left, pen up, pen down, setpencolor, repeat,
home, setxy, set heading, setpensize, penerase, circle, arc, print).
The main commands missing are those related to defining procedures (to ... end and output) and stopping the program (stop).
This is possibly the weirdest Mavo application ever built:
The main program being authored is a collection of commands (instruction).
Each instruction item is a heterogeneous collection with a type property for the type of command,
which determines which other properties are shown.
<labelmv-if="type = 'color'">
to <inputtype="color"property="color"mv-default="[color.$previous or '#ff0066']"/><metaproperty="logo"content="setpencolor [color]"/><metaproperty="js"content='ctx.strokeStyle = "[color]";
if (brushDown) ctx.closePath(); ctx.beginPath();'></label>
The pen up and down commands have no parameters,
and thus only contain the logo and js properties:
The repeat command is recursive:
it can contain any number of commands, including other repeat commands.
To implement this, we use the mv-like attribute to copy the template of the parent collection.
Collections with mv-like implicitly have mv-initial-items="0",
otherwise this would create an infinite loop.
Outside the root instruction collection, the values of the js and logo properties are joined to generate the final code
(the js_before property contains 5 lines of JavaScript code to set up the canvas and apply default styles):
These are then displayed in the UI for the user to copy, and for educational purposes.
The js_combined property is also used to generate the preview via a dynamic <iframe>:
A big limitation of this approach is its performance: since an entire webpage (in the <iframe>)
needs to be re-rendered every time the user makes a single change.
Surprisingly, while the user experience is not as smooth as it would be with a custom implementation,
it still very usable (at least in a Chrome browser in 2017),
with the main issue being some flickering when quickly incrementing numbers.
Another is lack of parsing: it would have been useful if this allowed inputting LOGO code directly
and displaying the corresponding visual program, rather than requiring it to be composed only via the GUI.
This is a common limitation across many Mavo apps — we have seen it before in the SVG path builder example (Section 8.2.1)
as well.
Last, lack of direct manipulation: the user cannot interact with the graphic in any way.
While by design this is a graphic generated by a series of commands,
it would be useful to at least be able to pan and zoom via direct manipulation,
something the app this is inspired from (Nicky Case’s Joy of Turtle Demo) does provide.
Even though our driving use cases have been primarily CRUD apps,
it was interesting to see how some Mavo authors pushed the boundaries of what types of applications can be built.
One such category is games, as most games are the polar opposite of CRUD apps,
with far more complex logic and interactions.
This is an implementation of the popular game Concentration (also known as Memory or Pairs),
where the player has to find matching pairs of cards.
Its entire implementation is fewer than 200 lines of Mavo HTML (and no JavaScript),
and that includes customizable themes, a timer, a performance rating, and a history of previous gameplays.
This use case argues the point behind the motivation for data actions quite well: the entire game is fully reactive,
with only a single (nontrivial) data action which is triggered when a card is clicked.
Yet, without that one data action, the game would not be possible to implement in Mavo.
The application is implemented as six Mavo apps, but architecturally it only needs three, the rest is done for modularity:
themes: Loads data about the available themes from a JSON file (colors, icons, etc.).
stats: History of past gameplays, stored in local storage.
The rest describes the game state and is broken down into four apps for modularity (game, gameState, game-over, (unnamed)),
but this state could have been combined into a single app.
The game is implemented as a dynamic collection of cards (card),
shuffling a list of symbols, with each symbol being present twice,
via the formula shuffle(list(symbols, symbols)).
While this is a reactive formula, it only gets re-evaluated if symbols changes,
which should not happen during gameplay.
CSS classes are used to reflect the state of each card (flipped, matched, incorrect, etc.).
The core game logic is implemented by this long data action,
which is triggered when a card is clicked:
As described in Chapter 6, data actions were envisioned as means to bridge the small gaps between purely reactive applications
and the reality of what many CRUD applications needed.
The kinds of data actions CRUD applications need rarely exceed one or two function calls,
and as we have seen in our user study, novices struggle with sequences of actions.
The fact that they can be used to implement complex game logic is a testament to their flexibility,
but using them in this way also reveals their weaknesses, since they were not envisioned as a general purpose programming language.
Namely, the lack of abstractions becomes painful the more complex the logic gets.
The large data action above could be a lot easier to comprehend if it could be broken down into smaller, more manageable pieces.
This is an issue we will also see in the next game.
Mavordle consists of several Mavo apps, each representing a different part of the game:
Three apps (keyboard, possible_words, common_words) load data from JSON files that is then used by the rest of the code.
An app for the statistics of past gameplays (statistics), whose data is stored in local storage.
The remaining three Mavo apps (game, mode, popup) implement the core game logic and were implemented
as separate apps for modularity — in terms of functionality, they could have been combined into a single app.
The gameplay is implemented as data actions for the onscreen keyboard buttons, which update state as needed.
The keyboard is implemented as a collection of letters,
each with a data action that appends it to the current guess and a class that styles them based on their status:
Most of the logic is on the Enter key,
which has two versions, one for when the guess is incomplete (fewer than five letters),
and one for when the guess is complete:
Gameplay needs to happen via the onscreen keyboard — pressing the corresponding letters on the physical keyboard does not work.
This is because Mavo does not provide a way to trigger actions based on key presses.
This could be added, but it would require a few lines of JavaScript:
Barth, A. 2011. The Web Origin Concept. RFC 6454. Internet Engineering Task Force: https://datatracker.ietf.org/doc/rfc6454. Accessed: 2024-08-28. 10.17487/RFC6454.
Cited in1
Solomon, C., Harvey, B., Kahn, K., Lieberman, H., Miller, M.L., Minsky, M., Papert, A. and Silverman, B. 2020. History of Logo. Proc. ACM Program. Lang. 4, HOPL (Jun. 2020), 79:1-79:66. 10.1145/3386329.
Cited in1, and
2