'+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();
+});
| |