add ajaxmethods and ajaxhandler for requests without site reload

This commit is contained in:
Tower committed 2026-07-18 14:43:08 +02:00
1 parent 951253c559
commit e570dcce25
2 files changed
+274

No files matched your search

+169
View File
@@ -0,0 +1,169 @@
<?php
include_once(__DIR__ . '/../../controller/DatabaseController.php');
header('Content-Type: application/json');
error_reporting(E_ALL);
ini_set('display_errors', '0');
$erlaubteSchema = [
'Einkaufsliste' => ['ID', 'PRODUKT', 'MENGE'],
];
$erlaubteOperatoren = ['=', '!=', '<', '>', '<=', '>=', 'LIKE'];
function isValidEntry(string $name): bool {
return (bool) preg_match('/^[a-zA-Z0-9_]+$/', $name);
}
function isValidTable(string $table, array $schema): void {
if (!isValidEntry($table) || !isset($schema[$table])) {
throw new InvalidArgumentException("Tabelle nicht erlaubt: $table");
}
}
function isValidColumn(string $table, array $columns, array $schema): void {
foreach ($columns as $col) {
if (!isValidEntry($col) || !in_array($col, $schema[$table], true)) {
throw new InvalidArgumentException("Spalte nicht erlaubt: $col");
}
}
}
function baueWhere(array $conditions, string $table, array $schema, array $erlaubteOperatoren): array {
$parts = [];
$values = [];
foreach ($conditions as $cond) {
$col = $cond['column'] ?? '';
$op = $cond['operator'] ?? '=';
$val = $cond['value'] ?? null;
isValidColumn($table, [$col], $schema);
if (!in_array($op, $erlaubteOperatoren, true)) {
throw new InvalidArgumentException("Ungültiger Operator: $op");
}
$parts[] = "`$col` $op ?";
$values[] = $val;
}
return [implode(' AND ', $parts), $values];
}
// -------------------------------------------------------------------
// Request einlesen
// -------------------------------------------------------------------
$daten = json_decode(file_get_contents('php://input'), true) ?? [];
$action = $daten['action'] ?? '';
$table = $daten['table'] ?? '';
try {
isValidTable($table, $erlaubteSchema);
switch ($action) {
case 'insert': {
$columns = $daten['columns'] ?? [];
$values = $daten['values'] ?? [];
isValidColumn($table, $columns, $erlaubteSchema);
if (count($columns) !== count($values)) {
throw new InvalidArgumentException('Anzahl der Werte passt nicht zu den Spalten');
}
$columnsSql = implode(', ', array_map(fn($c) => "`$c`", $columns));
$result = DatabaseInsert(
$table,
$columnsSql,
...$values);
echo json_encode(['success' => $result]);
break;
}
case 'show': {
$columns = $daten['columns'] ?? [];
$conditions = $daten['conditions'] ?? [];
if (empty($columns)) {
$columns = $erlaubteSchema[$table];
} else {
isValidColumn($table, $columns, $erlaubteSchema);
}
$columnsSql = implode(', ', array_map(fn($c) => "`$c`", $columns));
[$whereSql, $values] = baueWhere($conditions, $table, $erlaubteSchema, $erlaubteOperatoren);
$result = DatabaseShow(
$table,
$columnsSql,
$whereSql,
...$values
);
echo json_encode(['success' => true, 'entries' => array_values($result ?? [])]);
break;
}
case 'update': {
$updates = $daten['updates'] ?? [];
$conditions = $daten['conditions'] ?? [];
if (empty($conditions)) {
throw new InvalidArgumentException('WHERE-Bedingung ist bei UPDATE Pflicht');
}
isValidColumn($table, array_keys($updates), $erlaubteSchema);
$setParts = [];
$setValues = [];
foreach ($updates as $col => $val) {
$setParts[] = "`$col` = ?";
$setValues[] = $val;
}
$setSql = implode(', ', $setParts);
[$whereSql, $whereValues] = baueWhere($conditions, $table, $erlaubteSchema, $erlaubteOperatoren);
$result = DatabaseUpdate(
$table,
$setSql,
$whereSql,
...array_merge(
$setValues,
$whereValues
)
);
echo json_encode(['success' => $result]);
break;
}
case 'delete': {
$conditions = $daten['conditions'] ?? [];
if (empty($conditions)) {
throw new InvalidArgumentException('WHERE-Bedingung ist bei DELETE Pflicht');
}
[$whereSql, $values] = baueWhere($conditions, $table, $erlaubteSchema, $erlaubteOperatoren);
$result = DatabaseDelete($table, $whereSql, ...$values);
echo json_encode(['success' => $result]);
break;
}
default:
throw new InvalidArgumentException("Unbekannte Aktion: $action");
}
} catch (InvalidArgumentException $e) {
http_response_code(400);
echo json_encode(['error' => $e->getMessage()]);
} catch (Throwable $e) {
http_response_code(500);
error_log($e->getMessage());
echo json_encode(['error' => 'Serverfehler']);
}
+105
View File
@@ -0,0 +1,105 @@
const AJAX_HANDLER_URL = '/api/js/AjaxHandler';
function escapeHtml(str) {
const div = document.createElement('div');
div.textContent = str ?? '';
return div.innerHTML;
}
// -----------------------------------------------------------
// Sicher JSON aus einer Response lesen
// -----------------------------------------------------------
async function parseJsonResponse(response) {
const rawText = await response.text();
try {
return JSON.parse(rawText);
} catch (e) {
console.error('Antwort war kein gültiges JSON:', rawText);
throw new Error('Server hat kein gültiges JSON zurückgegeben (siehe Konsole)');
}
}
async function ajaxRequest(payload) {
try {
const response = await fetch(AJAX_HANDLER_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const data = await parseJsonResponse(response);
if (data.error) {
throw new Error(data.error);
}
return data;
} catch (error) {
console.error('Ajax-Fehler:', error.message);
throw error;
}
}
function DatabaseInsert(table, columns, values) {
return ajaxRequest({ action: 'insert', table, columns, values });
}
function DatabaseShow(table, columns = [], conditions = []) {
return ajaxRequest({ action: 'show', table, columns, conditions });
}
function DatabaseUpdate(table, updates, conditions) {
return ajaxRequest({ action: 'update', table, updates, conditions });
}
function DatabaseDelete(table, conditions) {
return ajaxRequest({ action: 'delete', table, conditions });
}
function renderTabelle(wrapperEl, entries, spaltenKonfig, idSpalte, onDelete) {
if (entries && !Array.isArray(entries)) {
console.warn('entries war kein Array:', entries);
entries = Object.values(entries);
}
if (!entries || entries.length === 0) {
wrapperEl.innerHTML = '<p>Keine Einträge vorhanden.</p>';
return;
}
let html = `
<table class="db-table mobile-responsive-table">
<thead>
<tr>
${spaltenKonfig.map(s => `<th>${escapeHtml(s.label)}</th>`).join('')}
<th style="width: 100px;">Aktion</th>
</tr>
</thead>
<tbody>
`;
for (const entry of entries) {
html += `<tr>`;
for (const s of spaltenKonfig) {
html += `<td data-label="${escapeHtml(s.label)}">${escapeHtml(entry[s.key])}</td>`;
}
html += `
<td data-label="Aktion">
<button type="button" class="btn-delete" data-id="${escapeHtml(entry[idSpalte])}">
🗑️ Löschen
</button>
</td>
</tr>`;
}
html += `</tbody>
</table>`;
wrapperEl.innerHTML = html;
wrapperEl.querySelectorAll('.btn-delete').forEach(btn => {
btn.addEventListener('click', async () => {
if (!confirm('Möchtest du diesen Eintrag wirklich löschen?')) return;
await onDelete(btn.dataset.id);
});
});
}