// 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>