五、使用表单生成器

在本章中,您将学习如何使用 Laravel 的表单生成器。将演示表单构建器,以便于构建以下元素:

  • 表单(打开和关闭)
  • 标签
  • 输入(文本、HTML5 密码、HTML5 电子邮件等。)
  • 检验盒
  • 使服从
  • 锚点标签(href 链接)

最后,我们将看到一个示例,说明如何使用表单生成器为住宿预订软件表单创建月份、日期和年份选择元素,以及如何创建一个宏来减少代码重复。

历史

Laravel 4 中的表单构建器包叫做 HTML。这是用来帮助你创建 HTML 的,尤其是开发人员,他们也必须履行网页设计师的职责,但更喜欢使用 Laravel 外观和助手方法。例如,下面的 Laravel facade select()方法,其中该语言的选项,在本例中为英式英语和美式英语,作为数组参数传递:

Form::select('language', ['en-us' => 'English (US)','en-gb' => 'English (UK)']);

这可以作为标准 HTML 的替代,标准 HTML 需要更多重复的代码,如下面的代码所示:

<select name="language">
    <option value="en-us">English (US)</option>
    <option value="en-gb">English (UK)</option>
</select>

由于框架在不断发展,它们需要适应以满足大多数用户的需求。此外,只要有可能,他们应该继续提高效率。在某些情况下,这意味着重写或重构框架的各个部分,添加特性,甚至删除它们。

虽然看起来很奇怪,但删除这些特征有几个合理的理由。以下是删除软件包的原因列表:

  • 减轻框架核心开发人员需要维护的包和特性的负担和数量。
  • 减少下载和自动加载的软件包数量。
  • 删除不重要的功能。
  • HTML 包从 Laravel 5 的核心中移除,现在是一个外部包。在这种情况下,之前的任何原因都可以作为删除该包的原因。
  • 如果前端开发人员也是后端或全栈开发人员,并且更喜欢 Laravel 的做事方式,则可以使用 HTML,它可以帮助开发人员构建表单。然而,在其他情况下,web 应用的 HTML 接口可以使用 JavaScript 框架或库来构建,例如 AngularJS 或主干. JS。在这种情况下,Laravel 表单包将是不必要的。或者,如前所述,Laravel 可以用来创建一个仅仅是 RESTful API 的应用。在这种情况下,在框架核心中包含 HTML 包将是不必要的,因此仍然是辅助的。

在这种特殊情况下,某些 Laravel 包被删除,以减轻整体体验,并转向更基于组件的方法,类似于 Symfony 中使用的方法。

安装 HTML 包

如果你想要使用 Laravel 5 中的 HTML 包,安装它是一个简单的过程。Laravel 社区中的一组开发人员组成了一个名为 Laravel 集体的存储库,在这里维护从 Laravel 中移除的包。要安装 HTML 包,只需使用composer命令将包添加到应用中,如下所示:

$ composer require laravelcollective/html

注意illuminate/HTML包已被弃用。

这将安装 HTML 包,并且composer.json将向您显示添加到require部分的包,如下所示:

"require": {
    "laravel/framework": "5.0.*",
    "laravelcollective/html": "~5.0",
  },

此时点,封装安装完毕。

现在,我们需要将HTMLServiceProvider添加到config/app.php文件中的提供商列表中:

  'providers' => [
  ...
    'Collective\Html\HtmlServiceProvider',
  ...
  ],

最后,FormHtml别名需要添加到config/app.php文件中,如下图所示:

'aliases' => [
   ...
        'Form' => 'Collective\Html\FormFacade',
        'Html' => 'Collective\Html\HtmlFacade',
   ...
  ],

用 Laravel 构建网页

Laravel 构建网络内容的方法是灵活的。可以使用或多或少的 Laravel 来创建 HTML。Laravel 使用filename.blade.php约定声明文件应该由 blade 解析器解析,它实际上将文件转换成普通的 PHP。刀锋战士这个名字的灵感来自。NET 的 razor 模板引擎,所以这对于使用过它的人来说可能很熟悉。Laravel 5 在/resources/views/目录中提供了一个表单的工作演示。当请求/home路线并且用户当前未登录时,会显示该视图。这个表单显然不是使用 Laravel 表单方法创建的。

路线在routes文件中定义如下:

Route::get('home', 'HomeController@index');

关于此路由如何使用中间件检查如何执行用户身份验证的解释将在第 7 章使用中间件过滤请求中讨论。

主模板

这是下面的app(或master)模板:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Laravel</title>

    <link href="/css/app.css" rel="stylesheet">

    <!-- Fonts -->
    <link href='//fonts.googleapis.com/css?family=Roboto:400,300' rel='stylesheet' type='text/css'>

    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
        <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
        <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->
</head>
<body>
    <nav class="navbarnavbar-default">
        <div class="container-fluid">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
                    <span class="sr-only">Toggle Navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a class="navbar-brand" href="#">Laravel</a>
            </div>

            <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                <ul class="navnavbar-nav">
                    <li><a href="/">Home</a></li>
                </ul>

                <ul class="navnavbar-navnavbar-right">
                    @if (Auth::guest())
                        <li><a href="{{ route('auth.login') }}">Login</a></li>
                        <li><a href="/auth/register">Register</a></li>
                    @else
                        <li class="dropdown">
                            <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">{{ Auth::user()->name }} <span class="caret"></span></a>
                            <ul class="dropdown-menu" role="menu">
                                <li><a href="/auth/logout">Logout</a></li>
                            </ul>
                        </li>
                    @endif
                </ul>
            </div>
        </div>
    </nav>

    @yield('content')

    <!-- Scripts -->
    <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.1/js/bootstrap.min.js"></script>
</body>
</html>

Laravel 5 主模板是一个标准的 HTML5 模板,具有以下特征:

  • 如果浏览器比 Internet Explorer 9 旧:
    • 使用 CDN 中的 HTML5 垫片
    • 使用来自 CDN 的 Respond.js JavaScript 代码来改进媒体查询和 CSS3 特性
  • 使用@if (Auth::guest()),如果用户未通过认证,显示登录表单;否则,将显示注销选项
  • 推特 bootstrap 3.x 包含在 CDN 中
  • jQuery2.x 包含在 CDN 中
  • 任何扩展此模板的模板都可以覆盖内容部分

示例页面

下面的截图给大家展示了登录页面:

An example page

登录页面的源代码如下:

@extends('app')
@section('content')
<div class="container-fluid">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <div class="panel panel-default">
                <div class="panel-heading">Login</div>
                <div class="panel-body">
                    @if (count($errors) > 0)
                        <div class="alert alert-danger">
                            <strong>Whoops!</strong> There were some problems with your input.<br><br>
                            <ul>
                                @foreach ($errors->all() as $error)
                                    <li>{{ $error }}</li>
                                @endforeach
                            </ul>
                        </div>
                    @endif

                    <form class="form-horizontal" role="form" method="POST" action="/auth/login">
                        <input type="hidden" name="_token" value="{{ csrf_token() }}">

                        <div class="form-group">
                            <label class="col-md-4 control-label">E-Mail Address</label>
                            <div class="col-md-6">
                                <input type="email" class="form-control" name="email" value="{{ old('email') }}">
                            </div>
                        </div>

                        <div class="form-group">
                            <label class="col-md-4 control-label">Password</label>
                            <div class="col-md-6">
                                <input type="password" class="form-control" name="password">
                            </div>
                        </div>

                        <div class="form-group">
                            <div class="col-md-6 col-md-offset-4">
                                <div class="checkbox">
                                    <label>
                                        <input type="checkbox" name="remember"> Remember Me
                                    </label>
                                </div>
                            </div>
                        </div>

                        <div class="form-group">
                            <div class="col-md-6 col-md-offset-4">
                                <button type="submit" lass="btn btn-primary" style="margin-right: 15px;">
                                    Login
                                </button>

                                <a href="/password/email">Forgot Your Password?</a>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

从静态 HTML 到静态方法

该登录页面以如下内容开始:

@extends('app')

它显然使用了面向对象的范式来声明app.blade.php模板将被渲染。下一行覆盖内容:

@section('content')

在本练习中,将使用表单生成器而不是静态 HTML。

表单标签

我们将把静态form标签转换成FormBuilder方法。该 HTML 如下:

<form class="form-horizontal" role="form" method="POST" action="/auth/login">

我们将使用的方法外观如下:

Form::open();

FormBuilder.php类中,$reserved属性的定义如下:

protected $reserved = ['method', 'url', 'route', 'action', 'files'];

我们需要传递给open()方法的属性是类、角色、方法和动作。由于方法和动作是保留字,因此有必要以下列方式传递参数:

|

Laravel 表单外观方法数组元素

|

HTML 表单标记属性

| | --- | --- | | 方法 | 方法 | | 全球资源定位器(Uniform Resource Locator) | 行为 | | 作用 | 作用 | | 班级 | 班级 |

因此,方法调用如下所示:

{!! 
  Form::open(['class'=>'form-horizontal',
  'role =>'form',
  'method'=>'POST',
  'url'=>'/auth/login']) 
!!}

{!! !!}标记用于开始和结束表单构建器方法的解析。表单方法POST被放在 HTML 表单标签的属性列表的第一位。

类型

action属性实际上需要是一个url。如果使用了action参数,则表示控制器动作。在这种情况下,url参数产生form标签的action属性。

其他属性将被传递给数组并添加到属性列表中。生成的 HTML 将如下所示:

<form method="POST" action="http://laravel.example/auth/login" accept-charset="UTF-8" class="form-horizontal" role="form">

<input name="_token" type="hidden" value="wUY2hFSEWCzKHFfhywHvFbq9TXymUDiRUFreJD4h">

自动添加 CRSF 令牌,因为form方法是POST

文本输入字段

为了转换输入字段,使用了外观。输入字段的 HTML 如下:

<input type="email" class="form-control" name="email" value="{{ old('email') }}">

使用外观转换前面的输入字段如下所示:

{!! Form::input('email','email',old('email'),['class'=>'form-control' ]) !!}

同样,文本字段变为:

{!! Form::input('password','password',null,['class'=>'form-control']) !!}

输入字段具有相同的签名。当然,这可以重构如下:

<?php $inputAttributes = ['class'=>'form-control'] ?>
{!! Form::input('email','email',old('email'),$inputAttributes ) !!}
...
{!! Form::input('password','password',null,$inputAttributes ) !!}

标签标签

label标签如下:

<label class="col-md-4 control-label">E-Mail Address</label>
<label class="col-md-4 control-label">Password</label>

为了转换label标签(E-Mail AddressPassword,我们将首先创建一个数组来保存属性,然后将这个数组传递给标签,如下所示:

$labelAttributes = ['class'=>'col-md-4 control-label'];

以下是表单标签代码:

{!! Form::label('email', 'E-Mail Address', $labelAttributes) !!}
{!! Form::label('password', 'Password', $labelAttributes) !!}

复选框

要将复选框转换为外观,我们将转换它:

<input type="checkbox" name="remember"> Remember Me

前面的代码转换为下面的代码:

{!! Form::checkbox('remember','') !!} Remember Me

类型

请记住,如果要解析的字符串中没有变量或其他特殊字符(如换行符),PHP 参数应该用单引号发送,而生成的 HTML 将有双引号。

提交按钮

最后,提交按钮将转换如下:

<button type="submit" class="btn btn-primary" style="margin-right: 15px;">
    Login
</button>

转换后的上述代码如下:

    {!! 
        Form::submit('Login',
        ['class'=>'btn btn-primary', 
        'style'=>'margin-right: 15px;'])
     !!}

类型

请注意,数组参数提供了一种简单的方法来提供任何所需的属性,甚至是那些不在标准 HTML 表单元素列表中的属性。

带链接的锚点标签

为了转换链接,使用了一个辅助方法。考虑以下代码行:

<a href="/password/email">Forgot Your Password?</a>

转换后的前一行代码变为:

{!! link_to('/password/email', $title = 'Forgot Your Password?', $attributes = array(), $secure = null) !!}

link_to_route()方法可用于链接到路线。类似的助手功能,请访问http://laravelcollective.com/docs/5.0/html

关闭表单

为了结束表单,我们将把传统的 HTML 表单标签</form>转换成 Laravel {!! Form::close() !!}表单方法。

结果形式

通过将所有内容放在一起,页面现在看起来像这样:

@extends('app')
@section('content')
<div class="container-fluid">
  <div class="row">
    <div class="col-md-8 col-md-offset-2">
      <div class="panel panel-default">
        <div class="panel-heading">Login</div>
          <div class="panel-body">
            @if (count($errors) > 0)
                <div class="alert alert-danger">
                    <strong>Whoops!</strong> There were some problems with your input.<br><br>
                    <ul>
                        @foreach ($errors->all() as $error)
                            <li>{{ $error }}</li>
                        @endforeach
                    </ul>
                </div>
            @endif
            <?php $inputAttributes = ['class'=>'form-control'];
                $labelAttributes = ['class'=>'col-md-4 control-label']; ?>
            {!! Form::open(['class'=>'form-horizontal','role'=>'form','method'=>'POST','url'=>'/auth/login']) !!}
                <div class="form-group">
                    {!! Form::label('email', 'E-Mail Address',$labelAttributes) !!}
                    <div class="col-md-6">
                    {!! Form::input('email','email',old('email'), $inputAttributes) !!}
                    </div>
                </div>
                <div class="form-group">
                    {!! Form::label('password', 'Password',$labelAttributes) !!}
                    <div class="col-md-6">
                        {!! Form::input('password','password',null,$inputAttributes) !!}
                    </div>
                </div>
                <div class="form-group">
                    <div class="col-md-6 col-md-offset-4">
                        <div class="checkbox">
                          <label>
                             {!! Form::checkbox('remember','') !!} Remember Me
                          </label>
                        </div>
                    </div>
                </div>
                <div class="form-group">
                    <div class="col-md-6 col-md-offset-4">
                        {!! Form::submit('Login',['class'=>'btn btn-primary', 'style'=>'margin-right: 15px;']) !!}
                        {!! link_to('/password/email', $title = 'Forgot Your Password?', $attributes = array(), $secure = null); !!}
                    </div>
                </div>
            {!! Form::close() !!}
          </div>
      </div>
    </div>
  </div>
</div>
@endsection

我们的例子

如果我们想创建一个表格来预订我们住处的房间,我们可以很容易地从我们的控制器调用一个路线:

/**
 * Show the form for creating a new resource.
 *
 * @return Response
 */
public function create()
{
    return view('auth/reserve');
}

现在我们需要创建一个位于resources/views/auth/reserve.blade.php的新视图。

在这个视图中,我们可以创建一个表单来预订住宿房间,用户可以选择开始日期和结束日期,开始日期包括月和年的开始日期,结束日期也包括月和年的开始日期:

Our example

表单将像以前一样开始,并在reserve-room处进行开机自检。然后,表单标签将被放置在选择输入字段旁边。最后,日、月和年选择表单元素将创建如下:

{!! Form::open(['class'=>'form-horizontal',
        'role'=>'form', 
        'method'=>'POST', 
        'url'=>'reserve-room']) !!}
        {!! Form::label(null, 'Start Date',$labelAttributes) !!}

        {!! Form::selectMonth('month',date('m')) !!}
        {!! Form::selectRange('date',1,31,date('d')) !!}
        {!! Form::selectRange('year',date('Y'),date('Y')+3) !!}

        {!! Form::label(null, 'End Date',$labelAttributes) !!}

        {!! Form::selectMonth('month',date('m')) !!}
        {!! Form::selectRange('date',1,31,date('d')) !!}
        {!! Form::selectRange('year',date('Y'),date('Y')+3,date('Y')) !!}

        {!! Form::submit('Reserve',
        ['class'=>'btn btn-primary', 
        'style'=>'margin-right: 15px;']) !!}
{!! Form::close() !!}

月份选择

首先,在selectMonth方法中,第一个参数是输入属性的名称,第二个属性是默认值。这里,PHP date 方法用于提取当前月份的数字部分——在本例中为三月:

{!! Form::selectMonth('month',date('m')) !!}

这里显示的输出格式如下:

<select name="month">
    <option value="1">January</option>
    <option value="2">February</option>
    <option value="3" selected="selected">March</option>
    <option value="4">April</option>
    <option value="5">May</option>
    <option value="6">June</option>
    <option value="7">July</option>
    <option value="8">August</option>
    <option value="9">September</option>
    <option value="10">October</option>
    <option value="11">November</option>
    <option value="12">December</option>
</select>

日期选择

类似的技术被应用于日期的选择,但是使用selectRange方法,一个月中的天数范围被传递给该方法。同样,PHP 日期函数用于将当前日期作为第四个参数发送给方法:

{!! Form::selectRange('date',1,31,date('d')) !!}

以下是格式化输出:

<select name="date">
    <option value="1">1</option>
    <option value="2">2</option>
    <option value="3">3</option>
    <option value="4">4</option>
    ...
    <option value="28">28</option>
    <option value="29">29</option>
    <option value="30" selected="selected">30</option>
    <option value="31">31</option>
</select>

应该选择的日期是 30 日,因为今天是 2015 年 3 月 30 日。

类型

对于没有 31 天的月份,通常会使用 JavaScript 方法根据月份和/或年份修改天数。

年份选择

用于日期范围的同样的技术用于年份的选择;再次使用selectRange方法。年的范围传递给方法。PHP date 函数用于将当前年份作为第四个参数发送给方法:

{!! Form::selectRange('year',date('Y'),date('Y')+3,date('Y')) !!}

以下是格式化输出:

<select name="year">
    <option value="2015" selected="selected">2015</option>
    <option value="2016">2016</option>
    <option value="2017">2017</option>
    <option value="2018">2018</option>
</select>

这里,当前选择的年份是 2015 年。

形成宏

我们具有相同的代码,该代码两次生成我们的月份、日期和年份选择表单块:一次用于开始日期,一次用于结束日期。为了重构代码,我们可以应用 DRY(不要重复自己)原则并创建一个表单宏。这将允许我们避免调用表单元素创建方法两次,如下所示:

<?php
Form::macro('monthDayYear',function($suffix='')
{
    echo Form::selectMonth(($suffix!=='')?'month-'.$suffix:'month',date('m'));
    echo Form::selectRange(($suffix!=='')?'date-'.$suffix:'date',1,31,date('d'));
    echo Form::selectRange(($suffix!=='')?'year-'.$suffix:'year',date('Y'),date('Y')+3,date('Y'));
}); 
?>

这里月、日、年生成代码放在一个宏里,在 PHP 标签里面,需要加上echo才能打印出结果。这个宏方法被命名为monthDayYear。调用我们的宏两次:每个标签后一次;每次通过$suffix变量添加不同的后缀。现在,我们的表单代码如下所示:

<?php
Form::macro('monthDayYear',function($suffix='')
{
    echo Form::selectMonth(($suffix!=='')?'month-'.$suffix:'month',date('m'));
    echo Form::selectRange(($suffix!=='')?'date-'.$suffix:'date',1,31,date('d'));
    echo Form::selectRange(($suffix!=='')?'year-'.$suffix:'year',date('Y'),date('Y')+3,date('Y'));
});
?>
{!! Form::open(['class'=>'form-horizontal',
                'role'=>'form',
                'method'=>'POST',
                'url'=>'/reserve-room']) !!}
    {!! Form::label(null, 'Start Date',$labelAttributes) !!}
    {!! Form::monthDayYear('-start') !!}
    {!! Form::label(null, 'End Date',$labelAttributes) !!}
    {!! Form::monthDayYear('-end') !!}
    {!! Form::submit('Reserve',['class'=>'btn btn-primary',
           'style'=>'margin-right: 15px;']) !!}
{!! Form::close() !!}

结论

选择在 Laravel 5 中包含 HTML 表单生成包可以减轻创建大量 HTML 表单的负担。这种方法允许开发人员使用方法,创建可重用的宏,并使用熟悉的 Laravel 方法来构建前端。一旦学习了基本方法,很容易简单地复制和粘贴之前创建的表单元素,然后更改它们的元素名称和/或发送给它们的数组。

根据项目的规模,这种方法可能是也可能不是正确的选择。对于一个非常小的应用,需要编写的代码量的差异不是很明显,尽管像selectMonthselectRange方法一样,所需的代码量非常大。

这种技术与宏的使用相结合,可以很容易地减少复制的发生。此外,前端设计的一个主要问题是,在整个应用中,各种元素的类内容可能需要改变。这将意味着执行大型的查找和替换操作,其中需要对 HTML 进行更改,例如更改类属性。通过为相似的元素创建包括类在内的属性数组,只需修改这些元素使用的数组,就可以对整个表单进行更改。

然而,在一个更大的项目中,部分表单可能会在整个应用中重复,明智地使用宏可以很容易地减少需要编写的代码量。不仅如此,宏还可以将内部代码与需要在多个文件中更改多个代码块的更改隔离开来。在要选择月份、日期和年份的示例中,这可能在大型应用中最多使用 20 次。对所需的 HTML 块所做的任何更改都可以简单地对宏进行,结果将反映在使用它的所有元素中。

最终,开发人员和设计人员将决定是否使用这个包。由于想要使用替代前端设计工具的设计者可能不喜欢也不能胜任处理包中的方法,所以他或她可能不想使用它。

总结

在本章中,概述了 HTML Laravel composer 包的历史和安装。解释了主模板的构造,然后通过示例展示了表单组件,例如各种表单输入类型。

最后,解释了在本书的示例软件中使用的房间预订表单的构造,以及“不要重复自己”表单宏创建技术。

在下一章中,我们将研究一种使用注解来减少为应用控制器创建路由所需时间的方法。