[JS pocket book] Chapter 11: use of HTML forms and localStorage

By valentinogagliardi
Translator: front-end wit
Source: github

Alibaba cloud server is very cheap and popular. This year, it is cheaper than last year. From October 24 to November 11, the purchase cost is 86 yuan in one year and 229 yuan in three years. You can click the following link to participate:
https://www.aliyun.com/1111/2...

In order to ensure readability, this paper adopts free translation instead of literal translation.

Reintroduce HTML forms

Web pages are not just for displaying data. With HTML forms, we can collect and manipulate user data. In this chapter, learn about forms by building a simple HTML form.

In this process, we will learn more about DOM events. From Chapter 8, we know that a < form > element is an HTML element, which may contain other child elements, such as:

  • < input > for data capture
  • < textarea > for capturing text
  • < button > for form submission

In this chapter, we build a recognition that includes < input >, < textarea > and < button >. Ideally, each input should have an attribute of type indicating the type of input: for example, text, email, number, date, and so on. In addition to the type attribute, you may want to add an id attribute to each form element.

input and textarea can also have a name attribute. If you want to send a form without using JS, the name attribute is very important. It will be described in detail later.

In addition, it is a common way to associate each form element with < label >. In the following example, you will see the id of the input element corresponding to each label and for attribute binding, which is used to focus input by clicking the label element.

If you do not fill in all the required information, users will not be able to submit the form. This is a simple validation to avoid empty data, thus preventing users from skipping important fields. With this knowledge, you can now create HTML forms. Create a new file called form.html and build the HTML:

<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>HTML forms and JavaScript</title>
</head>
<body>
<h1>What's next?</h1>
<form>
    <label for="name">Name</label>
    <input type="text" id="name" name="name" required>

    <label for="description">Short description</label>
    <input type="text" id="description" name="description" required>

    <label for="task">Task</label>
    <textarea id="task" name="tak" required></textarea>

    <button type="submit">Submit</button>
</form>
</body>
<script src="form.js"></script>
</html>

As mentioned above, the input in the form has the correct attributes, and from now on, you can test the form by filling in some data When writing HTML forms, pay special attention to the type attribute, because it determines what data the user can enter.

HTML 5 also introduces form validation: for example, input of type email only accepts email addresses with the "at" symbol @. Unfortunately, this is the only check that applies to email addresses: no one will prevent users from entering emails like a@a. It has @, but is still invalid (the pattern attribute for email input can help solve this problem.

There are many properties available on < input >, and I find minlength and maxlength to be the most useful. In practice, they can prevent lazy spammers from sending forms with "aa" or "testtest".

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>HTML forms and JavaScript</title>
</head>
<body>
<h1>What's next?</h1>
<form>
    <label for="name">Name</label>
    <input type="text" id="name" name="name" required minlength="5">

    <label for="description">Short description</label>
    <input type="text" id="description" name="description" required minlength="5">

    <label for="task">Task</label>
    <textarea id="task" name="tak" required minlength="10"></textarea>

    <button type="submit">Submit</button>
</form>
</body>
<script src="form.js"></script>
</html>

With this form, we can go further. Next, let's see how the form works.

How forms work

HTML form is HTMLFormElement An element of type. Like almost all HTML elements, it connects to the HTML element, which in turn connects to the EventTarget. When we access DOM elements, they are represented as JS objects. Try this in the browser:

const aForm = document.createElement("form");
console.log(typeof aForm);

The output is "object", while entities like HTMLElement or EventTarget are functions:

console.log(typeof EventTarget); // "function"

Therefore, if any HTML element is connected to EventTarget, this means that < form > is an "instance" of EventTarget, as follows:

const aForm = document.createElement("form");
console.log(aForm instanceof EventTarget); // true

form is a specialized type of EventTarget. Each EventTarget can receive and respond to DOM events (as shown in Chapter 8).

DOM events have many type s, such as click, blur, change, and so on. Now, we are interested in the submit event specific to HTML forms. When the user clicks the input or "submit" button (the element must appear in the form), the submit event is dispatched as follows:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>HTML forms and JavaScript</title>
</head>
<body>
<h1>What's next?</h1>
<form>
    <label for="name">Name</label>
    <input type="text" id="name" name="name" required minlength="5">

    <label for="description">Short description</label>
    <input type="text" id="description" name="description" required minlength="5">

    <label for="task">Task</label>
    <textarea id="task" name="task" required minlength="10"></textarea>

    <button type="submit">Submit</button>
</form>
</body>
<script src="form.js"></script>
</html>

Note that < button type = "submit" > submit < / button > is inside the form Some developers use the input method:

<! -- General submit button -- >
<input type="submit">

<! -- custom submit button -- >
< button > submit form < / button >

<! -- image button -- >
<input type='image' src='av.gif'/>

As long as the form has any of the buttons listed above, press enter to submit the form when the corresponding form control has focus. (textarea is an exception, where a carriage return wraps the line.) If there is no submit button in the form, pressing enter will not submit the form.

Our goal is to get all the user input on the form, so we need to monitor the submit event.

const formSelector = document.querySelector("form");

new Form(formSelector);

DOM also provides document.forms, which is a collection of all forms within a page We just need:

const formSelector = document.forms[0];

new Form(formSelector);

Now the idea is: given a form selector, we can register an event listener to respond to the sending of the form. To register the listener, we can use the constructor and have it call a method called init. Create a new file named form.js in the same folder as form.html, and start with a simple class:

"use strict";

class Form {
  constructor(formSelector) {
    this.formSelector = formSelector;
    this.init();
  }

  init() {
    this.formSelector.addEventListener("submit", this.handleSubmit);
  }
}

Our event listener is this. Handlesubmit. Like every event listener, it can access the parameter named event As you should know from Chapter 8, events are actually dispatched events that contain a lot of useful information about the action itself Let's implement this.handleSubmit:

"use strict";

class Form {
  constructor(formSelector) {
    this.formSelector = formSelector;
    this.init();
  }

  init() {
    this.formSelector.addEventListener("submit", this.handleSubmit);
  }

  handleSubmit(event) {
    console.log(event);
  }
}

Then, instantiate the class From:

const formSelector = document.forms[0];

new Form(formSelector);

At this point, open form.html in the browser. Enter the content and click Submit. What happens? The output is as follows:

http://localhost:63342/little-javascript/code/ch10/form.html?name=Valentino&description=Trip+to+Spoleto&tak=We%27re+going+to+visit+the+city%21

What's going on Most DOM events have so-called "default behavior.". In particular, the submit event attempts to send form data to a fictitious server. This is the way to send forms without JS, because it's part of an application based on web frameworks like Django, Rails, and friends.

Each input value is mapped to the corresponding name attribute. In this case, name is not needed, because we want to use JS to control the form, so we need to disable the default behavior. You can disable this by calling preventDefault:

"use strict";

class Form {
  constructor(formSelector) {
    this.formSelector = formSelector;
    this.init();
  }

  init() {
    this.formSelector.addEventListener("submit", this.handleSubmit);
  }

  handleSubmit(event) {
    event.preventDefault();
    console.log(event);
  }
}

const formSelector = document.forms[0];

new Form(formSelector);

Save the file and refresh form.html again Try filling out the form, and then click Submit You will see the event object printed to the console:

Event {...}
    bubbles: true
    cancelBubble: false
    cancelable: true
    composed: false
    currentTarget: null
    defaultPrevented: true
    eventPhase: 0
    isTrusted: true
    path: (5) [form, body, html, document, Window]
    returnValue: false
    srcElement: form
    target: form
    timeStamp: 8320.840000000317
    type: "submit"

Among the many properties of the event object, there is event.target, which indicates that our HTML form is saved there with all the input to see if it is.

Extract data from

To get the value of the form, you will find an attribute named elements by checking event.target This attribute is a collection of all elements in the form. The elements set is a sequential table that contains all the fields of the form, such as < input >, < textarea >, < button > and < fieldset >. If you try to print with console.log(event.target.elements), you will see:

0: input#name
1: input#description
2: textarea#task
3: button
length: 4
description: input#description
name: input#name
tak: textarea#task
task: textarea#task

The order of each form field in the elements collection is the same as the order in which they appear in tags, and they can be accessed by location and name attributes. Now, we have two ways to get the input value:

  • Through an array like representation: event.target.elements[0].value
  • Through id: event.target.elements.some_id.value

In fact, if you want to add an appropriate ID attribute to each form element now, you can access the same element as event.target.elements.some_id, where id is the string you assign to that attribute Because event.target.elements is an object first, you can also use ES6 object to deconstruct:

const { name, description, task } = event.target.elements;

This is not 100% recommended. For example, you will get an error in TypeScript, but just write "vanilla JS". Now that we have these values, we can complete handleSubmit. In the process, we also create another method called saveData. Now it just prints the values to the console:

"use strict";

class Form {
  constructor(formSelector) {
    this.formSelector = formSelector;
    this.init();
  }

  init() {
    this.formSelector.addEventListener("submit", this.handleSubmit);
  }

  handleSubmit(event) {
    event.preventDefault();
    const { name, description, task } = event.target.elements;
    this.saveData({
      name: name.value,
      description: description.value,
      task: task.value
    });
  }

  saveData(payload) {
    console.log(payload);
  }
}

const formSelector = document.forms[0];

new Form(formSelector);

This way of saving data is not the best judgment What if the field changes Now we have name, task and description, but more input may be added in the future, so we need to extract these fields dynamically Of course, to solve the problem of object destruction, take a look at event.target.elements

0: input#name
1: input#description
2: textarea#task
3: button
length: 4
description: input#description
name: input#name
tak: textarea#task
task: textarea#task

It looks like an array. Let's use the map method to convert it to include only name, description and task (Filter button type submit):

 
  handleSubmit(event) {
    event.preventDefault();
    const inputList = event.target.elements.map(function(formInput) {
      if (formInput.type !== "submit") {
        return formInput.value;
      }
    });

    /*
      TODO this.saveData( maybe inputList ?)
     */
  }

Try and view the console in the browser:

Uncaught TypeError: event.target.elements.map is not a function
    at HTMLFormElement.handleSubmit (form.js:15)

". map is not a function." So what exactly is event.target.elements It looks like an array, but it's another beast: it's the HTML formcontrols collection In Chapter 8, we learned about this and saw that some DOM methods returned HTMLCollection.

// Returns an HTMLCollection
document.chidren;

HTML collections look like arrays, but they lack methods such as map s or filter s to iterate over their elements You can still access each element using the square bracket notation. We can convert similar arrays into real arrays through Array.from:

  handleSubmit(event) {
    event.preventDefault();

    const arrOfElements = Array.from(event.target.elements);

    const inputList = arrOfElements.map(function(formInput) {
      if (formInput.type !== "submit") {
        return formInput.value;
      }
    });

    console.log(inputList);

    /*
      TODO this.saveData( maybe inputList ?)
     */
  }

Construct an array of event.target.elements through the array.from method. Array.from takes a mapping function as the second parameter to further optimize:

  handleSubmit(event) {
    event.preventDefault();

    const inputList = Array.from(event.target.elements, function(formInput) {
      if (formInput.type !== "submit") {
        return formInput.value;
      }
    });

    console.log(inputList);

    /*
      TODO this.saveData( maybe inputList ?)
     */
  }

Refresh form.html, fill out the form, and press submit See the following array in the console:

["Valentino", "Trip to Spoleto", "We're going to visit the city!", undefined]

Finally, I want to generate an array of objects, each of which also has the name attribute of the relevant form input:

  handleSubmit(event) {
    event.preventDefault();

    const inputList = Array.from(event.target.elements, function(formInput) {
      if (formInput.type !== "submit") {
        return {
          name: formInput.name,
          value: formInput.value
        };
      }
    });

    console.log(inputList);

    /*
      TODO this.saveData( maybe inputList ?)
     */
  }

Refresh form.html again, fill in the form, and you will see:

[
  {
    "name": "name",
    "value": "Valentino"
  },
  {
    "name": "description",
    "value": "Trip to Spoleto"
  },
  {
    "name": "task",
    "value": "We're going to visit the city!"
  },
  undefined
]

good job, which has an empty value of undefined, comes from the button element The default behavior of a map is to return undefined with an "empty" value Because we checked if (forminput. Type! = = "submit"), the button element was not returned from the map, but was replaced by undefined We can remove it later, now let's look at localStorage.

Understand localStorage and refine classes

Sometimes we need to keep some data for users. There are many reasons for this For example, consider a notes application where users can insert new content into an HTML form and then come back to view the notes The next time she opens the page, she will find everything in it.

What are the options for saving data in a browser? An important way to persist data is to use a database, but here we only have some HTML, JS and browsers. However, there is a built-in tool in the modern browser, which is just like a very simple database, very suitable for our needs: localStorage. localStorage behaves like a JS object, with a bunch of methods:

  • setItem is used to save data
  • getItem is used to read data
  • clear to delete all values
  • removeItem is used to clear the value of the corresponding key

Later, we will see setItem and getItem. First, we need to have a form.html file, which is as follows:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>HTML forms and JavaScript</title>
</head>
<body>
<h1>What's next?</h1>
<form>
    <label for="name">Name</label>
    <input type="text" id="name" name="name" required minlength="5">

    <label for="description">Short description</label>
    <input type="text" id="description" name="description" required minlength="5">

    <label for="task">Task</label>
    <textarea id="task" name="task" required minlength="10"></textarea>

    <button type="submit">Submit</button>
</form>
</body>
<script src="form.js"></script>
</html>

There are also JS codes for intercepting submitted events:

"use strict";

class Form {
  constructor(formSelector) {
    this.formSelector = formSelector;
    this.init();
  }

  init() {
    this.formSelector.addEventListener("submit", this.handleSubmit);
  }

  handleSubmit(event) {
    event.preventDefault();

    const inputList = Array.from(event.target.elements, function(formInput) {
      if (formInput.type !== "submit") {
        return {
          name: formInput.name,
          value: formInput.value
        };
      }
    });

    console.log(inputList);

    /*
      TODO this.saveData( maybe inputList ?)
     */
  }

  saveData(payload) {
    console.log(payload);
  }
}

const formSelector = document.forms[0];

new Form(formSelector);

At this point, we need to implement this.saveData to save each note to localStorage In doing so, you need to be as generic as possible In other words, I don't want to populate this. Savedata with the logic of saving directly to localStorage.

Instead, we provide an external dependency (another class) for the Form class, which implements the actual code It doesn't matter whether we save the note information to localStorage or database in the future For each use case, we should be able to provide a different "store" for the Form and move from one to another as the requirements change To do this, first adjust the constructor to accept the new store parameter:

class Form {
  constructor(formSelector, storage) {
    this.formSelector = formSelector;
    this.storage = storage;
    this.init();
  }

  init() {
    this.formSelector.addEventListener("submit", this.handleSubmit);
  }

  handleSubmit(event) {
    event.preventDefault();

    const inputList = Array.from(event.target.elements, function(formInput) {
      if (formInput.type !== "submit") {
        return {
          name: formInput.name,
          value: formInput.value
        };
      }
    });
  }

  saveData(payload) {
    console.log(payload);
  }
}

Now, as the complexity of the class increases, you need to verify the parameters of the constructor. As a class for processing HTML forms, we need to check whether formSelector is an HTML element of form type at least:

  constructor(formSelector, storage) {
    // Validating the arguments
    if (!(formSelector instanceof HTMLFormElement))
      throw Error(`Expected a form element got ${formSelector}`);
    //
    this.formSelector = formSelector;
    this.storage = storage;
    this.init();
  }

If formSelector is not a form type, an error will be reported. Also verify the storage, because we have to store user input somewhere.

  constructor(formSelector, storage) {
    // Validating the arguments
    if (!(formSelector instanceof HTMLFormElement))
      throw Error(`Expected a form element got ${formSelector}`);
    // Validating the arguments
    if (!storage) throw Error(`Expected a storage, got ${storage}`);
    //
    this.formSelector = formSelector;
    this.storage = storage;
    this.init();
  }

The storage implementation will be another class. In our example, it can be something similar to general LocalStorage. Create the class LocalStorage in form.js:

class LocalStorage {
  save() {
    return "saveStuff";
  }

  get() {
    return "getStuff";
  }
}

Now, with this structure, we can connect Form and LocalStorage:

  • saveData in Form should call the Storage implementation
  • LocalStorage.save and LocalStorage.get can be static

Still in form.js, change the class method as follows:

"use strict";

/*
Form implementation
 */
class Form {
  constructor(formSelector, storage) {
    // Validating the arguments
    if (!(formSelector instanceof HTMLFormElement))
      throw Error(`Expected a form element got ${formSelector}`);
    // Validating the arguments
    if (!(storage instanceof Storage))
      throw Error(`Expected a storage, got ${storage}`);
    //
    this.formSelector = formSelector;
    this.storage = storage;
    this.init();
  }

  init() {
    this.formSelector.addEventListener("submit", this.handleSubmit);
  }

  handleSubmit(event) {
    event.preventDefault();

    const inputList = Array.from(event.target.elements, function(formInput) {
      if (formInput.type !== "submit") {
        return {
          name: formInput.name,
          value: formInput.value
        };
      }
    });

    this.saveData('inputList', inputList);
  }

  saveData(key,payload) {
    this.storage.save(key, payload);
  }
}

/*
Storage implementation
 */
class LocalStorage {
  static save(key, val) {
    if (typeof val === 'object') {
      val = JSON.stringify(val)
    }
    localStorage.setItem(key, val, redis.print)
  }

  static get(key) {
    const val = localStorage.getItem(key)
    if (val === null)  return null
    return JSON.parse(val)
  }
}

const formSelector = document.forms[0];
const storage = LocalStorage;

new Form(formSelector, storage);

The bugs that may exist after code deployment can't be known in real time. In order to solve these bugs afterwards, a lot of time has been spent on log debugging. Here is a good BUG monitoring tool recommended by the way Fundebug.

Original text: https://github.com/valentinog...

Communication

Alibaba cloud has been doing activities recently, with a discount of 20%. If you are interested, please take a look at: https://promotion.aliyun.com/...

The articles of dry goods series are summarized as follows. I think it's a good idea to order a Star. Welcome to learn from each other.

https://github.com/qq449245884/xiaozhi

Because of the space limitation, today's sharing is only here. If you want to know more about it, you can scan the bottom two dimensional code of each article, then pay attention to our WeChat public address, and learn more information and valuable content.

I don't go to bed until 2 o'clock every time I sort out the articles. It's very painful. I hope I can support you and encourage you

Keywords: Javascript Attribute github Database

Added by andre3 on Fri, 01 Nov 2019 16:15:02 +0200