diff --git a/AUTHORS b/AUTHORS index 4886eb2881..db08e662cc 100644 --- a/AUTHORS +++ b/AUTHORS @@ -588,6 +588,7 @@ answer newbie questions, and generally made Django that much better: Rachel Willmer Radek Švarz Rajesh Dhawan + Ramez Ashraf Ramiro Morales Ram Rachum Randy Barlow diff --git a/django/contrib/admin/static/admin/js/inlines.js b/django/contrib/admin/static/admin/js/inlines.js index 3e082217c7..c725e30cbb 100644 --- a/django/contrib/admin/static/admin/js/inlines.js +++ b/django/contrib/admin/static/admin/js/inlines.js @@ -98,6 +98,7 @@ if (options.removed) { options.removed(row); } + $(document).trigger('formset:removed', [row, options.prefix]); // Update the TOTAL_FORMS form count. var forms = $("." + options.formCssClass); $("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length); @@ -120,6 +121,7 @@ if (options.added) { options.added(row); } + $(document).trigger('formset:added', [row, options.prefix]); }); } return this; diff --git a/django/contrib/admin/static/admin/js/inlines.min.js b/django/contrib/admin/static/admin/js/inlines.min.js index d968b58f4d..f83cbec6fc 100644 --- a/django/contrib/admin/static/admin/js/inlines.min.js +++ b/django/contrib/admin/static/admin/js/inlines.min.js @@ -1,9 +1,9 @@ -(function(b){b.fn.formset=function(c){var a=b.extend({},b.fn.formset.defaults,c),f=b(this);c=f.parent();var k=function(a,e,l){var d=new RegExp("("+e+"-(\\d+|__prefix__))");e=e+"-"+l;b(a).prop("for")&&b(a).prop("for",b(a).prop("for").replace(d,e));a.id&&(a.id=a.id.replace(d,e));a.name&&(a.name=a.name.replace(d,e))},h=b("#id_"+a.prefix+"-TOTAL_FORMS").prop("autocomplete","off"),l=parseInt(h.val(),10),e=b("#id_"+a.prefix+"-MAX_NUM_FORMS").prop("autocomplete","off"),d=""===e.val()||0'+a.addText+""),m=c.find("tr:last a")):(f.filter(":last").after('"),m=f.filter(":last").next().find("a"));m.click(function(d){d.preventDefault();d=b("#"+a.prefix+ -"-empty");var g=d.clone(!0);g.removeClass(a.emptyCssClass).addClass(a.formCssClass).attr("id",a.prefix+"-"+l);g.is("tr")?g.children(":last").append('"):g.is("ul")||g.is("ol")?g.append('
  • '+a.deleteText+"
  • "):g.children(":first").append(''+a.deleteText+"");g.find("*").each(function(){k(this, -a.prefix,h.val())});g.insertBefore(b(d));b(h).val(parseInt(h.val(),10)+1);l+=1;""!==e.val()&&0>=e.val()-h.val()&&m.parent().hide();g.find("a."+a.deleteCssClass).click(function(d){d.preventDefault();g.remove();--l;a.removed&&a.removed(g);d=b("."+a.formCssClass);b("#id_"+a.prefix+"-TOTAL_FORMS").val(d.length);(""===e.val()||0'+a.addText+""),m=d.find("tr:last a")):(e.filter(":last").after('"),m=e.filter(":last").next().find("a"));m.click(function(c){c.preventDefault();c=b("#"+a.prefix+ +"-empty");var g=c.clone(!0);g.removeClass(a.emptyCssClass).addClass(a.formCssClass).attr("id",a.prefix+"-"+l);g.is("tr")?g.children(":last").append('"):g.is("ul")||g.is("ol")?g.append('
  • '+a.deleteText+"
  • "):g.children(":first").append(''+a.deleteText+"");g.find("*").each(function(){k(this, +a.prefix,h.val())});g.insertBefore(b(c));b(h).val(parseInt(h.val(),10)+1);l+=1;""!==f.val()&&0>=f.val()-h.val()&&m.parent().hide();g.find("a."+a.deleteCssClass).click(function(c){c.preventDefault();g.remove();--l;a.removed&&a.removed(g);b(document).trigger("formset:removed",[g,a.prefix]);c=b("."+a.formCssClass);b("#id_"+a.prefix+"-TOTAL_FORMS").val(c.length);(""===f.val()||0` prepends ``None``) to any asset paths. The same rules apply as :ref:`regular asset definitions on forms `. +.. _contrib-admin-jquery: + jQuery ~~~~~~ diff --git a/docs/ref/contrib/admin/javascript.txt b/docs/ref/contrib/admin/javascript.txt new file mode 100644 index 0000000000..f953b269ad --- /dev/null +++ b/docs/ref/contrib/admin/javascript.txt @@ -0,0 +1,75 @@ +====================================== +JavaScript customizations in the admin +====================================== + +.. _admin-javascript-inline-form-events: + +Inline form events +================== + +.. versionadded:: 1.9 + +You may want to execute some JavaScript when an inline form is added or removed +in the admin change form. The ``formset:added`` and ``formset:removed`` jQuery +events allow this. The event handler is passed three arguments: + +* ``event`` is the ``jQuery`` event. +* ``$row`` is the newly added (or removed) row. +* ``formsetName`` is the formset the row belongs to. + +The event is fired using the :ref:`django.jQuery namespace +`. + +In your custom ``change_form.html`` template, extend the +``admin_change_form_document_ready`` block and add the event listener code: + +.. code-block:: html+django + + {% extends 'admin/change_form.html' %} + + {% block admin_change_form_document_ready %} + {{ block.super }} + + {% endblock %} + +Two points to keep in mind: + +* The JavaScript code must go in a template block if you are inheriting + ``admin/change_form.html`` or it won't be rendered in the final HTML. +* ``{{ block.super }}`` is added because Django's + ``admin_change_form_document_ready`` block contains JavaScript code to handle + various operations in the change form and we need that to be rendered too. + +Sometimes you'll need to work with ``jQuery`` plugins that are not registered +in the ``django.jQuery`` namespace. To do that, simply change how the code +listens for events. Instead of wrapping the listener in the ``django.jQuery`` +namespace, just listen to the event triggered from there. For example: + +.. code-block:: html+django + + {% extends 'admin/change_form.html' %} + + {% block admin_change_form_document_ready %} + {{ block.super }} + + {% endblock %} diff --git a/docs/releases/1.9.txt b/docs/releases/1.9.txt index f1eba56a1e..0bb1523f57 100644 --- a/docs/releases/1.9.txt +++ b/docs/releases/1.9.txt @@ -175,6 +175,9 @@ Minor features the display of empty values in admin change list. You can also customize the value for each field. +* Added jQuery events :ref:`when an inline form is added or removed + ` on the change form page. + * The time picker widget includes a '6 p.m' option for consistency of having predefined options every 6 hours. diff --git a/js_tests/admin/inlines.test.js b/js_tests/admin/inlines.test.js index 5fb63813f8..d4c642ec40 100644 --- a/js_tests/admin/inlines.test.js +++ b/js_tests/admin/inlines.test.js @@ -30,3 +30,24 @@ test('add form', function(assert) { addButton.click(); assert.ok(this.table.find('#first-1').hasClass('row2')); }); + +test('add/remove form events', function(assert) { + assert.expect(6); + var $ = django.jQuery; + var $document = $(document); + var addButton = this.table.find('.add-row a'); + $document.on('formset:added', function(event, $row, formsetName) { + assert.ok(true, 'event `formset:added` triggered'); + assert.equal(true, $row.is($('.row2'))); + assert.equal(formsetName, 'first'); + }); + addButton.click(); + var deletedRow = $('.row2'); + var deleteLink = this.table.find('.inline-deletelink'); + $document.on('formset:removed', function(event, $row, formsetName) { + assert.ok(true, 'event `formset:removed` triggered'); + assert.equal(true, $row.is(deletedRow)); + assert.equal(formsetName, 'first'); + }); + deleteLink.click(); +});