DEV Community

Rogue Paradigms
Rogue Paradigms

Posted on • Edited on

Part 2: Creating a web framework

Like I promised in my previous post, 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.

<script src="./module/.bundle/script.js"></script>
<p-frame src="./src/app.html"></p-frame>

Enter fullscreen mode Exit fullscreen mode

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

<!-- 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>

Enter fullscreen mode Exit fullscreen mode

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.

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

<!-- view controller -->
<script>
Enter fullscreen mode Exit fullscreen mode

promax is a global variable injected by p-frame component.
Note that promax.initState().setRenderer().attachScope() works only in this fixed order.

  promax
Enter fullscreen mode Exit fullscreen mode

initState sets first state and creates a closure around it to attach renderer.

    .initState(
      { buttonText: "starting"  }
    )
Enter fullscreen mode Exit fullscreen mode

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

    .setRenderer((
      {root,state }
    ) => {

      root.getElementById("button").innerHTML = state.buttonText;
    })
Enter fullscreen mode Exit fullscreen mode

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.

    .attachScope((
      { getState, patchDom }
    ) => {
      let clickCount = 0;
      return {
        // returns scope object
        clickListener: () => {
          clickCount++;
          patchDom({
            buttonText: "Clicked " + clickCount
          });
        },
      };
    });
Enter fullscreen mode Exit fullscreen mode
</script>

Enter fullscreen mode Exit fullscreen mode

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

To Do

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

Top comments (0)