Validate forms the Angular way. Also: Validate on submit.

This commit is contained in:
Your Name 2014-06-06 18:24:40 +02:00
parent caf732a04c
commit 2a0b2add0d
9 changed files with 51 additions and 43 deletions

View file

@ -59,7 +59,6 @@
<script src="scripts/directives/tokenForm.js"></script> <script src="scripts/directives/tokenForm.js"></script>
<script src="scripts/validation/constraints.js"></script> <script src="scripts/validation/constraints.js"></script>
<script src="scripts/validation/validator.js"></script>
<!-- endbuild --> <!-- endbuild -->
</body> </body>
</html> </html>

View file

@ -2,7 +2,7 @@
angular.module('ffffng') angular.module('ffffng')
.directive('fNodeForm', function () { .directive('fNodeForm', function () {
var ctrl = function ($scope, $timeout, Constraints, Validator, _, config, $window) { var ctrl = function ($scope, $timeout, Constraints, $element, _, config, $window) {
$scope.config = config; $scope.config = config;
angular.extend($scope, { angular.extend($scope, {
center: { center: {
@ -76,19 +76,13 @@ angular.module('ffffng')
$scope.markers = {}; $scope.markers = {};
}; };
var isValid = _.reduce(Constraints.node, function (isValids, constraint, field) { $scope.constraints = Constraints.node;
isValids[field] = Validator.forConstraint(constraint, true);
return isValids; var submitted = false;
}, {});
var areValid = Validator.forConstraints(Constraints.node);
$scope.hasError = function (field) { $scope.hasError = function (field) {
var value = $scope.node[field]; var input = $scope.nodeForm[field];
return !isValid[field](value); return input.$invalid && submitted;
};
$scope.hasAnyError = function () {
return !areValid($scope.node);
}; };
var duplicateError = { var duplicateError = {
@ -98,6 +92,18 @@ angular.module('ffffng')
}; };
$scope.onSubmit = function (node) { $scope.onSubmit = function (node) {
submitted = true;
if ($scope.nodeForm.$invalid) {
var firstInvalid = _.filter($element.find('form').find('input'), function (input) {
return $scope.nodeForm[input.name].$invalid;
})[0];
if (firstInvalid) {
$window.scrollTo(0, $window.pageYOffset + firstInvalid.getBoundingClientRect().top - 100);
}
return;
}
$scope.error = null; $scope.error = null;
$scope.save(node).error(function (response, code) { $scope.save(node).error(function (response, code) {
switch (code) { switch (code) {
@ -109,7 +115,7 @@ angular.module('ffffng')
} }
$window.scrollTo(0, 0); $window.scrollTo(0, 0);
}); });
}; }.bind(this);
$scope.updateMap($scope.node.coords); $scope.updateMap($scope.node.coords);
withValidCoords($scope.node.coords, function (lat, lng) { withValidCoords($scope.node.coords, function (lat, lng) {

View file

@ -2,17 +2,17 @@
angular.module('ffffng') angular.module('ffffng')
.directive('fTokenForm', function () { .directive('fTokenForm', function () {
var ctrl = function ($scope, Constraints, Validator) { var ctrl = function ($scope, Constraints) {
var isValid = Validator.forConstraint(Constraints.token); $scope.constraints = Constraints;
$scope.hasError = function () { $scope.submitted = false;
var value = $scope.token;
if (value === undefined) {
return false;
}
return !isValid(value);
};
$scope.doSubmit = function (token) { $scope.doSubmit = function (token) {
$scope.submitted = true;
if ($scope.tokenForm.$invalid) {
return;
}
$scope.error = null; $scope.error = null;
$scope.onSubmit(token) $scope.onSubmit(token)
.error(function (response, code) { .error(function (response, code) {

View file

@ -1 +0,0 @@
../../../shared/validation/validator.js

View file

@ -40,3 +40,7 @@ a[target="_blank"]:after {
font-size: 0.8em; font-size: 0.8em;
margin-left: 0.1em; margin-left: 0.1em;
} }
.has-error .help-block {
color: $text-color;
}

View file

@ -1,31 +1,31 @@
<form method="post" role="form" ng-submit="onSubmit(node)" ng-disabled="hasAnyError()" novalidate> <form name="nodeForm" method="post" role="form" ng-submit="onSubmit(node)" novalidate>
<div class="main-error" ng-if="error">{{error}}</div> <div class="main-error" ng-if="error">{{error}}</div>
<fieldset> <fieldset>
<div class="node-data"> <div class="node-data">
<h3>Knotendaten</h3> <h3>Knotendaten</h3>
<div class="hostname"> <div class="hostname" ng-class="{'has-error' : hasError('hostname')}">
<label for="hostname">Knotenname</label> <label for="hostname">Knotenname</label>
<f-help text="Das ist der Name, der auch auf der Karte auftaucht."></f-help> <f-help text="Das ist der Name, der auch auf der Karte auftaucht."></f-help>
<input type="text" id="hostname" placeholder="z. B. Lisas-Freifunk" ng-model="node.hostname" /> <input type="text" id="hostname" name="hostname" placeholder="z. B. Lisas-Freifunk" ng-model="node.hostname" ng-pattern="constraints.hostname.regex" ng-required="!constraints.hostname.optional" />
<span class="feedback" ng-if="hasError('hostname')"> <span class="feedback" ng-if="hasError('hostname')">
Knotennamen dürfen maximal 32 Zeichen lang sein und nur Klein- und Großbuchstaben, sowie Ziffern, - und _ enthalten. Knotennamen dürfen maximal 32 Zeichen lang sein und nur Klein- und Großbuchstaben, sowie Ziffern, - und _ enthalten.
</span> </span>
</div> </div>
<div class="key"> <div class="key" ng-class="{'has-error' : hasError('key')}">
<label for="key">VPN-Schlüssel (bitte nur weglassen, wenn Du weisst, was Du tust)</label> <label for="key">VPN-Schlüssel (bitte nur weglassen, wenn Du weisst, was Du tust)</label>
<f-help text="Dieser Schlüssel wird verwendet, um die Verbindung Deines Routers zu den Gateway-Servern abzusichern."></f-help> <f-help text="Dieser Schlüssel wird verwendet, um die Verbindung Deines Routers zu den Gateway-Servern abzusichern."></f-help>
<input type="text" id="key" placeholder="Dein 64-stelliger VPN-Schlüssel" ng-model="node.key" /> <input type="text" id="key" name="key" placeholder="Dein 64-stelliger VPN-Schlüssel" ng-model="node.key" ng-pattern="constraints.key.regex" ng-required="!constraints.key.optional" />
<span class="feedback" ng-if="hasError('key')"> <span class="feedback" ng-if="hasError('key')">
Der angegebene VPN-Schlüssel ist ungültig. Der angegebene VPN-Schlüssel ist ungültig.
</span> </span>
</div> </div>
<div class="mac"> <div class="mac" ng-class="{'has-error' : hasError('mac')}">
<label for="mac">MAC-Adresse</label> <label for="mac">MAC-Adresse</label>
<f-help text=" <f-help text="
Die MAC-Adresse (kurz &quot;MAC&quot;) steht üblicherweise auf dem Aufkleber auf der Unterseite deines Routers. Die MAC-Adresse (kurz &quot;MAC&quot;) steht üblicherweise auf dem Aufkleber auf der Unterseite deines Routers.
Sie wird verwendet, um die Daten Deines Routers auf der Karte korrekt zuzuordnen. Sie wird verwendet, um die Daten Deines Routers auf der Karte korrekt zuzuordnen.
"></f-help> "></f-help>
<input type="text" id="mac" placeholder="z. B. 12:34:56:78:9a:bc oder 123456789abc" ng-model="node.mac" /> <input type="text" id="mac" name="mac" placeholder="z. B. 12:34:56:78:9a:bc oder 123456789abc" ng-model="node.mac" ng-pattern="constraints.mac.regex" ng-required="!constraints.mac.optional" />
<span class="feedback" ng-if="hasError('mac')"> <span class="feedback" ng-if="hasError('mac')">
Die angegebene MAC-Adresse ist ungültig. Die angegebene MAC-Adresse ist ungültig.
</span> </span>
@ -34,7 +34,7 @@
<div class="node-position"> <div class="node-position">
<h3>Wo soll Dein Router stehen?</h3> <h3>Wo soll Dein Router stehen?</h3>
<div class="row"> <div class="row">
<div class="coords col-md-4"> <div class="coords col-md-4" ng-class="{'has-error' : hasError('coords')}">
<p class="help-block"> <p class="help-block">
Wenn Du möchtest, dass Dein Knoten an der richtigen Stelle auf der Wenn Du möchtest, dass Dein Knoten an der richtigen Stelle auf der
<a href="{{ config.map.mapUrl }}" target="_blank">Knotenkarte</a> angezeigt wird, <a href="{{ config.map.mapUrl }}" target="_blank">Knotenkarte</a> angezeigt wird,
@ -42,7 +42,7 @@
an die Stelle, wo Dein Knoten erscheinen soll. Durch erneutes Klicken kannst Du die Position jederzeit an die Stelle, wo Dein Knoten erscheinen soll. Durch erneutes Klicken kannst Du die Position jederzeit
anpassen. anpassen.
</p> </p>
<input type="text" id="coords" class="{{node.coords ? 'has-coords' : ''}}" placeholder="z. B. {{config.coordsSelector.lat}} {{config.coordsSelector.lng}}" ng-model="node.coords" ng-blur="updateMap" /> <input type="text" id="coords" name="coords" class="{{node.coords ? 'has-coords' : ''}}" placeholder="z. B. {{config.coordsSelector.lat}} {{config.coordsSelector.lng}}" ng-model="node.coords" ng-pattern="constraints.coords.regex" ng-required="!constraints.coords.optional" ng-blur="updateMap" />
<i class="reset-coords" ng-if="node.coords" ng-click="resetCoords()"></i> <i class="reset-coords" ng-if="node.coords" ng-click="resetCoords()"></i>
<span class="feedback" ng-if="hasError('coords')"> <span class="feedback" ng-if="hasError('coords')">
Bitte gib die Koordinaten wie folgt an, Beispiel: {{config.coordsSelector.lat}} {{config.coordsSelector.lng}} Bitte gib die Koordinaten wie folgt an, Beispiel: {{config.coordsSelector.lat}} {{config.coordsSelector.lng}}
@ -63,18 +63,18 @@
</p> </p>
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<div class="nickname"> <div class="nickname" ng-class="{'has-error' : hasError('nickname')}">
<label for="nickname">Nickname / Name</label> <label for="nickname">Nickname / Name</label>
<input type="text" id="nickname" placeholder="z. B. Lisa" ng-model="node.nickname" /> <input type="text" id="nickname" name="nickname" placeholder="z. B. Lisa" ng-model="node.nickname" ng-pattern="constraints.nickname.regex" ng-required="!constraints.nickname.optional" />
<span class="feedback" ng-if="hasError('nickname')"> <span class="feedback" ng-if="hasError('nickname')">
Nicknames dürfen maximal 64 Zeichen lang sein und nur Klein- und Großbuchstaben, sowie Ziffern, - und _ enthalten. Umlaute sind erlaubt. Nicknames dürfen maximal 64 Zeichen lang sein und nur Klein- und Großbuchstaben, sowie Ziffern, - und _ enthalten. Umlaute sind erlaubt.
</span> </span>
</div> </div>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<div class="email"> <div class="email" ng-class="{'has-error' : hasError('email')}">
<label for="email">E-Mail-Adresse</label> <label for="email">E-Mail-Adresse</label>
<input type="email" id="email" placeholder="z. B. lisa@{{config.community.domain}}" ng-model="node.email" /> <input type="email" id="email" name="email" placeholder="z. B. lisa@{{config.community.domain}}" ng-model="node.email" ng-pattern="constraints.email.regex" ng-required="!constraints.email.optional" />
<span class="feedback" ng-if="hasError('email')"> <span class="feedback" ng-if="hasError('email')">
Die angegebene E-Mail-Adresse ist ungültig. Die angegebene E-Mail-Adresse ist ungültig.
</span> </span>
@ -83,7 +83,7 @@
</div> </div>
</div> </div>
<div class="buttons"> <div class="buttons">
<button class="save {{action}}" type="submit" ng-disabled="hasAnyError()" popover="I appeared on mouse enter!" popover-trigger="mouseenter" ng-switch="action"> <button class="save {{action}}" type="submit" ng-switch="action">
<span ng-switch-when="create"><i class="fa fa-dot-circle-o"></i> Knoten anmelden</span> <span ng-switch-when="create"><i class="fa fa-dot-circle-o"></i> Knoten anmelden</span>
<span ng-switch-when="update"><i class="fa fa-pencil"></i> Daten ändern</span> <span ng-switch-when="update"><i class="fa fa-pencil"></i> Daten ändern</span>
</button> </button>

View file

@ -1,15 +1,15 @@
<form method="post" role="form" ng-submit="doSubmit(token)" ng-disabled="hasError()" novalidate> <form name="tokenForm" method="post" role="form" ng-submit="doSubmit(token)" ng-disabled="hasError()" novalidate>
<div class="main-error" ng-if="error">{{error}}</div> <div class="main-error" ng-if="error">{{error}}</div>
<fieldset> <fieldset>
<div class="token"> <div class="token">
<label for="token">Token</label> <label for="token">Token</label>
<input type="text" id="token" placeholder="Dein 16-stelliger Token" ng-model="token" /> <input type="text" name="token" id="token" placeholder="Dein 16-stelliger Token" ng-model="token" ng-pattern="constraints.token.regex" ng-required="!constraints.token.optional" />
<span class="feedback" ng-if="hasError()"> <span class="feedback" ng-if="tokenForm.token.$invalid && submitted">
Das Token ist ein 16-stelliger Wert bestehend aus 0-9 und a-f. Das Token ist ein 16-stelliger Wert bestehend aus 0-9 und a-f.
</span> </span>
</div> </div>
<div class="buttons"> <div class="buttons">
<button class="submit" type="submit" ng-disabled="!token || hasError()"> <button class="submit" type="submit">
<i class="fa fa-pencil"></i> Knotendaten ändern <i class="fa fa-pencil"></i> Knotendaten ändern
</button> </button>
<button class="cancel" type="reset" ng-click="onCancel()"> <button class="cancel" type="reset" ng-click="onCancel()">

View file

@ -20,7 +20,7 @@ require('./resources/nodeResource');
require('./services/nodeService'); require('./services/nodeService');
require('../shared/validation/constraints'); require('../shared/validation/constraints');
require('../shared/validation/validator'); require('./validation/validator');
angular.injector(['ffffng']).invoke(function (config, app, Router) { angular.injector(['ffffng']).invoke(function (config, app, Router) {
Router.init(); Router.init();