Home > database >  How can I write a code in JS that deletes the existing output by disappearing it letter by letter, t
How can I write a code in JS that deletes the existing output by disappearing it letter by letter, t

Time:01-22

Here's the code i've written, where when i write a word into e search field, it appends it in the element "word" by displaying it letter by letter. But, the problem is, that i don't know how to write the code that when i write another word in the search field, it deletes the word that appear to element "Word", then writes the new one i've written.

let text = document.getElementById("txt");
let elem = document.getElementsByClassName("target")[0];
let word = elem.querySelector(".word");
let btn = document.getElementsByClassName("btn")[0];
let error = document.querySelector('#error');
i = 0;

        word.style.color = "#ffe100";
        btn.addEventListener("click", function init() { 
             if (text.value == "") {
                 error.style.opacity = '1.0';
             } else {
                 error.style.opacity = '0.0';
                 let save = word.textContent  = text.value.charAt(i); 
                 i  ;
             }
            if (i < text.value.length) {
                window.setTimeout(init, 100);
            }
        }); 
            

I've try many of alternatives, but there's no result.

CodePudding user response:

One approach you could take to solve this problem is to set a variable that stores the current value of the text field. Then, when the button is clicked, compare the value stored in the variable to the new value of the text field. If they are different, clear the element "Word" and then append the new value letter by letter.

You could do something like this:

let text = document.getElementById("txt");
let elem = document.getElementsByClassName("target")[0];
let word = elem.querySelector(".word");
let btn = document.getElementsByClassName("btn")[0];
let error = document.querySelector('#error');
let currentText = ""; // Initialize a variable to store the current value
i = 0;

word.style.color = "#ffe100";
btn.addEventListener("click", function init() { 
    if (text.value == "") {
        error.style.opacity = '1.0';
    } else {
        error.style.opacity = '0.0';
        // If the new value is different than the old one, clear the element and start over
        if (currentText !== text.value) {
            word.textContent = "";
            i = 0;
        }
        let save = word.textContent  = text.value.charAt(i); 
        i  ;
        currentText = text.value; // Update the variable storing the current value
    }
    if (i < text.value.length) {
        window.setTimeout(init, 100);
    }
});

CodePudding user response:

I will iteratively change and/or improve your code in this answer, and will try to comment on each change in the code.

Refactor

First off, I'll have an easier time explaining the different approaches after refactoring your code:

// Prefer `const` over `let` when applicable
const input = document.querySelector("input");   // Previously: text
const output = document.querySelector("output"); // Previously: word
const button = document.querySelector("button"); // Previously: btn
const error = document.getElementById("error");

// You forgot `let`. Always declare your variables!
let i = 0;

button.addEventListener("click", () => {
  if (input.value == "") {
    error.style.opacity = '1.0';
  } else {
    error.style.opacity = '0.0';
    reveal();
  }
});

// Moved code from listener to here
function reveal() {
  // Removed `save`; was unused
  output.textContent  = input.value[i];
    i;
  
  // Moved here because it only runs if `input.value !== ""` anyways
  if (i < input.value.length) {
    setTimeout(reveal, 100);
  }
}
/* Prefer to use CSS for static styles! */
.word {
  color: #ffe100;
}
<!-- I assume your HTML to have looked similar to this: -->
<input><button>Submit</button>
<div>
  <output></output>
</div>
<p id="error">Please submit (actual) text.</p>

Now let's take a look at your refactored code from above:

  1. There is no resetting: Revealing can only continue (text can only be added).
  2. The value of input is referenced directly: When its value changes...
    • Then revealing may stop prematurely.
    • Then the further revealed text may not represent the entered text upon button-press.

Allow reusability

The issue of point 1 can be solved by (re-)setting i and output.textContent in the listener. To solve point 2, we need to use some buffer for the text:

const input = document.querySelector("input");
const output = document.querySelector("output");
const button = document.querySelector("button");
const error = document.getElementById("error");

let i = 0;
let text = ""; // The buffer

button.addEventListener("click", () => {
  if (input.value == "") {
    error.style.opacity = '1.0';
  } else {
    error.style.opacity = '0.0';
    
    // (Re-)setting
    i = 0;
    text = input.value;
    output.textContent = "";
    
    reveal();
  }
});

function reveal() {
  output.textContent  = text[i];
    i;
  
  if (i < text.length) {
    setTimeout(reveal, 100);
  }
}
.word {
  color: #ffe100;
}
<input><button>Submit</button>
<div>
  <output></output>
</div>
<p id="error">Please submit (actual) text.</p>

With these two small changes, your code now successfully deletes the revealed text in place for new text!

Adding states

But the deletion doesn't happen letter-by-letter. That would require some way to keep track of whether we are deleting or revealing.

Let's use a state-machine that –upon prompting– deletes the already revealed text (if any) and then reveals the new text:

const input = document.querySelector("input");
const output = document.querySelector("output");
const button = document.querySelector("button");
const error = document.getElementById("error");

let i = 0;
let text = "";
let state = "nothing"; // The state

button.addEventListener("click", () => {
  if (input.value == "") {
    error.style.opacity = '1.0';
  } else {
    error.style.opacity = '0.0';
    
    // Remember previous state (for below)
    const previousState = state;
    
    // Issue deletion and update text-to-reveal
    state = "delete";
    text = input.value;
    
    if (previousState === "nothing") {
      // Start state-machine
      nextCharacter();
    }
  }
});

// Rename function
function nextCharacter() {
  if (state === "nothing") return;
  
  if (state === "delete") {
    output.textContent = output.textContent.slice(0, i);
    
    // Finished deleting?
    if (i === 0) {
      const requiresRevealing = i < text.length;
      state = requiresRevealing ? "reveal" : "nothing";
    } else {
      --i;
    }
  }
  
  if (state === "reveal") {
    output.textContent  = text[i];
      i
    
    // Finished revealing?
    if (i === text.length) {
      state = "nothing";
    }
  }
  
  // Requires continuing?
  if (state !== "nothing") {
    setTimeout(nextCharacter, 100);
  }
}
.word {
  color: #ffe100;
}
<input><button>Submit</button>
<div>
  <output></output>
</div>
<p id="error">Please submit (actual) text.</p>

Code quality refactoring

The code now works, but(!) the logic is scattered everywhere, and you need to know what variables to update for the revealing to work correctly. Instead, we could make use of classes:

const input = document.querySelector("input");
const output = document.querySelector("output");
const button = document.querySelector("button");
const error = document.getElementById("error");

// Move related variables and functions into class
class Revealer {
  output;
  
  index = 0;
  text = "";
  state = "nothing";

  constructor(output) {
    this.output = output;
  }
  
  // Moved from listener
  reveal(text) {
    const previousState = this.state;
  
    this.state = "delete";
    this.text = text;
    
    if (previousState === "nothing") {
      this.next();
    }
  }
  
  // Previously nextCharacter()
  next() {
    if (this.state === "nothing") return;

    if (this.state === "delete") {
      this.deleteCharacter();
    } else if (this.state === "reveal") {
      this.revealCharacter();
    }
    
    if (this.state !== "nothing") {
      setTimeout(() => this.next(), 100);
    }
  }
  
  // Use more specific functions for readability
  deleteCharacter() {
    this.output.textContent = this.output.textContent.slice(0, this.index);

    if (this.index === 0) {
      const requiresRevealing = this.index < this.text.length;
      this.state = requiresRevealing ? "reveal" : "nothing";
    } else {
      --this.index;
    }
  }
  revealCharacter() {
    this.output.textContent  = this.text[this.index];
      this.index;

    if (this.index === this.text.length) {
      this.state = "nothing";
    }
  }
}

const revealer = new Revealer(output);

button.addEventListener("click", () => {
  if (input.value == "") {
    error.style.opacity = '1.0';
  } else {
    error.style.opacity = '0.0';
    
    // Use simple call
    revealer.reveal(input.value);
  }
});
.word {
  color: #ffe100;
}
<input><button>Submit</button>
<div>
  <output></output>
</div>
<p id="error">Please submit (actual) text.</p>


Ideas for practicing

While the above code works, there are still ways to improve it or implement it differently:

  • Only delete until the remaining text is the same as the leading substring of the text-to-add.
  • You can use promises (with async/await) instead of using setTimeout() directly.
  • You can implement the revealing as functionality of a custom element.
  • ...

Try implementing (one of) these as practice!

  • Related