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

This commit is contained in:
ramez 2015-06-18 19:27:58 +03:00 committed by Tim Graham
parent 65a1055a36
commit 1335aa2fb9
7 changed files with 114 additions and 9 deletions

View File

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

View File

@ -98,6 +98,7 @@
if (options.removed) { if (options.removed) {
options.removed(row); options.removed(row);
} }
$(document).trigger('formset:removed', [row, options.prefix]);
// Update the TOTAL_FORMS form count. // Update the TOTAL_FORMS form count.
var forms = $("." + options.formCssClass); var forms = $("." + options.formCssClass);
$("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length); $("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length);
@ -120,6 +121,7 @@
if (options.added) { if (options.added) {
options.added(row); options.added(row);
} }
$(document).trigger('formset:added', [row, options.prefix]);
}); });
} }
return this; return this;

View File

@ -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(); (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();
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+ 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=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, "-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(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}; 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,
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("-"); 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")},
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-"+ 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"))});
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, 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,
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, "#"+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"))});
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); 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);

View File

@ -59,6 +59,7 @@ Other topics
actions actions
admindocs admindocs
javascript
.. seealso:: .. 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 ``None``) to any asset paths. The same rules apply as :ref:`regular asset
definitions on forms <form-asset-paths>`. definitions on forms <form-asset-paths>`.
.. _contrib-admin-jquery:
jQuery jQuery
~~~~~~ ~~~~~~

View File

@ -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 %}

View File

@ -175,6 +175,9 @@ Minor features
the display of empty values in admin change list. You can also customize the the display of empty values in admin change list. You can also customize the
value for each field. 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 * The time picker widget includes a '6 p.m' option for consistency of having
predefined options every 6 hours. predefined options every 6 hours.

View File

@ -30,3 +30,24 @@ test('add form', function(assert) {
addButton.click(); addButton.click();
assert.ok(this.table.find('#first-1').hasClass('row2')); 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();
});