add ajaxmethods and ajaxhandler for requests without site reload
This commit is contained in:
1 parent
951253c559
commit
e570dcce25
2 files changed
+274
No files matched your search
@@ -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']);
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user