Compass forms

The Compass Forms package is a great way to quickly build your forms.
It takes away a lot of the boilerplate code and provides a simple way to build your forms.
Also validation messages are automatically added to the form fields.

Creating forms

Forms will always start and end with a form tag, these can easily be added by using the Form::open() and Form::close() calls derived from the Laravel Collective Form Facade (which Compass Forms is based on) in your blade file.
All form fields explained in the following sections are placed between these two calls.

{{ Form::open(['route' => '[your-route]', 'method' => 'POST']) }}
    // Form fields
{{ Form::close() }}

Adding a form this way will automatically add an csrf token to the form, which is required for Laravel to accept the form.
If you, for any reason, do not use the Form::open() and Form::close() calls, you will have to add the token yourself.

<form method="POST" action="[your-route]">
    @csrf
    // Form fields

Form fields are added using Laravel Components to which Validation messages are automatically added.

Structuring forms

When forms become bigger and bigger we should provide some extra structure to maintain an overview. The following bootstrap grid structure is usually used:

<div class="row">
    {{-- Main form fields--}}
    <div class="col-12 col-lg-6">
        <x-compass-forms.text name="name" label="Name"/>
        <x-compass-forms.ckeditor.simple name="description" label="Description"/>
    </div>

    {{-- Additional form fields--}}
    <div class="col-12 col-lg-6">
        <x-compass-forms.card>
            <x-slot:header-slot>
                <div class="d-flex align-items-center justify-content-between">
                    <span class="mb-0">Organisation information</span>
                    <a href="{{ route('admin.organisations.show', 1) }}">
                        <i class="fa fa-eye"></i>
                        <span class="d-none d-md-inline">View details</span>
                    </a>
                </div>
            </x-slot:header-slot>

            <x-compass-forms.text name="iban" label="IBAN"/>
        </x-compass-forms.card>

        <x-compass-forms.card :title="__('compass.forms::Additional information')">
            <x-compass-forms.select name="should_send_mail" label="Should send mail?" :options="[0 => 'No', 1 => 'Yes']"/>
        </x-compass-forms.card>
    </div>
</div>

Card

In the structure example above the card component is used to create categorized input fields. The header-slot takes precedence over the title attribute.

    <x-compass-forms.card :title="I will be ignored">
        <x-slot:header-slot>
            <div class="d-flex align-items-center justify-content-between">
                <span class="mb-0">Organisation information</span>
                    <a href="{{ route('admin.organisations.show', 1) }}">
                    <i class="fa fa-eye"></i>
                    <span class="d-none d-md-inline">View details</span>
                </a>
            </div>
        </x-slot:header-slot>
    </x-compass-forms.card>

Input types

Text

A text field is a single line input field and can be added in its simplest form by using the following code:

<x-compass-forms.text name="text-field"></x-compass-forms.text>

Attributes

Additional attributes can be used to customize the field to your specific needs.

<x-compass-forms.text
    name="form-text"
    id="form-text" <!-- Defaults to name. -->
    value="Default value"
    label="Text field test"
    placeholder="placeholder"
    :classes="['class1', 'class2'] <!-- Or as string: classes="class1 class2" -->
    :disabled="false"
    :readonly="false"
    :extra="['extra-attribute' => 1]">
    :tabindex="1"
</x-compass-forms.text>

Slots

Add content before the input field by using the prepend slot.

<x-compass-forms.text name="text-field">
    <x-slot name="prepend">
        <div class="input-group-text">€</div>
    </x-slot>
</x-compass-forms.text>

Add content after the input field by using the append slot.

<x-compass-forms.text name="text-field">
    <x-slot name="append">
        <button class="btn btn-primary" type="button">Button</button>
    </x-slot>
</x-compass-forms.text>

Add an extra explanation directly below the input field by using the explanation slot.

<x-compass-forms.text name="text-field">
    <x-slot name="explanation">
        Always use a comma as decimal separator.
    </x-slot>
</x-compass-forms.text>

Password

A password field is a single line input field, that does not show it's content, and can be added in its simplest form by using the following code:

<x-compass-forms.password name="password-field"></x-compass-forms.password>

The attributes and slots of the password field are identical to the text field, except for the value attribute, which is not available with any password field.

Number

A number field is a single line input field, only for numbers, and can be added in its simplest form by using the following code:

<x-compass-forms.number name="number-field"></x-compass-forms.number>

The attributes and slots of the number field are identical to the text field, it only adds two additional attributes to define the minimum and maximum value a user can select.
Browser support for the number input type can be found here.

E-mail

An e-mail field is a single line input field, only for e-mail addresses, and can be added in its simplest form by using the following code:

<x-compass-forms.email name="time-field"></x-compass-forms.email>

The attributes and slots of the e-mail field are identical to the text field.
Ensure yourself all targeted browsers support the e-mail input type.
Browser support for the email input type can be found here.

Date

A date field is a single line input field, only for dates, and can be added in its simplest form by using the following code:

<x-compass-forms.date name="date-field"></x-compass-forms.date>

The attributes and slots of the date field are identical to the text field.
Ensure yourself all targeted browsers support the date input type. Otherwise, use a text field with a datepicker.
Browser support for the date input type can be found here.

Time

A time field is a single line input field, only for times, and can be added in its simplest form by using the following code:

<x-compass-forms.time name="time-field"></x-compass-forms.time>

The attributes and slots of the time field are identical to the text field.
Ensure yourself all targeted browsers support the date input type. Otherwise, use a text field with a datepicker.
Browser support for the date input type can be found here.

Color

A color field is a single line input field, only for colors, and can be added in its simplest form by using the following code:

<x-compass-forms.color name="time-field"></x-compass-forms.color>

The attributes and slots of the color field are identical to the text field.
Ensure yourself all targeted browsers support the date input type.
Browser support for the color input type can be found here.

File

A file field is a single line input field for files, and can be added in its simplest form by using the following code:

<x-compass-forms.file name="time-field"></x-compass-forms.file>

The attributes and slots of the color field are identical to the text field except for the value attribute, which is not available with any file field.
The file field als has an extra preview attribute with which you can provide a path to a preview images. The path given should be the path after /storage in the URL.

<x-compass-forms.file
    name="time-field"
    preview="/path/to/preview.jpg"> <!-- After /storage -->
</x-compass-forms.file>

Before adding a file field, make sure you have added the files => true setting to the form opening tag, otherwise the file field will not be submitted.

Hidden

A hidden field is like a text field, but not visible to the user through the interface. The hidden field can be shown in the source code of the page, so do not use it for sensitive data.

<x-compass-forms.hidden
    name="form-hidden"
    id="form-hidden"
    value="Default value"
    :classes="['class1', 'class2']"
    :extra="['extra-attribute' => 1]">
</x-compass-forms.hidden>

The main difference between the hidden field and the text field is that the hidden field does not have a label, and the value is not shown to the user.

Textarea

A textarea field is a multiline input field and can be added in its simplest form by using the following code:

<x-compass-forms.textarea name="textarea-field"></x-compass-forms.textarea>

The attributes and slots of the textarea field are identical to the text field.

CKEditor5

A configurable WYSIWYG texteditor.

There are three pre-configured styles available. Available options are simple, advanced and markdown

Configurations

Simple:

Include

<x-compass-forms.ckeditor.simple name="text"/>

in your blade will load the default Simple configuration, plugins in this configuration include: Autoformat, Bold, Italic, Link & List

Advanced:

Include

<x-compass-forms.ckeditor.advanced name="text"/>

in your blade will load the default Simple configuration, plugins in this configuration include: Markdown, Autoformat, Bold, Italic, BlockQuote, Link & List

Markdown:

Include

<x-compass-forms.ckeditor.markdown name="text"/>

in your blade will load the default Simple configuration, plugins in this configuration include: Markdown, Autoformat, Bold, Italic, BlockQuote, Link, List, Paragraph

For custom configurations please refer to this guide

Checkbox

A checkbox field is can be added in its simplest form by using the following code:

<x-compass-forms.checkbox
    name="form-checkbox"
    id="form-checkbox" <!-- Unique for each checkbox -->
    value="1"
    label="Checkbox field test"
    :checked="false">
</x-compass-forms.checkbox>

Radiobutton

A radiobutton field is can be added in its simplest form by using the following code:

<x-compass-forms.radiobutton
    name="form-radiobutton"
    id="form-radiobutton" <!-- Unique for each checkbox -->
    value="1"
    label="Radiobutton field test"
    :checked="false">
</x-compass-forms.radiobutton>

Select

A select field can be added in its simplest form by using the following code:

<x-compass-forms.select
    name="form-select"
    :options="[1 => 'Option 1', 2 => 'Option 2']">
</x-compass-forms.select>

It supports several different attributes and the same slots as the text field.

<x-compass-forms.select
    name="form-select"
    id="form-select"
    value="1"
    label="Select field test"
    :classes="['class1', 'class2']"
    :disabled="false"
    :readonly="false"
    :options="[1 => 'Option 1', 2 => 'Option 2']" <!-- Array of options -->
    :multiple="false" <!-- Multiple select -->
    :chooseOption="true" <!-- Add a first option with the text 'Choose option' -->
    :extra="['extra-attribute' => 1]"
    tabindex="1">
    <x-slot name="prepend">
        <span class="input-group-text" id="basic-addon1">Select</span>
    </x-slot>
    <x-slot name="append">
        <button class="btn btn-primary" type="button">Button</button>
    </x-slot>
    <x-slot name="explanation">
        A short explanation of the field.
    </x-slot>
</x-compass-forms.select>

Range

A range field consists of a select field with all numbers between the given min and max values (0 - 100 if none given):

<x-compass-forms.range
    name="form-range">
</x-compass-forms.range>

It supports several different attributes and the same slots as the text field.

<x-compass-forms.range
    name="form-range"
    id="form-range"
    value="1"
    label="Range field test"
    :classes="['class1', 'class2']"
    :disabled="false"
    :readonly="false"
    :min="0"
    :max="100"
    :extra="['extra-attribute' => 1]"
    tabindex="1">
    <x-slot name="prepend">
        <span class="input-group-text" id="basic-addon1">Range</span>
    </x-slot>
    <x-slot name="append">
        <button class="btn btn-primary" type="button">Button</button>
    </x-slot>
    <x-slot name="explanation">
        A short explanation of the field.
    </x-slot>
</x-compass-forms.range>

Submit

It's possible to add a submit button to the form by using the following code:

<x-compass-forms.submit
    name="form-submit"
    id="form-submit"
    label="Submit"
    :classes="['class1', 'class2']">
</x-compass-forms.submit>

Redirects

Redirection after submitting a form is not part of the Compass Forms package but of the custom application code. But because this is a common use case, and there is a difference between plain redirecting and redirecting from a sidepanel, we will explain what is the best way to redirect after submitting a form.

Default redirect:

public function store(Request $request): JsonResponse
{
    // Add logic to save.

    // Redirect.
    return redirect()->route('route.name');
}

Redirect inside the sidepanel itself:

public function store(Request $request): JsonResponse
{
    // Add logic to save.

    // Redirect.
    return response()->json([
        'route' => route('route.name'),        
    ]);
}

Redirect parent page from sidepanel:

public function store(Request $request): JsonResponse
{
    // Add logic to save.

    // Redirect.
    return response()->json([
        'routeReloadPage' => route('route.name'),        
    ]);
}

Installation

The package is automatically installed when you install the Compass Core package. However, Compass Core isn't required. If you want to install it separately, you can do so by running the following command in your terminal:

composer require git@gitlab.noardcode.nl:compass/forms.git

Remember to add the repository to your composer.json file if installation fails.
The GitLab access token can be obtained from yout GitLab instance.

"repositories": [
    {
        "type": "git",
        "url": "https://___token___:[gitlab_access_token]@gitlab.noardcode.nl/compass/forms"
    }
]

Configuration

Compiling CKEditor5 using webpack mix

Run npm install to get all needed dependencies and incorporate the snippet below into the webpack.mix.js:

const mix = require('laravel-mix');
require('laravel-mix-polyfill')
require('laravel-mix-listen');

/*
 |--------------------------------------------------------------------------
 | Mix Asset Management
 |--------------------------------------------------------------------------
 |
 | Mix provides a clean, fluent API for defining some Webpack build steps
 | for your Laravel applications. By default, we are compiling the CSS
 | file for the application as well as bundling up all the JS files.
 |
 */

mix.js('resources/js/app.js', 'public/js')
    .sass('resources/sass/app.scss', 'public/css')
    .sourceMaps()
    .polyfill({
        enabled: true,
        useBuiltIns: "usage",
        targets: {"firefox": "50"}
    })
    .version();

const { CKEditorTranslationsPlugin } = require( '@ckeditor/ckeditor5-dev-translations' );
const {styles} = require('@ckeditor/ckeditor5-dev-utils');
const ckeditorAssetsPath = 'vendor/ckeditor5'

const CKERegex = {
    svg: /ckeditor5-[^/]+\/theme\/icons\/[^/]+\.svg$/,
    css: /ckeditor5-[^/]+\/theme\/[\w-/]+\.css$/,
}

mix.listen('configReady', webpackConfig => {
    const rules = webpackConfig.module.rules;

    // these change often! Make sure you copy the correct regexes for your Webpack version!
    const targetSVG = /(\.(png|jpe?g|gif|webp|avif)$|^((?!font).)*\.svg$)/;
    const targetFont = /(\.(woff2?|ttf|eot|otf)$|font.*\.svg$)/;
    const targetCSS = /\.p?css$/;

    // exclude CKE regex from mix's default rules
    for (let rule of rules) {
        if (rule.test.toString() === targetSVG.toString()) {
            rule.exclude = CKERegex.svg;
        } else if (rule.test.toString() === targetFont.toString()) {
            rule.exclude = CKERegex.svg;
        } else if (rule.test.toString() === targetCSS.toString()) {
            rule.exclude = CKERegex.css;
        }
    }
});

mix.webpackConfig({
    plugins: [
        new CKEditorTranslationsPlugin({
            language: 'en',
            addMainLanguageTranslationsToAllAssets: true,
            additionalLanguages: 'all',
        })
    ],
    module: {
        rules: [
            {
                test: CKERegex.svg,
                use: ['raw-loader'],
            },
            {
                test: CKERegex.css,
                use: [
                    {
                        loader: 'style-loader',
                        options: {
                            injectType: 'singletonStyleTag',
                            attributes: {
                                'data-cke': true
                            }
                        },
                    },
                    'css-loader',
                    {
                        loader: 'postcss-loader',
                        options: {
                            postcssOptions: styles.getPostCssConfig({ // moved into option `postcssOptions`
                                themeImporter: {
                                    themePath: require.resolve('@ckeditor/ckeditor5-theme-lark')
                                },
                                minify: true
                            })
                        }
                    }
                ],
            }
        ]
    }
});

mix.setPublicPath('public')

Now run npm run dev and it should compile succesfully.