mirror of
https://github.com/itflow-org/itflow
synced 2026-09-14 18:55:12 +00:00
Fixed Side nav titles, Fixed ajax on bootstrap modals, fixed pagination all regressions
This commit is contained in:
@@ -1,3 +1,36 @@
|
||||
/**
|
||||
* Re-run <script> elements that arrived via innerHTML, strictly in order.
|
||||
*
|
||||
* External scripts are awaited before the next one starts; inline scripts run
|
||||
* synchronously. Order matters because a modal's own script is emitted before
|
||||
* modal_footer.php's http.js / autocomplete.js / app.js, and depends on them.
|
||||
*/
|
||||
function runScriptsInOrder(scripts) {
|
||||
return scripts.reduce(function (chain, old) {
|
||||
return chain.then(function () {
|
||||
return new Promise(function (resolve) {
|
||||
const s = document.createElement('script');
|
||||
for (const attr of old.attributes) {
|
||||
s.setAttribute(attr.name, attr.value);
|
||||
}
|
||||
if (old.src) {
|
||||
s.async = false;
|
||||
s.onload = resolve;
|
||||
s.onerror = function () {
|
||||
console.error('ajax-modal: failed to load', old.src);
|
||||
resolve();
|
||||
};
|
||||
old.replaceWith(s);
|
||||
} else {
|
||||
s.textContent = old.textContent;
|
||||
old.replaceWith(s);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}, Promise.resolve());
|
||||
}
|
||||
|
||||
// Ajax Modal Load Script
|
||||
document.addEventListener('click', function (e) {
|
||||
const trigger = e.target.closest('.ajax-modal');
|
||||
@@ -64,19 +97,17 @@ document.addEventListener('click', function (e) {
|
||||
'</div>';
|
||||
host.appendChild(wrapper);
|
||||
|
||||
// innerHTML does not execute <script> tags. The modal payload ends
|
||||
// with modal_footer.php, which re-runs app.js to wire up Tom Select,
|
||||
// IMask, flatpickr and friends - so re-inject them by hand.
|
||||
wrapper.querySelectorAll('script').forEach(function (old) {
|
||||
const s = document.createElement('script');
|
||||
for (const attr of old.attributes) {
|
||||
s.setAttribute(attr.name, attr.value);
|
||||
}
|
||||
s.textContent = old.textContent;
|
||||
old.replaceWith(s);
|
||||
});
|
||||
|
||||
bootstrap.Modal.getOrCreateInstance(wrapper).show();
|
||||
// innerHTML does not execute <script> tags, so they have to be
|
||||
// re-injected. They must also run IN ORDER: a modal payload loads
|
||||
// its own script first and modal_footer.php's http.js / app.js
|
||||
// after, and the modal script depends on helpers those define.
|
||||
// A dynamically created <script src> is async by default and would
|
||||
// run in completion order instead - jQuery's .append() loaded them
|
||||
// sequentially, which is the behaviour reproduced here.
|
||||
runScriptsInOrder(Array.from(wrapper.querySelectorAll('script')))
|
||||
.then(function () {
|
||||
bootstrap.Modal.getOrCreateInstance(wrapper).show();
|
||||
});
|
||||
|
||||
wrapper.addEventListener('hidden.bs.modal', function () {
|
||||
wrapper.remove();
|
||||
|
||||
74
js/app.js
74
js/app.js
@@ -8,38 +8,20 @@
|
||||
* jQuery's delegation contract so the handler bodies are unchanged.
|
||||
*/
|
||||
/**
|
||||
* $.post replacement. jQuery serialised nested arrays/objects into PHP-style
|
||||
* bracket params (positions[0][status_id]=...), which is what ajax.php parses,
|
||||
* so that encoding is reproduced here rather than sending JSON.
|
||||
* Run one initialiser in isolation.
|
||||
*
|
||||
* itflowInit() sets up eight independent libraries in sequence. Without this,
|
||||
* a throw in any one of them aborts the whole function and every initialiser
|
||||
* after it silently never runs - which is exactly the kind of failure that
|
||||
* looks like "everything is broken" while the console shows one error from a
|
||||
* library you were not looking at.
|
||||
*/
|
||||
function itflowPostForm(url, data) {
|
||||
const params = new URLSearchParams();
|
||||
|
||||
(function add(prefix, value) {
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach(function (v, i) {
|
||||
add(prefix + '[' + i + ']', v);
|
||||
});
|
||||
} else if (value !== null && typeof value === 'object') {
|
||||
Object.keys(value).forEach(function (k) {
|
||||
add(prefix ? prefix + '[' + k + ']' : k, value[k]);
|
||||
});
|
||||
} else {
|
||||
params.append(prefix, value === true ? 'true' : String(value));
|
||||
}
|
||||
})('', data);
|
||||
|
||||
return fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
credentials: 'same-origin',
|
||||
body: params.toString()
|
||||
}).then(function (res) {
|
||||
if (!res.ok) {
|
||||
throw new Error('HTTP ' + res.status);
|
||||
}
|
||||
return res.text();
|
||||
});
|
||||
function itflowStep(name, fn) {
|
||||
try {
|
||||
fn();
|
||||
} catch (e) {
|
||||
console.error('itflow init [' + name + '] failed:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function itflowBindOnce(name, type, selector, handler) {
|
||||
@@ -90,8 +72,10 @@ function itflowInit() {
|
||||
|
||||
// Initialize Tom Select (replaces Select2). Every instance is reachable
|
||||
// afterwards as element.tomselect, which is how the helpers below reach it.
|
||||
document.querySelectorAll('.select2').forEach(function (el) {
|
||||
initTomSelect(el);
|
||||
itflowStep('tom-select', function () {
|
||||
document.querySelectorAll('.select2').forEach(function (el) {
|
||||
initTomSelect(el);
|
||||
});
|
||||
});
|
||||
|
||||
// Initialize TinyMCE
|
||||
@@ -532,23 +516,29 @@ function itflowInit() {
|
||||
// the lever, not a Bootstrap patch.
|
||||
|
||||
// Clipboard
|
||||
var clipboard = new ClipboardJS('.clipboardjs');
|
||||
itflowStep('clipboard', function () {
|
||||
var clipboard = new ClipboardJS('.clipboardjs');
|
||||
|
||||
clipboard.on('success', function(e) {
|
||||
flashTooltip(e.trigger, 'Copied!');
|
||||
});
|
||||
clipboard.on('success', function(e) {
|
||||
flashTooltip(e.trigger, 'Copied!');
|
||||
});
|
||||
|
||||
clipboard.on('error', function(e) {
|
||||
flashTooltip(e.trigger, 'Failed!');
|
||||
clipboard.on('error', function(e) {
|
||||
flashTooltip(e.trigger, 'Failed!');
|
||||
});
|
||||
});
|
||||
|
||||
// Enable Popovers
|
||||
document.querySelectorAll('[data-bs-toggle="popover"]').forEach(function (el) {
|
||||
bootstrap.Popover.getOrCreateInstance(el);
|
||||
itflowStep('popovers', function () {
|
||||
document.querySelectorAll('[data-bs-toggle="popover"]').forEach(function (el) {
|
||||
bootstrap.Popover.getOrCreateInstance(el);
|
||||
});
|
||||
});
|
||||
|
||||
// Data Tables
|
||||
new DataTable('.dataTables');
|
||||
itflowStep('datatables', function () {
|
||||
new DataTable('.dataTables');
|
||||
});
|
||||
}
|
||||
|
||||
// modal_footer.php re-loads this file on every ajax modal open, so run now if
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
function showOTPViaCredentialID(credential_id) {
|
||||
// Send a GET request to ajax.php as ajax.php?get_totp_token_via_id=true&credential_id=ID
|
||||
jQuery.get(
|
||||
itflowGet(
|
||||
"../ajax.php", {
|
||||
get_totp_token_via_id: 'true',
|
||||
credential_id: credential_id
|
||||
|
||||
96
js/http.js
Normal file
96
js/http.js
Normal file
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Drop-in replacements for itflowGet() and itflowPost().
|
||||
*
|
||||
* These keep jQuery's exact call shape - itflowGet(url, data, success, error) -
|
||||
* so the ~28 existing call sites did not have to be restructured when jQuery
|
||||
* was removed. Every one of them uses the plain callback form; none chained
|
||||
* .done()/.fail() or passed a dataType, so nothing else is reproduced here.
|
||||
*
|
||||
* The success callback receives the raw response TEXT, matching jQuery's
|
||||
* behaviour when the server does not send a JSON content type. Several call
|
||||
* sites do their own JSON.parse(), so returning a parsed object here would
|
||||
* break them.
|
||||
*/
|
||||
|
||||
/** Serialise to PHP-style bracket params, the way jQuery did. */
|
||||
function itflowSerialize(data) {
|
||||
const params = new URLSearchParams();
|
||||
|
||||
(function add(prefix, value) {
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach(function (v, i) {
|
||||
add(prefix + '[' + i + ']', v);
|
||||
});
|
||||
} else if (value !== null && typeof value === 'object') {
|
||||
Object.keys(value).forEach(function (k) {
|
||||
add(prefix ? prefix + '[' + k + ']' : k, value[k]);
|
||||
});
|
||||
} else {
|
||||
params.append(prefix, value === true ? 'true' : String(value));
|
||||
}
|
||||
})('', data || {});
|
||||
|
||||
return params.toString();
|
||||
}
|
||||
|
||||
function itflowGet(url, data, success, error) {
|
||||
const query = itflowSerialize(data);
|
||||
const sep = url.indexOf('?') === -1 ? '?' : '&';
|
||||
|
||||
return fetch(query ? url + sep + query : url, {
|
||||
method: 'GET',
|
||||
credentials: 'same-origin'
|
||||
})
|
||||
.then(function (res) {
|
||||
if (!res.ok) {
|
||||
throw new Error('HTTP ' + res.status);
|
||||
}
|
||||
return res.text();
|
||||
})
|
||||
.then(function (text) {
|
||||
if (typeof success === 'function') {
|
||||
success(text);
|
||||
}
|
||||
return text;
|
||||
})
|
||||
.catch(function (err) {
|
||||
if (typeof error === 'function') {
|
||||
error(err);
|
||||
} else {
|
||||
console.error('itflowGet ' + url + ':', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function itflowPost(url, data, success, error) {
|
||||
return fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
credentials: 'same-origin',
|
||||
body: itflowSerialize(data)
|
||||
})
|
||||
.then(function (res) {
|
||||
if (!res.ok) {
|
||||
throw new Error('HTTP ' + res.status);
|
||||
}
|
||||
return res.text();
|
||||
})
|
||||
.then(function (text) {
|
||||
if (typeof success === 'function') {
|
||||
success(text);
|
||||
}
|
||||
return text;
|
||||
})
|
||||
.catch(function (err) {
|
||||
if (typeof error === 'function') {
|
||||
error(err);
|
||||
} else {
|
||||
console.error('itflowPost ' + url + ':', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Kept for the kanban and invoice item-order callers, which use this name. */
|
||||
function itflowPostForm(url, data) {
|
||||
return itflowPost(url, data);
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
function keep_alive() {
|
||||
|
||||
//Send a GET request to keepalive.php as keepalive.php?keepalive
|
||||
jQuery.get(
|
||||
itflowGet(
|
||||
"/keepalive.php",
|
||||
{keepalive: 'true'},
|
||||
function(data) {
|
||||
|
||||
Reference in New Issue
Block a user