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/>
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>

View File

@ -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;

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();
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);

View File

@ -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
~~~~~~

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
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.

View File

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