Skip to content

Commit aeadeab

Browse files
committed
feat: add new confirm modals and convert modals to vanilla js
1 parent 5fc88d6 commit aeadeab

10 files changed

+312
-175
lines changed

src/WebExpress.WebUI/Assets/js/webexpress.webui.dropdownbutton.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,11 @@ webexpress.webui.DropdownButtonCtrl = class extends webexpress.webui.Ctrl {
102102
link.append($("<span/>").text(item.content));
103103

104104
link.click(() => {
105+
// execute the action associated with the item, if it exists
106+
if (typeof item.action === "function") {
107+
item.action();
108+
}
109+
105110
$(document).trigger(webexpress.webui.Event.CLICK_EVENT, {
106111
sender: this._element,
107112
id: $(this._element).attr("id") || null,

src/WebExpress.WebUI/Assets/js/webexpress.webui.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,19 @@ webexpress.webui.Ctrl = class {
200200
destroy() {
201201
// Cleanup code, e.g., for event listeners
202202
}
203+
204+
/**
205+
* Detaches an element from the DOM while preserving its event listeners.
206+
* @param {HTMLElement} element - The element to be detached.
207+
* @returns {HTMLElement} - The detached element.
208+
*/
209+
_detachElement(element) {
210+
if (!element) return null;
211+
212+
// Remove the element from the DOM, keeping its event listeners intact
213+
element.remove();
214+
return element;
215+
}
203216
}
204217

205218
/**

src/WebExpress.WebUI/Assets/js/webexpress.webui.modal.js

Lines changed: 105 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@
55
* - webexpress.webui.Event.MODAL_HIDE_EVENT
66
*/
77
webexpress.webui.ModalCtrl = class extends webexpress.webui.Ctrl {
8-
_close = null;
8+
_closeLabel = null;
99
_size = null;
1010
_autoShow = null;
11-
_contentDiv = null;
12-
_dialogDiv = $("<div class='modal-dialog modal-dialog-scrollable'>");
13-
_headerDiv = null;
14-
_footerDiv = null;
15-
_bodyDiv = null;
11+
_dialogDiv = document.createElement("div");
12+
_contentDiv = document.createElement("div");
13+
_headerDiv = document.createElement("div");
14+
_titleH1 = document.createElement("h1");
15+
_bodyDiv = document.createElement("div");
16+
_footerDiv = document.createElement("div");
17+
_cancelButton = document.createElement("button");
1618

1719
/**
1820
* Constructor
@@ -22,87 +24,110 @@ webexpress.webui.ModalCtrl = class extends webexpress.webui.Ctrl {
2224
super(element);
2325

2426
// Retrieve custom attributes or use default values
25-
this._close = $(element).data("close-label") || this._close;
26-
this._size = $(element).data("size") || null;
27-
this._autoShow = $(element).data("auto-show") || null;
28-
29-
// Extract header, content, and footer from the provided HTML structure
30-
this._headerDiv = $("<div>").append($(".wx-modal-header", this._element).detach());
31-
this._contentDiv = $("<div>").append($(".wx-modal-content", this._element).children().detach());
32-
this._footerDiv = $("<div>").append($(".wx-modal-footer", this._element).children().detach());
27+
this._closeLabel = element.getAttribute("data-close-label") || "Close";
28+
this._size = element.getAttribute("data-size") || "";
29+
this._autoShow = element.getAttribute("data-auto-show") === "true";
3330

3431
// Cleanup the DOM element
35-
$(this._element)
36-
.removeAttr("data-close-label data-size data-auto-show") // Remove unnecessary attributes
37-
.empty() // Clear existing content
38-
.addClass("modal fade") // Add modal-specific classes
39-
.attr("tabindex", "-1")
40-
.attr("aria-hidden", "true");
41-
42-
// Create modal header
43-
const header = $("<div class='modal-header'>")
44-
.append($("<h1 class='modal-title fs-5'>").append(this._headerDiv))
45-
.append(
46-
$("<button type='button' class='btn-close' data-wx-dismiss='modal'>")
47-
.attr("aria-label", this._close)
48-
);
49-
50-
// Create modal footer
51-
const footer = $("<div class='modal-footer'>")
52-
.append(this._footerDiv)
53-
.append(
54-
$("<button type='button' class='btn btn-secondary' data-wx-dismiss='modal'>")
55-
.text(this._close).prepend($("<i class='fas fa-times me-2'>"))
56-
57-
);
58-
59-
// Create modal body
60-
this._bodyDiv = $("<div class='modal-body'>");
61-
62-
// Combine header, body, and footer into modal content
63-
const modalContent = $("<div class='modal-content'>").append(header, this._bodyDiv, footer);
64-
65-
// Add size class to dialog and append modal content
66-
this._dialogDiv.addClass(this._size).append(modalContent);
67-
68-
if (this._autoShow === true) {
69-
this.show(); // Automatically show the modal if specified
32+
element.removeAttribute("data-close-label");
33+
element.removeAttribute("data-size");
34+
element.removeAttribute("data-auto-show");
35+
element.classList.add("modal", "fade");
36+
37+
// Create modal elements
38+
this._bodyDiv.className = "modal-body";
39+
this._headerDiv.className = "modal-header";
40+
this._titleH1.className = "modal-title fs-5";
41+
this._footerDiv.className = "modal-footer";
42+
43+
// Extract and append children from .wx-modal-header
44+
const headers = this._element.querySelectorAll(".wx-modal-header");
45+
headers.forEach(header => {
46+
this._titleH1.appendChild(this._detachElement(header));
47+
});
48+
49+
// Extract and append all .wx-modal-content elements
50+
const contents = this._element.querySelectorAll(".wx-modal-content");
51+
contents.forEach(content => {
52+
this._contentDiv.appendChild(this._detachElement(content));
53+
});
54+
55+
// Extract and append all .wx-modal-footer elements
56+
const footers = this._element.querySelectorAll(".wx-modal-footer");
57+
footers.forEach(footer => {
58+
this._footerDiv.appendChild(this._detachElement(footer));
59+
});
60+
61+
// Create header content
62+
this._headerDiv.appendChild(this._titleH1);
63+
this._bodyDiv.appendChild(this._contentDiv);
64+
65+
const closeButton = document.createElement("button");
66+
closeButton.type = "button";
67+
closeButton.className = "btn-close";
68+
closeButton.setAttribute("data-wx-dismiss", "modal");
69+
closeButton.setAttribute("aria-label", this._closeLabel);
70+
closeButton.addEventListener("click", () => this.hide());
71+
this._headerDiv.appendChild(closeButton);
72+
73+
// Create footer content
74+
this._cancelButton.type = "button";
75+
this._cancelButton.className = "btn btn-secondary";
76+
this._cancelButton.setAttribute("data-wx-dismiss", "modal");
77+
this._cancelButton.innerHTML = `<i class='fas fa-times me-2'></i>${this._closeLabel}`;
78+
this._cancelButton.addEventListener("click", () => this.hide());
79+
this._footerDiv.appendChild(this._cancelButton);
80+
81+
// Create modal content structure
82+
this._dialogDiv.className = `modal-dialog modal-dialog-scrollable ${this._size}`;
83+
84+
const modalContentDiv = document.createElement("div");
85+
modalContentDiv.className = "modal-content";
86+
modalContentDiv.appendChild(this._headerDiv);
87+
modalContentDiv.appendChild(this._bodyDiv);
88+
modalContentDiv.appendChild(this._footerDiv);
89+
90+
this._dialogDiv.appendChild(modalContentDiv);
91+
this._element.appendChild(this._dialogDiv);
92+
93+
// Auto-show if specified
94+
if (this._autoShow) {
95+
this.show();
7096
}
7197
}
7298

7399
/**
74100
* Updates the modal content with fetched data from the URI.
75101
*/
76102
update() {
77-
// Clear existing content in body and append the new content
78-
this._bodyDiv.empty().append(this._contentDiv);
79-
80-
// Clear the DOM element and append the dialog structure
81-
$(this._element).empty().append(this._dialogDiv);
103+
if (!this._element.hasChildNodes()) {
104+
this._element.appendChild(this._dialogDiv);
105+
}
82106

83107
// Bind click event to close the modal when dismiss button is clicked
84-
this._dialogDiv.find("[data-wx-dismiss='modal']").click(() => {
85-
this.hide(); // Hide only this modal dialog
86-
});
108+
const closeButton = this._dialogDiv.querySelector("[data-wx-dismiss='modal']");
109+
if (closeButton) {
110+
closeButton.removeEventListener("click", this.hide);
111+
closeButton.addEventListener("click", () => this.hide()); // Bind click event
112+
}
87113
}
88114

89115
/**
90116
* Displays the modal by retrieving or creating its Bootstrap instance.
91117
* Ensures the modal is properly initialized before showing it.
92118
*/
93119
show() {
94-
this.update(); // Ensure modal content is up to date
95-
const modalInstance = bootstrap.Modal.getOrCreateInstance(this._element, {
96-
backdrop: 'static',
97-
keyboard: true
120+
this.update(); // Ensure modal content is refreshed
121+
const modalInstance = new bootstrap.Modal(this._element, {
122+
backdrop: "static",
123+
keyboard: true,
98124
});
99-
modalInstance.show(); // Open the modal
125+
modalInstance.show();
100126

101-
// Trigger custom event for showing the modal
102-
$(document).trigger(webexpress.webui.Event.MODAL_SHOW_EVENT, {
103-
sender: this._element,
104-
id: $(this._element).attr("id"),
105-
});
127+
// Trigger event for showing the modal
128+
document.dispatchEvent(new CustomEvent(webexpress.webui.Event.MODAL_SHOW_EVENT, {
129+
detail: { sender: this._element, id: this._element.id }
130+
}));
106131
}
107132

108133
/**
@@ -111,22 +136,23 @@ webexpress.webui.ModalCtrl = class extends webexpress.webui.Ctrl {
111136
*/
112137
hide() {
113138
const modalInstance = bootstrap.Modal.getInstance(this._element);
114-
modalInstance?.hide(); // Close the modal
115139

140+
this._element.addEventListener("hidden.bs.modal", () => {
141+
this._element.removeAttribute("style");
142+
this._element.removeAttribute("aria-hidden");
116143

117-
// Cleanup content after the modal is fully hidden
118-
$(this._element).on("hidden.bs.modal", () => {
119-
$(this._element).empty(); // Clear modal content
120-
$(document).trigger(webexpress.webui.Event.MODAL_HIDE_EVENT, {
144+
document.dispatchEvent(new CustomEvent(webexpress.webui.Event.MODAL_HIDE_EVENT, {
121145
sender: this._element,
122-
id: $(this._element).attr("id"),
123-
});
124-
125-
modalInstance?.dispose(); // Free memory
146+
id: this._element.id
147+
}));
126148
});
127-
128-
// Ensure event listeners are properly removed
129-
$(this._element).off("hidden.bs.modal");
149+
150+
this._element.innerHTML = "";
151+
this._element.removeAttribute("style");
152+
this._element.removeAttribute("aria-hidden");
153+
document.body.focus();
154+
155+
modalInstance?.hide();
130156
}
131157
};
132158

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/**
2+
* A modular confirm form.
3+
* Triggers the following events:
4+
* - webexpress.webui.Event.MODAL_SHOW_EVENT
5+
* - webexpress.webui.Event.MODAL_HIDE_EVENT
6+
*/
7+
webexpress.webui.ModalConfirm = class extends webexpress.webui.ModalCtrl {
8+
_confirmButton = document.createElement("button");
9+
10+
/**
11+
* Constructor
12+
*/
13+
constructor() {
14+
super(document.createElement("div"));
15+
16+
document.body.appendChild(this._element);
17+
}
18+
19+
/**
20+
* Sets the confirmation message and action to execute when confirmed.
21+
* @param {string} message - The confirmation message.
22+
* @param {function} action - The function to execute upon confirmation.
23+
*/
24+
confirmation(message, action) {
25+
26+
// Create confirm button
27+
this._confirmButton.type = "button";
28+
this._confirmButton.className = "btn btn-danger";
29+
this._confirmButton.textContent = "Confirm";
30+
this._confirmButton.onclick = () => {
31+
if (typeof action === "function") {
32+
action();
33+
}
34+
this.hide();
35+
};
36+
37+
// Update modal content
38+
this._headerDiv.innerHTML = "<h5>Confirmation</h5>";
39+
this._bodyDiv.innerHTML = `<p>${message}</p>`;
40+
41+
// Create footer buttons
42+
this._footerDiv.innerHTML = "";
43+
this._cancelButton.setAttribute("data-wx-dismiss", "modal");
44+
45+
this._footerDiv.appendChild(this._confirmButton);
46+
this._footerDiv.appendChild(this._cancelButton);
47+
48+
this.update();
49+
}
50+
};
51+
52+
// Register the class in the controller
53+
webexpress.webui.Controller.registerClass("wx-webui-modalconfirm", webexpress.webui.ModalConfirm);
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* A modular confirmation modal specifically for delete actions.
3+
* Triggers the following events:
4+
* - webexpress.webui.Event.MODAL_SHOW_EVENT
5+
* - webexpress.webui.Event.MODAL_HIDE_EVENT
6+
*/
7+
webexpress.webui.ModalConfirmDelete = class extends webexpress.webui.ModalConfirm {
8+
/**
9+
* Constructor
10+
* @param {HTMLElement} element - The DOM element associated with the modal control.
11+
*/
12+
constructor(element) {
13+
super(element);
14+
}
15+
16+
/**
17+
* Sets the delete confirmation message and action to execute when confirmed.
18+
* @param {function} deleteAction - The function to execute upon confirmation.
19+
*/
20+
confirmation(deleteAction) {
21+
super.confirmation("Are you sure you want to delete this item?", deleteAction);
22+
23+
// Customize button style for deletion
24+
if (this._confirmButton) {
25+
this._confirmButton.classList.add("btn-danger");
26+
this._confirmButton.textContent = "Delete";
27+
}
28+
}
29+
};
30+
31+
// Register the class in the controller
32+
webexpress.webui.Controller.registerClass("wx-webui-modalconfirmdelete", webexpress.webui.ModalConfirmDelete);

0 commit comments

Comments
 (0)