svelte-native-forms

Svelte Native Forms

minimalist form validation with powerful results

svelte-native-forms

... minimalist form validation with powerful results

Validating forms has notoriously been a painful development experience. Implementing client side validation in a user friendly way is a tedious and arduous process • you want to validate fields only at the appropriate time (when the user has had the chance to enter the data) • you want to present validation errors in a pleasing way • you may need to apply custom validation (specific to your application domain) • etc.

Even with the introduction of HTML5's Form Validation, it is still overly complex, and doesn't address many common scenarios (mentioned above). Without the proper approach, form validation can be one of the most difficult tasks in web development.

Overview:

    svelte-native-forms (aka SNF) is a svelte utility that facilitates field validation in your native html forms.

    The term native refers to the utilization of the native HTML <form> tag and the corresponding form elements (<input>, <select>, <textarea>, etc.). In other words, SNF does NOT introduce components for these abstractions, rather you use the native html representations.

    Here are some key points to understand:

    • SNF is based on svelte actions. By applying a simple action to your form elements, the basics of the form validation control is defined right in your html markup ... see Native HTML Forms.

    • SNF improves user experience by validating fields at the appropriate time ... see Validation Timing.

    • SNF provides two simple reactive error display components, that "auto wires" themselves to the appropriate form/fields, dynamically displaying form-based errors as needed ... see Reactive Error Display.

    • SNF supports the native HTML5 Form Validation, by using the standard validation attributes in your form elements, such as type, required, minlength, etc. ... see Built-In Form Validation.

    • SNF allows you to easily apply custom validations, where your JavaScript code can perform app-specific validation (even involving cross field validations) ... see Custom Validation.

    • SNF is customizable • don't like the error display format? ... that is easily resolved • want to perform a custom field validation? ... easy peasy ... see Customization.

    SNF promotes a clean and simple approach to form validation, that yields powerful results.

    Important NOTE: SNF is intended to be used by applications that employ native html forms and form elements. Because of SNF's usage of svelte actions, this is a hard restriction! Svelte actions may only be applied to native DOM elements - not components. If you are a minimalist (using native html), you will appreciate this abstraction of form/field validation. If you are using a more inclusive UI library, it most likely already promotes Form/Element component abstractions (which will typically provide a technique to handle form validation).

At a Glance

Install

?? Installation

Basic Example

Here is a very basic example of SNF usage:

?? AI: would be nice to highlight the SNF specifics (NOT POSSIBLE IN quoted stuff) ?? consider an image?

<script>
 import {formChecker,  FormErr,
         fieldChecker, FieldErr} from 'svelte-native-forms';

 const submit     = (event, fieldValues) => alert(`Successful submit (all fields are valid)!`);
 const isIdUnique = ({id}) => id==='dup' ? 'ID must be unique' : '';
</script>

<form use:formChecker={{submit}}>
  <label>
    Name:
    <input id="name" name="name" type="text" required minlength="3"
           use:fieldChecker>
    <FieldErr/>
  </label>

  <label>
    ID:
    <input id="id" name="id" type="text" required minlength="2" maxlength="10"
           use:fieldChecker={{validate: isIdUnique}}>
    <FieldErr/>
  </label>

  <center>
    <FormErr/>
  </center>
  <center>
    <input type="submit" value="Make It SO!">
  </center>
</form>

At it's core, this example is using HTML's standard Built-In Form Validation. However with the simple injection SNF's Checker actions, and SNF's error display components, a sophisticated control structure has been applied that promotes a very usable form. We have even seamlessly introduced a custom field validation!

?? IMG: show result

You can interact with this example in the following ?REPL.

Here are some points of interest:

  • the use:formChecker action is applied to the <form>

    • a submit function is registered to this action. This is used in lue of the standard on:submit event. This function is invoked on a standard submit request, but only when all fields pass validation. If there are field errors, the appropriate messages are displayed, and no submit occurs.
  • the <input> form elements are employing some standard Built-In Form Validation constraints (type, required, minlength, etc.).

  • the use:fieldChecker actions are applied to each <input> form element. This completes the knowledge transfer of your form structure to SNF. (AI: can this be implied when all defaults are used?)

    • a custom field validation has been introduced (for the id field), by using the validate action parameter. This invokes a function that can apply application-specific validations. The function merely returns an error string (if invalid), or an empty string (when valid).

    • the <FieldErr/> components will dynamically bind to the input field of interest, and conditionally display appropriate errors for that field.

  • the <FormErr/> component dynamically binds to the form, and and conditionally displays an appropriate error when a submit is requested on an invalid form.

  • a standard submit control is applied to the form. As mentioned above, the SNF submit function is only invoked when all fields pass validation.

Concepts

svelte-native-forms (aka SNF) is a svelte utility that facilitates field validation in your native html forms.

The following sections discuss the basic concepts of SNF:

Native HTML Forms

    In SNF, your interactive forms continue to use the native HTML <form> tag along with the corresponding form elements (<input>, <select>, <textarea>, etc.). In other words, SNF does NOT introduce components for these abstractions, rather you use the native html representations.

    This is accomplished by using svelte actions. By applying a simple action to your form elements, the basics of the form validation control is defined right in your html markup.

    This represents a different approach from other form validation libraries. Some may require you to define a separate JavaScript control structure that either drives your html markup, or duplicates the html hierarchy in some way. Others may introduce their own component layer on top of the native form elements.

    Because SNF is action-based, it can utilize your DOM hierarchy to implicitly define the validation control structure, making it a simple and understandable single source of truth. In other words, your form validation control is defined right in your html markup!

Validation Timing

    Validation timing is an important characteristic in achieving a better user experience. You don't want to overwhelm your users by validating every field from the start, yet once a field has been seen, validation is more appropriate.

    SNF employs a validation heuristic, where fields are validated at the appropriate time:

    • only when they have been seen by the user (i.e. touched),
    • or when a submit has been attempted.

    The "touched" determination is made through focus semantics. A field is considered to have been touched when it has gone in and out of focus (i.e. blur).

    This heuristic is a common technique that is tedious to accomplish, when attempted in application code.

Reactive Error Display

    SNF provides two reactive error display components that dynamically display appropriate errors as needed. The beauty of these components are that they auto-wire themselves to the appropriate error. As a result, they are very simple to use.

    In the following example, the mere inclusion of <FieldErr/> will dynamically display any name-field errors at the appropriate time, simply because it is contained in the <label> that holds the name <input> (this is not the only way to bind errors, but a very common one):

    <label>
      Name:
      <input id="name" name="name" type="text" required minlength="3"
             use:fieldChecker>
      <FieldErr/>
    </label>
    

    There are two reactive error display components:

Built-In Form Validation

    Web standards provide a native way to achieve client-side validation (see HTML5 Form Validation). This is accomplished by simply applying validation attributes to your form elements, such as type, required, minlength, maxlength, min, max, pattern, etc.

    Roughly speaking, this standard is broken up into two parts:

    • validation (i.e. built-in validation):

      SNF supports the continued use of built-in validation, in addition to the ability to easily inject Custom Validation. In other words, you may continue to use the HTML validation attributes (mentioned above). All built-in validation messages are presented to the user before any custom validations.

    • presentation (of validation errors):

      The error presentation provided by standard HTML is somewhat primitive and unrefined. It is for this reason that SNF takes over the the presentation of validation errors. This (in conjunction with SNF's powerful validation heuristic), provides a much improved user experience.

    SNF accomplishes this by applying the novalidate attribute to your <form> element. While this disables the presentation aspects of the standard, it leaves the constraint validation API intact (along with CSS pseudo-classes like :valid etc.). This allows SNF to continue to interpret and support the built-in form validations!

Custom Validation

    You can easily apply custom validations, where your JavaScript code provides app-specific validation. This even includes more complex things, such as cross field validation!

    This is accomplished through the validate parameter of the fieldChecker action.

    The following simple example confirms that the id field is unique, based on an app-specific isUnique() function.

    <input id="id" name="id" type="text" required
           use:fieldChecker={{validate: ({id}) => isUnique(id) ? '' : 'ID must be unique''}}>
    

    The validate hook can employ any logic (as complex or simple as needed), and can reason about multiple fields (not just one). Ultimately it returns a string - which is either an error (for non-empty strings) or valid (for an empty string).

Actions

The primary instrument that SNF uses are svelte actions! There are two:

  1. formChecker: to be applied to your <form> element
  2. fieldChecker: to be applied to your interactive form elements (<input>, <select>, <textarea>, etc.)

formChecker

    The formChecker svelte action is applied to your html <form> element. It registers the form to SNF control, allowing it orchestrate form validation.

    Action Usage:

    <script>
     import {formChecker} from 'svelte-native-forms';
    
     const submit = (event, fieldValues) => alert(`Successful submit (all fields are valid)!`);
    </script>
    
    <form use:formChecker={{submit}}>
      ... snip snip
    </form>
    

    Action Parameters:

    The formChecker action supports the following parameters:

    • submit: a required submit function that executes a client-specific process. This is invoked when a submit occurs, only when all fields pass validation.

      submit() API:

      + submit(event, fieldValues): void
        NOTE: All field values (monitored by SNF) are passed as named parameters.
      
    • errStyles: an optional object containing the in-line styles to apply to input elements that are in error.

      • use an object with in-line styling pairs: camelCaseKey/value
      • or null to disable

      DEFAULT:

      {
        border:          '2px solid #900', ... solid red border
        backgroundColor: '#FDD',           ... pink background
      }
      

    formController:

    The formChecker action promotes a formController API through which your application can programmatically make various form-specific requests (like resetting the form). Please refer to the formController section for a discussion of this API (and how to access it).

fieldChecker

    The fieldChecker svelte action is applied to your interactive form elements (<input>, <select>, <textarea>, etc.). It registers the form element to SNF control, allowing it to participate in the form's validation. In addition it injects the form value into the set of formFields passed to your submit function.

    If your field has no validation constraints, this action is completely optional. The only down-side to omitting the action is that the field value will NOT be included in the set of formFields passed to your submit function.

    Form elements containing this action must have a name or id attribute. While this is a common form requirement, in the case of fieldChecker, it derives it's fieldName from these attributes (the name attribute takes precedence over id).

    AI: ?? per web standards I think a form submission REQUIRES a name attribute

    • however SNF may need id to sync up various elements
    • this may need to be tweaked (however we need to play with ALL the form elements)

    Action Usage:

    <script>
     import {formChecker} from 'svelte-native-forms';
    </script>
    
    ... in the context of a form:
    
        ... NO action parameters (all defaulted)
        <input id="name" name="name" type="text" required minlength="3"
               use:fieldChecker>
    
        ... WITH action parameters
        <input id="name" name="name" type="text" required minlength="3"
               use:fieldChecker={{validate, initialValue: 'WowZee'}}>
      
    ... snip snip
    

    Action Parameters:

    The fieldChecker action supports the following parameters (all optional):

    • validate: an optional function that applies custom client-specific validation to this field.

      DEFAULT: NO custom validation is applied ... only Built-In Form Validation (e.g. type, required, minlength, etc.)

      validate() API:

      + validate(fieldValues): errMsgStr (use '' for valid)
        NOTE: All field values are passed as named parameters,
              supporting complex inner-dependent field validation
                EX: validate({address, zip}) ...
              Typically you only access the single field being validated
                EX: validate({address}) ...
      

      validate() example: ?? more realistic

      function validate({foo}) {
        return foo==='dup' ? 'foo must be unique' : '';
      }
      
    • initialValue: optionally, the initial value to apply to this field, both on DOM creation and reset() functionality.

      DEFAULT: NO initial value is programmatically applied. In other words the initial value is strictly defined by your html.

    • boundValue: optionally, the application variable bound to this fieldNode. This is required when svelte's bind:value is in affect (due to a web limitation, see: Svelte Bound Variables).

    • changeBoundValue: optionally, a client function that changes it's bound value. This is required when SNF's initialValue and boundValue are in affect (also due to the same web limitation, see: more when initialValue in use ...).

      changeBoundValue() API:

      + changeBoundValue(initialValue): void
        ... the implementation should update the client boundValue to the supplied initialValue
      

Components

SNF promotes two reactive components that display form-based error messages in your application:

The beauty of these components are that they auto-wire themselves to the appropriate error. As a result, they are very simple to use.

<FormErr>

    The <FormErr> component dynamically displays a generalized message when something is wrong with one or more form fields. The default message is: Please correct the highlighted field errors but can easily be overwritten through the errMsg property.

    <FormErr> must be a descendant of a <form> that is controlled by a formChecker action. This is how it implicitly auto-wires itself to the form's error status.

    Usage:

    <form use:formChecker={{submit}}>
      ... snip snip
      <FormErr/>
    </form>
    

    Visual:

    ?? screen shot

    API:

    <FormErr [errMsg={}]    ... the generalized form-based error message
                                DEFAULT: "Please correct the highlighted field errors"
             [DispErr={}]/> ... the display component that renders the error
                                ACCEPTS: errMsg property ... an empty string ('') represents NO error
                                DEFAULT: the standard internal error display component
    

    Customization:

    Please refer to the Error Look and Feel section to see how you can customize the display of your error messages (using the DispErr property).

<FieldErr>

    The <FieldErr> component dynamically displays a field-specific message when the field it is monitoring is invalid.

    This component auto-wires itself to the monitored field of interest either through the forName property, or implicitly through it's placement within a <label> container (see Usage below).

    Usage (via forName):

      <FieldErr forName="zip"/>
      

      In this case, the monitored field is identified through the SNF defined fieldName (which can be the DOM name or id attributes, name taking precedence).

      Example: <input id="wow" name="zip" use:fieldChecker ...>

    Usage (via implicit label containment):

      <label>
        Zip:
        <input name="zip" type="text" required use:fieldChecker>
        <FieldErr/>
      </label>
      

      In this case, the monitored field is implicitly identified through it's placement in a <label> container. Both the field and <FieldErr> are nested in a <label> element. This is similar to how an <input> element can be implicitly associated to it's <label>.

    Visual:

    ?? screen shot

    API:

    <FieldErr [forName=""]   ... identifies the field to monitor errors on, as defined by the SNF
                                 fieldName (which can be the DOM name or id attributes, name taking
                                 precedence)
                                 DEFAULT: implicit <label> containment
              [DispErr={}]/> ... the display component that renders the error
                                 ACCEPTS: errMsg property ... an empty string ('') represents NO error
                                 DEFAULT: the standard internal error display component
    

    Customization:

    Please refer to the Error Look and Feel section to see how you can customize the display of your error messages (using the DispErr property).

Controllers

SNF promotes public APIs through which application code can programmatically make various requests.

  • These API's are created and promoted by SNF actions, and therefore are scoped to the DOM controlled by the action.

  • Access to these API's are provided through event handlers. The reason for this is there is no direct way to return content from svelte actions. Please refer to content (below) for details on each controller.

  • While this probably goes without saying, these APIs should not be used outside the scope of the action that created them. As an "internal note", there is no process by which an event notification can mark them as stale, because the action-attached DOM element has already been removed prior to any action life-cycle notification.

    If the application violates this tenet, the API will throw an Error such as:

    ***ERROR*** formController.reset(): stale reference, the formCheckerAction DOM has been removed!
    
  • These APIs promote true functions (not methods). In other words their execution does not require a controller dereference. As an example, the following snippet retains a "function only" (without invocation) ... so on event handler invocation, it is truly a function:

    <button on:click|preventDefault={formController.reset}>Reset</button>
    

formController

    The formController is specific to a <form> (i.e. the formChecker action).

    formController API:

    Currently, this API promotes only one function (but provides a place for future expansion).

    {
      + reset(): void ... reset the form back to it's original state.
    }
    

    Here are the specifics of each function:

    • + formController.reset(): void

      This resets the form back to it's original state, including:

      • initial field values (as supplied through the fieldChecker initialValue action parameter)
      • clear all error state (both form and fields)
        • form error message
        • field error messages
      • reset Validation Timing heuristics (both form and fields)
        • form as "submit NOT attempted"
        • fields as "not been touched"

      A reset is useful when a form is re-used by retaining it's DOM representation (say when used in a form-based dialog).

    Accessing formController:

    Because svelte actions have no direct way to return content to the client, a form-controller event is emitted that contains the formController (found in event.detail.formController). The application can simply monitor this event (on the <form> element), and retain the formController for it's use. Here is an example:

    <script>
     let formController;
     ... snip snip
    </script>
    
    <form use:formChecker={{submit}}
          on:form-controller={ (e) => formController = e.detail.formController }>
      ... snip snip
    </form>
    

Advanced Concepts

The following sections discuss more advanced concepts of SNF:

Svelte Bound Variables

    A very powerful feature of svelte is it's ability to provide a two-way binding between form elements and JS variables. By simply using the bind: directive these two aspects are kept "in sync". For example:

    <script>
        let name = 'World';
    </script>
    
    <input id="name" name="name"
           bind:value={name}>
    
    <h1>Hello {name}!</h1>
    

    For SNF usage, when two-way bindings are in affect, the boundValue parameter must be specified in the fieldChecker action. For example:

    <input id="name" name="name"
           bind:value={name}
           use:fieldChecker={{boundValue: name}}>
    

    Why is this required? It seems a bit cumbersome!

      As it turns out, this has nothing to do with svelte or the SNF library. Rather it is a limitation of the web itself, where updates to fieldNode.value do not emit any web events (such as the on:input event).

      • Svelte manages it's two-way binding by directly managing the fieldNode.value.

      • By default, SNF monitors form element changes through the on:input event.

      Because of this web limitation, SNF requires visibility to boundValues, in order to have access to a "single source of truth". This is unfortunate, but there is nothing we can do about it :-(

    more when initialValue in use ...

      This limitation gets worse, when you specify an initialValue for bound elements. In this scenario SNF must update the "single source of truth", which (in this case) is the boundValue. This cannot be accomplished by SNF directly, because the svelte compiler must have visibility of this change. As a result, when BOTH initialValue and boundValue are in affect, the client must also specify a changeBoundValue function. For example:

      <input id="name" name="name"
             bind:value={name}
             use:fieldChecker={{
               initialValue: 'WowZee',
               boundValue: name,
               changeBoundValue: (initialValue) => name = initialValue
             }}>
      

      changeBoundValue() API:

      + changeBoundValue(initialValue): void
        ... the implementation should update the client boundValue to the supplied initialValue
      

Customization

SNF is customizable!

  • don't like the error display format? ... that is easily resolved
  • want to perform a custom field validation? ... easy peasy

The following sections discuss various customization features:

Error Look and Feel

    SNF provides two reactive error display components that dynamically display appropriate errors as needed (<FormErr> and <FieldErr>).

    The "Look and Feel" of these components can easily be overridden. They merely monitor the appropriate reflective error state, and defer their display to an internal <DispErr> component (passing the errMsg to display ... where an empty string ('') represents no error).

    This display component is extremely simple! It styles and displays the message using animation. Here is the default implementation:

    DispErr.svelte ... the DEFAULT internal display component

    <script>
     import {fade} from 'svelte/transition';
     export let errMsg;
    </script>
    
    {#if errMsg}
      <span class="error" transition:fade>
        {errMsg}
      </span>
    {/if}
    
    <style>
     .error {
       font-size:    80%;
       font-weight:  bold;
       font-style:   italic;
       color:        #900;
     }
    </style>
    

    Because of it's simplicity, you can easily implement your own display component and pass it to <FormErr>/<FieldErr> through the DispErr property. Simply use the DispErr.svelte (above) as a pattern. You can tailor the style and animation to your application needs!

    Here is an example that overrides the default:

    <FormErr DispErr={MyErrComp}/>
    

    AI: ?? Provide a full-blown example that overrides the internal DispErr component. Here are some ideas:

    • NOTE: this covers BOTH FieldErr and FormErr
    • change the animation
    • change the error display into a red box via the following form style:
      <style>
       .formError {
         padding:          0.3em;
         font-size:        80%;
         color:            white;
         background-color: #900;
         border-radius:    5px;
         box-sizing:       border-box;
       }
      </style>
      
    • here is the corresponding field style (a red box with square top so it looks like part of the input):
      <style>
       .fieldError {
         width:            100%;
         padding:          0.3em;
         font-size:        80%;
         color:            white;
         background-color: #900;
         border-radius:    0 0 5px 5px;
         box-sizing:       border-box;
       }
      </style>
      

Competition

Quick review of current validation utils on "made with svelte" ...

BOTTOM LINE

  • think twice about joining the mix
  • there is a lot to think about here
  • may be too time consuming
  • there are a couple of form/validation libs that look robust

sveltejs-forms

Sveltik

    Form Library inspired by Formik (KJB: MAY BE minimalist COMPETITION <<< looks robust)
    * seems lightweight
    * communicates with let:xxx directives
    * can use native form/formElm
    * has a <Sveltik> component that can wrap the entire native form (again communicating via let:xxx)
    * also has option of <Form>/<Field> components
      KOOL: they can actually wrap native elms (using slots) <<< this is what I should do in my ErrorMsg
    * have <ErrorMessage> component ... must tell it what it's used for (just like mine)
      <ErrorMessage name="email" as="div"/>
    

Svelte Forms Lib

Svelte Svelte Formly

Top categories

Loading Svelte Themes