URLs and Timezones
Three resolvers adapt the widgets to your application. One turns the paths that widgets link to into addresses, one turns links with your own scheme, such as page://about, into addresses, and one gives the timezone of the person viewing the page. Each is a closure you set once, and the widgets work without any of them.
Setting the resolvers
Set your resolvers in a service provider's boot() method, such as your AppServiceProvider, so they apply to every request. This demo page sets them in its action instead, so they only apply there, and removes them when the request ends by passing null, which restores the default.
<?php
namespace App\Controllers\Reference\ExtendingController;
use Illuminate\Support\Facades\Route;
use Larajax\Ui\Facades\Ui;
/**
* SeamResolvers sets the resolvers the URLs and Timezones page demonstrates, which an app sets once in a service provider instead.
*/
trait SeamResolvers
{
/**
* registerSeamResolvers sends widget paths back to this page, resolves reference:// links to the page they name, and reads the viewer timezone from the address.
*/
protected function registerSeamResolvers(): void
{
Ui::resolveUrlUsing(function (string $path): string {
return route('reference.extending.seams', ['path' => $path]).'#widget-paths';
});
Ui::resolveLinkUsing('reference', function (string $link): ?string {
$routeName = 'reference.'.str_replace('/', '.', substr($link, strlen('reference://')));
return Route::has($routeName) ? route($routeName) : null;
});
Ui::resolveTimezoneUsing(function (): ?string {
return request()->query('timezone');
});
}
/**
* forgetSeamResolvers removes the resolvers when the request ends, so they never reach another page.
*/
protected function forgetSeamResolvers(): void
{
Ui::resolveUrlUsing(null);
Ui::resolveLinkUsing('reference', null);
Ui::resolveTimezoneUsing(null);
}
}Each resolver here does what the sections below show: widget paths go back to this page, so you can see the path a widget produced, reference:// links go to the Reference page they name, and the viewer timezone comes from the address.
Ui::url()
Ui::url() is what the widgets call for every path they link to, and you can call it in your own views for the same result. It goes through these steps:
- A link whose scheme has a link resolver, such as
reference://ui/tabs, goes to that resolver, and comes back as an empty string when the resolver finds nothing. - Any other path goes to the URL resolver, when one is set.
- Without a URL resolver, it returns
url($path).
<ul class="list-unstyled mb-0">
<li>
<code>
{{ Ui::url('samples/create') }}
</code>
</li>
<li>
<code>
{{ Ui::url('reference://ui/tabs') }}
</code>
</li>
<li>
<code>
{{ Ui::url('reference://extending/retired') ?: 'An empty string' }}
</code>
</li>
</ul>Widget paths
Ui::resolveUrlUsing() receives each path a widget links to, after the widget has filled in its parameters, such as :id, and returns the address. The paths come from:
- a list's
recordUrl, for the link on each row; - a linkage column's
linkUrl, for the link in each cell; - a toolbar button's
url.
A real resolver might add an admin prefix to every path, or map paths to named routes. This one sends every path back to the demo page, so clicking a row, a Preview link or the New Sample button shows the path it produced. Full addresses in linkUrl, starting with https://, http:// or //, are left alone.
buttons:
create:
label: New Sample
icon: ph ph-plus
type: primary
url: samples/createcolumns:
name:
label: Name
slug:
label: Preview
type: linkage
linkUrl: samples/:slugThe list sets its recordUrl where it is built:
'pathToolbar' => Toolbar::make(alias: 'pathToolbar', buttons: "{$folder}/path-buttons.yaml"),
'pathList' => Lists::make(
alias: 'pathList',
model: new Sample,
columns: "{$folder}/path-columns.yaml",
recordUrl: 'samples/:id/edit',
recordsPerPage: 5,
),Scheme links
Ui::resolveLinkUsing() registers a resolver for one scheme, such as reference for reference:// links. The resolver receives the whole link and returns its address, or null when it points nowhere, such as a page that was deleted. The scheme is matched without regard to case. A link whose scheme has no resolver, such as https://, is not a scheme link and is handled like any other address.
The widgets treat a scheme link as follows:
- A linkage column shows a resolved link with the path of its address as the text, unless the column sets
linkText, and shows a link that points nowhere as plain text. - A toolbar button with a scheme link links to its address, or renders as a plain button when there is none.
buttons:
buttons:
label: Buttons page
icon: ph ph-cursor-click
type: secondary
url: reference://ui/buttons
retired:
label: Retired page
icon: ph ph-prohibit
type: secondary
url: reference://extending/retiredcolumns:
name:
label: Name
stored_link:
label: Stored Value
valueFrom: page_link
sortable: false
page_link:
label: Link
type: linkageScheme links let an app link to its own records, such as page://about, without storing an address that changes when the record's URL does. Register one resolver per scheme, and pass null as the resolver to remove one.
Viewer timezone
Dates are stored in the application timezone, app.timezone. To show them in each person's own timezone, give Ui::resolveTimezoneUsing() a closure that returns the timezone of the person viewing, usually one saved on their account, as in Ui::resolveTimezoneUsing(fn () => auth()->user()?->timezone). The closure can return a timezone name or a DateTimeZone. On the demo page it comes from the address, so pick one.
Ui::timezone() returns the viewer timezone, for your own views. When there is no resolver, or it returns null or a name that is not a timezone, such as Mars/Olympus, it returns the application timezone.
<nav class="nav nav-pills mb-4" aria-label="Viewer timezone">
@foreach (['UTC', 'Australia/Sydney', 'America/New_York', 'Mars/Olympus'] as $timezone)
<a
@class(['nav-link', 'active' => Ui::timezone() === $timezone])
href="{{ route('reference.extending.seams', ['timezone' => $timezone]) }}#timezone">
{{ $timezone }}
</a>
@endforeach
</nav>
<p class="mb-0">
<code>
Ui::timezone()
</code>
returns
<strong>
{{ Ui::timezone() }}
</strong>
</p>Lists and filters in the viewer timezone
With a viewer timezone set, a datetime column shows the stored time in that timezone, while a date column shows the date as stored, since a date has no time to convert. A date filter reads the day the visitor picks in the viewer timezone, so in Sydney the 21st of October includes a sample published at 13:30 on the 20th, app time, which is already the 21st there.
scopes:
published_at:
label: Published
type: datecolumns:
name:
label: Name
published_at:
label: Published
type: datetime
format: j M Y, H:i T
starts_on:
label: Starts On
type: dateuseTimezone on a column, field or scope turns the conversion on or off for it:
| Type | Converts by default |
|---|---|
| datetime and timetense columns | yes. Set useTimezone: false to show the stored time. |
| date and time columns | no. Set useTimezone: true to convert. |
| date filter scopes | yes. Set useTimezone: false to read picked days in the application timezone. |
datepicker fields in datetime mode | yes. Set useTimezone: false to show and save the value as stored. |
datepicker fields in date or time mode | never. |
Storing scheme links
A field can store the scheme link itself, such as reference://ui/buttons, in place of the address. The address is worked out each time the link is shown, so it follows the page when its URL changes, and a link to a page that no longer exists shows as plain text instead of a broken link. On the demo page, the first sample's link shows in the Scheme links list after saving.
fields:
page_link:
label: Page Link
type: dropdown
emptyOption: No page
options:
'reference://ui/buttons': Buttons
'reference://ui/tabs': Tabs
'reference://extending/icons': Icons
'reference://extending/retired': A page that no longer exists
comment: Stores the reference:// link itself, which the list above resolves each time it renders.Date picker in the viewer timezone
The date picker shows the stored time in the viewer timezone, and converts what the visitor picks back to the application timezone when the form saves, so the database always holds application time. The date and time modes, and useTimezone: false, leave the value as stored. See the Date Picker for its other options.
fields:
published_at:
label: Published At
type: datepicker
mode: datetime
comment: Shown and entered in the viewer timezone, and stored in the app timezone.Methods
| Method | Description |
|---|---|
| Ui::resolveUrlUsing($resolver) | sets the closure that turns a widget path into an address, or null to use url(). |
| Ui::resolveLinkUsing($scheme, $resolver) | sets the closure that turns links with this scheme into addresses, or null to remove it. |
| Ui::url($path) | returns the address for a widget path or a scheme link, empty when a scheme link points nowhere. |
| Ui::resolveTimezoneUsing($resolver) | sets the closure that returns the viewer timezone, or null to use the application timezone. |
| Ui::timezone() | returns the viewer timezone, or the application timezone when there is none. |