Form

Form

Forms are not created like most components. Instead of importing a Form component then importing the various inputs to use, this is all done through props.

To use forms, you'll pass in your objects indicating the type of input you want. One of your inputs (usually the last one) should be a button, which will have an onsubmit handler that will deal with your form submission.

Bubbles provides two utility functions called getFormData, which will give you an object of your data and validateInputs which will validate the inputs based on the validation params that you supply to each of the inputs. Learn more about getFormData and validateInputs

All of the inputs that you provide to the form should have an id property. This will end up being the property name/key on the JSON object. If your id includes any periods in it like profile.name, the "name" will be nested under "profile".




Props


id string
You can set an ID for the form, otherwise an unique ID will automatically be applied.


inputs array<Object>
An array of input values that you want to add. See the individual articles for inputs like Input, Select, and Button to understand what kind of properties to add to each.




Below are specific properties you can add to an input when used with a form:


input[].width number
You can add inputs next to each other instead of stacking them simply by passing the width as a percentage. If you wanted two inputs side by side, you would passing 50 as the width for both of them. If you wanted three inputs all in one row, you would pass in 33 as the width for all of the inputs.


input[].hide function
A function that returns true if the input should be hidden.


Demo
Your Name

You'll be able to change this name later

Confirmation

Type 'confirmation' to proceed

Age
length
width
height

Email Updates

Let us know if you want to receive email updates

Your Email

You'll be able to change this name later

Date Of Birth
Your Address

Type your address

Select Your Role
Select an option

Details, Details, Details, Details, Details, Details, Details Details, Details, Details, Details, Details, Details, Details

Select Your Number
Select an option

Select your bread

Let us know what type of bread you want

Select your permission

Select permissions

Select Your Toppings

Select as many toppings as you'd like

Terms and conditions

By submitting this form you agree to our terms. View Terms.

Select a file
<script>
  import { Form } from "bubbles-ui";
  import { validateInputs, getFormData, showToast, showLoading, hideLoading } from "bubbles-ui";

  const formInputs = [
    {
      type: "text",
      id: "name",
      label: "Your Name",
      value: null,
      desc: "You'll be able to change this name later",
      error: "A name is required",
      validation: "string|required|min:3",
      validate_on_blur: true,
      vob: true,
    },
    {
      type: "number", //Will ensure that getFormData will return this as a number
      id: "age",
      label: "Age",
      value: null,
      desc: null,
      error: "You must be at least 13 years old",
      validation: "numeric|required|min:13",
      bounds: [0, 120], //Set the min and max values for this input. If you only want the min or max, you can do something like: [0, null]
    },
    {
      type: "switch",
      id: "preferences.email",
      label: "Email Updates",
      value: true,
      desc: "Let us know if you want to receive email updates",
      disabled: false,
      error: "An error occurred",
      validation: "accepted", //best UX is to not make switches mandatory, use a checkbox instead if you need
    },
    {
      type: "email",
      id: "email_required_id",
      label: "Your Email",
      value: null,
      desc: "You'll be able to change this name later",
      error: "Email is required",
      validation: "email|required|min:3",
      vob: true,
      hide: () => {
        const input = formInputs.find((input) => input.id === "preferences.email");
        if (input.value === false) {
          return true;
        }
      },
    },
    {
      type: "date",
      id: "dob",
      label: "Date Of Birth",
      value: null,
      desc: null,
      error: "Add your Date of Birth",
      validation: "required|date", //date validator is built in
    },
    {
      type: "textarea", //the textarea / text field input
      id: "description",
      label: "Your Address",
      value: "",
      desc: "Type your address",
      rows: 8, //specify how many rows you want. Defaults to 5
      validation: "required",
    },
    {
      type: "select",
      id: "role",
      label: "Select Your Role",
      value: null,
      desc: null,
      error: "Your role is required",
      validation: "required", //since you're adding the options, you can just set it to required
      search: false,
      options: [
        {
          label: "Owner",
          caption: "This is the highest position",
          value: "owner",
        },
        { divider: true },
        {
          label: "Collaborator",
          value: "collaborator",
        },
        {
          label: "User",
          value: "user",
        },
      ],
    },
    {
      type: "select-number", //This is a special type of select in case you need to select only from number elements. Use this if all values in a select need to be numbers (Floats or integers)
      id: "number",
      label: "Select Your Number",
      value: null,
      desc: null,
      error: "This is required",
      validation: "required",
      search: false,
      options: [
        {
          label: "One",
          value: 0,
        },
        {
          label: "Two",
          value: 1,
        },
        {
          label: "Three",
          value: 3,
        },
      ],
    },

    {
      type: "radio",
      id: "radio",
      label: "Select your bread",
      value: "plain",
      desc: "Let us know what type of bread you want",
      error: "Select a type of bread",
      validation: "required",
      options: [
        {
          label: "Plain",
          value: "plain",
        },
        {
          label: "Whole Wheat",
          value: "whole_wheat",
        },
        {
          label: "Spinach",
          value: "spinach",
        },
      ],
    },
    {
      type: "checkbox-group",
      id: "toppings",
      label: "Select Your Toppings",
      value: [], //Will need an array because you can select multiple
      desc: "Select as many toppings as you'd like",
      error: "Something went wrong",
      validation: null,
      options: [
        {
          label: "Beans",
          value: "beans",
        },
        {
          label: "Lettuce",
          value: "lettuce",
        },
        {
          label: "Tomato Sauce",
          value: "tomato_sauce",
        },
      ],
    },
    {
      type: "checkbox",
      id: "terms",
      label: "Terms and conditions",
      value: false,
      desc: "By submitting this form you agree to our terms. <a href='https://google.com' target='_blank'>View Terms</a>.",
      error: "You must accept the terms and conditions",
      validation: "required|accepted",
    },
    {
      type: "submit", //The form must have a submit button
      label: "Submit Form", //The label for the submit button,
      onsubmit: async (event) => {
        //the onsubmit and onclick function on buttons, will give you the event param
        //if you want to toggle the loading state on your button while doing a networking request
        //just use showLoading() and pass in the id
        //An id will automatically be assigned to the button for you

        const button_id = event.currentTarget.id;
        showLoading(button_id);

        try {
          await validateInputs(formInputs);
          const data = await getFormData(formInputs);
          console.log(data);
        } catch (error) {
          showToast(error.message);
        } finally {
          setTimeout(() => {
            hideLoading(button_id);
          }, 2000);
        }
      },
    },
  ];
</script>

<Form inputs={formInputs} />

Stripe

There are many ways to collect payments. At Bubbles our preferred way to process payments is on the backend server instead of the front end. This gives us a lot more flexibility with things like the application_fee_amount property, which is a more common use case for dashboards and SaaS products.

Therefore, the only thing that our stripe-card input does, is convert the user's credit card into the stripe token ID that you will send to your backend and finalize the transaction.

The process for this is pretty simple. Like any Bubbles Form component, you pass in the array of form inputs to the form component as a prop. In the onsumbit function of your button, you'll pass in the reference to your array of inputs to the getFormData utility you imported. Bubbles will automatically convert the card to the token, and the token will be part of the object returned. They key, as always, being the id you passed to that input.

Any StripeJS dependencies will only load client side after everything else mounts, so there will be no performance penalties for using this component.

Learn more about the stripe-card input type here.

Demo
Select Payment Method
Visa ···· 1234 (default)
<script>
  import { Form } from "bubbles-ui";
  import { validateInputs, getFormData, showToast, showLoading, hideLoading } from "bubbles-ui";

  const stripeFormInputs = [
    {
      type: "select",
      id: "payment_method",
      label: "Select Payment Method",
      error: "Select a payment method",
      value: "token_1234",
      options: [
        {
          label: "Visa ···· 1234 (default)",
          value: "token_1234",
          caption: "Expires: 11/26",
        },
        {
          label: "Mastercard ···· 5678",
          value: "token_5678",
          caption: "Expires: 11/26",
        },
        {
          label: "Amex ···· 9012",
          value: "token_9012",
          caption: "Expires: 11/26",
        },
        { divider: true },
        {
          label: "Add New Card",
          value: "",
        },
      ],
    },
    {
      type: "text",
      id: "cardholder_name",
      label: "Cardholder Name",
      error: "Cardholder name is required",
      validation: "string|required",
      hidden_unless: [{ id: "payment_method", value: "" }],
    },
    {
      type: "stripe-card",
      id: "stripe_card_token",
      hidden_unless: [{ id: "payment_method", value: "" }],
      stripe_key_name: "VITE_STRIPE_PUBLIC_KEY",
      stripe_token_values: {
        name: "cardholder_name",
      },
    },
    {
      type: "checkbox",
      id: "save_card",
      value: null,
      label: "Save Card Information",
      desc: "If you check this box, we'll save this card to yur profile and you'll see it as a dropdown in the future.",
      hidden_unless: [{ id: "payment_method", value: "" }],
    },
    {
      type: "button",
      label: "Purchase for $9.99",
      onsubmit: async (event) => {
        const button_id = event.currentTarget.id;

        showLoading(button_id);

        try {
          await validateInputs(stripeFormInputs);
          const data = await getFormData(stripeFormInputs);
          console.log(data);
        } catch (error) {
          showToast(error.message);
        } finally {
          hideLoading(button_id);
        }
      },
    },
  ];
</script>

<Form inputs={stripeFormInputs} />