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>

6
  • There are lots of existing frameworks for model binding, why are you reinventing the wheel here? Commented Jul 8, 2019 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. Commented Jul 8, 2019 at 15:17
  • 1
    This is a good answer to essentially the core of your question stackoverflow.com/questions/16483560/… 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. Commented Jul 8, 2019 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. Commented Jul 8, 2019 at 15:43
  • hmm, would a onchange event listener work for you? Commented Jul 8, 2019 at 15:44

1 Answer 1

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/

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks will experiment with this for a bit and look into closures.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.