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
urlis a link, and the path becomes an address throughUi::url(). - A button with a
handlerruns that AJAX handler, or withpopup: trueopens what the handler returns in a popup. typepicks the style,confirmasks before the request runs, andhotkeybinds a key, here Shift+P for the preview.attributesadds HTML attributes, heredata-request-flashso the button shows the flash message its handler returns.checked: truekeeps the button disabled until rows of the bound list are checked, and sends their ids to the handler aschecked.
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').
/**
* 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;
}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/**
* 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.
/**
* 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;
}<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 box and list setup
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.
/**
* 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;
}/**
* 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,
);
}columns:
title:
label: Title
searchable: true
status:
label: Status
type: selectable
views:
label: Views
type: number
excerpt:
label: Excerpt
type: summary
searchable: true
invisible: trueOverflowing 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".
/**
* 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',
);
}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/**
* 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.
<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.
| Attribute | Description |
|---|---|
| data-no-drag-support | turns dragging off, leaving the mouse wheel. |
| data-vertical | scrolls a column instead of a row. |
| data-use-native-drag | leaves 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.
| Option | Description |
|---|---|
| vertical | scrolls a column instead of a row. Default: false. |
| useDrag | allows dragging. Default: true. |
| useScroll | allows the mouse wheel. Default: true. |
| useComboScroll | lets a vertical wheel scroll a row, and a horizontal one a column. Default: true. |
| useNative | leaves the scrolling to the browser when the html element has the mobile class. Default: false. |
| noOverScroll | keeps the wheel from scrolling the page once the element reaches its end. Default: false. |
| scrollClassContainer | the element, or a selector, that gets the scroll-before and scroll-after classes. Default: the element. |
| noScrollClasses | leaves the scroll classes out. Default: false. |
| scrollMarkerContainer | an element, or a selector, to add before and after marker spans to. |
| dragSelector | starts a drag only on elements matching this selector. |
| scrollSelector | listens for the wheel on the first child matching this selector. |
| dragClass | the class on body while dragging. Default: drag. |
| start, drag, stop | functions called when a drag starts, moves and ends. |
| Method | Description |
|---|---|
| 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-triggerkeeps the element disabled until rows are checked. An element withdata-list-checked-counterinside it shows how many are checked, in brackets.data-list-checked-requestadds the ids of the rows checked on the page to the element's AJAX request aschecked, which the handler reads withinput('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.
| Option | Description |
|---|---|
| alias | the widget's name, unique on the page. |
| buttons | the buttons, as a YAML file path or an array of button definitions, or the path of a partial that renders them. |
| search | the search options for a search box, or a partial to render in its place. Without it there is no search box. |
| setupHandler | the 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
| Option | Description |
|---|---|
| label | the button text, passed through the translator. |
| icon | an icon name or CSS classes, such as ph ph-plus. |
| type | the style: default, primary, secondary or danger. Default: default. |
| url | makes the button a link to this address. |
| handler | makes the button run this AJAX handler. |
| popup | with handler, opens the handler's response in a popup. Default: false. |
| confirm | a message to confirm before the request runs. |
| checked | links the button to the checked rows of the bound list. Default: false. |
| visible | false hides the button, and the name of a controller method decides when it shows. Default: true. |
| permissions | gate abilities needed to see the button, as a string or an array where any one is enough. |
| hotkey | a key combination that clicks the button, such as shift+p. |
| requestData | data sent with the AJAX request. |
| attributes | HTML attributes added to the button, such as data-request-flash: true. |