Let Mavo Shine in Building Interactive Web Applications
This tutorial will dive into Mavo: a convenient and easy-to-use way to create complex, responsive, persistent web applications by writing HTML and CSS without a line of JavaScript code or server backend.
Mavo is developed by the Haystack group at CSAIL at MIT and led by Lea Verou.
We will jointly build a flashcard application for foreign language learning . This is a fully functional CRUD application that allows you to:
- Create, delete, update flashcards and rearrange them by drag and drop.
- Import and export flashcards.
- Assess your level of mastery of words on flashcards.
Here is how the application we completed looks:
In this tutorial, I will walk you through the entire process of building your application.
At the end of some steps, I'll give you some advice to try using Mavo - learn more - and make some enhancements to the app we're building.
Ready? Let's get started! ?
Static templates
To illustrate how Mavo enhances standard HTML, we will first create a pure static HTML page and then use Mavo to convert this static HTML into a fully functional web application.
Suppose we are in the following HTML code:
<h1 id="Flashcard"> Flashcard</h1> <main><p>Words or phrases</p> <p>translate</p> </main>
In this code,<main></main>
Elements represent a single flashcard .
Let's add some styles to make our HTML look more like an actual flashcard application. You can view the source code and use it here.
Get started with Mavo
Now, we only have static templates. It's time to add features so that it can really work like a flashcard app. Mavo makes a brilliant debut!
In order to use Mavo, we first need to Partially contains its JavaScript and CSS files:
... <link href="https://get.mavo.io/mavo.css" rel="stylesheet"> ...
You might need to support older browsers, or would like to be able to read the code? You can customize the version and build you are using by answering several questions.
Define Mavo applications
To enable Mavo functionality on HTML structure, we must use the mv-app
attribute on the element that contains the Mavo application (it can even be or
Elements, all are OK! ). Its value is the ID of the application and should be unique in the page. If we use <code>mv-app
without values and there is no id
or name
attribute on the same element, names such as mavo1
, mavo2
, etc. will be automatically generated.
However, it is highly recommended that you name the Mavo application, as the name will be used in many places.
Consider<main></main>
The element represents our Mavo application, let's add the mv-app
attribute to it and specify the ID "flashcards" for our application:
<main mv-app="flashcards"> ... </main>
Attribute attributes
It's time to tell Mavo which application elements are important , i.e. which elements we want to be editable and saved.
Now we have two such elements, they are<p> element. Let's add <code>property
attributes to these elements to tell Mavo that they contain data. An element with property
attribute is called a property .
We can put property
property on any HTML5 element and Mavo knows how to make it editable. For example,<input>
, you can edit its content, but<time></time>
Allows you to edit its date/time with the appropriate date/time selector.
You can also extend this set of rules through plugins and make elements editable in new ways (for example, rich text).
Remember that the value of property
attribute should describe the element similar to id
or class
attribute:
... <p property="source">Words or phrases</p> <p property="translation">translate</p> ...
If you already have class
, id
, or itemprop
attribute that fully describes the element, you can use property
without a value, such as property="source"
.
Have you noticed any changes in our application? A Mavo toolbar with an edit button appears at the top of the page. The Edit button allows the user to switch between reading and editing modes. Now our application is in reading mode. This means we cannot edit the data on the page.
The Mavo toolbar is completely customizable , and it's true that almost all UIs generated by Mavo: you can change its location, delete its default style, add custom button elements, or use your own HTML elements, and more.
We will see a custom example like this later in this tutorial.
Visit this section of the Mavo website to learn more.
Now let's switch to edit mode by clicking the Edit button. What has changed? The text of the edit button becomes editing to indicate that we are in editing mode. If you hover over paragraphs, Mavo tells you that you can click to edit them by highlighting them in yellow. Let's get started! Click the text and edit it. Wow! We can change content directly on the page!
Assume that in addition to the words and their translations, the flashcard should also contain an example of how words are used in a sentence. Enhance the application by adding corresponding elements to the flashcard.
mv-multiple
attribute
Currently, there is only one flashcard in our application. This is useless! For an effective flashcard application, we need to be able to add, delete and rearrange flashcards. How can we do this? We can add more to the code<main></main>
Elements to create more flashcards, but how do end users create and delete flashcards?
Fortunately, Mavo provides something that makes this a breeze: mv-multiple
property, which tells Mavo that certain elements can be copied . It converts the elements it uses into an editable collection of items and generates (customizable) UIs for adding, deleting, and rearranging items.
If you use mv-multiple
on an element without property
attribute, Mavo will automatically add property="collection"
to it (or collection2
, collection3
, etc. to make the name unique). However, it is recommended that you also use property
property, name your collection, and make sure that your HTML changes to preserve its data.
Let's convert our lonely flashcards into flashcard collections using the mv-multiple
attribute in our application:
... <main mv-app="flashcards" mv-multiple> <p property="source">Words or phrases</p> <p property="translation">translate</p> </main>
You can also specify the property name as the value of mv-multiple
, such as mv-multiple="flashcards"
.
mv-multiple
attribute is located on the element to be copied, not on the container of the collection. One of the mistakes that people make is to use<main mv-multiple></main>
Instead<main></main>
, and usually not discovered for a long time before checking the element or style to make it obvious. Now switch the application to edit mode. Please note that under the flashcard, there is now an add flashcard button. Let's give it a try: Use this button to create some flashcards. Now we can dynamically add new elements directly in the application, even if there is no corresponding element in the HTML. But that's not all!
Please note that<main></main>
property
property on the property does not actually make the whole<main></main>
Elements are editable, but instead act as grouping elements . This happens when you use property
properties on elements that contain other properties. Try hovering your mouse over the flashcard and pay attention to the three buttons appearing in its upper right corner to add , delete , and rearrange elements by drag-and-drop handles. By hovering over any item bar button, we can understand which flashcard they correspond to: Mavo will highlight it. Isn't it very magical?
You can customize any UI element generated by Mavo, for example, you can create your own drag handle by using the mv-drag-handle
class.
The buttons added to each item in the collection are also accessible via the keyboard . Even reorder: You can focus on the drag handle and use the arrow keys to move the item.
mv-storage
attribute
Now that we have the basic UI, let's try the following:
- Switch to edit mode (if you haven't done so yet).
- Edit the source words and translations of the first flashcard. Add a few flashcards.
- Switch the application back to reading mode.
- Finally...refresh the page.
What? ! Where did our data go? Shouldn't Mavo save it? what happened?
In fact, we never told Mavo whether or where to store our data!
To do this, we need to use the mv-storage
property. What choices do we have? Well, Mavo opens up huge possibilities for us, and Mavo plugin opens up even more possibilities!
In our application, we store the data in the browser's localStorage, which is one of the easiest options, so it's perfect for our first Mavo application. We just need to add the mv-storage
mv-app
with the value local
on the element (also called Mavo root ).
<main mv-app="flashcards" mv-storage="local"> ... </main>
Take a look at the Mavo toolbar. Have you noticed something? Another button appears - the Save button.
Try editing the application data again. Note that the Save button is now highlighted . Hover over the Save button and Mavo will highlight properties with unsaved data . Isn't it cool?
Click the Save button and refresh the page (no need to switch to reading mode before refreshing the page). Is your data still there? Very good! We are one step closer to our goal – a fully-featured flashcard application.
mv-autosave
property
Now do we have to click the Save button every time we need to save data? This may be safer and prevents valuable data from being damaged, but it can often be inconvenient. Can we automatically save data? certainly! To automatically save data every time we change it, we can use the mv-autosave
property on the Mavo root element. Its value is the number of seconds saved by throttling. Let's add mv-autosave="3"
to the root element of the application:
<main mv-app="flashcard" mv-autosave="3" mv-storage="local"> ... </main>
If mv-autosave="3"
, Mavo can only be saved once every three seconds at most. This is especially useful for backends that keep change history (e.g., GitHub , Dropbox ) to prevent flooding, which makes that history useless.
To disable throttling and save immediately, we can use mv-autosave="0"
or just use mv-autosave
, which also removes the save button from the UI (because it is useless in this case).
Change the data again and view the save button. Have you seen it? At first it was highlighted, but after 3 seconds – it was not highlighted. All our data is now automatically saved!
So now the main part of our application looks like this:
<main mv-app="flashcards" mv-autosave="3" mv-storage="local"><p property="source"> Words or phrases</p> <p property="translation">translate</p> </main>
We almost finished the alpha version of the application. Now it's your turn to make the app better. Don't worry, you have all the knowledge you need.
Enhance the application so that flashcards can be organized by the end user into different groups related to various topics, for example, the user can collect all clothing-related flashcards into one group, all kitchen utensils into another group, and so on.
?hint!
There are many ways to achieve this, depending on what you decide to follow. However, I hope you think about some issues before proceeding:
- What HTML elements will you use as grouping elements ? It would be convenient if the user can see the name of the flash deck (theme name) and can collapse the group to the title.
- What Mavo properties will you add to that element (if any)? Will this element be an attribute or a collection ?
- Can end users add new topics, delete and rearrange them, change topic titles, and move flashcards between topics?
If you decide not to organize flashcards by group, but instead tag them with labels corresponding to various topics, that's fine. The solution using tags is also suitable. For practice, try to complete this method.
mv-bar
attribute
Because our app stores data locally, by default, users of the app will not be able to share their cards with other users. Wouldn't it be great if we allow them to export their flashcards and import other people's flashcards? Thankfully, these features are already implemented in Mavo and we can easily add them to our app!
mv-bar
property controls which buttons (if any) will be displayed in the toolbar. It is usually specified on the Mavo root (element with mv-app
attribute). Buttons are represented by their ids (very logical): edit, import, export, etc.
Since we want to add only a few buttons to the default collection, we can use what is called relative syntax , which allows us to add and delete buttons to the default collection without explicitly listing everything. We just need to start with the with
keyword the value of mv-bar
attribute.
By doing this we will get the following:
<main mv-app="flashcards" mv-autosave="3" mv-bar="with import export" mv-storage="local"> ... </main>
Try these features: Add some flashcards and try exporting them to a file. Then delete the existing flashcard and import the flashcard from the previously exported file.
Expressions and MavoScript
Now let's add some statistics to our app, such as the number of flashcards! Does it sound interesting? I hope so. ?
To do this, we need to learn something new about Mavo.
We can dynamically refer to the value of any property we define anywhere in our Mavo application (including in HTML properties) by putting its name in square brackets like this: [propertyName]
. Here is an example of a simple expression that allows us to calculate things dynamically and react when they change.
Mavo's expression syntax is called MavoScript . It is similar to a spreadsheet formula, allowing us to perform calculations and other operations (using numbers, text, lists, etc.), but is designed to be easier to read and adapt to nested relationships. You can learn more about Mavo expressions and MavoScript in the documentation.
Now let's try it out, add the [source]
expression inside the flashcard attribute, for example, between two properties: source and translation.
... <p property="source">Words or phrases</p> [source] <p property="translation">translate</p> ...
What has changed in our application? The value of the flashcard source attribute is now displayed twice on the page.
Switch to edit mode and try to change the value of the source attribute. Have you seen it? The page content will be updated when you change the attribute value! This is why I said before that Mavo allows us to develop responsive web applications.
This is really cool, but unfortunately, in our case it doesn't work much: we can't use this expression to calculate the number of flashcards - we always have only one value .
What happens if we put the [source]
expression outside the flashcard attribute? We will get the following:
... [source] ... ...
How is this different from the previous cases? To see the differences, if you haven't done so, add some flashcards. Now, we are not a value , but a comma-separated list of values : source properties for all flashcards. This is exactly what we're always looking for: the number of items in the list corresponds to the number of flashcards in the app.
Does it make sense? Yes, but wouldn't it be more logical if we calculate the number of flashcards instead of the number of values of its source attribute? After all, the added flashcard exists even before we fill out its source or translation. I suggest you do the following: Let's replace the [source]
expression with [flashcard]
:
... [flashcard] ... ...
Have you noticed the difference? We still have a list, but its value is not a simple value, but an object , that is, a complex value containing all the data associated with each flashcard. The good news is that the number of these objects is equal to the number of flashcards, because each flashcard has one, even if it is completely empty. So, now we have an object for each flashcard, but how do we calculate them and display the total?
Now let's get familiar with the MavoScript function and find the function that allows us to calculate the number of flashcards. Remember, we have a flashcard list, so we need to find a function that allows us to calculate the number of items in the list . It's here - this is how the count()
function does it!
But how do we use functions in expressions? What rules do we need to pay attention to? Yes, there are several:
- Expressions are represented in square brackets.
- Do not nest brackets.
Let's try to use the count()
function to calculate the number of flashcards:
... [count(flashcard)] ... ...
That's exactly what we're after - now there are some statistics in our app! Isn't it cool?
I hope you've been warmed up and ready to continue trying Mavo.
Improve the application so that not only displays statistics on the total number of flashcards in the application, but also statistics on the number of flashcards in each topic, if any.
?hint!
Want to filter lists based on certain criteria? where
operator will help.
Self-assessment function
We already have an application that allows us to create, edit and store multiple flashcards. But how do we track which flashcards we have learned and which flashcards require more practice? Any respectable flashcard application requires a self-assessment feature. Let's look at how we add it!
Suppose in our application we have two buttons for self-evaluation: poor and good. What do we want to happen every time the end user clicks a button? Well, this idea is simple:
- Clicking the "Poor" button will indicate that the user has not learned the word yet and we want our app to move the corresponding flashcard to the beginning of the list so that they can see it as soon as possible after launching the app.
- Clicking the "OK" button will indicate that the user has learned the word, and the corresponding flashcard needs to be moved to the end of the list to allow them to use other flashcards they have not learned yet.
"Are you sure we can do this without JavaScript?" you might ask. Yes! Mavo is very powerful and can provide us with all the tools we need!
Now that we know what we are going to implement, let's set up the UI first and move on to the next step. Our tag looks like this:
... ... <h2 id="Assess-yourself">Assess yourself</h2> <button>Difference</button> <button>good</button> ...
mv-action
attribute
Mavo operations allow us to create our own controls that modify data in a custom way when the user interacts with them. Sounds promising, right?
To define custom actions, we need to use the mv-action
attribute on the corresponding elements within the Mavo application. This is performed every time the element is clicked. This is exactly what we're always looking for.
for<form> Element, custom actions are performed when the form <em>is submitted</em> . The value of <code>mv-action
property is an expression. We can use any expression functions and syntax provided to us by MavoScript, as well as some other functions to facilitate data manipulation, such as add()
, set()
, move()
, or delete()
. It is important to note that unlike ordinary expressions that are calculated in a reactive manner, these expressions are only calculated each time the operation is triggered.
Mavo expects the value of mv-action
property to be an expression, so there is no need to enclose it in parentheses : mv-action="expression"
. Furthermore, if we include them, they will be treated as parts of the expression.
Therefore, we need to move the flashcards within the collection, and Mavo has a suitable function that allows us to do this - move()
function. Its first parameter refers to the item we are moving, and the second parameter is its position in the collection. Remember that elements of a collection start from 0.
For more information about move
functions (and their variants) and custom actions, see the documentation.
Let's implement the first point of the outline we discussed earlier: During self-evaluation, the end user clicks the "poor" button and the corresponding flashcard will move to the beginning of the set, which is the first one. So in the code, we have:
... ... <button mv-action="move(this, 0)">Difference</button> ... ...
Note that in the mv-action
property, we reference the flashcard attribute inside the attribute because we want to process the current flashcard .
If we try to implement the second point of the outline, we will face a problem. Can you suggest what exactly is the problem?
Let's remember that if the end user clicks the "OK" button, the corresponding flashcard will move to the end of the collection, which is the last one. In order for flashcards to be the last in the set, we need to know the number of items in the set.
Thankfully, we have solved this task before and implemented the corresponding functions. But can we use this solution to solve our current problem? Unfortunately, we can't: As we already know, we can only reference the collection of flashcards (and evaluate its size) outside the flashcard properties. But in our case we need to do it: we need to write the expression for the "good" button inside the flashcard attribute.
So what should we do? I'm glad you asked. Mavo has solutions.
Save intermediate values using meta elements
So, on the one hand, we know that the [count(flashcards)]
expression will give us the number of flashcards when externally evaluating flashcard properties. On the other hand, we need to use the value flashcard attribute in it.
To solve this puzzle, we need to evaluate the number of flashcards in our code and save the results in some way so that we can use it elsewhere in the application, to be precise inside the flashcard properties. For this case, in Mavo, there are so-called computational properties .
In order to save the intermediate result so that we can reference it, we need the HTML element in the code. It is recommended to use it for this purpose<meta>
Elements, as shown below:<meta content="[expression]" property="propertyName">
. The advantage of using this element is that it is hidden outside of editing mode, semantically and visually.
Remember that computed properties are not saved by default.
Now let's add flashcardCount
computed property in our application. Remember, we have to put it outside the flashcard attribute, but then we can reference it from anywhere:
... <meta content="[count(flashcard)]" property="flashcardCount"> ... ...
There is only one step left to complete the implementation of the self-evaluation function: If the end user clicks the "OK" button, the corresponding flashcard will be moved to the end of the set, which will become the last one. Let's add relevant actions to the application's code:
... <meta content="[count(flashcard)]" property="flashcardCount"> ... <button mv-action="move(this, flashcardCount)">good</button> ...
We're done! Congratulations! ?
There is another way to solve this task: with the help of $all
special attribute . If $all
attribute is inside the collection , it represents the collection itself. Therefore, no computed properties are required in this case. Try to implement the solution yourself.
There is only one last thing left that we need to fix. Remember the part we added some statistics to the app? Remember the expression we built to evaluate the number of flashcards in the application: [count(flashcard)]
? Instead, we can now use (and should use) the computed properties we defined. Make corresponding changes in the application.
in conclusion
So what have we learned so far? Let's review it. In order to convert any static HTML page into a Mavo application, we need:
- On the page
Partially contains Mavo JavaScript and CSS files.
- Add
mv-app
attribute to the Mavo root element. - Tell Mavo which elements of our application are important by adding
property
attributes to them. - Place
mv-multiple
attribute on the element that will be copied and converted to a collection. - Tell Mavo where to store our data by adding the
mv-storage
attribute to the Mavo root. - Decide whether Mavo should save our data automatically. If so, add the
mv-autosave
property to the Mavo root. We also know: - The Mavo toolbar is completely customizable.
mv-bar
property controls which buttons will be displayed. - Expressions allow us to display the current value of the attribute in other elements and perform calculations. The value (and type) of an expression depends on where the expression is in the code. Mavo's expression syntax is called MavoScript.
- Custom actions allow the creation of controls that modify data in a custom way. To define custom actions on the corresponding elements within the Mavo application, set
mv-action
property. - An attribute with a value of an expression is called a computed attribute. To save the intermediate result so that it can be referenced elsewhere in the application, it is recommended to use
<meta>
element.
postscript
So we built our application. Is it perfect? Of course not, nothing is perfect! There are a lot to improve, and there are lots of features to add (we can even multilingualize our app with the help of Mavo!). Go ahead and enhance it further and don't hesitate to try something new!
So far, our knowledge of Mavo is just the tip of the iceberg, and there is a lot more to do. I encourage you to read the documentation carefully, check the examples (on the Mavo website, or on CodePen: made by Lea Verou and some made by myself), and create new content! Good luck! ?
Acknowledgements
I want to thank the two great people. First of all, I want to thank Lea Verou for not only inspiring me to write this tutorial (and help me implement it), but also always inspiring me to make the web development world a better place. I've never seen someone so talented and I'm glad to have the opportunity to do something with her!
I also thank James Moore. The examples he used in the "JavaScript functional programming for beginners" course on Udemy prompted me to make my own version of flashcard learning application. He is a great teacher!
The above is the detailed content of Let Mavo Shine in Building Interactive Web Applications. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics

It's out! Congrats to the Vue team for getting it done, I know it was a massive effort and a long time coming. All new docs, as well.

With the recent climb of Bitcoin’s price over 20k $USD, and to it recently breaking 30k, I thought it’s worth taking a deep dive back into creating Ethereum

I had someone write in with this very legit question. Lea just blogged about how you can get valid CSS properties themselves from the browser. That's like this.

The other day, I spotted this particularly lovely bit from Corey Ginnivan’s website where a collection of cards stack on top of one another as you scroll.

I'd say "website" fits better than "mobile app" but I like this framing from Max Lynch:

There are a number of these desktop apps where the goal is showing your site at different dimensions all at the same time. So you can, for example, be writing

If we need to show documentation to the user directly in the WordPress editor, what is the best way to do it?

Questions about purple slash areas in Flex layouts When using Flex layouts, you may encounter some confusing phenomena, such as in the developer tools (d...
