0

Server-side dev here, looking to dabble in a bit of vanilla JS to learn the ropes.

This question is about creating a form where textareas are initially hidden behind button clicks, and which also resize as the text entered into them grows larger. It's a challenging problem for someone at my level, and I'm trying to code it myself for learning purposes.


Here's what I've done so far.

Imagine a forum where users submit textual content. Each submission has a "reply" button under it. Pressing that opens a simple textarea that one can type their response into. I wrote simple JS to accomplish this:

var replyBtns = document.querySelectorAll('[id=rep]');

for(var i = 0; i < replyBtns.length; i++) {
 replyBtns[i].addEventListener('click', function(e) {
   e.preventDefault();
   var textarea = document.getElementById("reply-message");
   if (textarea) { textarea.parentNode.removeChild(textarea); }
   var replyBox = document.createElement('textarea');
    replyBox.setAttribute('id', 'reply-message');
    replyBox.setAttribute('placeholder', 'Reply');
   this.parentNode.insertAdjacentElement('beforeend', replyBox);
 }, false);
}

As you can see, this JS creates a text area and adds it in the HTML. The CSS to go with it is:

textarea#reply-message {
  display: block;
  border: none;
  color:#306654;
  font-size:120%;
  padding: 5px 10px;
  line-height: 20px;
  width:550px;
  height: 20px;
  border-radius: 6px;
  overflow: hidden;
  resize: none;
}

This works well.


My next endeavour was to make this textarea resize as the text starts overflowing. For that, I'm taking this route:

  • Grab the content loaded into the textarea

  • Create an invisible clone div

  • Give the clone the same width and typographical properties as the textarea

  • Place the content into the clone

  • Get the height of the clone

  • Apply the height of the clone to the height of the textarea

This strategy uses the property that any div element naturally stretches to fit the height of its content (assuming no floats or absolutely positioned elements). I owe this technique to this blog post.

Here's the JS for implementing the aforementioned technique:

let txt = document.getElementById('reply-message'),
    hiddenDiv = document.createElement('div'),
    content = null;

hiddenDiv.classList.add('hiddendiv');

document.body.appendChild(hiddenDiv);

txt.addEventListener('keyup', function () {

  content = this.value;
  hiddenDiv.innerHTML = content + '\n\n';
  this.style.height = hiddenDiv.getBoundingClientRect().height + 'px';

}, false);

Where hiddendiv is:

.hiddendiv {
  position: absolute;
  left: -9999px;
  visibility: hidden;
  white-space: pre-wrap;
  width: 550px;
  height: 20px;
  font-size: 120%;
  padding: 5px 10px;
  word-wrap: break-word;
  overflow-wrap: break-word;
}

But thing is, I need this JS snippet to run only once the textarea actually exists.

Currently, I have it running on page load, where it's unable to have any effect since textareas don't yet exist. How do I ensure it only runs once they've been created? Being a newbie in JS, this is totally eluding me. Please advise.

Hassan Baig
  • 15,055
  • 27
  • 102
  • 205

4 Answers4

1

This isn't exactly answering your question, but restructuring your code a bit to achieve the same result.

You should add the event listener to your textarea when you create it, before you add it to the page.

Insert code like this in your first glob of javascript:

replyBox.setAttribute('placeholder', 'Reply');
replyBox.addEventListener('keyup', function () {
    /* do event handling here */
}, false);

A cleaner way to handle all of this would be to move all of the code pertaining to setting up your textareas into a separate function.

function setUpReplyBox(replyBox) {
    replyBox.setAttribute('id', 'reply-message');
    replyBox.setAttribute('placeholder', 'Reply');

    hiddendiv.createElement('div');
    content = null;

    hiddendiv.classList.add('hiddendiv');
    document.body.appendChild(hiddenDiv);

    replyBox.addEventListener('keyup', function () {
        content = this.value;
        hiddenDiv.innerHTML = content + '\n\n';
        this.style.height = hiddenDiv.getBoundingClientRect().height + 'px';
    }, false);
}

Call this right after you create the item, at

var replyBox = document.createElement('textarea');
setUpReplyBox(replyBox);
this.parentNode.insertAdjacentElement('beforeend', replyBox);
Jason Warta
  • 430
  • 5
  • 8
  • Good solution. One aside. Being a server-side developer, I often think about efficiency of solution. In your opinion, what could be a more efficient way to solve this entire problem? Would be interested in knowing how I can write better code as well. – Hassan Baig Jan 27 '18 at 02:39
  • 1
    I have a similar situation on one of my projects. In my case, I have the textarea already existing on the page when it is rendered, and in the body onload event, I cycle through and attach the event handlers to the textareas. Here is my onload event: https://gist.github.com/jasonwarta/8c929881e53d6dd78764e5cbf4df9c53 For your case, you might be able to have them already hardcoded on the page and toggle the display attribute of the relevant textarea. That seems like it might be faster than building them on the fly, regardless of whether the user notices the impact. – Jason Warta Jan 28 '18 at 07:13
  • 1
    To amend to that... my use case was a bunch of forms running on flask and WTForms, so rendering the forms on the fly, on client-side, wasn't really an option, but I definitely needed the infinite expanding thing. – Jason Warta Jan 28 '18 at 07:16
0

Listen for the domContentLoaded Event

document.addEventListener("DOMContentLoaded", function(event) {
    console.log("DOM fully loaded and parsed");
});

Reference Link https://developer.mozilla.org/en-US/docs/Web/Events/DOMContentLoaded

Four_lo
  • 1,150
  • 10
  • 28
  • Are you suggesting I wrap my textarea resizing JS code within this? I tried that, it seems to only work on the page's initial loading, and not the subsequent changes in the DOM once a `textarea` is rendered – Hassan Baig Jan 27 '18 at 01:35
  • Hmm you are going to need to force a reflow or a repaint for this I believe. Here is a link describing that. https://www.phpied.com/rendering-repaint-reflowrelayout-restyle/ but if you are looking for a simpler solution to your problem here is an example of a growing textarea. https://developer.mozilla.org/en-US/docs/Web/API/HTMLTextAreaElement . LMK if you got that so I can delete my answer – Four_lo Jan 27 '18 at 01:50
  • Yep, got it, you can go ahead – Hassan Baig Jan 27 '18 at 01:52
0

Maybe using a div with contenteditable instead of a textarea would make your life a bit easier.

#textarea {
    -moz-appearance: textfield-multiline;
    -webkit-appearance: textarea;
    border: 1px solid gray;
    padding: 2px;
    width: 400px;
}
<div id="textarea" contenteditable>dummy textarea</div>
2pha
  • 9,798
  • 2
  • 29
  • 43
  • 1
    Yea I'm aware of that, I'm deliberately trying to use JS for this. Nevertheless, keep your answer, it'll surely help a ton of people. – Hassan Baig Jan 27 '18 at 01:40
0

There is plugin for that autosize

Check the src at github, you will get idea on how to do it.

If your code is working in page load and not in text update, you need to call your resize function in every keyup, resize and input events.

window.addEventListener('resize', yourFunc);
textarea.addEventListener('paste', yourFunc);
textarea.addEventListener('keyup', yourFunc);

There is simpler method here

function auto_grow(element) {
    element.style.height = "5px";
    element.style.height = (element.scrollHeight)+"px";
}
textarea {
    resize: none;
    overflow: hidden;
    min-height: 50px;
}
<textarea onkeyup="auto_grow(this)"></textarea>
kiranvj
  • 32,342
  • 7
  • 71
  • 76