2

In vanilla JavaScript how can I bind an element to an object so that if a child element of the object has a value I can update the object with it? Need to be compatible with IE10+ and all other browsers.

With a button I am dynamically adding an element (createElement()) containing a form input. When the element is created it also creates an object that should also be the element so I can update the object with the input value on change.

I then store each new object in an array.

The issue I am having is connecting the input value with the correct object. I tried looping through the array hoping to update each object in turn with the current input value of the event target but couldn't succeed. I tried registering the element (deprecated) and various other things but I cannot quite workout how to link the input to the container object (lineObject).

I could really use some help solving this problem and understanding how to bind an element to an object in the way I need.

//lineNumber *** // 
let lineNumber = document.querySelectorAll('.lineNumber');
let numberOfLines = lineNumber.length;
//first instance of input element
let lineText = document.querySelector('.lineText');
//first input value of element
let lineTextValue = document.querySelector('input[name="lineText"]').value;
//create initial lineObject for first line
let lastLine = lineNumber[numberOfLines - 1];
let lineContainer;

//lineNumber object constructor
function LineObject(lineText, writable) {
 //set properties
 this.lineText = lineText;
 this.writable = writable;
} 

//new object at new lineNumber element, set values
let lineObject = new LineObject(lineTextValue, true);

//create array containing initial line object
let lineArray = [lineObject];

//line functions
(function lineGeneration(){

  //add or remove lines
  document.addEventListener('click', function(e) {

   //this
   let self = e.target;

   // has class .addLine
   if (hasClass(self, 'addLine')) {
    
    //call function to get variables
    insertLineHTML();
        
    //clone new line after the last line\
    self.parentElement.parentElement.parentElement.parentElement.parentElement.appendChild(lineObject.cloneNode(true));

        //lineNumber input location
        let newlineTextInput = self.parentElement.parentElement.parentElement.parentElement.nextElementSibling.querySelector('input[name="lineText"]');
    
    //input value of new element
        let lineTextValue = newlineTextInput.value;//normally "" when created unless placeholder text
    
    //new object at new lineNumber element
        lineObject = new LineObject(lineTextValue, true);
    
        //add new object to lineArray
        lineArray.push(lineObject);
    
    refreshLineNodeList();
  
   } 

 }); 

    //combine accordion / refresh
    function refreshLineNodeList(){

          //refresh number of elements in nodelist
          lineNumber = document.querySelectorAll('.lineNumber');

          //get new length
          numberOfLines = lineNumber.length; 

        }

    //line html and vars
    function insertLineHTML(){
        lineObject = document.createElement('div');
        lineObject.setAttribute('class', 'lineNumber');
        lineObject.innerHTML = ` 
            <div class="accordion-title">
            <h3>Line 2</h3>
            </div>

            <div class="input-section"> 

            <div class="input-row">

            <div class="input-container">
            <label>Line 2 :</label>
            <input type="text" name="lineText" value="" class="lineText">
            </div>

           
            <div class="input-row">

            <div class="button-container">
            <div class="warning"></div>
            <button class="addLine">Add Another Line</button>

            </div>
            </div>
            </div>`;
        
            console.log(lineNumber);
        }
 
})();

//lineText addEventListener update object value
document.addEventListener('keyup', function(e) {
  let self = e.target;//input field
 let lineTextValue = self.value;
 
 // has class .lineText
 if (hasClass(self, 'lineText')) {      

      //for each lineObject in LineArray 
       //lineArray.forEach(function(arrayObject) {
        
        //update lineObject HTMLelement.prototype
       Object.defineProperty(lineObject, 'lineText', {
   
            //update object value to event target value
         get: function() {        
               return this.lineTextValue;//how can I get the right lineObject object from the array when I update the input  
             },
          
          set: function(lineTextValue) {
            this.lineText = lineTextValue;//how can I aet the right lineObject object in the array when I update the input
          }          
       });
        //debugging
        //console.log('objectProperty = ' + arrayObject.lineText);
        console.log('this.lineText = ' + this.lineText);  
    console.log('Object.entries(lineObject) - ' + Object.entries(lineObject));
        //console.log('lineObject.lineText = '+ lineObject.lineText);
        //console.log('lineTextValue = '+ lineTextValue);
      //});
  };
});

let button = document.getElementById('test');
button.addEventListener( "click", testFunction );
function testFunction(){ 

 button.addEventListener( "click", testFunction );
  //console.log('Object.keys(lineObject) - '+ Object.keys(lineObject));
  //console.log('Reflect.ownKeys(lineObject) - ' + Reflect.ownKeys(lineObject));
  //console.log('Object.values - ' + Object.values(lineObject));
  //console.log('lineObject = '+ lineObject.lineText);
  
  //console.log('Object.entries(lineObject) - ' + Object.entries(lineObject));
  //console.log('Object.entries(lineObjectClone) - ' + Object.entries(lineObjectClone));
  
  //console.log('lineObjectClone.lineText = ' + lineObject.lineText);
  //console.log('lineObjectClone[1].lineText = ' + lineObjectClone.lineText);
  //console.log('lineArray[0] = ' + lineArray[0].lineText);
  console.log('lineArray = ' + lineArray);
  console.log('numberOfLines = ' + numberOfLines);
  for(let i = 0; i < numberOfLines; ++i ){
    console.log('lineArray[i].lineText = ' + lineArray[i].lineText)
  }
};

//does the element have the class specified?
function hasClass(elem, className) {
      return elem.classList.contains(className);
};

 
<section>

  <button id="test">Test</button>
    
  <div class="lineNumber">
  <div class="accordion-title">
   <h3>Line</h3>
   
  </div>
  <div class="input-section" style="display: block;"> 
   <div class="input-row">

    <div class="input-container">
     <label>Line Text :</label>
     <input type="text" name="lineText" value="" class="lineText">
    </div>
   </div>

   <div class="input-row">

    <div class="button-container">
     <div class="warning"></div>
     <button class="addLine">Add Another Line</button>   
    </div>
 </div>
  </div>
 </div>
  
</section>
JPB
  • 592
  • 2
  • 6
  • 28
  • There are lots of existing frameworks for model binding, why are you reinventing the wheel here? – Seano666 Jul 08 '19 at 15:14
  • Learning exercise, cant get my head around the relationship and how to manipulate an object with a element this way. Want to understand the fundamentals. – JPB Jul 08 '19 at 15:17
  • 1
    This is a good answer to essentially the core of your question https://stackoverflow.com/questions/16483560/how-to-implement-dom-data-binding-in-javascript You should be able to suss out relationship with the child elements using querySelector and filter methods. IMHO, not a bad place to use jQuery to make it easier. – 4m1r Jul 08 '19 at 15:42
  • @4m1r thanks will take a look now. Not interested in jquery as trying to learn the fundamentals. Update: I've already been through that post and while informative I was hoping for some input on a solution to my specific approach. Will take another look though. – JPB Jul 08 '19 at 15:43
  • hmm, would a onchange event listener work for you? – Henrique Donati Jul 08 '19 at 15:44
  • @HenriqueDonati I've got the onChange listener set up (keyPress) the issue is how to update the correct object in the array with the value. That's currently where I am up to with the fiddle.It creates the element and the object and detects the events, but it does not update the objects value. – JPB Jul 08 '19 at 15:46

1 Answers1

1

One way to do this is to use a closure.

The purpose of a closure is to capture variables from the containing function so those variables can be used later, after the containing function exits.

A simple example could look like this:

let data = {
    nameGenerator: 0
};

function addInput() {
    // generate a new name and property in data object
    let propertyName = String.fromCharCode("a".charCodeAt() + data.nameGenerator++);
    // initialize property value to its name
    data[propertyName] = propertyName;

    // add <div><input value="(property)"></div> to container
    let containerElement = document.getElementById("container");
    let lineElement = document.createElement("div");
    let inputElement = document.createElement("input");
    lineElement.appendChild(inputElement);
    containerElement.appendChild(lineElement);

    // initialize input value (note: this does not bind the two, just initializes)
    inputElement.value = data[propertyName];

    // create a closure that binds the property to the element
    inputElement.addEventListener("keyup", function () {
        // inside this function, propertyName and inputElement 
        // are "captured" in the closure
        data[propertyName] = inputElement.value;
    })
}

Note that the propertyName and inputElement variables are defined in the outer addInput function, but they are captured in the closure that is created when you assign the anonymous function as an event listener.

Here is a fiddle with a complete working example: https://jsfiddle.net/b3ta60cn/

Jack A.
  • 4,245
  • 1
  • 20
  • 34