# Growing Software, Simply
Everybody knows that making software is difficult, complex. It is best left to professional developers who dedicate their entire life to gaining arcane knowledge. Certainly not something just anyone can learn to do, let alone do well.
Now imagine that all that is a load of crap.
There is no reason that making useful software should be so complex, so difficult, that most people can't do this with just a little effort.
But we've made it complex. There are many reasons for this, which people smarter than me have written about over the last decades. Instead here I will show how you can grow software simply.
We'll build a number of web applications, since that is what I know best and is probably the most common software build today. We'll use a number of libraries, bundles of predefined functionality, built by Muze, specifically to be simple.
_Simple_ is defined as 'easy to replace', and 'few abstractions' (or 'moving parts'). This also has the benefit of usually resulting in small code sizes, which has all kinds of extra benefits.
The libraries themselves may contain code that is difficult to follow, using arcane features of webbrowsers. But since the code size isn't large, if you want to, you can still peek under the hood and get a deep understanding if you want to. You shouldn't have to, though.
## your first web app
This chapter assumes you are somewhat familiar with HTML. It should still be easy enough to follow without that knowledge, but you'll get more out of it if you also study the basics of HTML.
Any web application starts with a bare HTML page. Something like this will do:
```htmlmixed!
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>My first webapp</title>
<body>
<h1>My first webapp</h1>
</body>
</html>
```
This sets up a few things:
- the first line tells the browser that this page uses modern HTML5, not any of the earlier HTML standards. This will make sure that your page will look mostly identical across multiple webbrowsers.
- The meta tag on line two makes sure that the browser knows that all characters in the page use the UTF-8 encoding. This is generally the default, but it can't hurt to be explicit. It avoids all kinds of mangling of special characters, like `€` for example.
- Then the title tag declares a title for the page, used in the browser tab or window name.
- Finally it adds the same title as a heading in the body of the page. The body is the part of the page rendered in the browser window.
Clearly this application doesn't do much yet. Applications should be interactive, otherwise its just a document. However it is good to realize that webbrowsers don't really make a difference between HTML documents and web apps. It's all the same to them, and there is no clear separation. Web pages and web applications exist on a spectrum. Some are more page-like, and some more application-like.
Lets create a simple counter. The idea is that the app will show a current count. You can press a button to increase the count. And there is another button to reset the count.
To implement this, we'll need some javascript, the native programming language of the web. Since it is such a simple application, you don't really need any library, you can just use what we call _vanilla_ javascript. Meaning, javascript without any other libraries.
However, we won't do that. We'll use two of the main libraries Muze developed, because this will mimic the structure of more complex applications later. So lets add these libraries, like this:
```htmlmixed!
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>My first webapp</title>
<body>
<h1>My first webapp</h1>
<script type="importmap">
{
"imports": {
"SimplyView":
"https://cdn.jsdelivr.net/npm/simplyview@3.4.0/src/everything.mjs",
"SimplyFlow":
"https://cdn.jsdelivr.net/npm/simplyflow@0.5.0/src/flow.mjs"
}
}
</script>
<script type="module">
// your application code goes here
</script>
</body>
</html>
```
This makes sure that the libraries _SimplyView_ and _SimplyFlow_ will be available to use in our application code.
The `importmap` script contains JSON (JavaScript Object Notation), which tells the browser where to find the source code for the named libraries.
The application code will be:
```javascript!
import 'SimplyView'
import 'SimplyFlow'
const myCounterApp = simply.app({
commands: {
add: function() {
this.state.count++
},
reset: function() {
this.state.count = 0
}
},
state: simply.state.signal({
count: 0
})
})
simply.bind({
root: myCounterApp.state,
attribute: 'data-flow'
})
```
The first 2 lines declare libraries we will use throughout this manual, to help make building web applications simple. We'll get into the details of these libraries, and how to use them later.
The actual code defines an application `myCounterApp`, which has two commands: `add` and `reset`. It also has a variable called `count`, which these commands operate on.
What it doesn't yet do, is show the add and reset buttons, and the current count. For this, we need to add the following html to the body:
```htmlmixed!
<body>
<h1>My first webapp</h1>
<div>
<label>Current count:</label>
<output data-flow-field="count"></output>
<button data-simply-command="add">Add 1</button>
<button data-simply-command="reset">Reset</button>
</div>
```
Combined with your application code, the output will now magically show the current count. Pressing the _Add 1_ button will increase the count. Pressing the _Reset_ button will reset the count to 0.
The magic is in two parts.
First there is the `simply.bind()` function, which I have not yet discussed. Bind takes care of updating the browser view of a specific set of data, linked to by the `root` parameter to `simply.bind()`.
When you create a html element with a `data-flow-field="count"` attribute, the bind library, as called here, will update the contents of that html element whenever `count` changes. This will happen automatically. This is called _data-binding_. We'll use it a lot in later chapters and go more deeply into what you can do with it there.
The second part is in tying the buttons to your code. You do this by adding the `data-simply-command="add"` attribute to a button. The `simply.app()` function creates a new _application_. When it does, it also adds a bit of code that listens for button presses (among other things). If it detects a button press on a button with the `data-simply-command` attribute, it looks for a command in the application with the same name, and then calls it.
Since the `add` command increases the myCounterApp.state.count variable, and the `simply.bind()` code has tied this variable to the HTML `output` element, whenever this variable changes, the output is automatically updated.
### Recap
Web applications are just a web page with some added javascript.
By using a few well-chosen simple libraries, you can create interactive applications without any complex code.
The `data-flow-field` attribute makes javascript variables appear in your HTML content.
The `data-simply-command` attribute allows you to call application functions when a user presses a button.
## Building a web application with an API
_This chapter assumes you have some knowledge of HTML._
For the next application, we'll build a custom news reader. The news is collected from an existing website with its own API (Application Programming Interface). The site in question is `https://news.ycombinator.com/`, more commonly known as 'Hackernews'. There is another site that provides an easy to use API for it: 'https://api.hackerwebapp.com/'. Check out [hackererwebapp](https://hackerwebapp.com) for more information. If you are not afraid of some JSON, take a look at https://api.hackerwebapp.com/newest -- this shows you the raw data that we'll work with.
The first step is to create a basic application web page, like this:
```htmlmixed!
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>Simply Hackernews</title>
<body>
<main>
<h1>Latest Hackernews</h1>
</main>
<script type="importmap">
{
"imports": {
"SimplyView":
"https://cdn.jsdelivr.net/npm/simplyview@3.4.0/src/everything.mjs",
"SimplyFlow":
"https://cdn.jsdelivr.net/npm/simplyflow@0.5.0/src/flow.mjs"
}
}
</script>
<script type="module">
import 'SimplyView'
import 'SimplyFlow'
const hnpwa = simply.app({
commands: {
},
state: signal({
})
})
simply.bind({
root: hnpwa.state,
attribute: 'data-flow'
})
</script>
</body>
</html>
```
This is almost the same as the previous counter app. There is one difference, I've now called the app `hnpwa`, which stands for 'HackerNews Progressive Web App'. I'll explain what a PWA is later.
Now we need to get some data from the remote API. This requires a few things, a way to connect over the internet, call the correct URL, check and parse the response. That is quite a bit of work, where a lot can go wrong. So lets introduce a new hero: [MetroJS](https://github.com/muze-nl/metro/).
Using this adding support for a remote API is a breeze. First load the library:
```htmlmixed!
<script type="importmap">
{
"imports": {
"SimplyView":
"https://cdn.jsdelivr.net/npm/simplyview@3.4.0/src/everything.mjs",
"SimplyFlow":
"https://cdn.jsdelivr.net/npm/simplyflow@0.5.0/src/flow.mjs",
"@muze-nl/metro":
"https://cdn.jsdelivr.net/npm/@muze-nl/metro@0.6.13/src/everything.mjs"
}
}
</script>
<script type="module">
import 'SimplyView'
import 'SimplyFlow'
import '@muze-nl/metro'
```
The [metro library](https://github.com/muze-nl/metro/blob/main/README.md) contains a number of useful functions, we're just using `metro.jsonApi` here. This makes it easy to create a client-side (browser) api to call the remote api.
In addition, we'll use the standard browser class [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams), which makes it easy to add parameters to a URL, e.g. `https://api.hackerwebapp.com/news?page=1`
```javascript=
const hnpwa = simply.app({
commands: {
},
state: simply.state.signal({
}),
api: metro.jsonApi(
'https://api.hackerwebapp.com/',
{
list: function(section, page=1) {
return this.get(section, new URLSearchParams({ page }))
}
}
)
})
```
This is the low level API sorted, now we need to call it and show the results. For this we'll start with adding a 'route' and an 'action'.
Routes are a way to match the URL of the browser and run some javascript code that is tied to specific URL. In this case, the route is `/`.
Actions are simply functions you can call from a route, or other parts of the application, which I'll introduce later.
```javascript=
const hnpwa = simply.app({
commands: {
},
routes: {
'/': function() {
this.actions.list('news')
}
},
actions: {
list: async function(section, page=1) {
this.state.items = await this.api.list(section, page)
}
},
...
```
The route `/` simply calls an _action_ named 'list', which calls the 'api.list' method and stores the result in `hnpwa.state.items`.
Now all that is left to do, is make sure those items show up in the HTML:
```html
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>Simply Hackernews</title>
<body>
<main>
<h1>Latest Hackernews</h1>
<ul data-flow-list="items">
<template>
<li>
<span data-flow-field="title">title</span>
</li>
</template>
</ul>
</main>
```
Here we've added new attribute `data-flow-list` and a new HTML element `<template>`. These work together to fill the list of items as `<li>` elements in the dom. Each item in `hnpwa.state.items` is a list item in the DOM (HTML). Whatever you enter inside the `<template>` tag, will get added for each item. Any `data-flow-field` value inside this template, is automatically applied to each separate item in the list. So the first item is actually rendered as:
```htmlmixed!
<li>
<span data-flow-field="items.0.title">A title</span>
</li>
```
You can see a [demo of this code in codepen](https://codepen.io/poef/pen/abBMpKN?editors=1111)
### Recap
TODO: fill this
## Switching pages
Most web applications today are whats called _Single Page Applications_ or 'SPA' for short. This means that your browser loads a single web page and this page will behave as an application. In contrast, the default website is a _Multiple Page Application_, in that each link you follow loads an entire new web page.
The drawbacks of an SPA are that you have to think about navigating between different virtual pages, instead of letting the browser do the work for you.
However, the advantage is that you can keep track of a lot of ephemeral state, stuff that is relevant to the current user interaction, without having to store it somewhere, like in a URL. You can just use variables to store this stuff.
For the Hackernews PWA we'll want to show a specific news item, and the comments people have posted for it. This is all included in the data from the hackerwebapp api, we just need to show it.
First we need to get the full information for a specific news item. The hackerweb api has an _endpoint_ (a specific url) you can call to get that information, if you know the news item's id (indentity). This is already returned in the list api. So lets add a new API method to return that information:
```javascript!
const hnpwa = simply.app({
...,
api: metro.jsonApi(
'https://api.hackerwebapp.com/',
{
list: function(section, page=1) {
return this.get(section, new URLSearchParams({ page }))
},
item: function(id) {
return this.get('item/'+id)
}
}
)
})
```
Then we need a way to let a user trigger that api function, and show the results. The best way in this case is to add a route, and turn the item titles into links, which will trigger that route.
The simplest way, which will work everywhere, is to use routes (or links) which start with a hash ('#'). Anything in a URL after the '#' symbol doesn't actually instruct the browser to load or reload a webpage. Instead, you can handle the change of the URL in javascript. So lets do that.
First, add a link to the title:
```htmlmixed!
<a data-flow-field="id" data-flow-transform="comments_link">
<span data-flow-field="title">title</span>
</a>
```
We can't just add a `data-flow-field="url"` around the `<span data-flow-field="title">` element, since the url content will overwrite the title span. However, we can add a _transformer_, which is a function that will take the normal input to be rendered and alter it in some way.
The transformer called here, `comments_link`, must be specified in the call to the `bind()` function:
```javascript!
simply.bind({
root: hnpwa.state,
attribute: 'data-flow',
transformers: {
comments_link: function(context, next) {
context.value = {
href: '#comments/'+context.value
}
next(context)
}
}
})
```
This `comments_link` transformer function takes two arguments: the render context, and a `next` transformer function to call.
The render context has all the information you need to decide what you want to do. In this case, I'm making sure that the only information the next transformer, the default anchor transformer, has, is the `href` attribute. So it will only update that attribute, and leave the innerHTML alone.
I'll explain transformers in depth later on.
Now that the titles have a link, lets add a route that handles that link:
```javascript!
var hnpwa = simply.app({
routes: {
'#comments/:id': async function(params) {
this.actions.comments(params.id)
},
...
```
And an action:
```javascript!
actions: {
...,
comments: async function(commentId) {
this.state.item = await this.api.item(commentId)
}
...
```
And an api method:
```javascript!
...,
api: metro.jsonApi(
'https://api.hackerwebapp.com/',
{
list: function(section, page=1) {
return this.get(section, new URLSearchParams({ page }))
},
item: function(id) {
return this.get('item/'+id)
}
}
)
...
```
If you follow the logic, it works like this. In the `list` api, the data that is returned contains a list of items. Each item has a title and an id.
The id is rendered in HTML as an anchor (link) with an `href` attribute equal to `#comments/43928` for example, if the value in item.id is 43928.
When you clink this link, SimplyView finds a matching route named `#comments/:id`, and will call the matching function with a params object like this:
```javascript!
{
id: 43928
}
```
This means the route function can call the comments action, with the correct item id. Which will call the api item method, with the same id. The result of that api call is then saved in the `hnpwa.state.item` variable.
Now all we need to do is to render that item, which incidentally also contains all the comments for it.
First thing is to define a way to switch between the list of items view we've been using so far, and the item and comments view we'll need now. I'll use a variable named `page` for this. It will start with the value `list`, which will show the list of items used so far. But if we set `page` to `item`, it will show the item and comments instead. Here's how to do that:
```htmlmixed!
<div data-flow-field="page">
<template data-flow-match="list">
<ul data-flow-list="items">
<template>
...
</template>
</ul>
</template>
<template data-flow-match="item">
<h1 data-flow-field="item.title"></h1>
</template>
</div>
```
And now just set the value of `page` correctly in the actions:
```javascript!
...
actions: {
list: async function(section, page=1) {
this.state.items = await this.api.list(section, page)
this.state.page = 'list'
},
comments: async function(commentId) {
this.state.item = await this.api.item(commentId)
this.state.page = 'item'
}
}
```
Take a look at [the running code here](https://codepen.io/poef/pen/YzpmZRZ).
### Recap
You can create hyperlinks, or anchors (`<a>` tags) which when clicked will trigger a route. This way you can handle what happens when a user clicks on a link, instead of letting the browser do its normal thing.
By adding a `<template data-flow-match="value">` element inside a `data-flow-field` you can make the field variable switch which template is rendered, depending on the value of the variable.
## Adding a menu
The hackernews website, `https://news.ycombinator.com/`, has different sections with different news feeds. The main ones are: 'new', 'ask', 'show' and 'jobs'. And ofcourse the default feed is 'news'. In this chapter I'll add a menubar above the news feed and comments, which allows you to switch between these feeds. The last pressed menu item will be highlighted, and stay highlighted.
Since the list is fixed, we can just create a menubar in HTML, with fixed sections:
```
<header>
<nav>
<ul>
<li><a href="#news">top</a></li>
<li><a href="#newest">new</a></li>
<li><a href="#show">show</a></li>
<li><a href="#ask">ask</a></li>
<li><a href="#jobs">jobs</a></li>
</ul>
</nav>
</header>
```
And with a bit of CSS magic, add the toolbar to the top of the page, and make sure it stays there
```css
header {
position: sticky;
top: 0;
}
header nav {
background-color: #4700ff;
color: white;
}
header ul {
list-style: none;
display: flex;
flex-direction: row;
}
header li {
margin: 0;
padding: 0;
flex-grow: 1;
}
header a {
display: block;
height: 50px;
line-height: 40px;
text-align: center;
text-decoration: none;
}
```
The whole menu is contained inside the `header` element, so by prefixing the css with `header`, these CSS styles will only be applied there.
The header will stay visible on top, because of the `position: sticky` style, combined with `top: 0`.
The menu items must be displayed in a single row, but the `ul` element is displayed as a stack of rows by default. The `display: flex;` and `flex-direction: row;` styles change that.
Then I get rid of the default list item bullets, and padding and margin for `header li`. I also set `flex-grow: 1`, which makes all list items equal width.
Finally I make sure that the links fill the entire width of each list item by setting `display: block`, and setting a fixed height and line-height. I'm leaving a bit of space below each link, so I can indicate the current active menu item later.
With the toolbar visible, now is the time to make the links actually do something. For that we add a route for each link. The routes we had so far were:
- `/` - to show the list of news items
- `#comments/:id` - to show the selected item and its comments.
I could just add 5 new routes, one for each link, and that will work. But I can get away with a single, smarter route like this:
```javascript!
const hnpwa = simply.app({
routes: {
'#comments/:id': async function(params) { ... },
'#:section': async function(params) {
this.actions.list(params.section)
},
'/': async function() { ... }
}
})
```
The order is important here. The routes will get checked from first to last. The first route that matches will get called. If I put the `#:section` route first, it would also match a link like this: `#comments/1`. So I put that route first.
The menu is now working, but it would be nice to highlight the selected section. I could add some code in the `list` action to search for a link in the header which matches the given `section`. But as a rule, I try not to do direct DOM (HTML) manipulation in an action. Instead I just add a state variable to indicate the selected section, like this:
```javascript!
actions: {
list: async function(section, page=1) {
this.state.items = await this.api.list(section, page)
this.state.page = 'list'
this.state.section = section
},
...
```
And then I create whats called an `effect`. That is a function that automatically runs when a state variable changes. But only if the effect is actually using that state variable.
```javascript!
simply.state.effect(() => {
const section = hnpwa.state.section
const menu = document.querySelector('header nav ul')
menu.querySelector('.active')
?.classList.remove('active')
menu.querySelector('[href="#'+section+'"')
?.classList.add('active')
})
```
Now whenever any code in changes the `hnpwa.state.section` variable, this effect function is run automatically. I don't have to remember to call an menu update function every time I change the section. And this means, I can't forget to do that either.
The code itself first uses the `section` state variable. It is important to keep that line _inside_ the effect function. If you move it outside, the effect no longer 'sees' that it should run when that variable changes.
The next line looks up the menu in the DOM (HTML).
Then the next line removes the `active` class from the menu item that was selected. Because initially I haven't added a `active` class to any menu item, the `querySelector` may return `null`. Which means there is no active menu item. To allow for this, I've used the `?.` operator, which checks that there is a result before getting the `classList` property. If there isn't, the whole remainder of that line of code is skipped.
Then I search for a menu item with a link to the new section. This shouldn't fail, but I'm pessimistic and defensive, so I've used the `?.` operator here as well. The CSS query I've used is called an _attribute_ selector. And it only matches links with an `href` attribute that is an exact match for the section.