Trying to copy text from two different text areas on button click

Viewed 82

I'm new to JS so I apologize in advance if this is an easy answer.

I'm trying to select and copy text from two different text area boxes when a button is pressed. I can only figure out how to get one text area to be copied at a time using getElementbyID. I've tried using querySelectorAll("").select(); with no luck. What am I missing?

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Test</title>
</head>

<body class="bg-gray-100">

  <div class="container font-sans bg-white mx-auto px-4 py-4 rounded shadow-md">

    <form action="" method="" class="form grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
      <div class="text-lg w-full underline font-bold col-span-1 sm:col-span-2 md:col-span-3 lg:col-span-4">
        <h1>Notification Information</h1>
      </div>

 <div class="inline-flex w-full text-lg underline font-bold mt-4 col-span-1 sm:col-span-2 md:col-span-3 lg:col-span-4">
        <h1 class="mx-2">Media Script</h1>
        <div><button type="button" id="plusScript" onclick="addText()" class="text-sm border border-transpartent border-2 border-blue-300 bg-trasparent px-1 p-1 rounded shadow text-blue-400 mx-2 font-bold">New Script</button></div>
        <div><button type="button" onclick="copyAll()" class="text-sm border border-transpartent border-2 border-blue-300 bg-trasparent px-1 p-1 rounded shadow text-blue-400 mx-2 font-bold">Copy Script</button></div>
      </div>

      <div class="Media Script Header space-y-4">
        <label for="Media Script Header"></label>
        <textarea name="Media Script" rows="1" cols="70" id="script" placeholder="Media Script Header" class="copyScript resize-none text-sm col-span-1 sm:col-span-2 md:col-span-3 focus:outline-none focus:ring-2 focus:ring-blue-300 focus:border-transparent rounded shadow-sm focus:ring-opacity-75 mx-2">
</textarea>

        <label for="Media Script"></label>
        <textarea name="Media Script" rows="5" cols="70" id="scriptNarrative" placeholder="Media Script Narrative" class="copyScript no-wrap resize-none text-sm col-span-1 sm:col-span-2 md:col-span-3 focus:outline-none focus:ring-2 focus:ring-blue-300 focus:border-transparent rounded shadow-sm focus:ring-opacity-75 mx-2">
</textarea>
      </div>
</div>
</body>

    function copyAll() {
  document.querySelectorAll("copyScript").select()
  document.execCommand("copy");
}

 
3 Answers

One approach is below, though this is quite likely imperfect since I wasn't quite sure what you wanted to do with the result once obtained.

// I personally don't like using inline event-handlers, though that's obviously the form
// that frameworks such as React and Angular take; if you're writing the
// script manually then I prefer to use bind event-handlers in the JavaScript:
const userFunctions = {
    // these function-names are retrieved from the elements, so
    // it's still technically obtrusive JavaScript but I don't
    // quite know how you're working, so I came to this compromise.
    // Here the event Object is passed in to the functions (declared
    // using Arrow function syntax):
    // this specific function was in your posted code, without any detail
    // so I left it, and just logged my lack of knowledge of your intent
    // to the console:
    addText: (e) => console.log("Not a clue, unfortunately"),
    // this function-name is - again - found in the HTML custom data-*
    // attribute, and again the event Object is passed in:
    copyAll: (e) => {
      // we retrieve the element to which the event-handler was bound:
      const target = e.currentTarget,
        // we retrieve the <form> associated with that element:
        form = target.form,
        // we search within that <form> using Element.querySelectorAll()
        // to recover the textarea elements, passing that to an Array-
        // literal along with the spread syntax which forms an Array from
        // the iterable NodeList from Element.querySelectorAll():
        textareas = [...form.querySelectorAll('textarea')],
        // we then iterate over that Array, using Array.prototype.map()
        // which returns a new Array based on the contents of the
        // Array to which it was chained:
        contents = textareas.map(
          // here we return a two-part Array, composed of the
          // current element's id as the first entry, and
          // the value of that element as the second entry
          // (having trimmed the leading/trailing white-space):
          (elem) => [elem.id,elem.value.trim()]
        );
        
      // we then use Object.prototype.fromEntries() to take that
      // Array and from its contents create an Object, from each
      // two-part Array the first array-element is the Object
      // property key, and the second is the property-value; this
      // Object is returned to the calling context (though I'm unsure
      // as to what your specific intent is, but this should give
      // you enough information to do as you wish):
      return Object.fromEntries(contents);
      // this returns an Object of the form:
      // {
      //    <textarea id>: <script header contents>
      //    <textarea id>: <script narrative contents>
      // }
    }
  },
  // this function is the event-handler for the button-click events;
  // again using Arrow syntax, passing in the event Object:
  ascribeFunction = (e) => {
    // here we use bracket-notation to call the function that
    // ultimately handles the event:
    userFunctions[
      // here we retrieve the event-target (e.target),
      // we use the HTMLOrForeignElement.dataset API to
      // access the value of the 'data-function'
      // attribute (getAttribute('data-function') would
      // also work, and then the event Object is
      // passed into the function:
      e.target.dataset.function](e);
  };

// here we use document.querySelectorAll(), along with NodeList.prototype.forEach
// to iterate over all <button> elements that have a 'data-function' attribute:
document.querySelectorAll('button[data-function]').forEach(
  // we pass the current element into the Arrow function, and
  // bind the ascribeFunction() as the 'click' event-handler:
  (elem) => elem.addEventListener('click', ascribeFunction)
);
/*
  None of the CSS is necessary, adjust to your own taste and disregard
  any, or all, of it; I wrote this because I think it looks somewhat
  useable, but adjust to taste. This first part is simply a simple
  CSS reset to ensure all elements are appropriately sized from the
  same baseline:
*/
*,
::before,
 ::after {
  box-sizing: border-box;
  font-family: "Roboto", "Ubuntu", Calibri, Helvetica, sans-serif;
  font-size: 1rem;
  line-height: 1.5;
  margin: 0;
  padding: 0;
}

form {
  width: 80vw;
  margin: 0 auto;
}

fieldset {
  border: 0 none transparent;
  margin-top: 1em;
  display: flex;
  justify-content: start;
  gap: 0.5em 2em;
  padding-top: 0.5em;
}

#scriptDetails {
  flex-direction: column;
  row-gap: 0;
}

legend {
  border-bottom: 1px solid currentColor;
  font-size: 120%;
  width: 100%;
}

legend::after {
  content: ': ';
}

label {
  margin-top: 0.5em;
}

label::after {
  content: ':';
}

textarea {
  outline-offset: 0;
  outline: 3px solid transparent;
  transition: all 0.5s ease-in-out;
}

textarea:is(:focus, :active) {
  outline-offset: 0.1em;
  outline: 5px solid #f909;
}

#script {
  height: 1.5rem;
  resize: none;
}

#scriptNarrative {
  height: 15rem;
}
<div>

  <form action="#">
    <div>
      <h1>Notification Information</h1>
    </div>

    <!-- I've made a few changes to your HTML, stripping out the redundant
         classes (since no CSS was included with your code), I've used
         fieldsets to group related form-elements together and used a
         <legend> element to 'label' each of those groups of elements;
         incidentally no page should have more than one <h1>, if more
         headings must be used then they should be <h2>,<h3> and so on -->
    <fieldset id="controls">
      <legend>Media Script</legend>
      <div>
        <button type="button" id="plusScript" data-function="addText">New Script</button>
      </div>
      <div>
        <button type="button" data-function="copyAll">Copy Script</button>
      </div>
    </fieldset>

    <fieldset id="scriptDetails">
      <legend>Scripts</legend>
      <!-- an empty <label> element serves no purpose at all, so populate it
           with text to provide relevant instruction to the user, and to increase
           accessibility (particularly in the case of check-box and radio <input>
           elements because it increases the touch area for users with touch
           screens), the "for" attribute must be the the same as the "id" of the
           element to which it's associated (unless that element is nested within
           the <label> in which case the association is implicit), otherwise
           the <label> is not associated with that element and serves no purpose
           except for littering your (unnecessarily inaccessible) HTML -->
      <label for="script">Media Script Header</label>
      <textarea name="Media Script" id="script" placeholder="Media Script Header">
</textarea>

      <label for="scriptNarrative">Media Script</label>
      <textarea name="Media Script" id="scriptNarrative" placeholder="Media Script Narrative">
</textarea>
    </fieldset>
  </form>
</div>

JS Fiddle demo.

References:

When you try to select by class with querySelectorAll you need to add '.' before the class name.

In your case:

document.querySelectorAll(".copyScript")

Notice that you get an array and not a single element

I'm not exactly sure if i understood you right, but querySelectorAll creates "something like" an Array of classes with the same name, so you can get access to it by inputting it position, you can use loops if you have more than two textareas

function copyAll() {
   let x;
  
  x =document.querySelectorAll(".copyScript")
  console.log( x[0].value )
  console.log( x[1].value )
  
}
Related