Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Forms / Listing

An essential part of the Quokka Admin is, that it provides an easy way for adding UI for managing whatever entity you want. This work the easiest for entities that reside in PostgreSQL, because the provided derive macros already build the queries for them, but you can also simply pass on your own function to store the data in whatever other Database you like and have access to through your state.

Forms (AdminCreateForm & AdminUpdateForm)

Each form item needs it's struct, which also implement the serde::Deserialize, serde::Serialize and if sqlx for the Database is used, also the sqlx::FromRow trait.

From there on you simply derive the AdminCreateForm and AdminUpdateForm traits and specify what you need.

Examples

In this example the minimum set of attributes is used, so you always provide the entity_name and for the update form you have to provide a primary_key.

#[derive(Clone, Debug, quokka_admin::service::AdminCreateForm, serde::Deserialize, serde::Serialize, sqlx::FromRow)]
#[create_form(entity_name = "test")]
pub struct TestEntityCreateForm {
    name: String,
    age: i32,
}

#[derive(Clone, Debug, quokka_admin::service::AdminUpdateForm, serde::Deserialize, serde::Serialize, sqlx::FromRow)]
#[update_form(entity_name = "test")]
pub struct TestEntityUpdateForm {
    #[update_field(primary_key)]
    id: i32,
    name: String,
    age: i32,
}

Each field can also have a broader set of attributes, and also the Form itself can be supplied some more settings, here is a full example of which attributes you have and can be used to modify the form.

#[derive(serde::Deserialize, serde::Serialize, sqlx::Type)]
pub enum ExampleEnum {
    One,
    Two,
    Three,
}

#[derive(quokka_admin::service::AdminCreateForm, serde::Deserialize, serde::Serialize, sqlx::FromRow)]
#[create_form(
    entity_name = "test",
    create_query = some_persisting_function(state).await?, // This field allow you to provide a custom function to persist the new entity. You have access to the full state object and you can return a future which resolves into a quokka::Result<()>
)]
pub struct TestEntityCreateForm {
    #[create_field(required)] // Makes the field required, also supported in the update form
    name: String,
    #[create_field(label = "Your Age")] // The label for the input, also valid in the update form
    age: i32,
    #[create_field(default = 0)] // The default value for the input, also valid in the update form
    default_value: i32,
    #[create_field(name = "type")] // Overwrites the name of the field similar to how the serde rename attribute works, also available in the update form
    #[serde(rename = "type")]
    typ: String,
    #[create_field(field = SelectField::new("custom_input", "Custom Enum Input") // Sets the field for the frontend explicitely. See the fields section for details on fields
        .add_option("One", "One")
        .add_option("Two", "Two")
        .add_option("Three", "Three"))]
    custom_input: ExampleEnum,
}

// For the fields on the update form we only have the `primary_key` as additional attribute, the rest just works as described for the create form
#[derive(Clone, Debug, quokka_admin::service::AdminUpdateForm, serde::Deserialize, serde::Serialize, sqlx::FromRow)]
#[update_form(
    entity_name = "test",
    get_query = some_getter_function(state).await?, // this allows you to get the entity from some other place then the database. You have access to the full state and you are expected to return a quokka::Result<Self>
    update_query = some_updating_function(state).await?, // this allows you to update the entity at some other place then the database. You have access to the full state and you can return a future which resolves to a quokka::Result<()>
)]
pub struct TestEntityUpdateForm {
    #[update_field(primary_key)]
    id: i32,
}

Listing

The listing follows pretty much the same pattern as the Form. It just uses a different derive macro and has some different attributes. But the requirements (serde, sqlx etc.) are the same

Example

Here is an example explaining all supported attributes to the listing

#[derive(quokka_admin::service::AdminListing, serde::Deserialize, serde::Serialize, sqlx::FromRow)]
#[listing(
    entity_name = "test", // This one is always required
    get_entity = getter_for_all_entities(pagination: quokka::helper::database::PaginationQuery, search: Option<String>, state: &S), // This query should provide a list of entities. In the best case even respecting the pagination and search fields. As all the callbacks it can be a future and result in a quokka::Result<Vec<Self>>
    get_one_entity = getter_for_a_single_entity(state: &S, PrimaryKeys), // This should return a single entity. It the primary keys will be collected from the fields having a `primary_key`. The output should be a future with quokka::Result<Self>
    delete_entity = remove_a_single_entity(PrimaryKeys, state: &S), // This query should delete a single entity. It gets the primary keys the same way and is supposed to return a quokka::Result<()>
)]
pub struct TestEntityListing {
    #[listing_field(primary_key)] // You always need at least one primary_key field
    id: i32,
    #[listing_field(field = NumberField::new("age", "Age"))], // This expects a `quokka_admin::service::page_loader::ListingColumn`. The most interesting part is, that it provides a template which can be used to map the value to something else.
    age: i32,
    #[listing_field(searchable)] // You can mark fields as "searchable". This attribute will be utilized with the generated get_query which utilizes the PaginatedSearch helper of quokka.
    name: String,
}

Fields

The derive macro will try to detect the best Field type for forms based on the Rust type of a field. So numeric types will resolve to the NumberField, bool will be translated to a CheckboxField and the String defaults to a TextField. For every other usecase (like passwords) you have to specify the Field on your own.

The default, available fields are located at:

quokka_admin::service::form_builder::fields::{
  TextField,
  PasswordField,
  NumberField,
  CheckboxField,
  SelectField,
  HiddenField,
  DisplayField,
  HtmlInputField,
};

They all provide a new(name, label) constructor. Using this will overwrite whatever is set in the form attibutes.

SelectField

There is a special case for the SelectField which additionally provides a add_option(label, value) method so that you can specify your options. It also provides a style(style) field which takes a quokka_admin::service::form_builder::fields::SelectFieldStyle enum allowing you to choose either a combobox or radio buttons for the selection.

HtmlInputField

Then there is also the HtmlInputField which allows you to set a custom value for the type= attribute in the HTML <input> using the set_type(type) function and setting any arbitrary attribute using the set_attribute(name, value) function.

Custom Fields

Of cause you have the option to provide your own fields. For this you simply have to create a new field type and implement the quokka_admin::service::form_builder::FormField trait.

The following functions I consider self explanatory

fn template(&self) -> String;

fn name(&self) -> String;

fn label(&self) -> String;

fn default(&self) -> Option<String> {
    None
}

fn required(&self) -> bool {
    false
}

The fn additional_options(&self) -> HashMap<String, serde_json::Value> can be used to provide some custom data with your field, for example to be used with the template. The fn processor(&self, _state: &S) -> Option<Box<dyn FormFieldPreProcessor + Send + Sync>> allows you to provide a custom FormFieldPreProcessor where you can inject custom logic to query for values that can be used in a <select> input for example or a searchable text input.