如何在非角度SPA应用程序中使用angularjs表单验证

时间:2014-04-24 09:33:49

标签: angularjs validation angularjs-scope

我想知道是否有人可以提供帮助。从表面上看,我的问题标题可能听起来有些愚蠢!我正在尝试在非angularjs应用程序中使用angularjs表单验证!让我试着解释......

我们有一个'传统'网络应用。它不是SPA。后端是java,java app服务器管理会话和其中的数据。每个页面都是完整的http请求/响应。 (有少量的ajax请求/响应,但这些是为页面添加一些bling,而不是它的核心功能)。在这方面,应用程序的体系结构是非常传统的/旧的skool,因为服务器端java代码负责从其会话存储中保存的模型数据的版本生成标记和填充表单字段值。 (我认为这是问题的关键)

该应用程序主要是基于HTML表单的应用程序,为了增强用户体验,我们编写了一些基于jQuery的javascript字段验证器。出于多种原因,这些已经开始变得有点失控,我们正在探索其他选择。

一个简单的谷歌搜索找到无数的jQuery插件进行表单验证。我们正在研究这些,但随机的jQuery插件不是我们喜欢的方法(我们倾向于避开'somebloke.com'插件,因为我们无法保证它们的编写性,浏览器兼容性,未来的维护,如何好吧,他们使用其他插件等 - 我们之前已经用手指烧了这种东西)

所以我们正在研究其他方法,目前正在探索使用angularjs 能够“轻柔地”将angularjs引入我们的架构中具有一些优势。它来自一个稳定的最佳组织(即它不是'somebloke.com'),因此得到了很好的支持和维护。它鼓励我们以非常模块化的方式编写我们的js。可测试的方式(我们目前的jQuery插件,回调等大鼠巢是远远不够的!)。 angularjs表单验证基于html5标准,并且是声明性和语义性的。它为我们提供了一种方法,可以将我们基于jQuery的其他代码迁移到更好的代码(angularjs指令)。总的来说,如果我们可以将angularjs分层到我们当前的应用程序中,它为我们在将来某个时候将应用程序转换为现代SPA提供了良好的基础。

此时作为angularjs(或任何其他mv *框架)SPA重写整个应用程序不是一种选择,因此如上所述,我们正在考虑一次引入少量功能;今天的挑战是形式验证。

所以,这就是背景。

我已经删除了当前的js客户端验证,我们的服务器端java代码正在生成这样的标记:

<form method="POST" action="/renew">
    <input name="firstname" type="text" value="alf" />
    <input name="surname" type="text" value="garnet" />
    <input name="age" type="number" value="88" />
    <input type="submit" />
</form>

(其中输入字段的值已从服务器持有的模型填充到服务器端)

我已将angularjs库添加到页面中,并且已按照以下方式进行表单验证:

<form method="POST" action="/renew" novalidate name="renewForm" 
      ng-controller="yourDetails" ng-submit="submitForm(renewForm, $event)">
    <input name="firstname" type="text" value="alf" required ng-model="firstname"/>
    <input name="surname" type="text" value="garnet" required ng-model="surname"/>
    <input name="age" type="number" value="88" required ng-model="age"/>
    <input type="submit" />
</form>

app.controller('yourDetails', function($scope) {
    $scope.submitForm = function(form, $event) {
        if (!form.$valid) {
            $event.preventDefault();
            return false;
        }
    };
});

这是一个合理的起点。从广义上讲,它的工作原理是angularjs正在处理表单验证和提交。执行submitForm方法,如果表单无效,则输入if块并取消表单提交。从这里我可以看到使用ng-show

添加字段错误消息等很容易

但问题是在每个html字段上使用ng-model。据我了解,我需要使用它,以便angularjs将字段绑定到表单,因此可以跟踪每个字段的有效状态。
但是,ng-model似乎也设置了双向数据绑定,并将字段的值设置为模型数据的版本...这是空的。例如:

我们的服务器端模板可能包含以下内容:
<input th:field="*{firstname}" type="text" required ng-model="firstname"/>

可能会生成此标记:
<input name="firstname" value="alf" type="text" required ng-model="firstname"/>

提供给客户的标记包括value="alf"
但随后angularjs介入并为该字段设置双向绑定。因为我们在angularjs范围内没有firstname属性,所以它使用空值初始化一个属性,并在字段的DOM中设置该空白值。
这导致页面由浏览器呈现,并在字段中显示空白值,即使服务器端我们在模型中有值,并且服务器已正确生成标记等。

所以,我认为我理解核心问题及其发生的原因。我的问题是,我可以在每个字段上没有ng-model属性的情况下进行angularjs表单验证,或者是否只有单向绑定的ng-model指令版本 - 特别是DOM - &gt;模型

非常感谢任何帮助;

感谢

2 个答案:

答案 0 :(得分:1)

在服务器端生成表单时,可以使用ng-init初始化模型:

<input ng-init="firstname='alf'" th:field="*{firstname}" type="text" required ng-model="firstname" />

答案 1 :(得分:0)

好的,@ Alexandre的答案几乎正确,这是他的答案指出了我的最终解决方案(所以他应该得到这个真正的功劳:) )

ng-init 的工作正如@Alexandre所建议的那样。我无法让它发挥作用的原因是我试图在数字领域使用它。

以下情况有效,因为使用ngInit和html输入类型在模型上设置的值都是text / string:

<input ng-init="firstname='alf'" th:field="*{firstname}" type="text" required ng-model="firstname" />

我在年龄领域尝试如下:

<input ng-init="age='88'" th:field="*{age}" type="number" required ng-model="age" />

这不起作用,因为age属性在模型上设置为字符串,但html输入类型是一个数字。以下工作正常:

<input ng-init="age=88" th:field="*{age}" type="number" required ng-model="age" />

这让我意识到在角度模型上设置的值需要匹配html输入类型的数据类型(当然对于chrome,不确定其他浏览器)(即模型中的字符串属性 - 甚至如果它们可以解析为数字 - 不能在带有ngModel的html数字字段中使用)

考虑到这一点,我认为有两种选择。我可以在生成标记的代码中执行服务器端:

    <input ng-init="age=(some-potentially-complex-logic-to-workout-whether-its-a-string-or-number)" th:field="*{age}" type="number" required ng-model="age" />

或者我可以使用自定义指令在客户端执行此操作。最后我使用了自定义指令,因为a)这意味着我可以编写一个指令(学习的所有部分:))和b)我意识到可能还有其他需要特殊考虑的情况可能会使服务器端更复杂(即选择字段没有值,它们有一个选定的索引,你需要得到它的值;单选按钮都有一个值,但你只想设置已检查无线电的值

这是我想出的:

angularApp.directive('lvInitializeValueOnScope', function() {
    return {
        restrict: 'A',
        link: function(scope, element, attrs, controller) {
            var propertyName = attrs.name,
                propertyValue = attrs.value,
                elementName = element.get(0).tagName.toLowerCase(),
                fieldType = ( elementName === 'input' ? attrs.type.toLowerCase() : elementName ),
                // set expression to assume propertyValue is a string value
                expression = propertyName + '=\'' + ( !!propertyValue ? propertyValue : '' ) + '\'';

            // if the input field type is number and propertyValue is parse-able as a number
            if (fieldType === 'number' && !isNaN(parseFloat(propertyValue))) {
                // set expression without quotes surrounding propertyValue
                expression = propertyName + '=' + propertyValue;
            }
            // if the field is a html select element
            if (fieldType === 'select') {
                // propertyValue will be blank because select elements don't have a value attribute
                // instead, we need to use the value of the child option element that is selected
                propertyValue = $(element.html()).filter(":selected").val();
                // set expression to assume propertyValue is a string value
                expression = propertyName + '=\'' + ( !!propertyValue ? propertyValue : '' ) + '\'';
            }
            // if the input field type is a radio button but its not checked (selected)
            if (fieldType === 'radio' && !element.is(':checked')) {
                // we need to reset the expression so a blank value is used
                // doing this means that only the checked/selected radio button values get set on the model
                expression = propertyName + '=\'\'';
            }

            // evaluate the expression, just as angular's ngInit does
            scope.$eval(expression);
        }
    };
});

(它看起来比实际情况更复杂,因为我已经将评论留在了 - 剥去那些并且那里真的没什么了)

要使用它,我使用ng-model的每个字段,我还需要使用属性data-lv-initialize-value-on-scope。您不需要在属性上传递任何值,该指令从元素和attr参数中获取所需的一切

到目前为止,它迎合了文本,数字,广播和选择字段。我正在处理的页面没有任何复选框,但是当我遇到那些我敢说的时候我需要为那些添加一些代码。

它有效,而我认为我更喜欢这样使用ng-init和服务器端负载来确定是否设置一个值,它的数据类型应该是什么等等

目前我唯一能看到的缺点是处理单选按钮设置可能效率低下。 IE浏览器。如果你有10个单选按钮,每个按钮都标有ng-model和data-lv-initialize-value-on-scope; all 10将运行data-lv-initialize-value-on-scope指令并在作用域上设置一个值。至少有9个迭代将在范围上设置一个空白值,最多只有1个将在范围上设置实际值。

嘿嘿,它似乎现在起作用:)。希望这可以帮助将来的某个人