Reactivity?
Tags: javascript, framework, reactive, rjsf
Published: 2022-08-17 12:26:48

---
title: Reactivity?
published: true
description: A starting point for the reactive framework I'll be building in this series.
series: DIY reactive framework
tags: javascript, framework, reactive, rjsf
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1eble812kv34qoah8l1k.png
# Use a ratio of 100:42 for best results.
---

## Introduction

What am I doing here? That's a fair question.

I have been using Vue.js for years. I like it. I've also tried angular. Recently I read [this post](https://dev.to/this-is-learning/the-cost-of-consistency-in-ui-frameworks-4agi) about how different frameworks deal with state, and it got me thinking about how they might work under the hood.

Now, I know I could read through the source code, and figure it out that way, and it's possible during this series that I will, but I like tinkering and going from the abstract to the concrete by making things. So I thought I'd try to figure out how *I'd* do it.

I'd like, fair reader, for you to limit your expectations. First of all, I will not be making a CLI like any of the frameworks out there have. Secondly, this won't be as good as any of the others. So don't be surprised if when you compare mine to anyone elses, that it's slower or buggier 😋. (If you do find bugs, let me know!)

Finally, I don't get a lot of free time, so I think I will be able to manage a monthly update schedule.

On with the show!

## Step 1 &ndash; A name

I won't dwell too long on naming, as this isn't going to be a project for the ages, so I don't have to think about marketing it. I will go with a straightforward initialism: RJSF. That is: Reactive JavaScript Framework. Bland enough that noöne else will pick it, and simple to understand and remember.

If you want to see how I'm getting on, and get a sneak peak at what future articles will explore:

{% embed https://github.com/Mellen/rjsf %}

## Step 2 &ndash; can I do a reactive?

What should a reactive javascript framework do? The first thing I want to explore is how I can change the value of a variable, and have that variable be shown in the UI without having to `document.getElementById('myspan').textContent = value;`.

For this proof of concept, I'll just update the text in a span from nothing to the evergreen "Hello, World!".

As I'm sure you're aware, HTML elements have a key/value store. All attributes that start `data-` add to this store, the key being what comes after `data-`, and the value being what is stored in the attribute, for example:

``` html
<span id="example" data-flavour="cranberry">super tasty</span>
```
In the above, the key is "flavour" and the value is "cranberry". This can be accessed in javascript using the `dataset` property of the element:

```javascript
const span = document.getElementById('example');
console.log(span.dataset.flavour);
```
So, rather than coming up with my own prefix for attributes, I'm going to use this.

This is what the HTML looks like:

```html
<div id="app">
  <span data-model="message"></span>
</div>
```
Now for the reactive bit. I'll put the whole thing and then try to break it down:

``` javascript
const rjsf = (function()
{
  function AppBuilder(baseElement)
  {
    this.base = baseElement;
  }

  AppBuilder.prototype.init = function(viewmodel)
  {
    const elements = this.base.getElementsByTagName('*');
  
    this.originalViewmodel = viewmodel;
    
    this.elements = {};
    
    const _internal = this;
    
    this.data = new Proxy({}, 
    {
      get(target, name, receiver) 
      {
        if (!(name in _internal.originalViewmodel.data))
        {
          return undefined;
        }

        return _internal.originalViewmodel.data[name];
      },
      
      set(target, name, value, receiver) 
      {
        if (!(name in _internal.originalViewmodel.data))
        {
          console.warn(`Setting non-existent property '${name}', initial value: ${value}`);
        }

        _internal.originalViewmodel.data[name] = value;
        
        for(let el of _internal.elements[name])
        {
      	  _internal.updateElement(el);
        }     
        
        return _internal.originalViewmodel.data[name];
      }
    });
    
    for(let el of elements)
    {
      if('model' in el.dataset)
      {
        if(el.dataset.model in this.elements)
        {
    	  this.elements[el.dataset.model].push(el);
        }
        else
        {
      	  this.elements[el.dataset.model] = [el];
        }
        
        this.updateElement(el);
      }
    } 
    
  };

  AppBuilder.prototype.updateElement = function(el)
  {
    const property = el.dataset.model;
    el.textContent = this.data[property];
  };

  return AppBuilder;
    
})();

```

So, the first thing is our constructor:
``` javascript
function AppBuilder(baseElement)
{
  this.base = baseElement;
}
```
Basic stuff. Just like in Vue.js, you pass in the base element, the children of which will be subject to the whims of the framework.

Next up is the initialisation function `AppBuilder.prototype.init = function(viewmodel)`. The first step is a little bit of setup 

``` javascript
    const elements = this.base.getElementsByTagName('*');
```
Get all of the elements in the base element.

``` javascript  
    this.originalViewmodel = viewmodel;
``` 
Store the original data that gets passed in.

``` javascript
    this.elements = {};
```    
Create a map for the elements that will be controlled by the framework.

``` javascript
    const _internal = this;
```
Avoid any shenanigans with `this`.

Now we come to the meat and potatoes, setting up a proxy (`this.data = new Proxy(...`) of an object, intercepting getting and setting of properties:

``` javascript
get(target, name, receiver) 
{
  if (!(name in _internal.originalViewmodel.data))
  {
    return undefined;
  }

  return _internal.originalViewmodel.data[name];
},
```
Quite straight forward. If a property exists in the data section of the original viewmodel, return the value of that, otherwise return `undefined`.

``` javascript
set(target, name, value, receiver) 
{
  if (!(name in _internal.originalViewmodel.data))
  {
    console.warn(`Setting non-existent property '${name}', initial value: ${value}`);
  }

  _internal.originalViewmodel.data[name] = value;
        
  for(let el of _internal.elements[name])
  {
    _internal.updateElement(el);
  }     
        
  return _internal.originalViewmodel.data[name];
}
```
A little bit of monkey business. Assigning to things that don't currently exist in the original viewmodel data section creates them in the section, then all the elements whose `data-model` is set to the name of the property being set get updated with the new value. Finally the value is returned.

Finally the child elements of the base elements that have the `data-model` attribute set get added to the `this.elements` map:

``` javascript
for(let el of elements)
{
  if('model' in el.dataset)
  {
    if(el.dataset.model in this.elements)
    {
      this.elements[el.dataset.model].push(el);
    }
    else
    {
      this.elements[el.dataset.model] = [el];
    }
        
    this.updateElement(el);
  }
} 
```

OK! That's the biggest chunk. One last function and we can wrap up.

``` javascript
AppBuilder.prototype.updateElement = function(el)
{
  const property = el.dataset.model;
  el.textContent = this.data[property];
};
```

This is the `updateElement` method that is being called in the setter function.

It gets the model name from `data-model` and then uses that to get the value from the viewmodel's data section and write that value into the `textContent` of the supplied element.

## Step 3 &ndash; Put it into action

``` javascript
const appElement = document.getElementById('app');

const app = new rjsf(appElement);

const viewmodel = 
{
  data:
  {
    message: 'Hello, World!',
  },
};

app.init(viewmodel);
```

A quick refresher of the HTML this applies to:
``` html
<div id="app">
  <span data-model="message"></span>
</div>
```

So put it all together and you will see that the span has the value "Hello, World!"!

Next time I will investigate the `onclick` handler.

Please let me know any thoughts or questions you have in the comments below.

❤️, 🦄, share, and follow for the next instalment!