Skip to content

Commit c18df58

Browse files
authored
Ported to Bootstrap5 and dropped jQuery support (#169)
* Ported bootstrap_modal_forms to Bootstrap5 and dropped jQuery dependency * Added documentation for Bootstrap5
1 parent bbbdc53 commit c18df58

File tree

3 files changed

+243
-2
lines changed

3 files changed

+243
-2
lines changed

README.rst

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,17 @@ IMPORTANT: Adjust Bootstrap and jQuery file paths to match yours, but include ``
4848

4949
<body>
5050
<script src="{% static 'assets/js/bootstrap.js' %}"></script>
51+
52+
<!-- Bootstrap 4 -->
5153
<script src="{% static 'assets/js/jquery.js' %}"></script>
5254
<script src="{% static 'js/jquery.bootstrap.modal.forms.js' %}"></script>
5355
<!-- You can alternatively load the minified version -->
5456
<script src="{% static 'js/jquery.bootstrap.modal.forms.min.js' %}"></script>
57+
58+
<!-- Bootstrap 5 -->
59+
<script src="{% static 'js/bootstrap5.modal.forms.js' %}"></script>
60+
<!-- You can alternatively load the minified version -->
61+
<script src="{% static 'js/bootstrap5.modal.forms.min.js' %}"></script>
5562
</body>
5663

5764
How it works?
@@ -61,13 +68,21 @@ How it works?
6168
index.html
6269

6370
<script type="text/javascript">
64-
$(document).ready(function() {
6571
72+
// BS4
73+
$(document).ready(function() {
6674
$("#create-book").modalForm({
6775
formURL: "{% url 'create_book' %}"
6876
});
77+
});
6978
79+
// BS5
80+
document.addEventListener('DOMContentLoaded', (e) => {
81+
modalForm(document.getElementById('create-book'), {
82+
formURL: "{% url 'create_book' %}"
83+
})
7084
});
85+
7186
</script>
7287

7388
1. Click event on html element instantiated with ``modalForm`` opens modal
@@ -211,13 +226,21 @@ Add script to the template from #5 and bind the ``modalForm`` to the trigger ele
211226
index.html
212227

213228
<script type="text/javascript">
214-
$(document).ready(function() {
215229
230+
// BS4
231+
$(document).ready(function() {
216232
$("#create-book").modalForm({
217233
formURL: "{% url 'create_book' %}"
218234
});
235+
});
219236
237+
// BS5
238+
document.addEventListener('DOMContentLoaded', (e) => {
239+
modalForm(document.getElementById('create-book'), {
240+
formURL: "{% url 'create_book' %}"
241+
})
220242
});
243+
221244
</script>
222245

223246
Async create/update with or without modal closing on submit
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
/*
2+
django-bootstrap-modal-forms
3+
version : 2.x
4+
Copyright (c) 2021 Marcel Rupp
5+
*/
6+
7+
// Open modal & load the form at formURL to the modalContent element
8+
const modalFormCallback = function (settings) {
9+
let modal = document.querySelector(settings.modalID);
10+
let content = modal.querySelector(settings.modalContent);
11+
12+
let modalInstance = bootstrap.Modal.getInstance(modal);
13+
if (modalInstance === null) {
14+
modalInstance = new bootstrap.Modal(modal, {
15+
keyboard: false
16+
})
17+
}
18+
19+
fetch(settings.formURL).then(res => {
20+
return res.text();
21+
}).then(data => {
22+
content.innerHTML = data;
23+
}).then(() => {
24+
modalInstance.show();
25+
26+
let form = modal.querySelector(settings.modalForm);
27+
if (form) {
28+
form.action = settings.formURL;
29+
addEventHandlers(modal, form, settings)
30+
}
31+
});
32+
};
33+
34+
const addEventHandlers = function (modal, form, settings) {
35+
form.addEventListener('submit', (event) => {
36+
if (settings.isDeleteForm === false) {
37+
event.preventDefault();
38+
isFormValid(settings, submitForm);
39+
return false;
40+
}
41+
});
42+
43+
modal.addEventListener('hidden.bs.modal', (event) => {
44+
let content = modal.querySelector(settings.modalContent);
45+
while (content.lastChild) {
46+
content.removeChild(content.lastChild);
47+
}
48+
});
49+
};
50+
51+
// Check if form.is_valid() & either show errors or submit it via callback
52+
const isFormValid = function (settings, callback) {
53+
let modal = document.querySelector(settings.modalID);
54+
let form = modal.querySelector(settings.modalForm);
55+
const headers = new Headers();
56+
headers.append('X-Requested-With', 'XMLHttpRequest');
57+
58+
let btnSubmit = modal.querySelector('button[type="submit"]');
59+
btnSubmit.disabled = true;
60+
fetch(form.action, {
61+
headers: headers,
62+
method: form.method,
63+
body: new FormData(form),
64+
}).then(res => {
65+
return res.text();
66+
}).then(data => {
67+
if (data.includes(settings.errorClass)) {
68+
modal.querySelector(settings.modalContent).innerHTML = data;
69+
70+
form = modal.querySelector(settings.modalForm);
71+
if (!form) {
72+
console.error('no form present in response')
73+
return;
74+
}
75+
76+
form.action = settings.formURL;
77+
addEventHandlers(modal, form, settings)
78+
} else {
79+
callback(settings);
80+
}
81+
});
82+
};
83+
84+
// Submit form callback function
85+
const submitForm = function (settings) {
86+
let modal = document.querySelector(settings.modalID);
87+
let form = modal.querySelector(settings.modalForm);
88+
89+
if (!settings.asyncUpdate) {
90+
form.submit();
91+
} else {
92+
let asyncSettingsValid = validateAsyncSettings(settings.asyncSettings);
93+
if (asyncSettingsValid) {
94+
let asyncSettings = settings.asyncSettings;
95+
// Serialize form data
96+
let formData = new FormData(form);
97+
// Add asyncUpdate and check for it in save method of CreateUpdateAjaxMixin
98+
formData.append("asyncUpdate", "True");
99+
100+
fetch(form.action, {
101+
method: form.method,
102+
body: formData,
103+
}).then(res => {
104+
return res.text();
105+
}).then(data => {
106+
let body = document.body;
107+
if (body === undefined) {
108+
console.error("django-bootstrap-modal-forms: <body> element missing in your html.");
109+
return;
110+
}
111+
112+
let doc = new DOMParser().parseFromString(asyncSettings.successMessage, "text/xml");
113+
body.insertBefore(doc.firstChild, body.firstChild);
114+
115+
if (asyncSettings.dataUrl) {
116+
// Update page without refresh
117+
fetch(asyncSettings.dataUrl).then(res => res.json()).then(data => {
118+
// Update page
119+
let dataElement = document.querySelector(asyncSettings.dataElementId);
120+
if (dataElement) {
121+
dataElement.innerHTML = data[asyncSettings.dataKey];
122+
}
123+
124+
// Add modalForm to trigger element after async page update
125+
if (asyncSettings.addModalFormFunction) {
126+
asyncSettings.addModalFormFunction();
127+
}
128+
129+
if (asyncSettings.closeOnSubmit) {
130+
bootstrap.Modal.getInstance(modal).hide();
131+
} else {
132+
// Reload form
133+
fetch(settings.formURL).then(res => {
134+
return res.text();
135+
}).then(data => {
136+
let content = modal.querySelector(settings.modalContent);
137+
content.innerHTML = data;
138+
139+
form = modal.querySelector(settings.modalForm);
140+
if (!form) {
141+
console.error('no form present in response')
142+
return;
143+
}
144+
145+
form.action = settings.formURL;
146+
addEventHandlers(modal, form, settings)
147+
});
148+
}
149+
});
150+
} else if (asyncSettings.closeOnSubmit) {
151+
bootstrap.Modal.getInstance(modal).hide();
152+
}
153+
});
154+
}
155+
}
156+
};
157+
158+
const validateAsyncSettings = function (settings) {
159+
console.log(settings)
160+
var missingSettings = [];
161+
162+
if (!settings.successMessage) {
163+
missingSettings.push("successMessage");
164+
console.error("django-bootstrap-modal-forms: 'successMessage' in asyncSettings is missing.");
165+
}
166+
if (!settings.dataUrl) {
167+
missingSettings.push("dataUrl");
168+
console.error("django-bootstrap-modal-forms: 'dataUrl' in asyncSettings is missing.");
169+
}
170+
if (!settings.dataElementId) {
171+
missingSettings.push("dataElementId");
172+
console.error("django-bootstrap-modal-forms: 'dataElementId' in asyncSettings is missing.");
173+
}
174+
if (!settings.dataKey) {
175+
missingSettings.push("dataKey");
176+
console.error("django-bootstrap-modal-forms: 'dataKey' in asyncSettings is missing.");
177+
}
178+
if (!settings.addModalFormFunction) {
179+
missingSettings.push("addModalFormFunction");
180+
console.error("django-bootstrap-modal-forms: 'addModalFormFunction' in asyncSettings is missing.");
181+
}
182+
183+
if (missingSettings.length > 0) {
184+
return false;
185+
}
186+
187+
return true;
188+
};
189+
190+
const modalForm = function(elem, options) {
191+
// Default settings
192+
let defaults = {
193+
modalID: "#modal",
194+
modalContent: ".modal-content",
195+
modalForm: ".modal-content form",
196+
formURL: null,
197+
isDeleteForm: false,
198+
errorClass: "is-invalid",
199+
asyncUpdate: false,
200+
asyncSettings: {
201+
closeOnSubmit: false,
202+
successMessage: null,
203+
dataUrl: null,
204+
dataElementId: null,
205+
dataKey: null,
206+
addModalFormFunction: null
207+
}
208+
};
209+
210+
let settings = {...defaults, ...options}
211+
212+
elem.addEventListener('click', () => {
213+
modalFormCallback(settings);
214+
})
215+
216+
return elem;
217+
}

bootstrap_modal_forms/static/js/bootstrap5.modal.forms.min.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)