Sin descripción

jquery.grp_inline.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. /**
  2. * GRAPPELLI INLINES
  3. * jquery-plugin for inlines (stacked and tabular)
  4. */
  5. (function($) {
  6. $.fn.grp_inline = function(options) {
  7. var defaults = {
  8. prefix: "form", // The form prefix for your django formset
  9. addText: "add another", // Text for the add link
  10. deleteText: "remove", // Text for the delete link
  11. addCssClass: "grp-add-handler", // CSS class applied to the add link
  12. removeCssClass: "grp-remove-handler", // CSS class applied to the remove link
  13. deleteCssClass: "grp-delete-handler", // CSS class applied to the delete link
  14. emptyCssClass: "grp-empty-form", // CSS class applied to the empty row
  15. formCssClass: "grp-dynamic-form", // CSS class applied to each form in a formset
  16. predeleteCssClass: "grp-predelete",
  17. onBeforeInit: function(form) {}, // Function called before a form is initialized
  18. onBeforeAdded: function(inline) {}, // Function called before a form is added
  19. onBeforeRemoved: function(form) {}, // Function called before a form is removed
  20. onBeforeDeleted: function(form) {}, // Function called before a form is deleted
  21. onAfterInit: function(form) {}, // Function called after a form has been initialized
  22. onAfterAdded: function(form) {}, // Function called after a form has been added
  23. onAfterRemoved: function(inline) {}, // Function called after a form has been removed
  24. onAfterDeleted: function(form) {} // Function called after a form has been deleted
  25. };
  26. options = $.extend(defaults, options);
  27. return this.each(function() {
  28. var inline = $(this); // the current inline node
  29. var totalForms = inline.find("#id_" + options.prefix + "-TOTAL_FORMS");
  30. // set autocomplete to off in order to prevent the browser from keeping the current value after reload
  31. totalForms.attr("autocomplete", "off");
  32. // init inline and add-buttons
  33. initInlineForms(inline, options);
  34. initAddButtons(inline, options);
  35. // button handlers
  36. addButtonHandler(inline.find("a." + options.addCssClass), options);
  37. removeButtonHandler(inline.find("a." + options.removeCssClass), options);
  38. deleteButtonHandler(inline.find("a." + options.deleteCssClass), options);
  39. });
  40. };
  41. getFormIndex = function(elem, options, regex) {
  42. var formIndex = elem.find("[id^='id_" + options.prefix + "']").attr('id');
  43. if (!formIndex) { return -1; }
  44. return parseInt(regex.exec(formIndex)[1], 10);
  45. };
  46. updateFormIndex = function(elem, options, replace_regex, replace_with) {
  47. elem.find(':input,span,table,iframe,label,a,ul,p,img,div').each(function() {
  48. var node = $(this),
  49. node_id = node.attr('id'),
  50. node_name = node.attr('name'),
  51. node_for = node.attr('for'),
  52. node_href = node.attr("href"),
  53. node_class = node.attr("class"),
  54. node_onclick = node.attr("onclick");
  55. if (node_id) { node.attr('id', node_id.replace(replace_regex, replace_with)); }
  56. if (node_name) { node.attr('name', node_name.replace(replace_regex, replace_with)); }
  57. if (node_for) { node.attr('for', node_for.replace(replace_regex, replace_with)); }
  58. if (node_href) { node.attr('href', node_href.replace(replace_regex, replace_with)); }
  59. if (node_class) { node.attr('class', node_class.replace(replace_regex, replace_with)); }
  60. if (node_onclick) { node.attr('onclick', node_onclick.replace(replace_regex, replace_with)); }
  61. });
  62. // update prepopulate ids for function initPrepopulatedFields
  63. elem.find('.prepopulated_field').each(function() {
  64. var dependency_ids = $(this).data('dependency_ids') || [],
  65. dependency_ids_updated = [];
  66. $.each(dependency_ids, function(i, id) {
  67. dependency_ids_updated.push(id.replace(replace_regex, replace_with));
  68. });
  69. $(this).data('dependency_ids', dependency_ids_updated);
  70. });
  71. };
  72. var initPrepopulatedFields = function(elem, options) {
  73. elem.find('.prepopulated_field').each(function() {
  74. var dependency_ids = $(this).data('dependency_ids') || [];
  75. $(this).prepopulate(dependency_ids, $(this).attr('maxlength'));
  76. });
  77. };
  78. initInlineForms = function(elem, options) {
  79. elem.find("div.grp-module").each(function() {
  80. var form = $(this);
  81. // callback
  82. options.onBeforeInit(form);
  83. // add options.formCssClass to all forms in the inline
  84. // except table/theader/add-item
  85. if (form.attr('id') !== "") {
  86. form.not("." + options.emptyCssClass).not(".grp-table").not(".grp-thead").not(".add-item").addClass(options.formCssClass);
  87. }
  88. // add options.predeleteCssClass to forms with the delete checkbox checked
  89. form.find("li.grp-delete-handler-container input").each(function() {
  90. if ($(this).is(":checked") && form.hasClass("has_original")) {
  91. form.toggleClass(options.predeleteCssClass);
  92. }
  93. });
  94. // callback
  95. options.onAfterInit(form);
  96. });
  97. };
  98. initAddButtons = function(elem, options) {
  99. var totalForms = elem.find("#id_" + options.prefix + "-TOTAL_FORMS");
  100. var maxForms = elem.find("#id_" + options.prefix + "-MAX_NUM_FORMS");
  101. var addButtons = elem.find("a." + options.addCssClass);
  102. // hide add button in case we've hit the max, except we want to add infinitely
  103. if ((maxForms.val() !== '') && (maxForms.val()-totalForms.val()) <= 0) {
  104. hideAddButtons(elem, options);
  105. }
  106. };
  107. addButtonHandler = function(elem, options) {
  108. elem.on("click", function(e) {
  109. var inline = elem.parents(".grp-group"),
  110. totalForms = inline.find("#id_" + options.prefix + "-TOTAL_FORMS"),
  111. maxForms = inline.find("#id_" + options.prefix + "-MAX_NUM_FORMS"),
  112. addButtons = inline.find("a." + options.addCssClass),
  113. empty_template = inline.find("#" + options.prefix + "-empty");
  114. // callback
  115. options.onBeforeAdded(inline);
  116. // create new form
  117. var index = parseInt(totalForms.val(), 10),
  118. form = empty_template.clone(true);
  119. form.removeClass(options.emptyCssClass)
  120. .attr("id", empty_template.attr('id').replace("-empty", index));
  121. // update form index
  122. var re = /__prefix__/g;
  123. updateFormIndex(form, options, re, index);
  124. // after "__prefix__" strings has been substituted with the number
  125. // of the inline, we can add the form to DOM, not earlier.
  126. // This way we can support handlers that track live element
  127. // adding/removing, like those used in django-autocomplete-light
  128. form.insertBefore(empty_template)
  129. .addClass(options.formCssClass);
  130. // update total forms
  131. totalForms.val(index + 1);
  132. // hide add button in case we've hit the max, except we want to add infinitely
  133. if ((maxForms.val() !== 0) && (maxForms.val() !== "") && (maxForms.val() - totalForms.val()) <= 0) {
  134. hideAddButtons(inline, options);
  135. }
  136. // prepopulate fields
  137. initPrepopulatedFields(form, options);
  138. // select2: we need to use the django namespace here
  139. django.jQuery(document).trigger('formset:added', [django.jQuery(form), options.prefix]);
  140. // callback
  141. options.onAfterAdded(form);
  142. });
  143. };
  144. removeButtonHandler = function(elem, options) {
  145. elem.on("click", function() {
  146. var inline = elem.parents(".grp-group"),
  147. form = $(this).parents("." + options.formCssClass).first(),
  148. totalForms = inline.find("#id_" + options.prefix + "-TOTAL_FORMS"),
  149. maxForms = inline.find("#id_" + options.prefix + "-MAX_NUM_FORMS"),
  150. re = /-(\d+)-/,
  151. removedFormIndex = getFormIndex(form, options, re);
  152. // callback
  153. options.onBeforeRemoved(form);
  154. // remove form
  155. form.remove();
  156. // update total forms
  157. totalForms.val(parseInt(totalForms.val(), 10) - 1);
  158. // show add button in case we've dropped below max
  159. if ((maxForms.val() !== 0) && (maxForms.val() - totalForms.val()) > 0) {
  160. showAddButtons(inline, options);
  161. }
  162. // update form index (only forms with a higher index than the removed form)
  163. inline.find("." + options.formCssClass).each(function() {
  164. var form = $(this),
  165. formIndex = getFormIndex(form, options, re);
  166. if (formIndex > removedFormIndex) {
  167. updateFormIndex(form, options, re, "-" + (formIndex - 1) + "-");
  168. }
  169. });
  170. // callback
  171. options.onAfterRemoved(inline);
  172. });
  173. };
  174. deleteButtonHandler = function(elem, options) {
  175. elem.on("click", function() {
  176. var deleteInput = $(this).prev(),
  177. form = $(this).parents("." + options.formCssClass).first();
  178. // callback
  179. options.onBeforeDeleted(form);
  180. // toggle options.predeleteCssClass and toggle checkbox
  181. if (form.hasClass("has_original")) {
  182. form.toggleClass(options.predeleteCssClass);
  183. if (deleteInput.prop("checked")) {
  184. deleteInput.removeAttr("checked");
  185. } else {
  186. deleteInput.prop("checked", true);
  187. }
  188. }
  189. // callback
  190. options.onAfterDeleted(form);
  191. });
  192. };
  193. hideAddButtons = function(elem, options) {
  194. var addButtons = elem.find("a." + options.addCssClass);
  195. addButtons.hide().parents('.grp-add-item').hide();
  196. // last row with stacked/tabular
  197. addButtons.closest('.grp-module.grp-transparent').hide();
  198. };
  199. showAddButtons = function(elem, options) {
  200. var addButtons = elem.find("a." + options.addCssClass);
  201. addButtons.show().parents('.grp-add-item').show();
  202. // last row with stacked/tabular
  203. addButtons.closest('.grp-module.grp-transparent').show();
  204. };
  205. })(grp.jQuery);