Browse Source

Fix up the Form so it's easier to use and more consistent. Rather than have Form load the schema it accepts a schema. That allows the page using Form to handle the errors and other things. Form is also not meant to be a great complex form but a starter form.

master
Zed A. Shaw 2 months ago
parent
commit
3d274246e7
  1. 19
      client/api.js
  2. 60
      client/components/Form.svelte
  3. 47
      client/pages/admin/Create.svelte
  4. 81
      client/pages/admin/ReadUpdate.svelte
  5. 15
      lib/api.js
  6. 1
      lib/ormish.js

19
client/api.js

@ -150,8 +150,25 @@ export const logout_user = async () => {
window.location.replace("/client/#/login");
}
export const schema = async (table) => {
let [status, tables] = await get('/api/admin/schema');
if(status == 200) {
for(let t of tables) {
if(t.name === table) {
// this exits the function with the schema
return t._columns;
}
}
// this happens if the table isn't in the schema
return undefined;
} else {
return undefined;
}
}
export default {
post, get, put, del, mock, options, fetch_opts,
logout_user, validate, can_submit, clean_form
logout_user, validate, can_submit, clean_form, schema
}

60
client/components/Form.svelte

@ -1,67 +1,23 @@
<script>
import { link } from 'svelte-spa-router';
import Spinner from "./Spinner.svelte";
import FormField from "./FormField.svelte";
import { onMount } from 'svelte';
import { fade } from 'svelte/transition';
import api from "$/client/api.js";
import { defer } from "$/client/helpers.js";
let label_names = [];
export let data = {_errors: {}, _valid: false};
let schema = {};
export let schema = {};
export let table = "";
export let notice = "";
let schema_promise = defer();
onMount(async () => {
let [status, tables] = await api.get('/api/admin/schema');
if(status == 200) {
for(let t of tables) {
if(t.name === table) {
schema = t._columns;
break;
}
}
label_names = Object.keys(schema);
schema_promise.resolve();
} else {
notice = "Unable to load table schema.";
schema_promise.reject();
}
});
console.log("SCHEMA IS", schema, "DATA IS", data);
</script>
<style>
form schemas {
display: flex;
flex-direction: column;
padding: 0.5rem;
margin-top: 0.5rem;
border-radius: var(--border-radius);
background: var(--color-bg-secondary);
}
form schemas h3 {
margin: 0px;
}
b.notice {
color: var(--color-accent);
text-shadow: 2px 2px var(--color-shadow);
}
span#help-overlay {
font-size: 0.6em;
}
</style>
<section>
{#await schema_promise}
<Spinner />
{:then form}
<form>
<card>
<top>
@ -74,7 +30,7 @@
</top>
<middle>
{#each label_names as label}
{#each Object.getOwnPropertyNames(schema) as label}
<FormField form={ data } field={ label }
label={ label }>
<span id="help-overlay">
@ -89,20 +45,10 @@
</middle>
<bottom>
{#if notice}
<callout in:fade|local>
{ notice }
</callout>
{/if}
<button-group>
<slot></slot>
</button-group>
</bottom>
</card>
</form>
{:catch}
<callout class="error">
<p>{ notice }</p>
</callout>
{/await}
</section>

47
client/pages/admin/Create.svelte

@ -1,15 +1,30 @@
<script>
import { push, link } from 'svelte-spa-router';
import { onMount } from "svelte";
import Icon from "$/client/components/Icon.svelte";
import Layout from "$/client/Layout.svelte";
import Form from "$/client/components/Form.svelte";
import api from "$/client/api.js";
import { fade } from "svelte/transition";
import { defer } from "$/client/helpers.js";
export let params = {};
let data = {};
let label_names = [];
let error = "";
export let params = {};
let schema = {};
const load_promise = defer();
onMount(async () => {
schema = await api.schema(params.table);
if(schema === undefined) {
error = "Failed to load schema.";
load_promise.reject();
} else {
load_promise.resolve();
}
});
const create_record = async () => {
let [status, row] = await api.put(`/api/admin/table?name=${params.table}`, data);
@ -17,25 +32,29 @@
if(status == 200) {
error = "";
push(`/admin/table/${params.table}/${row.id}/`);
} else if(status == 401) {
push("/login");
} else {
// TODO: need to vastly improve the error checking but it's tough
// to get good errors out of knex because they throw raw strings
error = "Could not save. Check logs.";
error = "Failed saving record.";
data = row;
}
}
</script>
<Layout authenticated={ true } testid="admin-create-page">
<Form label_names={label_names} data={data} table={params.table}>
<a data-testid="button-back" href="/admin/table/{ params.table }" use:link>
<Icon name="arrow-left-circle" size="48" />
</a>
<span data-testid="button-create" on:click={ create_record }>
<Icon name="save" size="48" />
</span>
{#await load_promise}
... loading ...
{:then}
<Form data={data} table={params.table} schema={ schema }>
<a data-testid="button-back" href="/admin/table/{ params.table }" use:link>
<Icon name="arrow-left-circle" size="48" />
</a>
</Form>
<span data-testid="button-create" on:click={ create_record }>
<Icon name="save" size="48" />
</span>
</Form>
{/await}
{#if error}
<toast-list class="bottom right active">

81
client/pages/admin/ReadUpdate.svelte

@ -6,14 +6,17 @@
import Modal from "$/client/components/Modal.svelte";
import Form from "$/client/components/Form.svelte";
import api from "$/client/api.js";
import { fade } from "svelte/transition";
import { log } from "$/client/logging.js";
import { defer } from "$/client/helpers.js";
export let params = {};
let form_data = {_errors: {}, _valid: false};
let notice = "";
export let params = {};
let delete_confirm = false;
let data_promise = defer();
let load_promise = defer();
let schema = {};
const delete_record = async () => {
let [status, data] = await api.del(`/api/admin/table?name=${params.table}&row_id=${params.row_id}`);
@ -55,16 +58,23 @@
}
onMount(async () => {
let [status, data] = await api.get(`/api/admin/table?name=${params.table}&row_id=${params.row_id}`);
schema = await api.schema(params.table);
if(status == 200) {
form_data = data;
data_promise.resolve();
} else if(status == 401) {
push('/login/');
if(schema === undefined) {
notice = "Failed to load schema."
load_promise.reject();
} else {
notice = "Failed to load table data.";
data_promise.reject();
let [status, data] = await api.get(`/api/admin/table?name=${params.table}&row_id=${params.row_id}`);
if(status == 200) {
form_data = data;
load_promise.resolve();
} else if(status == 401) {
push('/login/');
} else {
notice = "Failed to load table data.";
load_promise.reject();
}
}
});
</script>
@ -76,33 +86,46 @@
</style>
<Layout authenticated={ true } testid="page-admin-readupdate">
{#await data_promise}
{#await load_promise}
<!-- form already has a spinner -->
{:then}
<Form data={form_data} table={params.table} notice={ notice }>
<a href="/admin/table/{ params.table }" data-testid="button-back" use:link>
<Icon name="arrow-left-circle" size="48" />
</a>
<span data-testid="button-copy" on:click={ json_copy }>
<Icon name="copy" size="48" />
</span>
<span data-testid="button-update" on:click={ update_record }>
<Icon name="save" size="48" />
</span>
<span data-testid="button-delete" on:click={ () => delete_confirm = true }>
<Icon name="trash" size="48" />
</span>
</Form>
<Form data={form_data} table={params.table} schema={ schema }>
<a href="/admin/table/{ params.table }" data-testid="button-back" use:link>
<Icon name="arrow-left-circle" size="48" />
</a>
<span data-testid="button-copy" on:click={ json_copy }>
<Icon name="copy" size="48" />
</span>
<span data-testid="button-update" on:click={ update_record }>
<Icon name="save" size="48" />
</span>
<span data-testid="button-delete" on:click={ () => delete_confirm = true }>
<Icon name="trash" size="48" />
</span>
</Form>
{:catch}
<callout class="error">
<p>{ notice }</p>
<p>{ notice }</p>
<p>Return to the table:
<a href="/admin/table/{ params.table }" data-testid="button-back" use:link>
<Icon name="arrow-left-circle" size="48" />
</a>
</p>
</callout>
{/await}
</Layout>
{#if notice}
<toast-list class="bottom right active">
<toast transition:fade|local>
{notice}
</toast>
</toast-list>
{/if}
{#if delete_confirm}
<Modal on:close={() => delete_confirm = false }>
<form>

15
lib/api.js

@ -34,7 +34,20 @@ export class API {
validate(rules, extra) {
const form = this.req.method === "GET" ? this.req.query : this.req.body;
let validation = Validator.make(form, rules);
form._valid = validation.passes();
// BUG: validator has a bug that considers an empty
// form valid even if there are required rules
if(Object.getOwnPropertyNames(form).length === 0) {
// add in a fake entry in the form
form["_empty"] = null;
// validate it
form._valid = validation.passes();
// then remove _empty to keep it clean
delete form["_empty"];
} else {
form._valid = validation.passes();
}
form._errors = validation.getErrors();
if(extra) extra(form);
return form;

1
lib/ormish.js

@ -73,7 +73,6 @@ export const validation = (name, rules, all=false) => {
}
}
console.log("RULES", rules);
return rules;
}

Loading…
Cancel
Save