-
-
Notifications
You must be signed in to change notification settings - Fork 4.4k
Add EventHelper class with delegation and trigger methods #8126
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,321 @@ | ||||||||||||||
| /** | ||||||||||||||
| * Bootstrap Table Event System Utility Library | ||||||||||||||
| * Provides jQuery-style event handling APIs using native JavaScript | ||||||||||||||
| */ | ||||||||||||||
|
|
||||||||||||||
| import DOMHelper from './dom.js' | ||||||||||||||
|
|
||||||||||||||
| class EventHelper { | ||||||||||||||
| /** | ||||||||||||||
| * Add event listener with namespace support | ||||||||||||||
| * @param {Element|string} element - DOM element or selector | ||||||||||||||
| * @param {string} event - Event name with optional namespace (e.g., "click.namespace") | ||||||||||||||
| * @param {Function} handler - Event handler function | ||||||||||||||
| * @param {Object|boolean} [options] - Event options or useCapture flag | ||||||||||||||
| * @returns {Element} The element itself | ||||||||||||||
| */ | ||||||||||||||
| static on (element, event, handler, options = {}) { | ||||||||||||||
| if (typeof element === 'string') element = DOMHelper.$(element) | ||||||||||||||
| if (!element) return element | ||||||||||||||
|
|
||||||||||||||
| // Validate handler | ||||||||||||||
| if (typeof handler !== 'function') { | ||||||||||||||
| throw new Error('Event handler must be a function') | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| // Parse event type and namespace | ||||||||||||||
| const [eventType, namespace] = event.split('.') | ||||||||||||||
|
||||||||||||||
|
|
||||||||||||||
| // Initialize event handlers storage if not exists | ||||||||||||||
| if (!element._eventHandlers) { | ||||||||||||||
| element._eventHandlers = {} | ||||||||||||||
| } | ||||||||||||||
| if (!element._eventHandlers[eventType]) { | ||||||||||||||
| element._eventHandlers[eventType] = [] | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| // Create wrapped handler to maintain context | ||||||||||||||
| const wrappedHandler = e => { | ||||||||||||||
| handler.call(element, e) | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| // Store handler info | ||||||||||||||
| element._eventHandlers[eventType].push({ | ||||||||||||||
| handler, | ||||||||||||||
| namespace, | ||||||||||||||
| wrappedHandler, | ||||||||||||||
| original: handler | ||||||||||||||
| }) | ||||||||||||||
|
Comment on lines
+43
to
+48
|
||||||||||||||
|
|
||||||||||||||
| // Add native event listener | ||||||||||||||
| element.addEventListener(eventType, wrappedHandler, options) | ||||||||||||||
| return element | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| /** | ||||||||||||||
| * Remove event listener with namespace support | ||||||||||||||
| * @param {Element|string} element - DOM element or selector | ||||||||||||||
| * @param {string} event - Event name with optional namespace | ||||||||||||||
| * @param {Function} [handler] - Specific handler to remove (optional) | ||||||||||||||
| * @returns {Element} The element itself | ||||||||||||||
|
Comment on lines
+55
to
+60
|
||||||||||||||
| */ | ||||||||||||||
| static off (element, event, handler) { | ||||||||||||||
| if (typeof element === 'string') element = DOMHelper.$(element) | ||||||||||||||
| if (!element || !element._eventHandlers) return element | ||||||||||||||
|
|
||||||||||||||
| const [eventType, namespace] = event.split('.') | ||||||||||||||
|
||||||||||||||
|
|
||||||||||||||
| if (element._eventHandlers[eventType]) { | ||||||||||||||
| // Find handlers to remove | ||||||||||||||
| const handlersToRemove = element._eventHandlers[eventType].filter(info => { | ||||||||||||||
| const matches = | ||||||||||||||
| (!namespace || info.namespace === namespace) && | ||||||||||||||
| (!handler || info.original === handler) | ||||||||||||||
|
|
||||||||||||||
| if (matches && info.wrappedHandler) { | ||||||||||||||
| element.removeEventListener(eventType, info.wrappedHandler) | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| return matches | ||||||||||||||
| }) | ||||||||||||||
|
|
||||||||||||||
| // Remove handlers from array | ||||||||||||||
| element._eventHandlers[eventType] = element._eventHandlers[eventType].filter(info => | ||||||||||||||
| !handlersToRemove.includes(info) | ||||||||||||||
| ) | ||||||||||||||
|
|
||||||||||||||
| // Clean up empty event type arrays | ||||||||||||||
| if (element._eventHandlers[eventType].length === 0) { | ||||||||||||||
| delete element._eventHandlers[eventType] | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| // Clean up if no handlers left at all | ||||||||||||||
| if (Object.keys(element._eventHandlers).length === 0) { | ||||||||||||||
| delete element._eventHandlers | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| return element | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| /** | ||||||||||||||
| * Trigger custom event | ||||||||||||||
| * @param {Element|string} element - DOM element or selector | ||||||||||||||
| * @param {string} event - Event name | ||||||||||||||
| * @param {*} [detail] - Custom data to pass with event | ||||||||||||||
| * @param {Object} [options] - Event options | ||||||||||||||
| * @returns {Element} The element itself | ||||||||||||||
|
Comment on lines
+101
to
+107
|
||||||||||||||
| */ | ||||||||||||||
| static trigger (element, event, detail = {}, options = {}) { | ||||||||||||||
| if (typeof element === 'string') element = DOMHelper.$(element) | ||||||||||||||
| if (!element) return element | ||||||||||||||
|
|
||||||||||||||
| const customEvent = new CustomEvent(event, { | ||||||||||||||
| detail, | ||||||||||||||
| bubbles: options.bubbles !== false, | ||||||||||||||
| cancelable: options.cancelable !== false | ||||||||||||||
| }) | ||||||||||||||
|
|
||||||||||||||
| element.dispatchEvent(customEvent) | ||||||||||||||
| return element | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| /** | ||||||||||||||
| * Event delegation - add event listener that works for dynamically added elements | ||||||||||||||
| * @param {Element|string} parent - Parent element or selector | ||||||||||||||
| * @param {string} selector - Child element selector | ||||||||||||||
| * @param {string} event - Event name with optional namespace | ||||||||||||||
| * @param {Function} handler - Event handler function | ||||||||||||||
| * @param {Object|boolean} [options] - Event options or useCapture flag | ||||||||||||||
| * @returns {Element} The parent element | ||||||||||||||
|
||||||||||||||
| * @returns {Element} The parent element | |
| * @returns {Element|null} The parent element |
Copilot
AI
Dec 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Namespace parsing discards multiple namespace segments. When an event string like "click.ns1.ns2" is provided, the split and destructuring at line 136 only captures namespace = "ns1", discarding "ns2". This is the same issue as in the on and off methods. Change the parsing to preserve the full namespace: const parts = event.split('.'); const eventType = parts[0]; const namespace = parts.slice(1).join('.').
Copilot
AI
Dec 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incomplete JSDoc return type documentation. The return type is documented as "The parent element", but the method can also return null when the parent parameter is null/undefined or when a selector doesn't match any element (line 174). The return type should be documented as {Element|null} to accurately reflect all possible return values.
| * @returns {Element} The parent element | |
| * @returns {Element|null} The parent element |
Copilot
AI
Dec 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Namespace parsing discards multiple namespace segments. When an event string like "click.ns1.ns2" is provided, the split and destructuring at line 176 only captures namespace = "ns1", discarding "ns2". This is the same issue as in other methods. Change the parsing to preserve the full namespace: const parts = event.split('.'); const eventType = parts[0]; const namespace = parts.slice(1).join('.').
Copilot
AI
Dec 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The undelegate method has a potential bug in handler matching. Line 185 checks (!handler || delegate.handler === handler), but delegate.handler is the wrapped delegate handler created in the delegate method, not the original handler passed by the user. This means the optional handler parameter for selective removal will never match, making it impossible to remove a specific delegated handler while keeping others. The comparison should be against the original handler, which would require storing it in the delegate metadata.
Copilot
AI
Dec 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Memory leak: the _delegates array is never cleaned up when it becomes empty. After all delegated handlers are removed via undelegate, the empty array remains attached to the parent element. Consider adding cleanup logic similar to the _eventHandlers cleanup in the off method, deleting the _delegates property when the array becomes empty.
| // Clean up delegates storage if no delegates remain | |
| if (parent._delegates.length === 0) { | |
| delete parent._delegates | |
| } |
Copilot
AI
Dec 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incomplete JSDoc return type documentation. The return type is documented as "The element itself", but the method can also return null when the element parameter is null/undefined or when a selector doesn't match any element (line 207). The return type should be documented as {Element|null} to accurately reflect all possible return values.
Copilot
AI
Dec 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incomplete JSDoc return type documentation. The return type is documented as "The element itself", but the method can also return null when the element parameter is null/undefined or when a selector doesn't match any element (line 228). The return type should be documented as {Element|null} to accurately reflect all possible return values.
| * @returns {Element} The element itself | |
| * @returns {Element|null} The element itself or null if no element is found |
Copilot
AI
Dec 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incomplete JSDoc return type documentation. The return type is documented as "The element itself", but the method can also return null when the element parameter is null/undefined or when a selector doesn't match any element (line 261). The return type should be documented as {Element|null} to accurately reflect all possible return values.
| * @returns {Element} The element itself | |
| * @returns {Element|null} The element itself or null if no element is found |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incomplete JSDoc return type documentation. The return type is documented as "The element itself", but the method can also return null when the element parameter is null/undefined or when a selector doesn't match any element (line 19). The return type should be documented as
{Element|null}to accurately reflect all possible return values.