Skip to content

Toolbar ​

The Toolbar widget is the bar above a list. It has three optional parts: a row of buttons, a search box and the list setup icon. bindToListWidget() links the toolbar to a list, so its buttons can follow and send the list's checked rows, and its search box searches the list. Placing a toolbar and its list on a page is covered in Building a List Page. The handlers on this page only report what they would do.

Buttons from YAML ​

buttons takes a YAML file of button definitions, or the same definitions as an array. Each one renders through the Ui factory:

  • A button with a url is a link, and the path becomes an address through Ui::url().
  • A button with a handler runs that AJAX handler, or with popup: true opens what the handler returns in a popup.
  • type picks the style, confirm asks before the request runs, and hotkey binds a key, here Shift+P for the preview.
  • attributes adds HTML attributes, here data-request-flash so the button shows the flash message its handler returns.
  • checked: true keeps the button disabled until rows of the bound list are checked, and sends their ids to the handler as checked.

The Refresh handler returns the list's onRefresh(), which renders the list again, and the Count and Delete handlers read the checked ids with input('checked').

php
/**
 * makeButtonsToolbar declares its buttons in a YAML file and links them to the given list.
 */
protected function makeButtonsToolbar(Lists $list): Toolbar
{
    $toolbar = Toolbar::make(
        alias: 'buttonsToolbar',
        buttons: '~/resources/ui/reference/lists/toolbar/buttons.yaml',
    );

    $toolbar->bindToListWidget($list);

    return $toolbar;
}
yaml
buttons:
    create:
        label: New Post
        icon: ph ph-plus
        type: primary
        url: posts/create

    preview:
        label: Preview
        icon: ph ph-app-window
        handler: onLoadToolbarDialog
        popup: true
        hotkey: shift+p

    refresh:
        label: Refresh
        icon: ph ph-arrows-clockwise
        handler: onRefreshToolbarList
        attributes:
            data-request-flash: true

    count:
        label: Count
        icon: ph ph-calculator
        type: secondary
        handler: onCountCheckedPosts
        checked: true
        attributes:
            data-request-flash: true

    delete:
        label: Delete
        icon: ph ph-trash
        type: danger
        handler: onDeleteCheckedPosts
        confirm: Delete the checked posts?
        checked: true
        attributes:
            data-request-flash: true
php
/**
 * onLoadToolbarDialog returns the dialog opened by the popup buttons.
 */
public function onLoadToolbarDialog(): string
{
    return view('reference.lists.toolbar._dialog')->render();
}

/**
 * onRefreshToolbarList reloads the list of the buttons example.
 */
public function onRefreshToolbarList(): AjaxResponse
{
    return ajax()
        ->flash('info', 'The list was refreshed.')
        ->update($this->getComponentInstance('buttonsList')->onRefresh());
}

/**
 * onCountCheckedPosts counts the checked posts and their views, as the checked buttons ask.
 */
public function onCountCheckedPosts(): AjaxResponse
{
    $posts = Post::whereIn('id', (array) input('checked'))->get();

    return ajax()->flash('info', $posts->count().' '.Str::plural('post', $posts->count()).' checked, with '.number_format($posts->sum('views')).' views.');
}

/**
 * onDeleteCheckedPosts reports the checked posts it would delete, leaving the seeded posts in place.
 */
public function onDeleteCheckedPosts(): AjaxResponse
{
    $count = Post::whereIn('id', (array) input('checked'))->count();

    return ajax()->flash('warning', $count.' '.Str::plural('post', $count).' would be deleted. The reference pages leave the posts in place.');
}

Buttons from a partial ​

Given any path other than a YAML file, buttons renders that file as a partial of the controller, so the buttons can be anything the Ui factory makes. Here that is a popup button with a hotkey, Shift+Q, a dropdown of bulk actions and a plain button that counts the checked rows. The data-list-checked-* attributes link them to the list, as checked-state buttons explains, and the data-control="toolbar" container keeps them on one line.

The partial renders through the controller, so the controller needs to render views, as set up in Installation.

php
/**
 * makePartialToolbar renders its buttons from a partial and links them to the given list.
 */
protected function makePartialToolbar(Lists $list): Toolbar
{
    $toolbar = Toolbar::make(
        alias: 'partialToolbar',
        buttons: '~/resources/views/reference/lists/toolbar/_list_toolbar.php',
    );

    $toolbar->bindToListWidget($list);

    return $toolbar;
}
php
<div data-control="toolbar">
    <?= Ui::popupButton(
        label: 'Preview',
        handler: 'onLoadToolbarDialog',
        icon: 'ph ph-app-window',
        hotkey: 'shift+q'
    ) ?>

    <?php Ui::dropdownButton(
        label: 'Bulk Actions',
        icon: 'ph ph-stack',
        dataListCheckedTrigger: true
    )->slot() ?>
        <?= Ui::dropdownItem(
            label: 'Count',
            handler: 'onCountCheckedPosts',
            icon: 'ph ph-calculator',
            dataListCheckedRequest: true,
            dataRequestFlash: true
        ) ?>
        <?= Ui::dropdownDivider() ?>
        <?= Ui::dropdownItem(
            label: 'Delete',
            handler: 'onDeleteCheckedPosts',
            icon: 'ph ph-trash',
            dataListCheckedRequest: true,
            dataRequestFlash: true,
            dataRequestConfirm: 'Delete the checked posts?'
        ) ?>
    <?php Ui::end() ?>

    <button
        type="button"
        class="btn"
        data-request="onCountCheckedPosts"
        data-request-flash
        data-list-checked-trigger
        data-list-checked-request>
        <i class="ph ph-check-square" aria-hidden="true"></i>
        Checked
        <span data-list-checked-counter></span>
    </button>
</div>

search adds a search box, configured with the search options, such as its prompt. On a toolbar bound to a list, the box searches the list's searchable columns as you type. Given a partial name in place of an array, search renders that partial instead of the box.

setupHandler adds the icon that opens the list setup. It takes the list's onLoadSetup handler through getEventHandler(), and the visitor can then choose the columns, here including the invisible excerpt, and the page size.

php
/**
 * makeSearchToolbar adds a search box and the list setup icon for the given list.
 */
protected function makeSearchToolbar(Lists $list): Toolbar
{
    $toolbar = Toolbar::make(
        alias: 'searchToolbar',
        buttons: [
            'create' => [
                'label' => 'New Post',
                'icon' => 'ph ph-plus',
                'type' => 'primary',
                'url' => 'posts/create',
            ],
        ],
        search: ['prompt' => 'Search posts...'],
        setupHandler: $list->getEventHandler('onLoadSetup'),
    );

    $toolbar->bindToListWidget($list);

    return $toolbar;
}
php
/**
 * makeToolbarList builds a list of posts with checkboxes for a toolbar to work on.
 */
protected function makeToolbarList(string $alias): Lists
{
    return Lists::make(
        alias: $alias,
        model: new Post,
        columns: '~/resources/ui/reference/lists/toolbar/columns.yaml',
        recordsPerPage: 5,
        showCheckboxes: true,
        showSetup: true,
        customPageName: false,
    );
}
yaml
columns:
    title:
        label: Title
        searchable: true

    status:
        label: Status
        type: selectable

    views:
        label: Views
        type: number

    excerpt:
        label: Excerpt
        type: summary
        searchable: true
        invisible: true

Overflowing buttons ​

The toolbar control on the button container keeps the buttons on one line. When they do not fit, drag them sideways or scroll with the mouse wheel, and a shadow marks the side with more buttons. Each button here sends its name with requestData, which the handler reads with input('button').

The control takes the options of drag scroll, such as data-no-drag-support="true".

php
/**
 * makeOverflowToolbar has more buttons than fit its width, each sending its name as request data.
 */
protected function makeOverflowToolbar(): Toolbar
{
    return Toolbar::make(
        alias: 'overflowToolbar',
        buttons: '~/resources/ui/reference/lists/toolbar/overflow.yaml',
    );
}
yaml
buttons:
    publish:
        label: Publish
        icon: ph ph-eye
        handler: onToolbarButton
        requestData:
            button: publish
        attributes:
            data-request-flash: true

    unpublish:
        label: Unpublish
        icon: ph ph-eye-slash
        handler: onToolbarButton
        requestData:
            button: unpublish
        attributes:
            data-request-flash: true

    feature:
        label: Feature
        icon: ph ph-star
        handler: onToolbarButton
        requestData:
            button: feature
        attributes:
            data-request-flash: true

    duplicate:
        label: Duplicate
        icon: ph ph-copy
        handler: onToolbarButton
        requestData:
            button: duplicate
        attributes:
            data-request-flash: true

    export:
        label: Export
        icon: ph ph-export
        handler: onToolbarButton
        requestData:
            button: export
        attributes:
            data-request-flash: true

    print:
        label: Print
        icon: ph ph-printer
        handler: onToolbarButton
        requestData:
            button: print
        attributes:
            data-request-flash: true

    share:
        label: Share
        icon: ph ph-share-network
        handler: onToolbarButton
        requestData:
            button: share
        attributes:
            data-request-flash: true

    archive:
        label: Archive
        icon: ph ph-archive
        handler: onToolbarButton
        requestData:
            button: archive
        attributes:
            data-request-flash: true
php
/**
 * onToolbarButton names the button that sent the request, from its request data.
 */
public function onToolbarButton(): AjaxResponse
{
    return ajax()->flash('info', 'The '.Str::headline((string) input('button')).' button was clicked.');
}

Drag scroll ​

The drag-scroll control, which the toolbar builds on, scrolls any row that is wider than its container, by dragging or with the mouse wheel, and touch devices drag it too. Add data-control="drag-scroll" to the element that overflows. A click that ends a drag is not passed on, so the links inside do not open by accident.

While there is more to see, the control adds the scroll-before and scroll-after classes to the element's parent, which this page styles as fades. When an item with the active class is out of view, the parent also gets scroll-active-before or scroll-active-after.

blade
<div class="reference-drag-strip">
    <div data-control="drag-scroll">
        @foreach (range(1, 20) as $week)
            <span class="badge badge-subtle theme-secondary">
                Week {{ $week }}
            </span>
        @endforeach
    </div>
</div>

The control, and the toolbar control, take these data attributes. Give each one the value true, since an attribute with no value counts as off.

AttributeDescription
data-no-drag-supportturns dragging off, leaving the mouse wheel.
data-verticalscrolls a column instead of a row.
data-use-native-dragleaves the scrolling to the browser when the html element has the mobile class, and adds is-native-drag to the element.

The element dispatches drag-scroll:start when a drag begins, drag-scroll:drag as it moves or the wheel scrolls it, and drag-scroll:stop when the drag ends. The events bubble, so a parent can listen for them.

DragScroll class ​

The library's index.js exports the DragScroll class, for a Vite app that imports the library, as in import { DragScroll } from '../../vendor/larajax/ui/resources/assets/index.js'. new DragScroll(element, options) makes an element scrollable without the control.

OptionDescription
verticalscrolls a column instead of a row. Default: false.
useDragallows dragging. Default: true.
useScrollallows the mouse wheel. Default: true.
useComboScrolllets a vertical wheel scroll a row, and a horizontal one a column. Default: true.
useNativeleaves the scrolling to the browser when the html element has the mobile class. Default: false.
noOverScrollkeeps the wheel from scrolling the page once the element reaches its end. Default: false.
scrollClassContainerthe element, or a selector, that gets the scroll-before and scroll-after classes. Default: the element.
noScrollClassesleaves the scroll classes out. Default: false.
scrollMarkerContaineran element, or a selector, to add before and after marker spans to.
dragSelectorstarts a drag only on elements matching this selector.
scrollSelectorlistens for the wheel on the first child matching this selector.
dragClassthe class on body while dragging. Default: drag.
start, drag, stopfunctions called when a drag starts, moves and ends.
MethodDescription
goToElement(element, callback, options)scrolls an item into view, animated over options.duration milliseconds (300 by default), then calls callback.
goToStart()scrolls back to the start.
isStart()returns whether the element is scrolled to its start.
isEnd()returns whether the element is scrolled to its end.
fixScrollClasses()updates the scroll classes, such as after the content changes.
pause()stops dragging and the wheel until resume().
resume()allows dragging and the wheel again.
dispose()removes the listeners and the markers.

Checked-state buttons ​

Buttons inside an element with data-list-linkage="{list id}" follow the checked rows of that list. A toolbar bound with bindToListWidget() sets the attribute on itself, so every button in it is linked.

  • data-list-checked-trigger keeps the element disabled until rows are checked. An element with data-list-checked-counter inside it shows how many are checked, in brackets.
  • data-list-checked-request adds the ids of the rows checked on the page to the element's AJAX request as checked, which the handler reads with input('checked').

A button definition with checked: true sets both attributes, the second only when the button has a handler. The Ui factory takes them as named arguments, dataListCheckedTrigger: true and dataListCheckedRequest: true, as the partial above does.

Conditional buttons ​

visible: false hides a button. visible can also name a controller method, which the toolbar calls with the button definition when it renders, showing the button when the method returns true. A method that does not exist throws an error. permissions hides the button from visitors without one of the named gate abilities.

For changes that involve several buttons, add a toolbarExtendButtons($toolbar) method to the controller. The toolbar calls it after defining its buttons, and it can use the toolbar's addButtons(), getButton() and removeButton() methods. Each definition is a ToolbarButton object whose options change with fluent calls, as in $toolbar->getButton('delete')->confirm('This cannot be undone.'). Listeners of the toolbar's toolbar.extendButtons event receive the toolbar in the same way.

Toolbar options ​

Toolbar::make() takes these options as named arguments.

OptionDescription
aliasthe widget's name, unique on the page.
buttonsthe buttons, as a YAML file path or an array of button definitions, or the path of a partial that renders them.
searchthe search options for a search box, or a partial to render in its place. Without it there is no search box.
setupHandlerthe handler that opens the list setup, usually $list->getEventHandler('onLoadSetup'). It shows the setup icon.

bindToListWidget($list) links the toolbar to a list: its buttons follow the list's checked rows, and its search box sets the list's search term and refreshes the list when the term changes.

Button definitions ​

OptionDescription
labelthe button text, passed through the translator.
iconan icon name or CSS classes, such as ph ph-plus.
typethe style: default, primary, secondary or danger. Default: default.
urlmakes the button a link to this address.
handlermakes the button run this AJAX handler.
popupwith handler, opens the handler's response in a popup. Default: false.
confirma message to confirm before the request runs.
checkedlinks the button to the checked rows of the bound list. Default: false.
visiblefalse hides the button, and the name of a controller method decides when it shows. Default: true.
permissionsgate abilities needed to see the button, as a string or an array where any one is enough.
hotkeya key combination that clicks the button, such as shift+p.
requestDatadata sent with the AJAX request.
attributesHTML attributes added to the button, such as data-request-flash: true.