<div id="root">
  <h1>Vanilla JS - Two way binding using Proxy</h1>
  <div class="form-group">
    <div class="form-elements">
      Name: <input data-bind="username" type="text" />
      <br/>
      <span data-bind="username"></span>
      <br/><br/>
      Email: <input data-bind="email" type="text"/>
      <br/>
      <span data-bind="email"></span>
    </div>
    <div class="button-group">
      <button onclick="log()">Inspect Scope</button>
      <button onclick="changeUsernameByCode()">Change Username Scope</button>
      <button onclick="changeEmailByCode()">Change Email Scope</button>
    </div>
  </div>
  
  <div id="debug-container">
    <button id="btnClearLog" title="Clear logs" onclick="clear_logs()">x</button>
    <pre id="debug">
    </pre>
  </div>
</div>
@import url(https://fonts.googleapis.com/css?family=Roboto:400,100,500,300italic,500italic,700italic,900,300);

html{
  color: rgba(33, 33, 33, 0.87);
}
body{
  font-family: "Roboto", 'Helvetica Neue, Helvetica, Arial';
  margin:0;
  padding:0;
  background: black;
  color: white;
  font-size: 1.3em;
}
#root {
  width: 100%;
}

h1 {
  
}

.form-group{
  display: flex;
  flex-direction: row;
  flex: 3;
}
.form-elements {
  padding: 10px;
}
.form-elements span {
  color: #ff4500;
}
.button-group {
  margin: 0;
  order: -1;
  display: flex;
  flex-direction: column;
  font-size: 1rem;
  height: 100vh;
  background: #2b0c46;
/*   flex-wrap: wrap; */

}
.button-group button {
  margin: 5px;
}

#debug-container {
  position: fixed;
  left: 0;
  min-height: 40px;
  max-height: 20vh;
  bottom: 0;
  width: 100%;
  background: #0e6b0e;
  margin: 0;
  padding: 10px;
  border:0;
  overflow-y: auto;
}

#debug {
  position: relative;
  overflow-wrap: break-word;
  overflow-y: auto;
  overflow-x: hidden;
  min-height: 20vh;
  max-height: 20vh;
}

#btnClearLog {
  position:fixed;
  right: 10px;
  bottom:135px;
  width:25px;
  height:25px;
}
console.clear();
/* proxy code */

const handler = {
  get: function(obj, prop) {
    return obj[prop] ;
  },
  set: function(obj, prop, value) {
    obj[prop] = value;
    elms.forEach((elm) => {
      if (elm.getAttribute("data-bind") == prop) {
        // Only supporting text and textarea
        //   => Feel free to add support for more
        if (elm.type && (elm.type === "text" || elm.type === "textarea")) {
          elm.value = value;
        } else if (!elm.type) {
          elm.innerHTML = value;
        }
      }
    })
    return true;
  }
};


let elms = document.querySelectorAll("[data-bind]");
let debug = document.querySelector("#debug");
debug.innerHTML = "";
let scope = {};  // stores data
scope = new Proxy(scope, handler);

elms.forEach((elm) => {
  if (elm.type === "text" || elm.type === "textarea") {
    let propToBind = elm.getAttribute("data-bind");
    elm.addEventListener("keyup", (e) => {
      scope[propToBind] = elm.value;  // proxy set method fires
    });
  }
});

var clear_logs = function () {
  debug.innerHTML = "";
}

const log = function () {
  Object.keys(scope).forEach((k) => {
    debug.innerHTML += JSON.stringify(scope) + "<br/>";
  });
  debug.scrollTop = debug.scrollHeight;
}

const changeUsernameByCode = function () {
  scope.username = "username Changed by Code";
}

const changeEmailByCode = function () {
  scope.email = "email changed by Code";
}



External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.