input value does not change after keypress event

Viewed 1254

I am building/experimenting with building my own small framework.

Well in the end I pass the element's value to the keypress method and it works but with the problem that I always have my initial value of test.

So why isn't console.log showing me my updated text?

let initialProps = {
   change(text) {
      console.log(text);
   }
}

document.querySelectorAll("[keypress]").forEach(element => {
  this.keyHandler(element);
});
function keyHandler(element) {
  let handler = element.attributes.keypress.nodeValue;

  element.addEventListener(
    "keypress",
    initialProps[handler].bind(
      null,
      element.nodeName === "INPUT" ? element.value : undefined
    )
  );
}
<input keypress="change" value="test" type="text" />

Here is the full code

setTimeout(()=>{
  new Tinyflow({
    name: "Max",
    age: 21,
    state: true,
    text: "",
    myTrans: "slide",
    increaseAge() {
      this.age++
    },
    toggle() {
      this.state = !this.state
    },
    changedText(text){
      this.text = text;
    },
    observers: {
      age(oldVal) {   
      }
    }
  });
})








class Tinyflow {
  constructor(properties) {
    console.time();
    this.reactivityHandler = {
      get: function(obj, prop) {
        return obj[prop];
      }.bind(this),
      set: function(obj, prop, newVal) {
        let oldVal = obj[prop];
        obj[prop] = newVal;
        if (prop in this.reactiveProps["observers"]) {
          this.reactiveProps["observers"][prop].call(
            this.reactiveProps,
            oldVal
          );
        }

        let i = this.reactiveElements.findIndex(el => el.var === "$" + prop);
        if (i !== -1) {
          this.renderSingleNode(i, newVal);
        }

        if (prop in this.showElements) {
          this.updateVisibility(this.showElements[prop], newVal);
        }
      }.bind(this)
    };
    if ("observers" in properties) {
      if (typeof properties["observers"] !== "object")
        throw Error("Observers needs to be an Object");
      for (let prop in properties["observers"]) {
        if (typeof properties["observers"][prop] !== "function")
          throw Error("An property inside observers is not an method: " + prop);
        if (!(prop in properties))
          throw Error(
            "Observer " +
              prop +
              " has nothing to observer. Add an property to observe"
          );
      }
    }
    this.initialProps = properties;
    this.reactiveProps = new Proxy(this.initialProps, this.reactivityHandler);
    this.reactiveElements = [];
    this.showElements = {};
    this.listenerRemover = null;
    this.collectHandlers();
    this.collectVariables();
    this.renderAllNodes();
    this.collectShowElements();
    console.timeEnd();
  }
  updateVisibility(elements, state) {
    if (state) {
      elements.forEach(elementProperties => {
        let { displayProperty, element } = elementProperties;
        let cssClassAvailable = element.attributes.trans;

        if (cssClassAvailable) {
          element.removeEventListener("transitionend", this.listenerRemover);
          let cssClass = this.reactiveProps[cssClassAvailable.nodeValue];
          setTimeout(() => {
            element.classList.remove(cssClass);
          });
          element.style.setProperty("display", displayProperty);
        } else {
          element.style.setProperty("display", displayProperty);
        }
      });
    } else {
      elements.forEach(elementProperties => {
        let { element } = elementProperties;
        let cssClassAvailable = element.attributes.trans;

        if (cssClassAvailable) {
          let cssClass = this.reactiveProps[cssClassAvailable.nodeValue];

          element.classList.add(cssClass);
          function removeListener() {
            element.style.setProperty("display", "none");
            element.removeEventListener("transitionend", this.listenerRemover);
          }
          this.listenerRemover = removeListener;
          element.addEventListener("transitionend", this.listenerRemover);
        } else {
          element.style.setProperty("display", "none");
        }
      });
    }
  }
  collectShowElements() {
    document.querySelectorAll("[show]").forEach(element => {
      let attributes = element.attributes;
      let stateVar = attributes.show.nodeValue;

      let displayProperty =
        window.getComputedStyle(element).getPropertyValue("display") === "none"
          ? "block"
          : window.getComputedStyle(element).getPropertyValue("display");

      if (this.showElements[stateVar]) {
        this.showElements[stateVar].push({
          element,
          displayProperty
        });
        if (!this.initialProps[stateVar]) {
          element.style.setProperty("display", "none");
        }
        return;
      }
      this.showElements[stateVar] = [
        {
          element,
          displayProperty
        }
      ];
      if (!this.initialProps[stateVar]) {
        element.style.setProperty("display", "none");
      }
    });
  }
  renderSingleNode(index, newVal) {
    let elements = this.reactiveElements;
    let value = elements[index].varValue;
    if (value === newVal) return;
    elements[index].varValue = newVal;

    elements[index].reference.forEach(ref => {
      let original = ref.originalText;
      ref.vars.forEach(vari => {
        let varWithoutDollar = vari.replace("$", "");
        let regex = new RegExp("\\" + vari, "g");
        if (varWithoutDollar === newVal) {
          original = original.replace(regex, newVal);
          return;
        }

        original = original.replace(regex, this.initialProps[varWithoutDollar]);
      });
      ref.element.innerHTML = original;
    });
  }
  renderAllNodes() {
    this.reactiveElements.forEach(element => {
      let variable = element.var;
      let varWithoutDollar = element.var.replace("$", "");

      element.reference.forEach(ref => {
        let regex = new RegExp("\\" + variable, "g");
        ref.element.innerHTML = ref.element.innerHTML.replace(
          regex,
          this.initialProps[varWithoutDollar]
        );
      });
    });
  }
  collectVariables() {
    document.querySelectorAll("[reactive]").forEach(element => {
      let vars = element.innerHTML.match(/(\$\w+)/g).reduce((a, v) => {
        if (!a.includes(v)) {
          a.push(v);
        }
        return a;
      }, []);

      vars.forEach(variable => {
        let i = this.reactiveElements.findIndex(el => el.var === variable);
        if (i === -1) {
          this.reactiveElements.push({
            reference: [{ element, originalText: element.innerHTML, vars }],
            var: variable,
            varValue: null
          });
          return;
        }
        this.reactiveElements[i].reference.push({
          element,
          originalText: element.innerHTML,
          vars
        });
      });
    });
  }

  clickHandler(element) {
    let handler = element.attributes.click.nodeValue;
    if (!this.initialProps[handler]) return;
    element.addEventListener(
      "click",
      this.initialProps[handler].bind(this.reactiveProps)
    );
  }
  changeHandler(element) {
    let handler = element.attributes.change.nodeValue;
    if (!this.initialProps[handler]) return;
    element.addEventListener(
      "change",
      this.initialProps[handler].bind(this.reactiveProps)
    );
  }
  //problem down here ------------------------------------------
  keyHandler(element) {
    let handler = element.attributes.keypress.nodeValue;
    if (!this.initialProps[handler]) return;

    element.addEventListener(
      "keypress",
      this.initialProps[handler].bind(
        this.reactiveProps,
        element.nodeName === "INPUT" ? element.value : undefined
      )
    );
  }

  collectHandlers() {
    document.querySelectorAll("[click]").forEach(element => {
      this.clickHandler(element);
    });
    document.querySelectorAll("[change]").forEach(element => {
      this.changeHandler(element);
    });
    document.querySelectorAll("[keypress]").forEach(element => {
      this.keyHandler(element);
    });
  }
}
#testapp {
    height: 200px;
    width: 200px;
    background: red;

  }

  .box {
    height: 300px;
    width: 200px;
    position: fixed;
    background: green;
    transition: left 500ms;
    transform: translateY(-50%);
    top: 50%;
    left: 0;
    height: 20vh;
  }

  .slide {
    left: -200px;
  }
<body>
  <div id="box">
    <p reactive>Hello. My name is: $name. I am $age old</p>
    <p reactive>my text: $text </p>
    <button click="increaseAge">Increase Age</button>
    <button click="toggle">Toggle</button>
    <input value="sdf" keypress="changedText" type="text">
    <div show="state" trans="myTrans" class="box"></div>
  </div>
</body>

UPDATE: Thank you guys here is the working code:

setTimeout(()=>{
  new Tinyflow({
    name: "Max",
    age: 21,
    state: true,
    text: "",
    myTrans: "slide",
    increaseAge() {
      this.age++
    },
    toggle() {
      this.state = !this.state
    },
    changedText(text){
      this.text = text;
    },
    observers: {
      age(oldVal) {   
      }
    }
  });
})








class Tinyflow {
  constructor(properties) {
    console.time();
    this.reactivityHandler = {
      get: function(obj, prop) {
        return obj[prop];
      }.bind(this),
      set: function(obj, prop, newVal) {
        let oldVal = obj[prop];
        obj[prop] = newVal;
        if (prop in this.reactiveProps["observers"]) {
          this.reactiveProps["observers"][prop].call(
            this.reactiveProps,
            oldVal
          );
        }

        let i = this.reactiveElements.findIndex(el => el.var === "$" + prop);
        if (i !== -1) {
          this.renderSingleNode(i, newVal);
        }

        if (prop in this.showElements) {
          this.updateVisibility(this.showElements[prop], newVal);
        }
      }.bind(this)
    };
    if ("observers" in properties) {
      if (typeof properties["observers"] !== "object")
        throw Error("Observers needs to be an Object");
      for (let prop in properties["observers"]) {
        if (typeof properties["observers"][prop] !== "function")
          throw Error("An property inside observers is not an method: " + prop);
        if (!(prop in properties))
          throw Error(
            "Observer " +
              prop +
              " has nothing to observer. Add an property to observe"
          );
      }
    }
    this.initialProps = properties;
    this.reactiveProps = new Proxy(this.initialProps, this.reactivityHandler);
    this.reactiveElements = [];
    this.showElements = {};
    this.listenerRemover = null;
    this.collectHandlers();
    this.collectVariables();
    this.renderAllNodes();
    this.collectShowElements();
    console.timeEnd();
  }
  updateVisibility(elements, state) {
    if (state) {
      elements.forEach(elementProperties => {
        let { displayProperty, element } = elementProperties;
        let cssClassAvailable = element.attributes.trans;

        if (cssClassAvailable) {
          element.removeEventListener("transitionend", this.listenerRemover);
          let cssClass = this.reactiveProps[cssClassAvailable.nodeValue];
          setTimeout(() => {
            element.classList.remove(cssClass);
          });
          element.style.setProperty("display", displayProperty);
        } else {
          element.style.setProperty("display", displayProperty);
        }
      });
    } else {
      elements.forEach(elementProperties => {
        let { element } = elementProperties;
        let cssClassAvailable = element.attributes.trans;

        if (cssClassAvailable) {
          let cssClass = this.reactiveProps[cssClassAvailable.nodeValue];

          element.classList.add(cssClass);
          function removeListener() {
            element.style.setProperty("display", "none");
            element.removeEventListener("transitionend", this.listenerRemover);
          }
          this.listenerRemover = removeListener;
          element.addEventListener("transitionend", this.listenerRemover);
        } else {
          element.style.setProperty("display", "none");
        }
      });
    }
  }
  collectShowElements() {
    document.querySelectorAll("[show]").forEach(element => {
      let attributes = element.attributes;
      let stateVar = attributes.show.nodeValue;

      let displayProperty =
        window.getComputedStyle(element).getPropertyValue("display") === "none"
          ? "block"
          : window.getComputedStyle(element).getPropertyValue("display");

      if (this.showElements[stateVar]) {
        this.showElements[stateVar].push({
          element,
          displayProperty
        });
        if (!this.initialProps[stateVar]) {
          element.style.setProperty("display", "none");
        }
        return;
      }
      this.showElements[stateVar] = [
        {
          element,
          displayProperty
        }
      ];
      if (!this.initialProps[stateVar]) {
        element.style.setProperty("display", "none");
      }
    });
  }
  renderSingleNode(index, newVal) {
    let elements = this.reactiveElements;
    let value = elements[index].varValue;
    if (value === newVal) return;
    elements[index].varValue = newVal;

    elements[index].reference.forEach(ref => {
      let original = ref.originalText;
      ref.vars.forEach(vari => {
        let varWithoutDollar = vari.replace("$", "");
        let regex = new RegExp("\\" + vari, "g");
        if (varWithoutDollar === newVal) {
          original = original.replace(regex, newVal);
          return;
        }

        original = original.replace(regex, this.initialProps[varWithoutDollar]);
      });
      ref.element.innerHTML = original;
    });
  }
  renderAllNodes() {
    this.reactiveElements.forEach(element => {
      let variable = element.var;
      let varWithoutDollar = element.var.replace("$", "");

      element.reference.forEach(ref => {
        let regex = new RegExp("\\" + variable, "g");
        ref.element.innerHTML = ref.element.innerHTML.replace(
          regex,
          this.initialProps[varWithoutDollar]
        );
      });
    });
  }
  collectVariables() {
    document.querySelectorAll("[reactive]").forEach(element => {
      let vars = element.innerHTML.match(/(\$\w+)/g).reduce((a, v) => {
        if (!a.includes(v)) {
          a.push(v);
        }
        return a;
      }, []);

      vars.forEach(variable => {
        let i = this.reactiveElements.findIndex(el => el.var === variable);
        if (i === -1) {
          this.reactiveElements.push({
            reference: [{ element, originalText: element.innerHTML, vars }],
            var: variable,
            varValue: null
          });
          return;
        }
        this.reactiveElements[i].reference.push({
          element,
          originalText: element.innerHTML,
          vars
        });
      });
    });
  }

  clickHandler(element) {
    let handler = element.attributes.click.nodeValue;
    if (!this.initialProps[handler]) return;
    element.addEventListener(
      "click",
      this.initialProps[handler].bind(this.reactiveProps)
    );
  }
  changeHandler(element) {
    let handler = element.attributes.change.nodeValue;
    if (!this.initialProps[handler]) return;
    element.addEventListener(
      "change",
      this.initialProps[handler].bind(this.reactiveProps)
    );
  }
  //problem down here ------------------------------------------
  keyHandler(element) {
    let handler = element.attributes.keypress.nodeValue;
    if (!this.initialProps[handler]) return;

    element.addEventListener(
      "keyup",(event) =>
      this.initialProps[handler].call(
        this.reactiveProps,
        element.nodeName === "INPUT" ? event.target.value : undefined
      )
    );
  }

  collectHandlers() {
    document.querySelectorAll("[click]").forEach(element => {
      this.clickHandler(element);
    });
    document.querySelectorAll("[change]").forEach(element => {
      this.changeHandler(element);
    });
    document.querySelectorAll("[keypress]").forEach(element => {
      this.keyHandler(element);
    });
  }
}
#testapp {
    height: 200px;
    width: 200px;
    background: red;

  }

  .box {
    height: 100px;
    width: 100px;
    position: fixed;
    background: green;
    transition: left 500ms;
    transform: translateY(-50%);
    top: 70%;
    left: 0;
    height: 20vh;
  }

  .slide {
    left: -200px;
  }
<body>
  <div id="box">
    <p reactive>Hello. My name is: $name. I am $age old</p>
    <p reactive>my text: $text </p>
    <button click="increaseAge">Increase Age</button>
    <button click="toggle">Toggle</button>
    <input value="sdf" keypress="changedText" type="text">
    <div show="state" trans="myTrans" class="box"></div>
  </div>
</body>

3 Answers

You could try something along these lines.

Basically capture the input event and get the value after that event. I prefer using 'input' event since it also updates on paste, undo, redo, and so on.

document.getElementById("ip").addEventListener('input', function(e) {
  console.log(e.target.value);
});
<input type="text" id="ip">

Why it does not work?

Because element.value will always return value inited in html code

Solution

  • Pass event.target.value to function.
  • Dont' bind, call event handler

Bonus

Subscribe to "keyup" event instead of "keypress" for better state sync

let initialProps = {
   change(text) {
      console.log(text);
   }
}

document.querySelectorAll("[keypress]").forEach(element => {
  this.keyHandler(element);
});
function keyHandler(element) {
  let handler = element.attributes.keypress.nodeValue;

  element.addEventListener(
    "keyup",
    (event) => {
      initialProps[handler].call(
      null,
      element.nodeName === "INPUT" ? event.target.value : undefined
      )
    }
  );
}
<input keypress="change" value="test" type="text" />

This can be done only capturing event while listening keypress event. When you listen event will get latest updated value. In your code your are not capturing event due to reason not getting updated value of target element.

function keyHandler(element) {
  let handler = element.attributes.keypress.nodeValue;
  console.log(handler);
  element.addEventListener(
    "keypress",
    function(event) {
      console.log(event.target.value);
    })
  }

let initialProps = {
  change(text) {
    console.log(text);
  }
}

document.querySelectorAll("[keypress]").forEach(element => {
  console.log(element);
  this.keyHandler(element);
});

function keyHandler(element) {
  let handler = element.attributes.keypress.nodeValue;
  console.log(handler);
  element.addEventListener(
    "keypress",
    function(event) {
      console.log(event.target.value);
    })
  }
<input keypress="change" value="test" type="text" />

Related