【JS 口袋书】第 11 章:HTML 表单及 localStorage 的使用

作者:valentinogagliardi
译者:前端小智
来源:github

阿里云服务器很便宜火爆,今年比去年便宜,10.24~11.11购买是1年86元,3年229元,可以点击 下面链接进行参与:
https://www.aliyun.com/1111/2...


为了保证的可读性,本文采用意译而非直译。

重新介绍 HTML 表单

网页不仅仅是用来显示数据的。有了 HTML 表单,咱们可以收集和操作用户数据。在本章中,通过构建一个简单的 HTML 表单来学习表单的相关的知识。

在这个过程中,会了解更多关于 DOM 事件的信息,从在 第8章 我们知道了一个

元素是一个 HTML 元素,它可能包含其他的子元素,比如:

  • 用于捕获数据
  • 如上所述,表单中的 input 具有正确的属性,从现在开始,可以通过填充一些数据来测试表单。 编写 HTML 表单时,要特别注意 type 属性,因为它决定了用户能够输入什么样的数据。

    HTML5 还引入了表单验证:例如,类型为 email 的输入只接受带有“at”符 号@ 的电子邮件地址。不幸的是,这是对电子邮件地址应用的惟一检查:没有人会阻止用户输入类似 a@a 这样的电子邮件。它有 @,但仍然是无效的(用于电子邮件输入的 pattern 属性可以帮助解决这个问题。

    上有很多可用的属性,我发现 minlengthmaxlength 是最有用的两个。在实战中,它们可以阻止懒惰的垃圾邮件发送者发送带有 “aa”“testtest” 的表单。

    
    
    
        
        HTML forms and JavaScript
    
    
    

    What's next?

    有了这个表单,咱们就可以更进一步了,接着,来看下表单是如何工作的。

    表单是如何工作

    HTML 表单是 HTMLFormElement 类型的一个元素。与几乎所有的 HTML 元素一样,它连接到 HTMLElement,后者又连接到 EventTarget。当我们访问 DOM 元素时,它们被表示为 JS 对象。在浏览器中试试这个:

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

    输出是 “object”,而像 HTMLElementEventTarget 这样的实体是函数:

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

    因此,如果任何 HTML 元素都连接到 EventTarget,这意味着

    EventTarget 的“实例”,如下:

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

    formEventTarget 的一种专门化类型。每个EventTarget 都可以接收和响应 DOM 事件(如第8章所示)。

    DOM 事件有很多类型,比如 clickblurchange 等等。现在,咱们感兴趣的是 HTML 表单特有的 submit 事件。当用户单击 inputtype 为 “submit” 的按钮(元素必须出现在表单中)时,就会分派 submit 事件,如下所示:

    
    
    
        
        HTML forms and JavaScript
    
    
    

    What's next?

    请注意, 就在表单内部。 一些开发人员使用input 方式:

    
    
    
    
    
    
    
    
    

    只要表单存在上面 列出的任何一种按钮,那么在相应表单控件拥有焦点的情况下,按回车键就可以提交表单。(textarea 是一个例外,在文本中回车会换行。)如果表单里没有提交按钮,按回车键不会提交表单。

    咱们的目标是获取表单上的所有用户输入,所以,需要监听 submit 事件。

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

    DOM 还提供 document.forms,这是页面内所有表单的集合。 咱们现在只需要:

    const formSelector = document.forms[0];
    
    new Form(formSelector);
    

    现在的想法是:给定一个表单选择器,我们可以注册一个事件监听器来响应表单的发送。为了注册监听器,我们可以使用构造函数,并让它调用一个名为 init 的方法。在与 form.html 相同的文件夹中创建一个名为 form.js 的新文件,并从一个简单的类开始:

    "use strict";
    
    class Form {
      constructor(formSelector) {
        this.formSelector = formSelector;
        this.init();
      }
    
      init() {
        this.formSelector.addEventListener("submit", this.handleSubmit);
      }
    }
    

    咱们的事件监听器是 this.handleSubmit,与每个事件监听器一样,它可以访问名为 event 的参数。 从第8章中应该知道,事件是实际分派的事件,其中包含有关动作本身的许多有用信息。 咱们来实现 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);
      }
    }
    

    然后,实例化类 From:

    const formSelector = document.forms[0];
    
    new Form(formSelector);
    

    此时,在浏览器中打开 form.html。输入内容并点击“提交”。会发生什么呢? 输出如下:

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

    这是怎么回事? 大多数 DOM 事件都有所谓的“默认行为”。submit 事件尤其尝试将表单数据发送到虚构的服务器。这就是在没有 JS的 情况下发送表单的方式,因为它是基于 DjangoRailsfriends 等 web 框架的应用程序的一部分。

    每个输入值都映射到相应的 name 属性。在本例中不需要 name,因为这里咱们想用 JS 来控制表单,所以需要禁用默认行为。可以通过调用 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);
    

    保存文件,然后再次刷新 form.html。 尝试填写表格,然后单击提交。 会看到 event 对象打印到控制台:

    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"
    

    event 对象的许多属性中,还有 event.target,这表明咱们的 HTML 表单与所有输入一起保存在那里,来看看是否确实如此。

    从 from 提取数据

    为了获取表单的值,通过检查 event.target,您将发现有一个名为 elements 的属性。 该属性是表单中所有元素的集合。这个 elements 集合是一个有序列表,其中包含着表单的所有字段,例如

    还有用于拦截提交事件的相关 JS 代码:

    "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);
    

    此时,咱们需要实现 this.saveData 来将每个笔记保存到 localStorage。 这样做时,需要保持尽可能的通用。 换句话说,我不想用直接保存到 localStorage 的逻辑来填充this.saveData

    相反,咱们为 Form 类提供一个外部依赖项(另一个类),该类的作用是实现实际代码。 将来我们将这些笔记信息保存到 localStorage 还是数据库中都没有关系。 对于每种用例,我们应该能够为 Form 提供不同的“存储”,并随着需求的变化而从一种转换为另一种。 为此,我们首先调整构造函数以接受新的“存储”参数:

    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);
      }
    }
    

    现在,随着类的复杂度增加,需要验证构造函数的参数。作为一个用于处理 HTML 表单的类,咱们至少需要检查 formSelector 是否是 form 类型的 HTML 元素:

      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();
      }
    

    如果 formSelector 不是一个表单类型的,就会报错。另外还要验证 storage,因为我们必须将用户输入存储到某个地方。

      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();
      }
    

    存储实现将是另一个类。在我们的例子中,可以是类似于通用LocalStorage的东西,在 form.js 中创建类 LocalStorage

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

    现在,有了这个结构,我们就可以连接 FormLocalStorage

    • Form 中的 saveData 应该调用 Storage 实现
    • LocalStorage.saveLocalStorage.get 可以是静态的

    仍然在 form.js 中,如下更改类方法:

    "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);
    

    代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug。

    原文:https://github.com/valentinog...

    交流

    阿里云最近在做活动,低至2折,有兴趣可以看看:https://promotion.aliyun.com/...

    干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。

    https://github.com/qq449245884/xiaozhi

    因为篇幅的限制,今天的分享只到这里。如果大家想了解更多的内容的话,可以去扫一扫每篇文章最下面的二维码,然后关注咱们的微信公众号,了解更多的资讯和有价值的内容。

    每次整理文章,一般都到2点才睡觉,一周4次左右,挺苦的,还望支持,给点鼓励

你可能感兴趣的:(javascript,前端,后端,es6)