Skip to content

Instantly share code, notes, and snippets.

@hilios
Last active October 26, 2017 15:34
Show Gist options
  • Save hilios/8679399 to your computer and use it in GitHub Desktop.
Save hilios/8679399 to your computer and use it in GitHub Desktop.
AngularJS Django Formset
<div django-formset-child>
{{form.as_p}}
<button django-formset-remove>Remove</button>
</div>
formset = angular.module('djangoFormsets', []);
formset.directive('djangoFormset', function() {
return {
require: 'djangoFormset',
restrict: 'A',
scope: true,
controller: 'djangoFormsetController',
link: function postLink(scope, element, attrs, controller) {
controller.setup(element);
}
};
});
formset.controller('djangoFormsetController', [
'$attrs', '$templateCache', '$compile',
function($attrs, $templateCache, $compile) {
var self = this;
self.__fid__ = 0;
self.__children__ = [];
self.__template__ = $templateCache.get($attrs.djangoFormset);
self.__formsetprefix__ = $attrs.djangoFormsetPrefix || 'form';
self.__candelete__ = $attrs.djangoFormsetCanDelete || false;
self.__canorder__ = $attrs.djangoFormsetCanOrder || false;
self.__formset__ = null;
self.__container__ = null;
self.__totalforms__ = null;
self.__minforms__ = 0;
self.__maxforms__ = 1000;
self.setup = function(element) {
self.__formset__ = element;
// Removes leading whitespaces from template, hence jqLite fails can't
// parse them.
if(self.__template__) {
self.__template__ = self.__template__.replace(/^(\s|\n){1,}/gi, '');
}
// Grab management form elements
var fidRegexp = new RegExp(self.__formsetprefix__ +
"\\-([0-9]{1,})", "i"),
managementFormRegexp = new RegExp(self.__formsetprefix__ +
"\\-([A-Z_]+)");
// Find the higher __fid__
angular.forEach(self.__children__, function(value, index) {
var inputName = value.find('input').prop('name'), fid;
inputName = inputName.match(fidRegexp);
if(inputName) {
fid = parseInt(inputName[1]);
if(fid > self.__fid__) {
self.__fid__ = fid;
}
}
});
// Find formset management fields
angular.forEach(element.find('input'), function(value, index) {
var input = angular.element(value),
match = input.prop('name').match(managementFormRegexp);
if(match) {
switch(match[1]) {
case 'TOTAL_FORMS':
self.__totalforms__ = input;
break;
case 'INITIAL_FORMS':
self.__minforms__ = parseInt(input.val());
break;
case 'MAX_NUM_FORMS':
self.__maxforms__ = parseInt(input.val());
break;
}
}
});
}
self.update = function() {
if(self.__totalforms__) {
self.__totalforms__.val(self.__children__.length);
}
}
self.addFormset = function() {
if(self.__children__.length + 1 <= self.__maxforms__) {
self.__fid__ += 1;
// Setup a new element from template
var element = angular.element(
self.__template__.replace(/__prefix__/gi, self.__fid__)
);
// Add the template to container and children's list
self.__container__.append(element);
// Compile after append to inherits controller
$compile(element)(self.__formset__.scope());
}
}
self.removeFormset = function(element) {
if(self.__children__.length - 1 >= self.__minforms__) {
var child = element, isChild = false;
while(!isChild && child.prop('tagName') !== 'BODY') {
child = child.parent();
isChild = child.attr('django-formset-child') !== undefined ||
child.attr('data-django-formset-child') !== undefined ||
child.attr('x-django-formset-child') !== undefined;
}
if(child.prop('tagName') !== 'BODY') {
child.scope().$destroy();
child.remove();
}
}
}
self.registerChild = function(element) {
self.__children__.push(element);
self.update();
}
self.destroyChild = function(element) {
var childIndex = self.__children__.indexOf(element);
self.__children__.splice(childIndex, 1);
self.update();
}
}
]);
formset.directive('djangoFormsetContainer', function() {
return {
require: '^djangoFormset',
restrict: 'A',
link: function postLink(scope, element, attrs, controller) {
controller.__container__ = element;
}
};
});
formset.directive('djangoFormsetChild', function() {
return {
require: '^djangoFormset',
restrict: 'A',
scope: true,
link: function postLink(scope, element, attrs, controller) {
controller.registerChild(element);
scope.$on('$destroy', function() {
controller.destroyChild(element);
});
}
};
});
formset.directive('djangoFormsetAdd', function() {
return {
require: '^djangoFormset',
restrict: 'A',
link: function postLink(scope, element, attrs, controller) {
element.on('click', function(event) {
event.preventDefault();
controller.addFormset(element);
});
scope.$on('$destroy', function() {
element.off('click');
});
}
};
});
formset.directive('djangoFormsetRemove', function() {
return {
require: '^djangoFormset',
restrict: 'A',
link: function postLink(scope, element, attrs, controller) {
element.on('click', function(event) {
event.preventDefault();
controller.removeFormset(element);
});
scope.$on('$destroy', function() {
element.off('click');
});
}
};
});
from django import forms
from django.forms.formsets import formset_factory
class SimpleForm(forms.Form):
name = forms.CharField()
email = forms.EmailField()
SimpleFormSet = formset_factory(SimpleForm, extra=2, can_delete=True)
<form method="post">{% csrf_token %}
<div django-formset="_formset_template.html"
django-formset-prefix="{{formset.prefix}}"
django-formset-can-delete="{{formset.can_delete}}"
django-formset-can-order="{{formset.can_order}}">
{{formset.management_form}}
<div django-formset-container>
{% for form in formset %}
{% include '_formset_template.html' with form=form %}
{% endfor %}
</div>
<button django-formset-add>Add</button>
</div>
</form>
<script type="text/ng-template" id="_formset_template.html">
{% include '_formset_template.html' with form=formset.empty_form %}
</script>
from django.shortcuts import render, redirect
from forms import SimpleFormSet
def main(request):
formset = SimpleFormSet(request.POST or None, prefix="foo")
if form.is_valid() and formset.is_valid():
return redirect('/')
return render(request, 'template.html', {
'formset': formset,
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment