All files for deployment ready and tested - further development in this repo.
This commit is contained in:
347
nested_admin/dist/nested_admin.css
vendored
Normal file
347
nested_admin/dist/nested_admin.css
vendored
Normal file
@@ -0,0 +1,347 @@
|
||||
/*!***********************************************************************************************************************************************************************************************************************!*\
|
||||
!*** css ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].use[2]!./node_modules/sass-loader/dist/cjs.js!./nested_admin/static/nested_admin/src/nested_admin.scss ***!
|
||||
\***********************************************************************************************************************************************************************************************************************/
|
||||
/* stylelint-disable no-descending-specificity */
|
||||
.djn-group .djn-group-nested {
|
||||
float: none;
|
||||
width: auto;
|
||||
margin: 0 10px;
|
||||
background: transparent; }
|
||||
|
||||
.djn-group-nested.grp-stacked h2.djn-collapse-handler,
|
||||
.djn-group-nested.grp-stacked > .grp-tools {
|
||||
display: none; }
|
||||
|
||||
.djn-group-nested {
|
||||
border-color: transparent; }
|
||||
|
||||
.grp-tools span.delete {
|
||||
cursor: auto !important; }
|
||||
|
||||
.djn-group-nested .djn-items .inline-related {
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px; }
|
||||
#grp-content .djn-group-nested .djn-items .inline-related {
|
||||
margin-bottom: 5px;
|
||||
border: 1px solid #a7a7a7; }
|
||||
#grp-content .djn-group-nested .djn-items .inline-related.djn-item-dragging {
|
||||
border: 0; }
|
||||
.djn-group-nested .djn-items .inline-related:first-child {
|
||||
margin-top: 0; }
|
||||
.djn-group-nested .djn-items .inline-related.last-related {
|
||||
margin-bottom: 0; }
|
||||
|
||||
.djn-group-nested div.items .module:first-child {
|
||||
margin-top: 0 !important; }
|
||||
|
||||
.nested-placeholder,
|
||||
.djn-group .ui-sortable-placeholder {
|
||||
margin-bottom: 5px;
|
||||
background: #9f9f9f !important; }
|
||||
|
||||
.djn-group .ui-nestedsortable-error,
|
||||
.djn-group .ui-nestedSortable-error {
|
||||
background: #9f6464 !important; }
|
||||
|
||||
.ui-sortable .grp-module.ui-sortable-placeholder.ui-nestedSortable-error {
|
||||
background-color: #9f6464 !important; }
|
||||
|
||||
.djn-items {
|
||||
position: relative;
|
||||
min-height: 0;
|
||||
overflow: visible; }
|
||||
|
||||
.djn-item {
|
||||
overflow: visible; }
|
||||
|
||||
.djn-item.djn-no-drag:first-child {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
height: 19px; }
|
||||
.djn-item.djn-no-drag:first-child + .djn-item.ui-sortable-helper,
|
||||
.djn-item.djn-no-drag:first-child + .djn-item-dragging {
|
||||
margin-top: 0; }
|
||||
|
||||
.djn-item-dragging {
|
||||
height: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
border: 0; }
|
||||
|
||||
.djn-tbody.djn-item-dragging {
|
||||
display: none !important; }
|
||||
|
||||
.djn-tbody.ui-sortable-placeholder td {
|
||||
background: #fbfad0; }
|
||||
|
||||
.djn-collapse-handler-verbose-name {
|
||||
display: inline; }
|
||||
|
||||
#grp-content .grp-tabular .grp-table .grp-tbody .grp-th,
|
||||
#grp-content .grp-tabular .grp-table .grp-tbody .grp-td {
|
||||
vertical-align: top;
|
||||
overflow: visible; }
|
||||
|
||||
#grp-content .grp-tabular .grp-table .grp-tbody .grp-tr > td.original:first-child {
|
||||
width: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: #eee; }
|
||||
|
||||
#grp-content .grp-tabular .grp-table .grp-tbody .grp-tr.djn-has-inlines .grp-td {
|
||||
border-bottom: 0 !important; }
|
||||
|
||||
#grp-content .grp-tabular .grp-table .grp-thead .grp-th {
|
||||
border-radius: 0;
|
||||
border-top: 0;
|
||||
border-bottom: 0;
|
||||
line-height: 16px;
|
||||
color: #aaa;
|
||||
font-weight: bold; }
|
||||
|
||||
#grp-content table.djn-table thead > tr > th {
|
||||
font-size: 11px;
|
||||
line-height: inherit; }
|
||||
|
||||
#grp-content .grp-tabular .grp-table.djn-table .grp-thead > .grp-tr > .grp-th {
|
||||
padding-top: 1px;
|
||||
padding-bottom: 1px; }
|
||||
|
||||
#grp-content
|
||||
.grp-tabular
|
||||
.grp-table.djn-table
|
||||
.grp-thead
|
||||
> .grp-tr
|
||||
> .grp-th:last-of-type {
|
||||
border-right: 0; }
|
||||
|
||||
#grp-content
|
||||
.grp-tabular
|
||||
.grp-table.djn-table
|
||||
.grp-tbody
|
||||
> .grp-tr
|
||||
> .grp-td:first-of-type {
|
||||
border-left: 1px solid #d4d4d4 !important; }
|
||||
|
||||
table.djn-table.grp-table td div.grp-readonly,
|
||||
table.djn-table.grp-table th div.grp-readonly {
|
||||
margin: 0 !important; }
|
||||
|
||||
.grp-tabular.djn-tabular td.grp-td ul.errorlist {
|
||||
margin: 0 !important; }
|
||||
|
||||
table.djn-table.grp-table td div.grp-readonly:empty,
|
||||
table.djn-table.grp-table th div.grp-readonly:empty {
|
||||
margin-bottom: -5px !important; }
|
||||
|
||||
table.djn-table.grp-table td > input[type="checkbox"],
|
||||
table.djn-table.grp-table td > input[type="radio"],
|
||||
table.djn-table.grp-table th > input[type="checkbox"],
|
||||
table.djn-table.grp-table th > input[type="radio"] {
|
||||
margin: 3px 0.5ex !important;
|
||||
margin: revert !important; }
|
||||
|
||||
table.djn-table.grp-table td > textarea,
|
||||
table.djn-table.grp-table th > textarea {
|
||||
margin: 0 !important; }
|
||||
|
||||
table.djn-table.grp-table td > input[type="text"],
|
||||
table.djn-table.grp-table td > input[type="password"],
|
||||
table.djn-table.grp-table td > input[type="url"],
|
||||
table.djn-table.grp-table td > input[type="email"],
|
||||
table.djn-table.grp-table td > input[type="number"],
|
||||
table.djn-table.grp-table td > input[type="button"],
|
||||
table.djn-table.grp-table td > select,
|
||||
table.djn-table.grp-table td p input[type="text"],
|
||||
table.djn-table.grp-table td p input[type="url"],
|
||||
table.djn-table.grp-table td p input[type="email"],
|
||||
table.djn-table.grp-table td p input[type="number"],
|
||||
table.djn-table.grp-table td p > input[type="button"],
|
||||
table.djn-table.grp-table th > input[type="text"],
|
||||
table.djn-table.grp-table th > input[type="password"],
|
||||
table.djn-table.grp-table th > input[type="url"],
|
||||
table.djn-table.grp-table th > input[type="email"],
|
||||
table.djn-table.grp-table th > input[type="number"],
|
||||
table.djn-table.grp-table th > input[type="button"],
|
||||
table.djn-table.grp-table th > select,
|
||||
table.djn-table.grp-table th p input[type="text"],
|
||||
table.djn-table.grp-table th p input[type="url"],
|
||||
table.djn-table.grp-table th p input[type="email"],
|
||||
table.djn-table.grp-table th p input[type="number"],
|
||||
table.djn-table.grp-table th p > input[type="button"] {
|
||||
vertical-align: middle;
|
||||
margin-top: 0 !important;
|
||||
margin-bottom: 0 !important; }
|
||||
|
||||
.djn-empty-form,
|
||||
.djn-empty-form * {
|
||||
display: none !important; }
|
||||
|
||||
#content.colM .inline-group .tabular .ui-sortable-placeholder tr.has_original td {
|
||||
padding: 1px; }
|
||||
|
||||
#content.colM .inline-group.djn-group ul.tools {
|
||||
height: 0; }
|
||||
|
||||
#content.colM .djn-item.module {
|
||||
margin-bottom: 0; }
|
||||
|
||||
#content.colM tr.djn-has-inlines td {
|
||||
border-bottom: 1px solid #fff; }
|
||||
|
||||
#content.colM td.original {
|
||||
width: 0;
|
||||
padding: 2px 0 0 0; }
|
||||
|
||||
#content.colM td.original.is-sortable {
|
||||
position: relative;
|
||||
width: 15px; }
|
||||
|
||||
#content.colM td.original.is-sortable .djn-drag-handler {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 0;
|
||||
display: block;
|
||||
width: 10px;
|
||||
height: 20px;
|
||||
margin: 5px;
|
||||
cursor: move;
|
||||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAyCAYAAABcfPsmAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAE5JREFUeNrs0zEKACAMBEEDST6X9+Z92lpYHNgI7naBK9KMdfcch6rK9lvdeWYOJXXnESEN1d2HH9J9hhSkEFKQQkhBClKQghSkPNYSYADFZiuygfao+AAAAABJRU5ErkJggg==) no-repeat top left;
|
||||
background-size: 10px 25px;
|
||||
cursor: -webkit-grab;
|
||||
cursor: grab;
|
||||
cursor: -moz- -webkit-grab;
|
||||
cursor: -moz- grab;
|
||||
cursor: -webkit- -webkit-grab;
|
||||
cursor: -webkit- grab; }
|
||||
|
||||
#content.colM td.original.is-sortable .djn-drag-handler:active {
|
||||
cursor: -webkit-grabbing;
|
||||
cursor: grabbing;
|
||||
cursor: -moz- -webkit-grabbing;
|
||||
cursor: -moz- grabbing;
|
||||
cursor: -webkit- -webkit-grabbing;
|
||||
cursor: -webkit- grabbing; }
|
||||
|
||||
#content.colM td.original.is-sortable p + .djn-drag-handler {
|
||||
top: 20px; }
|
||||
|
||||
#content.colM td.original.is-sortable p {
|
||||
top: 0;
|
||||
left: 19px;
|
||||
white-space: nowrap; }
|
||||
|
||||
#content.colM fieldset.has-inlines > .djn-form-row-last {
|
||||
border-bottom: 0; }
|
||||
|
||||
.polymorphic-add-choice .grp-tools {
|
||||
overflow: visible; }
|
||||
|
||||
.polymorphic-add-choice .grp-tools li {
|
||||
float: none; }
|
||||
|
||||
.polymorphic-add-choice .grp-tools li:first-child,
|
||||
.polymorphic-add-choice .grp-tools li:last-child {
|
||||
padding: 4px 8px; }
|
||||
|
||||
.polymorphic-add-choice .grp-tools a {
|
||||
width: auto;
|
||||
height: auto; }
|
||||
|
||||
.polymorphic-add-choice .grp-tools > li > a {
|
||||
min-width: 24px;
|
||||
min-height: 24px; }
|
||||
|
||||
.polymorphic-add-choice .grp-tools .polymorphic-type-menu {
|
||||
right: 0.5em;
|
||||
left: auto; }
|
||||
|
||||
.grp-tools.grp-related-widget-tools a.add-another {
|
||||
top: 0;
|
||||
margin: 0; }
|
||||
|
||||
.grp-td > .grp-related-widget-wrapper .grp-related-widget-tools {
|
||||
overflow: visible;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex; }
|
||||
|
||||
.select2-container + .grp-tools.grp-related-widget-tools {
|
||||
position: relative;
|
||||
right: 0; }
|
||||
|
||||
#grp-content .grp-group > .grp-items > .grp-module > .grp-tabular {
|
||||
background: #fff;
|
||||
border: 2px solid #ccc;
|
||||
margin-bottom: 5px; }
|
||||
#grp-content .grp-group > .grp-items > .grp-module > .grp-tabular::after {
|
||||
content: "";
|
||||
display: block;
|
||||
clear: both; }
|
||||
|
||||
table.grp-table.djn-table td.djn-td > input[type="text"],
|
||||
table.grp-table.djn-table td.djn-td > input[type="password"],
|
||||
table.grp-table.djn-table td.djn-td > input[type="url"],
|
||||
table.grp-table.djn-table td.djn-td > input[type="email"],
|
||||
table.grp-table.djn-table td.djn-td > input[type="number"],
|
||||
table.grp-table.djn-table td.djn-td > input[type="button"],
|
||||
table.grp-table.djn-table td.djn-td > select,
|
||||
table.grp-table.djn-table td.djn-td p input[type="text"],
|
||||
table.grp-table.djn-table td.djn-td p input[type="url"],
|
||||
table.grp-table.djn-table td.djn-td p input[type="email"],
|
||||
table.grp-table.djn-table td.djn-td p input[type="number"],
|
||||
table.grp-table.djn-table td.djn-td p > input[type="button"],
|
||||
table.grp-table.djn-table td.djn-td div.grp-related-widget-wrapper,
|
||||
table.grp-table.djn-table th.djn-th > input[type="text"],
|
||||
table.grp-table.djn-table th.djn-th > input[type="password"],
|
||||
table.grp-table.djn-table th.djn-th > input[type="url"],
|
||||
table.grp-table.djn-table th.djn-th > input[type="email"],
|
||||
table.grp-table.djn-table th.djn-th > input[type="number"],
|
||||
table.grp-table.djn-table th.djn-th > input[type="button"],
|
||||
table.grp-table.djn-table th.djn-th > select,
|
||||
table.grp-table.djn-table th.djn-th p input[type="text"],
|
||||
table.grp-table.djn-table th.djn-th p input[type="url"],
|
||||
table.grp-table.djn-table th.djn-th p input[type="email"],
|
||||
table.grp-table.djn-table th.djn-th p input[type="number"],
|
||||
table.grp-table.djn-table th.djn-th p > input[type="button"],
|
||||
table.grp-table.djn-table th.djn-th div.grp-related-widget-wrapper {
|
||||
vertical-align: baseline;
|
||||
margin-top: 0 !important;
|
||||
margin-bottom: 0 !important; }
|
||||
|
||||
table.grp-table.djn-table td.djn-td a.fb_show,
|
||||
table.grp-table.djn-table td.djn-td a.related-lookup,
|
||||
table.grp-table.djn-table td.djn-td .ui-datepicker-trigger,
|
||||
table.grp-table.djn-table td.djn-td .ui-timepicker-trigger,
|
||||
table.grp-table.djn-table th.djn-th a.fb_show,
|
||||
table.grp-table.djn-table th.djn-th a.related-lookup,
|
||||
table.grp-table.djn-table th.djn-th .ui-datepicker-trigger,
|
||||
table.grp-table.djn-table th.djn-th .ui-timepicker-trigger {
|
||||
margin: 0 0 0 -25px !important; }
|
||||
|
||||
table.grp-table.djn-table td.djn-td .grp-autocomplete-wrapper-m2m,
|
||||
table.grp-table.djn-table td.djn-td .grp-autocomplete-wrapper-fk,
|
||||
table.grp-table.djn-table th.djn-th .grp-autocomplete-wrapper-m2m,
|
||||
table.grp-table.djn-table th.djn-th .grp-autocomplete-wrapper-fk {
|
||||
margin: 0 !important; }
|
||||
|
||||
table.grp-table.djn-table td.djn-td > input[type="file"],
|
||||
table.grp-table.djn-table td.djn-td > input[type="checkbox"],
|
||||
table.grp-table.djn-table td.djn-td > input[type="radio"],
|
||||
table.grp-table.djn-table td.djn-td > select,
|
||||
table.grp-table.djn-table td.djn-td p input[type="text"],
|
||||
table.grp-table.djn-table th.djn-th > input[type="file"],
|
||||
table.grp-table.djn-table th.djn-th > input[type="checkbox"],
|
||||
table.grp-table.djn-table th.djn-th > input[type="radio"],
|
||||
table.grp-table.djn-table th.djn-th > select,
|
||||
table.grp-table.djn-table th.djn-th p input[type="text"] {
|
||||
margin-top: 0 !important;
|
||||
margin-bottom: 0 !important; }
|
||||
|
||||
|
||||
/*# sourceMappingURL=nested_admin.css.map*/
|
||||
1
nested_admin/dist/nested_admin.css.map
vendored
Normal file
1
nested_admin/dist/nested_admin.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
7129
nested_admin/dist/nested_admin.js
vendored
Normal file
7129
nested_admin/dist/nested_admin.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
nested_admin/dist/nested_admin.js.map
vendored
Normal file
1
nested_admin/dist/nested_admin.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
4
nested_admin/dist/nested_admin.min.css
vendored
Normal file
4
nested_admin/dist/nested_admin.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
nested_admin/dist/nested_admin.min.css.map
vendored
Normal file
1
nested_admin/dist/nested_admin.min.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
3
nested_admin/dist/nested_admin.min.js
vendored
Normal file
3
nested_admin/dist/nested_admin.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
15
nested_admin/dist/nested_admin.min.js.LICENSE.txt
vendored
Normal file
15
nested_admin/dist/nested_admin.min.js.LICENSE.txt
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
/*!
|
||||
* jQuery UI Sortable @VERSION
|
||||
* http://jqueryui.com
|
||||
*
|
||||
* Copyright 2012 jQuery Foundation and other contributors
|
||||
* Released under the MIT license.
|
||||
* http://jquery.org/license
|
||||
*
|
||||
* http://api.jqueryui.com/sortable/
|
||||
*
|
||||
* Depends:
|
||||
* jquery.ui.core.js
|
||||
* jquery.ui.mouse.js
|
||||
* jquery.ui.widget.js
|
||||
*/
|
||||
1
nested_admin/dist/nested_admin.min.js.map
vendored
Normal file
1
nested_admin/dist/nested_admin.min.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
nested_admin/src/drag-handle.png
Normal file
BIN
nested_admin/src/drag-handle.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 172 B |
20
nested_admin/src/nested-admin/django$.js
Normal file
20
nested_admin/src/nested-admin/django$.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import $ from "jquery";
|
||||
|
||||
/**
|
||||
* Converts a grp.jQuery instance to a django.jQuery instance.
|
||||
*/
|
||||
function django$($sel) {
|
||||
if (typeof window.grp === "undefined") {
|
||||
return $($sel);
|
||||
}
|
||||
if (window.grp.jQuery.fn.init === $.fn.init) {
|
||||
return $($sel);
|
||||
}
|
||||
const $djangoSel = $($sel);
|
||||
if ($sel.prevObject) {
|
||||
$djangoSel.prevObject = django$($sel.prevObject);
|
||||
}
|
||||
return $djangoSel;
|
||||
}
|
||||
|
||||
export default django$;
|
||||
23
nested_admin/src/nested-admin/grp$.js
Normal file
23
nested_admin/src/nested-admin/grp$.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import $ from "jquery";
|
||||
|
||||
/**
|
||||
* For grappelli 2.14, converts a django.jQuery instance to a grp.jQuery
|
||||
* instance. Otherwise (if grappelli is not present, or for grappelli <= 2.13,
|
||||
* where the grappelli jQuery instance is the same as django's), returns the
|
||||
* object that was passed in, unchanged.
|
||||
*/
|
||||
function grp$($sel) {
|
||||
if (typeof window.grp === "undefined") {
|
||||
return $($sel);
|
||||
}
|
||||
if (window.grp.jQuery.fn.init === $.fn.init) {
|
||||
return $($sel);
|
||||
}
|
||||
const $grpSel = window.grp.jQuery($sel);
|
||||
if ($sel.prevObject) {
|
||||
$grpSel.prevObject = grp$($sel.prevObject);
|
||||
}
|
||||
return $grpSel;
|
||||
}
|
||||
|
||||
export default grp$;
|
||||
54
nested_admin/src/nested-admin/index.js
Normal file
54
nested_admin/src/nested-admin/index.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import $ from "jquery";
|
||||
import * as grappelli from "grappelli";
|
||||
import DJNesting from "./utils";
|
||||
import DjangoFormset from "./jquery.djangoformset";
|
||||
|
||||
DJNesting.DjangoFormset = DjangoFormset;
|
||||
|
||||
$(document).ready(function () {
|
||||
// Remove the border on any empty fieldsets
|
||||
$("fieldset.grp-module, fieldset.module")
|
||||
.filter(function (i, element) {
|
||||
return element.childNodes.length == 0;
|
||||
})
|
||||
.css("border-width", "0");
|
||||
|
||||
// Set predelete class on any form elements with the DELETE input checked.
|
||||
// These can occur on forms rendered after a validation error.
|
||||
$('input[name$="-DELETE"]:checked')
|
||||
.not('[name*="__prefix__"]')
|
||||
.closest(".djn-inline-form")
|
||||
.addClass("grp-predelete");
|
||||
|
||||
$(document).on(
|
||||
"djnesting:initialized djnesting:mutate",
|
||||
function onMutate(e, $inline) {
|
||||
var $items = $inline.find(
|
||||
"> .djn-items, > .tabular > .module > .djn-items"
|
||||
);
|
||||
var $rows = $items.children(".djn-tbody");
|
||||
$rows.removeClass("row1 row2");
|
||||
$rows.each(function (i, row) {
|
||||
var n = 1 + (i % 2);
|
||||
$(row).addClass("row" + n);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// Register the nested formset on top level djnesting-stacked elements.
|
||||
// It will handle recursing down the nested inlines.
|
||||
$(".djn-group-root").each(function (i, rootGroup) {
|
||||
$(rootGroup).djangoFormset();
|
||||
});
|
||||
|
||||
$("form").on("submit.djnesting", function (e) {
|
||||
$(".djn-group").each(function () {
|
||||
DJNesting.updatePositions($(this).djangoFormsetPrefix());
|
||||
$(document).trigger("djnesting:mutate", [
|
||||
$(this).djangoFormset().$inline,
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
export default DJNesting;
|
||||
712
nested_admin/src/nested-admin/jquery.djangoformset.js
Normal file
712
nested_admin/src/nested-admin/jquery.djangoformset.js
Normal file
@@ -0,0 +1,712 @@
|
||||
import $ from "jquery";
|
||||
|
||||
import regexQuote from "./regexquote";
|
||||
import DJNesting from "./utils";
|
||||
import * as grappelli from "grappelli";
|
||||
import grp from "grp";
|
||||
import grp$ from "./grp$";
|
||||
import django$ from "./django$";
|
||||
|
||||
var pluginName = "djangoFormset";
|
||||
|
||||
class DjangoFormset {
|
||||
constructor(inline) {
|
||||
this.opts = {
|
||||
emptyClass: "empty-form grp-empty-form djn-empty-form",
|
||||
predeleteClass: "grp-predelete",
|
||||
};
|
||||
this.$inline = $(inline);
|
||||
this.prefix = this.$inline.djangoFormsetPrefix();
|
||||
this._$totalForms = this.$inline.find(
|
||||
"#id_" + this.prefix + "-TOTAL_FORMS"
|
||||
);
|
||||
this._$totalForms.attr("autocomplete", "off");
|
||||
this._$template = $("#" + this.prefix + "-empty");
|
||||
|
||||
var inlineModelClassName = this.$inline.djnData("inlineModel");
|
||||
const nestingLevel = this.$inline.djnData("nestingLevel");
|
||||
const handlerSelector = `.djn-model-${inlineModelClassName}.djn-level-${nestingLevel}`;
|
||||
|
||||
this.opts = $.extend({}, this.opts, {
|
||||
childTypes: this.$inline.data("inlineFormset").options.childTypes,
|
||||
formsetFkModel: this.$inline.djnData("formsetFkModel"),
|
||||
addButtonSelector: ".djn-add-handler" + handlerSelector,
|
||||
removeButtonSelector: ".djn-remove-handler" + handlerSelector,
|
||||
deleteButtonSelector: ".djn-delete-handler" + handlerSelector,
|
||||
formClass:
|
||||
"dynamic-form grp-dynamic-form djn-dynamic-form-" +
|
||||
inlineModelClassName,
|
||||
formClassSelector: ".djn-dynamic-form-" + inlineModelClassName,
|
||||
});
|
||||
|
||||
DJNesting.initRelatedFields(this.prefix, this.$inline.djnData());
|
||||
DJNesting.initAutocompleteFields(this.prefix, this.$inline.djnData());
|
||||
|
||||
if (this.opts.childTypes) {
|
||||
this._setupPolymorphic();
|
||||
}
|
||||
this._bindEvents();
|
||||
|
||||
this._initializeForms();
|
||||
|
||||
this.$inline
|
||||
.find('.djn-items:not([id*="-empty"])')
|
||||
.trigger("djnesting:init");
|
||||
|
||||
// initialize nested formsets
|
||||
this.$inline
|
||||
.find(
|
||||
'.djn-group[id$="-group"][id^="' +
|
||||
this.prefix +
|
||||
'"][data-inline-formset]:not([id*="-empty"])'
|
||||
)
|
||||
.each(function () {
|
||||
$(this)[pluginName]();
|
||||
});
|
||||
|
||||
if (this.$inline.is(".djn-group-root")) {
|
||||
DJNesting.createSortable(this.$inline);
|
||||
}
|
||||
|
||||
$(document).trigger("djnesting:initialized", [this.$inline, this]);
|
||||
}
|
||||
_setupPolymorphic() {
|
||||
if (!this.opts.childTypes) {
|
||||
throw Error(
|
||||
"The polymorphic fieldset options.childTypes is not defined!"
|
||||
);
|
||||
}
|
||||
let menu = '<div class="polymorphic-type-menu" style="display: none"><ul>';
|
||||
this.opts.childTypes.forEach((c) => {
|
||||
menu += `<li><a href="#" data-type="${c.type}">${c.name}</a></li>`;
|
||||
});
|
||||
menu += "</ul></div>";
|
||||
const $addButton = this.$inline.find(this.opts.addButtonSelector);
|
||||
const $menu = $(menu);
|
||||
$addButton.after($menu);
|
||||
}
|
||||
|
||||
_initializeForms() {
|
||||
var totalForms = this.mgmtVal("TOTAL_FORMS");
|
||||
var maxForms = this.mgmtVal("MAX_NUM_FORMS");
|
||||
if (maxForms <= totalForms) {
|
||||
this.$inline
|
||||
.find(this.opts.addButtonSelector)
|
||||
.parents(".djn-add-item")
|
||||
.hide();
|
||||
}
|
||||
for (var i = 0; i < totalForms; i++) {
|
||||
this._initializeForm("#" + this.prefix + "-" + i);
|
||||
}
|
||||
}
|
||||
_initializeForm(form) {
|
||||
var $form = $(form);
|
||||
var formPrefix = $form.djangoFormPrefix();
|
||||
$form.addClass(this.opts.formClass);
|
||||
if ($form.hasClass("has_original")) {
|
||||
$("#id_" + formPrefix + "DELETE:checked").toggleClass(
|
||||
this.opts.predeleteClass
|
||||
);
|
||||
}
|
||||
var minForms = this.mgmtVal("MIN_NUM_FORMS");
|
||||
var totalForms = this.mgmtVal("TOTAL_FORMS");
|
||||
var self = this;
|
||||
var hideRemoveButton = totalForms <= minForms;
|
||||
this.$inline.djangoFormsetForms().each(function () {
|
||||
var showHideMethod = hideRemoveButton ? "hide" : "show";
|
||||
$(this).find(self.opts.removeButtonSelector)[showHideMethod]();
|
||||
});
|
||||
}
|
||||
_bindEvents($el) {
|
||||
var self = this;
|
||||
if (typeof $el == "undefined") {
|
||||
$el = this.$inline;
|
||||
}
|
||||
const $addButton = $el.find(this.opts.addButtonSelector);
|
||||
$addButton.off("click.djnesting").on("click.djnesting", function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const $menu = $(this).next(".polymorphic-type-menu");
|
||||
if (!$menu.length) {
|
||||
self.add();
|
||||
} else {
|
||||
if (!$menu.is(":visible")) {
|
||||
function hideMenu() {
|
||||
$menu.hide();
|
||||
$(document).off("click", hideMenu);
|
||||
}
|
||||
$(document).on("click", hideMenu);
|
||||
}
|
||||
$menu.show();
|
||||
}
|
||||
});
|
||||
const $menuButtons = $addButton.parent().find("> .polymorphic-type-menu a");
|
||||
$menuButtons.off("click.djnesting").on("click.djnesting", function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const polymorphicType = $(this).attr("data-type");
|
||||
self.add(null, polymorphicType);
|
||||
const $menu = $(e.target).closest(".polymorphic-type-menu");
|
||||
if ($menu.is(":visible")) {
|
||||
$menu.hide();
|
||||
}
|
||||
});
|
||||
$el
|
||||
.find(this.opts.removeButtonSelector)
|
||||
.filter(function () {
|
||||
return !$(this).closest(".djn-empty-form").length;
|
||||
})
|
||||
.off("click.djnesting")
|
||||
.on("click.djnesting", function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
var $form = $(this).closest(self.opts.formClassSelector);
|
||||
self.remove($form);
|
||||
});
|
||||
|
||||
var deleteClickHandler = function (e) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
var $form = $(this).closest(self.opts.formClassSelector);
|
||||
var $deleteInput = $("#id_" + $form.djangoFormPrefix() + "DELETE");
|
||||
if (!$deleteInput.is(":checked")) {
|
||||
self["delete"]($form);
|
||||
} else {
|
||||
self.undelete($form);
|
||||
}
|
||||
};
|
||||
|
||||
var $deleteButton = $el
|
||||
.find(this.opts.deleteButtonSelector)
|
||||
.filter(function () {
|
||||
return !$(this).closest(".djn-empty-form").length;
|
||||
});
|
||||
|
||||
$deleteButton
|
||||
.off("click.djnesting")
|
||||
.on("click.djnesting", deleteClickHandler);
|
||||
$deleteButton
|
||||
.find('[id$="-DELETE"]')
|
||||
.on("mousedown.djnesting", deleteClickHandler);
|
||||
}
|
||||
remove(form) {
|
||||
var $form = $(form);
|
||||
var totalForms = this.mgmtVal("TOTAL_FORMS");
|
||||
var minForms = this.mgmtVal("MIN_NUM_FORMS");
|
||||
var maxForms = this.mgmtVal("MAX_NUM_FORMS");
|
||||
var index = $form.djangoFormIndex();
|
||||
var isInitial = $form.data("isInitial");
|
||||
|
||||
// Clearing out the form HTML itself using DOM APIs is much faster
|
||||
// than using jQuery to remove the element. Using jQuery is so
|
||||
// slow that it hangs the page.
|
||||
$form[0].innerHTML = "";
|
||||
$form.remove();
|
||||
|
||||
totalForms -= 1;
|
||||
this.mgmtVal("TOTAL_FORMS", totalForms);
|
||||
|
||||
if (maxForms - totalForms >= 0) {
|
||||
this.$inline
|
||||
.find(this.opts.addButtonSelector)
|
||||
.parent(".djn-add-item,li")
|
||||
.show();
|
||||
}
|
||||
|
||||
this._fillGap(index, isInitial);
|
||||
|
||||
var self = this;
|
||||
var hideRemoveButton = totalForms <= minForms;
|
||||
this.$inline.djangoFormsetForms().each(function () {
|
||||
var showHideMethod = hideRemoveButton ? "hide" : "show";
|
||||
$(this).find(self.opts.removeButtonSelector)[showHideMethod]();
|
||||
});
|
||||
|
||||
DJNesting.updatePositions(this.prefix);
|
||||
$(document).trigger("djnesting:mutate", [this.$inline]);
|
||||
|
||||
// Also fire using the events that were added in Django 1.9
|
||||
$(document).trigger("formset:removed", [$form, this.prefix]);
|
||||
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("formset:removed", {
|
||||
detail: {
|
||||
formsetName: this.prefix,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
delete(form) {
|
||||
var self = this,
|
||||
$form = $(form),
|
||||
formPrefix = $form.djangoFormPrefix(),
|
||||
$deleteInput = $("#id_" + formPrefix + "DELETE");
|
||||
|
||||
if ($form.hasClass(this.opts.predeleteClass)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$form.data("isInitial")) {
|
||||
return;
|
||||
}
|
||||
$deleteInput.attr("checked", "checked");
|
||||
if ($deleteInput.length) {
|
||||
$deleteInput[0].checked = true;
|
||||
}
|
||||
$form.addClass(this.opts.predeleteClass);
|
||||
|
||||
$form.find(".djn-group").each(function () {
|
||||
var $childInline = $(this);
|
||||
var childFormset = $childInline.djangoFormset();
|
||||
$childInline.djangoFormsetForms().each(function () {
|
||||
if ($(this).hasClass(self.opts.predeleteClass)) {
|
||||
$(this).data("alreadyDeleted", true);
|
||||
} else {
|
||||
childFormset.delete(this);
|
||||
}
|
||||
});
|
||||
});
|
||||
$form.find(".cropduster-form").each(function () {
|
||||
var formPrefix = $(this).djangoFormsetPrefix() + "-0-";
|
||||
var $deleteInput = $("#id_" + formPrefix + "DELETE");
|
||||
$deleteInput.attr("checked", "checked");
|
||||
if ($deleteInput.length) {
|
||||
$deleteInput[0].checked = true;
|
||||
}
|
||||
});
|
||||
DJNesting.updatePositions(this.prefix);
|
||||
$(document).trigger("djnesting:mutate", [this.$inline]);
|
||||
$(document).trigger("formset:deleted", [$form, this.prefix]);
|
||||
}
|
||||
undelete(form) {
|
||||
var $form = $(form),
|
||||
formPrefix = $form.djangoFormPrefix(),
|
||||
$deleteInput = $("#id_" + formPrefix + "DELETE");
|
||||
|
||||
if ($form.parent().closest("." + this.opts.predeleteClass).length) {
|
||||
return;
|
||||
}
|
||||
if ($form.hasClass("has_original")) {
|
||||
$deleteInput.removeAttr("checked");
|
||||
if ($deleteInput.length) {
|
||||
$deleteInput[0].checked = false;
|
||||
}
|
||||
$form.removeClass(this.opts.predeleteClass);
|
||||
}
|
||||
$form.data("alreadyDeleted", false);
|
||||
$form.find(".djn-group").each(function () {
|
||||
var $childInline = $(this);
|
||||
var childFormset = $childInline.djangoFormset();
|
||||
$childInline.djangoFormsetForms().each(function () {
|
||||
if ($(this).data("alreadyDeleted")) {
|
||||
$(this).data("alreadyDeleted", false);
|
||||
} else {
|
||||
childFormset.undelete(this);
|
||||
}
|
||||
});
|
||||
});
|
||||
$form.find(".cropduster-form").each(function () {
|
||||
var formPrefix = $(this).djangoFormsetPrefix() + "-0-";
|
||||
var $deleteInput = $("#id_" + formPrefix + "DELETE");
|
||||
$deleteInput.removeAttr("checked");
|
||||
if ($deleteInput.length) {
|
||||
$deleteInput[0].checked = false;
|
||||
}
|
||||
});
|
||||
DJNesting.updatePositions(this.prefix);
|
||||
$(document).trigger("djnesting:mutate", [this.$inline]);
|
||||
$(document).trigger("formset:undeleted", [$form, this.prefix]);
|
||||
}
|
||||
add(spliceIndex, ctype) {
|
||||
var self = this;
|
||||
const $template = ctype
|
||||
? $(`#${this.prefix}-empty-${ctype}`)
|
||||
: this._$template;
|
||||
var $form = $template.clone(true);
|
||||
|
||||
// For django-grappelli >= 2.14, where the grp.jQuery instance is not
|
||||
// the same as django.jQuery, we must copy any prepopulated_field
|
||||
// dependency data from grp.jQuery to the cloned nodes.
|
||||
grp$($template)
|
||||
.find(":data(dependency_ids)")
|
||||
.each(function () {
|
||||
const id = $(this).attr("id");
|
||||
const $el = $form.find(`#${id}`);
|
||||
grp$($el).data($.extend({}, $el.data(), grp$(this).data()));
|
||||
});
|
||||
|
||||
var index = this.mgmtVal("TOTAL_FORMS");
|
||||
var maxForms = this.mgmtVal("MAX_NUM_FORMS");
|
||||
var isNested = this.$inline.hasClass("djn-group-nested");
|
||||
|
||||
$(document).trigger("djnesting:beforeadded", [this.$inline, $form]);
|
||||
|
||||
$form.removeClass(this.opts.emptyClass);
|
||||
$form.addClass("djn-item");
|
||||
$form.attr("id", $form.attr("id").replace(/\-empty.*?$/, "-" + index));
|
||||
|
||||
if (isNested) {
|
||||
$form.append(DJNesting.createContainerElement());
|
||||
}
|
||||
|
||||
DJNesting.updateFormAttributes(
|
||||
$form,
|
||||
new RegExp(
|
||||
'([#_]id_|[\\#]|^id_|"|^)' +
|
||||
regexQuote(this.prefix) +
|
||||
"\\-(?:__prefix__|empty)\\-",
|
||||
"g"
|
||||
),
|
||||
"$1" + this.prefix + "-" + index + "-"
|
||||
);
|
||||
|
||||
let $firstTemplate = this._$template;
|
||||
if (this.opts.childTypes) {
|
||||
$firstTemplate = $template
|
||||
.closest(".djn-group")
|
||||
.find(
|
||||
'> .djn-items > [id*="-empty"], > .djn-fieldset > .djn-items > [id*="-empty"]'
|
||||
)
|
||||
.eq(0);
|
||||
}
|
||||
if (this.opts.childTypes) {
|
||||
const compatibleParents = this.$inline.djnData("compatibleParents") || {};
|
||||
$form.find("> .djn-group").each((i, el) => {
|
||||
const fkModel = $(el).djnData("formsetFkModel");
|
||||
const compatModels = compatibleParents[ctype] || [];
|
||||
const $el = $(el);
|
||||
const parentModel = $el.djnData("inlineParentModel");
|
||||
const isPolymorphic = !!$el.data("inlineFormset").options.childTypes;
|
||||
const formPrefix = $el.data("inlineFormset").options.prefix;
|
||||
if (
|
||||
parentModel !== ctype ||
|
||||
(isPolymorphic &&
|
||||
fkModel !== ctype &&
|
||||
compatModels.indexOf(fkModel) === -1)
|
||||
) {
|
||||
$el.find('input[id$="_FORMS"]').each((i, input) => {
|
||||
input.value = 0;
|
||||
input.setAttribute("value", "0");
|
||||
el.parentNode.appendChild(input);
|
||||
});
|
||||
el.parentNode.removeChild(el);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$form.insertBefore($firstTemplate);
|
||||
|
||||
this.mgmtVal("TOTAL_FORMS", index + 1);
|
||||
if (maxForms - (index + 1) <= 0) {
|
||||
this.$inline
|
||||
.find(this.opts.addButtonSelector)
|
||||
.parent(".djn-add-item,li")
|
||||
.hide();
|
||||
}
|
||||
|
||||
DJNesting.updatePositions(this.prefix);
|
||||
|
||||
if ($.isNumeric(spliceIndex)) {
|
||||
this.spliceInto($form, spliceIndex, true);
|
||||
} else {
|
||||
$(document).trigger("djnesting:mutate", [this.$inline]);
|
||||
}
|
||||
|
||||
if (grappelli) {
|
||||
grappelli.reinitDateTimeFields(grp$($form));
|
||||
}
|
||||
DJNesting.DjangoInlines.initPrepopulatedFields(django$($form));
|
||||
DJNesting.DjangoInlines.reinitDateTimeShortCuts();
|
||||
DJNesting.DjangoInlines.updateSelectFilter($form);
|
||||
DJNesting.initRelatedFields(this.prefix);
|
||||
DJNesting.initAutocompleteFields(this.prefix);
|
||||
if (grp && grp.jQuery.fn.grp_collapsible) {
|
||||
var addBackMethod = grp.jQuery.fn.addBack ? "addBack" : "andSelf";
|
||||
grp$($form)
|
||||
.find('.grp-collapse:not([id$="-empty"]):not([id*="-empty-"])')
|
||||
[addBackMethod]()
|
||||
.grp_collapsible({
|
||||
toggle_handler_slctr: ".grp-collapse-handler:first",
|
||||
closed_css: "closed grp-closed",
|
||||
open_css: "open grp-open",
|
||||
on_toggle: function () {
|
||||
$(document).trigger("djnesting:toggle", [self.$inline]);
|
||||
},
|
||||
});
|
||||
}
|
||||
if (typeof $.fn.curated_content_type == "function") {
|
||||
$form.find(".curated-content-type-select").each(function () {
|
||||
$(this).curated_content_type();
|
||||
});
|
||||
}
|
||||
|
||||
this._initializeForm($form);
|
||||
this._bindEvents($form);
|
||||
|
||||
if (ctype) {
|
||||
const formsetModelClassName = this.$inline.djnData("inlineModel");
|
||||
const inlineModelClassName = $form.attr("data-inline-model");
|
||||
const $buttons = $form.find(`.djn-model-${formsetModelClassName}`);
|
||||
$buttons.addClass(`djn-model-${inlineModelClassName}`);
|
||||
$form.addClass(`djn-dynamic-form-${inlineModelClassName}`);
|
||||
}
|
||||
|
||||
// find any nested formsets
|
||||
$form
|
||||
.find(
|
||||
'.djn-group[id$="-group"][id^="' +
|
||||
this.prefix +
|
||||
'"][data-inline-formset]:not([id*="-empty"])'
|
||||
)
|
||||
.each(function () {
|
||||
$(this)[pluginName]();
|
||||
});
|
||||
|
||||
// Fire an event on the document so other javascript applications
|
||||
// can be alerted to the newly inserted inline
|
||||
$(document).trigger("djnesting:added", [this.$inline, $form]);
|
||||
|
||||
// Also fire using the events that were added in Django 1.9
|
||||
$(document).trigger("formset:added", [$form, this.prefix]);
|
||||
|
||||
try {
|
||||
$form.get(0).dispatchEvent(
|
||||
new CustomEvent("formset:added", {
|
||||
bubbles: true,
|
||||
detail: {
|
||||
formsetName: this.prefix,
|
||||
},
|
||||
})
|
||||
);
|
||||
} catch (e) {}
|
||||
|
||||
return $form;
|
||||
}
|
||||
_fillGap(index, isInitial) {
|
||||
var $initialForm, $newForm;
|
||||
var formsets = this.$inline.djangoFormsetForms().toArray();
|
||||
// Sort formsets in index order, so that we get the last indexed form for the swap.
|
||||
formsets.sort(function (a, b) {
|
||||
return $(a).djangoFormIndex() - $(b).djangoFormIndex();
|
||||
});
|
||||
formsets.forEach(function (form) {
|
||||
var $form = $(form);
|
||||
var i = $form.djangoFormIndex();
|
||||
if (i <= index) {
|
||||
return;
|
||||
}
|
||||
if ($form.data("isInitial")) {
|
||||
$initialForm = $form;
|
||||
} else {
|
||||
$newForm = $form;
|
||||
}
|
||||
});
|
||||
var $form = isInitial ? $initialForm || $newForm : $newForm;
|
||||
if (!$form) {
|
||||
return;
|
||||
}
|
||||
var oldIndex = $form.djangoFormIndex();
|
||||
var oldFormPrefixRegex = new RegExp(
|
||||
"([\\#_]|^)" + regexQuote(this.prefix + "-" + oldIndex) + "(?!\\-\\d)"
|
||||
);
|
||||
$form.attr("id", this.prefix + "-" + index);
|
||||
DJNesting.updateFormAttributes(
|
||||
$form,
|
||||
oldFormPrefixRegex,
|
||||
"$1" + this.prefix + "-" + index
|
||||
);
|
||||
|
||||
// Update prefixes on nested DjangoFormset objects
|
||||
$form.find(".djn-group").each(function () {
|
||||
var $childInline = $(this);
|
||||
var childFormset = $childInline.djangoFormset();
|
||||
childFormset.prefix = $childInline.djangoFormsetPrefix();
|
||||
});
|
||||
|
||||
$(document).trigger("djnesting:attrchange", [this.$inline, $form]);
|
||||
|
||||
if (isInitial && $initialForm && $newForm) {
|
||||
this._fillGap(oldIndex, false);
|
||||
}
|
||||
}
|
||||
_makeRoomForInsert() {
|
||||
var initialFormCount = this.mgmtVal("INITIAL_FORMS"),
|
||||
totalFormCount = this.mgmtVal("TOTAL_FORMS"),
|
||||
gapIndex = initialFormCount,
|
||||
$existingForm = $("#" + this.prefix + "-" + gapIndex);
|
||||
|
||||
if (!$existingForm.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var oldFormPrefixRegex = new RegExp(
|
||||
"([\\#_]|^)" + regexQuote(this.prefix) + "-" + gapIndex + "(?!\\-\\d)"
|
||||
);
|
||||
$existingForm.attr("id", this.prefix + "-" + totalFormCount);
|
||||
DJNesting.updateFormAttributes(
|
||||
$existingForm,
|
||||
oldFormPrefixRegex,
|
||||
"$1" + this.prefix + "-" + totalFormCount
|
||||
);
|
||||
|
||||
// Update prefixes on nested DjangoFormset objects
|
||||
$existingForm.find(".djn-group").each(function () {
|
||||
var $childInline = $(this);
|
||||
var childFormset = $childInline.djangoFormset();
|
||||
childFormset.prefix = $childInline.djangoFormsetPrefix();
|
||||
});
|
||||
|
||||
$(document).trigger("djnesting:attrchange", [this.$inline, $existingForm]);
|
||||
}
|
||||
/**
|
||||
* Splice a form into the current formset at new position `index`.
|
||||
*/
|
||||
spliceInto($form, index, isNewAddition) {
|
||||
var initialFormCount = this.mgmtVal("INITIAL_FORMS"),
|
||||
totalFormCount = this.mgmtVal("TOTAL_FORMS"),
|
||||
oldFormsetPrefix = $form.djangoFormsetPrefix(),
|
||||
newFormsetPrefix = this.prefix,
|
||||
isInitial = $form.data("isInitial"),
|
||||
newIndex,
|
||||
$before;
|
||||
|
||||
// Make sure the form being spliced is from a different inline
|
||||
if ($form.djangoFormsetPrefix() == this.prefix) {
|
||||
var currentPosition = $form.prevAll(
|
||||
".djn-item:not(.djn-no-drag,.djn-thead)"
|
||||
).length;
|
||||
if (currentPosition === index || typeof index == "undefined") {
|
||||
DJNesting.updatePositions(newFormsetPrefix);
|
||||
return;
|
||||
}
|
||||
$before = this.$inline
|
||||
.find("> .djn-items, > .tabular > .module > .djn-items")
|
||||
.find("> .djn-item:not(#" + $form.attr("id") + ")")
|
||||
.eq(index);
|
||||
$before.after($form);
|
||||
} else {
|
||||
var $oldInline = $("#" + oldFormsetPrefix + "-group");
|
||||
var $currentFormInline = $form.closest(".djn-group");
|
||||
|
||||
if ($currentFormInline.djangoFormsetPrefix() != newFormsetPrefix) {
|
||||
$before = this.$inline
|
||||
.find("> .djn-items, > .tabular > .module > .djn-items")
|
||||
.find("> .djn-item")
|
||||
.eq(index);
|
||||
$before.after($form);
|
||||
}
|
||||
|
||||
var oldDjangoFormset = $oldInline.djangoFormset();
|
||||
oldDjangoFormset.mgmtVal(
|
||||
"TOTAL_FORMS",
|
||||
oldDjangoFormset.mgmtVal("TOTAL_FORMS") - 1
|
||||
);
|
||||
oldDjangoFormset._fillGap($form.djangoFormIndex(), isInitial);
|
||||
|
||||
if (isInitial) {
|
||||
oldDjangoFormset.mgmtVal(
|
||||
"INITIAL_FORMS",
|
||||
oldDjangoFormset.mgmtVal("INITIAL_FORMS") - 1
|
||||
);
|
||||
|
||||
var $parentInline = this.$inline.parent().closest(".djn-group");
|
||||
if ($parentInline.length) {
|
||||
var $parentForm = this.$inline.closest(".djn-inline-form");
|
||||
var parentPkField = ($parentInline.djnData("fieldNames") || {}).pk;
|
||||
var $parentPk = $parentForm.djangoFormField(parentPkField);
|
||||
if (!$parentPk.val()) {
|
||||
$form.data("isInitial", false);
|
||||
$form.attr("data-is-initial", "false");
|
||||
isInitial = false;
|
||||
// Set initial form counts to 0 on nested DjangoFormsets
|
||||
setTimeout(function () {
|
||||
$form
|
||||
.find(
|
||||
'[name^="' +
|
||||
$form.djangoFormPrefix() +
|
||||
'"][name$="-INITIAL_FORMS"]'
|
||||
)
|
||||
.val("0")
|
||||
.trigger("change");
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isInitial) {
|
||||
this._makeRoomForInsert();
|
||||
}
|
||||
|
||||
// Replace the ids for the splice form
|
||||
var oldFormPrefixRegex = new RegExp(
|
||||
"([\\#_]|^)" + regexQuote($form.attr("id")) + "(?!\\-\\d)"
|
||||
);
|
||||
newIndex = isInitial ? initialFormCount : totalFormCount;
|
||||
$form.attr("id", newFormsetPrefix + "-" + newIndex);
|
||||
DJNesting.updateFormAttributes(
|
||||
$form,
|
||||
oldFormPrefixRegex,
|
||||
"$1" + newFormsetPrefix + "-" + newIndex
|
||||
);
|
||||
|
||||
// Update prefixes on nested DjangoFormset objects
|
||||
$form.find(".djn-group").each(function () {
|
||||
var $childInline = $(this);
|
||||
var childFormset = $childInline.djangoFormset();
|
||||
childFormset.prefix = $childInline.djangoFormsetPrefix();
|
||||
});
|
||||
|
||||
$(document).trigger("djnesting:attrchange", [this.$inline, $form]);
|
||||
|
||||
if (isInitial) {
|
||||
this.mgmtVal("INITIAL_FORMS", initialFormCount + 1);
|
||||
}
|
||||
this.mgmtVal("TOTAL_FORMS", totalFormCount + 1);
|
||||
|
||||
DJNesting.updatePositions(oldFormsetPrefix);
|
||||
$(document).trigger("djnesting:mutate", [$oldInline]);
|
||||
}
|
||||
|
||||
DJNesting.updatePositions(newFormsetPrefix);
|
||||
if (!isNewAddition) {
|
||||
$(document).trigger("djnesting:mutate", [this.$inline]);
|
||||
}
|
||||
}
|
||||
mgmtVal(name, newValue) {
|
||||
var $field = this.$inline.find("#id_" + this.prefix + "-" + name);
|
||||
if (typeof newValue == "undefined") {
|
||||
return parseInt($field.val(), 10);
|
||||
} else {
|
||||
return parseInt($field.val(newValue).trigger("change").val(), 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$.fn[pluginName] = function () {
|
||||
var options, fn, args;
|
||||
var $el = this.eq(0);
|
||||
|
||||
if (
|
||||
arguments.length === 0 ||
|
||||
(arguments.length === 1 && $.type(arguments[0]) != "string")
|
||||
) {
|
||||
options = arguments[0];
|
||||
var djangoFormset = $el.data(pluginName);
|
||||
if (!djangoFormset) {
|
||||
djangoFormset = new DjangoFormset($el, options);
|
||||
$el.data(pluginName, djangoFormset);
|
||||
}
|
||||
return djangoFormset;
|
||||
}
|
||||
|
||||
fn = arguments[0];
|
||||
args = $.makeArray(arguments).slice(1);
|
||||
|
||||
if (fn in DjangoFormset.prototype) {
|
||||
return $el.data(pluginName)[fn](args);
|
||||
} else {
|
||||
throw new Error("Unknown function call " + fn + " for $.fn." + pluginName);
|
||||
}
|
||||
};
|
||||
|
||||
export default DjangoFormset;
|
||||
234
nested_admin/src/nested-admin/jquery.djnutils.js
Normal file
234
nested_admin/src/nested-admin/jquery.djnutils.js
Normal file
@@ -0,0 +1,234 @@
|
||||
import $ from "jquery";
|
||||
|
||||
var prefixCache = {};
|
||||
|
||||
$.fn.djnData = function (name) {
|
||||
var inlineFormsetData = $(this).data("inlineFormset") || {},
|
||||
nestedOptions = inlineFormsetData.nestedOptions || {};
|
||||
if (!name) {
|
||||
return nestedOptions;
|
||||
} else {
|
||||
return nestedOptions[name];
|
||||
}
|
||||
};
|
||||
|
||||
$.fn.djangoPrefixIndex = function () {
|
||||
var $this = this.length > 1 ? this.first() : this;
|
||||
var id = $this.attr("id"),
|
||||
name = $this.attr("name"),
|
||||
forattr = $this.attr("for"),
|
||||
prefix,
|
||||
$form,
|
||||
$group,
|
||||
groupId,
|
||||
cacheKey,
|
||||
match,
|
||||
index;
|
||||
|
||||
if (
|
||||
(match = prefixCache[id]) ||
|
||||
(match = prefixCache[name]) ||
|
||||
(match = prefixCache[forattr])
|
||||
) {
|
||||
return match;
|
||||
}
|
||||
|
||||
if (id && !prefix) {
|
||||
prefix = (id.match(/^(.*)\-group$/) || [null, null])[1];
|
||||
}
|
||||
|
||||
if (id && !prefix && $this.is(".djn-item") && id.match(/\d+$/)) {
|
||||
[cacheKey, prefix, index] = id.match(/(.*?)\-(\d+)$/) || [null, null, null];
|
||||
}
|
||||
|
||||
if (!prefix) {
|
||||
$form = $this.closest(".djn-inline-form");
|
||||
if ($form.length) {
|
||||
[cacheKey, prefix, index] = $form.attr("id").match(/(.*?)\-(\d+)$/) || [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
];
|
||||
} else {
|
||||
$group = $this.closest(".djn-group");
|
||||
if (!$group.length) {
|
||||
return null;
|
||||
}
|
||||
groupId = $group.attr("id") || "";
|
||||
prefix = (groupId.match(/^(.*)\-group$/) || [null, null])[1];
|
||||
}
|
||||
} else {
|
||||
if (prefix.substr(0, 3) == "id_") {
|
||||
prefix = prefix.substr(3);
|
||||
}
|
||||
|
||||
if (!document.getElementById(prefix + "-group")) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (cacheKey) {
|
||||
prefixCache[cacheKey] = [prefix, index];
|
||||
}
|
||||
|
||||
return [prefix, index];
|
||||
};
|
||||
|
||||
$.fn.djangoFormPrefix = function () {
|
||||
var prefixIndex = this.djangoPrefixIndex();
|
||||
if (!prefixIndex || !prefixIndex[1]) {
|
||||
return null;
|
||||
}
|
||||
return prefixIndex[0] + "-" + prefixIndex[1] + "-";
|
||||
};
|
||||
|
||||
$.fn.djangoFormIndex = function () {
|
||||
var prefixIndex = this.djangoPrefixIndex();
|
||||
return !prefixIndex || !prefixIndex[1] ? null : parseInt(prefixIndex[1], 10);
|
||||
};
|
||||
|
||||
$.fn.djangoFormsetPrefix = function () {
|
||||
var prefixIndex = this.djangoPrefixIndex();
|
||||
return !prefixIndex ? null : prefixIndex[0];
|
||||
};
|
||||
|
||||
var filterDjangoFormsetForms = function (form, $group, formsetPrefix) {
|
||||
var formId = form.getAttribute("id"),
|
||||
formIndex = formId.substr(formsetPrefix.length + 1);
|
||||
|
||||
// Check if form id matches /{prefix}-\d+/
|
||||
if (formId.indexOf(formsetPrefix) !== 0) {
|
||||
return false;
|
||||
}
|
||||
return !formIndex.match(/\D/);
|
||||
};
|
||||
|
||||
// Selects all initial forms within the same formset as the
|
||||
// element the method is being called on.
|
||||
$.fn.djangoFormsetForms = function () {
|
||||
var forms = [];
|
||||
this.each(function () {
|
||||
var $this = $(this),
|
||||
formsetPrefix = $this.djangoFormsetPrefix(),
|
||||
$group = formsetPrefix ? $("#" + formsetPrefix + "-group") : null,
|
||||
$forms;
|
||||
|
||||
if (!formsetPrefix || !$group.length) return;
|
||||
|
||||
$forms = $group.find(".djn-inline-form").filter(function () {
|
||||
return filterDjangoFormsetForms(this, $group, formsetPrefix);
|
||||
});
|
||||
var sortedForms = $forms.toArray().sort(function (a, b) {
|
||||
return $(a).djangoFormIndex() - $(b).djangoFormIndex;
|
||||
});
|
||||
Array.prototype.push.apply(forms, sortedForms);
|
||||
});
|
||||
return this.pushStack(forms);
|
||||
};
|
||||
|
||||
if (typeof $.djangoFormField != "function") {
|
||||
$.djangoFormField = function (fieldName, prefix, index) {
|
||||
var $empty = $([]),
|
||||
matches;
|
||||
if ((matches = prefix.match(/^(.+)\-(\d+)\-$/))) {
|
||||
prefix = matches[1];
|
||||
index = matches[2];
|
||||
}
|
||||
index = parseInt(index, 10);
|
||||
if (isNaN(index)) {
|
||||
return $empty;
|
||||
}
|
||||
var namePrefix = prefix + "-" + index + "-";
|
||||
if (fieldName == "*") {
|
||||
return $('*[name^="' + namePrefix + '"]').filter(function () {
|
||||
var fieldPart = $(this).attr("name").substring(namePrefix.length);
|
||||
return fieldPart.indexOf("-") === -1;
|
||||
});
|
||||
}
|
||||
var $field = $("#id_" + namePrefix + fieldName);
|
||||
if (!$field.length && (fieldName == "pk" || fieldName == "position")) {
|
||||
var $group = $("#" + prefix + "-group"),
|
||||
fieldNameData = $group.djnData("fieldNames") || {};
|
||||
fieldName = fieldNameData[fieldName];
|
||||
if (!fieldName) {
|
||||
return $empty;
|
||||
}
|
||||
$field = $("#id_" + namePrefix + fieldName);
|
||||
}
|
||||
return $field;
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof $.fn.djangoFormField != "function") {
|
||||
/**
|
||||
* Given a django model's field name, and the forms index in the
|
||||
* formset, returns the field's input element, or an empty jQuery
|
||||
* object on failure.
|
||||
*
|
||||
* @param String fieldName - 'pk', 'position', or the field's
|
||||
* name in django (e.g. 'content_type',
|
||||
* 'url', etc.)
|
||||
* @return jQuery object containing the field's input element, or
|
||||
* an empty jQuery object on failure
|
||||
*/
|
||||
$.fn.djangoFormField = function (fieldName, index) {
|
||||
var prefixAndIndex = this.djangoPrefixIndex();
|
||||
var $empty = $([]);
|
||||
if (!prefixAndIndex) {
|
||||
return $empty;
|
||||
}
|
||||
var prefix = prefixAndIndex[0];
|
||||
if (typeof index == "undefined") {
|
||||
index = prefixAndIndex[1];
|
||||
if (typeof index == "undefined") {
|
||||
return $empty;
|
||||
}
|
||||
}
|
||||
return $.djangoFormField(fieldName, prefix, index);
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof $.fn.filterDjangoField != "function") {
|
||||
var djRegexCache = {};
|
||||
$.fn.filterDjangoField = function (prefix, fieldName, index) {
|
||||
var $field, fieldNameData;
|
||||
if (typeof index != "undefined") {
|
||||
if (typeof index == "string") {
|
||||
index = parseInt(index, 10);
|
||||
}
|
||||
if (typeof index == "number" && !isNaN(index)) {
|
||||
var fieldId = "id_" + prefix + "-" + index + "-" + fieldName;
|
||||
$field = $("#" + fieldId);
|
||||
}
|
||||
} else {
|
||||
if (typeof djRegexCache[prefix] != "object") {
|
||||
djRegexCache[prefix] = {};
|
||||
}
|
||||
if (typeof djRegexCache[prefix][fieldName] == "undefined") {
|
||||
djRegexCache[prefix][fieldName] = new RegExp(
|
||||
"^" + prefix + "-\\d+-" + fieldName + "$"
|
||||
);
|
||||
}
|
||||
$field = this.find('input[name$="' + fieldName + '"]').filter(
|
||||
function () {
|
||||
return this.getAttribute("name").match(
|
||||
djRegexCache[prefix][fieldName]
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
if (!$field.length && (fieldName == "pk" || fieldName == "position")) {
|
||||
fieldNameData = $("#" + prefix + "-group").djnData("fieldNames") || {};
|
||||
if (
|
||||
typeof fieldNameData[fieldName] &&
|
||||
fieldNameData[fieldName] != fieldName
|
||||
) {
|
||||
$field = $(this).filterDjangoField(
|
||||
prefix,
|
||||
fieldNameData[fieldName],
|
||||
index
|
||||
);
|
||||
}
|
||||
}
|
||||
return $field;
|
||||
};
|
||||
}
|
||||
1
nested_admin/src/nested-admin/jquery.shim.js
Normal file
1
nested_admin/src/nested-admin/jquery.shim.js
Normal file
@@ -0,0 +1 @@
|
||||
export default window.django.jQuery;
|
||||
1462
nested_admin/src/nested-admin/jquery.ui.djnsortable.js
vendored
Normal file
1462
nested_admin/src/nested-admin/jquery.ui.djnsortable.js
vendored
Normal file
File diff suppressed because one or more lines are too long
794
nested_admin/src/nested-admin/jquery.ui.nestedsortable.js
vendored
Normal file
794
nested_admin/src/nested-admin/jquery.ui.nestedsortable.js
vendored
Normal file
@@ -0,0 +1,794 @@
|
||||
import $ from "jquery";
|
||||
import "./jquery.ui.djnsortable";
|
||||
|
||||
/*
|
||||
* jQuery UI Nested Sortable
|
||||
* v 1.3.4 / 28 apr 2011
|
||||
* http://mjsarfatti.com/sandbox/nestedSortable
|
||||
*
|
||||
* Depends:
|
||||
* jquery.ui.sortable.js 1.8+
|
||||
*
|
||||
* License CC BY-SA 3.0
|
||||
* Copyright 2010-2011, Manuele J Sarfatti
|
||||
*/
|
||||
if (typeof $.fn.nearest != "function") {
|
||||
/**
|
||||
* Returns the descendant(s) matching a given selector which are the
|
||||
* shortest distance from the search context element (in otherwords,
|
||||
* $.fn.closest(), in reverse).
|
||||
*/
|
||||
$.fn.nearest = function (selector) {
|
||||
var nearest = [],
|
||||
node = this,
|
||||
distance = 10000;
|
||||
node.find(selector).each(function () {
|
||||
var d = $(this).parentsUntil(node).length;
|
||||
if (d < distance) {
|
||||
distance = d;
|
||||
nearest = [this];
|
||||
} else if (d == distance) {
|
||||
nearest.push(this);
|
||||
}
|
||||
});
|
||||
return this.pushStack(nearest, "nearest", [selector]);
|
||||
};
|
||||
}
|
||||
|
||||
var counter = 0;
|
||||
var expando = "djn" + ("" + Math.random()).replace(/\D/g, "");
|
||||
|
||||
var createChildNestedSortable = function (parent, childContainer) {
|
||||
// Don't continue if the new element is the same as the old
|
||||
if (parent && parent.element && parent.element[0] == childContainer) {
|
||||
return;
|
||||
}
|
||||
var $childContainer = $(childContainer),
|
||||
options = $.extend({}, parent.options);
|
||||
options.connectWith = [parent.element];
|
||||
|
||||
if ($childContainer.data(parent.widgetName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var widgetConstructor = $childContainer[parent.widgetName];
|
||||
widgetConstructor.call($childContainer, options);
|
||||
var newInstance = $childContainer.data(parent.widgetName);
|
||||
for (var i = 0; i < parent.options.connectWith.length; i++) {
|
||||
var $otherContainer = parent.options.connectWith[i];
|
||||
newInstance.addToConnectWith($otherContainer);
|
||||
var otherInstance = $otherContainer.data(parent.widgetName);
|
||||
if (otherInstance) {
|
||||
otherInstance.addToConnectWith($childContainer);
|
||||
}
|
||||
}
|
||||
parent.addToConnectWith($childContainer);
|
||||
return newInstance;
|
||||
};
|
||||
|
||||
$.widget("ui.nestedSortable", $.ui.djnsortable, {
|
||||
options: {
|
||||
tabSize: 20,
|
||||
disableNesting: "ui-nestedSortable-no-nesting",
|
||||
errorClass: "ui-nestedSortable-error",
|
||||
nestedContainerSelector: ":not(*)",
|
||||
// Whether to clear empty list item and container elements
|
||||
doNotClear: false,
|
||||
/**
|
||||
* Create a list container element if the draggable was dragged
|
||||
* to the top or bottom of the elements at its level.
|
||||
*
|
||||
* @param DOMElement parent - The element relative to which the
|
||||
* new element will be inserted.
|
||||
* @return DOMElement - The new element.
|
||||
*/
|
||||
createContainerElement: function (parent) {
|
||||
return $(document.createElement("ol"));
|
||||
},
|
||||
// Selector which matches all container elements in the nestedSortable
|
||||
containerElementSelector: "ol",
|
||||
// Selector which matches all list items (draggables) in the nestedSortable
|
||||
listItemSelector: "li",
|
||||
// Selector which, when applied to a container, returns its child list items
|
||||
items: "> li",
|
||||
maxLevels: 0,
|
||||
revertOnError: 1,
|
||||
protectRoot: false,
|
||||
rootID: null,
|
||||
rtl: false,
|
||||
// if true, you can not move nodes to different levels of nesting
|
||||
fixedNestingDepth: false,
|
||||
// show the error div or just not show a drop area
|
||||
showErrorDiv: true,
|
||||
// if true only allows you to rearrange within its parent container
|
||||
keepInParent: false,
|
||||
isAllowed: function (item, parent) {
|
||||
return true;
|
||||
},
|
||||
canConnectWith: function (container1, container2, instance) {
|
||||
var model1 = container1.data("inlineModel");
|
||||
var model2 = container2.data("inlineModel");
|
||||
if (model1 !== model2) {
|
||||
return false;
|
||||
}
|
||||
var instance2 = container2.data(instance.widgetName);
|
||||
if (!instance.options.fixedNestingDepth) {
|
||||
if (!instance2 || !instance2.options.fixedNestingDepth) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
var container1Level = instance._getLevel(container1);
|
||||
var container2Level = instance._getLevel(container2);
|
||||
return container1Level === container2Level;
|
||||
},
|
||||
},
|
||||
|
||||
_createWidget: function (options, element) {
|
||||
var $element = $(element || this.defaultElement || this),
|
||||
dataOptions = $element.data("djnsortableOptions");
|
||||
element = $element[0];
|
||||
if (dataOptions) {
|
||||
options = $.extend({}, options, dataOptions);
|
||||
}
|
||||
return $.ui.djnsortable.prototype._createWidget.call(
|
||||
this,
|
||||
options,
|
||||
element
|
||||
);
|
||||
},
|
||||
|
||||
_create: function () {
|
||||
if (this.element.data("uiNestedSortable")) {
|
||||
this.element.data(
|
||||
"nestedSortable",
|
||||
this.element.data("uiNestedSortable")
|
||||
);
|
||||
}
|
||||
if (this.element.data("ui-nestedSortable")) {
|
||||
this.element.data(
|
||||
"nestedSortable",
|
||||
this.element.data("ui-nestedSortable")
|
||||
);
|
||||
}
|
||||
this.element.data("djnsortable", this.element.data("nestedSortable"));
|
||||
if (this.element.data("uiNestedSortable")) {
|
||||
this.element.data("uiSortable", this.element.data("nestedSortable"));
|
||||
}
|
||||
// if (!this.element.is(this.options.containerElementSelector)) {
|
||||
// throw new Error('nestedSortable: Please check that the ' +
|
||||
// 'containerElementSelector option matches ' +
|
||||
// 'the element passed to the constructor.');
|
||||
// }
|
||||
|
||||
$.ui.djnsortable.prototype._create.apply(this, arguments);
|
||||
|
||||
this._connectWithMap = {};
|
||||
|
||||
var self = this,
|
||||
o = this.options,
|
||||
$document = $(document);
|
||||
|
||||
var originalConnectWith = o.connectWith;
|
||||
if (!originalConnectWith || typeof originalConnectWith == "string") {
|
||||
this.options.connectWith = [];
|
||||
if (typeof originalConnectWith == "string") {
|
||||
var connected = this._connectWith();
|
||||
for (var i = 0; i < connected.length; i++) {
|
||||
this.addToConnectWith($(connected[i]));
|
||||
}
|
||||
}
|
||||
|
||||
// HACK!! FIX!! (django-specific logic)
|
||||
$document.on(
|
||||
"djnesting:init.nestedSortable",
|
||||
o.containerElementSelector,
|
||||
function (event) {
|
||||
createChildNestedSortable(self, this);
|
||||
}
|
||||
);
|
||||
this.element
|
||||
.find(o.containerElementSelector + ":not(.subarticle-wrapper)")
|
||||
.each(function (i, el) {
|
||||
if (
|
||||
$(el)
|
||||
.closest("[data-inline-formset]")
|
||||
.attr("id")
|
||||
.indexOf("-empty") > -1
|
||||
) {
|
||||
return;
|
||||
}
|
||||
createChildNestedSortable(self, el);
|
||||
});
|
||||
}
|
||||
$document.trigger("nestedSortable:created", [this]);
|
||||
$document.on(
|
||||
"nestedSortable:created.nestedSortable",
|
||||
function (e, instance) {
|
||||
instance.addToConnectWith(self.element);
|
||||
self.addToConnectWith(instance.element);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
addToConnectWith: function (element) {
|
||||
var self = this,
|
||||
$element = typeof element.selector != "undefined" ? element : $(element),
|
||||
uniqueId;
|
||||
|
||||
if ($element.length > 1) {
|
||||
$element.each(function (i, el) {
|
||||
self.addToConnectWith($(el));
|
||||
});
|
||||
return;
|
||||
}
|
||||
uniqueId = element[0][expando];
|
||||
if (typeof uniqueId == "undefined") {
|
||||
uniqueId = element[0][expando] = ++counter;
|
||||
}
|
||||
if (typeof this.options.connectWith == "string") {
|
||||
return;
|
||||
}
|
||||
if (this._connectWithMap[uniqueId]) {
|
||||
return;
|
||||
}
|
||||
this.options.connectWith.push(element);
|
||||
this._connectWithMap[uniqueId] = 1;
|
||||
},
|
||||
|
||||
_destroy: function () {
|
||||
this.element.removeData("nestedSortable").unbind(".nestedSortable");
|
||||
$(document).unbind(".nestedSortable");
|
||||
return $.ui.djnsortable.prototype.destroy.apply(this, arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* Override this method to add extra conditions on an item before it's
|
||||
* rearranged.
|
||||
*/
|
||||
_intersectsWithPointer: function _intersectsWithPointer(item) {
|
||||
var itemElement = item.item[0],
|
||||
o = this.options,
|
||||
intersection = $.ui.djnsortable.prototype._intersectsWithPointer.apply(
|
||||
this,
|
||||
arguments
|
||||
);
|
||||
|
||||
this.lastItemElement = null;
|
||||
if (!intersection) {
|
||||
return intersection;
|
||||
}
|
||||
|
||||
// Only put the placeholder inside the current Container, skip all
|
||||
// items from other containers. This works because when moving
|
||||
// an item from one container to another the
|
||||
// currentContainer is switched before the placeholder is moved.
|
||||
//
|
||||
// Without this moving items in "sub-sortables" can cause the placeholder to jitter
|
||||
// between the outer and inner container.
|
||||
if (item.instance !== this.currentContainer) {
|
||||
return false;
|
||||
}
|
||||
var $itemElement = $(itemElement);
|
||||
|
||||
if (
|
||||
o.fixedNestingDepth &&
|
||||
this._getLevel(this.currentItem) === 1 + this._getLevel($itemElement)
|
||||
) {
|
||||
$itemElement = (function () {
|
||||
var containerSel = o.containerElementSelector;
|
||||
var $childItems = $itemElement.find(".djn-item");
|
||||
if ($childItems.length != 1) {
|
||||
return $itemElement;
|
||||
}
|
||||
if (!$childItems.is(".djn-no-drag,.djn-thead")) {
|
||||
return $itemElement;
|
||||
}
|
||||
var itemElementClosestContainer = $itemElement.closest(containerSel);
|
||||
if (!itemElementClosestContainer.length) {
|
||||
return $itemElement;
|
||||
}
|
||||
// Make sure the item is only one level deeper
|
||||
if (
|
||||
itemElementClosestContainer[0] !=
|
||||
$childItems.closest(containerSel).closest(containerSel)[0]
|
||||
) {
|
||||
return $itemElement;
|
||||
}
|
||||
return $($childItems[0]);
|
||||
})();
|
||||
itemElement = $itemElement[0];
|
||||
}
|
||||
|
||||
if (
|
||||
itemElement != this.currentItem[0] && //cannot intersect with itself
|
||||
this.placeholder[intersection == 1 ? "next" : "prev"]()[0] !=
|
||||
itemElement && //no useless actions that have been done before
|
||||
!$.contains(this.placeholder[0], itemElement) && //no action if the item moved is the parent of the item checked
|
||||
(this.options.type == "semi-dynamic"
|
||||
? !$.contains(this.element[0], itemElement)
|
||||
: true) &&
|
||||
(!o.keepInParent ||
|
||||
itemElement.parentNode == this.placeholder[0].parentNode) && //only rearrange items within the same container
|
||||
(!o.fixedNestingDepth ||
|
||||
this._getLevel(this.currentItem) === this._getLevel($itemElement)) && //maintain the nesting level of node
|
||||
(o.showErrorDiv ||
|
||||
o.isAllowed.call(
|
||||
this,
|
||||
this.currentItem[0],
|
||||
itemElement.parentNode,
|
||||
this.placeholder
|
||||
))
|
||||
) {
|
||||
this.lastItemElement = itemElement;
|
||||
return intersection;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
// This method is called after items have been iterated through.
|
||||
// Overriding this is cleaner than copying and pasting _mouseDrag()
|
||||
// and inserting logic in the middle.
|
||||
_contactContainers: function _contactContainers(event) {
|
||||
if (this.lastItemElement) {
|
||||
this._clearEmpty(this.lastItemElement);
|
||||
}
|
||||
|
||||
var o = this.options,
|
||||
_parentItem = this.placeholder.closest(o.listItemSelector),
|
||||
parentItem =
|
||||
_parentItem.length && _parentItem.closest(".ui-sortable").length
|
||||
? _parentItem
|
||||
: null,
|
||||
level = this._getLevel(this.placeholder),
|
||||
childLevels = this._getChildLevels(this.helper);
|
||||
|
||||
var placeholderClassName = this.placeholder.attr("class");
|
||||
var phClassSearch = " " + placeholderClassName + " ";
|
||||
// If the current level class isn't already set
|
||||
if (
|
||||
phClassSearch.indexOf(" ui-sortable-nested-level-" + level + " ") == -1
|
||||
) {
|
||||
var phOrigClassName;
|
||||
// Check if another level class is set
|
||||
var phOrigClassNameEndPos =
|
||||
phClassSearch.indexOf(" ui-sortable-nested-level-") - 1;
|
||||
if (phOrigClassNameEndPos > -1) {
|
||||
phOrigClassName = placeholderClassName.substring(
|
||||
0,
|
||||
phOrigClassNameEndPos
|
||||
);
|
||||
} else {
|
||||
phOrigClassName = placeholderClassName;
|
||||
}
|
||||
// Add new level to class
|
||||
this.placeholder.attr(
|
||||
"class",
|
||||
phOrigClassName + " ui-sortable-nested-level-" + level
|
||||
);
|
||||
}
|
||||
|
||||
// To find the previous sibling in the list, keep backtracking until we hit a valid list item.
|
||||
var previousItem = this.placeholder[0].previousSibling
|
||||
? $(this.placeholder[0].previousSibling)
|
||||
: null;
|
||||
if (previousItem != null) {
|
||||
while (
|
||||
!previousItem.is(this.options.listItemSelector) ||
|
||||
previousItem[0] == this.currentItem[0] ||
|
||||
previousItem[0] == this.helper[0]
|
||||
) {
|
||||
if (previousItem[0].previousSibling) {
|
||||
previousItem = $(previousItem[0].previousSibling);
|
||||
} else {
|
||||
previousItem = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// To find the next sibling in the list, keep stepping forward until we hit a valid list item.
|
||||
var nextItem = this.placeholder[0].nextSibling
|
||||
? $(this.placeholder[0].nextSibling)
|
||||
: null;
|
||||
if (nextItem != null) {
|
||||
while (
|
||||
!nextItem.is(this.options.listItemSelector) ||
|
||||
nextItem[0] == this.currentItem[0] ||
|
||||
nextItem[0] == this.helper[0]
|
||||
) {
|
||||
if (nextItem[0].nextSibling) {
|
||||
nextItem = $(nextItem[0].nextSibling);
|
||||
} else {
|
||||
nextItem = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.beyondMaxLevels = 0;
|
||||
|
||||
// We will change this to the instance of the nested container if
|
||||
// appropriate, so that the appropriate context is applied to the
|
||||
// super _contactContainers prototype method
|
||||
var containerInstance = this;
|
||||
this.refreshPositions();
|
||||
|
||||
// If the item is moved to the left, send it to its parent's level unless there are siblings below it.
|
||||
if (
|
||||
!o.fixedNestingDepth &&
|
||||
parentItem != null &&
|
||||
nextItem == null &&
|
||||
((o.rtl &&
|
||||
this.positionAbs.left + this.helper.outerWidth() >
|
||||
parentItem.offset().left + parentItem.outerWidth()) ||
|
||||
(!o.rtl && this.positionAbs.left < parentItem.offset().left))
|
||||
) {
|
||||
parentItem.after(this.placeholder[0]);
|
||||
containerInstance =
|
||||
parentItem.closest(o.containerElementSelector).data(this.widgetName) ||
|
||||
containerInstance;
|
||||
this._clearEmpty(parentItem[0]);
|
||||
this.refreshPositions();
|
||||
this._trigger("change", event, this._uiHash());
|
||||
}
|
||||
// If the item is below a sibling and is moved to the right, make it a child of that sibling.
|
||||
else if (
|
||||
!o.fixedNestingDepth &&
|
||||
previousItem != null &&
|
||||
!previousItem.is(".djn-no-drag,.djn-thead") &&
|
||||
((o.rtl &&
|
||||
this.positionAbs.left + this.helper.outerWidth() <
|
||||
previousItem.offset().left + previousItem.outerWidth() - o.tabSize) ||
|
||||
(!o.rtl &&
|
||||
this.positionAbs.left > previousItem.offset().left + o.tabSize))
|
||||
) {
|
||||
this._isAllowed(previousItem, level, level + childLevels);
|
||||
|
||||
if (this.beyondMaxLevels > 0) {
|
||||
return $.ui.djnsortable.prototype._contactContainers.apply(
|
||||
this,
|
||||
arguments
|
||||
);
|
||||
}
|
||||
var $previousItemChildContainer;
|
||||
$previousItemChildContainer = previousItem
|
||||
.nearest(o.containerElementSelector)
|
||||
.first();
|
||||
|
||||
if (
|
||||
!$previousItemChildContainer.length &&
|
||||
!previousItem.closest(o.nestedContainerSelector).length
|
||||
) {
|
||||
$previousItemChildContainer = this.options.createContainerElement(
|
||||
previousItem[0]
|
||||
);
|
||||
previousItem.append($previousItemChildContainer);
|
||||
}
|
||||
if ($previousItemChildContainer.length) {
|
||||
$previousItemChildContainer.append(this.placeholder);
|
||||
containerInstance = $previousItemChildContainer.data(this.widgetName);
|
||||
if (!containerInstance) {
|
||||
containerInstance = createChildNestedSortable(
|
||||
this,
|
||||
$previousItemChildContainer[0]
|
||||
);
|
||||
}
|
||||
this.refreshPositions();
|
||||
}
|
||||
this._trigger("change", event, this._uiHash());
|
||||
} else {
|
||||
this._isAllowed(parentItem, level, level + childLevels);
|
||||
}
|
||||
|
||||
$.ui.djnsortable.prototype._contactContainers.call(this, event);
|
||||
},
|
||||
|
||||
_rearrange: function _rearrange(event, item, a, hardRefresh) {
|
||||
// Cache the rearranged element for the call to _clear()
|
||||
var o = this.options;
|
||||
if (item && typeof item == "object" && item.item) {
|
||||
this.lastRearrangedElement = item.item[0];
|
||||
}
|
||||
if (
|
||||
item &&
|
||||
typeof item == "object" &&
|
||||
item.item &&
|
||||
this.placeholder.closest(o.nestedContainerSelector).length
|
||||
) {
|
||||
// This means we have been dropped into a nested container down a level
|
||||
// from the parent.
|
||||
var placeholderParentItem = this.placeholder.closest(o.listItemSelector);
|
||||
var comparisonElement =
|
||||
this.direction == "down"
|
||||
? placeholderParentItem.next(o.nestedContainerSelector)
|
||||
: placeholderParentItem;
|
||||
if (comparisonElement.length && comparisonElement[0] == item.item[0]) {
|
||||
//Various things done here to improve the performance:
|
||||
// 1. we create a setTimeout, that calls refreshPositions
|
||||
// 2. on the instance, we have a counter variable, that get's higher after every append
|
||||
// 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same
|
||||
// 4. this lets only the last addition to the timeout stack through
|
||||
this.counter = this.counter ? ++this.counter : 1;
|
||||
var counter = this.counter;
|
||||
|
||||
this._delay(function () {
|
||||
if (counter == this.counter) this.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove
|
||||
});
|
||||
// The super method will pop the container out of its nested container,
|
||||
// which we don't want.
|
||||
return;
|
||||
}
|
||||
}
|
||||
$.ui.djnsortable.prototype._rearrange.apply(this, arguments);
|
||||
},
|
||||
|
||||
_convertPositionTo: function (d, pos) {
|
||||
// Cache the top offset before rearrangement
|
||||
this.previousTopOffset = this.placeholder.offset().top;
|
||||
return $.ui.djnsortable.prototype._convertPositionTo.apply(this, arguments);
|
||||
},
|
||||
|
||||
_clear: function () {
|
||||
$.ui.djnsortable.prototype._clear.apply(this, arguments);
|
||||
// If lastRearrangedElement exists and is still attached to the document
|
||||
// (i.e., hasn't been removed)
|
||||
if (
|
||||
typeof this.lastRearrangedElement == "object" &&
|
||||
this.lastRearrangedElement.ownerDocument
|
||||
) {
|
||||
this._clearEmpty(this.lastRearrangedElement);
|
||||
}
|
||||
},
|
||||
|
||||
_mouseStop: function _mouseStop(event, noPropagation) {
|
||||
// If the item is in a position not allowed, send it back
|
||||
if (this.beyondMaxLevels) {
|
||||
this.placeholder.removeClass(this.options.errorClass);
|
||||
|
||||
if (this.domPosition.prev) {
|
||||
$(this.domPosition.prev).after(this.placeholder);
|
||||
} else {
|
||||
$(this.domPosition.parent).prepend(this.placeholder);
|
||||
}
|
||||
this._trigger("revert", event, this._uiHash());
|
||||
}
|
||||
|
||||
// Clean last empty container/list item
|
||||
for (var i = this.items.length - 1; i >= 0; i--) {
|
||||
var item = this.items[i].item[0];
|
||||
this._clearEmpty(item);
|
||||
}
|
||||
|
||||
$.ui.djnsortable.prototype._mouseStop.apply(this, arguments);
|
||||
},
|
||||
|
||||
toArray: function (o) {
|
||||
o = $.extend(true, {}, this.options, o || {});
|
||||
|
||||
var sDepth = o.startDepthCount || 0,
|
||||
ret = [],
|
||||
left = 2;
|
||||
|
||||
ret.push({
|
||||
item_id: o.rootID,
|
||||
parent_id: "none",
|
||||
depth: sDepth,
|
||||
left: "1",
|
||||
right: ($(o.listItemSelector, this.element).length + 1) * 2,
|
||||
});
|
||||
|
||||
var _recursiveArray = function (item, depth, left) {
|
||||
var right = left + 1,
|
||||
id,
|
||||
pid;
|
||||
|
||||
var $childItems = $(item)
|
||||
.children(o.containerElementSelector)
|
||||
.find(o.items);
|
||||
|
||||
if ($childItems.length > 0) {
|
||||
depth++;
|
||||
$childItems.each(function () {
|
||||
right = _recursiveArray($(this), depth, right);
|
||||
});
|
||||
depth--;
|
||||
}
|
||||
|
||||
id = $(item)
|
||||
.attr(o.attribute || "id")
|
||||
.match(o.expression || /(.+)[-=_](.+)/);
|
||||
|
||||
if (depth === sDepth + 1) {
|
||||
pid = o.rootID;
|
||||
} else {
|
||||
var parentItem = $(item)
|
||||
.parent(o.containerElementSelector)
|
||||
.parent(o.items)
|
||||
.attr(o.attribute || "id")
|
||||
.match(o.expression || /(.+)[-=_](.+)/);
|
||||
pid = parentItem[2];
|
||||
}
|
||||
|
||||
if (id) {
|
||||
ret.push({
|
||||
item_id: id[2],
|
||||
parent_id: pid,
|
||||
depth: depth,
|
||||
left: left,
|
||||
right: right,
|
||||
});
|
||||
}
|
||||
|
||||
left = right + 1;
|
||||
return left;
|
||||
};
|
||||
|
||||
$(this.element)
|
||||
.children(o.listItemSelector)
|
||||
.each(function () {
|
||||
left = _recursiveArray(this, sDepth + 1, left);
|
||||
});
|
||||
|
||||
ret = ret.sort(function (a, b) {
|
||||
return a.left - b.left;
|
||||
});
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
_clearEmpty: function (item) {
|
||||
if (this.options.doNotClear) {
|
||||
return;
|
||||
}
|
||||
var $item = $(item);
|
||||
var childContainers = $item.nearest(this.options.containerElementSelector);
|
||||
childContainers.each(function (i, childContainer) {
|
||||
var $childContainer = $(childContainer);
|
||||
if (!$childContainer.children().length) {
|
||||
var instance = $childContainer.data(this.widgetName);
|
||||
if (typeof instance == "object" && instance.destroy) {
|
||||
instance.destroy();
|
||||
}
|
||||
$childContainer.remove();
|
||||
}
|
||||
});
|
||||
if (!$item.children().length) {
|
||||
$item.remove();
|
||||
}
|
||||
},
|
||||
|
||||
_getLevel: function (item) {
|
||||
var level = 1,
|
||||
o = this.options,
|
||||
list;
|
||||
|
||||
if (o.containerElementSelector) {
|
||||
list = item.closest(o.containerElementSelector);
|
||||
while (list && list.length > 0 && !list.parent().is(".djn-group-root")) {
|
||||
// if (!list.is(o.nestedContainerSelector)) {
|
||||
level++;
|
||||
// }
|
||||
list = list.parent().closest(o.containerElementSelector);
|
||||
}
|
||||
}
|
||||
|
||||
return level;
|
||||
},
|
||||
|
||||
_getChildLevels: function (parent, depth) {
|
||||
var self = this,
|
||||
o = this.options,
|
||||
result = 0;
|
||||
depth = depth || 0;
|
||||
|
||||
$(parent)
|
||||
.nearest(o.containerElementSelector)
|
||||
.first()
|
||||
.find(o.items)
|
||||
.each(function (index, child) {
|
||||
if ($(child).is(".djn-no-drag,.djn-thead")) {
|
||||
return;
|
||||
}
|
||||
result = Math.max(self._getChildLevels(child, depth + 1), result);
|
||||
});
|
||||
|
||||
return depth ? result + 1 : result;
|
||||
},
|
||||
|
||||
_isAllowed: function _isAllowed(parentItem, level, levels) {
|
||||
var o = this.options,
|
||||
isRoot = $(this.domPosition.parent).hasClass("ui-sortable")
|
||||
? true
|
||||
: false;
|
||||
// this takes into account the maxLevels set to the recipient list
|
||||
// var maxLevels = this.placeholder.closest('.ui-sortable').nestedSortable('option', 'maxLevels');
|
||||
var maxLevels = o.maxLevels;
|
||||
|
||||
// Is the root protected?
|
||||
// Are we trying to nest under a no-nest?
|
||||
// Are we nesting too deep?
|
||||
if (
|
||||
parentItem &&
|
||||
typeof parentItem == "object" &&
|
||||
typeof parentItem.selector == "undefined"
|
||||
) {
|
||||
parentItem = $(parentItem);
|
||||
}
|
||||
|
||||
if (
|
||||
!o.isAllowed.call(this, this.currentItem, parentItem, this.placeholder) ||
|
||||
(parentItem && parentItem.hasClass(o.disableNesting)) ||
|
||||
(o.protectRoot &&
|
||||
((parentItem == null && !isRoot) || (isRoot && level > 1)))
|
||||
) {
|
||||
this.placeholder.addClass(o.errorClass);
|
||||
if (maxLevels < levels && maxLevels != 0) {
|
||||
this.beyondMaxLevels = levels - maxLevels;
|
||||
} else {
|
||||
this.beyondMaxLevels = 1;
|
||||
}
|
||||
} else {
|
||||
if (maxLevels < levels && maxLevels != 0) {
|
||||
this.placeholder.addClass(o.errorClass);
|
||||
this.beyondMaxLevels = levels - maxLevels;
|
||||
} else {
|
||||
this.placeholder.removeClass(o.errorClass);
|
||||
this.beyondMaxLevels = 0;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_connectWith: function _connectWith() {
|
||||
var origConnectWith = $.ui.djnsortable.prototype._connectWith.apply(
|
||||
this,
|
||||
arguments
|
||||
),
|
||||
connectWith = [];
|
||||
var self = this;
|
||||
for (var i = 0; i < origConnectWith.length; i++) {
|
||||
var $elements = $(origConnectWith[i]);
|
||||
$elements.each(function (j, el) {
|
||||
if (el == self.element[0]) {
|
||||
return;
|
||||
}
|
||||
if (!self.options.canConnectWith(self.element, $(el), self)) {
|
||||
return;
|
||||
}
|
||||
connectWith.push(el);
|
||||
});
|
||||
}
|
||||
return connectWith;
|
||||
},
|
||||
_removeCurrentsFromItems: function () {
|
||||
var list = this.currentItem.find(":data(sortable-item)");
|
||||
for (var i = 0; i < this.items.length; i++) {
|
||||
for (var j = 0; j < list.length; j++) {
|
||||
if (list[j] == this.items[i].item[0]) {
|
||||
this.items.splice(i, 1);
|
||||
if (i >= this.items.length) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
createContainerElement: function (parent) {
|
||||
if (!parent.childNodes) {
|
||||
throw new Error(
|
||||
"Invalid element 'parent' passed to " + "createContainerElement."
|
||||
);
|
||||
}
|
||||
var newContainer = this.options.createContainerElement.apply(
|
||||
this,
|
||||
arguments
|
||||
);
|
||||
parent.appendChild(newContainer[0]);
|
||||
return $(newContainer);
|
||||
},
|
||||
});
|
||||
|
||||
$.ui.nestedSortable.prototype.options = $.extend(
|
||||
{},
|
||||
$.ui.djnsortable.prototype.options,
|
||||
$.ui.nestedSortable.prototype.options
|
||||
);
|
||||
5
nested_admin/src/nested-admin/regexquote.js
Normal file
5
nested_admin/src/nested-admin/regexquote.js
Normal file
@@ -0,0 +1,5 @@
|
||||
function regexQuote(str) {
|
||||
return (str + "").replace(/([\.\?\*\+\^\$\[\]\\\(\)\{\}\|\-])/g, "\\$1");
|
||||
}
|
||||
|
||||
export default regexQuote;
|
||||
256
nested_admin/src/nested-admin/sortable.js
Normal file
256
nested_admin/src/nested-admin/sortable.js
Normal file
@@ -0,0 +1,256 @@
|
||||
import $ from "jquery";
|
||||
import regexQuote from "./regexquote";
|
||||
import "./jquery.ui.nestedsortable";
|
||||
|
||||
function updatePositions(prefix) {
|
||||
var position = 0, // the value of the position formfield
|
||||
count = 1, // The count displayed in stacked inline headers
|
||||
$group = $("#" + prefix + "-group"),
|
||||
groupData = $group.djnData(),
|
||||
fieldNames = groupData.fieldNames,
|
||||
// The field name on the fieldset which is a ForeignKey to the parent model
|
||||
groupFkName = groupData.formsetFkName,
|
||||
parentPkVal,
|
||||
[, parentPrefix, index] =
|
||||
prefix.match(/^(.*)\-(\d+)-[^\-]+(?:\-\d+)?$/) || [],
|
||||
sortableOptions = groupData.sortableOptions,
|
||||
sortableExcludes = (sortableOptions || {}).sortableExcludes || [];
|
||||
|
||||
sortableExcludes.push(groupFkName);
|
||||
|
||||
if (parentPrefix) {
|
||||
var $parentGroup = $("#" + parentPrefix + "-group");
|
||||
var parentFieldNames = $parentGroup.djnData("fieldNames");
|
||||
var parentPkFieldName = parentFieldNames.pk;
|
||||
var parentPkField = $parentGroup.filterDjangoField(
|
||||
parentPrefix,
|
||||
parentPkFieldName,
|
||||
index
|
||||
);
|
||||
parentPkVal = parentPkField.val();
|
||||
}
|
||||
|
||||
if (groupFkName && typeof parentPkVal != "undefined") {
|
||||
$group
|
||||
.filterDjangoField(prefix, groupFkName)
|
||||
.val(parentPkVal)
|
||||
.trigger("change");
|
||||
}
|
||||
|
||||
$group.find(".djn-inline-form").each(function () {
|
||||
if (!this.id || this.id.substr(-6) == "-empty") {
|
||||
return true; // Same as continue
|
||||
}
|
||||
var regex = new RegExp("^(?:id_)?" + regexQuote(prefix) + "\\-\\d+$");
|
||||
|
||||
if (!this.id.match(regex)) {
|
||||
return true;
|
||||
}
|
||||
// Cache jQuery object
|
||||
var $this = $(this),
|
||||
[formPrefix, index] = $this.djangoPrefixIndex() || [null, null],
|
||||
namePrefix = formPrefix + "-" + index + "-";
|
||||
|
||||
if (!formPrefix) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set header position for stacked inlines in Django 1.9+
|
||||
var $inlineLabel = $this.find("> h3 > .inline_label");
|
||||
if ($inlineLabel.length) {
|
||||
$inlineLabel.html($inlineLabel.html().replace(/(#\d+)/g, "#" + count));
|
||||
}
|
||||
count++;
|
||||
|
||||
var $fields = $this.djangoFormField("*"),
|
||||
$positionField,
|
||||
setPosition = false;
|
||||
|
||||
// position is being updated if
|
||||
// a) the field has a value
|
||||
// b) if the field is not exluded with sortable_excludes (e.g. with default values)
|
||||
$fields.each(function () {
|
||||
var $field = $(this);
|
||||
if (!$field.is(":input[type!=radio][type!=checkbox],input:checked")) {
|
||||
return;
|
||||
}
|
||||
var hasValue =
|
||||
$field.val() ||
|
||||
($field.attr("type") == "file" && $field.siblings("a").length),
|
||||
fieldName = $field.attr("name").substring(namePrefix.length);
|
||||
if (fieldName == fieldNames.position) {
|
||||
$positionField = $field;
|
||||
}
|
||||
if (hasValue && $.inArray(fieldName, sortableExcludes) === -1) {
|
||||
setPosition = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (!setPosition || !$positionField) {
|
||||
return;
|
||||
}
|
||||
|
||||
$positionField.val(position).trigger("change");
|
||||
position++;
|
||||
});
|
||||
}
|
||||
|
||||
function createSortable($group) {
|
||||
const isPolymorphic = $group.is(".djn-is-polymorphic");
|
||||
return $group
|
||||
.find(
|
||||
"> .djn-items, > .djn-fieldset > .djn-items, > .tabular > .module > .djn-items"
|
||||
)
|
||||
.nestedSortable({
|
||||
handle: [
|
||||
"> h3.djn-drag-handler",
|
||||
"> .djn-tools .drag-handler",
|
||||
"> .djn-td > .djn-tools .djn-drag-handler",
|
||||
"> .djn-tr > .is-sortable > .djn-drag-handler",
|
||||
"> .djn-tr > .grp-tools-container .djn-drag-handler",
|
||||
].join(", "),
|
||||
/**
|
||||
* items: The selector for ONLY the items underneath a given
|
||||
* container at that level. Not to be confused with
|
||||
* listItemSelector, which is the selector for all list
|
||||
* items in the nestedSortable
|
||||
*/
|
||||
items: "> .djn-item",
|
||||
forcePlaceholderSize: true,
|
||||
placeholder: {
|
||||
element: function ($currentItem) {
|
||||
var el = $(document.createElement($currentItem[0].nodeName))
|
||||
.addClass($currentItem[0].className + " ui-sortable-placeholder")
|
||||
.removeClass("ui-sortable-helper")[0];
|
||||
|
||||
if ($currentItem.is(".djn-tbody")) {
|
||||
var $originalTr = $currentItem.children(".djn-tr").eq(0);
|
||||
var trTagName = $originalTr.prop("tagName").toLowerCase();
|
||||
var $tr = $(`<${trTagName}></${trTagName}>`);
|
||||
$tr.addClass($originalTr.attr("class"));
|
||||
var $originalTd = $originalTr.children(".djn-td").eq(0);
|
||||
var tdTagName = $originalTd.prop("tagName").toLowerCase();
|
||||
var numColumns = 0;
|
||||
$originalTr.children(".djn-td").each(function (i, td) {
|
||||
numColumns += parseInt($(td).attr("colspan"), 10) || 1;
|
||||
});
|
||||
$tr.append(
|
||||
$(
|
||||
`<${tdTagName} colspan="${numColumns}" class="djn-td grp-td"></${tdTagName}>`
|
||||
)
|
||||
);
|
||||
el.appendChild($tr[0]);
|
||||
}
|
||||
|
||||
return el;
|
||||
},
|
||||
update: function (instance, $placeholder) {
|
||||
var $currItem = instance.currentItem;
|
||||
if (!$currItem) {
|
||||
return;
|
||||
}
|
||||
var opts = instance.options;
|
||||
// 1. If a className is set as 'placeholder option, we
|
||||
// don't force sizes - the class is responsible for that
|
||||
// 2. The option 'forcePlaceholderSize can be enabled to
|
||||
// force it even if a class name is specified
|
||||
if (opts.className && !opts.forcePlaceholderSize) return;
|
||||
|
||||
if ($placeholder.is(".djn-tbody")) {
|
||||
// debugger;
|
||||
$placeholder = $placeholder
|
||||
.children(".djn-tr")
|
||||
.eq(0)
|
||||
.children(".djn-td")
|
||||
.eq(0);
|
||||
}
|
||||
|
||||
// If the element doesn't have a actual height by itself
|
||||
// (without styles coming from a stylesheet), it receives
|
||||
// the inline height from the dragged item
|
||||
if (!$placeholder.height()) {
|
||||
var innerHeight = $currItem.innerHeight(),
|
||||
paddingTop = parseInt($currItem.css("paddingTop") || 0, 10),
|
||||
paddingBottom = parseInt($currItem.css("paddingBottom") || 0, 10);
|
||||
$placeholder.height(innerHeight - paddingTop - paddingBottom);
|
||||
}
|
||||
if (!$placeholder.width()) {
|
||||
var innerWidth = $currItem.innerWidth(),
|
||||
paddingLeft = parseInt($currItem.css("paddingLeft") || 0, 10),
|
||||
paddingRight = parseInt($currItem.css("paddingRight") || 0, 10);
|
||||
$placeholder.width(innerWidth - paddingLeft - paddingRight);
|
||||
}
|
||||
},
|
||||
},
|
||||
helper: "clone",
|
||||
opacity: 0.6,
|
||||
maxLevels: 0,
|
||||
connectWith: ".djn-items",
|
||||
tolerance: "intersection",
|
||||
// Don't allow dragging beneath an inline that is marked for deletion
|
||||
isAllowed: function (currentItem, parentItem) {
|
||||
if (parentItem && parentItem.hasClass("predelete")) {
|
||||
return false;
|
||||
}
|
||||
const $parentGroup = parentItem.closest(".djn-group");
|
||||
const parentModel = $parentGroup.data("inlineModel");
|
||||
const childModels = $parentGroup.djnData("childModels");
|
||||
const currentModel = currentItem.data("inlineModel");
|
||||
const isPolymorphicChild =
|
||||
childModels && childModels.indexOf(currentModel) !== -1;
|
||||
if (currentModel !== parentModel && !isPolymorphicChild) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
// fixedNestingDepth: not a standard ui.sortable parameter.
|
||||
// Prevents dragging items up or down levels
|
||||
fixedNestingDepth: true,
|
||||
// The selector for ALL list containers in the nested sortable.
|
||||
containerElementSelector: ".djn-items",
|
||||
// The selector for ALL list items in the nested sortable.
|
||||
listItemSelector: ".djn-item",
|
||||
start: function (event, ui) {
|
||||
ui.item.addClass("djn-item-dragging");
|
||||
ui.item.show();
|
||||
},
|
||||
stop: function (event, ui) {
|
||||
ui.item.removeClass("djn-item-dragging");
|
||||
},
|
||||
/**
|
||||
* Triggered when a sortable is dropped into a container
|
||||
*/
|
||||
receive: function (event, ui) {
|
||||
var $inline = $(this).closest(".djn-group");
|
||||
$inline.djangoFormset().spliceInto(ui.item);
|
||||
updatePositions(ui.item.djangoFormsetPrefix());
|
||||
},
|
||||
update: function (event, ui) {
|
||||
// Ensure that <div class='djn-item djn-no-drag'/>
|
||||
// is the first child of the djn-items. If there
|
||||
// is another <div class='djn-item'/> before the
|
||||
// .do-not-drag element then the drag-and-drop placeholder
|
||||
// margins don't work correctly.
|
||||
var $nextItem = ui.item.nextAll(".djn-item").first();
|
||||
if ($nextItem.is(".djn-no-drag,.djn-thead")) {
|
||||
var nextItem = $nextItem[0];
|
||||
var parent = nextItem.parentNode;
|
||||
parent.insertBefore(nextItem, parent.firstChild);
|
||||
}
|
||||
var groupId = $(event.target).closest(".djn-group").attr("id"),
|
||||
$form = ui.item,
|
||||
$parentGroup = $form.closest("#" + groupId);
|
||||
if ($form.data("updateOperation") == "removed") {
|
||||
$form.removeAttr("data-update-operation");
|
||||
} else if (!$parentGroup.length) {
|
||||
$form.attr("data-update-operation", "removed");
|
||||
}
|
||||
updatePositions($form.djangoFormsetPrefix());
|
||||
$(document).trigger("djnesting:mutate", [
|
||||
$("#" + $form.djangoFormsetPrefix() + "-group"),
|
||||
]);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export { updatePositions, createSortable };
|
||||
406
nested_admin/src/nested-admin/utils.js
Normal file
406
nested_admin/src/nested-admin/utils.js
Normal file
@@ -0,0 +1,406 @@
|
||||
/* globals SelectFilter, DateTimeShortcuts */
|
||||
import $ from "jquery";
|
||||
import "./jquery.djnutils";
|
||||
import { createSortable, updatePositions } from "./sortable";
|
||||
import regexQuote from "./regexquote";
|
||||
import django$ from "./django$";
|
||||
import grp$ from "./grp$";
|
||||
|
||||
var DJNesting = typeof window.DJNesting != "undefined" ? window.DJNesting : {};
|
||||
|
||||
DJNesting.regexQuote = regexQuote;
|
||||
DJNesting.createSortable = createSortable;
|
||||
DJNesting.updatePositions = updatePositions;
|
||||
|
||||
/**
|
||||
* Update attributes based on a regular expression
|
||||
*/
|
||||
DJNesting.updateFormAttributes = function ($elem, search, replace, selector) {
|
||||
if (!selector) {
|
||||
selector = [
|
||||
":input",
|
||||
"span",
|
||||
"table",
|
||||
"iframe",
|
||||
"label",
|
||||
"a",
|
||||
"ul",
|
||||
"p",
|
||||
"img",
|
||||
".djn-group",
|
||||
".djn-inline-form",
|
||||
".cropduster-form",
|
||||
".dal-forward-conf",
|
||||
].join(",");
|
||||
}
|
||||
var addBackMethod = $.fn.addBack ? "addBack" : "andSelf";
|
||||
$elem
|
||||
.find(selector)
|
||||
[addBackMethod]()
|
||||
.each(function () {
|
||||
var $node = $(this),
|
||||
attrs = [
|
||||
"id",
|
||||
"name",
|
||||
"for",
|
||||
"href",
|
||||
"class",
|
||||
"onclick",
|
||||
"data-inline-formset",
|
||||
];
|
||||
|
||||
$.each(attrs, function (i, attrName) {
|
||||
var attrVal = $node.attr(attrName);
|
||||
if (attrVal) {
|
||||
$node.attr(attrName, attrVal.replace(search, replace));
|
||||
if (attrName === "data-inline-formset") {
|
||||
$node.data("inlineFormset", JSON.parse($node.attr(attrName)));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
// update prepopulate ids for function initPrepopulatedFields
|
||||
$elem.find(".prepopulated_field").each(function () {
|
||||
var $node = grp$(this);
|
||||
if (typeof $node.prepopulate !== "function") {
|
||||
$node = django$(this);
|
||||
}
|
||||
var dependencyIds = $.makeArray($node.data("dependency_ids") || []);
|
||||
$node.data(
|
||||
"dependency_ids",
|
||||
$.map(dependencyIds, function (id) {
|
||||
return id.replace(search, replace);
|
||||
})
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
DJNesting.createContainerElement = function () {
|
||||
return;
|
||||
};
|
||||
|
||||
// Slight tweaks to the grappelli functions of the same name
|
||||
// (initRelatedFields and initAutocompleteFields).
|
||||
//
|
||||
// The most notable tweak is the call to $.fn.grp_related_generic() (a
|
||||
// jQuery method provided by django-curated) and the use of
|
||||
// DJNesting.LOOKUP_URLS to determine the ajax lookup urls.
|
||||
//
|
||||
// We abstract this out using form prefix because the way grappelli does it
|
||||
// (adding javascript at the bottom of each formset) doesn't really scale
|
||||
// with nested formsets.
|
||||
|
||||
// The second parameter (groupData) is optional, and only exists to prevent
|
||||
// redundant calls to jQuery() and jQuery.fn.data() in the calling context
|
||||
DJNesting.initRelatedFields = function (prefix, groupData) {
|
||||
if (
|
||||
typeof DJNesting.LOOKUP_URLS != "object" ||
|
||||
!DJNesting.LOOKUP_URLS.related
|
||||
) {
|
||||
return;
|
||||
}
|
||||
var lookupUrls = DJNesting.LOOKUP_URLS;
|
||||
var $inline = $("#" + prefix + "-group");
|
||||
|
||||
if (!groupData) {
|
||||
groupData = $inline.djnData();
|
||||
}
|
||||
var lookupFields = groupData.lookupRelated;
|
||||
|
||||
$inline.djangoFormsetForms().each(function (i, form) {
|
||||
$.each(lookupFields.fk || [], function (i, fk) {
|
||||
$(form)
|
||||
.djangoFormField(fk)
|
||||
.each(function () {
|
||||
grp$(this).grp_related_fk({
|
||||
lookup_url: lookupUrls.related,
|
||||
});
|
||||
});
|
||||
});
|
||||
$.each(lookupFields.m2m || [], function (i, m2m) {
|
||||
$(form)
|
||||
.djangoFormField(m2m)
|
||||
.each(function () {
|
||||
grp$(this).grp_related_m2m({ lookup_url: lookupUrls.m2m });
|
||||
});
|
||||
});
|
||||
$.each(lookupFields.generic || [], function () {
|
||||
var [contentType, objectId] = this;
|
||||
$(form)
|
||||
.djangoFormField(objectId)
|
||||
.each(function () {
|
||||
var $this = $(this);
|
||||
var index = $this.djangoFormIndex();
|
||||
if ($this.hasClass("grp-has-related-lookup")) {
|
||||
$this.parent().find("a.related-lookup").remove();
|
||||
$this.parent().find(".grp-placeholder-related-generic").remove();
|
||||
}
|
||||
grp$($this).grp_related_generic({
|
||||
content_type: `#id_${prefix}-${index}-${contentType}`,
|
||||
object_id: `#id_${prefix}-${index}-${objectId}`,
|
||||
lookup_url: lookupUrls.related,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
DJNesting.initAutocompleteFields = function (prefix, groupData) {
|
||||
if (
|
||||
typeof DJNesting.LOOKUP_URLS != "object" ||
|
||||
!DJNesting.LOOKUP_URLS.related
|
||||
) {
|
||||
return;
|
||||
}
|
||||
var lookupUrls = DJNesting.LOOKUP_URLS;
|
||||
|
||||
var $inline = $("#" + prefix + "-group");
|
||||
|
||||
if (!groupData) {
|
||||
groupData = $inline.djnData();
|
||||
}
|
||||
var lookupFields = groupData.lookupAutocomplete;
|
||||
|
||||
$inline.djangoFormsetForms().each(function (i, form) {
|
||||
$.each(lookupFields.fk || [], function (i, fk) {
|
||||
$(form)
|
||||
.djangoFormField(fk)
|
||||
.each(function () {
|
||||
var $this = $(this),
|
||||
id = $this.attr("id");
|
||||
// An autocomplete widget has already been initialized, return
|
||||
if ($("#" + id + "-autocomplete").length) {
|
||||
return;
|
||||
}
|
||||
grp$($this).grp_autocomplete_fk({
|
||||
lookup_url: lookupUrls.related,
|
||||
autocomplete_lookup_url: lookupUrls.autocomplete,
|
||||
});
|
||||
});
|
||||
});
|
||||
$.each(lookupFields.m2m || [], function (i, m2m) {
|
||||
$(form)
|
||||
.djangoFormField(m2m)
|
||||
.each(function () {
|
||||
var $this = $(this),
|
||||
id = $this.attr("id");
|
||||
// An autocomplete widget has already been initialized, return
|
||||
if ($("#" + id + "-autocomplete").length) {
|
||||
return;
|
||||
}
|
||||
grp$($this).grp_autocomplete_m2m({
|
||||
lookup_url: lookupUrls.m2m,
|
||||
autocomplete_lookup_url: lookupUrls.autocomplete,
|
||||
});
|
||||
});
|
||||
});
|
||||
$.each(lookupFields.generic || [], function () {
|
||||
var [contentType, objectId] = this;
|
||||
$(form)
|
||||
.djangoFormField(objectId)
|
||||
.each(function () {
|
||||
var $this = $(this);
|
||||
var index = $this.djangoFormIndex();
|
||||
// An autocomplete widget has already been initialized, return
|
||||
if ($("#" + $this.attr("id") + "-autocomplete").length) {
|
||||
return;
|
||||
}
|
||||
grp$($this).grp_autocomplete_generic({
|
||||
content_type: `#id_${prefix}-${index}-${contentType}`,
|
||||
object_id: `#id_${prefix}-${index}-${objectId}`,
|
||||
lookup_url: lookupUrls.related,
|
||||
autocomplete_lookup_url: lookupUrls.m2m,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function getLevelPrefix(id) {
|
||||
return id
|
||||
.replace(/^\#?id_/, "")
|
||||
.split(/-(?:empty|__prefix__|\d+)-/g)
|
||||
.slice(0, -1)
|
||||
.join("-");
|
||||
}
|
||||
|
||||
// I very much regret that these are basically copy-pasted from django's
|
||||
// inlines.js, but they're hidden in closure scope so I don't have much choice.
|
||||
DJNesting.DjangoInlines = {
|
||||
initPrepopulatedFields: function (row) {
|
||||
const formPrefix = row.djangoFormPrefix();
|
||||
if (!formPrefix) return;
|
||||
const fields = $("#django-admin-prepopulated-fields-constants").data(
|
||||
"prepopulatedFields"
|
||||
);
|
||||
const fieldNames = new Set();
|
||||
const fieldDependencies = {};
|
||||
|
||||
if (Array.isArray(fields)) {
|
||||
fields.forEach(
|
||||
({ id, name, dependency_list, maxLength, allowUnicode }) => {
|
||||
fieldNames.add(name);
|
||||
const levelPrefix = getLevelPrefix(id);
|
||||
if (typeof fieldDependencies[levelPrefix] !== "object") {
|
||||
fieldDependencies[levelPrefix] = {};
|
||||
}
|
||||
fieldDependencies[levelPrefix][name] = {
|
||||
dependency_list,
|
||||
maxLength,
|
||||
allowUnicode,
|
||||
};
|
||||
}
|
||||
);
|
||||
fieldNames.forEach((name) => {
|
||||
row
|
||||
.find(`.form-row .field-${name}, .form-row.field-${name}`)
|
||||
.each(function () {
|
||||
const $el = $(this);
|
||||
const prefix = $el.djangoFormPrefix();
|
||||
if (!prefix) return;
|
||||
const levelPrefix = getLevelPrefix(prefix);
|
||||
const dep = (fieldDependencies[levelPrefix] || {})[name];
|
||||
if (dep) {
|
||||
$el.addClass("prepopulated_field");
|
||||
const $field = $el.is(":input") ? $el : $el.find(":input");
|
||||
$field.data("dependency_list", dep.dependency_list);
|
||||
$field.data("maxLength", dep.maxLength);
|
||||
$field.data("allowUnicode", dep.allowUnicode);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
if (formPrefix.match(/__prefix__/)) return;
|
||||
row.find(".prepopulated_field").each(function () {
|
||||
var field = $(this),
|
||||
input = field.is(":input") ? field : field.find(":input"),
|
||||
$input = grp$(input),
|
||||
inputFormPrefix = input.djangoFormPrefix(),
|
||||
dependencyList = $input.data("dependency_list") || [],
|
||||
dependencies = [];
|
||||
if (!inputFormPrefix || inputFormPrefix.match(/__prefix__/)) return;
|
||||
if (!dependencyList.length || !$input.prepopulate) {
|
||||
$input = django$(input);
|
||||
dependencyList = $input.data("dependency_list") || [];
|
||||
}
|
||||
$.each(dependencyList, function (i, fieldName) {
|
||||
dependencies.push("#id_" + inputFormPrefix + fieldName);
|
||||
});
|
||||
if (dependencies.length) {
|
||||
$input.prepopulate(
|
||||
dependencies,
|
||||
$input.data("maxLength") || $input.attr("maxlength"),
|
||||
$input.data("allowUnicode")
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
reinitDateTimeShortCuts: function () {
|
||||
// Reinitialize the calendar and clock widgets by force
|
||||
if (typeof window.DateTimeShortcuts !== "undefined") {
|
||||
$(".datetimeshortcuts").remove();
|
||||
DateTimeShortcuts.init();
|
||||
}
|
||||
},
|
||||
updateSelectFilter: function ($form) {
|
||||
// If any SelectFilter widgets are a part of the new form,
|
||||
// instantiate a new SelectFilter instance for it.
|
||||
if (typeof window.SelectFilter !== "undefined") {
|
||||
$form.find(".selectfilter").each(function (index, value) {
|
||||
var namearr = value.name.split("-");
|
||||
SelectFilter.init(value.id, namearr[namearr.length - 1], false);
|
||||
});
|
||||
$form.find(".selectfilterstacked").each(function (index, value) {
|
||||
var namearr = value.name.split("-");
|
||||
SelectFilter.init(value.id, namearr[namearr.length - 1], true);
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function patchSelectFilter() {
|
||||
window.SelectFilter.init = (function (oldFn) {
|
||||
return function init(field_id, field_name, is_stacked) {
|
||||
if (field_id.match(/\-empty\-/)) {
|
||||
return;
|
||||
} else {
|
||||
oldFn.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
})(window.SelectFilter.init);
|
||||
}
|
||||
|
||||
if (typeof window.SelectFilter !== "undefined") {
|
||||
patchSelectFilter();
|
||||
} else {
|
||||
setTimeout(function () {
|
||||
if (typeof window.SelectFilter !== "undefined") {
|
||||
patchSelectFilter();
|
||||
}
|
||||
}, 12);
|
||||
}
|
||||
|
||||
const djangoFuncs = ["prepopulate", "djangoAdminSelect2"];
|
||||
|
||||
djangoFuncs.forEach((funcName) => {
|
||||
(function patchDjangoFunction(callCount) {
|
||||
if (callCount > 2) {
|
||||
return;
|
||||
}
|
||||
if (typeof $.fn[funcName] === "undefined") {
|
||||
return setTimeout(() => patchDjangoFunction(++callCount), 12);
|
||||
}
|
||||
$.fn[funcName] = (function (oldFn) {
|
||||
return function django_fn_patch() {
|
||||
return oldFn.apply(
|
||||
this.filter(
|
||||
':not([id*="-empty-"]):not([id$="-empty"]):not([id*="__prefix__"])'
|
||||
),
|
||||
arguments
|
||||
);
|
||||
};
|
||||
})($.fn[funcName]);
|
||||
})(0);
|
||||
});
|
||||
|
||||
const grpFuncs = [
|
||||
"grp_autocomplete_fk",
|
||||
"grp_autocomplete_generic",
|
||||
"grp_autocomplete_m2m",
|
||||
"grp_collapsible",
|
||||
"grp_collapsible_group",
|
||||
"grp_inline",
|
||||
"grp_related_fk",
|
||||
"grp_related_generic",
|
||||
"grp_related_m2m",
|
||||
"grp_timepicker",
|
||||
"datepicker",
|
||||
"prepopulate",
|
||||
"djangoAdminSelect2",
|
||||
];
|
||||
|
||||
grpFuncs.forEach((funcName) => {
|
||||
(function patchGrpFunction(callCount) {
|
||||
if (callCount > 2) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
typeof window.grp === "undefined" ||
|
||||
typeof window.grp.jQuery.fn[funcName] === "undefined"
|
||||
) {
|
||||
return setTimeout(() => patchGrpFunction(++callCount), 12);
|
||||
}
|
||||
window.grp.jQuery.fn[funcName] = (function (oldFn) {
|
||||
return function grp_fn_patch() {
|
||||
return oldFn.apply(
|
||||
this.filter(
|
||||
':not([id*="-empty-"]):not([id$="-empty"]):not([id*="__prefix__"])'
|
||||
),
|
||||
arguments
|
||||
);
|
||||
};
|
||||
})(window.grp.jQuery.fn[funcName]);
|
||||
})(0);
|
||||
});
|
||||
|
||||
export default DJNesting;
|
||||
390
nested_admin/src/nested_admin.scss
Normal file
390
nested_admin/src/nested_admin.scss
Normal file
@@ -0,0 +1,390 @@
|
||||
/* stylelint-disable no-descending-specificity */
|
||||
|
||||
@mixin cursor($val) {
|
||||
cursor: $val;
|
||||
cursor: -moz-$val;
|
||||
cursor: -webkit-$val;
|
||||
}
|
||||
|
||||
.djn-group .djn-group-nested {
|
||||
float: none;
|
||||
width: auto;
|
||||
margin: 0 10px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.djn-group-nested.grp-stacked h2.djn-collapse-handler,
|
||||
.djn-group-nested.grp-stacked > .grp-tools {
|
||||
display: none;
|
||||
}
|
||||
.djn-group-nested {
|
||||
border-color: transparent;
|
||||
}
|
||||
.grp-tools span.delete {
|
||||
cursor: auto !important;
|
||||
}
|
||||
.djn-group-nested .djn-items .inline-related {
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
|
||||
#grp-content & {
|
||||
margin-bottom: 5px;
|
||||
border: 1px solid #a7a7a7;
|
||||
|
||||
&.djn-item-dragging {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
&.last-related {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.djn-group-nested div.items .module:first-child {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
.nested-placeholder,
|
||||
.djn-group .ui-sortable-placeholder {
|
||||
margin-bottom: 5px;
|
||||
background: #9f9f9f !important;
|
||||
}
|
||||
.djn-group .ui-nestedsortable-error,
|
||||
.djn-group .ui-nestedSortable-error {
|
||||
background: #9f6464 !important;
|
||||
}
|
||||
.ui-sortable .grp-module.ui-sortable-placeholder.ui-nestedSortable-error {
|
||||
background-color: #9f6464 !important;
|
||||
}
|
||||
|
||||
.djn-items {
|
||||
position: relative;
|
||||
min-height: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
.djn-item {
|
||||
overflow: visible;
|
||||
}
|
||||
.djn-item.djn-no-drag:first-child {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
height: 19px;
|
||||
|
||||
& + .djn-item.ui-sortable-helper,
|
||||
& + .djn-item-dragging {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.djn-item-dragging {
|
||||
height: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.djn-tbody.djn-item-dragging {
|
||||
display: none !important;
|
||||
}
|
||||
.djn-tbody.ui-sortable-placeholder td {
|
||||
background: #fbfad0;
|
||||
}
|
||||
|
||||
.djn-collapse-handler-verbose-name {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
#grp-content .grp-tabular .grp-table .grp-tbody {
|
||||
.grp-th,
|
||||
.grp-td {
|
||||
vertical-align: top;
|
||||
overflow: visible;
|
||||
}
|
||||
.grp-tr > td.original:first-child {
|
||||
width: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: #eee;
|
||||
}
|
||||
.grp-tr.djn-has-inlines .grp-td {
|
||||
border-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
#grp-content .grp-tabular .grp-table .grp-thead .grp-th {
|
||||
border-radius: 0;
|
||||
border-top: 0;
|
||||
border-bottom: 0;
|
||||
line-height: 16px;
|
||||
color: #aaa;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#grp-content table.djn-table thead > tr > th {
|
||||
font-size: 11px;
|
||||
line-height: inherit;
|
||||
}
|
||||
#grp-content .grp-tabular .grp-table.djn-table .grp-thead > .grp-tr > .grp-th {
|
||||
padding-top: 1px;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
#grp-content
|
||||
.grp-tabular
|
||||
.grp-table.djn-table
|
||||
.grp-thead
|
||||
> .grp-tr
|
||||
> .grp-th:last-of-type {
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
#grp-content
|
||||
.grp-tabular
|
||||
.grp-table.djn-table
|
||||
.grp-tbody
|
||||
> .grp-tr
|
||||
> .grp-td:first-of-type {
|
||||
border-left: 1px solid #d4d4d4 !important;
|
||||
}
|
||||
|
||||
table.djn-table.grp-table td div.grp-readonly,
|
||||
table.djn-table.grp-table th div.grp-readonly {
|
||||
margin: 0 !important;
|
||||
}
|
||||
.grp-tabular.djn-tabular td.grp-td ul.errorlist {
|
||||
margin: 0 !important;
|
||||
}
|
||||
table.djn-table.grp-table td div.grp-readonly:empty,
|
||||
table.djn-table.grp-table th div.grp-readonly:empty {
|
||||
margin-bottom: -5px !important;
|
||||
}
|
||||
|
||||
table.djn-table.grp-table td > input[type="checkbox"],
|
||||
table.djn-table.grp-table td > input[type="radio"],
|
||||
table.djn-table.grp-table th > input[type="checkbox"],
|
||||
table.djn-table.grp-table th > input[type="radio"] {
|
||||
margin: 3px 0.5ex !important;
|
||||
margin: revert !important;
|
||||
}
|
||||
|
||||
table.djn-table.grp-table td > textarea,
|
||||
table.djn-table.grp-table th > textarea {
|
||||
margin: 0 !important;
|
||||
}
|
||||
// Grappelli is the absolute worst with !important
|
||||
table.djn-table.grp-table td > input[type="text"],
|
||||
table.djn-table.grp-table td > input[type="password"],
|
||||
table.djn-table.grp-table td > input[type="url"],
|
||||
table.djn-table.grp-table td > input[type="email"],
|
||||
table.djn-table.grp-table td > input[type="number"],
|
||||
table.djn-table.grp-table td > input[type="button"],
|
||||
table.djn-table.grp-table td > select,
|
||||
table.djn-table.grp-table td p input[type="text"],
|
||||
table.djn-table.grp-table td p input[type="url"],
|
||||
table.djn-table.grp-table td p input[type="email"],
|
||||
table.djn-table.grp-table td p input[type="number"],
|
||||
table.djn-table.grp-table td p > input[type="button"],
|
||||
table.djn-table.grp-table th > input[type="text"],
|
||||
table.djn-table.grp-table th > input[type="password"],
|
||||
table.djn-table.grp-table th > input[type="url"],
|
||||
table.djn-table.grp-table th > input[type="email"],
|
||||
table.djn-table.grp-table th > input[type="number"],
|
||||
table.djn-table.grp-table th > input[type="button"],
|
||||
table.djn-table.grp-table th > select,
|
||||
table.djn-table.grp-table th p input[type="text"],
|
||||
table.djn-table.grp-table th p input[type="url"],
|
||||
table.djn-table.grp-table th p input[type="email"],
|
||||
table.djn-table.grp-table th p input[type="number"],
|
||||
table.djn-table.grp-table th p > input[type="button"] {
|
||||
vertical-align: middle;
|
||||
margin-top: 0 !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.djn-empty-form {
|
||||
&,
|
||||
& * {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Django (sans grappelli) specific styles
|
||||
#content.colM {
|
||||
.inline-group .tabular .ui-sortable-placeholder tr.has_original td {
|
||||
padding: 1px;
|
||||
}
|
||||
.inline-group.djn-group ul.tools {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.djn-item.module {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
tr.djn-has-inlines td {
|
||||
border-bottom: 1px solid #fff;
|
||||
}
|
||||
|
||||
td.original {
|
||||
width: 0;
|
||||
padding: 2px 0 0 0;
|
||||
}
|
||||
|
||||
td.original.is-sortable {
|
||||
position: relative;
|
||||
width: 15px;
|
||||
}
|
||||
td.original.is-sortable .djn-drag-handler {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 0;
|
||||
display: block;
|
||||
width: 10px;
|
||||
height: 20px;
|
||||
margin: 5px;
|
||||
cursor: move;
|
||||
background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAyCAYAAABcfPsmAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAE5JREFUeNrs0zEKACAMBEEDST6X9+Z92lpYHNgI7naBK9KMdfcch6rK9lvdeWYOJXXnESEN1d2HH9J9hhSkEFKQQkhBClKQghSkPNYSYADFZiuygfao+AAAAABJRU5ErkJggg==")
|
||||
no-repeat top left;
|
||||
background-size: 10px 25px;
|
||||
@include cursor(grab);
|
||||
}
|
||||
|
||||
// (Optional) Apply a "closed-hand" cursor during drag operation.
|
||||
td.original.is-sortable .djn-drag-handler:active {
|
||||
@include cursor(grabbing);
|
||||
}
|
||||
td.original.is-sortable p + .djn-drag-handler {
|
||||
top: 20px;
|
||||
}
|
||||
|
||||
td.original.is-sortable p {
|
||||
top: 0;
|
||||
left: 19px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
fieldset.has-inlines > .djn-form-row-last {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// polymorphic
|
||||
.polymorphic-add-choice {
|
||||
.grp-tools {
|
||||
overflow: visible;
|
||||
}
|
||||
.grp-tools li {
|
||||
float: none;
|
||||
}
|
||||
.grp-tools li:first-child,
|
||||
.grp-tools li:last-child {
|
||||
padding: 4px 8px;
|
||||
}
|
||||
.grp-tools a {
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
.grp-tools > li > a {
|
||||
min-width: 24px;
|
||||
min-height: 24px;
|
||||
}
|
||||
.grp-tools .polymorphic-type-menu {
|
||||
right: 0.5em;
|
||||
left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.grp-tools.grp-related-widget-tools a.add-another {
|
||||
top: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.grp-td > .grp-related-widget-wrapper .grp-related-widget-tools {
|
||||
overflow: visible;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.select2-container + .grp-tools.grp-related-widget-tools {
|
||||
position: relative;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#grp-content .grp-group > .grp-items > .grp-module > .grp-tabular {
|
||||
background: #fff;
|
||||
border: 2px solid #ccc;
|
||||
margin-bottom: 5px;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
table.grp-table.djn-table td.djn-td > input[type="text"],
|
||||
table.grp-table.djn-table td.djn-td > input[type="password"],
|
||||
table.grp-table.djn-table td.djn-td > input[type="url"],
|
||||
table.grp-table.djn-table td.djn-td > input[type="email"],
|
||||
table.grp-table.djn-table td.djn-td > input[type="number"],
|
||||
table.grp-table.djn-table td.djn-td > input[type="button"],
|
||||
table.grp-table.djn-table td.djn-td > select,
|
||||
table.grp-table.djn-table td.djn-td p input[type="text"],
|
||||
table.grp-table.djn-table td.djn-td p input[type="url"],
|
||||
table.grp-table.djn-table td.djn-td p input[type="email"],
|
||||
table.grp-table.djn-table td.djn-td p input[type="number"],
|
||||
table.grp-table.djn-table td.djn-td p > input[type="button"],
|
||||
table.grp-table.djn-table td.djn-td div.grp-related-widget-wrapper,
|
||||
table.grp-table.djn-table th.djn-th > input[type="text"],
|
||||
table.grp-table.djn-table th.djn-th > input[type="password"],
|
||||
table.grp-table.djn-table th.djn-th > input[type="url"],
|
||||
table.grp-table.djn-table th.djn-th > input[type="email"],
|
||||
table.grp-table.djn-table th.djn-th > input[type="number"],
|
||||
table.grp-table.djn-table th.djn-th > input[type="button"],
|
||||
table.grp-table.djn-table th.djn-th > select,
|
||||
table.grp-table.djn-table th.djn-th p input[type="text"],
|
||||
table.grp-table.djn-table th.djn-th p input[type="url"],
|
||||
table.grp-table.djn-table th.djn-th p input[type="email"],
|
||||
table.grp-table.djn-table th.djn-th p input[type="number"],
|
||||
table.grp-table.djn-table th.djn-th p > input[type="button"],
|
||||
table.grp-table.djn-table th.djn-th div.grp-related-widget-wrapper {
|
||||
vertical-align: baseline;
|
||||
margin-top: 0 !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
table.grp-table.djn-table td.djn-td a.fb_show,
|
||||
table.grp-table.djn-table td.djn-td a.related-lookup,
|
||||
table.grp-table.djn-table td.djn-td .ui-datepicker-trigger,
|
||||
table.grp-table.djn-table td.djn-td .ui-timepicker-trigger,
|
||||
table.grp-table.djn-table th.djn-th a.fb_show,
|
||||
table.grp-table.djn-table th.djn-th a.related-lookup,
|
||||
table.grp-table.djn-table th.djn-th .ui-datepicker-trigger,
|
||||
table.grp-table.djn-table th.djn-th .ui-timepicker-trigger {
|
||||
margin: 0 0 0 -25px !important;
|
||||
}
|
||||
|
||||
table.grp-table.djn-table td.djn-td .grp-autocomplete-wrapper-m2m,
|
||||
table.grp-table.djn-table td.djn-td .grp-autocomplete-wrapper-fk,
|
||||
table.grp-table.djn-table th.djn-th .grp-autocomplete-wrapper-m2m,
|
||||
table.grp-table.djn-table th.djn-th .grp-autocomplete-wrapper-fk {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
table.grp-table.djn-table td.djn-td > input[type="file"],
|
||||
table.grp-table.djn-table td.djn-td > input[type="checkbox"],
|
||||
table.grp-table.djn-table td.djn-td > input[type="radio"],
|
||||
table.grp-table.djn-table td.djn-td > select,
|
||||
table.grp-table.djn-table td.djn-td p input[type="text"],
|
||||
table.grp-table.djn-table th.djn-th > input[type="file"],
|
||||
table.grp-table.djn-table th.djn-th > input[type="checkbox"],
|
||||
table.grp-table.djn-table th.djn-th > input[type="radio"],
|
||||
table.grp-table.djn-table th.djn-th > select,
|
||||
table.grp-table.djn-table th.djn-th p input[type="text"] {
|
||||
margin-top: 0 !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
Reference in New Issue
Block a user