Compare commits

...

2 Commits

Author SHA1 Message Date
Peter Pfeufer
0360184c2d
[ADD] QuerySelector function to prevent forms from double submitting
This is to prevent forms from submitting multiple times when users double-click or even more …
2025-08-09 16:37:20 +02:00
Peter Pfeufer
099a39a2a2
[ADD] objectDeepMerge function
Recursively merges properties from source objects into a target object. If a property at the current level is an object,
and both target and source have it, the property is merged. Otherwise, the source property overwrites the target property.

This function does not modify the source objects and prevents prototype pollution by not allowing `__proto__`, `constructor`,
and `prototype` property names.
2025-08-09 15:54:39 +02:00
4 changed files with 110 additions and 3 deletions

View File

@ -15,6 +15,10 @@
ul#nav-right:has(li) + ul#nav-right-character-control > li:first-child { ul#nav-right:has(li) + ul#nav-right-character-control > li:first-child {
display: list-item !important; display: list-item !important;
} }
form.is-submitting button[type="submit"] {
cursor: not-allowed;
}
} }
@media all and (max-width: 991px) { @media all and (max-width: 991px) {

View File

@ -1,3 +1,7 @@
/**
* Functions and utilities for the Alliance Auth framework.
*/
/* jshint -W097 */ /* jshint -W097 */
'use strict'; 'use strict';
@ -217,3 +221,80 @@ const fetchPost = async ({
responseIsJson: responseIsJson responseIsJson: responseIsJson
}); });
}; };
/**
* Recursively merges properties from source objects into a target object. If a property at the current level is an object,
* and both target and source have it, the property is merged. Otherwise, the source property overwrites the target property.
* This function does not modify the source objects and prevents prototype pollution by not allowing __proto__, constructor,
* and prototype property names.
*
* @usage
* ```javascript
* const target = {a: 1, b: {c: 2}};
* const source1 = {b: {d: 3}, e: 4 };
* const source2 = {a: 5, b: {c: 6}};
*
* const merged = objectDeepMerge(target, source1, source2);
*
* console.log(merged); // {a: 5, b: {c: 6, d: 3}, e: 4}
* ```
*
* @param {Object} target The target object to merge properties into.
* @param {...Object} sources One or more source objects from which to merge properties.
* @returns {Object} The target object after merging properties from sources.
*/
function objectDeepMerge (target, ...sources) {
if (!sources.length) {
return target;
}
// Iterate through each source object without modifying the `sources` array.
sources.forEach(source => {
if (isObject(target) && isObject(source)) {
for (const key in source) {
if (isObject(source[key])) {
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
continue; // Skip potentially dangerous keys to prevent prototype pollution.
}
if (!target[key] || !isObject(target[key])) {
target[key] = {};
}
objectDeepMerge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}
});
return target;
}
/**
* When the document is ready
*/
$(document).ready(() => {
/**
* Prevent double form submits by adding a class to the form
* when it is submitted.
*
* This class can be used to show a visual indicator that the form is being
* submitted, such as a spinner.
*
* This is useful to prevent users from double-clicking the submit button
* and submitting the form multiple times.
*/
document.querySelectorAll('form').forEach((form) => {
form.addEventListener('submit', (e) => {
// Prevent if already submitting
if (form.classList.contains('is-submitting')) {
e.preventDefault();
}
// Add class to hook our visual indicator on
form.classList.add('is-submitting');
});
});
});

View File

@ -21,9 +21,11 @@
{% theme_css %} {% theme_css %}
{% include 'bundles/fontawesome.html' %} {% include 'bundles/fontawesome.html' %}
{% include 'bundles/auth-framework-js.html' %}
{% include 'bundles/auth-framework-css.html' %} {% include 'bundles/auth-framework-css.html' %}
{% include 'bundles/jquery-js.html' %}
{% include 'bundles/auth-framework-js.html' %}
<style> <style>
@media all { @media all {
.nav-padding { .nav-padding {
@ -138,8 +140,6 @@
})(); })();
</script> </script>
{% include 'bundles/jquery-js.html' %}
{% theme_js %} {% theme_js %}
{% if user.is_authenticated %} {% if user.is_authenticated %}

View File

@ -100,3 +100,25 @@ fetchPost({
- `csrfToken`: The CSRF token to include in the request headers. - `csrfToken`: The CSRF token to include in the request headers.
- `payload`: The data as JS object to send with the request. - `payload`: The data as JS object to send with the request.
- `responseIsJson`: Optional boolean indicating if the response should be parsed as JSON (default is `true`). - `responseIsJson`: Optional boolean indicating if the response should be parsed as JSON (default is `true`).
### objectDeepMerge()
Recursively merges properties from source objects into a target object. If a property at the current level is an object,
and both target and source have it, the property is merged. Otherwise, the source property overwrites the target property.
This function does not modify the source objects and prevents prototype pollution by not allowing `__proto__`, `constructor`,
and `prototype` property names.
Usage:
```javascript
/* global objectDeepMerge */
const target = {a: 1, b: {c: 2}};
const source1 = {b: {d: 3}, e: 4 };
const source2 = {a: 5, b: {c: 6}};
const merged = objectDeepMerge(target, source1, source2);
console.log(merged); // {a: 5, b: {c: 6, d: 3}, e: 4}
```