19.面向对象的表单(一)

未匹配的标注
  • 本系列文章为laracasts.com 的系列视频教程——Learn Vue 2: Step By Step 的学习笔记。若喜欢该系列视频,可去该网站订阅后下载该系列视频,支持正版
  • 视频源码地址:github.com/laracasts/Vue-Forms
  • 项目初始版本为Vue 2.1.3,教程还在更新中。

本节说明

  • 对应第 19 小节:Object Oriented Forms Part-1

本节内容

本节我们开始学习如何使用 Vue 来进行表单提交。下面是准备工作:

routes/web.php

Route::get('projects/create','ProjectsController@create');
Route::post('projects','ProjectsController@store');

app/Project.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Project extends Model
{

}

app/Http/Controllers/ProjectsController.php

<?php

namespace App\Http\Controllers;

use App\Project;
use Illuminate\Http\Request;

class ProjectsController extends Controller
{
    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        return view('projects.create',[
            'projects' => Project::all()
        ]);
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $this->validate(request(),[
            'name' => 'required',
            'description' => 'required'
        ]);

        Project::forecCreate([
            'name' => request('name'),
            'description' => request('description')
        ]); 

        return ['message' => 'Project created!'];
    }
}

resources/views/projects/create.blade.php

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="urf-8">
    <title>Document</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.2.3/css/bulma.css">
    <style> body{padding-top: 40px} </style>
</head>

<body>
    <div id="app" class="container">
        @include('projects.list')

        <form method="POST" action="/projects">
            <div class="control">
                <label for="name" class="label">Project Name:</label>

                <input type="text" id="name" name="name" class="input">
            </div>

            <div class="control">
                <label for="description" class="label">Project Description:</label>

                <input type="text" id="description" name="description" class="input">
            </div>

            <div class="control">
                <button class="button is-primary">Create</button>
            </div>
        </form>
    </div>

    <script src="https://unpkg.com/vue@2.1.3/dist/vue.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script src="/js/app.js"></script>
</body>

</html>

resources/views/projects/list.blade.php

<div class="control">
    <ul>
        @foreach ($projects as $project)
            <li>{{ $project->name }}</li>
        @endforeach
    </ul>
</div>

database/migrations/{timestamp}_create_projects_table.php

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateProjectsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('projects', function e(Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('name');
            $table->text('description');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('projects');
    }
}

准备工作完成后,我们在浏览器查看页面:

19.面向对象的表单(一)

接下来我们正式开始我们的内容。第一步我们来绑定 namedescription

resources/views/projects/create.blade.php

    .
    .
    <form method="POST" action="/projects">
        <div class="control">
            <label for="name" class="label">Project Name:</label>

            <input type="text" id="name" name="name" class="input" v-model="name">
        </div>

        <div class="control">
            <label for="description" class="label">Project Description:</label>

            <input type="text" id="description" name="description" class="input" v-model="description">
        </div>
        .
        .

public/js/app.js

new Vue({
    el:'#app',

    data: {
        name: '',
        description: ''
    }
}); 

效果如图:

19.面向对象的表单(一)

现在我们来完善我们的逻辑:点击 Create 按钮,异步提交表单。首先我们要修改表单:

<form method="POST" action="/projects" @submit.prevent="onSubmit">

注意 @submit.prevent,我们使用了 Vue 提供的 事件修饰符,从而达到点击按钮触发 onSubmit 方法,并且提交事件不再重载页面的目的。接下来就是新增 onSubmit 方法:

public/js/app.js

new Vue({
    el:'#app',

    data: {
        name: '',
        description: ''
    },

    methods: {
        onSubmit() {
            axios.post('/projects',this.$data);
        }
    }
})

注:使用 this.$datdata 对象的全部提交。

现在我们可以在页面进行创建的测试,成功后的结果如图:

19.面向对象的表单(一)

下面我们来为创建成功加上反馈:

public/js/app.js

onSubmit() {
    axios.post('/projects',this.$data)
        .then(response => alert('success'));

再次创建:

19.面向对象的表单(一)

通常情况下,我们还会限定 namedescription 字段必填。Laravel 为我们提供了表单验证功能,并且我们已经成功应用:

app/Http/Controllers/ProjectsController.php

.
.
public function store(Request $request)
{
    $this->validate(request(),[
        'name' => 'required',
        'description' => 'required'
    ]);
    .
}
.

所以我们可以把验证交给后端来处理,在前端提示验证不通过的提示即可。首先我们来捕获验证不通过的异常:

onSubmit() {
    axios.post('/projects',this.$data)
        .then(response => alert('success'))
        .catch(error => console.log(error.response.data));
}

尝试提交空白表单:

19.面向对象的表单(一)

现在我们已经获取到了服务端返回的错误提示,在 response.data.errors 属性中显示了所有的错误。接下来我们要做的就是展示服务端返回的错误提示:

public/js/app.js

class Errors {
    constructor() {
        this.errors = {};
    }

    get(field) {
        if(this.errors[field]) {
            return this.errors[field][0];
        }
    }

    record(errors) {
        this.errors = errors;
    }
}

new Vue({
    el:'#app',

    data: {
        name: '',
        description: '',
        errors: new Errors()
    },

    methods: {
        onSubmit() {
            axios.post('/projects',this.$data)
                .then(response => alert('success'))
                .catch(error => this.errors.record(error.response.data.errors));
        }
    }
})

在上面的代码中,我们定义了一个 Error 类,该类在初始化时会有一个值为空对象的属性 error。我们实例化 Vue 时,实例化 Error 类,并绑定到 Vue 实例的 errors 属性。在我们捕获到服务端返回的验证的错误时,我们更新 Error 类的 error 属性,这样我们就能通过 error.get('name') 获取到返回的错误中关于 name 的错误提示:

resources/views/projects/create.blade.php

.
.
<div class="control">
    <label for="name" class="label">Project Name:</label>

    <input type="text" id="name" name="name" class="input" v-model="name">

    <span class="help is-danger" v-text="errors.get('name')"></span>
</div>

<div class="control">
    <label for="description" class="label">Project Description:</label>

    <input type="text" id="description" name="description" class="input" v-model="description">

    <span class="help is-danger" v-text="errors.get('description')"></span>
</div>
.
.

再次尝试提交空白表单:

19.面向对象的表单(一)

当我们再次输入时,我们想让错误提示消失。常见的做法是增加一个 clear() 方法,清空属性:

public/js/app.js

class Errors {
    constructor() {
        this.errors = {};
    }

    get(field) {
        if(this.errors[field]) {
            return this.errors[field][0];
        }
    }

    record(errors) {
        this.errors = errors;
    }

    clear(field) {
        delete this.errors[field];
    }
}
.
.

在页面应用:

resources/views/projects/create.blade.php

<input type="text" id="name" name="name" class="input" v-model="name" @keydown="errors.clear('name')">

在页面尝试:

19.面向对象的表单(一)

我们当然可以这么做,但是想像一下,当你的表单有几十个字段需要处理时,相信我,你一定会抓狂。所以我们进行下优化:

resources/views/projects/create.blade.php

<form method="POST" action="/projects" @submit.prevent="onSubmit" @keydown="errors.clear($event.target.name)">

这样一来,当表单中的任意一个元素触发 keydown 事件时,我们通过 $event.target.name获取到该元素的 name 属性,然后触发 clear 方法。

再次尝试:

19.面向对象的表单(一)

最后,我们还有两个优化的地方。首先,没有错误时,我们不显示错误提示的 span 元素,因为会出现不自然的间距;然后,当任意一个错误提示存在时,不能点击 Create 按钮进行提交。

首先我们来看第一个问题,我们提供一个 errors.has(field) 方法来判断是否存在 filed 类型的错误即可:

public/js/app.js

class Errors {
    constructor() {
        this.errors = {};
    }

    get(field) {
        if(this.errors[field]) {
            return this.errors[field][0];
        }
    }

    has(field) {
        return this.errors.hasOwnProperty(field);
    }
    .
    .

resources/views/projects/create.blade.php

.
.
<span v-if="errors.has('name')" class="help is-danger" v-text="errors.get('name')"></span>
.
<span class="help is-danger" v-text="errors.get('description')"></span>
.
.

然后我们提供一个 errors.any() 方法,来判断是否存在错误,进而决定按钮是否可以点击:

public/js/app.js

class Errors {
    constructor() {
        this.errors = {};
    }

    get(field) {
        if(this.errors[field]) {
            return this.errors[field][0];
        }
    }

    has(field) {
        return this.errors.hasOwnProperty(field);
    }

    any() {
        return Object.keys(this.errors).length > 0;
    }
    .
    .

resources/views/projects/create.blade.php

<div class="control">
    <button class="button is-primary" :disabled="errors.any()">Create</button>
</div>

再次尝试:

19.面向对象的表单(一)

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 0
发起讨论 只看当前版本


暂无话题~