Browse Source

Fixed #15760 -- Added JavaScript events for admin inline forms.

ramez 9 years ago
parent
commit
1335aa2fb9

+ 1 - 0
AUTHORS

@@ -588,6 +588,7 @@ answer newbie questions, and generally made Django that much better:
     Rachel Willmer <http://www.willmer.com/kb/>
     Radek Švarz <http://www.svarz.cz/translate/>
     Rajesh Dhawan <rajesh.dhawan@gmail.com>
+    Ramez Ashraf <ramezashraf@gmail.com>
     Ramiro Morales <ramiro@rmorales.net>
     Ram Rachum <ram@rachum.com>
     Randy Barlow <randy@electronsweatshop.com>

+ 2 - 0
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;

+ 9 - 9
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<e.val()-h.val();
-f.each(function(e){b(this).not("."+a.emptyCssClass).addClass(a.formCssClass)});if(f.length&&d){var m;"TR"===f.prop("tagName")?(f=this.eq(-1).children().length,c.append('<tr class="'+a.addCssClass+'"><td colspan="'+f+'"><a href="javascript:void(0)">'+a.addText+"</a></tr>"),m=c.find("tr:last a")):(f.filter(":last").after('<div class="'+a.addCssClass+'"><a href="javascript:void(0)">'+a.addText+"</a></div>"),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('<div><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></div>"):g.is("ul")||g.is("ol")?g.append('<li><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></li>"):g.children(":first").append('<span><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></span>");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<e.val()-d.length)&&m.parent().show();var c,f,h=function(){k(this,a.prefix,c)};c=0;for(f=d.length;c<f;c++)k(b(d).get(c),a.prefix,c),b(d.get(c)).find("*").each(h)});a.added&&a.added(g)})}return this};
-b.fn.formset.defaults={prefix:"form",addText:"add another",deleteText:"remove",addCssClass:"add-row",deleteCssClass:"delete-row",emptyCssClass:"empty-row",formCssClass:"dynamic-form",added:null,removed:null};b.fn.tabularFormset=function(c){var a=b(this),f=function(l){b(a.selector).not(".add-row").removeClass("row1 row2").filter(":even").addClass("row1").end().filter(":odd").addClass("row2")},k=function(){"undefined"!==typeof SelectFilter&&(b(".selectfilter").each(function(a,e){var b=e.name.split("-");
-SelectFilter.init(e.id,b[b.length-1],!1)}),b(".selectfilterstacked").each(function(a,b){var d=b.name.split("-");SelectFilter.init(b.id,d[d.length-1],!0)}))},h=function(a){a.find(".prepopulated_field").each(function(){var e=b(this).find("input, select, textarea"),d=e.data("dependency_list")||[],c=[];b.each(d,function(b,e){c.push("#"+a.find(".field-"+e).find("input, select, textarea").attr("id"))});c.length&&e.prepopulate(c,e.attr("maxlength"))})};a.formset({prefix:c.prefix,addText:c.addText,formCssClass:"dynamic-"+
-c.prefix,deleteCssClass:"inline-deletelink",deleteText:c.deleteText,emptyCssClass:"empty-form",removed:f,added:function(a){h(a);"undefined"!==typeof DateTimeShortcuts&&(b(".datetimeshortcuts").remove(),DateTimeShortcuts.init());k();f(a)}});return a};b.fn.stackedFormset=function(c){var a=b(this),f=function(c){b(a.selector).find(".inline_label").each(function(a){a+=1;b(this).html(b(this).html().replace(/(#\d+)/g,"#"+a))})},k=function(){"undefined"!==typeof SelectFilter&&(b(".selectfilter").each(function(a,
-b){var d=b.name.split("-");SelectFilter.init(b.id,d[d.length-1],!1)}),b(".selectfilterstacked").each(function(a,b){var d=b.name.split("-");SelectFilter.init(b.id,d[d.length-1],!0)}))},h=function(a){a.find(".prepopulated_field").each(function(){var c=b(this).find("input, select, textarea"),d=c.data("dependency_list")||[],f=[];b.each(d,function(b,c){f.push("#"+a.find(".form-row .field-"+c).find("input, select, textarea").attr("id"))});f.length&&c.prepopulate(f,c.attr("maxlength"))})};a.formset({prefix:c.prefix,
-addText:c.addText,formCssClass:"dynamic-"+c.prefix,deleteCssClass:"inline-deletelink",deleteText:c.deleteText,emptyCssClass:"empty-form",removed:f,added:function(a){h(a);"undefined"!==typeof DateTimeShortcuts&&(b(".datetimeshortcuts").remove(),DateTimeShortcuts.init());k();f(a)}});return a}})(django.jQuery);
+(function(b){b.fn.formset=function(d){var a=b.extend({},b.fn.formset.defaults,d),e=b(this);d=e.parent();var k=function(a,f,l){var c=new RegExp("("+f+"-(\\d+|__prefix__))");f=f+"-"+l;b(a).prop("for")&&b(a).prop("for",b(a).prop("for").replace(c,f));a.id&&(a.id=a.id.replace(c,f));a.name&&(a.name=a.name.replace(c,f))},h=b("#id_"+a.prefix+"-TOTAL_FORMS").prop("autocomplete","off"),l=parseInt(h.val(),10),f=b("#id_"+a.prefix+"-MAX_NUM_FORMS").prop("autocomplete","off"),c=""===f.val()||0<f.val()-h.val();
+e.each(function(f){b(this).not("."+a.emptyCssClass).addClass(a.formCssClass)});if(e.length&&c){var m;"TR"===e.prop("tagName")?(e=this.eq(-1).children().length,d.append('<tr class="'+a.addCssClass+'"><td colspan="'+e+'"><a href="javascript:void(0)">'+a.addText+"</a></tr>"),m=d.find("tr:last a")):(e.filter(":last").after('<div class="'+a.addCssClass+'"><a href="javascript:void(0)">'+a.addText+"</a></div>"),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('<div><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></div>"):g.is("ul")||g.is("ol")?g.append('<li><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></li>"):g.children(":first").append('<span><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></span>");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<f.val()-c.length)&&m.parent().show();var d,e,h=function(){k(this,a.prefix,d)};d=0;for(e=c.length;d<e;d++)k(b(c).get(d),a.prefix,
+d),b(c.get(d)).find("*").each(h)});a.added&&a.added(g);b(document).trigger("formset:added",[g,a.prefix])})}return this};b.fn.formset.defaults={prefix:"form",addText:"add another",deleteText:"remove",addCssClass:"add-row",deleteCssClass:"delete-row",emptyCssClass:"empty-row",formCssClass:"dynamic-form",added:null,removed:null};b.fn.tabularFormset=function(d){var a=b(this),e=function(l){b(a.selector).not(".add-row").removeClass("row1 row2").filter(":even").addClass("row1").end().filter(":odd").addClass("row2")},
+k=function(){"undefined"!==typeof SelectFilter&&(b(".selectfilter").each(function(a,b){var c=b.name.split("-");SelectFilter.init(b.id,c[c.length-1],!1)}),b(".selectfilterstacked").each(function(a,b){var c=b.name.split("-");SelectFilter.init(b.id,c[c.length-1],!0)}))},h=function(a){a.find(".prepopulated_field").each(function(){var f=b(this).find("input, select, textarea"),c=f.data("dependency_list")||[],d=[];b.each(c,function(b,c){d.push("#"+a.find(".field-"+c).find("input, select, textarea").attr("id"))});
+d.length&&f.prepopulate(d,f.attr("maxlength"))})};a.formset({prefix:d.prefix,addText:d.addText,formCssClass:"dynamic-"+d.prefix,deleteCssClass:"inline-deletelink",deleteText:d.deleteText,emptyCssClass:"empty-form",removed:e,added:function(a){h(a);"undefined"!==typeof DateTimeShortcuts&&(b(".datetimeshortcuts").remove(),DateTimeShortcuts.init());k();e(a)}});return a};b.fn.stackedFormset=function(d){var a=b(this),e=function(d){b(a.selector).find(".inline_label").each(function(a){a+=1;b(this).html(b(this).html().replace(/(#\d+)/g,
+"#"+a))})},k=function(){"undefined"!==typeof SelectFilter&&(b(".selectfilter").each(function(a,b){var c=b.name.split("-");SelectFilter.init(b.id,c[c.length-1],!1)}),b(".selectfilterstacked").each(function(a,b){var c=b.name.split("-");SelectFilter.init(b.id,c[c.length-1],!0)}))},h=function(a){a.find(".prepopulated_field").each(function(){var d=b(this).find("input, select, textarea"),c=d.data("dependency_list")||[],e=[];b.each(c,function(b,c){e.push("#"+a.find(".form-row .field-"+c).find("input, select, textarea").attr("id"))});
+e.length&&d.prepopulate(e,d.attr("maxlength"))})};a.formset({prefix:d.prefix,addText:d.addText,formCssClass:"dynamic-"+d.prefix,deleteCssClass:"inline-deletelink",deleteText:d.deleteText,emptyCssClass:"empty-form",removed:e,added:function(a){h(a);"undefined"!==typeof DateTimeShortcuts&&(b(".datetimeshortcuts").remove(),DateTimeShortcuts.init());k();e(a)}});return a}})(django.jQuery);

+ 3 - 0
docs/ref/contrib/admin/index.txt

@@ -59,6 +59,7 @@ Other topics
 
    actions
    admindocs
+   javascript
 
 .. seealso::
 
@@ -1882,6 +1883,8 @@ The :doc:`staticfiles app </ref/contrib/staticfiles>` prepends
 ``None``) to any asset paths. The same rules apply as :ref:`regular asset
 definitions on forms <form-asset-paths>`.
 
+.. _contrib-admin-jquery:
+
 jQuery
 ~~~~~~
 

+ 75 - 0
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
+<contrib-admin-jquery>`.
+
+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 }}
+    <script type="text/javascript">
+    (function($) {
+        $(document).on('formset:added', function(event, $row, formsetName) {
+            if (formsetName == 'author_set') {
+                // Do something
+            }
+        });
+
+        $(document).on('formset:removed', function(event, $row, formsetName) {
+            // Row removed
+        });
+    })(django.jQuery);
+    </script>
+    {% 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 }}
+    <script type="text/javascript">
+        django.jQuery(document).on('formset:added', function(event, $row, formsetName) {
+            // Row added
+        });
+
+        django.jQuery(document).on('formset:removed', function(event, $row, formsetName) {
+            // Row removed
+        });
+    </script>
+    {% endblock %}

+ 3 - 0
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
+  <admin-javascript-inline-form-events>` on the change form page.
+
 * The time picker widget includes a '6 p.m' option for consistency of having
   predefined options every 6 hours.
 

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