Part 2: Creating a web framework
Tags: javascript, html, react, polymer
Published: 2022-03-16 06:24:17

Like I promised in my [previous post](https://dev.to/acashjos/creating-a-web-framework-in-under-an-hour-58m), I spent more time in this project to create a usable frontend framework with a javascript view controller. 

The main objective of this exercise is to create a low level view controller. This should be flexible and modular and programmable. If I write down a check list, it would look like this.

1. No transpiling/template-rendering (custom syntaxes)
2. No bundlers. Apps should be able to run straight in browser
3. Use low level apis dom apis, create custom-elements with shadow-root and maintain isolation

The decision to keep the api minimal/low-level is so that it will be possible to use other existing legacy libraries while maintaining isolation. For example, even though `promax` doesn't have a template system, one could bring their own template system and use within `promax`

This is the api I came up with:
> `index.html`. Here `p-frame` is like iframe, but it loads another html file into a shadow dom.
```html
<script src="./module/.bundle/script.js"></script>
<p-frame src="./src/app.html"></p-frame>
                                                                              
```

You can use this without the rest of promax too, just to embed any html with its own contained style and scoped scripts. (Limiting scope is what the rest of promax is about)

> `./src/app.html`
```html
<!-- UI -->
<button id="button" onclick="pscope.clickListener(event)">
  This text will be replaced by initialState value
</button>

<!-- view controller -->

<script>
  promax
    .initState({ buttonText: "starting" })
    .setRenderer(({ root, state }) => {
      root.getElementById("button").innerHTML = state.buttonText;
    })
    .attachScope(({ getState, patchDom }) => {
      let clickCount = 0;
      return {
        // returns scope object
        clickListener: () => {
          clickCount++;
          patchDom({
            buttonText: "Clicked " + clickCount,
          });
        },
      };
    });
</script>

```
This is a click counter button. At first rendering, button text is set to "starting". Then we attach an event listener to count clicks. Butten text updates to "Clicked #count" after each click
## Breakdown of `./src/app.html`

Notice the onclick=`pscope.clickListener(event)`. `pscope` is a global proxy that know how to look up the component scope object at run time. It only works for events with `target` field. It will throw error if a promax scope is not defined.

```html
<!-- UI -->
<button id="button" onclick="pscope.clickListener(event)">
  This is the welcome page!! This is also an html file. :)
</button>

<!-- view controller -->
<script>
```
`promax` is a global variable injected by `p-frame` component.
Note that `promax.initState().setRenderer().attachScope()` works only in this fixed order.
```js
  promax
```
> `initState` sets first state and creates a closure around it to attach renderer.
```js
    .initState(
      { buttonText: "starting"  }
    )
```
> `setRenderer` sets a renderer function. This is the only place you touch dom. asynchronous dom modifications are prohibited. This will also invoke renderer immediately after setting. This is also why we start with `initState`
```js
    .setRenderer((
      {root,state }
    ) => {

      root.getElementById("button").innerHTML = state.buttonText;
    })
```
> `attachScope` is where you inject values to component scope. Event listeners are defined through scope object. Currently this is the only use-case, but it has other uses.
 
```js
    .attachScope((
      { getState, patchDom }
    ) => {
      let clickCount = 0;
      return {
        // returns scope object
        clickListener: () => {
          clickCount++;
          patchDom({
            buttonText: "Clicked " + clickCount
          });
        },
      };
    });
```
```html
</script>

```

That is basically it for this exercise. The above code is a click counter button, if you didn't guess it already.

## Next episode
I am debating myself on adding a prop system to pass javascript objects, or stick with html attributes. I really don't like to add custom syntax. Most likely to stick with attributes. 

I haven't figured out how to render lists and conditional views yet. But the fact that we have a view controller is giving me great confidence.

> I rencently renamed the project to `romax`. I know this is not an objectively better name than promax, but at least it sounds less lame to me.
Github: [/bwowsersource/romax](https://github.com/bwowsersource/romax)


### To Do

- Props
- Dynamic rendering lists
- Conditional rendeing
- script scope
- promax.renderFromString and promax.renderFromTemplate