D?yY5r
zRqg-r#9=els>1#p{SY&W(F*LOK~|^lClI2UXnUBe>C~Z!FeOBpg_)9M-3|C1!+^QN
zD$gyv`&n2KS_hY!U^VkUMd&kAtP1D{^}VTxFfN2?N&1p3{mJNheq0bUldLjq`5qM`
z!i3O63$fP`2HXBSfv}lw6?K>TJ{{JY9BKiOU2tWV=5P$##AOHafKmY;|=qx}#{};ry5cwYu2tWV=
z5P$##AOHafKmY;|fB*y@K%k|Ne^#$`dRdT?$($f8*EVk#wzFE}rk-6mcT&w}GMSU*
zXV!1*t}O1Z?p}G}rZC!jSn^EUyRdqIdPFB??a_SSR`nbBdu(-4!+|tWNgKa*_w)N`f=Hpw{vLNhV
zh<^Sri2oBJ|KkAx2tWV=5P$##AOHafKmY;|fWRXq&=;1>y9N07{~xJwMQ0!Y0SG_<
z0uX=z1Rwwb2tWV=!2{P*@Fx)krd;
zMPH2mU+kOlvEJ?8H~TL1y_LAv|J1;913#DiTJndfUr7CR&)dSH5DC3W{yX^b^k_`F
zJ}ZQZm4fkVeby^LsNYz2RA@+yvsHkqoj_AD#fJy
z#KD2O5lZ(SI}(>BCWJSpn)+tRa6)&U9J#QXUtY`0Ys=4E&dX^hJuRO}7Yk{*SZNwN
zMqOUHx+brzU%o8AxO(x*^6G|sDZe2vudiLbxI${Ul3!UnE2nFEqwz|$PKw>p>svSV
z`WZEQUUABj93{Qc)JoNzVkIpX^rq1)mJNP?v7t3~Yet=dr{&FRwZ!kGsTK0$5x4$a
z#;smnU%7Z~Jx^NMF4jpi`{%R+>6pVP>&24W(&B>K5-CpuZR*?G?Lm?~`c|`eheYX*
zpY~Y0MJAR(h}ycZTpEc<+ET~yWphv?K;>eiQLI+%>DZP&(%KN>(%hV|r?ZhS+|nyM
zRjpjysq0Nzuq~m-I}_UWKh%uSYc-cJyWlnS^85FaqNQRm)QdeaX=X;)U_G;r*oN`)
zu2I=Cd@sYEUio&Op%I)VC6!hs%(U+Hn{^uXJLHHj?3T-S%hiHWA`{P>5b@NTZre|^
z5;A!_=s{!_>Lg?4U{PhYphNVmnS&l$+eM=kG}RJH>ue-0sj9FS4(@Qatw-7D0bS0%
zZ+#}=u0_pouk+zHisf3#;0K6Hi3NtsWq2>cUEtn_EdLJgAJ#inLb(u&NjKE}j(BSb
zxR5oqJuSZXv3Oj{=Y`jYc$;#z%wWt(teHqw>A-!xfnI@G-<_OOWrxY@9(loYTwcvT
zmtW1VT*zOS+YdfE6X}7cm_uQn7f2G{GIz|0{8Z{
zguXa^%W5qJjgfCy5*FJ|(8ZN!
z^DhN>J0;Wd)s=w#bU55METuKyAD3?Ah1LciNFbcHaNfwNOAES>$TO=40>kTEpC{75
ztSo5zAfl70wK!mgtsNZp_QK|THZyN@3Oo4#VcYfHU)b)*v7<-Xi>2aHE+^c}vDs&x
z+PA9Y+OE>nns;kPK$7U4g#kINqwK-15xu82HN(GglIzb}O)Ke}26y4*&%!2>dr{%r
z?HucFP2@;7%P2}-j7b~0{f_T|E^2B(;^5xngY0Ow-f`JQvwKbEdztb&`u4sDhMsee
zoaTo$@cO{>-Wkw6D@4nX(!-+rY&-MTDz$cRfsV|F9hfj!q+4BWq
zEA%g+kN5t?!K?GnOvk0CXNA@{dsnxP2fa`(R_H=iGtMt&RJ}0o-*MU+5c8bq-|G}(
zCq(~V+IMzKft^-jC(Qt6g9DgX7q`_-b<1DLsm=k|#r6%rf5a;HPEW<9*;(NYfj>()
z68`q5y))9k`=RC{T{u@O8eO^sERe~xsBYdewwi3RHH}xB?3RR^TsLauu7Q2;f&BXw
zo+Z0g`F2U)Nz0A0UMi8B8}e?YQQRR5a{fL8xph(5F*Ne8R5bJ*5{cQAbq9pKv$bNE$x41KXbAm9_yM%`QCs8HTCr|u^ezIqG&y%F
zE-8wzcbXqME4R1JGl9d#RcyC^+qmmm&U2LfEg*tn=O>Zq?Nof2@AoYa-0ZTaK7U(`
z3=zBQ<~@)5a&%a%lJf7z&8~;InYlN7l)XXS`-FQLz}=Z;Cm8PaQSVIm?|!H^DEnQz
z{hF6wkUC9SZO}_8=j3)DMOj|AtaF02Tj#4&ly`k^d2jg`Sr{%0uP1%3tUdQk+;IA3
z-Vo>Z9N*8{U&W7D-3`%spZ46%ZW}eXKxcn9pLu<9eJm#JEO)#fWE6f6{^JDyzVTMi
z@woI9xocpq*cm_9uzia6rJu0I!n`u^?bPf-?6l(BNxR&3Sem0d1!!UBD-=re!`4wxl?54bBno6b&l)0Gr?b2G8i_(tOE__3emq~vOS`pVs0$lUh2bMQl6iZ&Gsi;
z9WJZG?QcKbxqW+}_idjWpHq`@>H4zJ%KIW=dk+X^XKzOwa%u^
zK(8GXs67*0G=Ie>?>A7ps{07krD78c7wYzP|3h7jd!mf7cB;It&2_KmY;|fB*y_009U<00Lbofc1YD
b4m!FF0SG_<0uX=z1Rwwb2tWV=T_^DWv?yk;
From 72787035dba5090efca14a782f802455a25dfaca Mon Sep 17 00:00:00 2001
From: jamesrobb
Date: Wed, 26 Nov 2014 19:50:04 +0000
Subject: [PATCH 03/25] added overriden get_widget_attrs to NgMessagesMixin to
add rejected-validator
---
.gitignore | 1 +
djangular/forms/angular_messages.py | 8 ++++++--
examples/server/forms/ng_messages.py | 2 +-
3 files changed, 8 insertions(+), 3 deletions(-)
diff --git a/.gitignore b/.gitignore
index 1be7f55..671c689 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,4 @@ docs/_build/
node_modules/
bower_components/
build/
+example/test.sqlite
diff --git a/djangular/forms/angular_messages.py b/djangular/forms/angular_messages.py
index fef4c1d..71e4101 100644
--- a/djangular/forms/angular_messages.py
+++ b/djangular/forms/angular_messages.py
@@ -33,8 +33,12 @@ def as_ul(self):
class NgMessagesMixin(NgFormBaseMixin):
+
def __init__(self, data=None, *args, **kwargs):
kwargs['error_class'] = NgMessagesTupleErrorList
super(NgMessagesMixin, self).__init__(data, *args, **kwargs)
- for field in self.fields.itervalues():
- field.widget.attrs['validate-rejected'] = ""
\ No newline at end of file
+
+ def get_widget_attrs(self, bound_field):
+ attrs = super(NgMessagesMixin, self).get_widget_attrs(bound_field)
+ attrs['validate-rejected'] = ""
+ return attrs
\ No newline at end of file
diff --git a/examples/server/forms/ng_messages.py b/examples/server/forms/ng_messages.py
index 3a7df40..040074e 100644
--- a/examples/server/forms/ng_messages.py
+++ b/examples/server/forms/ng_messages.py
@@ -7,7 +7,7 @@
from . import subscribe_form
-class SubscribeForm(NgModelFormMixin, NgFormValidationMixin, NgMessagesMixin, subscribe_form.SubscribeForm):
+class SubscribeForm(NgMessagesMixin, NgModelFormMixin, NgFormValidationMixin, subscribe_form.SubscribeForm):
scope_prefix = 'subscribe_data'
form_name = 'my_form'
From 7e1bc4dab38b63c8fae8656c0916194364eb5008 Mon Sep 17 00:00:00 2001
From: jamesrobb
Date: Wed, 26 Nov 2014 21:03:14 +0000
Subject: [PATCH 04/25] removed need for hardcoded rejected from
msg_format_bind
---
.gitignore | 2 +-
djangular/forms/angular_messages.py | 15 +++++++++++----
2 files changed, 12 insertions(+), 5 deletions(-)
diff --git a/.gitignore b/.gitignore
index 671c689..e0b7922 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,4 +12,4 @@ docs/_build/
node_modules/
bower_components/
build/
-example/test.sqlite
+examples/test.sqlite
\ No newline at end of file
diff --git a/djangular/forms/angular_messages.py b/djangular/forms/angular_messages.py
index 71e4101..b62f171 100644
--- a/djangular/forms/angular_messages.py
+++ b/djangular/forms/angular_messages.py
@@ -2,8 +2,7 @@
from django.utils.encoding import force_text
from django.utils.safestring import mark_safe, SafeText
-from djangular.forms.angular_base import TupleErrorList
-from djangular.forms.angular_base import NgFormBaseMixin
+from djangular.forms.angular_base import TupleErrorList, SafeTuple, NgFormBaseMixin
class NgMessagesTupleErrorList(TupleErrorList):
@@ -11,7 +10,7 @@ class NgMessagesTupleErrorList(TupleErrorList):
msgs_format = '{2}
'
msg_format = '{3}
'
""" span's necessary due to this bug https://github.com/angular/angular.js/issues/8089"""
- msg_format_bind = '
'
+ msg_format_bind = '
'
def as_ul(self):
if not self:
@@ -20,7 +19,7 @@ def as_ul(self):
if isinstance(first, tuple):
error_list = []
for e in self:
- if e[3] == '$valid':
+ if e[3] == '$valid' or e[2] == '$pristine':
continue
msg_format = e[5] == '$message' and self.msg_format_bind or self.msg_format
msg_type = e[3].split('.')
@@ -38,6 +37,14 @@ def __init__(self, data=None, *args, **kwargs):
kwargs['error_class'] = NgMessagesTupleErrorList
super(NgMessagesMixin, self).__init__(data, *args, **kwargs)
+ def get_field_errors(self, field):
+ errors = super(NgMessagesMixin, self).get_field_errors(field)
+ if field.is_hidden:
+ return errors
+ identifier = format_html('{0}.{1}', self.form_name, field.name)
+ errors.append(SafeTuple((identifier, self.field_error_css_classes, '$dirty', 'rejected', 'invalid', '$message')))
+ return errors
+
def get_widget_attrs(self, bound_field):
attrs = super(NgMessagesMixin, self).get_widget_attrs(bound_field)
attrs['validate-rejected'] = ""
From 048ffe10ec9c8f6095a99d312cf9293f7a1e2025 Mon Sep 17 00:00:00 2001
From: jamesrobb
Date: Thu, 27 Nov 2014 10:16:34 +0000
Subject: [PATCH 05/25] bindable field error messages are now assigned to a
related property on the fields $message object - this allows for multiple
binded validators
---
client/src/js/ng-django-messages.js | 8 +++++++-
djangular/forms/angular_messages.py | 2 +-
2 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/client/src/js/ng-django-messages.js b/client/src/js/ng-django-messages.js
index 51120f8..168b00e 100644
--- a/client/src/js/ng-django-messages.js
+++ b/client/src/js/ng-django-messages.js
@@ -32,6 +32,7 @@ function validateRejected() {
}else{
_hasMessage = ngModel.$message !== undefined;
+
if(_hasMessage) {
_value = value;
}
@@ -74,7 +75,12 @@ function djangoMessagesForm() {
field = form[key];
field.$dirty = true;
- field.$message = message;
+
+ if(!angular.isObject(field.$message)) {
+ field.$message = {};
+ }
+
+ field.$message.rejected = message;
if (angular.isFunction(field.$validate)) {
field.$validate();
diff --git a/djangular/forms/angular_messages.py b/djangular/forms/angular_messages.py
index b62f171..d51d4b7 100644
--- a/djangular/forms/angular_messages.py
+++ b/djangular/forms/angular_messages.py
@@ -10,7 +10,7 @@ class NgMessagesTupleErrorList(TupleErrorList):
msgs_format = '{2}
'
msg_format = '{3}
'
""" span's necessary due to this bug https://github.com/angular/angular.js/issues/8089"""
- msg_format_bind = '
'
+ msg_format_bind = '
'
def as_ul(self):
if not self:
From 5369961d605d4e8b44fe302fa5193b1191fe6ae5 Mon Sep 17 00:00:00 2001
From: jamesrobb
Date: Thu, 27 Nov 2014 10:22:26 +0000
Subject: [PATCH 06/25] changed exclusion of $pristine error to exclusion of
$message error, to prevent NgModelFoermMixin rejected error from being
included
---
djangular/forms/angular_messages.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/djangular/forms/angular_messages.py b/djangular/forms/angular_messages.py
index d51d4b7..4186734 100644
--- a/djangular/forms/angular_messages.py
+++ b/djangular/forms/angular_messages.py
@@ -19,7 +19,7 @@ def as_ul(self):
if isinstance(first, tuple):
error_list = []
for e in self:
- if e[3] == '$valid' or e[2] == '$pristine':
+ if e[3] in ('$valid', '$message'):
continue
msg_format = e[5] == '$message' and self.msg_format_bind or self.msg_format
msg_type = e[3].split('.')
From b0f013c66e4862ea69b22b1c7eba010a43400c47 Mon Sep 17 00:00:00 2001
From: jamesrobb
Date: Thu, 27 Nov 2014 14:54:44 +0000
Subject: [PATCH 07/25] refactoring
---
djangular/forms/angular_messages.py | 17 ++++++++++++++---
1 file changed, 14 insertions(+), 3 deletions(-)
diff --git a/djangular/forms/angular_messages.py b/djangular/forms/angular_messages.py
index 4186734..7714d5c 100644
--- a/djangular/forms/angular_messages.py
+++ b/djangular/forms/angular_messages.py
@@ -1,10 +1,14 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
from django.utils.html import format_html
-from django.utils.encoding import force_text
+from django.utils.encoding import python_2_unicode_compatible, force_text
from django.utils.safestring import mark_safe, SafeText
from djangular.forms.angular_base import TupleErrorList, SafeTuple, NgFormBaseMixin
+@python_2_unicode_compatible
class NgMessagesTupleErrorList(TupleErrorList):
msgs_format = '{2}
'
@@ -12,14 +16,17 @@ class NgMessagesTupleErrorList(TupleErrorList):
""" span's necessary due to this bug https://github.com/angular/angular.js/issues/8089"""
msg_format_bind = '
'
- def as_ul(self):
+ def __str__(self):
+ return self.as_divs()
+
+ def as_divs(self):
if not self:
return SafeText()
first = self[0]
if isinstance(first, tuple):
error_list = []
for e in self:
- if e[3] in ('$valid', '$message'):
+ if e[3] == '$valid':
continue
msg_format = e[5] == '$message' and self.msg_format_bind or self.msg_format
msg_type = e[3].split('.')
@@ -41,6 +48,10 @@ def get_field_errors(self, field):
errors = super(NgMessagesMixin, self).get_field_errors(field)
if field.is_hidden:
return errors
+ #remove error added by NgModelFormMixin
+ for item in errors[:]:
+ if item[2] == '$pristine' and item[5] == '$message':
+ errors.remove(item)
identifier = format_html('{0}.{1}', self.form_name, field.name)
errors.append(SafeTuple((identifier, self.field_error_css_classes, '$dirty', 'rejected', 'invalid', '$message')))
return errors
From 35a3a8db956de2239e211d30b684545709e8d471 Mon Sep 17 00:00:00 2001
From: jamesrobb
Date: Sat, 29 Nov 2014 14:25:35 +0000
Subject: [PATCH 08/25] fixed super c and p error
---
examples/server/views/ng_messages.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/examples/server/views/ng_messages.py b/examples/server/views/ng_messages.py
index af39c58..eec3245 100644
--- a/examples/server/views/ng_messages.py
+++ b/examples/server/views/ng_messages.py
@@ -16,7 +16,7 @@ class SubscribeView(FormView):
def post(self, request, **kwargs):
if request.is_ajax():
return self.ajax(request)
- return super(NgMessagesView, self).post(request, **kwargs)
+ return super(SubscribeView, self).post(request, **kwargs)
def ajax(self, request):
form = self.form_class(data=json.loads(request.body))
From d278bf1cd2631e864a59efb856753a0e7a8231fe Mon Sep 17 00:00:00 2001
From: jamesrobb
Date: Mon, 1 Dec 2014 22:06:08 +0000
Subject: [PATCH 09/25] added seperate form and field error classes for
ngMessages mixin
---
client/src/js/ng-django-messages.js | 67 ++++++++++++++++++++++++++++-
djangular/forms/angular_base.py | 1 +
djangular/forms/angular_messages.py | 35 ++++++++++-----
3 files changed, 92 insertions(+), 11 deletions(-)
diff --git a/client/src/js/ng-django-messages.js b/client/src/js/ng-django-messages.js
index 168b00e..437a2f8 100644
--- a/client/src/js/ng-django-messages.js
+++ b/client/src/js/ng-django-messages.js
@@ -6,12 +6,77 @@ angular
'ngMessages'
])
+ .directive('form', formExtension)
.directive('validateRejected', validateRejected)
.factory('djangoMessagesForm', djangoMessagesForm);
+/**
+ * An extension to form
+ *
+ * Adds the following methods and functionality:
+ *
+ * - setValidFieldsPristine()
+ */
+function formExtension() {
+
+ return {
+ restrict: 'E',
+ require: [
+ '^?form'
+ ],
+ link: {
+ pre: function($scope, $element, $attrs, ctrls) {
+
+ var ctrl = ctrls[0],
+ controls,
+ modelName;
+
+ var _superAdd = ctrl.$addControl;
+
+ ctrl.$addControl = function(control) {
+
+ _superAdd(control)
+
+ controls = controls || [];
+
+ if(controls.indexOf(control) === -1) {
+ controls.push(control);
+ }
+ }
+
+ var _superRemove = ctrl.$removeControl;
+
+ ctrl.$removeControl = function(control) {
+
+ _superRemove(control)
+
+ if(controls && controls.indexOf(control) !== -1) {
+ controls.splice(controls.indexOf(control), 1);
+ }
+ }
+
+ ctrl.setValidFieldsPristine = function() {
+
+ var i = 0,
+ len = controls.length,
+ control;
+
+ for(; i < len; i++) {
+ control = controls[i];
+ if(control.$valid) {
+ control.$setPristine();
+ }
+ }
+ }
+ }
+ }
+ }
+};
+
+
function validateRejected() {
return {
@@ -97,7 +162,7 @@ function djangoMessagesForm() {
}else{
form.$message = message;
- form.$setPristine();
+ form.setValidFieldsPristine();
}
}
);
diff --git a/djangular/forms/angular_base.py b/djangular/forms/angular_base.py
index 54dee88..4afffe1 100644
--- a/djangular/forms/angular_base.py
+++ b/djangular/forms/angular_base.py
@@ -71,6 +71,7 @@ def as_ul(self):
li_format = e[5] == '$message' and self.li_format_bind or self.li_format
err_tuple = (e[0], e[3], e[4], force_text(e[5]))
error_lists[e[2]].append(format_html(li_format, *err_tuple))
+ print err_tuple
# renders and combine both of these lists
return mark_safe(''.join([format_html(self.ul_format, first[0], first[1], prop,
mark_safe(''.join(list_items))) for prop, list_items in error_lists.items()]))
diff --git a/djangular/forms/angular_messages.py b/djangular/forms/angular_messages.py
index 7714d5c..5eb1f44 100644
--- a/djangular/forms/angular_messages.py
+++ b/djangular/forms/angular_messages.py
@@ -8,8 +8,13 @@
from djangular.forms.angular_base import TupleErrorList, SafeTuple, NgFormBaseMixin
+
+class NgMessagesFormErrorList(TupleErrorList):
+ ul_format = ''
+
+
@python_2_unicode_compatible
-class NgMessagesTupleErrorList(TupleErrorList):
+class NgMessagesFieldErrorList(TupleErrorList):
msgs_format = '{2}
'
msg_format = '{3}
'
@@ -33,30 +38,40 @@ def as_divs(self):
err_tuple = (e[0], msg_type[0] if len(msg_type) == 1 else msg_type.pop(), e[4], force_text(e[5]))
error_list.append(format_html(msg_format, *err_tuple))
- return (error_list and \
- format_html(self.msgs_format, first[0], first[1], mark_safe(''.join(error_list)))
- or '')
+ return mark_safe(format_html(self.msgs_format, first[0], first[1], mark_safe(''.join(error_list))))
+
+ return format_html('{0}
',
+ format_html_join('', '{0}
', ((force_text(e),) for e in self)))
class NgMessagesMixin(NgFormBaseMixin):
def __init__(self, data=None, *args, **kwargs):
- kwargs['error_class'] = NgMessagesTupleErrorList
+ self.form_error_class = kwargs.pop('form_error_class', NgMessagesFormErrorList)
+ error_class = kwargs.pop('error_class', NgMessagesFieldErrorList)
+ kwargs.setdefault('error_class', error_class)
super(NgMessagesMixin, self).__init__(data, *args, **kwargs)
def get_field_errors(self, field):
errors = super(NgMessagesMixin, self).get_field_errors(field)
if field.is_hidden:
return errors
- #remove error added by NgModelFormMixin
- for item in errors[:]:
- if item[2] == '$pristine' and item[5] == '$message':
- errors.remove(item)
+ self._remove_ng_model_form_mixin_rejected_error(errors)
identifier = format_html('{0}.{1}', self.form_name, field.name)
errors.append(SafeTuple((identifier, self.field_error_css_classes, '$dirty', 'rejected', 'invalid', '$message')))
return errors
+ def non_field_errors(self):
+ errors = super(NgMessagesMixin, self).non_field_errors()
+ return self.form_error_class(errors)
+
def get_widget_attrs(self, bound_field):
attrs = super(NgMessagesMixin, self).get_widget_attrs(bound_field)
attrs['validate-rejected'] = ""
- return attrs
\ No newline at end of file
+ return attrs
+
+ def _remove_ng_model_form_mixin_rejected_error(self, errors):
+ for item in errors:
+ if item[2] == '$pristine' and item[5] == '$message':
+ errors.remove(item)
+ return
From 5f3af874df87f8d826ed277f696b149485818b4f Mon Sep 17 00:00:00 2001
From: jamesrobb
Date: Tue, 2 Dec 2014 16:03:37 +0000
Subject: [PATCH 10/25] added valid message display and changed field error
format to ul/li
---
client/src/js/ng-django-messages.js | 17 +++++----
djangular/forms/angular_base.py | 1 -
djangular/forms/angular_messages.py | 38 +++++++++++--------
djangular/static/djangular/css/styles.css | 9 ++---
examples/server/static/css/djangular-demo.css | 9 ++---
examples/server/templates/ng-messages.html | 2 +-
6 files changed, 40 insertions(+), 36 deletions(-)
diff --git a/client/src/js/ng-django-messages.js b/client/src/js/ng-django-messages.js
index 437a2f8..919c037 100644
--- a/client/src/js/ng-django-messages.js
+++ b/client/src/js/ng-django-messages.js
@@ -84,14 +84,14 @@ function validateRejected() {
link: function($scope, $element, $attrs, ngModel) {
var _hasMessage = false,
- _value;
+ _value = null;
ngModel.$validators.rejected = function(value) {
-
- if(_hasMessage && (_value && _value !== value)) {
+
+ if(_hasMessage && (_value !== value)) {
_hasMessage = false;
- _value = undefined;
+ _value = null;
ngModel.$message = undefined;
}else{
@@ -146,14 +146,17 @@ function djangoMessagesForm() {
}
field.$message.rejected = message;
-
+
if (angular.isFunction(field.$validate)) {
+
field.$validate();
+
} else {
// this field is a composite of input elements
angular.forEach(field, function(subField, subKey) {
- if (angular.isDefined(subField) &&
- angular.isFunction(subField.$validate)) {
+ if(angular.isDefined(subField) &&
+ angular.isFunction(subField.$validate)) {
+
subField.$validate();
}
});
diff --git a/djangular/forms/angular_base.py b/djangular/forms/angular_base.py
index 4afffe1..54dee88 100644
--- a/djangular/forms/angular_base.py
+++ b/djangular/forms/angular_base.py
@@ -71,7 +71,6 @@ def as_ul(self):
li_format = e[5] == '$message' and self.li_format_bind or self.li_format
err_tuple = (e[0], e[3], e[4], force_text(e[5]))
error_lists[e[2]].append(format_html(li_format, *err_tuple))
- print err_tuple
# renders and combine both of these lists
return mark_safe(''.join([format_html(self.ul_format, first[0], first[1], prop,
mark_safe(''.join(list_items))) for prop, list_items in error_lists.items()]))
diff --git a/djangular/forms/angular_messages.py b/djangular/forms/angular_messages.py
index 5eb1f44..c150973 100644
--- a/djangular/forms/angular_messages.py
+++ b/djangular/forms/angular_messages.py
@@ -13,35 +13,43 @@ class NgMessagesFormErrorList(TupleErrorList):
ul_format = ''
-@python_2_unicode_compatible
class NgMessagesFieldErrorList(TupleErrorList):
- msgs_format = '{2}
'
- msg_format = '{3}
'
- """ span's necessary due to this bug https://github.com/angular/angular.js/issues/8089"""
- msg_format_bind = '
'
+ ul_format_valid = ''
+ li_format_valid = '{3} '
- def __str__(self):
- return self.as_divs()
+ ul_format = ''
+ li_format = '{3} '
+ """ span's necessary due to this bug https://github.com/angular/angular.js/issues/8089"""
+ li_format_bind = ' '
- def as_divs(self):
+ def as_ul(self):
if not self:
return SafeText()
first = self[0]
if isinstance(first, tuple):
- error_list = []
+ valid_list = []
+ invalid_list = []
for e in self:
if e[3] == '$valid':
- continue
- msg_format = e[5] == '$message' and self.msg_format_bind or self.msg_format
+ li_format = self.li_format_valid
+ error_list = valid_list
+ elif e[5] == '$message':
+ li_format = self.li_format_bind
+ error_list = invalid_list
+ else:
+ li_format = self.li_format
+ error_list = invalid_list
+
msg_type = e[3].split('.')
err_tuple = (e[0], msg_type[0] if len(msg_type) == 1 else msg_type.pop(), e[4], force_text(e[5]))
- error_list.append(format_html(msg_format, *err_tuple))
+ error_list.append(format_html(li_format, *err_tuple))
- return mark_safe(format_html(self.msgs_format, first[0], first[1], mark_safe(''.join(error_list))))
+ return mark_safe(format_html(self.ul_format_valid, first[0], first[1], mark_safe(''.join(valid_list)))) \
+ + mark_safe(format_html(self.ul_format, first[0], first[1], mark_safe(''.join(invalid_list))))
- return format_html('{0}
',
- format_html_join('', '{0}
', ((force_text(e),) for e in self)))
+ return format_html('',
+ format_html_join('', '{0} ', ((force_text(e),) for e in self)))
class NgMessagesMixin(NgFormBaseMixin):
diff --git a/djangular/static/djangular/css/styles.css b/djangular/static/djangular/css/styles.css
index 2baea64..5ece16e 100644
--- a/djangular/static/djangular/css/styles.css
+++ b/djangular/static/djangular/css/styles.css
@@ -9,19 +9,16 @@ form .ng-invalid-bound.ng-pristine:focus, form .ng-invalid.ng-dirty:focus {
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483;
}
-ul.djng-form-errors, ul.djng-field-errors,
-div.djng-form-errors, div.djng-field-errors {
+ul.djng-form-errors, ul.djng-field-errors {
display: inline-block;
list-style-type: none;
margin: 5px 0 0 0;
padding: 0;
}
-ul.djng-form-errors li, ul.djng-field-errors li,
-div.djng-form-errors div, div.djng-field-errors div {
+ul.djng-form-errors li, ul.djng-field-errors li {
color: #e9322d;
}
-ul.djng-field-errors li.invalid:before,
-div.djng-field-errors div.invalid:before {
+ul.djng-field-errors li.invalid:before {
content: "\2716";
margin-right: 10px;
}
diff --git a/examples/server/static/css/djangular-demo.css b/examples/server/static/css/djangular-demo.css
index 23e3f29..b05d850 100644
--- a/examples/server/static/css/djangular-demo.css
+++ b/examples/server/static/css/djangular-demo.css
@@ -2,8 +2,7 @@ body{
position:relative;
}
table.subscribe ul.errorlist,
-table.subscribe ul.djng-field-errors,
-table.subscribe div.djng-field-errors {
+table.subscribe ul.djng-field-errors {
display: inline-block;
list-style-type: none;
position: absolute;
@@ -13,13 +12,11 @@ table.subscribe div.djng-field-errors {
table.subscribe ul.errorlist li {
color: #e9322d;
}
-table.subscribe ul.djng-field-errors li.invalid, {
-table.subscribe div.djng-field-errors div.invalid {
+table.subscribe ul.djng-field-errors li.invalid {
color: #e9322d;
}
table.subscribe ul.errorlist li:before,
-table.subscribe ul.djng-field-errors li.invalid:before,
-table.subscribe div.djng-field-errors div.invalid:before {
+table.subscribe ul.djng-field-errors li.invalid:before {
content: "\2716\20";
}
table.subscribe ul.djng-field-errors li.valid:before {
diff --git a/examples/server/templates/ng-messages.html b/examples/server/templates/ng-messages.html
index f5eb6ec..dbf7e9b 100644
--- a/examples/server/templates/ng-messages.html
+++ b/examples/server/templates/ng-messages.html
@@ -3,7 +3,7 @@
{% block form_title %}NgMessages Demo{% endblock %}
-{% block form_header %}Combine client-side Form validation with two-way data-binding and angular-messages. Requires angular 1.3.{% endblock %}
+{% block form_header %}Combine client-side Form validation with two-way data-binding and angular-messages. Requires angular 1.3+{% endblock %}
{% block form_tag %}name="{{ form.form_name }}" method="post" action="." novalidate ng-controller="MyFormCtrl"{% endblock %}
From 546ac9df4c1a99e3ab6f8b978d83b0bc34349dd4 Mon Sep 17 00:00:00 2001
From: jamesrobb
Date: Wed, 3 Dec 2014 11:13:21 +0000
Subject: [PATCH 11/25] tweaking field states to display errors correctly
---
client/src/js/ng-django-messages.js | 16 ++++++++++++----
djangular/forms/angular_messages.py | 6 +++---
2 files changed, 15 insertions(+), 7 deletions(-)
diff --git a/client/src/js/ng-django-messages.js b/client/src/js/ng-django-messages.js
index 919c037..818491b 100644
--- a/client/src/js/ng-django-messages.js
+++ b/client/src/js/ng-django-messages.js
@@ -129,18 +129,24 @@ function djangoMessagesForm() {
};
function _displayErrors(form, errors) {
+ console.log(errors)
+ console.log(form)
angular.forEach(errors,
function(error, key) {
var field,
message = error[0];
- form.$setDirty();
+ form.$setSubmitted();
+ //form.$setDirty();
if(form.hasOwnProperty(key)) {
field = form[key];
- field.$dirty = true;
-
+ //
+ // field.$pristine = false;
+ // field.$dirty = true;
+ if(field.$name === 'password' || field.$name === 'height')
+ console.log(field)
if(!angular.isObject(field.$message)) {
field.$message = {};
}
@@ -149,6 +155,7 @@ function djangoMessagesForm() {
if (angular.isFunction(field.$validate)) {
+ field.$setTouched();
field.$validate();
} else {
@@ -156,7 +163,8 @@ function djangoMessagesForm() {
angular.forEach(field, function(subField, subKey) {
if(angular.isDefined(subField) &&
angular.isFunction(subField.$validate)) {
-
+
+ subField.$setTouched();
subField.$validate();
}
});
diff --git a/djangular/forms/angular_messages.py b/djangular/forms/angular_messages.py
index c150973..15b21f8 100644
--- a/djangular/forms/angular_messages.py
+++ b/djangular/forms/angular_messages.py
@@ -10,15 +10,15 @@
class NgMessagesFormErrorList(TupleErrorList):
- ul_format = ''
+ ul_format = ''
class NgMessagesFieldErrorList(TupleErrorList):
- ul_format_valid = ''
+ ul_format_valid = ''
li_format_valid = '{3} '
- ul_format = ''
+ ul_format = ''
li_format = '{3} '
""" span's necessary due to this bug https://github.com/angular/angular.js/issues/8089"""
li_format_bind = ' '
From 7e8d0e54a6aafec7a31266290d364c1e2637674b Mon Sep 17 00:00:00 2001
From: jamesrobb
Date: Wed, 3 Dec 2014 14:07:28 +0000
Subject: [PATCH 12/25] styling and error/valid display trigger properties
updated
---
client/src/js/ng-django-messages.js | 25 ++++++++---------------
djangular/forms/angular_messages.py | 12 ++++++++---
djangular/static/djangular/css/styles.css | 9 ++++++--
3 files changed, 25 insertions(+), 21 deletions(-)
diff --git a/client/src/js/ng-django-messages.js b/client/src/js/ng-django-messages.js
index 818491b..d02ad33 100644
--- a/client/src/js/ng-django-messages.js
+++ b/client/src/js/ng-django-messages.js
@@ -129,42 +129,35 @@ function djangoMessagesForm() {
};
function _displayErrors(form, errors) {
- console.log(errors)
- console.log(form)
+
+ form.$setSubmitted();
+
angular.forEach(errors,
function(error, key) {
var field,
message = error[0];
- form.$setSubmitted();
- //form.$setDirty();
-
if(form.hasOwnProperty(key)) {
field = form[key];
- //
- // field.$pristine = false;
- // field.$dirty = true;
- if(field.$name === 'password' || field.$name === 'height')
- console.log(field)
+
if(!angular.isObject(field.$message)) {
field.$message = {};
}
-
+
field.$message.rejected = message;
if (angular.isFunction(field.$validate)) {
-
- field.$setTouched();
+
field.$validate();
} else {
// this field is a composite of input elements
+ field.$setSubmitted();
+
angular.forEach(field, function(subField, subKey) {
if(angular.isDefined(subField) &&
- angular.isFunction(subField.$validate)) {
-
- subField.$setTouched();
+ angular.isFunction(subField.$validate)) {
subField.$validate();
}
});
diff --git a/djangular/forms/angular_messages.py b/djangular/forms/angular_messages.py
index 15b21f8..6d4335b 100644
--- a/djangular/forms/angular_messages.py
+++ b/djangular/forms/angular_messages.py
@@ -15,10 +15,10 @@ class NgMessagesFormErrorList(TupleErrorList):
class NgMessagesFieldErrorList(TupleErrorList):
- ul_format_valid = ''
+ ul_format_valid = ''
li_format_valid = '{3} '
- ul_format = ''
+ ul_format = ''
li_format = '{3} '
""" span's necessary due to this bug https://github.com/angular/angular.js/issues/8089"""
li_format_bind = ' '
@@ -46,11 +46,17 @@ def as_ul(self):
error_list.append(format_html(li_format, *err_tuple))
return mark_safe(format_html(self.ul_format_valid, first[0], first[1], mark_safe(''.join(valid_list)))) \
- + mark_safe(format_html(self.ul_format, first[0], first[1], mark_safe(''.join(invalid_list))))
+ + mark_safe(format_html(self.ul_format, first[0], first[1], self._get_form_name(first[0]), mark_safe(''.join(invalid_list))))
return format_html('',
format_html_join('', '{0} ', ((force_text(e),) for e in self)))
+ def _get_form_name(self, value):
+ parts = value.split('.')
+ parts.pop()
+ return '.'.join(parts)
+
+
class NgMessagesMixin(NgFormBaseMixin):
diff --git a/djangular/static/djangular/css/styles.css b/djangular/static/djangular/css/styles.css
index 5ece16e..3730322 100644
--- a/djangular/static/djangular/css/styles.css
+++ b/djangular/static/djangular/css/styles.css
@@ -2,10 +2,15 @@
display: none !important;
}
-form .ng-invalid-bound.ng-pristine, form .ng-invalid.ng-dirty {
+form.ng-submitted .ng-invalid,
+form .ng-invalid-bound.ng-pristine,
+form .ng-invalid.ng-dirty {
border-color: #e9322d;
}
-form .ng-invalid-bound.ng-pristine:focus, form .ng-invalid.ng-dirty:focus {
+
+form.ng-submitted .ng-invalid:focus,
+form .ng-invalid-bound.ng-pristine:focus,
+form .ng-invalid.ng-dirty:focus {
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483;
}
From f5829c26647942d70495367f83dead1fe104ac5d Mon Sep 17 00:00:00 2001
From: jamesrobb
Date: Thu, 4 Dec 2014 14:23:56 +0000
Subject: [PATCH 13/25] updated rejected validation directive to check
$message.rejected value
---
client/src/js/ng-django-messages.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/client/src/js/ng-django-messages.js b/client/src/js/ng-django-messages.js
index d02ad33..a237ad4 100644
--- a/client/src/js/ng-django-messages.js
+++ b/client/src/js/ng-django-messages.js
@@ -92,11 +92,11 @@ function validateRejected() {
_hasMessage = false;
_value = null;
- ngModel.$message = undefined;
+ ngModel.$message.rejected = undefined;
}else{
- _hasMessage = ngModel.$message !== undefined;
+ _hasMessage = ngModel.$message.rejected !== undefined;
if(_hasMessage) {
_value = value;
From 6fed82e8cf456b3fb357b22dd9bb2eeb1270535e Mon Sep 17 00:00:00 2001
From: jamesrobb
Date: Thu, 4 Dec 2014 14:30:21 +0000
Subject: [PATCH 14/25] updated rejected validation directive to check
$message.rejected value
---
client/src/js/ng-django-messages.js | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/client/src/js/ng-django-messages.js b/client/src/js/ng-django-messages.js
index a237ad4..62dcaf0 100644
--- a/client/src/js/ng-django-messages.js
+++ b/client/src/js/ng-django-messages.js
@@ -92,11 +92,14 @@ function validateRejected() {
_hasMessage = false;
_value = null;
- ngModel.$message.rejected = undefined;
+
+ if(ngModel.$message) {
+ ngModel.$message.rejected = undefined;
+ }
}else{
- _hasMessage = ngModel.$message.rejected !== undefined;
+ _hasMessage = ngModel.$message && ngModel.$message.rejected !== undefined;
if(_hasMessage) {
_value = value;
From 778be3e8172aa3dc39beb34467b8d4eb0f1896b8 Mon Sep 17 00:00:00 2001
From: jamesrobb
Date: Mon, 8 Dec 2014 11:22:29 +0000
Subject: [PATCH 15/25] added client tests
---
client/package.json | 72 ++++++++++++++++-------------
client/src/js/ng-django-messages.js | 5 +-
2 files changed, 42 insertions(+), 35 deletions(-)
diff --git a/client/package.json b/client/package.json
index 225cbf8..f4e72f3 100644
--- a/client/package.json
+++ b/client/package.json
@@ -1,35 +1,41 @@
{
- "name": "django-angular",
- "description": "Let Django play nice together with AngularJS",
- "version": "0.7.0",
- "homepage": "https://github.com/jrief/django-angular",
- "author": {
- "name": "Jacob Rief",
- "email": "jacob.rief@gmail.com"
- },
- "repository": {
- "type": "git",
- "url": "git://github.com/jrief/django-angular.git"
- },
- "bugs": {
- "url": "https://github.com/jrief/django-angular/issues"
- },
- "licenses": [{
- "type": "MIT",
- "url": "https://github.com/jrief/django-angular/LICENSE.txt"
- }],
- "dependencies": {},
- "devDependencies": {
- "karma": "latest",
- "karma-firefox-launcher": "latest",
- "karma-chrome-launcher": "latest",
- "karma-jasmine": "latest",
- "grunt": "latest",
- "grunt-contrib-concat": "latest",
- "grunt-contrib-jshint": "latest",
- "grunt-contrib-uglify": "latest",
- "grunt-ngmin": "latest",
- "grunt-karma": "latest"
- },
- "keywords": ["Django", "AngularJS", "djangular"]
+ "name": "django-angular",
+ "description": "Let Django play nice together with AngularJS",
+ "version": "0.7.0",
+ "homepage": "https://github.com/jrief/django-angular",
+ "author": {
+ "name": "Jacob Rief",
+ "email": "jacob.rief@gmail.com"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/jrief/django-angular.git"
+ },
+ "bugs": {
+ "url": "https://github.com/jrief/django-angular/issues"
+ },
+ "licenses": [
+ {
+ "type": "MIT",
+ "url": "https://github.com/jrief/django-angular/LICENSE.txt"
+ }
+ ],
+ "dependencies": {},
+ "devDependencies": {
+ "grunt": "latest",
+ "grunt-contrib-concat": "latest",
+ "grunt-contrib-jshint": "latest",
+ "grunt-contrib-uglify": "latest",
+ "grunt-karma": "latest",
+ "grunt-ngmin": "latest",
+ "karma": "^0.12.28",
+ "karma-chrome-launcher": "latest",
+ "karma-firefox-launcher": "latest",
+ "karma-jasmine": "latest"
+ },
+ "keywords": [
+ "Django",
+ "AngularJS",
+ "djangular"
+ ]
}
diff --git a/client/src/js/ng-django-messages.js b/client/src/js/ng-django-messages.js
index 62dcaf0..63ca9bb 100644
--- a/client/src/js/ng-django-messages.js
+++ b/client/src/js/ng-django-messages.js
@@ -128,7 +128,7 @@ function djangoMessagesForm() {
};
function _clearFormMessage(form) {
- form.$message = '';
+ form.$message = undefined;
};
function _displayErrors(form, errors) {
@@ -160,7 +160,8 @@ function djangoMessagesForm() {
angular.forEach(field, function(subField, subKey) {
if(angular.isDefined(subField) &&
- angular.isFunction(subField.$validate)) {
+ angular.isFunction(subField.$validate)) {
+
subField.$validate();
}
});
From f7ce5c8b7e68f1c60cbc973413d3b3d510f7eb0c Mon Sep 17 00:00:00 2001
From: jamesrobb
Date: Fri, 13 Mar 2015 14:09:32 +0000
Subject: [PATCH 16/25] added ngMessages client tests
---
client/karma.conf.1.3.js | 59 ++++++++++++
client/karma.conf.js | 2 +-
client/src/js/ng-django-forms.js | 4 +-
client/src/js/ng-django-messages.js | 34 +++++--
client/tests/djangoNgMessagesSpec.js | 136 +++++++++++++++++++++++++++
5 files changed, 222 insertions(+), 13 deletions(-)
create mode 100644 client/karma.conf.1.3.js
create mode 100644 client/tests/djangoNgMessagesSpec.js
diff --git a/client/karma.conf.1.3.js b/client/karma.conf.1.3.js
new file mode 100644
index 0000000..b4b3951
--- /dev/null
+++ b/client/karma.conf.1.3.js
@@ -0,0 +1,59 @@
+'use strict';
+
+// Karma configuration
+module.exports = function(config) {
+ function getFiles() {
+ var fs = require('fs');
+ var files = [];
+ ['angular.js', 'angular-mocks.js', 'angular-messages.js'].forEach(function(item) {
+ var cachename = 'cdncache/' + item;
+ files.push(fs.existsSync(cachename) ? cachename : 'http://code.angularjs.org/1.3.0/' + item);
+ });
+ return files.concat(['src/js/*.js', 'tests/*.js', 'mocks/*.js']);
+ }
+
+ config.set({
+ // frameworks to use
+ frameworks: ['jasmine'],
+
+ // list of files / patterns to load in the browser
+ files: getFiles(),
+
+ // list of files to exclude
+ exclude: [],
+
+ // test results reporter to use
+ // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
+ reporters: ['progress'],
+
+ // web server port
+ port: 9090,
+
+ // enable / disable colors in the output (reporters and logs)
+ colors: true,
+
+ // level of logging
+ // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
+ logLevel: config.LOG_INFO,
+
+ // enable / disable watching file and executing tests whenever any file changes
+ autoWatch: false,
+
+ // Start these browsers, currently available:
+ // - Chrome
+ // - ChromeCanary
+ // - Firefox
+ // - Opera (has to be installed with `npm install karma-opera-launcher`)
+ // - Safari (only Mac; has to be installed with `npm install karma-safari-launcher`)
+ // - PhantomJS
+ // - IE (only Windows; has to be installed with `npm install karma-ie-launcher`)
+ browsers: ['ChromeCanary'],
+
+ // If browser does not capture in given timeout [ms], kill it
+ captureTimeout: 60000,
+
+ // Continuous Integration mode
+ // if true, it capture browsers, run tests and exit
+ singleRun: false
+ });
+};
diff --git a/client/karma.conf.js b/client/karma.conf.js
index f6041fd..71c2512 100644
--- a/client/karma.conf.js
+++ b/client/karma.conf.js
@@ -20,7 +20,7 @@ module.exports = function(config) {
files: getFiles(),
// list of files to exclude
- exclude: [],
+ exclude: ['tests/djangoNgMessagesSpec.js'],
// test results reporter to use
// possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
diff --git a/client/src/js/ng-django-forms.js b/client/src/js/ng-django-forms.js
index 88b3c6f..92b20c6 100644
--- a/client/src/js/ng-django-forms.js
+++ b/client/src/js/ng-django-forms.js
@@ -273,12 +273,12 @@ djng_forms_module.factory('djangoForm', function() {
}
function isField(field) {
- return angular.isArray(field.$viewChangeListeners)
+ return angular.isArray(field.$viewChangeListeners);
}
return {
// setErrors takes care of updating prepared placeholder fields for displaying form errors
- // deteced by an AJAX submission. Returns true if errors have been added to the form.
+ // detected by an AJAX submission. Returns true if errors have been added to the form.
setErrors: function(form, errors) {
// remove errors from this form, which may have been rejected by an earlier validation
form.$message = '';
diff --git a/client/src/js/ng-django-messages.js b/client/src/js/ng-django-messages.js
index 63ca9bb..90b00f6 100644
--- a/client/src/js/ng-django-messages.js
+++ b/client/src/js/ng-django-messages.js
@@ -6,7 +6,8 @@ angular
'ngMessages'
])
- .directive('form', formExtension)
+ .directive('form', formDirectiveFactory())
+ .directive('ngForm', formDirectiveFactory(true))
.directive('validateRejected', validateRejected)
.factory('djangoMessagesForm', djangoMessagesForm);
@@ -20,15 +21,18 @@ angular
*
* - setValidFieldsPristine()
*/
-function formExtension() {
+
+function formDirectiveFactory(isNgForm) {
+
+return function() {
return {
- restrict: 'E',
+ restrict: isNgForm ? 'EAC' : 'E',
require: [
'^?form'
],
link: {
- pre: function($scope, $element, $attrs, ctrls) {
+ pre: function(scope, element, attrs, ctrls) {
var ctrl = ctrls[0],
controls,
@@ -74,14 +78,16 @@ function formExtension() {
}
}
}
-};
+}
+
+}
function validateRejected() {
return {
require: 'ngModel',
- link: function($scope, $element, $attrs, ngModel) {
+ link: function(scope, element, attrs, ngModel) {
var _hasMessage = false,
_value = null;
@@ -99,7 +105,7 @@ function validateRejected() {
}else{
- _hasMessage = ngModel.$message && ngModel.$message.rejected !== undefined;
+ _hasMessage = !!(ngModel.$message && ngModel.$message.rejected !== undefined);
if(_hasMessage) {
_value = value;
@@ -133,13 +139,11 @@ function djangoMessagesForm() {
function _displayErrors(form, errors) {
- form.$setSubmitted();
-
angular.forEach(errors,
function(error, key) {
var field,
message = error[0];
-
+
if(form.hasOwnProperty(key)) {
field = form[key];
@@ -170,6 +174,16 @@ function djangoMessagesForm() {
}else{
form.$message = message;
+ /*
+ * Only set current valid fields to pristine
+ *
+ * Any field that's been submitted with an error should
+ * still display its error
+ *
+ * Any field that was valid when the form was submitted,
+ * may have caused the NON_FIELD_ERRORS, so should be set
+ * to pristine to prevent it's valid state being displayed
+ */
form.setValidFieldsPristine();
}
}
diff --git a/client/tests/djangoNgMessagesSpec.js b/client/tests/djangoNgMessagesSpec.js
new file mode 100644
index 0000000..5433347
--- /dev/null
+++ b/client/tests/djangoNgMessagesSpec.js
@@ -0,0 +1,136 @@
+'use strict';
+
+describe('unit tests for module ng.django.messages', function() {
+
+ function compileForm($compile, scope, replace_value) {
+ var template =
+ '';
+ var form = angular.element(template.replace('{value}', replace_value));
+ $compile(form)(scope);
+ scope.$digest();
+ }
+
+ beforeEach(function() {
+ module('ng.django.messages');
+ });
+
+
+ describe('form extension behaviour', function() {
+
+ var scope, form, field;
+
+ beforeEach(inject(function($rootScope, $compile) {
+ scope = $rootScope.$new();
+ compileForm($compile, scope, '');
+ form = scope.valid_form;
+ field = scope.valid_form.email_field;
+ }));
+
+ it('should set valid fields back to pristine', function() {
+ field.$setViewValue('example@example.com');
+ expect(field.$valid).toBe(true);
+ expect(field.$pristine).toBe(false);
+ form.setValidFieldsPristine();
+ expect(field.$valid).toBe(true);
+ expect(field.$pristine).toBe(true);
+ });
+
+ it('should leave invalid fields dirty', function() {
+ field.$setViewValue('example');
+ expect(field.$valid).toBe(false);
+ expect(field.$pristine).toBe(false);
+ form.setValidFieldsPristine();
+ expect(field.$valid).toBe(false);
+ expect(field.$pristine).toBe(false);
+ });
+
+ });
+
+ describe('rejected validation directive', function() {
+
+ var scope, form, field;
+
+ beforeEach(inject(function($rootScope, $compile) {
+ scope = $rootScope.$new();
+ compileForm($compile, scope, '');
+ form = scope.valid_form;
+ field = scope.valid_form.email_field;
+ }));
+
+ it('should invalidate field when rejected message exists', function() {
+ field.$setViewValue('example@example.com');
+ field.$message = {rejected: 'email already in use'};
+ field.$validate();
+ expect(field.$valid).toBe(false);
+ expect(field.$error.rejected).toBe(true);
+ });
+
+ it('should clear message and return true when new value is set on field and rejected message exists from previous failed rejected validation', function() {
+ field.$setViewValue('example@example.com');
+ field.$message = {rejected: 'email already in use'};
+ field.$validate();
+ expect(field.$valid).toBe(false);
+ expect(field.$error.rejected).toBe(true);
+ field.$setViewValue('example@example2.com');
+ expect(field.$valid).toBe(true);
+ expect(field.$error.rejected).toBe(undefined);
+ });
+
+ it('should not remove message if field value is the same as previous failed rejected validation', function() {
+ field.$setViewValue('example@example.com');
+ field.$message = {rejected: 'email already in use'};
+ field.$validate();
+ expect(field.$valid).toBe(false);
+ expect(field.$error.rejected).toBe(true);
+ field.$setViewValue('example@example.com');
+ field.$validate();
+ expect(field.$valid).toBe(false);
+ expect(field.$error.rejected).toBe(true);
+ });
+ });
+
+
+ describe('form rejected error handling', function() {
+
+ var scope, form, field, djangoMessagesForm,
+ formError = {errors: {__all__: ["The email is rejected by the server."]}},
+ fieldError = {errors: {email_field: ["This field is required."]}};
+
+ beforeEach(inject(function($rootScope, $compile, _djangoMessagesForm_){
+ scope = $rootScope.$new();
+ compileForm($compile, scope, '');
+ form = scope.valid_form;
+ field = scope.valid_form.email_field;
+ djangoMessagesForm = _djangoMessagesForm_;
+ }));
+
+ it('should return false if no errors listed', function() {
+ expect(djangoMessagesForm.setErrors(form, undefined)).toBe(false);
+ });
+
+ it('should return true if non field errors listed', function() {
+ expect(djangoMessagesForm.setErrors(form, formError.errors)).toBe(true);
+ });
+
+ it('should return true if field errors listed', function() {
+ expect(djangoMessagesForm.setErrors(form, fieldError.errors)).toBe(true);
+ });
+
+ it('should add rejected non field errors to form.$message and set valid fields pristine', function() {
+ field.$setViewValue('example@example.com');
+ expect(field.$pristine).toBe(false);
+ djangoMessagesForm.setErrors(form, formError.errors);
+ expect(form.$message).toBe('The email is rejected by the server.');
+ expect(field.$pristine).toBe(true);
+ });
+
+ it('should add rejected error message to field.$message.rejected and validate', function() {
+ djangoMessagesForm.setErrors(form, fieldError.errors);
+ expect(field.$message.rejected).toBe('This field is required.');
+ expect(field.$valid).toBe(false);
+ });
+ });
+
+});
From 2eb373169a4be9886fd2791dbfa4e8289a36a5d7 Mon Sep 17 00:00:00 2001
From: jamesrobb
Date: Wed, 18 Mar 2015 17:36:16 +0000
Subject: [PATCH 17/25] added bound form error handling and tests
---
client/src/js/ng-django-messages.js | 37 +-
client/tests/djangoNgMessagesSpec.js | 39 ++-
djangular/forms/angular_messages.py | 21 +-
docs/angular-messages.rst | 322 ++++++++++++++++++
examples/server/forms/subscribe_form.py | 3 +-
examples/server/templates/base.html | 19 +-
.../server/templates/form-data-valid.html | 7 -
examples/server/templates/model-scope.html | 12 +-
examples/server/templates/ng-messages.html | 43 +--
examples/server/templates/subscribe-form.html | 6 +-
.../templates/three-way-data-binding.html | 18 +-
examples/server/tutorial/ng-messages.html | 13 +
12 files changed, 479 insertions(+), 61 deletions(-)
create mode 100644 docs/angular-messages.rst
create mode 100644 examples/server/tutorial/ng-messages.html
diff --git a/client/src/js/ng-django-messages.js b/client/src/js/ng-django-messages.js
index 90b00f6..e9cbdf0 100644
--- a/client/src/js/ng-django-messages.js
+++ b/client/src/js/ng-django-messages.js
@@ -8,7 +8,8 @@ angular
.directive('form', formDirectiveFactory())
.directive('ngForm', formDirectiveFactory(true))
- .directive('validateRejected', validateRejected)
+ .directive('djngError', djngError)
+ .directive('djngValidateRejected', validateRejected)
.factory('djangoMessagesForm', djangoMessagesForm);
@@ -83,12 +84,40 @@ return function() {
}
+function djngError($timeout) {
+
+ return {
+ restrict: 'A',
+ require: '?^form',
+ link: function(scope, element, attrs, formCtrl) {
+ var boundField;
+ if(!formCtrl || attrs.djngError === 'bound-field')
+ return;
+ element.removeAttr('djng-error');
+
+ $timeout(function(){
+ formCtrl.$setSubmitted();
+ boundField = formCtrl[attrs.name];
+
+ if(boundField) {
+ boundField.$message = {rejected: attrs.djngError};
+ boundField.$validate();
+ }
+ });
+ }
+ }
+}
+
+
function validateRejected() {
return {
- require: 'ngModel',
+ restrict: 'A',
+ require: '?ngModel',
link: function(scope, element, attrs, ngModel) {
-
+
+ if(!ngModel) return;
+
var _hasMessage = false,
_value = null;
@@ -139,6 +168,8 @@ function djangoMessagesForm() {
function _displayErrors(form, errors) {
+ form.$setSubmitted();
+
angular.forEach(errors,
function(error, key) {
var field,
diff --git a/client/tests/djangoNgMessagesSpec.js b/client/tests/djangoNgMessagesSpec.js
index 5433347..d0bd743 100644
--- a/client/tests/djangoNgMessagesSpec.js
+++ b/client/tests/djangoNgMessagesSpec.js
@@ -5,7 +5,17 @@ describe('unit tests for module ng.django.messages', function() {
function compileForm($compile, scope, replace_value) {
var template =
'';
+ var form = angular.element(template.replace('{value}', replace_value));
+ $compile(form)(scope);
+ scope.$digest();
+ }
+
+ function compileFormWithBoundError($compile, scope, replace_value) {
+ var template =
+ '';
var form = angular.element(template.replace('{value}', replace_value));
$compile(form)(scope);
@@ -48,6 +58,33 @@ describe('unit tests for module ng.django.messages', function() {
});
+
+ ddescribe('bound form error handling', function() {
+
+ var scope, form, field, $timeout;
+
+ beforeEach(inject(function($rootScope, $compile, _$timeout_) {
+ scope = $rootScope.$new();
+ compileFormWithBoundError($compile, scope, 'value="barry"');
+ form = scope.valid_form;
+ field = scope.valid_form.email_field;
+ $timeout = _$timeout_;
+ }));
+
+ it('should invalidate field when djng-error exists', function() {
+ $timeout.flush();
+ expect(field.$valid).toBe(false);
+ expect(field.$error.rejected).toBe(true);
+ expect(field.$message.rejected).toBe('valid email required');
+ });
+
+ it('should set form to $submitted', function() {
+ $timeout.flush();
+ expect(form.$submitted).toBe(true);
+ });
+ });
+
+
describe('rejected validation directive', function() {
var scope, form, field;
diff --git a/djangular/forms/angular_messages.py b/djangular/forms/angular_messages.py
index 6d4335b..bf55a94 100644
--- a/djangular/forms/angular_messages.py
+++ b/djangular/forms/angular_messages.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
-from django.utils.html import format_html
+from django.utils.html import format_html, format_html_join
from django.utils.encoding import python_2_unicode_compatible, force_text
from django.utils.safestring import mark_safe, SafeText
@@ -20,7 +20,7 @@ class NgMessagesFieldErrorList(TupleErrorList):
ul_format = ''
li_format = '{3} '
- """ span's necessary due to this bug https://github.com/angular/angular.js/issues/8089"""
+ """ a span is necessary due to this bug https://github.com/angular/angular.js/issues/8089 """
li_format_bind = ' '
def as_ul(self):
@@ -31,6 +31,9 @@ def as_ul(self):
valid_list = []
invalid_list = []
for e in self:
+ if e[2] == '$pristine':
+ continue
+
if e[3] == '$valid':
li_format = self.li_format_valid
error_list = valid_list
@@ -70,7 +73,6 @@ def get_field_errors(self, field):
errors = super(NgMessagesMixin, self).get_field_errors(field)
if field.is_hidden:
return errors
- self._remove_ng_model_form_mixin_rejected_error(errors)
identifier = format_html('{0}.{1}', self.form_name, field.name)
errors.append(SafeTuple((identifier, self.field_error_css_classes, '$dirty', 'rejected', 'invalid', '$message')))
return errors
@@ -81,11 +83,12 @@ def non_field_errors(self):
def get_widget_attrs(self, bound_field):
attrs = super(NgMessagesMixin, self).get_widget_attrs(bound_field)
- attrs['validate-rejected'] = ""
+ attrs.update({'djng-validate-rejected': ''})
+ if self.is_bound:
+ self._apply_bound_error(bound_field, attrs)
return attrs
- def _remove_ng_model_form_mixin_rejected_error(self, errors):
- for item in errors:
- if item[2] == '$pristine' and item[5] == '$message':
- errors.remove(item)
- return
+ def _apply_bound_error(self, bound_field, attrs):
+ for error in bound_field.errors:
+ if error[3] == '$pristine':
+ attrs.update({'djng-error': error[5]})
diff --git a/docs/angular-messages.rst b/docs/angular-messages.rst
new file mode 100644
index 0000000..69a59d0
--- /dev/null
+++ b/docs/angular-messages.rst
@@ -0,0 +1,322 @@
+.. _angular-messages:
+
+============================================
+Render form error lists in ngMessages format
+============================================
+
+https://docs.angularjs.org/api/ngMessages
+
+The following classes add support for rendering and using ngMessages to manage form error message
+display
+
+NgMessagesMixin
+===============
+
+Works in combination with other django-angular form mixins (), to facilitate the rendering of
+form errors in the correct format for ngMessages and
+
+.. code-block:: python
+
+
+
+Django's forms.Form_ class offers many possibilities to validate a given form. This, for obvious
+reasons is done on the server. However, customers may not always accept to submit a form, just to
+find out that they missed to input some correct data into a field. Therefore, adding client side
+form validation is a good idea and very common. But since client side validation easily can be
+bypassed, the same validation has to occur a second time, when the server accepts the forms data
+for final processing.
+
+*This leads to code duplication and generally violates the DRY principle!*
+
+
+A workaround to this problem is to use Django's form declaration to automatically generate client
+side validation code, suitable for AngularJS. By adding a special mixin class to the form class,
+this can be achieved automatically and on the fly
+
+.. code-block:: python
+
+ from django import forms
+ from django.utils import six
+ from djangular.forms import NgDeclarativeFieldsMetaclass, NgFormValidationMixin
+
+ class MyValidatedForm(six.with_metaclass(NgDeclarativeFieldsMetaclass, NgFormValidationMixin, forms.Form)):
+ form_name = 'my_valid_form'
+ surname = forms.CharField(label='Surname', min_length=3, max_length=20)
+ age = forms.DecimalField(min_value=18, max_value=99)
+
+In the majority of cases, the Form is derived from Django's ``forms.Form``, so the above example
+can be rewritten in a simpler way, by using the convenience class ``NgForm`` as a replacement:
+
+.. code-block:: python
+
+ from djangular.forms import NgFormValidationMixin, NgForm
+
+ class MyValidatedForm(NgFormValidationMixin, NgForm):
+ # members as above
+
+If the Form shall inherit from Django's ``forms.ModelForm``, use the convenience class
+``NgModelForm``:
+
+.. code-block:: python
+
+ from djangular.forms import NgFormValidationMixin, NgModelForm
+
+ class MyValidatedForm(NgFormValidationMixin, NgModelForm):
+ class Meta:
+ model = Article
+
+ # fields as usual
+
+Each page under control of AngularJS requires a unique form name, otherwise the AngularJS's form
+validation engine shows undefined behavior. Therefore you must name each form inheriting from
+``NgFormValidationMixin``. If a form is used only once per page, the form's name can be added to
+the class declaration, as shown above. If no form name is specified, it defaults to ``form``,
+limiting the number of validated forms per page to one.
+
+If a form inheriting from ``NgFormValidationMixin`` shall be instantiated more than once per page,
+each instance of that form must be instantiated with a different name. This then must be done in
+the constructor of the form, by passing in the argument ``form_name='my_form'``.
+
+In the view class, add the created form to the rendering context:
+
+.. code-block:: python
+
+ def get_context_data(self, **kwargs):
+ context = super(MyRenderingView, self).get_context_data(**kwargs)
+ context.update(form=MyValidatedForm())
+ return context
+
+or if the same form declaration shall be used more than once:
+
+.. code-block:: python
+
+ def get_context_data(self, **kwargs):
+ context = super(MyRenderingView, self).get_context_data(**kwargs)
+ context.update(form1=MyValidatedForm(form_name='my_valid_form1'),
+ form2=MyValidatedForm(form_name='my_valid_form2'))
+ return context
+
+.. note:: Do not use an empty ``label`` when declaring a form field, otherwise the class
+ ``NgFormValidationMixin`` won't be able to render AngularJS's validation error elements.
+ This also applies to ``auto_id``, which if False, will not include ```` tags while
+ rendering the form.
+
+
+Render this form in a template
+------------------------------
+
+.. code-block:: html
+
+
+
+Remember to add the entry ``name="{{ form.form_name }}"`` to the ``form`` element, otherwise AngularJS's
+validation engine won't work. Use the directive ``novalidate`` to disable the browser’s native form
+validation. If you just need AngularJS's built in form validation mechanisms without customized
+checks on the forms data, there is no need to add an ``ng-controller`` onto a wrapping HTML element.
+The only measure to take, is to give each form on a unique name, otherwise the AngularJS form
+validation engine shows undefined behavior.
+
+Forms which do not validate on the client, probably shall not be posted. This can simply be disabled
+by replacing the submit button with the following HTML code:
+
+.. code-block:: html
+
+
+
+.. note:: On Django-1.5, some field constraints, such as the attributes ``min_length`` and
+ ``max_length``, are ignored when used with ``NgFormValidationMixin``. This has been fixed
+ in Django-1.6 .
+
+More granular output
+....................
+If the form fields shall be explicitly rendered, the potential field validation errors can be
+rendered in templates using a special field tag. Say, the form contains
+
+.. code-block:: python
+
+ from django import forms
+ from djangular.forms import NgFormValidationMixin
+
+ class MyValidatedForm(NgFormValidationMixin, forms.Form):
+ email = forms.EmailField(label='Email')
+
+then access the potential validation errors in templates using ``{{ form.email.errors }}``. This
+renders the form with an unsorted list of potential errors, which may occur during client side
+validation.
+
+.. code-block:: html
+
+
+
+The AngularJS form validation engine, normally hides these potential errors. They only become
+visible, if the user enters an invalid email address.
+
+
+Bound forms
+...........
+If the `form is bound`_ and rendered, then errors detected by the server side's validation code are
+rendered as unsorted list in addition to the list of potential errors. Both of these error lists are
+rendered using their own ```` elements. The behavior for potential errors remains the same, but
+detected errors are hidden the moment, the user sets the form into a dirty state.
+
+.. note:: AngularJS normally hides the content of bound forms, which means that `` `` fields
+ seem empty, even if their ``value`` attribute is set. In order to restore the content of
+ those input fields to their default value, initialize your AngularJS application with
+ ``angular.module('MyApp', ['ng.django.forms']);``.
+
+
+Combine NgFormValidationMixin with NgModelFormMixin
+---------------------------------------------------
+While it is possible to use ``NgFormValidationMixin`` on itself, it is perfectly legal to mix
+``NgModelFormMixin`` with ``NgFormValidationMixin``. However, a few precautions have to be taken.
+
+On class declaration inherit first from ``NgModelFormMixin`` and *afterward* from
+``NgFormValidationMixin``. Valid example:
+
+.. code-block:: python
+
+ from django import forms
+ from djangular.forms import NgFormValidationMixin, NgModelFormMixin
+
+ class MyValidatedForm(NgModelFormMixin, NgFormValidationMixin, forms.Form):
+ # custom form fields
+
+but don't do this
+
+.. code-block:: python
+
+ class MyValidatedForm(NgFormValidationMixin, NgModelFormMixin, forms.Form):
+ # custom form fields
+
+Another precaution to take, is to use different names for the forms name and the ``scope_prefix``.
+So, this is legal
+
+.. code-block:: python
+
+ form = MyValidatedForm(form_name='my_form', scope_prefix='my_model')
+
+but this is not
+
+.. code-block:: python
+
+ form = MyValidatedForm(form_name='my_form', scope_prefix='my_form')
+
+An implementation note
+......................
+AngularJS names each input field to validate, by concatenating its forms name with its fields name.
+This object member then contains an error object, named ``my_form.field_name.$error`` filled by
+the AngularJS validation mechanism. The placeholder for the error object would clash with
+``ng-model``, if the form name is identical to the scope prefix. Therefore, just remember to use
+different names.
+
+
+Customize detected and potential validation errors
+==================================================
+If a form with AngularJS validation is rendered, each input field is prefixed with an unsorted list
+```` of potential validation errors. For each possible constraint violation, a list item
+```` containing a descriptive message is added to that list.
+
+If a client enters invalid data into that form, AngularJS unhides one of these prepared error
+messages, using ng-show_. The displayed message text is exactly the same as would be shown if
+the server side code complains about invalid data during form validation. These prepared error
+messages can be customized during `form field definition`_.
+
+The default error list is rendered as ````. To each `` ``
+of this error list, the attribute ``class="invalid"`` is added. The last list-item
+`` `` is somehow special, as it is only visible if the corresponding input
+field contains valid data. By using special style sheets, one can for instance add a green
+tick after a validated input field, to signal that everything is OK.
+
+The styling of these validation elements must be done through CSS, for example with:
+
+.. code-block:: css
+
+ ul.djng-form-errors {
+ margin-left: 0;
+ display: inline-block;
+ list-style-type: none;
+ }
+ ul.djng-form-errors li.invalid {
+ color: #e9322d;
+ }
+ ul.djng-form-errors li.invalid:before {
+ content: "\2716\20"; /* adds a red cross before the error message */
+ }
+ ul.djng-form-errors li.valid:before {
+ color: #00c900;
+ content: "\2714"; /* adds a green tick */
+ }
+
+If you desire an alternative CSS class or an alternative way of rendering the list of errors, then
+initialize the form instance with
+
+.. code-block:: python
+
+ class MyErrorList(list):
+ # rendering methods go here
+
+ # during form instantiation
+ my_form = MyForm(error_class=MyErrorList)
+
+Refer to ``TupleErrorList`` on how to implement an alternative error list renderer. Currently this
+error list renderer, renders two ````-elements for each input field, one to be shown for
+*pristine* forms and one to be shown for *dirty* forms.
+
+
+Adding form validation to customized fields
+-------------------------------------------
+Django's form validation is not 1:1 compatible with AngularJS's validation. Therefore **djangular**
+is shipped with a mapping module, which translate Django's form validation to AngularJS. This module
+is located in ``djangular.forms.patched_fields``.
+
+If you need to add or to replace any of these mappings, create a Python module which implements an
+alternative mapping to the module shipped with **djangular**. Refer to an alternative module in your
+``settings.py`` with the configuration directive ``DJANGULAR_VALIDATION_MAPPING_MODULE``.
+
+For further information about how to use form validation with AngularJS, please refer to the
+:ref:`demo pages`.
+
+
+Adding an AngularJS directive for validating form fields
+--------------------------------------------------------
+Sometimes it can be useful to add a generic field validator on the client side, which can be
+controlled by the form's definition on the server. One such example is Django's DateField:
+
+.. code-block:: python
+
+ from django import forms
+
+ class MyForm(forms.Form):
+ # other fields
+ date = forms.DateField(label='Date',
+ widget=forms.widgets.DateInput(attrs={'validate-date': '^(\d{4})-(\d{1,2})-(\d{1,2})$'}))
+
+Since AngularJS can not validate dates, such a field requires a customized directive, which with
+the above definition, will be added as new attribute to the input element for date:
+
+.. code-block:: html
+
+
+
+If your AngularJS application has been initialized with
+
+.. code-block:: javascript
+
+ angular.module('MyApp', ['ng.django.forms']);
+
+then this new attribute is detected by the AngularJS directive ``validateDate``, which in turn
+checks the date for valid input and shows the content of the errors fields, if not.
+
+If you need to write a reusable component for customized form fields, refer to that directive as a
+starting point.
+
+.. _forms.Form: https://docs.djangoproject.com/en/dev/topics/forms/#form-objects
+.. _form field definition: https://docs.djangoproject.com/en/dev/ref/forms/fields/#error-messages
+.. _ng-show: http://docs.angularjs.org/api/ng.directive:ngShow
+.. _form is bound: https://docs.djangoproject.com/en/dev/ref/forms/api/#django.forms.BoundField.errors
diff --git a/examples/server/forms/subscribe_form.py b/examples/server/forms/subscribe_form.py
index 1cfafb7..3897845 100644
--- a/examples/server/forms/subscribe_form.py
+++ b/examples/server/forms/subscribe_form.py
@@ -21,6 +21,7 @@ class SubscribeForm(Bootstrap3Form):
NOTIFY_BY = (('email', 'EMail'), ('phone', 'Phone'), ('sms', 'SMS'), ('postal', 'Postcard'),)
first_name = forms.CharField(label='First name', min_length=3, max_length=20)
+
last_name = forms.RegexField(r'^[A-Z][a-z -]?', label='Last name',
error_messages={'invalid': 'Last names shall start in upper case'})
sex = forms.ChoiceField(choices=(('m', 'Male'), ('f', 'Female')),
@@ -53,4 +54,4 @@ class SubscribeForm(Bootstrap3Form):
validators=[validate_password],
help_text='The password is "secret"')
confirmation_key = forms.CharField(max_length=40, required=True, widget=forms.HiddenInput(),
- initial='hidden value')
+ initial='hidden value')
\ No newline at end of file
diff --git a/examples/server/templates/base.html b/examples/server/templates/base.html
index b906743..506d4e6 100644
--- a/examples/server/templates/base.html
+++ b/examples/server/templates/base.html
@@ -36,7 +36,7 @@
Combined Validation