Skip to content

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
<?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:

  1. 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.
  2. Any other path goes to the URL resolver, when one is set.
  3. Without a URL resolver, it returns url($path).
blade
<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.

yaml
buttons:
    create:
        label: New Sample
        icon: ph ph-plus
        type: primary
        url: samples/create
yaml
columns:
    name:
        label: Name
    slug:
        label: Preview
        type: linkage
        linkUrl: samples/:slug

The list sets its recordUrl where it is built:

php
'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,
),

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.
yaml
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/retired
yaml
columns:
    name:
        label: Name
    stored_link:
        label: Stored Value
        valueFrom: page_link
        sortable: false
    page_link:
        label: Link
        type: linkage

Scheme 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.

blade
<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.

yaml
scopes:
    published_at:
        label: Published
        type: date
yaml
columns:
    name:
        label: Name
    published_at:
        label: Published
        type: datetime
        format: j M Y, H:i T
    starts_on:
        label: Starts On
        type: date

useTimezone on a column, field or scope turns the conversion on or off for it:

TypeConverts by default
datetime and timetense columnsyes. Set useTimezone: false to show the stored time.
date and time columnsno. Set useTimezone: true to convert.
date filter scopesyes. Set useTimezone: false to read picked days in the application timezone.
datepicker fields in datetime modeyes. Set useTimezone: false to show and save the value as stored.
datepicker fields in date or time modenever.

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.

yaml
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.

yaml
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 ​

MethodDescription
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.