协调 Angular.js 和 Bootstrap 表单验证样式

Posted

技术标签:

【中文标题】协调 Angular.js 和 Bootstrap 表单验证样式【英文标题】:Reconcile Angular.js and Bootstrap form validation styling 【发布时间】:2012-12-30 04:19:26 【问题描述】:

我正在使用 Angular 和 Bootstrap。以下是参考代码:

<form name="newUserForm" ng-submit="add()" class="" novalidate>
    <input type="text" class="input" ng-model="newUser.uname" placeholder="Twitter" ng-pattern="/^@[A-Za-z0-9_]1,15$/" required></td>
    <button type="submit" ng-disabled="newUserForm.$invalid" class="btn btn-add btn-primary">Add</button>
</form>

Bootstrap 具有input:invalid .... 形式的无效字段样式;当字段为空时,这些会启动。现在我还通过 Angular 进行了一些模式匹配。这会在“:invalid”关闭但“.ng-invalid”打开时产生奇怪的情况,这需要我为“.ng-invalid”类重新实现引导 CSS 类。

我看到了两个选项,但都遇到了问题

让 Angular 使用一些自定义类名而不是“ng-valid”(我不知道该怎么做)。 禁用 html5 验证(我认为这是表单标签中的“novalidate”属性应该做的,但由于某种原因无法使其工作)。

那里的 Angular-Bootstrap 指令不包括样式。

【问题讨论】:

novalidate 应该“禁用浏览器的原生表单验证” -- form docs 【参考方案1】:

使用 Bootstrap 的“错误”类进行样式设置。您可以编写更少的代码。

<form name="myForm">
  <div class="control-group" ng-class="error: myForm.name.$invalid">
    <label>Name</label>
    <input type="text" name="name" ng-model="project.name" required>
    <span ng-show="myForm.name.$error.required" class="help-inline">
        Required</span>
  </div>
</form>

编辑: 正如其他答案和 cmets 指出的那样 - 在 Bootstrap 3 中,该类现在是“有错误”,而不是“错误”。

【讨论】:

或者如果你使用的是 bootstrap 3 ng-class="'has-error': myForm.name.$invalid" 您也可以添加&amp;&amp; myForm.name.$dirty 使验证样式仅在用户与表单控件交互后显示。 @bibstha bs3 的 help-inline 怎么样? 这行得通,但它的开销很大,并且使模板非常冗长。我正在寻找一种更整洁的方法。 我一向下滚动几个答案就找到了。【参考方案2】:

Bootstrap 3 中的类已更改:

<form class="form-horizontal" name="form" novalidate ng-submit="submit()" action="/login" method="post">
  <div class="row" ng-class="'has-error': form.email.$invalid, 'has-success': !form.email.$invalid">
    <label for="email" class="control-label">email:</label>
    <div class="col">
    <input type="email" id="email" placeholder="email" name="email" ng-model="email" required>
    <p class="help-block error" ng-show="form.email.$dirty && form.email.$error.required">please enter your email</p>
    <p class="help-block error" ng-show="form.email.$error.email">please enter a valid email</p>
  ...

注意'has-error''has-success' 周围的引号:花了一段时间才找到...

【讨论】:

有人找ng-class="(form.email.$invalid ? 'has-error' : 'has-success')"工作吗? 为了避免在页面加载后立即出现无效的输入,我认为您应该检查 $dirty 属性,指示该字段是否已被编辑:'has-error': form.email.$dirty &amp;&amp; form.email.$invalid, 'has-success': form.email.$dirty &amp;&amp; !form.email.$invalid 但是现在这个表达式变得太长以至于它变得容易打字错误和难以阅读,它总是相似的,所以应该有更好的方法,不是吗? 我使用了一个指令,该指令将“提交”标志添加到表单中,请参阅:***.com/questions/14965968/… @kristianlm 你在上面的 div 上尝试过吗?输入... @malix 可能有用,但我希望不必重复 form.email.$invalid。【参考方案3】:

另一种解决方案:创建根据子输入切换has-error 类的指令。

app.directive('bsHasError', [function() 
  return 
      restrict: "A",
      link: function(scope, element, attrs, ctrl) 
          var input = element.find('input[ng-model]'); 
          if (input.length) 
              scope.$watch(function() 
                  return input.hasClass('ng-invalid');
              , function(isInvalid) 
                  element.toggleClass('has-error', isInvalid);
              );
          
      
  ;
]);

然后在模板中简单地使用它

<div class="form-group" bs-has-error>
    <input class="form-control" ng-model="foo" ng-pattern="/.../"/>
</div>

【讨论】:

我认为在这种情况下指令是最好的解决方案。 值得注意的是 jqlite 不支持 element[attribute] 选择器语法,因此如果您不使用 jQuery,则需要稍作修改。 不提供表达式时手表永远不会被销毁 与其看类,不如直接看NgModelController的属性不是更好吗? input.controller('ngModel').$invalid 而不是 input.hasClass('ng-invalid')。我遇到了在 $watch 触发之前没有更新 css 类的问题。 @Thomas Wajs 是绝对正确的。上述解决方案(以及此处发布的其他几个解决方案)将始终是一个不同步的摘要周期,因为在更新无效类之前在摘要周期的中间执行评估。 input.controller('ngModel').$invalid 修复了这个问题。【参考方案4】:

对@farincz's answer 的小幅改进。我同意指令是这里最好的方法,但我不想在每个 .form-group 元素上重复它,所以我更新了代码以允许将其添加到 .form-group 或父 &lt;form&gt; 元素(这会将其添加到所有包含的 .form-group 元素中):

angular.module('directives', [])
  .directive('showValidation', [function() 
    return 
        restrict: "A",
        link: function(scope, element, attrs, ctrl) 

            if (element.get(0).nodeName.toLowerCase() === 'form') 
                element.find('.form-group').each(function(i, formGroup) 
                    showValidation(angular.element(formGroup));
                );
             else 
                showValidation(element);
            

            function showValidation(formGroupEl) 
                var input = formGroupEl.find('input[ng-model],textarea[ng-model]');
                if (input.length > 0) 
                    scope.$watch(function() 
                        return input.hasClass('ng-invalid');
                    , function(isInvalid) 
                        formGroupEl.toggleClass('has-error', isInvalid);
                    );
                
            
        
    ;
]);

【讨论】:

【参考方案5】:

@Andrew Smith 的回答略有改进。 我更改输入元素并使用require 关键字。

.directive('showValidation', [function() 
    return 
        restrict: "A",
        require:'form',
        link: function(scope, element, attrs, formCtrl) 
            element.find('.form-group').each(function() 
                var $formGroup=$(this);
                var $inputs = $formGroup.find('input[ng-model],textarea[ng-model],select[ng-model]');

                if ($inputs.length > 0) 
                    $inputs.each(function() 
                        var $input=$(this);
                        scope.$watch(function() 
                            return $input.hasClass('ng-invalid');
                        , function(isInvalid) 
                            $formGroup.toggleClass('has-error', isInvalid);
                        );
                    );
                
            );
        
    ;
]);

【讨论】:

需要改为$(element)。 jqlite 不支持按类搜索。 (我尝试提交编辑,但我需要更改 6 个字符......)【参考方案6】:

感谢@farincz 的精彩回答。以下是我为适应我的用例所做的一些修改。

这个版本提供了三个指令:

bs-has-success bs-has-error bs-has(方便您同时使用其他两个)

我所做的修改:

添加了一项检查以仅在表单字段脏时显示 has 状态,即在有人与它们交互之前它们不会显示。 为不使用 jQuery 的用户更改了传递给 element.find() 的字符串,因为 Angular 的 jQLite 中的 element.find() 仅支持通过标记名查找元素。 添加了对选择框和文本区域的支持。 将 element.find() 包装在 $timeout 中,以支持元素可能尚未将其子元素呈现到 DOM 的情况(例如,如果元素的子元素被标记为 ng-if)。 更改了 if 表达式以检查返回数组的长度(if(input) 来自 @farincz's answer 始终返回 true,因为来自 element.find() 的返回是一个 jQuery 数组)。

我希望有人觉得这很有用!

angular.module('bs-has', [])
  .factory('bsProcessValidator', function($timeout) 
    return function(scope, element, ngClass, bsClass) 
      $timeout(function() 
        var input = element.find('input');
        if(!input.length)  input = element.find('select'); 
        if(!input.length)  input = element.find('textarea'); 
        if (input.length) 
            scope.$watch(function() 
                return input.hasClass(ngClass) && input.hasClass('ng-dirty');
            , function(isValid) 
                element.toggleClass(bsClass, isValid);
            );
        
      );
    ;
  )
  .directive('bsHasSuccess', function(bsProcessValidator) 
    return 
      restrict: 'A',
      link: function(scope, element) 
        bsProcessValidator(scope, element, 'ng-valid', 'has-success');
      
    ;
  )
  .directive('bsHasError', function(bsProcessValidator) 
    return 
      restrict: 'A',
      link: function(scope, element) 
        bsProcessValidator(scope, element, 'ng-invalid', 'has-error');
      
    ;
  )
  .directive('bsHas', function(bsProcessValidator) 
    return 
      restrict: 'A',
      link: function(scope, element) 
        bsProcessValidator(scope, element, 'ng-valid', 'has-success');
        bsProcessValidator(scope, element, 'ng-invalid', 'has-error');
      
    ;
  );

用法:

<!-- Will show success and error states when form field is dirty -->
<div class="form-control" bs-has>
  <label for="text"></label>
  <input 
   type="text" 
   id="text" 
   name="text" 
   ng-model="data.text" 
   required>
</div>

<!-- Will show success state when select box is anything but the first (placeholder) option -->
<div class="form-control" bs-has-success>
  <label for="select"></label>
  <select 
   id="select" 
   name="select" 
   ng-model="data.select" 
   ng-options="option.name for option in data.selectOptions"
   required>
    <option value="">-- Make a Choice --</option>
  </select>
</div>

<!-- Will show error state when textarea is dirty and empty -->
<div class="form-control" bs-has-error>
  <label for="textarea"></label>
  <textarea 
   id="textarea" 
   name="textarea" 
   ng-model="data.textarea" 
   required></textarea>
</div>

您还可以安装 Guilherme 的 bower package 将所有这些捆绑在一起。

【讨论】:

我已经在 bower 上以“angular-bootstrap-validation”的形式发布了它,感谢您和@farincz,希望您不介意 这很酷。我注意到您添加了一项功能,您可以将指令放在表单级别并通过嵌套的.form-group 元素进行递归。这很好,但除非你包含 jQuery,否则它不会起作用,因为 find 的内置 angular jqlite 实现只支持按标记名查找,而不支持按选择器查找。您可能需要在 README 中为此添加注释。 您可以使用form.$submitted 仅在提交时显示错误。这里的 Angular 文档中有一个示例:docs.angularjs.org/guide/forms。查找标题“绑定到表单和控件状态”。【参考方案7】:

如果样式是问题,但您不想禁用本机验证,为什么不使用您自己的更具体样式覆盖样式?

input.ng-invalid, input.ng-invalid:invalid 
   background: red;
   /*override any styling giving you fits here*/

使用 CSS 选择器的特殊性解决您的问题!

【讨论】:

【参考方案8】:

我对 Jason Im 的回答的改进如下添加了两个新指令 show-validation-errors 和 show-validation-error。

'use strict';
(function() 

    function getParentFormName(element,$log) 
        var parentForm = element.parents('form:first');
        var parentFormName = parentForm.attr('name');

        if(!parentFormName)
            $log.error("Form name not specified!");
            return;
        

        return parentFormName;
    

    angular.module('directives').directive('showValidation', function () 
        return 
            restrict: 'A',
            require: 'form',
            link: function ($scope, element) 
                element.find('.form-group').each(function () 
                    var formGroup = $(this);
                    var inputs = formGroup.find('input[ng-model],textarea[ng-model],select[ng-model]');

                    if (inputs.length > 0) 
                        inputs.each(function () 
                            var input = $(this);
                            $scope.$watch(function () 
                                return input.hasClass('ng-invalid') && !input.hasClass('ng-pristine');
                            , function (isInvalid) 
                                formGroup.toggleClass('has-error', isInvalid);
                            );
                            $scope.$watch(function () 
                                return input.hasClass('ng-valid') && !input.hasClass('ng-pristine');
                            , function (isInvalid) 
                                formGroup.toggleClass('has-success', isInvalid);
                            );
                        );
                    
                );
            
        ;
    );

    angular.module('directives').directive('showValidationErrors', function ($log) 
        return 
            restrict: 'A',
            link: function ($scope, element, attrs) 
                var parentFormName = getParentFormName(element,$log);
                var inputName = attrs['showValidationErrors'];
                element.addClass('ng-hide');

                if(!inputName)
                    $log.error("input name not specified!")
                    return;
                

                $scope.$watch(function () 
                    return !($scope[parentFormName][inputName].$dirty && $scope[parentFormName][inputName].$invalid);
                ,function(noErrors)
                    element.toggleClass('ng-hide',noErrors);
                );

            
        ;
    );

    angular.module('friport').directive('showValidationError', function ($log) 
        return 
            restrict: 'A',
            link: function ($scope, element, attrs) 
                var parentFormName = getParentFormName(element,$log);
                var parentContainer = element.parents('*[show-validation-errors]:first');
                var inputName = parentContainer.attr('show-validation-errors');
                var type = attrs['showValidationError'];

                element.addClass('ng-hide');

                if(!inputName)
                    $log.error("Could not find parent show-validation-errors!");
                    return;
                

                if(!type)
                    $log.error("Could not find validation error type!");
                    return;
                

                $scope.$watch(function () 
                    return !$scope[parentFormName][inputName].$error[type];
                ,function(noErrors)
                    element.toggleClass('ng-hide',noErrors);
                );

            
        ;
    );

)();

可以将 show-validation-errors 添加到错误容器中,以便根据表单字段的有效性显示/隐藏容器。

show-validation-error 根据给定类型的表单字段有效性显示或隐藏元素。

预期用途示例:

        <form role="form" name="organizationForm" novalidate show-validation>
            <div class="form-group">
                <label for="organizationNumber">Organization number</label>
                <input type="text" class="form-control" id="organizationNumber" name="organizationNumber" required ng-pattern="/^[0-9]3[ ]?[0-9]3[ ]?[0-9]3$/" ng-model="organizationNumber">
                <div class="help-block with-errors" show-validation-errors="organizationNumber">
                    <div show-validation-error="required">
                        Organization number is required.
                    </div>
                    <div show-validation-error="pattern">
                        Organization number needs to have the following format "000 000 000" or "000000000".
                    </div>
                </div>
            </div>
       </form>

【讨论】:

验证错误也可以使用ngMessage【参考方案9】:

我认为现在回复为时已晚,但希望你会喜欢它:

CSS您可以添加其他类型的控件,例如选择、日期、密码等

input[type="text"].ng-invalid
    border-left: 5px solid #ff0000;
    background-color: #FFEBD6;

input[type="text"].ng-valid
    background-color: #FFFFFF;
    border-left: 5px solid #088b0b;

input[type="text"]:disabled.ng-valid
    background-color: #efefef;
    border: 1px solid #bbb;

HTML:不需要在控件中添加任何东西,除非是 ng-required(如果是)

<input type="text"
       class="form-control"
       ng-model="customer.ZipCode"
       ng-required="true">

只需尝试并在您的控件中键入一些文本,我发现它非常方便且很棒。

【讨论】:

【参考方案10】:

没有小提琴很难确定,但查看 angular.js 代码它不会替换类 - 它只是添加和删除自己的。所以任何引导类(由引导 UI 脚本动态添加)都应该不受 angular 影响。

也就是说,在使用 Angular 的同时使用 Bootstrap 的 JS 功能进行验证是没有意义的——只使用 Angular。我建议您使用引导样式和 Angular JS,即使用自定义验证指令将引导 css 类添加到您的元素中。

【讨论】:

你是对的,禁用本机验证是要走的路。然而我还没有做到这一点。我会继续寻找。谢谢!【参考方案11】:
<div class="form-group has-feedback" ng-class=" 'has-error': form.uemail.$invalid && form.uemail.$dirty ">
  <label class="control-label col-sm-2" for="email">Email</label>
  <div class="col-sm-10">
    <input type="email" class="form-control" ng-model="user.email" name="uemail" placeholder="Enter email" required>
    <div ng-show="form.$submitted || form.uphone.$touched" ng-class=" 'has-success': form.uemail.$valid && form.uemail.$dirty ">
    <span ng-show="form.uemail.$valid" class="glyphicon glyphicon-ok-sign form-control-feedback" aria-hidden="true"></span>
    <span ng-show="form.uemail.$invalid && form.uemail.$dirty" class="glyphicon glyphicon-remove-circle form-control-feedback" aria-hidden="true"></span>
    </div>
  </div>
</div>

【讨论】:

【参考方案12】:

当我没有听说过 AngularJS 本身的名字时,我知道这是一个非常古老的问题回答线程 :-)

但是对于那些登陆此页面并以干净和自动化的方式寻找 Angular + Bootstrap 表单验证的其他人,我编写了一个非常小的模块来实现相同的目标,而无需以任何形式更改 HTML 或 javascript

结帐Bootstrap Angular Validation。

以下是三个简单的步骤:

    通过 Bower 安装 bower install bootstrap-angular-validation --save 添加脚本文件&lt;script src="bower_components/bootstrap-angular-validation/dist/bootstrap-angular-validation.min.js"&gt;&lt;/script&gt; 将依赖项 bootstrap.angular.validation 添加到您的应用程序中,就是这样!

这适用于 Bootstrap 3,jQuery 不需要

这是基于 jQuery 验证的概念。该模块为验证错误提供了一些额外的验证和常见的通用消息。

【讨论】:

以上是关于协调 Angular.js 和 Bootstrap 表单验证样式的主要内容,如果未能解决你的问题,请参考以下文章

Laravel 4 和 Angular JS 和 Twitter Bootstrap 3 分页

angular js入门总结,bootstrap引入不成功等问题

Angular Js ui.bootstrap 选项卡自定义

Angular JS bootstrap ui日历问题

Angular JS UI Bootstrap 选项卡 (ui.bootstrap.tabs) 导致页面在选择时滚动/跳转

单击屏幕上的任意位置关闭所有 Angular JS Bootstrap 弹出窗口?