Skip to content
Closed
92 changes: 73 additions & 19 deletions client/src/js/ng-django-forms.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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 <code>ng-model</code> attibute, is used to set that default
* model value on scope.
*
* <input type="text" value="barry" ng-model="data.first_name">
*
* 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);
}
}

Expand All @@ -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);
}
};
});
Expand Down
4 changes: 3 additions & 1 deletion examples/server/forms/combined_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()


6 changes: 6 additions & 0 deletions examples/server/forms/subscribe_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 3 additions & 0 deletions examples/server/views/classic_subscribe.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'}
3 changes: 3 additions & 0 deletions examples/server/views/client_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'}
3 changes: 3 additions & 0 deletions examples/server/views/combined_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions examples/server/views/model_scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Binary file added examples/test.sqlite
Binary file not shown.