View on GitLab

Custom Elements Examples

Larastrap provides the ability to define your own custom element, just extending the existing ones and providing some configuration. Those can be paired with Javascript libraries and custom PHP functions to fine-tune the intended behaviour, and reuse across your code.

alert

You can define Bootstrap Alerts just as a Custom Element extending the Typography node. So you have far more flexibility than just the color, and define multiple types with specific CSS classes and specific behaviors.
The same applies to Bootstrap Badges.

Use a Custom Element to define semantic alerts, to be globally configured once to keep consistency across the whole application.
[
    'customs' => [
        'suggestion' => [
            'extends' => 't',
            'params' => [
                'node' => 'div',
                'classes' => ['alert', 'alert-info', 'custom-class'],
            ],
        ],
    ]
]
<x-larastrap::suggestion>
    Use a Custom Element to define semantic alerts, to be globally configured once
    to keep consistency across the whole application.
</x-larastrap::suggestion>
<div id="basenode-faaa7c6c346a16ed4c95acfef0617afa" class="alert alert-info custom-class">
  Use a Custom Element to define semantic alerts, to be globally configured once to keep consistency across the whole application.
</div>

commonbuttons

When building an administration interface, it may be useful to define once the common buttons to create, edit or delete elements. Those can be reused across all templates, and can be modified once to keep consistency.

[
    'customs' => [
        'ebutton' => [
            'extends' => 'link',
            'params' => [
                'color' => 'info',
                'label' => 'Edit',
            ],
        ],
        'cbutton' => [
            'extends' => 'link',
            'params' => [
                'color' => 'success',
                'label' => 'Create New',
            ],
        ],
        'dbutton' => [
            'extends' => 'link',
            'params' => [
                'color' => 'danger',
                'label' => 'Delete',
            ],
        ],
    ]
]
<x-larastrap::cbutton />
<x-larastrap::ebutton />
<x-larastrap::dbutton />
<a id="link-d446d171511033bcc9d76bb2e8bc35d1" class="btn btn-success" href="#">Create New</a> <a id="link-ba3a3b8c7ac9d7d509e978956fed4773" class="btn btn-info" href="#">Edit</a> <a id="link-e490129f660e8c519b8248f0d8c5f38c" class="btn btn-danger" href="#">Delete</a>

datepicker

A simple x-larastrap::text input can be decorated with a Bootstrap icon and marked with the .date CSS class, used to attach Bootstrap Datepicker (or whatever datepicker JS library you prefer) initialization.

Using reviewCallback it is possible to format the value before display.

[
    'customs' => [
        'datepicker' => [
            'extends' => 'text',
            'params' => [
                'classes' => ['date'],
                'placeholder' => 'Never',
                'textappend' => '<i class="bi-calendar"></i>',

                'reviewCallback' => function($component, $params) {
                    $params['value'] = ucwords(strftime('%A %d %B %Y', $params['value']));
                    return $params;
                }
            ],
        ],
    ]
]
<x-larastrap::datepicker label="Pick a Date" :value="time()" />

<!-- Inclusion of Bootstrap Datepicker CSS and JS, and initialization -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.9.0/css/bootstrap-datepicker.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.9.0/js/bootstrap-datepicker.min.js"></script>

<script>
$('input.date').datepicker({
    format: 'DD dd MM yyyy',
    autoclose: true,
    clearBtn: true,
});
</script>
<div class="row mb-3">
  <label for="input-3208b5a1a56209436b76f03190b62ec0" class="col-4 col-form-label">Pick a Date</label>
  <div class="col-8">
    <div class="input-group">
      <input id="input-3208b5a1a56209436b76f03190b62ec0" type="text" class="date form-control" name="T1GlsGCiJY" value="Thursday 21 November 2024" placeholder="Never">
    </div>
  </div>
</div><!-- Inclusion of Bootstrap Datepicker CSS and JS, and initialization -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.9.0/css/bootstrap-datepicker.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> 
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.9.0/js/bootstrap-datepicker.min.js"></script> 
<script>

$('input.date').datepicker({
    format: 'DD dd MM yyyy',
    autoclose: true,
    clearBtn: true,
});
</script>

hlink

In multi-language websites, it may be useful to centralize the generation of hreflang attributes for each internal link using the current locale by default.

[
    'customs' => [
        'hlink' => [
            'extends' => 'link',
            'params' => [
                'reviewCallback' => function($button, $params) {
                    if (isset($params['attributes']['hreflang']) == false) {
                        $params['attributes']['hreflang'] = App::getLocale();
                    }

                    return $params;
                },
            ]
        ],
    ]
]
<x-larastrap::hlink label="Just a Link" :href="route('homepage')" />
<a id="link-8c5bb96546f7f86e5862aabb2979f0a6" class="btn btn-primary" href="https://larastrap.madbob.org" hreflang="en">Just a Link</a>

linkedtitle

All headers internal to the Larastrap documentation are defined with a Custom Element translating the label into a link, for convenient and visibile internal linking.
The version actually used in this website also pushes each label/link pair to a singleton array, then used for automatic generation of the menu displayed on the top right for internal navigation.

[
    'customs' => [
        'linkedtitle' => [
            'extends' => 'link',
            'params' => [
                'override_classes' => ['title-link'],
                'reviewCallback' => function($node, $params) {
                    $identifier = Str::slug($params['label']);
                    $params['id'] = $identifier;
                    $params['label'] = sprintf('<h3>%s</h3>', $params['label']);
                    $params['href'] = sprintf('#%s', $identifier);
                    return $params;
                }
            ],
        ],
    ]
]
<x-larastrap::linkedtitle label="Example 1" />
<x-larastrap::linkedtitle label="Example 2" />
<a id="example-1" class="title-link" href="#example-1">
<h3>
  Example 1
</h3></a> <a id="example-2" class="title-link" href="#example-2">
<h3>
  Example 2
</h3></a>

mbutton

It may be useful to append an icon by default to a x-larastrap::button with a specific intent, such as open a x-larastrap::modal window.

[
    'customs' => [
        'mbutton' => [
            'extends' => 'button',
            'params' => [
                'color' => 'light',
                'postlabel' => ' <i class="bi-window"></i>',
            ],
        ],
    ]
]
<x-larastrap::mbutton label="Open Modal" triggers_modal="#test-modal" />

<x-larastrap::modal title="Test" id="test-modal">
  <p>Ciao</p>
</x-larastrap::modal>
<button id="button-5a6f2aef4aa839b4451bbafe8df57c2a" class="btn btn-light" data-bs-toggle="modal" data-bs-target="#test-modal">Open Modal</button>
<div class="modal fade" id="test-modal" tabindex="-1" aria-hidden="true">
  <div class="modal-dialog modal-none">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">
          Test
        </h5>
      </div>
      <div class="modal-body">
        <p>
          Ciao
        </p>
      </div>
      <div class="modal-footer">
        <button id="button-a7af7ed32b08714471be51337ff4aa68" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
      </div>
    </div>
  </div>
</div>

mobiletab

With a reviewCallback applied to x-larastrap::tabpane it is possible to dynamically build its HTML label and keep the template clean and readable. In this example, text in the labels is hidden when displaying on mobile - so to have compact and pretty tabs even on small screens.

This is the first tab
This is the second tab
[
    'customs' => [
        'mobiletab' => [
            'extends' => 'tabpane',
            'params' => [
                'reviewCallback' => function($component, $params) {
                    if (isset($params['attributes']['icon'])) {
                        $params['label'] = sprintf('<span class="d-none d-md-inline-block me-2">%s</span><i class="%s"></i>',
                            $params['label'],
                            $params['attributes']['icon']
                        );
                    }

                    return $params;
                }
            ],
        ],
    ]
]
<x-larastrap::tabs active="0" classes="mobiletabs">
    <x-larastrap::mobiletab label="First" icon="bi-person">
        This is the first tab
    </x-larastrap::mobiletab>
    <x-larastrap::mobiletab label="Second" icon="bi-people">
        This is the second tab
    </x-larastrap::mobiletab>
</x-larastrap::tabs>
<ul class="mobiletabs nav nav-tabs" id="tabs-248696b1d9ee8403875ac654d3cd8865" role="tablist">
  <li class="nav-item" role="presentation">
    <button id="tabpane-681bdf5db85872b569f6b206c9284391-0fe79d7d9354b5e6c9a5dc7ee3ab19cb" class="nav-link active" aria-current="true" role="tab" data-bs-toggle="tab" data-bs-target="#tabpane-681bdf5db85872b569f6b206c9284391"><span class="d-none d-md-inline-block me-2">First</span></button>
  </li>
  <li class="nav-item" role="presentation">
    <button id="tabpane-ac429ea1d3ff7032f830bb4ed6362cc8-c3e8a1ae5882c569bc20f4bda48bbe6d" class="nav-link" role="tab" data-bs-toggle="tab" data-bs-target="#tabpane-ac429ea1d3ff7032f830bb4ed6362cc8"><span class="d-none d-md-inline-block me-2">Second</span></button>
  </li>
</ul>
<div class="tab-content" id="tabs-248696b1d9ee8403875ac654d3cd8865_contents">
  <div class="p-3 tab-pane fade show active" id="tabpane-681bdf5db85872b569f6b206c9284391" role="tabpanel" aria-labelledby="tabpane-681bdf5db85872b569f6b206c9284391-0fe79d7d9354b5e6c9a5dc7ee3ab19cb">
    This is the first tab
  </div>
  <div class="p-3 tab-pane fade" id="tabpane-ac429ea1d3ff7032f830bb4ed6362cc8" role="tabpanel" aria-labelledby="tabpane-ac429ea1d3ff7032f830bb4ed6362cc8-c3e8a1ae5882c569bc20f4bda48bbe6d">
    This is the second tab
  </div>
</div>

price

One of the most basic usage of Custom Elements is to define input types with a specific role, usually identified by an icon or a suffix. An input for a monetary amount is probably the most classic example.

[
    'customs' => [
        'price' => [
            'extends' => 'number',
            'params' => [
                'textappend' => '€',
                'attributes' => [
                    'step' => 0.01,
                ]
            ],
        ],
    ]
]
<x-larastrap::price label="Price" name="amount" />
<div class="row mb-3">
  <label for="input-01d6fa33c4d0f453330a79383fbbc9d2" class="col-4 col-form-label">Price</label>
  <div class="col-8">
    <div class="input-group">
      <input id="input-01d6fa33c4d0f453330a79383fbbc9d2" type="number" class="form-control" name="amount" value="" step="0.01"> <span class="input-group-text">€</span>
    </div>
  </div>
</div>

testablechecks

What about injecting your Laravel Dusk selectors only when actually running Dusk tests? This can be in particular useful with x-larastrap::checks and x-larastrap::radios, and be able to select with precision your options and test your own combinations.

[
    'customs' => [
        'testablechecks' => [
            'extends' => 'checks',
            'params' => [
                'reviewCallback' => function($component, $params) {
                    /*
                        Choose your method to determine if you are within a Dusk test
                    */
                    if (env('DUSK_TESTING')) {
                        $options = $params['options'];
                        $new_options = [];

                        foreach($options as $value => $option) {
                            if (is_object($option)) {
                                if (!isset($option->button_attributes)) {
                                    $option->button_attributes = [];
                                }
                            }
                            else {
                                $option = (object) [
                                    'label' => $option,
                                    'button_attributes' => [],
                                ];
                            }

                            $option->button_attributes['dusk'] = sprintf('%s-%s', $params['name'], $value);
                            $new_options[$value] = $option;
                        }

                        $params['options'] = $new_options;
                    }

                    return $params;
                }
            ],
        ],
    ]
]
<x-larastrap::testablechecks name="choose" label="Choose one" :options="['1' => 'one', '2' => 'two', '3' => 'three']" />
<div class="row mb-3">
  <label for="checks-bcddfa7e3c5c71fab4e58a1624b6fd76" class="col-4 col-form-label">Choose one</label>
  <div class="col-8">
    <div class="btn-group" role="group">
      <input type="checkbox" class="btn-check" name="choose[]" id="checks-bcddfa7e3c5c71fab4e58a1624b6fd76-f97c5d29941bfb1b2fdab0874906ab82" autocomplete="off" value="1"> <label class="btn btn-outline-primary" for="checks-bcddfa7e3c5c71fab4e58a1624b6fd76-f97c5d29941bfb1b2fdab0874906ab82" dusk="choose-1">one</label> <input type="checkbox" class="btn-check" name="choose[]" id="checks-bcddfa7e3c5c71fab4e58a1624b6fd76-b8a9f715dbb64fd5c56e7783c6820a61" autocomplete="off" value="2"> <label class="btn btn-outline-primary" for="checks-bcddfa7e3c5c71fab4e58a1624b6fd76-b8a9f715dbb64fd5c56e7783c6820a61" dusk="choose-2">two</label> <input type="checkbox" class="btn-check" name="choose[]" id="checks-bcddfa7e3c5c71fab4e58a1624b6fd76-35d6d33467aae9a2e3dccb4b6b027878" autocomplete="off" value="3"> <label class="btn btn-outline-primary" for="checks-bcddfa7e3c5c71fab4e58a1624b6fd76-35d6d33467aae9a2e3dccb4b6b027878" dusk="choose-3">three</label>
    </div>
  </div>
</div>

username

Mobile text input tends to capitalize all texts, including those that are not intended to be capitalized, such as usernames. This custom widget adds all the required HTML attributes to disable unwanted modifications.

[
    'customs' => [
        'username' => [
            'extends' => 'text',
            'params' => [
                'attributes' => [
                    'autocomplete' => 'off',
                    'autocorrect' => 'off',
                    'autocapitalize' => 'none',
                    'spellcheck' => 'false',
                ]
            ],
        ],
    ]
]
<x-larastrap::username label="Username" name="username" />
<div class="row mb-3">
  <label for="input-19c00181716f0dcef2c13eb5329c1aaf" class="col-4 col-form-label">Username</label>
  <div class="col-8">
    <input id="input-19c00181716f0dcef2c13eb5329c1aaf" type="text" class="form-control" name="username" value="" autocomplete="off" autocorrect="off" autocapitalize="none" spellcheck="false">
  </div>
</div>