Add a jQuery rebind() helper and roll it out across the core UI (#8279)

* Add a jQuery rebind() helper and roll it out across the core UI

Add $.fn.rebind(events[, selector], handler...) to include/layout.js as a
shorthand for the common off(events).on(events, ...) re-attach idiom, then
convert the matching chained calls across the core JavaScript and inline
PHP handlers to use it. Only pairs whose off() and on() events (and
delegated selector) match are converted; off() with no arguments is left
as-is since it intentionally clears every handler.

* changelog: add 8279.feature fragment

* tests: update layout.js JS tests for the rebind() helper

* tests: assert the cactiPostAction handler via rebind()

* rebind: preserve the delegated selector for the two-argument event-map overload; add helper tests
This commit is contained in:
TheWitness authored and GitHub committed 2026-10-10 17:01:49 -04:00
1 parent d49126df58
commit bfa3efb7a9
27 files changed
+216 -100

No files matched your search

+1 -1
View File
@@ -682,7 +682,7 @@ function cdef_edit() : void {
applySkin();
$('#continue').off('click').on('click', function(data) {
$('#continue').rebind('click', function(data) {
$.post('cdef.php?action=item_remove', {
__csrf_magic: csrfMagicToken,
cdef_id: $('#my_cdef_id').val(),
+1
View File
@@ -0,0 +1 @@
Add a jQuery rebind() helper for the common off(events).on(events, ...) idiom and roll it out across the core JavaScript and inline PHP event handlers
+1 -1
View File
@@ -1201,7 +1201,7 @@ function profile_edit() : void {
applySkin();
$('#continue').off('click').on('click', function(data) {
$('#continue').rebind('click', function(data) {
$.post('data_source_profiles.php?action=item_remove', {
__csrf_magic: csrfMagicToken,
id: $('#rra_id').val()
+2 -2
View File
@@ -265,13 +265,13 @@ midwinter.navigationBox.table = {
const ns = midwinter.navigationBox.table;
// bind toggle checkboxes
$box.off('change', '.plugin-table-toggle').on('change', '.plugin-table-toggle', (e) => {
$box.rebind('change', '.plugin-table-toggle', (e) => {
const $el = $(e.currentTarget);
ns._executeToggle($el.data('table'), $el.data('column'), e.target.checked);
});
// bind reset control button
$box.off('click', '[data-action="reset_table"]').on('click', '[data-action="reset_table"]', (e) => {
$box.rebind('click', '[data-action="reset_table"]', (e) => {
const tableHash = $box.find('input[data-table]').first().data('table');
ns._executeReset(tableHash);
});
+45 -30
View File
@@ -278,6 +278,22 @@ $.fn.delayKeyup = function (callback, ms) {
return $(this);
};
/**
* rebind - Shorthand for off(events).on(events, ...) to cleanly re-attach an
* event handler, optionally for a delegated selector.
*/
$.fn.rebind = function (events) {
// A string second argument is always a delegated selector (jQuery never
// treats a string as handler data), including the .on(map, selector) overload.
if (typeof arguments[1] === 'string') {
this.off(events, arguments[1]);
} else {
this.off(events);
}
return this.on.apply(this, arguments);
};
/**
* bindFirst - Function ensures that the event is found at the top
* of the event stack.
@@ -735,7 +751,7 @@ function updateCheckboxes(checkboxes, clicked_element) {
* taking action as required to enable or disable rows. */
function applySelectorVisibilityAndActions() {
// Change for accessibility
$('input[type="radio"]').off('click').on('click', function () {
$('input[type="radio"]').rebind('click', function () {
if ($(this).is(':checked')) {
$(this).attr('aria-checked', 'true');
} else {
@@ -754,12 +770,12 @@ function applySelectorVisibilityAndActions() {
});
// Create Actions for Rows
$('tr[id^="gt_line"].selectable:not(.disabled_row)').off('click').on('click', function (event) {
$('tr[id^="gt_line"].selectable:not(.disabled_row)').rebind('click', function (event) {
selectUpdateRow(event, $(this));
});
// Create Actions for Rows
$('tr[id^="line"].selectable').filter(':not(.disabled_row)').off('click').on('click', function (event) {
$('tr[id^="line"].selectable').filter(':not(.disabled_row)').rebind('click', function (event) {
selectUpdateRow(event, $(this));
});
}
@@ -845,7 +861,7 @@ function dqUpdateDeps(snmp_query_id) {
$('#all_' + snmp_query_id).prop('checked', false);
}
$('tr[id^="dqline' + snmp_query_id + '_"]').not('.disabled_row').off('click').on('click', function (event) {
$('tr[id^="dqline' + snmp_query_id + '_"]').not('.disabled_row').rebind('click', function (event) {
selectUpdateRow(event, $(this));
});
}
@@ -943,8 +959,7 @@ function cactiReturnTo(href) {
function setupSelectmenuScrollClose() {
$('.cactiConsoleContentArea, .cactiGraphContentArea, .cactiGraphContentAreaPreview, .cactiTreeNavigationArea')
.add(window)
.off('scroll.cactiSelectmenu')
.on('scroll.cactiSelectmenu', function () {
.rebind('scroll.cactiSelectmenu', function () {
if (!$('.ui-selectmenu-open').length) {
return;
}
@@ -1039,7 +1054,7 @@ function applySkin() {
setupObjectChange();
$('.helpPage').off('click').on('click', function (event) {
$('.helpPage').rebind('click', function (event) {
event.stopPropagation();
getCactiHelp($(this).attr('data-page'));
@@ -1058,7 +1073,7 @@ function applySkin() {
setupButtonStyle();
// Debug message actions
$('table.debug tr:nth-child(1)').off('click').on('click', function () {
$('table.debug tr:nth-child(1)').rebind('click', function () {
if ($(this).parent().find('table').is(':visible')) {
$(this).parent().find('table').slideUp('fast');
} else {
@@ -1066,7 +1081,7 @@ function applySkin() {
}
});
$('.cactiTableCopy').off('click').on('click', function (event) {
$('.cactiTableCopy').rebind('click', function (event) {
event.preventDefault();
event.stopPropagation();
var containerId = $(this).attr('id');
@@ -1399,7 +1414,7 @@ function applySkin() {
* select2 widget on every AJAX filter reload, but that destroy doesn't remove
* a plain jQuery listener bound to the underlying <select> itself, so without
* this the handler would accumulate and fire the callback multiple times */
$select.off('select2:select.select2Callback').on('select2:select.select2Callback', function() {
$select.rebind('select2:select.select2Callback', function() {
executeFunctionByName(changeFunc.replace('(', '').replace(')', ''), window);
});
}
@@ -1776,7 +1791,7 @@ function makeFiltersResponsive() {
filterHeader.find('div.cactiTableButton').append('<span class="cactiSwitchConstraintWrapper"><a title="' + tableConstraints + '" class="linkOverDark cactiSwitchConstraints" href="#"><i id="overflow" class="ti ti-arrows-diagonal"></i></a></span>');
}
$('.cactiSwitchConstraints').off('click').on('click', function (event) {
$('.cactiSwitchConstraints').rebind('click', function (event) {
event.preventDefault();
event.stopPropagation();
@@ -1800,7 +1815,7 @@ function makeFiltersResponsive() {
title = $('#export').attr('title');
filterHeader.find('div.cactiTableButton').append($('<span style="display:none;" class="cactiFilterExport"><i class="ti ti-table-export"></i></span>').attr('title', title));
$('.cactiFilterExport').off('click').on('click', function (event) {
$('.cactiFilterExport').rebind('click', function (event) {
event.stopPropagation();
$('#export').trigger('click');
}).tooltip();
@@ -1810,7 +1825,7 @@ function makeFiltersResponsive() {
title = $('#import').attr('title');
filterHeader.find('div.cactiTableButton').append($('<span style="display:none;" class="cactiFilterImport"><i class="ti ti-table-import"></i></span>').attr('title', title));
$('.cactiFilterImport').off('click').on('click', function (event) {
$('.cactiFilterImport').rebind('click', function (event) {
event.stopPropagation();
$('#import').trigger('click');
}).tooltip();
@@ -1821,7 +1836,7 @@ function makeFiltersResponsive() {
filterHeader.find('div.cactiTableButton').append('<span title="' + clearFilterTitle + '" style="display:none;" class="cactiFilterClear"><i class="ti ti-trash"></i></span>');
}
$('.cactiFilterClear').off('click').on('click', function(event) {
$('.cactiFilterClear').rebind('click', function(event) {
event.stopPropagation();
$('#clear').trigger('click');
}).tooltip();
@@ -1849,7 +1864,7 @@ function makeFiltersResponsive() {
}
}
filterHeader.find('.cactiFilterState').off('click').on('click', function() {
filterHeader.find('.cactiFilterState').rebind('click', function() {
id = $(this).closest('.cactiTable').attr('id');
child = id + '_child';
toggleFilterAndIcon(id, child, false);
@@ -1933,7 +1948,7 @@ function setGraphTabs() {
}
function setupResponsiveMenuAndTabs() {
$('.maintabs a.lefttab, .dropdownMenu a, .menuoptions a, #gtabs a.righttab').not('[href^="http"], [href^="https"], [href^="#"], [target="_blank"]').off('click').on('click', function (event) {
$('.maintabs a.lefttab, .dropdownMenu a, .menuoptions a, #gtabs a.righttab').not('[href^="http"], [href^="https"], [href^="#"], [target="_blank"]').rebind('click', function (event) {
if (!shouldCaptureClick(event)) {
return;
}
@@ -2790,7 +2805,7 @@ function loadPageUsingPost(href, postData, returnLocation) {
function setNavigationScroll() {
var object = '';
$('.cactiConsoleNavigationArea, .cactiTreeNavigationArea').off('mousemove').on('mousemove', function (pos) {
$('.cactiConsoleNavigationArea, .cactiTreeNavigationArea').rebind('mousemove', function (pos) {
object = '';
if ($('.cactiConsoleNavigationArea').length) {
@@ -2829,7 +2844,7 @@ function setNavigationScroll() {
}
});
$('.cactiConsoleNavigationArea, .cactiTreeNavigationArea').off('mouseleave').on('mouseleave', function (pos) {
$('.cactiConsoleNavigationArea, .cactiTreeNavigationArea').rebind('mouseleave', function (pos) {
if ($('.cactiConsoleNavigationArea').length) {
object = '.cactiConsoleNavigationArea';
} else if ($('.cactiTreeNavigationArea').length) {
@@ -3300,7 +3315,7 @@ function getPresentHTTPErrorOrRedirect(data, url) {
function ajaxAnchors() {
var page = basename(location.pathname);
$(document).off('click.cactiPostAction', 'a.cactiPostAction').on('click.cactiPostAction', 'a.cactiPostAction', function (event) {
$(document).rebind('click.cactiPostAction', 'a.cactiPostAction', function (event) {
if (!shouldCaptureClick(event)) {
return;
}
@@ -3317,7 +3332,7 @@ function ajaxAnchors() {
return false;
});
$('a.pic, a.linkOverDark, a.linkEditMain, a.console, a.hyperLink, a.tab').not('[href^="http"], [href^="https"], [href^="#"], [href^="mailto"], [target="_blank"]').not('.cactiPostAction').off('click').on('click', function (event) {
$('a.pic, a.linkOverDark, a.linkEditMain, a.console, a.hyperLink, a.tab').not('[href^="http"], [href^="https"], [href^="#"], [href^="mailto"], [target="_blank"]').not('.cactiPostAction').rebind('click', function (event) {
if (!shouldCaptureClick(event)) {
return;
}
@@ -3424,7 +3439,7 @@ function setupCollapsible() {
}
});
$('.collapsible').off('click').on('click', function (data) {
$('.collapsible').rebind('click', function (data) {
var id = $(this).attr('id') + '_cs';
if ($(this).find('i').hasClass('ti-chevrons-up')) {
@@ -3499,7 +3514,7 @@ function setupSpecialKeys() {
}
// Clicking the search glyph applies the filter: submit its form, or on the modern bar (no form) trigger Enter.
$(document).off('click.cactiSearchIcon').on('click.cactiSearchIcon', 'i.ti.ti-search.filter', function () {
$(document).rebind('click.cactiSearchIcon', 'i.ti.ti-search.filter', function () {
var input = $(this).prev('input').get(0);
if (!input) {
return;
@@ -4232,7 +4247,7 @@ function setupEllipsis() {
'</ul>' +
'</div>').appendTo('body');
$('.maintabs-submenu, .usertabs-submenu, .submenu-ellipsis').off('click').on('click', function (event) {
$('.maintabs-submenu, .usertabs-submenu, .submenu-ellipsis').rebind('click', function (event) {
event.preventDefault();
var submenu_index = $(this).attr('id').replace('menu-', 'submenu-');
@@ -4844,7 +4859,7 @@ function initializeGraphs(disable_cache) {
$(this).attr('href', urlPath + pageName + 'action=view' + (tree ? '-tree':'-preview') + '&local_graph_id=' + graph_id);
$(this).off('click').on('click', function (event) {
$(this).rebind('click', function (event) {
if (!shouldCaptureClick(event))
return;
@@ -4887,7 +4902,7 @@ function initializeGraphs(disable_cache) {
$(this).attr('href', url);
$(this).off('click').on('click', function (event) {
$(this).rebind('click', function (event) {
if (!shouldCaptureClick(event))
return;
@@ -4907,7 +4922,7 @@ function initializeGraphs(disable_cache) {
});
});
$('#form_graph_view').off('submit').on('submit', function (event) {
$('#form_graph_view').rebind('submit', function (event) {
event.preventDefault();
event.stopPropagation();
applyFilter();
@@ -5031,11 +5046,11 @@ function initializeGraphs(disable_cache) {
});
});
$('#realtimeoff').off('click').on('click', function () {
$('#realtimeoff').rebind('click', function () {
stopRealtime();
});
$('#ds_step').off('change').on('change', function () {
$('#ds_step').rebind('change', function () {
realtimeGrapher();
});
@@ -5051,7 +5066,7 @@ function initializeGraphs(disable_cache) {
'&graph_start=' + timestampDate1 +
'&graph_end=' + timestampDate2);
$(this).off('click').on('click', function (event) {
$(this).rebind('click', function (event) {
if (!shouldCaptureClick(event))
return;
@@ -5078,7 +5093,7 @@ function initializeGraphs(disable_cache) {
return false;
});
$(this).off('click').on('click', function (event) {
$(this).rebind('click', function (event) {
if (!shouldCaptureClick(event))
return;
+2 -2
View File
@@ -103,7 +103,7 @@ function themeReady() {
$(window).trigger('resize');
$('.action-icon-user').off('click').on('click', function(event) {
$('.action-icon-user').rebind('click', function(event) {
event.preventDefault();
if ($('.menuoptions').is(':visible') === false) {
@@ -152,7 +152,7 @@ function setMenuVisibility() {
$('#navigation').show();
// Functon to give life to the Navigation pane
$('#nav li:has(ul) a.active').off('click').on('click', function(event) {
$('#nav li:has(ul) a.active').rebind('click', function(event) {
event.preventDefault();
id = $(this).closest('.menuitem').attr('id');
+2 -2
View File
@@ -103,7 +103,7 @@ function themeReady() {
$(window).trigger('resize');
$('.action-icon-user').off('click').on('click', function(event) {
$('.action-icon-user').rebind('click', function(event) {
event.preventDefault();
if ($('.menuoptions').is(':visible') === false) {
@@ -152,7 +152,7 @@ function setMenuVisibility() {
$('#navigation').show();
// Functon to give life to the Navigation pane
$('#nav li:has(ul) a.active').off('click').on('click', function(event) {
$('#nav li:has(ul) a.active').rebind('click', function(event) {
event.preventDefault();
id = $(this).closest('.menuitem').attr('id');
+1 -1
View File
@@ -166,7 +166,7 @@ function setMenuVisibility() {
});
// Function to give life to the Navigation pane
$('#nav li:has(ul) a.active').off('click').on('click', function(event) {
$('#nav li:has(ul) a.active').rebind('click', function(event) {
event.preventDefault();
id = $(this).closest('.menuitem').attr('id');
+2 -2
View File
@@ -104,7 +104,7 @@ function themeReady() {
$(window).trigger('resize');
$('.action-icon-user').off('click').on('click', function(event) {
$('.action-icon-user').rebind('click', function(event) {
event.preventDefault();
if ($('.menuoptions').is(':visible') === false) {
@@ -169,7 +169,7 @@ function setMenuVisibility() {
});
// Function to give life to the Navigation pane
$('#nav li:has(ul) a.active').off('click').on('click', function(event) {
$('#nav li:has(ul) a.active').rebind('click', function(event) {
event.preventDefault();
id = $(this).closest('.menuitem').attr('id');
+2 -2
View File
@@ -103,7 +103,7 @@ function themeReady() {
$(window).trigger('resize');
$('.action-icon-user').off('click').on('click', function(event) {
$('.action-icon-user').rebind('click', function(event) {
event.preventDefault();
if ($('.menuoptions').is(':visible') === false) {
@@ -152,7 +152,7 @@ function setMenuVisibility() {
$('#navigation').show();
// Functon to give life to the Navigation pane
$('#nav li:has(ul) a.active').off('click').on('click', function(event) {
$('#nav li:has(ul) a.active').rebind('click', function(event) {
event.preventDefault();
id = $(this).closest('.menuitem').attr('id');
+3 -3
View File
@@ -275,7 +275,7 @@ function hideConsoleNavigation() {
}
function updateAjaxAnchors() {
$('a.pic, a.linkOverDark, a.linkEditMain, a.console, a.hyperLink, a.tab').not('[href^="http"], [href^="https"], [href^="#"], [href^="mailto"], [target="_blank"]').off('click').on('click', function(event) {
$('a.pic, a.linkOverDark, a.linkEditMain, a.console, a.hyperLink, a.tab').not('[href^="http"], [href^="https"], [href^="#"], [href^="mailto"], [target="_blank"]').rebind('click', function(event) {
event.preventDefault();
event.stopPropagation();
@@ -508,7 +508,7 @@ function setupTheme() {
const $settingsBox = $('[class^="mdw-ConsoleNavigationBox"][data-helper="settings"]');
$('[class^="compact_nav_icon"][data-helper="settings"]').toggleClass('hide', $settingsBox.has('li').length === 0);
$('#main').off('resize').on('resize', function() {
$('#main').rebind('resize', function() {
$('#main .saveRowParent').width($(this).width());
});
}
@@ -1088,7 +1088,7 @@ function kioskMode(event = false) {
toggleConsoleNavigationBox(event);
setDocumentAttribute('kiosk-mode', 'on');
if(isMobile.any() != null) {
$('#mdw-Main').off('click').on('click', function(e) {
$('#mdw-Main').rebind('click', function(e) {
let tap;
mdw.cache.tab.count++;
+1 -1
View File
@@ -122,7 +122,7 @@ function setMenuVisibility() {
});
// Function to give life to the Navigation pane
$('#nav li:has(ul) a.active').off('click').on('click', function(event) {
$('#nav li:has(ul) a.active').rebind('click', function(event) {
event.preventDefault();
id = $(this).closest('.menuitem').attr('id');
+3 -3
View File
@@ -105,7 +105,7 @@ function themeReady() {
/* User Menu */
$('.menuoptions').parent().appendTo('body');
$('.action-icon-user').off('click').on('click', function(event) {
$('.action-icon-user').rebind('click', function(event) {
event.preventDefault();
if ($('.menuoptions').is(':visible') === false) {
@@ -118,7 +118,7 @@ function themeReady() {
return false;
});
$('.bottom_scroll_up').off('click').on('click', function(event) {
$('.bottom_scroll_up').rebind('click', function(event) {
event.preventDefault();
$('#navigation_right').animate({ scrollLeft:0, scrollTop: 0 }, 1000, 'easeInOutQuart');
});
@@ -187,7 +187,7 @@ function setMenuVisibility() {
});
// Function to give life to the Navigation pane
$('#nav li:has(ul) a.active').off('click').on('click', function(event) {
$('#nav li:has(ul) a.active').rebind('click', function(event) {
event.preventDefault();
id = $(this).closest('.menuitem').attr('id');
+2 -2
View File
@@ -104,7 +104,7 @@ function themeReady() {
$(window).trigger('resize');
$('.action-icon-user').off('click').on('click', function(event) {
$('.action-icon-user').rebind('click', function(event) {
event.preventDefault();
if ($('.menuoptions').is(':visible') === false) {
@@ -169,7 +169,7 @@ function setMenuVisibility() {
});
// Function to give life to the Navigation pane
$('#nav li:has(ul) a.active').off('click').on('click', function(event) {
$('#nav li:has(ul) a.active').rebind('click', function(event) {
event.preventDefault();
id = $(this).closest('.menuitem').attr('id');
+2 -2
View File
@@ -103,7 +103,7 @@ function themeReady() {
$(window).trigger('resize');
$('.action-icon-user').off('click').on('click', function(event) {
$('.action-icon-user').rebind('click', function(event) {
event.preventDefault();
if ($('.menuoptions').is(':visible') === false) {
@@ -152,7 +152,7 @@ function setMenuVisibility() {
$('#navigation').show();
// Functon to give life to the Navigation pane
$('#nav li:has(ul) a.active').off('click').on('click', function(event) {
$('#nav li:has(ul) a.active').rebind('click', function(event) {
event.preventDefault();
id = $(this).closest('.menuitem').attr('id');
+4 -4
View File
@@ -25,7 +25,7 @@ function themeReady() {
// Setup the navigation menu
setMenuVisibility();
$('#navigation_right').off('scroll').on('scroll', function (event) {
$('#navigation_right').rebind('scroll', function (event) {
var scroll_position_x = $('#navigation_right').scrollLeft();
var scroll_position_y = $('#navigation_right').scrollTop();
$('.bottom_scroll_up').css({'color': ((scroll_position_x == 0 & scroll_position_y == 0) ? '' : '#93CEFF') });
@@ -109,7 +109,7 @@ function themeReady() {
/* User Menu */
$('.menuoptions').parent().appendTo('body');
$('.action-icon-user').off('click').on('click', function(event) {
$('.action-icon-user').rebind('click', function(event) {
event.preventDefault();
if ($('.menuoptions').is(':visible') === false) {
@@ -122,7 +122,7 @@ function themeReady() {
return false;
});
$('.bottom_scroll_up').off('click').on('click', function(event) {
$('.bottom_scroll_up').rebind('click', function(event) {
event.preventDefault();
$('#navigation_right').animate({ scrollLeft:0, scrollTop: 0 }, 1000, 'easeInOutQuart');
});
@@ -229,7 +229,7 @@ function setMenuVisibility() {
});
// Function to give life to the Navigation pane
$('#nav li:has(ul) a.active').off('click').on('click', function(event) {
$('#nav li:has(ul) a.active').rebind('click', function(event) {
event.preventDefault();
id = $(this).closest('.menuitem').attr('id');
+1 -1
View File
@@ -2158,7 +2158,7 @@ function draw_actions_dropdown(array $actions_array, int $delete_action = 1) : v
});
}
$('tr[id^="line"]').filter(':not(.disabled_row)').off('click').on('click', function(event) {
$('tr[id^="line"]').filter(':not(.disabled_row)').rebind('click', function(event) {
selectUpdateRow(event, $(this));
});
}
+11 -11
View File
@@ -1133,35 +1133,35 @@ class CactiTableFilter {
case 'timespan':
// Delegated bindings survive ajax content reloads that replace the timespan controls; direct .change()/.click() handlers would be lost after the first filter apply.
if (!isset($field_array['span_function'])) {
$readyAdd .= "$(document).off('change.cactiTimespan', '#predefined_timespan').on('change.cactiTimespan', '#predefined_timespan', function() { applyGraphTimespan(); });" . PHP_EOL;
$readyAdd .= "$(document).rebind('change.cactiTimespan', '#predefined_timespan', function() { applyGraphTimespan(); });" . PHP_EOL;
} else {
$readyAdd .= "$(document).off('change.cactiTimespan', '#predefined_timespan').on('change.cactiTimespan', '#predefined_timespan', function() { " . $field_array['span_function'] . '; });' . PHP_EOL;
$readyAdd .= "$(document).rebind('change.cactiTimespan', '#predefined_timespan', function() { " . $field_array['span_function'] . '; });' . PHP_EOL;
}
if (isset($field_array['shifter']) && $field_array['shifter'] === true) {
if (!isset($field_array['lshift_function'])) {
$readyAdd .= "$(document).off('click.cactiTimespan', '#shift_left').on('click.cactiTimespan', '#shift_left', function() { timeshiftGraphFilterLeft(); });" . PHP_EOL;
$readyAdd .= "$(document).rebind('click.cactiTimespan', '#shift_left', function() { timeshiftGraphFilterLeft(); });" . PHP_EOL;
} else {
$readyAdd .= "$(document).off('click.cactiTimespan', '#shift_left').on('click.cactiTimespan', '#shift_left', function() { " . $field_array['lshift_function'] . '; });' . PHP_EOL;
$readyAdd .= "$(document).rebind('click.cactiTimespan', '#shift_left', function() { " . $field_array['lshift_function'] . '; });' . PHP_EOL;
}
if (!isset($field_array['rshift_function'])) {
$readyAdd .= "$(document).off('click.cactiTimespan', '#shift_right').on('click.cactiTimespan', '#shift_right', function() { timeshiftGraphFilterRight(); });" . PHP_EOL;
$readyAdd .= "$(document).rebind('click.cactiTimespan', '#shift_right', function() { timeshiftGraphFilterRight(); });" . PHP_EOL;
} else {
$readyAdd .= "$(document).off('click.cactiTimespan', '#shift_right').on('click.cactiTimespan', '#shift_right', function() { " . $field_array['rshift_function'] . '; });' . PHP_EOL;
$readyAdd .= "$(document).rebind('click.cactiTimespan', '#shift_right', function() { " . $field_array['rshift_function'] . '; });' . PHP_EOL;
}
}
if (!isset($field_array['refresh_function'])) {
$readyAdd .= "$(document).off('click.cactiTimespan', '#tsrefresh').on('click.cactiTimespan', '#tsrefresh', function() { $(this).find('i').addClass('icon-rotate'); refreshGraphTimespanFilter(); });" . PHP_EOL;
$readyAdd .= "$(document).rebind('click.cactiTimespan', '#tsrefresh', function() { $(this).find('i').addClass('icon-rotate'); refreshGraphTimespanFilter(); });" . PHP_EOL;
} else {
$readyAdd .= "$(document).off('click.cactiTimespan', '#tsrefresh').on('click.cactiTimespan', '#tsrefresh', function() { $(this).find('i').addClass('icon-rotate'); " . $field_array['refresh_function'] . '; });' . PHP_EOL;
$readyAdd .= "$(document).rebind('click.cactiTimespan', '#tsrefresh', function() { $(this).find('i').addClass('icon-rotate'); " . $field_array['refresh_function'] . '; });' . PHP_EOL;
}
if (!isset($field_array['clear_function'])) {
$readyAdd .= "$(document).off('click.cactiTimespan', '#tsclear').on('click.cactiTimespan', '#tsclear', function() { clearGraphTimespanFilter(); });" . PHP_EOL;
$readyAdd .= "$(document).rebind('click.cactiTimespan', '#tsclear', function() { clearGraphTimespanFilter(); });" . PHP_EOL;
} else {
$readyAdd .= "$(document).off('click.cactiTimespan', '#tsclear').on('click.cactiTimespan', '#tsclear', function() { " . $field_array['clear_function'] . '; });' . PHP_EOL;
$readyAdd .= "$(document).rebind('click.cactiTimespan', '#tsclear', function() { " . $field_array['clear_function'] . '; });' . PHP_EOL;
}
break;
@@ -1591,7 +1591,7 @@ class CactiTableFilter {
$js .= "\t$('#layout_delete').click(function() { var id = layoutSelectedId(); if (id == 0 || !layoutEditable()) { return; } layoutDeleteDialog(id); });" . PHP_EOL;
$js .= "\t$('#layout_name').keydown(function(e) { if (e.keyCode == 13) { e.preventDefault(); layoutDialogSave(layoutDialogForceNew); } });" . PHP_EOL;
// Enter in the bar Search box applies the filter; delegated so it survives ajax content reloads.
$js .= "\t$(document).off('keydown.cactiSearch', '#filter, #rfilter').on('keydown.cactiSearch', '#filter, #rfilter', function(e) { if (e.keyCode == 13) { e.preventDefault(); " . $changeFunction . '; } });' . PHP_EOL;
$js .= "\t$(document).rebind('keydown.cactiSearch', '#filter, #rfilter', function(e) { if (e.keyCode == 13) { e.preventDefault(); " . $changeFunction . '; } });' . PHP_EOL;
$js .= '});' . PHP_EOL;
$js .= '</script>' . PHP_EOL;
+2 -2
View File
@@ -2026,7 +2026,7 @@ function html_graph_single_view() : void {
});
});
$('a[id$="_util"]').off('click').on('click', function() {
$('a[id$="_util"]').rebind('click', function() {
graph_id = $(this).attr('id').replace('graph_', '').replace('_util', '');
rra_id = $(this).attr('rra_id');
graph_start = $(this).attr('graph_start');
@@ -2044,7 +2044,7 @@ function html_graph_single_view() : void {
});
$('a[id$="_csv"]').each(function() {
$(this).off('click').on('click', function(event) {
$(this).rebind('click', function(event) {
event.preventDefault();
event.stopPropagation();
document.location = $(this).attr('href');
+3 -3
View File
@@ -1397,16 +1397,16 @@ function grow_right_pane_tree(int $tree_id, int $leaf_id, string $host_group_dat
}
$(function() {
$('#go').off('click').on('click', function(event) {
$('#go').rebind('click', function(event) {
event.preventDefault();
applyGraphFilter();
});
$('#clear').off('click').on('click', function() {
$('#clear').rebind('click', function() {
clearGraphFilter();
});
$('#save').off('click').on('click', function() {
$('#save').rebind('click', function() {
saveGraphFilter('tree');
});
+3 -3
View File
@@ -1335,7 +1335,7 @@ function import_display_package_data(array $templates, array $files, string $pac
makePackagesClickable();
}
$('.diffme').off('click').on('click', function(event) {
$('.diffme').rebind('click', function(event) {
event.preventDefault();
var url = $(this).attr('href');
@@ -1868,11 +1868,11 @@ function package_import() : void {
$(function() {
refreshMSeconds = 9999999;
$('#package_location').off('change').on('change', function() {
$('#package_location').rebind('change', function() {
switchRepo();
});
$('#import_file').off('change').on('change', function() {
$('#import_file').rebind('change', function() {
var form = $('#import')[0];
var data = new FormData(form);
var formExtra = '?action=upload&package_location=0&preview_only=on';
+9 -9
View File
@@ -882,7 +882,7 @@ function update_show_current() : void {
});
}
$('.pirestore').off('click').on('click', function(event) {
$('.pirestore').rebind('click', function(event) {
event.preventDefault();
var dialogTitle = '<?php print $resarchive_title; ?>';
@@ -895,7 +895,7 @@ function update_show_current() : void {
displayDialog(url, dialogTitle, dialogMessage, dialogForm, buttonContinue, buttonCancel, 80, 400);
});
$('.pirmarchive').off('click').on('click', function(event) {
$('.pirmarchive').rebind('click', function(event) {
event.preventDefault();
var dialogTitle = '<?php print $rmarchive_title; ?>';
@@ -908,7 +908,7 @@ function update_show_current() : void {
displayDialog(url, dialogTitle, dialogMessage, dialogForm, buttonContinue, buttonCancel, 80, 400);
});
$('.piarchive').off('click').on('click', function(event) {
$('.piarchive').rebind('click', function(event) {
event.preventDefault();
var dialogTitle = '<?php print $archive_title; ?>';
@@ -921,7 +921,7 @@ function update_show_current() : void {
displayDialog(url, dialogTitle, dialogMessage, dialogForm, buttonContinue, buttonCancel, 120, 600);
});
$('.pirmdata').off('click').on('click', function(event) {
$('.pirmdata').rebind('click', function(event) {
event.preventDefault();
var dialogTitle = '<?php print $rmdata_title; ?>';
@@ -934,7 +934,7 @@ function update_show_current() : void {
displayDialog(url, dialogTitle, dialogMessage, dialogForm, buttonContinue, buttonCancel, 80, 400);
});
$('.piuninstall').off('click').on('click', function(event) {
$('.piuninstall').rebind('click', function(event) {
event.preventDefault();
var dialogTitle = '<?php print $uninstall_title; ?>';
@@ -947,7 +947,7 @@ function update_show_current() : void {
displayDialog(url, dialogTitle, dialogMessage, dialogForm, buttonContinue, buttonCancel, 80, 400);
});
$('.pidisable').off('click').on('click', function(event) {
$('.pidisable').rebind('click', function(event) {
event.preventDefault();
var url = $(this).attr('href');
@@ -959,7 +959,7 @@ function update_show_current() : void {
postUrl({ url: url }, { __csrf_magic: csrfMagicToken });
});
$('.piforceremove').off('click').on('click', function(event) {
$('.piforceremove').rebind('click', function(event) {
event.preventDefault();
var cfg = <?php print $forceremove_cfg; ?>;
@@ -974,7 +974,7 @@ function update_show_current() : void {
$('#dialogForm input[name=plugin]').val(plugin);
});
$('.pireadme').off('click').on('click', function(event) {
$('.pireadme').rebind('click', function(event) {
event.preventDefault();
var dialogTitle = '<?php print __esc('Plugin Reame File'); ?>';
@@ -983,7 +983,7 @@ function update_show_current() : void {
displayFileDialog(url, dialogTitle, 400, 700);
});
$('.pichangelog').off('click').on('click', function(event) {
$('.pichangelog').rebind('click', function(event) {
event.preventDefault();
var dialogTitle = '<?php print __esc('Plugin ChangeLog File'); ?>';
@@ -157,9 +157,8 @@ test('the cactiPostAction handler posts the token and refuses another origin', f
/* The class has to be bound on its own, not only through the ajaxAnchors
selector, because several tagged anchors carry none of the classes that
selector names. */
expect($js)->toContain("$(document).off('click.cactiPostAction', 'a.cactiPostAction')")
->and($js)->toContain(".on('click.cactiPostAction', 'a.cactiPostAction'")
->and($js)->toContain(".not('.cactiPostAction').off('click')")
expect($js)->toContain("$(document).rebind('click.cactiPostAction', 'a.cactiPostAction'")
->and($js)->toContain(".not('.cactiPostAction').rebind('click'")
->and($js)->toContain('submitPageUsingPost')
->and($js)->toContain('cactiPreparePostRequestFromUrl');
+1 -1
View File
@@ -75,6 +75,6 @@ test('cross-origin targets are rejected before a token is exposed', () => {
});
test('the state-action handler survives element-level unbind calls', () => {
assert.match(source, /\$\(document\)\.off\('click\.cactiPostAction', 'a\.cactiPostAction'\)\.on\('click\.cactiPostAction', 'a\.cactiPostAction'/);
assert.match(source, /\$\(document\)\.rebind\('click\.cactiPostAction', 'a\.cactiPostAction'/);
assert.doesNotMatch(source, /\$\('a\.cactiPostAction'\)\.off\('click\.cactiPostAction'/);
});
+105
View File
@@ -0,0 +1,105 @@
/*
+-------------------------------------------------------------------------+
| Copyright (C) 2004-2026 The Cacti Group |
+-------------------------------------------------------------------------+
| Cacti: The Complete RRDtool-based Graphing Solution |
+-------------------------------------------------------------------------+
*/
'use strict';
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const test = require('node:test');
const vm = require('node:vm');
const layoutSource = fs.readFileSync(
path.join(__dirname, '..', '..', 'include', 'layout.js'),
'utf8'
);
// Pull the `$.fn.rebind = function ... };` assignment out of layout.js by
// brace-matching so the helper can be exercised without a browser or jQuery.
function rebindSource() {
const start = layoutSource.indexOf('$.fn.rebind = function');
assert.notEqual(start, -1, '$.fn.rebind must exist in layout.js');
const bodyStart = layoutSource.indexOf('{', start);
let depth = 0;
for (let offset = bodyStart; offset < layoutSource.length; offset++) {
if (layoutSource[offset] === '{') {
depth++;
} else if (layoutSource[offset] === '}') {
depth--;
if (depth === 0) {
return layoutSource.slice(start, offset + 1) + ';';
}
}
}
throw new Error('Unable to extract $.fn.rebind');
}
function loadRebind() {
const $ = { fn: {} };
vm.runInNewContext(rebindSource(), { $ });
return $.fn.rebind;
}
// A minimal jQuery-set stand-in that records off()/on() calls and chains.
function makeSet(calls) {
const set = {
off(...args) { calls.push(['off', args]); return set; },
on(...args) { calls.push(['on', args]); return set; },
};
return set;
}
const rebind = loadRebind();
test('rebind unbinds then binds a direct handler and returns the set', () => {
const calls = [];
const set = makeSet(calls);
const handler = () => {};
const result = rebind.apply(set, ['click', handler]);
assert.deepEqual(calls, [['off', ['click']], ['on', ['click', handler]]]);
assert.equal(result, set);
});
test('rebind preserves the selector for delegated handlers', () => {
const calls = [];
const set = makeSet(calls);
const handler = () => {};
rebind.apply(set, ['click', '.item', handler]);
assert.deepEqual(calls, [['off', ['click', '.item']], ['on', ['click', '.item', handler]]]);
});
test('rebind preserves the selector for the two-argument event-map overload', () => {
const calls = [];
const set = makeSet(calls);
const map = { click: () => {} };
rebind.apply(set, [map, '.item']);
assert.deepEqual(calls, [['off', [map, '.item']], ['on', [map, '.item']]]);
});
test('rebind treats a non-string second argument as handler data, not a selector', () => {
const calls = [];
const set = makeSet(calls);
const data = { count: 1 };
const handler = () => {};
rebind.apply(set, ['click', data, handler]);
assert.deepEqual(calls, [['off', ['click']], ['on', ['click', data, handler]]]);
});
+4 -8
View File
@@ -65,15 +65,11 @@ test('scroll handlers close initialized select menus without duplicate bindings'
assert.equal(target, windowObject);
return {
off: (event) => {
assert.equal(event, 'scroll.cactiSelectmenu');
rebind: (bound, handler) => {
assert.equal(bound, 'scroll.cactiSelectmenu');
return {
on: (bound, handler) => {
boundEvent = bound;
scrollHandler = handler;
}
};
boundEvent = bound;
scrollHandler = handler;
}
};
}
+1 -1
View File
@@ -678,7 +678,7 @@ function vdef_edit() : void {
applySkin();
$('#continue').off('click').on('click', function(data) {
$('#continue').rebind('click', function(data) {
$.post('vdef.php?action=item_remove', {
__csrf_magic: csrfMagicToken,
vdef_id: $('#my_vdef_id').val(),