0

I have the following function, in order to update values from four different input fields:

UPDATED

My Package Class is defined as followed:

class Package {
  constructor(packageID, type, weight, height, description) {
    this.packageID = packageID;
    this.type = type;
    this.weight = weight;
    this.height = height;
    this.description = description;
  }
  getPackageId() {
    return this.packageId;
  }
  getType() {
    return this.type;
  }
  getWeight() {
    return this.weight;
  }
  getHeight() {
    return this.height;
  }
  getDescription() {
    return this.description;
  }/*
  setPackageId(packageId){
    this.packageId= packageId
    return packageId;
  }*/
  setType(type) {
    this.type = type;
    return type;
  }
  setWeight(weight) {
    this.weight = weight;
    return this.weight;
  }
  setHeight(height) {
    this.height = height;
    return this.height;
  }
  setDescription(description) {
    this.description = description;
    return this.description;
  }
}
let inputfieldtype = document.getElementById('selectPackageType');
let inputfieldweight = document.getElementById('floatWeight');
let inputfieldheight = document.getElementById('floatHeight');
let inputfielddescription = document.getElementById('description');

function updateValues() {
  var value = this.value;

  if (value == null) {

    console.log('value of packege is null');

  } else if (value != "" && (inputfieldtype == 'selectPackageType')) {
    type = value;
  } else if (value != "" && (inputfieldweight == 'floatWeight')) {
    weight = value;
  } else if (value != "" && (inputfieldheight == 'floatHeight')) {
    height = value;
  } else if (value != "" && (inputfielddescription == 'description')) {
    description = value;
  }
}
inputfieldweight.addEventListener('change', updateValues);
inputfieldheight.addEventListener('change', updateValues);
inputfielddescription.addEventListener('change', updateValues);
inputfieldtype.addEventListener('change', updateValues);

What I've learned so far is, that the condition of my if statement is not useful, because in all four cases, the string I want to compare it with, is true. My goal is the following: I want to check, which input field has been clicked and I want to save the value of this field into a variable.

Then I want to create a new instance of my Class "Package" and fill their attributes with this values.

//Create instance of package
const package = new Package();

//shoot all the values into one package
function submit() {
  if (typeof package != "undefined") {
    package.packageID = randomID;
    package.type = type;
    package.weight = weight;
    package.height = height;
    package.description = description;
    console.log(package);
  } else {
    console.log(package);
  }
}

Here is the HTML Code

<form autocomplete="off">
  <div class="custom-select">
    <label for="selectPackageType">Type</label>
    <select id="selectPackageType" name="type">
      <option value="1">letter</option>
      <option value="2">package</option>
    </select>
  </div>
</form>
<div class="fancy-input">
  <label for="floatWeight">Weight</label>
  <input id="floatWeight" name="floatWeight" maxlength="8">
</div>
<div class="fancy-input">
  <label for="floatHeight">Height</label>
  <input id="floatHeight" name="floatHeight" maxlength="8">
</div>
<div class="fancy-input">
  <label for="description">Description</label>
  <input id="description" name="description">
</div>
<button onclick="submit()">Submit</button>
9
  • 1
    Where did you defined your Package class? Commented Jul 7 at 16:13
  • 1
    i don't think you need an updatevalue() function as user is submitting a form and you are not checking for changes
    – Mehdi
    Commented Jul 7 at 16:53
  • 1
    Couple of things... How is your Package class configured? Why don't you place all your input fields in your form and when the user submits, run a single eventListener on the submit button and then gather the entered fields and pass them into your class constructor as params? Commented Jul 7 at 17:40
  • @dalelandry I followed your suggestions and it seems to be a good solution.
    – Melle
    Commented Jul 7 at 23:40
  • 1
    @Peter thank you for asking. There sure will be a few, but I need to work myself trough it first. Unfortunately It will take a lot of time, as I'm only able to work on this for one or two hours a day. I hope StackOwerflow will forgive me this comment, which is irrelevant to the topic.
    – Melle
    Commented Jul 10 at 12:59

3 Answers 3

1

For the sake of easy maintenance/refactoring a solution should be implemented both generic and with higher abstraction.

For the OP's use case one firstly has to slightly refactor the form-markup including the unification of form-control names and Package class property names.

Speaking of the latter, the OP's Package class implementation introduces getter and setter methods which do not serve any purpose since a) all properties are public anyhow, and b) no protection has been introduced with the setter implementation. Get/set only makes sense for protected data, hence private data where one is in control of how one allows access to or even the alteration of such data. The OP is better off with the most simple Package abstraction that features just public data.

The creation of a package instance should always happen in relation to its form-node. In order to establish such a relationship, one would make use of a WeakMap instance, where the package-configuration form-node serves as key for its package instance, and the latter has to be updated according to any form-control's value-changes of its related form-node.

One could introduce exactly two functions, putPackageData which creates and registers the form-related package-instance and patchPackageData which creates a data-patch from the form's current element-values and does assign it to the form-related package-instance.

One has to make use of e.g. ...

The next provided example code shows a possible implementation of the just described approach which of cause, due to being generic, patches a package with all current available form-data every time either a single form-control value changes or the form(-node) has to handle a submit event.

function patchPackageData(evt) {
  // - prevent the form from being submitted ...
  // - but only in case a real event with its own `preventDefault` method was provided.
  // - see ... [https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault]
  // - see ... [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining]
  evt.preventDefault?.();

  const { currentTarget: formNode } = evt;

  // - create a form-data instance.
  // - see ... [https://developer.mozilla.org/en-US/docs/Web/API/FormData]
  const formData = new FormData(formNode);

  // - create an object from all form-data entries.
  // - see ... [https://developer.mozilla.org/en-US/docs/Web/API/FormData/entries]
  // - see ... [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries]
  const dataPatch = Object.fromEntries([...formData.entries()]);

  // - get the form-node related package instance.
  // - see ... [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/get]
  const package = packageRegistry.get(formNode);

  // - assign the data-patch to the package instance.
  // - see ... [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign]
  Object.assign(package, dataPatch);

  console.log('PATCH package data ...', package);
}
function putPackageData(formNode) {
  // - create and register the form-node related package instance.
  // - see ... [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/set]
  packageRegistry.set(
    formNode, new Package({ id: formNode.dataset.packageId }),
  );
  console.log('PUT package data ...', packageRegistry.get(formNode));

  // - assign more form-node related data to the newly created package instance.
  patchPackageData({ currentTarget: formNode });
}
const packageRegistry = new WeakMap;

document
  // - query every available package configuration related form-node ...
  .querySelectorAll('form[data-name="package-config"]')

  // - ... where one for each form-control ...
  .forEach(elmNode => {

    // ... does trigger the creation and registration of the form-node related package instance.
    putPackageData(elmNode);

    // ... does register the correct handlers with the relevant event-types.
    elmNode.addEventListener('change', patchPackageData);
    elmNode.addEventListener('submit', patchPackageData);
  });
body { margin: 0; }
form {

  float: left;
  width: 25%;
  padding: 8px;
  font-size: .8em;

  label,
  label > span {

    display: block;
    width: 100%;
  }
  label > span {
    margin: 2px 0;
  }
  label, fieldset, [type="submit"] {
    margin: 4px 0;
  }
  select, input {
    width: 90%;
  }
}
.as-console-wrapper {
  left: auto!important;
  bottom: 0;
  width: 44%;
  min-height: 100%;
}
<form data-name="package-config" data-package-id="6dfc38b8-41f0-4b4f-86d8-4aea355aef79" autocomplete="off">

  <label>
    <span>Type</span>

    <select name ="type">
      <option value="1">letter</option>
      <option value="2">package</option> 
    </select>
  </label>

  <fieldset>
    <legend>Measures</legend>

    <label>
      <span>Weight</span>

      <input name="weight" maxlength="8" />
    </label>
    <label>
      <span>Height</span>

      <input name="height" maxlength ="8" />
    </label>
  </fieldset>

  <label>
    <span>Description</span>

    <input name="description" />
  </label>

  <button type="submit">Submit</button>
</form>


<form data-name="package-config" autocomplete="off">

  <label>
    <span>Type</span>

    <select name ="type">
      <option value="1">letter</option>
      <option value="2" selected>package</option> 
    </select>
  </label>

  <fieldset>
    <legend>Measures</legend>

    <label>
      <span>Weight</span>

      <input name="weight" maxlength="8" />
    </label>
    <label>
      <span>Height</span>

      <input name="height" maxlength ="8" />
    </label>
  </fieldset>

  <label>
    <span>Description</span>

    <input name="description" />
  </label>

  <button type="submit">Submit</button>
</form>


<script>
const isStringValue = value => (typeof value === 'string');
const isNonEmptyStringValue = value => (isStringValue(value) && !!value.trim());

class Package {
  // #data = {};

  constructor(options) {
    const { id, type, weight, height, description } = options;

    // Object.assign(this.#data, {
    Object.assign(this, {

      id: isNonEmptyStringValue(id) && id || crypto.randomUUID(),
      type: isNonEmptyStringValue(type) && type || 'not configured',
      weight: isNonEmptyStringValue(weight) && weight || 'not provided',
      height: isNonEmptyStringValue(height) && height || 'not provided',
      description: isNonEmptyStringValue(description) && description || 'not provided',
    });
  }/*

  get id() {
    return this.#data.id;
  }

  get type() {
    return this.#data.type;
  }
  get weight() {
    return this.#data.weight;
  }
  get height() {
    return this.#data.height;
  }
  get description() {
    return this.#data.description;
  }

  set type(value) {
    if (isNonEmptyStringValue(value)) {

      this.#data.type = value;
    }
    return this.#data.type;
  }
  set weight(value) {
    if (isNonEmptyStringValue(value)) {

      this.#data.weight = value;
    }
    return this.#data.weight;
  }
  set height(value) {
    if (isNonEmptyStringValue(value)) {

      this.#data.height = value;
    }
    return this.#data.height;
  }
  set description(value) {
    if (isNonEmptyStringValue(value)) {

      this.#data.description = value;
    }
    return this.#data.description;
  }

  valueOf() {
    return this.#data;
  }
  toString() {
    return JSON.stringify(this.valueOf());
  }
  [Symbol.toPrimitive](hint) {
    return (hint === 'string') && this.toString() || this.valueOf();
  }*/
}
</script>

Edit/Update

The above solution could be changed to one that creates the package-instance at initialization time (as it already does), but updates the package data selectively according to the most recent form-control value-change.

For this scenario a Package class implementation with protected data through real getter/setter functionality does make sense.

function patchPackageData(evt) {

  const { target: formControl, currentTarget: formNode } = evt;
  let { name: key, value } = formControl;

  const package = packageRegistry.get(formNode);

  // update (protected by a package's setter logic).
  package[key] = value;

  // bidirectional update (the current package state is the single source of truth).
  formControl.value = package[key];

  console.log('PATCH package data ...', { id: package.id, key, value: package[key] });
}

function postPackageData(formNode) {

  const formData = new FormData(formNode);
  const dataPatch = Object.fromEntries([...formData.entries()]);

  const package = packageRegistry.get(formNode);

  // post/update.
  Object.assign(package, dataPatch);

  Object
    .keys(dataPatch)

    // bidirectional update.
    .forEach(key => formNode.elements[key].value = package[key]);

  console.log('POST package data ...', package.valueOf());
}
function putPackageData(formNode) {
  packageRegistry.set(
    formNode, new Package({ id: formNode.dataset.packageId }),
  );
  console.log('PUT package data ...', packageRegistry.get(formNode).valueOf());

  postPackageData(formNode);
}
const packageRegistry = new WeakMap;

document
  .querySelectorAll('form[data-name="package-config"]')
  .forEach(elmNode => {

    putPackageData(elmNode);

    elmNode.addEventListener('change', patchPackageData);
    elmNode.addEventListener('submit', evt => evt.preventDefault());
  });
body { margin: 0; }
form {

  float: left;
  width: 25%;
  padding: 8px;
  font-size: .8em;

  label,
  label > span {

    display: block;
    width: 100%;
  }
  label > span {
    margin: 2px 0;
  }
  label, fieldset, [type="submit"] {
    margin: 4px 0;
  }
  select, input {
    width: 90%;
  }
}
.as-console-wrapper {
  left: auto!important;
  bottom: 0;
  width: 44%;
  min-height: 100%;
}
<form data-name="package-config" data-package-id="6dfc38b8-41f0-4b4f-86d8-4aea355aef79" autocomplete="off">

  <label>
    <span>Type</span>

    <select name ="type">
      <option value="1">letter</option>
      <option value="2">package</option> 
    </select>
  </label>

  <fieldset>
    <legend>Measures</legend>

    <label>
      <span>Weight</span>

      <input name="weight" maxlength="8" value="20" />
    </label>
    <label>
      <span>Height</span>

      <input name="height" maxlength ="8" />
    </label>
  </fieldset>

  <label>
    <span>Description</span>

    <input name="description" value="letter 20/3" />
  </label>

  <button type="submit">Submit</button>
</form>


<form data-name="package-config" autocomplete="off">

  <label>
    <span>Type</span>

    <select name ="type">
      <option value="1">letter</option>
      <option value="2" selected>package</option> 
    </select>
  </label>

  <fieldset>
    <legend>Measures</legend>

    <label>
      <span>Weight</span>

      <input name="weight" maxlength="8" />
    </label>
    <label>
      <span>Height</span>

      <input name="height" maxlength ="8" value="5" />
    </label>
  </fieldset>

  <label>
    <span>Description</span>

    <input name="description" />
  </label>

  <button type="submit">Submit</button>
</form>


<script>
const isStringValue = value => (typeof value === 'string');
const isNonEmptyStringValue = value => (isStringValue(value) && !!value.trim());

class Package {
  #data = {};

  constructor(options) {
    const { id, type, weight, height, description } = options;

    Object.assign(this.#data, {

      id: isNonEmptyStringValue(id) && id || crypto.randomUUID(),
      type: isNonEmptyStringValue(type) && type || 'not configured',
      weight: isNonEmptyStringValue(weight) && weight || 'not provided',
      height: isNonEmptyStringValue(height) && height || 'not provided',
      description: isNonEmptyStringValue(description) && description || 'not provided',
    });
  }

  get id() {
    return this.#data.id;
  }

  get type() {
    return this.#data.type;
  }
  get weight() {
    return this.#data.weight;
  }
  get height() {
    return this.#data.height;
  }
  get description() {
    return this.#data.description;
  }

  set type(value) {
    if (isNonEmptyStringValue(value)) {

      this.#data.type = value;
    }
    return this.#data.type;
  }
  set weight(value) {
    if (isNonEmptyStringValue(value)) {

      this.#data.weight = value;
    }
    return this.#data.weight;
  }
  set height(value) {
    if (isNonEmptyStringValue(value)) {

      this.#data.height = value;
    }
    return this.#data.height;
  }
  set description(value) {
    if (isNonEmptyStringValue(value)) {

      this.#data.description = value;
    }
    return this.#data.description;
  }

  valueOf() {
    return this.#data;
  }
  toString() {
    return JSON.stringify(this.valueOf());
  }
  [Symbol.toPrimitive](hint) {
    return (hint === 'string') && this.toString() || this.valueOf();
  }
}
</script>

3
  • ok, babysteps: It is not quite clear for me, where the "formNode" in the object "const { currentTarget: formNode }" comes from. What value has it? It represents the form itself, with their values, I guess? But where is this defined?
    – Melle
    Commented Jul 10 at 18:29
  • @Melle ... 1/2 ... Since the handler function got registered at the form-element, the passed event's currentTarget-property holds the reference of the form-node itself, whereas its target points to the element which triggered the event first (here most likely any of the form's control-elements). The line const { currentTarget: formNode } = evt; uses destructuring assignment syntax for object-destructuring together with aliasing (assigning to a new variable name). Commented Jul 10 at 20:52
  • 1
    @Melle ... 2/2 ... This line is equal to const formNode = evt.currentTarget. Commented Jul 10 at 20:53
0

I changed my code to the following:

/**
 * this class describes the package that should be send or collect
 */

 
 class Package{
     
     constructor(packageID, type, weight,height,description){
     this.packageID = packageID;
     this.type = type;
     this.weight = weight;
     this.height = height;
     this.description= description;
     
     }
     
     getPackageId(){
         
         return this.packageId;
     }
     
     getType(){
         return this.type;
     }
     
     getWeight(){
         return this.weight;
     }
     getHeight(){
         return this.height;
     }
     getDescription(){
         return this.description;
     }
     
     /*setPackageId(packageId){
         this.packageId= packageId
         return packageId;
     }*/
     
     setType(type){
         this.type = type;
         return type;
     }
    
     
     setWeight(weight){
         
         this.weight = weight;
         return this.weight;
     }
     setHeight(height){
         this.height = height;
         return this.height;
     }
     setDescription(description){
         this.description = description;
         return this.description;
     }
     
    
}

//Generate PackageId 
const generateID = () => Math.random().toString(36).slice(2);
//Global Variables
const randomID = generateID();

const formgetInputfields = document.getElementById("getInputfields");
const inputfieldtype = document.getElementById('selectPackageType');
const inputfieldweight = document.getElementById('floatWeight');
const inputfieldheight = document.getElementById('floatHeight');
const inputfielddescription = document.getElementById('description');

//Create instance of package
const package = new Package();
//shoot all the values into one package
function submit(){  
const type = inputfieldtype.value;
const weight = inputfieldweight.value;
const height = inputfieldheight.value;
const description = inputfielddescription.value;
    console.log(formgetInputfields);
    
  if (typeof package != "undefined") {
      package.packageID = randomID;
      package.type = type;
      package.weight = weight; 
      package.height = height;
      package.description = description;   
      console.log(package);
  }else {
        console.log(package);
  }
}
formgetInputfields.addEventListener("change", submit);

It seems to work.

2
  • 1
    After studying the well documented suggestions from @Peter Selinger, I came to the conclusion, that this version is working fine, but is not the best solution. I wanted to use classes, because I'm used to using them, I'm unfamiliar with modules and it seemed like a good solution to structure my project. The built-in features a <form> has to offer, seems like a better way to implement my project.
    – Melle
    Commented Jul 12 at 16:33
  • 1
    I think the first suggestion from Peter Seliger, in which the two functions "putPackageData" and "PatchPacketData" are implemented, is very good.
    – Melle
    Commented Jul 12 at 16:54
0

classes are encapsulated, which means that the variables and methods can't be used by other classes. Global variables, and functions outside the class should be few if not any at all. A <form> has many built-in features that save us from writing a lot of JavaScript and a class IMO is overkill. A <form> relies on user interaction so event handlers for "change", "input", "submit", "click" etc. events are needed. The way to include event handlers in a class is tricky, here's an example for all <button>:

class Example {
  constructor(selector) {
    this.nodeList = document.querySelectorAll(selector);
    for (const node of this.nodeList) {
      node.addEventListener("click", this.onClick.bind(this));
    }
  }
  onClick(event) {
    event.target.textContent = "You clicked me";
  }
}

const x = new Example("button");
    

Details are commented in the example

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

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Package Shipments</title>
  <style>
    :root {
      font: 5vmin/1 "Segoe UI";
    }
    
    form {
      display: flex;
      flex-flow: row nowrap;
    }
    
    fieldset {
      display: inline-block;
      max-width: fit-content;
      border-radius: 4px;
    }
    
    legend {
      font-size: 1.2rem;
    }
    
    label {
      display: inline-block;
      width: 6ch;
      margin-bottom: 0.5rem;
    }
    
    select,
    input,
    textarea,
    button {
      margin-bottom: 0.5rem;
      font: inherit
    }
    
    select {
      margin-top: 0.5rem;
      padding: 0.15rem 0 0.15rem 0;
    }
    
    .buttons {
      display: flex;
      justify-content: flex-end;
      width: 100%;
    }
    
    button {
      padding: 0.2rem 0.5rem;
      cursor: pointer;
    }
    
    [type="number"] {
      width: 5ch;
      font-weight: 300;
      font-family: Consolas;
      text-align: right;
    }
    
    [for="type"] {
      margin-top: 0.5rem;
    }
    
    [for="notes"] {
      vertical-align: top;
    }
    
    textarea {
      box-sizing: border-box;
      width: 100%;
    }
    
    #views {
      display: inline-flex;
      border: 0;
    }
    
    output {
      display: block;
      max-width: 36ch;
      margin: 1rem 0 0 2ch;
      font-size: 0.6rem;
      font-family: Consolas;
      white-space: pre-wrap;
      word-break: break-word;
      line-height: 1.2;
      color: blue;
    }
    
    #viewResponse {
      max-width: 60ch;
      color: #930;
    }
  </style>
</head>

<body>
  <!-- #1
    A HTMLFormControlsCollection on this <form> includes all form controls:
      <fieldset>
      <select>
      <input>
      <textarea>
      <button>
      <output>
  -->
  <form id="shipments">
    <fieldset>
      <legend>Packages</legend>
      <label for="type">Type</label>
      <!-- #2
        Any form control with [required] attribute will interrupt
        a "submit" event when it has no value or if the value does not
        meet criteria.
      -->
      <select id="type" name="type" required>
        <option value="">Choose Type</option>
        <option value="package">Package</option>
        <option value="letter">Letter</option>
      </select>
      <br>
      <label for="weight">Weight</label>
      <!-- #2 -->
      <input id="weight" name="weight" type="number" min="0" max="50" required>
      <label for="weight"> lbs.</label>
      <br>
      <label>Dimensions</label>
      <br>
      <label for="width">Width</label>
      <!-- #2 -->
      <!-- #3
        Belongs to the "dimensions" HTMLCollection.
      -->
      <input id="width" name="dimensions" type="number" min="4" max="36" required>
      <label for="width"> in.</label>
      <br>
      <label for="length">Length</label>
      <!-- #2 -->
      <!-- #3 -->
      <input id="length" name="dimensions" type="number" min="6" max="36" required>
      <label for="length"> in.</label>
      <br>
      <label for="height">Height</label>
      <!-- #2 -->
      <!-- #3 -->
      <input id="height" name="dimensions" type="number" min="0" max="36" required>
      <label for="height"> in.</label>
      <br>
      <label for="notes">Notes</label>
      <br>
      <textarea id="notes" name="notes" cols="20" rows="3"></textarea>
      <br>
      <label class="buttons">
        <!-- #4
          Belongs to the "buttons" HTMLCollection.
        -->
        <button id="view" name="buttons" type="button">View</button>
        <button type="reset">Clear</button>
        <!-- #4 -->
        <!-- #5
          A <button> with [type="submit"] or without any type that's
          within a <form> will trigger a "submit" event when clicked.
        -->
        <button name="buttons">Done</button>
      </label>
    </fieldset>
    <fieldset id="views">
      <output id="viewData"></output>
      <output id="viewResponse"></output>
    </fieldset>
  </form>
  <script>
    /**
     * This gathers data from a <form> that has form controls
     * with [name="..."]: "type", "weight", "dimensions",
     * "notes", and "buttons". The data will be formatted and it
     * will be assigned a timestamp id when it is sent to a
     * server.
     * @param formID - #id or [name] of the <form>
     */
    class Package {
      constructor(formID) {
        // #6 Reference the <form>
        this.form = document.forms[formID];
        // #7 References to all form controls (see #1)
        this.io = this.form.elements;
        // #8 An array of all form controls with [name="dimensions"] (see #3)
        this.dim = Array.from(this.io.dimensions);
        // #9 An array of all form controls with [name="buttons"] (see #4)
        this.btn = Array.from(this.io.buttons);
        // #10 This will store formatted data 
        this.data = {};
        /* #11 
        Register the <select>, <textarea>, and all <input>s
        (see #7) to the "change" event
        */
        for (const input of this.io) {
          input.addEventListener("change", this.onChange.bind(this));
        }
        // #12 Register all [name="buttons"] (see #9) to the "click" event
        for (const button of this.btn) {
          button.addEventListener("click", this.onClick.bind(this));
        }
        // #13 Register <form> (see #6) to the "reset" event
        this.form.addEventListener("reset", this.onReset.bind(this));
        // #14 Register <form> (see #6) to the "submit" event
        this.form.addEventListener("submit", this.onSubmit.bind(this));
      }

      /**
       * This method is an event handler bound to some form
       * controls (see #11) that will be invoked when the
       * "change" event occurs (user leaves the form control).
       * Each value entered will be assigned to a key name
       * identical to the form control's [name] and formatted
       * (except "type") to this.data object (see #10).
       * @param event - Event object type "change"
       */
      onChange(event) {
        const name = event.target.name;
        switch (name) {
          case "type":
            this.data.type = this.io.type.value;
            break;
          case "weight":
            this.data.weight = `${this.io.weight.value} lbs.`;
            break;
          case "dimensions":
            const wlh = this.dim
              .map(d => `${d.value} in.`)
              .join(` x `);
            this.data.dimensions = wlh;
            break;
          case "notes":
            this.data.notes = `\n${this.io.notes.value}`;
            break;
          default:
            break;
        }
      }

      /**
       * This method is an event handler bound to all form
       * controls with [name="buttons"] (see #12) that will be
       * invoked when the "click" event occurs (user clicks a
       * <button>). Each key name and any values will be
       * displayed. If the submit <button> (see #5) was
       * clicked, a timestamp id will be assigned as well.
       * This feature can be used to check this.data at any 
       * time.
       * @param event - Event object type "click"
       */
      onClick(event) {
        this.io.viewData.textContent = `\nData\n====\n`;
        if (event.target.id != "view") {
          this.data.pkgid = this.pkgID();
        }
        ["pkgid", "type", "weight", "dimensions", "notes"]
        .forEach(n => {
          if (!this.data[n]) {
            this.data[n] = " ";
          }
          this.io.viewData.textContent += `${n}: ${this.data[n]}\n`;
        });
      }

      /**
       * This method is an event handler bound to the <form> 
       * (see #13) that will be invoked when the "reset" event 
       * is triggered (user clicks the <button type="reset">).
       * <form> is reset, this.data, and the <output>s are
       * cleared.
       * This feature can be used at any time.
       * @param event - Event object type "reset"
       */
      onReset(event) {
        ["pkgid", "type", "weight", "dimensions", "notes"]
        .forEach(n => {
          this.data[n] = " ";
        });
        this.io.views.textContent = "";
      }

      /**
       * This method is an event handler bound to the <form> 
       * (see #14) that will be invoked when the "submit" event 
       * is triggered (user clicks the submit <button> (see #5)
       * or clicks the enter/return key). A FormData object 
       * will be instantiated and all of this.data (see #10)
       * key names/values will be assigned to it. 
       * this.sendPkg() method will be invoked. Normal <form> 
       * behavior will be prevented. 
       * This feature can only be used when the user has
       * entered correct data in the <select> and all of the
       * <input>s (see #2).
       * @param event - Event object type "submit"
       */
      onSubmit(event) {
        const fData = new FormData();
        ["pkgid", "type", "weight", "dimensions", "notes"]
        .forEach(n => {
          fData.set(n, this.data[n]);
        });
        this.sendPkg(fData);
        event.preventDefault();
      }

      /**
       * This method will pass a FormData object and send it to
       * a live test server. It will display the response if it
       * was successful.
       * @param data - FormData object
       */
      async sendPkg(data) {
        const response = await fetch(
          "https://httpbin.org/post", {
            method: "POST",
            body: data
          });
        const json = await response.text();
        this.io.viewResponse.textContent = `\nResponse\n========\n`;
        this.io.viewResponse.textContent += json;
      }

      // This method generates a timestamp
      pkgID() {
        return Date.now();
      }
    }
    // Instantiate 
    const pkgData = new Package("shipments");
  </script>
</body>

</html>

Not the answer you're looking for? Browse other questions tagged or ask your own question.