diff --git a/client/src/js/ng-django-forms.js b/client/src/js/ng-django-forms.js index 5900413..7b2d9e1 100644 --- a/client/src/js/ng-django-forms.js +++ b/client/src/js/ng-django-forms.js @@ -55,33 +55,33 @@ djng_forms_module.directive('djngError', function() { // This directive overrides some of the internal behavior on forms if used together with AngularJS. // Otherwise, the content of bound forms is not displayed, because AngularJS does not know about // the concept of bound forms and thus hides values preset by Django while rendering HTML. -djng_forms_module.directive('ngModel', function() { - function restoreInputField(modelCtrl, field) { +djng_forms_module.directive('ngModel', function($parse) { + function restoreInputField(field) { // restore the field's content from the rendered content of bound fields switch (field.type) { case 'radio': if (field.defaultChecked) { - modelCtrl.$setViewValue(field.defaultValue); + return field.defaultValue; } break; case 'checkbox': if (field.defaultChecked) { - modelCtrl.$setViewValue(true); + return true; } break; case 'password': // after an (un)successful submission, reset the password field - modelCtrl.$setViewValue(null); + return null; break; default: - if (field.defaultValue) { - modelCtrl.$setViewValue(field.defaultValue); + if(field.defaultValue) { + return field.defaultValue; } break; } } - function restoreSelectOptions(modelCtrl, field) { + function restoreSelectOptions(field) { var multivalues = []; angular.forEach(field.options, function(option) { if (option.defaultSelected) { @@ -90,19 +90,70 @@ djng_forms_module.directive('ngModel', function() { if (field.multiple) { multivalues.push(option.value); } else { - modelCtrl.$setViewValue(option.value); + return option.value; } } }); if (field.multiple) { - modelCtrl.$setViewValue(multivalues); + return multivalues; } } - function restoreTextArea(modelCtrl, field) { - if (field.defaultValue) { - // restore the field's content from the rendered content of bound fields - modelCtrl.$setViewValue(field.defaultValue); + function restoreTextArea(field) { + // restore the field's content from the rendered content of bound fields + if(field.defaultValue) { + return field.defaultValue; + } + } + + /** + * @private + * + * If a default value for an input exists (either set as an initial or bound value by django), + * then the value assigned to the ng-model attibute, is used to set that default + * model value on scope. + * + * + * + * var parts = ['data', 'first_name'], + * prop = 'first_name', + * modelName = 'data', \\ this could be a multi namespaced object or nothing if the prop is set direct on scope + * fn = [ Function ], + * model; + * + * @param {object} scope The scope of the ngModel + * @param {string} ngModelName The value assigned to the ng-model attribute. Not ngModel.$name + * @param {object} value The initial value of the input + */ + function processDefaultValue(scope, ngModelName, value) { + + if(!angular.isDefined(value)) + return; + + var parts = ngModelName.split('.'), + prop = parts.pop(), + modelName = parts.join('.'), + fn = $parse(model !== '' ? model : prop), + model; + + /* + * We have a namespace for the model i.e 'data' + */ + if(modelName !== '') { + + fn = $parse(modelName); // generate safe function to retrieve model from scope + model = fn(scope) || {}; // if model already exists (default or previous values may have been set on scope) then use it. If not, create a new object + model[prop] = value; // apply the default value to the model + fn.assign(scope, model); // set the model back on scope + + /* + * the property is being assigned directly to scope, not 'hung' off another + * namespace object. No need to retrieve as we can overwrite any existing value. + */ + }else{ + + fn = $parse(prop); + fn.assign(scope, value); } } @@ -116,22 +167,25 @@ djng_forms_module.directive('ngModel', function() { var modelCtrl = ctrls[0], formCtrl = ctrls[1] || null; if (!field || !formCtrl) return; + + var defaultValue; + switch (field.tagName) { case 'INPUT': - restoreInputField(modelCtrl, field); + defaultValue = restoreInputField(field); break; case 'SELECT': - restoreSelectOptions(modelCtrl, field); + defaultValue = restoreSelectOptions(field); break; case 'TEXTAREA': - restoreTextArea(modelCtrl, field); + defaultValue = restoreTextArea(field); break; default: console.log('Unknown field type'); break; } - // restore the form's pristine state - formCtrl.$setPristine(); + + processDefaultValue(scope, attrs.ngModel, defaultValue); } }; }); diff --git a/examples/server/forms/combined_validation.py b/examples/server/forms/combined_validation.py index b55b859..80e3457 100644 --- a/examples/server/forms/combined_validation.py +++ b/examples/server/forms/combined_validation.py @@ -9,8 +9,10 @@ class SubscribeForm(NgModelFormMixin, NgFormValidationMixin, subscribe_form.SubscribeForm): scope_prefix = 'subscribe_data' form_name = 'my_form' - + def clean(self): if self.cleaned_data.get('first_name') == 'John' and self.cleaned_data.get('last_name') == 'Doe': raise ValidationError('The full name "John Doe" is rejected by the server.') return super(SubscribeForm, self).clean() + + diff --git a/examples/server/forms/subscribe_form.py b/examples/server/forms/subscribe_form.py index 1cfafb7..b86ae5d 100644 --- a/examples/server/forms/subscribe_form.py +++ b/examples/server/forms/subscribe_form.py @@ -54,3 +54,9 @@ class SubscribeForm(Bootstrap3Form): help_text='The password is "secret"') confirmation_key = forms.CharField(max_length=40, required=True, widget=forms.HiddenInput(), initial='hidden value') + + + def get_widget_attrs(self, bound_field): + attrs = super(SubscribeForm, self).get_widget_attrs(bound_field) + attrs['ng-model-options'] = "{ debounce : { 'default' : 500, blur : 0 } }" + return attrs diff --git a/examples/server/views/classic_subscribe.py b/examples/server/views/classic_subscribe.py index 87e101b..e262a30 100644 --- a/examples/server/views/classic_subscribe.py +++ b/examples/server/views/classic_subscribe.py @@ -9,3 +9,6 @@ class SubscribeView(FormView): template_name = 'subscribe-form.html' form_class = SubscribeForm success_url = reverse_lazy('form_data_valid') + + def get_initial(self): + return {'first_name': 'james'} diff --git a/examples/server/views/client_validation.py b/examples/server/views/client_validation.py index 3c9985c..05cc551 100644 --- a/examples/server/views/client_validation.py +++ b/examples/server/views/client_validation.py @@ -9,3 +9,6 @@ class SubscribeView(FormView): template_name = 'client-validation.html' form_class = SubscribeForm success_url = reverse_lazy('form_data_valid') + + def get_initial(self): + return {'first_name': 'james'} diff --git a/examples/server/views/combined_validation.py b/examples/server/views/combined_validation.py index 8bed410..86c7c26 100644 --- a/examples/server/views/combined_validation.py +++ b/examples/server/views/combined_validation.py @@ -13,6 +13,9 @@ class SubscribeView(FormView): form_class = SubscribeForm success_url = reverse_lazy('form_data_valid') + def get_initial(self): + return {'first_name': 'james'} + def post(self, request, **kwargs): if request.is_ajax(): return self.ajax(request) diff --git a/examples/server/views/model_scope.py b/examples/server/views/model_scope.py index a3e0b4a..52979b5 100644 --- a/examples/server/views/model_scope.py +++ b/examples/server/views/model_scope.py @@ -13,6 +13,9 @@ class SubscribeView(FormView): form_class = SubscribeForm success_url = reverse_lazy('form_data_valid') + def get_initial(self): + return {'first_name': 'james'} + def post(self, request, **kwargs): if request.is_ajax(): return self.ajax(request) diff --git a/examples/test.sqlite b/examples/test.sqlite new file mode 100644 index 0000000..ace7e9f Binary files /dev/null and b/examples/test.sqlite differ