django-unfold 0.67.0__py3-none-any.whl → 0.69.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {django_unfold-0.67.0.dist-info → django_unfold-0.69.0.dist-info}/METADATA +33 -41
- {django_unfold-0.67.0.dist-info → django_unfold-0.69.0.dist-info}/RECORD +54 -47
- unfold/admin.py +46 -15
- unfold/components.py +2 -2
- unfold/contrib/filters/admin/choice_filters.py +13 -1
- unfold/contrib/filters/admin/mixins.py +3 -3
- unfold/contrib/filters/admin/numeric_filters.py +6 -6
- unfold/contrib/forms/widgets.py +5 -5
- unfold/contrib/inlines/admin.py +3 -3
- unfold/contrib/inlines/forms.py +5 -4
- unfold/dataclasses.py +13 -13
- unfold/datasets.py +90 -0
- unfold/decorators.py +19 -19
- unfold/fields.py +3 -5
- unfold/forms.py +41 -22
- unfold/mixins/__init__.py +2 -1
- unfold/mixins/action_model_admin.py +11 -10
- unfold/mixins/base_model_admin.py +6 -6
- unfold/mixins/dataset_model_admin.py +62 -0
- unfold/settings.py +1 -0
- unfold/sites.py +19 -18
- unfold/static/admin/js/actions.js +246 -0
- unfold/static/unfold/css/styles.css +2 -2
- unfold/static/unfold/fonts/material-symbols/Material-Symbols-Outlined.woff2 +0 -0
- unfold/static/unfold/js/app.js +3 -1
- unfold/styles.css +21 -16
- unfold/templates/admin/actions.html +2 -2
- unfold/templates/admin/change_form.html +10 -2
- unfold/templates/admin/change_list.html +1 -1
- unfold/templates/admin/change_list_results.html +10 -62
- unfold/templates/admin/dataset_actions.html +50 -0
- unfold/templates/admin/edit_inline/stacked.html +2 -8
- unfold/templates/admin/edit_inline/tabular.html +1 -7
- unfold/templates/admin/includes/fieldset.html +1 -3
- unfold/templates/admin/search_form.html +6 -4
- unfold/templates/registration/password_change_done.html +3 -4
- unfold/templates/registration/password_change_form.html +10 -6
- unfold/templates/unfold/helpers/change_list_actions.html +1 -1
- unfold/templates/unfold/helpers/change_list_headers.html +65 -0
- unfold/templates/unfold/helpers/dataset.html +31 -0
- unfold/templates/unfold/helpers/edit_inline/tabular_field.html +1 -1
- unfold/templates/unfold/helpers/empty_results.html +6 -4
- unfold/templates/unfold/helpers/field_readonly_value_file.html +1 -1
- unfold/templates/unfold/helpers/fieldsets_tabs.html +9 -11
- unfold/templates/unfold/helpers/inline_heading.html +11 -0
- unfold/templates/unfold/helpers/tab_items.html +9 -1
- unfold/templatetags/unfold.py +64 -82
- unfold/templatetags/unfold_list.py +76 -8
- unfold/typing.py +5 -6
- unfold/utils.py +9 -9
- unfold/views.py +15 -1
- unfold/widgets.py +31 -31
- {django_unfold-0.67.0.dist-info → django_unfold-0.69.0.dist-info}/WHEEL +0 -0
- {django_unfold-0.67.0.dist-info → django_unfold-0.69.0.dist-info}/licenses/LICENSE.md +0 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from django.contrib.admin import helpers
|
|
4
|
+
from django.contrib.admin.views import main
|
|
5
|
+
from django.contrib.admin.views.main import IGNORED_PARAMS
|
|
6
|
+
from django.http import HttpRequest
|
|
7
|
+
from django.template.response import TemplateResponse
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DatasetModelAdminMixin:
|
|
11
|
+
def changeform_view(
|
|
12
|
+
self,
|
|
13
|
+
request: HttpRequest,
|
|
14
|
+
object_id: str | None = None,
|
|
15
|
+
form_url: str = "",
|
|
16
|
+
extra_context: dict[str, Any] | None = None,
|
|
17
|
+
) -> TemplateResponse:
|
|
18
|
+
self.request = request
|
|
19
|
+
extra_context = extra_context or {}
|
|
20
|
+
datasets = self.get_changeform_datasets(request)
|
|
21
|
+
|
|
22
|
+
# Monkeypatch IGNORED_PARAMS to add dataset page and search arguments into ignored params
|
|
23
|
+
ignored_params = []
|
|
24
|
+
for dataset in datasets:
|
|
25
|
+
ignored_params.append(f"{dataset.model._meta.model_name}-q")
|
|
26
|
+
ignored_params.append(f"{dataset.model._meta.model_name}-p")
|
|
27
|
+
|
|
28
|
+
main.IGNORED_PARAMS = (*IGNORED_PARAMS, *ignored_params)
|
|
29
|
+
|
|
30
|
+
rendered_datasets = []
|
|
31
|
+
for dataset in datasets:
|
|
32
|
+
rendered_datasets.append(
|
|
33
|
+
dataset(
|
|
34
|
+
request=request,
|
|
35
|
+
extra_context={
|
|
36
|
+
"object": object_id,
|
|
37
|
+
},
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
extra_context["datasets"] = rendered_datasets
|
|
42
|
+
selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME)
|
|
43
|
+
|
|
44
|
+
if (
|
|
45
|
+
request.method == "POST"
|
|
46
|
+
and selected
|
|
47
|
+
and "dataset" in request.POST
|
|
48
|
+
and helpers.ACTION_CHECKBOX_NAME in request.POST
|
|
49
|
+
):
|
|
50
|
+
dataset = None
|
|
51
|
+
for item in rendered_datasets:
|
|
52
|
+
if item.id == request.POST["dataset"]:
|
|
53
|
+
dataset = item
|
|
54
|
+
|
|
55
|
+
response = dataset.model_admin_instance.response_action(
|
|
56
|
+
request, queryset=dataset.cl.get_queryset(request)
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
if response:
|
|
60
|
+
return response
|
|
61
|
+
|
|
62
|
+
return super().changeform_view(request, object_id, form_url, extra_context)
|
unfold/settings.py
CHANGED
unfold/sites.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import copy
|
|
2
2
|
import time
|
|
3
|
+
from collections.abc import Callable
|
|
3
4
|
from http import HTTPStatus
|
|
4
|
-
from typing import Any
|
|
5
|
+
from typing import Any
|
|
5
6
|
from urllib.parse import parse_qs, urlparse
|
|
6
7
|
|
|
7
8
|
from django.contrib.admin import AdminSite
|
|
@@ -46,7 +47,11 @@ class UnfoldAdminSite(AdminSite):
|
|
|
46
47
|
|
|
47
48
|
super().__init__(name)
|
|
48
49
|
|
|
49
|
-
|
|
50
|
+
custom_login_form = get_config(self.settings_name)["LOGIN"]["form"]
|
|
51
|
+
|
|
52
|
+
if custom_login_form is not None:
|
|
53
|
+
self.login_form = import_string(custom_login_form)
|
|
54
|
+
elif self.login_form is None:
|
|
50
55
|
self.login_form = AuthenticationForm
|
|
51
56
|
|
|
52
57
|
def get_urls(self) -> list[URLPattern]:
|
|
@@ -141,7 +146,7 @@ class UnfoldAdminSite(AdminSite):
|
|
|
141
146
|
return context
|
|
142
147
|
|
|
143
148
|
def index(
|
|
144
|
-
self, request: HttpRequest, extra_context:
|
|
149
|
+
self, request: HttpRequest, extra_context: dict[str, Any] | None = None
|
|
145
150
|
) -> TemplateResponse:
|
|
146
151
|
app_list = self.get_app_list(request)
|
|
147
152
|
|
|
@@ -166,7 +171,7 @@ class UnfoldAdminSite(AdminSite):
|
|
|
166
171
|
)
|
|
167
172
|
|
|
168
173
|
def toggle_sidebar(
|
|
169
|
-
self, request: HttpRequest, extra_context:
|
|
174
|
+
self, request: HttpRequest, extra_context: dict[str, Any] | None = None
|
|
170
175
|
) -> HttpResponse:
|
|
171
176
|
if "toggle_sidebar" not in request.session:
|
|
172
177
|
request.session["toggle_sidebar"] = True
|
|
@@ -214,14 +219,14 @@ class UnfoldAdminSite(AdminSite):
|
|
|
214
219
|
request: HttpRequest,
|
|
215
220
|
app_list: list[dict[str, Any]],
|
|
216
221
|
search_term: str,
|
|
217
|
-
allowed_models:
|
|
222
|
+
allowed_models: list[str] | None = None,
|
|
218
223
|
) -> list[SearchResult]:
|
|
219
224
|
results = []
|
|
220
225
|
|
|
221
226
|
for app in app_list:
|
|
222
227
|
for model in app["models"]:
|
|
223
228
|
# Skip models which are not allowed
|
|
224
|
-
if isinstance(allowed_models,
|
|
229
|
+
if isinstance(allowed_models, list | tuple):
|
|
225
230
|
if model["model"]._meta.label.lower() not in [
|
|
226
231
|
m.lower() for m in allowed_models
|
|
227
232
|
]:
|
|
@@ -263,7 +268,7 @@ class UnfoldAdminSite(AdminSite):
|
|
|
263
268
|
return results
|
|
264
269
|
|
|
265
270
|
def search(
|
|
266
|
-
self, request: HttpRequest, extra_context:
|
|
271
|
+
self, request: HttpRequest, extra_context: dict[str, Any] | None = None
|
|
267
272
|
) -> TemplateResponse:
|
|
268
273
|
start_time = time.time()
|
|
269
274
|
|
|
@@ -302,10 +307,10 @@ class UnfoldAdminSite(AdminSite):
|
|
|
302
307
|
self._get_config("COMMAND", request).get("search_models"), request
|
|
303
308
|
)
|
|
304
309
|
|
|
305
|
-
if search_models is True or isinstance(search_models,
|
|
310
|
+
if search_models is True or isinstance(search_models, list | tuple):
|
|
306
311
|
allowed_models = (
|
|
307
312
|
search_models
|
|
308
|
-
if isinstance(search_models,
|
|
313
|
+
if isinstance(search_models, list | tuple)
|
|
309
314
|
else None
|
|
310
315
|
)
|
|
311
316
|
|
|
@@ -340,7 +345,7 @@ class UnfoldAdminSite(AdminSite):
|
|
|
340
345
|
)
|
|
341
346
|
|
|
342
347
|
def password_change(
|
|
343
|
-
self, request: HttpRequest, extra_context:
|
|
348
|
+
self, request: HttpRequest, extra_context: dict[str, Any] | None = None
|
|
344
349
|
) -> HttpResponse:
|
|
345
350
|
from django.contrib.auth.views import PasswordChangeView
|
|
346
351
|
|
|
@@ -462,7 +467,7 @@ class UnfoldAdminSite(AdminSite):
|
|
|
462
467
|
return tabs
|
|
463
468
|
|
|
464
469
|
def _call_permission_callback(
|
|
465
|
-
self, callback:
|
|
470
|
+
self, callback: str | Callable | None, request: HttpRequest
|
|
466
471
|
) -> bool:
|
|
467
472
|
if callback is None:
|
|
468
473
|
return True
|
|
@@ -490,7 +495,7 @@ class UnfoldAdminSite(AdminSite):
|
|
|
490
495
|
return target
|
|
491
496
|
|
|
492
497
|
def _get_is_active(
|
|
493
|
-
self, request: HttpRequest, link:
|
|
498
|
+
self, request: HttpRequest, link: str | Callable, is_tab: bool = False
|
|
494
499
|
) -> bool:
|
|
495
500
|
if not isinstance(link, str):
|
|
496
501
|
link = str(link)
|
|
@@ -545,9 +550,7 @@ class UnfoldAdminSite(AdminSite):
|
|
|
545
550
|
if key in config and config[key]:
|
|
546
551
|
return self._get_value(config[key], *args)
|
|
547
552
|
|
|
548
|
-
def _get_theme_images(
|
|
549
|
-
self, key: str, *args: Any
|
|
550
|
-
) -> Union[dict[str, str], str, None]:
|
|
553
|
+
def _get_theme_images(self, key: str, *args: Any) -> dict[str, str] | str | None:
|
|
551
554
|
images = self._get_config(key, *args)
|
|
552
555
|
|
|
553
556
|
if isinstance(images, dict):
|
|
@@ -613,9 +616,7 @@ class UnfoldAdminSite(AdminSite):
|
|
|
613
616
|
for item in items
|
|
614
617
|
]
|
|
615
618
|
|
|
616
|
-
def _get_value(
|
|
617
|
-
self, value: Union[str, Callable, lazy, None], *args: Any
|
|
618
|
-
) -> Optional[str]:
|
|
619
|
+
def _get_value(self, value: str | Callable | None, *args: Any) -> str | None:
|
|
619
620
|
if value is None:
|
|
620
621
|
return None
|
|
621
622
|
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/*global gettext, interpolate, ngettext, Actions*/
|
|
2
|
+
"use strict";
|
|
3
|
+
{
|
|
4
|
+
function show(options, selector) {
|
|
5
|
+
options.parent.querySelectorAll(selector).forEach(function (el) {
|
|
6
|
+
el.classList.remove("hidden");
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function hide(options, selector) {
|
|
11
|
+
options.parent.querySelectorAll(selector).forEach(function (el) {
|
|
12
|
+
el.classList.add("hidden");
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function showQuestion(options) {
|
|
17
|
+
hide(options, options.acrossClears);
|
|
18
|
+
show(options, options.acrossQuestions);
|
|
19
|
+
hide(options, options.allContainer);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function showClear(options) {
|
|
23
|
+
show(options, options.acrossClears);
|
|
24
|
+
hide(options, options.acrossQuestions);
|
|
25
|
+
options.parent
|
|
26
|
+
.querySelector(options.actionContainer)
|
|
27
|
+
.classList.remove(options.selectedClass);
|
|
28
|
+
show(options, options.allContainer);
|
|
29
|
+
hide(options, options.counterContainer);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function reset(options) {
|
|
33
|
+
hide(options, options.acrossClears);
|
|
34
|
+
hide(options, options.acrossQuestions);
|
|
35
|
+
hide(options, options.allContainer);
|
|
36
|
+
show(options, options.counterContainer);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function clearAcross(options) {
|
|
40
|
+
reset(options);
|
|
41
|
+
const acrossInputs = options.parent.querySelectorAll(options.acrossInput);
|
|
42
|
+
acrossInputs.forEach(function (acrossInput) {
|
|
43
|
+
acrossInput.value = 0;
|
|
44
|
+
acrossInput.dispatchEvent(new Event("input"));
|
|
45
|
+
});
|
|
46
|
+
options.parent
|
|
47
|
+
.querySelector(options.actionContainer)
|
|
48
|
+
.classList.remove(options.selectedClass);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function checker(actionCheckboxes, options, checked) {
|
|
52
|
+
if (checked) {
|
|
53
|
+
showQuestion(options);
|
|
54
|
+
} else {
|
|
55
|
+
reset(options);
|
|
56
|
+
}
|
|
57
|
+
actionCheckboxes.forEach(function (el) {
|
|
58
|
+
el.checked = checked;
|
|
59
|
+
el.closest("tr").classList.toggle(options.selectedClass, checked);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function updateCounter(actionCheckboxes, options) {
|
|
64
|
+
const sel = Array.from(actionCheckboxes).filter(function (el) {
|
|
65
|
+
return el.checked;
|
|
66
|
+
}).length;
|
|
67
|
+
const counter = options.parent.querySelector(options.counterContainer);
|
|
68
|
+
// data-actions-icnt is defined in the generated HTML
|
|
69
|
+
// and contains the total amount of objects in the queryset
|
|
70
|
+
const actions_icnt = Number(counter.dataset.actionsIcnt);
|
|
71
|
+
counter.textContent = interpolate(
|
|
72
|
+
ngettext(
|
|
73
|
+
"%(sel)s of %(cnt)s selected",
|
|
74
|
+
"%(sel)s of %(cnt)s selected",
|
|
75
|
+
sel
|
|
76
|
+
),
|
|
77
|
+
{
|
|
78
|
+
sel: sel,
|
|
79
|
+
cnt: actions_icnt,
|
|
80
|
+
},
|
|
81
|
+
true
|
|
82
|
+
);
|
|
83
|
+
const allToggle = options.parent.querySelector(".action-toggle");
|
|
84
|
+
allToggle.checked = sel === actionCheckboxes.length;
|
|
85
|
+
if (allToggle.checked) {
|
|
86
|
+
showQuestion(options);
|
|
87
|
+
} else {
|
|
88
|
+
clearAcross(options);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const defaults = {
|
|
93
|
+
actionContainer: "div.actions",
|
|
94
|
+
counterContainer: "span.action-counter",
|
|
95
|
+
allContainer: "div.actions span.all",
|
|
96
|
+
acrossInput: "div.actions input.select-across",
|
|
97
|
+
acrossQuestions: "div.actions span.question",
|
|
98
|
+
acrossClears: "div.actions span.clear",
|
|
99
|
+
allToggleId: "action-toggle",
|
|
100
|
+
selectedClass: "selected",
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
window.Actions = function (actionCheckboxes, options) {
|
|
104
|
+
options = Object.assign({}, defaults, options);
|
|
105
|
+
let list_editable_changed = false;
|
|
106
|
+
let lastChecked = null;
|
|
107
|
+
let shiftPressed = false;
|
|
108
|
+
|
|
109
|
+
document.addEventListener("keydown", (event) => {
|
|
110
|
+
shiftPressed = event.shiftKey;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
document.addEventListener("keyup", (event) => {
|
|
114
|
+
shiftPressed = event.shiftKey;
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const allToggle = options.parent.querySelector(".action-toggle");
|
|
118
|
+
allToggle.addEventListener("click", function (event) {
|
|
119
|
+
checker(actionCheckboxes, options, this.checked);
|
|
120
|
+
updateCounter(actionCheckboxes, options);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
options.parent
|
|
124
|
+
.querySelectorAll(options.acrossQuestions + " a")
|
|
125
|
+
.forEach(function (el) {
|
|
126
|
+
el.addEventListener("click", function (event) {
|
|
127
|
+
event.preventDefault();
|
|
128
|
+
const acrossInputs = options.parent.querySelectorAll(
|
|
129
|
+
options.acrossInput
|
|
130
|
+
);
|
|
131
|
+
acrossInputs.forEach(function (acrossInput) {
|
|
132
|
+
acrossInput.value = 1;
|
|
133
|
+
acrossInput.dispatchEvent(new Event("input"));
|
|
134
|
+
});
|
|
135
|
+
showClear(options);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
options.parent
|
|
140
|
+
.querySelectorAll(options.acrossClears + " a")
|
|
141
|
+
.forEach(function (el) {
|
|
142
|
+
el.addEventListener("click", function (event) {
|
|
143
|
+
event.preventDefault();
|
|
144
|
+
options.parent.querySelector(".action-toggle").checked = false;
|
|
145
|
+
clearAcross(options);
|
|
146
|
+
checker(actionCheckboxes, options, false);
|
|
147
|
+
updateCounter(actionCheckboxes, options);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
function affectedCheckboxes(target, withModifier) {
|
|
152
|
+
const multiSelect = lastChecked && withModifier && lastChecked !== target;
|
|
153
|
+
if (!multiSelect) {
|
|
154
|
+
return [target];
|
|
155
|
+
}
|
|
156
|
+
const checkboxes = Array.from(actionCheckboxes);
|
|
157
|
+
const targetIndex = checkboxes.findIndex((el) => el === target);
|
|
158
|
+
const lastCheckedIndex = checkboxes.findIndex((el) => el === lastChecked);
|
|
159
|
+
const startIndex = Math.min(targetIndex, lastCheckedIndex);
|
|
160
|
+
const endIndex = Math.max(targetIndex, lastCheckedIndex);
|
|
161
|
+
const filtered = checkboxes.filter(
|
|
162
|
+
(el, index) => startIndex <= index && index <= endIndex
|
|
163
|
+
);
|
|
164
|
+
return filtered;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const resultList = options.parent.querySelector(".result-list").tBodies;
|
|
168
|
+
Array.from(resultList).forEach(function (el) {
|
|
169
|
+
el.addEventListener("change", function (event) {
|
|
170
|
+
const target = event.target;
|
|
171
|
+
if (target.classList.contains("action-select")) {
|
|
172
|
+
const checkboxes = affectedCheckboxes(target, shiftPressed);
|
|
173
|
+
checker(checkboxes, options, target.checked);
|
|
174
|
+
updateCounter(actionCheckboxes, options);
|
|
175
|
+
lastChecked = target;
|
|
176
|
+
} else {
|
|
177
|
+
list_editable_changed = true;
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
options.parent
|
|
183
|
+
.querySelector("button[name=index]")
|
|
184
|
+
.addEventListener("click", function (event) {
|
|
185
|
+
if (list_editable_changed) {
|
|
186
|
+
const confirmed = confirm(
|
|
187
|
+
gettext(
|
|
188
|
+
"You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."
|
|
189
|
+
)
|
|
190
|
+
);
|
|
191
|
+
if (!confirmed) {
|
|
192
|
+
event.preventDefault();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const el = options.parent.querySelector("input[name=_save]");
|
|
198
|
+
|
|
199
|
+
// The button does not exist if no fields are editable.
|
|
200
|
+
if (el) {
|
|
201
|
+
el.addEventListener("click", function (event) {
|
|
202
|
+
if (document.querySelector("[name=action]").value) {
|
|
203
|
+
const text = list_editable_changed
|
|
204
|
+
? gettext(
|
|
205
|
+
"You have selected an action, but you haven’t saved your changes to individual fields yet. Please click OK to save. You’ll need to re-run the action."
|
|
206
|
+
)
|
|
207
|
+
: gettext(
|
|
208
|
+
"You have selected an action, and you haven’t made any changes on individual fields. You’re probably looking for the Go button rather than the Save button."
|
|
209
|
+
);
|
|
210
|
+
if (!confirm(text)) {
|
|
211
|
+
event.preventDefault();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Sync counter when navigating to the page, such as through the back
|
|
218
|
+
// button.
|
|
219
|
+
window.addEventListener("pageshow", (event) =>
|
|
220
|
+
updateCounter(actionCheckboxes, options)
|
|
221
|
+
);
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
// Call function fn when the DOM is loaded and ready. If it is already
|
|
225
|
+
// loaded, call the function now.
|
|
226
|
+
// http://youmightnotneedjquery.com/#ready
|
|
227
|
+
function ready(fn) {
|
|
228
|
+
if (document.readyState !== "loading") {
|
|
229
|
+
fn();
|
|
230
|
+
} else {
|
|
231
|
+
document.addEventListener("DOMContentLoaded", fn);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
ready(function () {
|
|
236
|
+
document.querySelectorAll(".result-list-wrapper").forEach(function (el) {
|
|
237
|
+
const actionsEls = el.querySelectorAll("tr input.action-select");
|
|
238
|
+
|
|
239
|
+
if (actionsEls.length > 0) {
|
|
240
|
+
Actions(actionsEls, {
|
|
241
|
+
parent: el,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
}
|