yf_html_bs5 class

Bootstrap 5.3 markup of the yf_html widgets, used by yf_html when conf('css_framework') is bs5 (yf_html::_bs5()). Same signatures and options as the bs3 methods; ids come from the calling yf_html's counters, so they stay the bs3 ids.

Fileplugins/html/classes/html/yf_html_bs5.class.php
Use_class('html_bs5', 'classes/html/')

Public properties

A site sets them in its config with $PROJECT_CONF['html_bs5']['NAME'] = ...;

PropertyDefaultDescription
$CLASS_LABEL_BTN
string
'btn btn-outline-primary' label class of button_radio_box() / button_check_box() items
$CLASS_SWITCH
string
'form-check form-switch' wrapper of a check_box() with the `switch` option
$POPOVER_LEGACY_DATA
string
['toggle', 'content', 'container', 'html', 'trigger', 'placement', ... (11 items)]
array (
  0 => 'toggle',
  1 => 'content',
  2 => 'container',
  3 => 'html',
  4 => 'trigger',
  5 => 'placement',
  6 => 'title',
  7 => 'delay',
  8 => 'animation',
  9 => 'template',
  10 => 'offset',
)
bs3 data-* popover options tooltip() accepts and renames to data-bs-*
$_parent
yf_html
null set by yf_html::_bs5()

Public methods 44

accordion

public function accordion($data = [], $extra = [])
    public function accordion($data = [], $extra = [])
    {
        $extra = (array) $extra;
        $extra['id'] = ($extra['id'] ?? null) ?: $this->_id('accordion');
        $parent_attr = ! empty($extra['always_open']) ? '' : ' data-bs-parent="#' . $extra['id'] . '"';
        $items = [];
        $i = 0;
        foreach ((array) $data as $k => $v) {
            if ( ! is_array($v)) {
                $content = $v;
                $v = [];
            } else {
                $content = $v['body'] ?? '';
            }
            $name = ($v['name'] ?? null) ?: $k;
            $desc = ($v['desc'] ?? null) ?: ucfirst(str_replace('_', ' ', $name));
            $id = ($v['id'] ?? null) ?: 'accordion_item_' . $k;
            $is_selected = isset($extra['selected']) ? ($extra['selected'] == $k) : (++$i == 1);
            $class_group = ($v['class_group'] ?? null) ?: ($extra['class_group'] ?? '');
            $class_head = ($v['class_head'] ?? null) ?: ($extra['class_head'] ?? '');
            $class_body = ($v['class_body'] ?? null) ?: ($extra['class_body'] ?? '');
            $badge = ($v['badge'] ?? null) ? $this->_badge($v) : '';
            $items[] = '<div class="' . $this->_cls('accordion-item', $class_group) . '">'
                . '<h2 class="' . $this->_cls('accordion-header', $class_head) . '">'
                . '<button class="' . $this->_cls('accordion-button', $is_selected ? '' : 'collapsed') . '" type="button" data-bs-toggle="collapse" data-bs-target="#' . $id . '"'
                . ' aria-expanded="' . ($is_selected ? 'true' : 'false') . '" aria-controls="' . $id . '">' . $desc . $badge . '</button>'
                . '</h2>'
                . '<div id="' . $id . '" class="' . $this->_cls('accordion-collapse collapse', $is_selected ? 'show' : '', $class_body) . '"' . $parent_attr . '>'
                . '<div class="accordion-body">' . $content . '</div>'
                . '</div>'
                . '</div>';
        }
        return '<div class="' . $this->_cls('accordion', ! empty($extra['flush']) ? 'accordion-flush' : '', $extra['class'] ?? '') . '" id="' . $extra['id'] . '"'
            . $this->_parent->_theme_attr($extra['theme'] ?? '') . '>' . implode(PHP_EOL, $items) . '</div>';
    }

alert

public function alert($data = [], $extra = [])
    public function alert($data = [], $extra = [])
    {
        $extra = (array) $extra;
        $extra['id'] = ($extra['id'] ?? null) ?: $this->_id('alert');
        $is_arr = is_array($data);
        $closable = ! ($extra['no_close'] ?? null) && ! ($is_arr && ($data['no_close'] ?? null));
        $head = $is_arr ? ($data['head'] ?? '') : '';
        $body = $is_arr ? ($data['body'] ?? '') : $data;
        $alert_type = (($extra['alert'] ?? null) ?: ($is_arr ? ($data['alert'] ?? '') : '')) ?: 'error';
        $alert_type = ['error' => 'danger'][$alert_type] ?? $alert_type;
        return '<div class="' . $this->_cls('alert alert-' . $alert_type, $closable ? 'alert-dismissible' : '', 'fade show', $extra['class'] ?? '') . '" role="alert" id="' . $extra['id'] . '">'
            . ($head ? '<h4 class="alert-heading">' . $head . '</h4>' : '')
            . $body
            . ($closable ? '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="' . t('Close') . '"></button>' : '')
            . '</div>';
    }

button_box

public function button_box($name, $values = [], $selected = '', $extra = [])
    public function button_box($name, $values = [], $selected = '', $extra = [])
    {
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = $extra['name'] ?? '';
        }
        $extra = (array) $extra;
        $desc = isset($extra['desc']) ? $extra['desc'] : ucfirst(str_replace('_', '', (string) $name));
        $values = isset($extra['values']) ? $extra['values'] : (array) $values;
        $translate = ($extra['no_translate'] ?? null) ? 0 : ($extra['translate'] ?? 0);
        $selected = isset($extra['selected']) ? $extra['selected'] : $selected;
        $extra['id'] = ($extra['id'] ?? null) ?: $this->_id('button_box');
        if ( ! $values) {
            return false;
        }
        [$items, $selected_val] = $this->_dropdown_items($values, (string) $selected, $translate);
        $class = $this->_cls(($extra['class'] ?? null) ?: 'btn btn-outline-secondary dropdown-toggle', $extra['class_add'] ?? '');
        $text = $selected_val ?: $desc;
        $body = ['<div class="btn-group" id="' . $extra['id'] . '">'];
        if ($extra['button_split'] ?? null) {
            $body[] = '<button type="button" class="btn btn-outline-secondary">' . $text . '</button>';
            $body[] = '<button type="button" class="' . $class . ' dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false"><span class="visually-hidden">' . t('Toggle Dropdown') . '</span></button>';
        } else {
            $body[] = '<button type="button" class="' . $class . '" data-bs-toggle="dropdown" aria-expanded="false">' . $text . '</button>';
        }
        return implode(PHP_EOL, array_merge($body, ['<ul class="dropdown-menu">'], $items, ['</ul>', '</div>']));
    }
public function button_check_box($name, $values = [], $selected = '')

One checkbox as a Bootstrap 5 toggle button (input.btn-check + label.btn); options of yf_html::check_box().

    public function button_check_box($name, $values = [], $selected = '')
    {
        $extra = [];
        if (is_array($name)) {
            $extra = $name;
            $name = $extra['name'] ?? '';
            $selected = $extra['selected'] ?? $selected;
        }
        $name = strlen((string) $name) ? $name : 'checkbox';
        if (isset($extra['checked'])) {
            $selected = $extra['checked'];
        }
        $id = (string) (($extra['force_id'] ?? null) ?: ($extra['id'] ?? null));
        // form2 passes the field name as the id: two boxes with one name would share it and the second label would toggle the first box
        if ( ! strlen($id) || ( ! ($extra['force_id'] ?? null) && $id === (string) $name)) {
            $id = $this->_id('check_box');
        }
        $desc = isset($extra['desc']) ? $extra['desc'] : ucfirst(str_replace('_', '', $name));
        if ($extra['no_desc'] ?? null) {
            $desc = '';
        }
        $add_str = $extra['add_str'] ?? '';
        $label_class = $this->_cls(($extra['label_extra']['class'] ?? null) ?: $this->CLASS_LABEL_BTN, $extra['class_add_label_checkbox'] ?? '');
        $label_right = ($extra['label_right'] ?? null) ? '<label class="text"> <small>' . ($extra['desc'] ?? '') . '</small></label>' : '';
        $input = [
            'type' => 'checkbox',
            'name' => $name,
            'id' => $id,
            'value' => strlen((string) ($extra['value'] ?? '')) ? $extra['value'] : '1',
            'checked' => $selected ? 'checked' : null,
            'class' => $this->_cls($extra['class'] ?? '', $extra['class_add'] ?? '', 'btn-check'),
            'autocomplete' => 'off',
        ] + $extra;
        return '<div class="btn-group" role="group">'
            . (($extra['outer_label'] ?? null) ? '<label class="outer-label">' . $extra['outer_label'] . '</label>' : '')
            . '<input' . _attrs($input, ['type', 'name', 'id', 'value', 'checked', 'class', 'style', 'disabled', 'required', 'autocomplete']) . ($add_str ? ' ' . $add_str : '') . '>'
            . '<label class="' . $label_class . '" for="' . _htmlchars(fix_html_attr_id($id)) . '">' . (($extra['translate'] ?? false) ? t($desc) : $desc) . '</label>'
            . $label_right
            . '</div>';
    }
public function button_radio_box($name, $values = [], $selected = null, $extra = [])

Radios as a Bootstrap 5 button group (input.btn-check + label.btn); options of yf_html::radio_box().

    public function button_radio_box($name, $values = [], $selected = null, $extra = [])
    {
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = $extra['name'] ?? '';
        }
        ! is_array($extra) && $extra = [];
        $name = $name ?: ($extra['name'] ?? '');
        $values = $values ?: ($extra['values'] ?? []);
        $selected = (string) ($selected ?? ($extra['selected'] ?? ''));
        $type = $extra['type'] ?? 2;
        $add_str = $extra['add_str'] ?? '';
        if ($extra['class_add'] ?? null) {
            $add_str .= ' class="' . trim(($extra['class'] ?? '') . ' ' . $extra['class_add']) . '" ';
        }
        if ($extra['style'] ?? null) {
            $add_str .= ' style="' . $extra['style'] . '" ';
        }
        $body = [];
        if ($values) {
            $id_prefix = $this->_id('radio_box');
            ($extra['force_id'] ?? null) && $id_prefix = $extra['force_id'];
            if ($extra['outer_label'] ?? null) {
                $body[] = '<label class="outer-label">' . $extra['outer_label'] . '</label>';
            }
            $input_class = $this->_class_attr('btn-check', $add_str);
            $counter = 0;
            foreach ((array) $values as $value => $val_name) {
                $item_extra = $extra;
                $item_class = '';
                if (is_array($val_name)) {
                    $item_extra += (array) ($val_name['extra'] ?? []);
                    $item_class = $val_name['class'] ?? '';
                    $val_name = $val_name['html'] ?? '';
                }
                $label_class = $this->_cls(($item_class ?: ($item_extra['label_extra']['class'] ?? null)) ?: $this->CLASS_LABEL_BTN, $item_extra['class_add_label_radio'] ?? '');
                // loose, like yf_html::radio_box()
                $is_selected = (string) ($type == 1 ? $val_name : $value) == $selected;
                $id = $id_prefix . '_' . ++$counter;
                $label_attrs = (array) ($item_extra['label_extra'] ?? []);
                $label_attrs['class'] = $label_class;
                $label_attrs['for'] = $id;
                $body[] = '<input type="radio"' . $input_class . ' name="' . $name . '" id="' . $id . '" value="' . $value . '" autocomplete="off"'
                    . ($add_str ? ' ' . trim($add_str) : '') . ($is_selected ? ' checked="checked"' : '') . '>'
                    . '<label' . _attrs($label_attrs, ['id', 'class', 'style', 'for']) . '>' . t($val_name) . '</label>';
            }
        }
        $label_right = ($extra['label_right'] ?? null) ? '<label class="text"> <small>' . ($extra['desc'] ?? '') . '</small></label>' : '';
        $group = array_key_exists('horizontal', $extra) && ! $extra['horizontal'] ? 'btn-group-vertical' : 'btn-group';
        return '<div class="' . $group . '" role="group">' . implode(PHP_EOL, $body) . $label_right . '</div>';
    }
public function button_split_box($name, $values = [], $selected = '', $extra = [])
    public function button_split_box($name, $values = [], $selected = '', $extra = [])
    {
        $extra = (array) $extra;
        $extra['button_split'] = true;
        // through yf_html, so a site override of button_box() applies to split boxes too (as under bs3)
        return $this->_parent ? $this->_parent->button_box($name, $values, $selected, $extra) : $this->button_box($name, $values, $selected, $extra);
    }

div_box

public function div_box($name, $values = [], $selected = '', $extra = [])
    public function div_box($name, $values = [], $selected = '', $extra = [])
    {
        if (is_array($name)) {
            $extra = (array) $extra + $name;
            $name = $extra['name'] ?? '';
        }
        $extra = (array) $extra;
        $desc = ($extra['desc'] ?? null) ?: ucfirst(str_replace('_', '', (string) $name));
        $values = isset($extra['values']) ? $extra['values'] : (array) $values;
        $translate = ($extra['no_translate'] ?? null) ? 0 : ($extra['translate'] ?? 0);
        $selected = ($extra['selected'] ?? null) ?: $selected;
        $extra['id'] = ($extra['id'] ?? null) ?: $this->_id('div_box');
        if ( ! $values) {
            return false;
        }
        [$items, $selected_val] = $this->_dropdown_items($values, (string) $selected, $translate);
        return implode(PHP_EOL, array_merge(
            [
                '<li class="dropdown" style="list-style-type:none;" id="' . $extra['id'] . '">',
                '<a class="dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">' . ($selected_val ?: $desc) . '</a>',
                '<ul class="dropdown-menu">',
            ],
            $items,
            ['</ul>', '</li>'],
        ));
    }

grid

public function grid($data = [], $extra = [])
    public function grid($data = [], $extra = [])
    {
        $extra = (array) $extra;
        $extra['id'] = ($extra['id'] ?? null) ?: $this->_id('grid');
        $rows = [];
        foreach ((array) $data as $row) {
            $row = (array) $row;
            $row_col = max(1, count($row) ? (int) floor(12 / count($row)) : 1);
            $items = [];
            foreach ($row as $item) {
                $item = is_array($item) ? $item : [$item];
                $body = ($item[0] ?? null) ?: ($item['body'] ?? '');
                $col = (($item[1] ?? null) ?: ($item['col'] ?? null)) ?: $row_col;
                $items[] = '<div class="' . $this->_cls('col-md-' . $col, $item['class'] ?? '') . '">' . $body . '</div>';
            }
            $rows[] = '<div class="row">' . implode(PHP_EOL, $items) . '</div>';
        }
        return '<div class="' . $this->_cls('grid', $extra['class'] ?? '') . '" id="' . $extra['id'] . '">' . implode(PHP_EOL, $rows) . '</div>';
    }

jumbotron

public function jumbotron($data = [], $extra = [])
    public function jumbotron($data = [], $extra = [])
    {
        $extra = (array) $extra;
        $extra['id'] = ($extra['id'] ?? null) ?: $this->_id('jumbotron');
        $data = is_array($data) ? $data : ['body' => $data];
        return '<div class="' . $this->_cls('p-5 mb-4 bg-body-tertiary rounded-3', $extra['class'] ?? '') . '" id="' . $extra['id'] . '">'
            . (($data['head'] ?? null) ? '<h1 class="display-5 fw-bold">' . $data['head'] . '</h1>' : '')
            . ($data['body'] ?? '')
            . '</div>';
    }

list_group

public function list_group($data = [], $extra = [])
    public function list_group($data = [], $extra = [])
    {
        $extra = (array) $extra;
        $extra['id'] = ($extra['id'] ?? null) ?: $this->_id('list_group');
        $p = $this->_parent;
        $has_links = $p->_list_group_has_links($data);
        $items = [];
        foreach ((array) $data as $v) {
            if ( ! is_array($v)) {
                $body = $v;
                $v = [];
            } else {
                $body = $v['body'] ?? '';
            }
            $badge = (string) ($v['badge'] ?? '');
            $class_item = ($v['class_item'] ?? null) ?: ($extra['class_item'] ?? '');
            $badge_html = strlen($badge) ? '<span class="badge text-bg-primary rounded-pill float-end">' . $badge . '</span> ' : '';
            $state = $p->_list_group_item_state($v, $extra, 'bs5');
            if ( ! $has_links && $state === null) {
                $items[] = '<li class="' . $this->_cls('list-group-item', $class_item) . '">' . $badge_html . $body . '</li>';
                continue;
            }
            $state ??= ['link' => null, 'class' => '', 'attrs' => []];
            $tag = $has_links ? ($state['link'] !== null ? 'a' : 'div') : 'li';
            $a = ['href' => $state['link'] ?? '', 'class' => $this->_cls('list-group-item' . $state['class'], $class_item)] + $state['attrs'];
            $items[] = '<' . $tag . $p->_b4_attrs($a) . '>' . $badge_html . $body . '</' . $tag . '>';
        }
        $tag = $has_links ? 'div' : ( ! empty($extra['numbered']) ? 'ol' : 'ul');
        $class = $this->_cls('list-group', ! empty($extra['numbered']) ? 'list-group-numbered' : '', ! empty($extra['flush']) ? 'list-group-flush' : '', $extra['class'] ?? '');
        return '<' . $tag . ' class="' . $class . '" id="' . $extra['id'] . '"' . $p->_theme_attr($extra['theme'] ?? '') . '>' . implode(PHP_EOL, $items) . '</' . $tag . '>';
    }

media_objects

public function media_objects($data = [], $extra = [])
    public function media_objects($data = [], $extra = [])
    {
        $extra = (array) $extra;
        $extra['id'] = ($extra['id'] ?? null) ?: $this->_id('media_objects');
        $data = $data ? $this->_parent->_recursive_sort_items($data) : [];
        $img_class = $this->_cls($extra['img_class'] ?? '', $extra['img_class_add'] ?? '');
        $keys = array_keys($data);
        $keys_counter = array_flip($keys);
        $items = [];
        foreach ($data as $id => $item) {
            $next_id = $keys[$keys_counter[$id] + 1] ?? null;
            $next_item = $next_id ? $data[$next_id] : [];
            $close_num_levels = 1;
            if ($next_item) {
                $close_num_levels = max(0, $item['level'] - $next_item['level'] + 1);
            } elseif ($item['level'] > 0) {
                $close_num_levels = $item['level'] + 1;
            }
            $link = $item['link'] ?? '';
            $items[] = '<div class="d-flex mb-3">'
                . '<a class="flex-shrink-0"' . ($link ? ' href="' . $link . '"' : '') . '>'
                . '<img' . ($img_class ? ' class="' . $img_class . '"' : '') . ' alt="' . ($item['alt'] ?? '') . '" src="' . ($item['img'] ?? '') . '"'
                . (($item['img_width'] ?? null) ? ' width="' . $item['img_width'] . '"' : '') . (($item['img_height'] ?? null) ? ' height="' . $item['img_height'] . '"' : '') . '></a>'
                . '<div class="flex-grow-1 ms-3">'
                . '<h5 class="mt-0">'
                . ($link ? '<a href="' . $link . '">' : '') . ($item['head'] ?? '') . ($link ? '</a>' : '')
                . (($item['date'] ?? null) ? ' <small class="float-end">' . _format_date($item['date'], ($extra['date_format'] ?? null) ?: 'full') . '</small>' : '')
                . '</h5>'
                . ($item['body'] ?? '')
                . str_repeat('</div></div>', $close_num_levels);
        }
        return '<div class="' . $this->_cls('media-objects', $extra['class'] ?? '') . '" id="' . $extra['id'] . '">' . implode(PHP_EOL, $items) . '</div>';
    }

modal

public function modal($extra = [])
    public function modal($extra = [])
    {
        $extra = (array) $extra;
        $id = ($extra['id'] ?? null) ?: $this->_id('modal');
        $style = $extra['style'] ?? '';
        $title_id = $id . '_title';
        $inline = (bool) ($extra['inline'] ?? null);
        return '<div class="' . $this->_cls('modal', $inline ? 'position-static d-block' : '', ! empty($extra['fade']) && ! $inline ? 'fade' : '') . '"' . ($style ? ' style="' . $style . '"' : '') . ' id="' . $id . '" tabindex="-1"'
            . (($extra['header'] ?? null) ? ' aria-labelledby="' . $title_id . '"' : '')
            . ( ! empty($extra['static']) ? ' data-bs-backdrop="static" data-bs-keyboard="false"' : '') . $this->_parent->_theme_attr($extra['theme'] ?? '') . '>'
            . '<div class="' . $this->_modal_dialog_class($extra) . '"><div class="modal-content">'
            . '<div class="modal-header">'
            . (($extra['header'] ?? null) ? '<h5 class="modal-title" id="' . $title_id . '">' . $extra['header'] . '</h5>' : '')
            . (($extra['show_close'] ?? null) ? '<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="' . t('Close') . '"></button>' : '')
            . '</div>'
            . '<div class="modal-body">' . ($extra['body'] ?? '') . '</div>'
            . (($extra['footer'] ?? null) ? '<div class="modal-footer">' . $extra['footer'] . '</div>' : '')
            . '</div></div></div>';
    }

offcanvas

public function offcanvas($extra = [])

Options of yf_html::offcanvas(); the id comes from yf_html.

    public function offcanvas($extra = [])
    {
        $p = $this->_parent;
        $extra = (array) $extra;
        $id = fix_html_attr_id((string) (($extra['id'] ?? null) ?: $this->_id('offcanvas')));
        $placement = in_array($extra['placement'] ?? '', ['start', 'end', 'top', 'bottom'], true) ? $extra['placement'] : 'start';
        $title = $p->_b4_text($extra['title'] ?? '', $extra);
        $backdrop = $extra['backdrop'] ?? true;
        $a = [
            'class' => $this->_cls('offcanvas offcanvas-' . $placement, ($extra['show'] ?? null) ? 'show' : '', $extra['class_add'] ?? ''),
            'tabindex' => '-1',
            'id' => $id,
            'aria-labelledby' => strlen($title) ? $id . '_title' : '',
            'data-bs-backdrop' => $backdrop === 'static' ? 'static' : ($backdrop ? '' : 'false'),
            'data-bs-scroll' => ($extra['scroll'] ?? null) ? 'true' : '',
        ] + $p->_b4_pass($extra);
        $close = ($extra['no_close'] ?? null) ? '' : '<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="' . t('Close') . '"></button>';
        $header = (strlen($title) || $close) ? '<div class="offcanvas-header">' . (strlen($title) ? '<h5 class="offcanvas-title" id="' . $id . '_title">' . $title . '</h5>' : '') . $close . '</div>' : '';
        return '<div' . $p->_b4_attrs($a) . '>' . $header . '<div class="offcanvas-body">' . ($extra['body'] ?? '') . '</div></div>';
    }

pagination

public function pagination($data = [], $extra = [])
    public function pagination($data = [], $extra = [])
    {
        $extra = (array) $extra;
        $extra['id'] = ($extra['id'] ?? null) ?: $this->_id('pagination');
        $data = (array) $data;
        $prev = $data['prev'] ?? null;
        $next = $data['next'] ?? null;
        unset($data['prev'], $data['next']);
        $html = $this->_parent ?: html();
        $items = [];
        if ($prev || $html->_pagination_state('prev', $extra) === 'disabled') {
            $items[] = $this->_pagination_item('prev', $prev, t('Prev'), $extra);
        }
        foreach ($data as $page => $link) {
            $items[] = $this->_pagination_item($page, $link, $page, $extra);
        }
        if ($next || $html->_pagination_state('next', $extra) === 'disabled') {
            $items[] = $this->_pagination_item('next', $next, t('Next'), $extra);
        }
        [$size_class] = $html->_pagination_classes($extra['size'] ?? '', $extra['align'] ?? '', 'bs5');
        return '<nav><ul class="' . $this->_cls('pagination', $size_class, $extra['class'] ?? '') . '" id="' . $extra['id'] . '">' . implode(PHP_EOL, $items) . '</ul></nav>';
    }

panel

public function panel($extra = [])
    public function panel($extra = [])
    {
        $extra = (array) $extra;
        $extra['id'] = ($extra['id'] ?? null) ?: $this->_id('panel');
        $title = (string) ($extra['title'] ?? '');
        $p = $this->_parent;
        $border = $p->_color_type($extra['border'] ?? '', '');
        $subtitle = strlen((string) ($extra['subtitle'] ?? '')) ? '<h6 class="card-subtitle mb-2 text-body-secondary">' . $p->_b4_text($extra['subtitle'], $extra) . '</h6>' : '';
        $img = strlen((string) ($extra['img_top'] ?? '')) ? '<img' . $p->_b4_attrs(['src' => $p->_safe_href($extra['img_top']), 'class' => $this->_cls('card-img-top', strlen($title) ? 'rounded-0' : '')]) . $p->_img_alt($extra) . '>' : '';
        $footer = strlen((string) ($extra['footer'] ?? '')) ? '<div class="card-footer">' . $extra['footer'] . '</div>' : '';
        return '<div class="' . $this->_cls(($extra['class'] ?? null) ?: 'card', $border ? 'border-' . $border : '', $extra['class_add'] ?? '') . '" id="' . $extra['id'] . '"' . $p->_theme_attr($extra['theme'] ?? '') . '>'
            . (strlen($title) ? '<div class="card-header"><h5 class="card-title mb-0">' . $title . '</h5></div>' : '')
            . $img
            . '<div class="card-body">' . $subtitle . ($extra['body'] ?? '') . '</div>'
            . $footer
            . '</div>';
    }

placeholder

public function placeholder($extra = [])

Options of yf_html::placeholder().

    public function placeholder($extra = [])
    {
        $p = $this->_parent;
        $extra = (array) $extra;
        $lines = max(1, min(50, (int) ($extra['lines'] ?? 3)));
        $widths = [];
        foreach ((array) ($extra['widths'] ?? [12, 8, 6]) as $w) {
            (int) $w > 0 && $widths[] = min(12, (int) $w);
        }
        $widths = $widths ?: [12];
        $glow = $extra['glow'] ?? true;
        $anim = $glow === 'wave' ? 'placeholder-wave' : ($glow ? 'placeholder-glow' : '');
        $size = in_array($extra['size'] ?? '', ['xs', 'sm', 'lg'], true) ? 'placeholder-' . $extra['size'] : '';
        $color = $p->_color_type($extra['color'] ?? '', '');
        $spans = [];
        for ($i = 0; $i < $lines; $i++) {
            $spans[] = '<span class="' . $this->_cls('placeholder col-' . $widths[$i % count($widths)], $size, $color ? 'bg-' . $color : '') . '"></span>';
        }
        $a = ['class' => $this->_cls($anim, $extra['class_add'] ?? ''), 'id' => $extra['id'] ?? '', 'aria-hidden' => 'true'];
        return '<div' . $p->_b4_attrs($a) . '>' . implode('<br>', $spans) . '</div>';
    }

progress_bar

public function progress_bar($data = [], $extra = [])

One value: a .progress with one bar; several: a .progress-stacked of one-bar .progress elements (Bootstrap 5.3 markup).

    public function progress_bar($data = [], $extra = [])
    {
        $extra = (array) $extra;
        $extra['id'] = ($extra['id'] ?? null) ?: $this->_id('progress_bar');
        $bars = [];
        foreach ((array) $data as $v) {
            if ( ! is_array($v)) {
                $val = $v;
                $v = [];
            } else {
                $val = $v['val'] ?? 0;
            }
            $type = ($v['type'] ?? null) ?: ($extra['type'] ?? '');
            $class_item = ($v['class_item'] ?? null) ?: ($extra['class_item'] ?? '');
            [$striped, $animated, $label] = $this->_parent->_progress_bar_item($v, $extra);
            $bars[] = [
                'val' => $val,
                'aria' => ' role="progressbar"' . (strlen($label) ? ' aria-label="' . $this->_aria_text($label) . '"' : '') . ' aria-valuenow="' . $val . '" aria-valuemin="0" aria-valuemax="100"',
                'class' => $this->_cls('progress-bar', $type ? 'bg-' . $type : '', ($striped || $animated) ? 'progress-bar-striped' : '', $animated ? 'progress-bar-animated' : '', $class_item),
                'style' => $v['style'] ?? '',
                'label' => $label,
            ];
        }
        $class = $extra['class'] ?? '';
        $height = $this->_parent->_css_length($extra['height'] ?? '');
        $theme = $this->_parent->_theme_attr($extra['theme'] ?? '');
        if (count($bars) > 1) {
            $items = [];
            foreach ($bars as $bar) {
                $items[] = '<div class="progress"' . $bar['aria'] . ' style="width: ' . $bar['val'] . '%' . ($height !== '' ? '; height: ' . $height : '') . '">'
                    . '<div class="' . $bar['class'] . '"' . ($bar['style'] ? ' style="' . $bar['style'] . '"' : '') . '>' . $bar['label'] . '</div></div>';
            }
            return '<div class="' . $this->_cls('progress-stacked', $class) . '" id="' . $extra['id'] . '"' . ($height !== '' ? ' style="height: ' . $height . '"' : '') . $theme . '>' . implode(PHP_EOL, $items) . '</div>';
        }
        $bar = $bars[0] ?? null;
        return '<div class="' . $this->_cls('progress', $class) . '" id="' . $extra['id'] . '"' . ($bar ? $bar['aria'] : '') . ($height !== '' ? ' style="height: ' . $height . '"' : '') . $theme . '>'
            . ($bar ? '<div class="' . $bar['class'] . '" style="width: ' . $bar['val'] . '%;' . ($bar['style'] ? ' ' . $bar['style'] : '') . '">' . $bar['label'] . '</div>' : '')
            . '</div>';
    }

switch_box

public function switch_box($extra, $add_str = '', $translate = false)

A checkbox as a Bootstrap 5 switch: div.form-check.form-switch, the input with role="switch", then its label.

  • $extra array the input attributes yf_html::check_box() prepared (type, name, id, value, checked, class, desc, ...)
  • $add_str string
  • $translate bool
    public function switch_box($extra, $add_str = '', $translate = false)
    {
        $wrap = ['class' => $this->_cls($this->CLASS_SWITCH, $extra['class_add_label_checkbox'] ?? '')];
        $extra['role'] = 'switch';
        $desc = (string) ($extra['desc'] ?? '');
        return '<div' . _attrs($wrap, ['class']) . '>'
            . '<input' . _attrs($extra, ['type', 'name', 'id', 'value', 'checked', 'class', 'style', 'disabled', 'required', 'role']) . ($add_str ? ' ' . $add_str : '') . '>'
            . (strlen($desc) ? '<label' . _attrs(['class' => 'form-check-label', 'for' => fix_html_attr_id((string) ($extra['id'] ?? ''))], ['class', 'for']) . '>' . ($translate ? t($desc) : $desc) . '</label>' : '')
            . '</div>';
    }

tabs

public function tabs($tabs = [], $extra = [])
    public function tabs($tabs = [], $extra = [])
    {
        $extra = (array) $extra;
        $extra['id'] = ($extra['id'] ?? null) ?: $this->_id('tabs');
        $extra_by_id = (array) ($extra['by_id'] ?? []);
        unset($extra['by_id']);
        $links_prefix = ($extra['links_prefix'] ?? null) ?: 'tab_';
        [$nav_class, $nav_toggle, $nav_vertical] = $this->_parent->_tabs_nav_options($extra, 'bs5');

        $headers = [];
        $items = [];
        $i = 0;
        foreach ((array) $tabs as $k => $v) {
            if ( ! is_array($v)) {
                $content = $v;
                $v = [];
            } else {
                $content = $v['content'] ?? '';
            }
            $content = trim($content ?? '');
            $_extra = (array) ($extra_by_id[$k] ?? []) + $extra;
            if (($_extra['hide_empty'] ?? null) && ! strlen($content)) {
                continue;
            }
            $name = ($v['name'] ?? null) ?: $k;
            $desc = ($v['desc'] ?? null) ?: ( ! ($_extra['no_auto_desc'] ?? null) ? ucfirst(str_replace('_', ' ', $name)) : $name);
            $id = fix_html_attr_id(preg_replace('~[^a-z0-9_-]+~i', '', ($v['id'] ?? null) ?: $links_prefix . $k));
            $is_active = isset($_extra['selected']) ? ($_extra['selected'] == $k) : (++$i == 1);
            if (isset($_extra['totals'][$name])) {
                $v['badge'] = (int) ($_extra['totals'][$name]['total'] ?? $_extra['totals'][$name]);
            }
            $badge = isset($v['badge']) ? $this->_badge($v) : '';
            $head_id = '';
            if ( ! ($_extra['no_headers'] ?? null)) {
                $class_head = ($v['class_head'] ?? null) ?: ($_extra['class_head'] ?? '');
                $class_head .= ($_extra['class_add_head'] ?? null) ? ' ' . $_extra['class_add_head'] : '';
                $_extra_head = (array) ($_extra['tab_head'] ?? []);
                $_extra_head['class'] = ($_extra_head['class'] ?? null) ?: $this->_cls('nav-item', $class_head);
                $_extra_head['attr'] = (array) ($_extra_head['attr'] ?? []) + ['role' => 'presentation'];
                if ($v['disabled'] ?? null) {
                    $link = '<a class="nav-link disabled" aria-disabled="true">';
                } else {
                    $head_id = $id . '-tab';
                    $link = '<a class="' . $this->_cls('nav-link', $is_active ? 'active' : '') . '" id="' . $head_id . '" href="#' . $id . '" data-bs-toggle="' . $nav_toggle . '" data-bs-target="#' . $id . '"'
                        . ' role="tab" aria-controls="' . $id . '" aria-selected="' . ($is_active ? 'true' : 'false') . '">';
                }
                $headers[] = '<li' . _attrs($_extra_head, ['id', 'class', 'style']) . '>' . $link . (($v['desc_raw'] ?? null) ?: t($desc)) . $badge . '</a></li>';
            }
            $class_body = ((($_extra['class'] ?? null) ?: ($v['class_body'] ?? null)) ?: ($_extra['class_body'] ?? null)) ?: 'tab-pane';
            $class_body .= ($_extra['class_add_body'] ?? null) ? ' ' . $_extra['class_add_body'] : '';
            $class_body .= ($is_active || ($_extra['show_all'] ?? null)) ? ' fade show active' : ' fade';
            $_extra_body = (array) ($_extra['tab_body'] ?? []);
            $_extra_body['id'] = ($_extra_body['id'] ?? null) ?: $id;
            $_extra_body['class'] = ($_extra_body['class'] ?? null) ?: $class_body;
            $_extra_body['attr'] = (array) ($_extra_body['attr'] ?? []) + ['role' => 'tabpanel'] + ($head_id ? ['aria-labelledby' => $head_id] : []);
            $items[] = '<div' . _attrs($_extra_body, ['id', 'class', 'style']) . '>' . $content . '</div>';
        }
        $vertical = $nav_vertical && $headers;
        $nav = $headers ? '<ul id="' . $extra['id'] . '" class="' . $this->_cls($nav_class, $vertical ? 'me-3' : '') . '" role="tablist"' . ($vertical ? ' aria-orientation="vertical"' : '') . '>' . implode(PHP_EOL, $headers) . '</ul>' : '';
        $content = '<div id="' . $extra['id'] . '_content" class="' . $this->_cls('tab-content', $vertical ? 'flex-grow-1' : '') . '">' . implode(PHP_EOL, $items) . '</div>';
        if ($vertical) {
            return '<div class="d-flex align-items-start">' . $nav . PHP_EOL . $content . '</div>';
        }
        return ($headers ? $nav . PHP_EOL : '') . $content;
    }

thumbnails

public function thumbnails($data = [], $extra = [])
    public function thumbnails($data = [], $extra = [])
    {
        $extra = (array) $extra;
        $columns = (int) ($extra['columns'] ?? 0) ?: 3;
        $col_class = 'col-md-' . (int) round(12 / $columns);
        $items = [];
        foreach ((array) $data as $v) {
            if ( ! is_array($v)) {
                $img_src = $v;
                $v = [];
            } else {
                $img_src = $v['img'] ?? '';
            }
            $head = $v['head'] ?? '';
            $body = $v['body'] ?? '';
            $class_item = ($v['class_item'] ?? null) ?: ($extra['class_item'] ?? '');
            $items[] = '<div class="' . $this->_cls($col_class, $class_item) . '"' . (($v['style'] ?? null) ? ' style="' . $v['style'] . '"' : '') . '>'
                . '<div class="card mb-3">'
                . '<img class="card-img-top" alt="' . _prepare_html(($v['alt'] ?? null) ?: $head) . '" src="' . _prepare_html($img_src) . '">'
                . (($head || $body) ? '<div class="card-body">' . ($head ? '<h5 class="card-title">' . _prepare_html($head) . '</h5>' : '') . $body . '</div>' : '')
                . '</div>'
                . '</div>';
        }
        $rows = [];
        foreach (array_chunk($items, $columns) as $_items) {
            $rows[] = '<div class="' . $this->_cls('row', $extra['class'] ?? '') . '" id="' . $this->_id('thumbnails') . '">' . implode(PHP_EOL, $_items) . '</div>';
        }
        return implode(PHP_EOL, $rows);
    }

toast

public function toast($body = '', $extra = [])

Options of yf_html::toast(). Rendered visible (`show`, also without JS); asset yf_bs5_toasts restarts it with its autohide timer.

    public function toast($body = '', $extra = [])
    {
        $p = $this->_parent;
        $extra = (array) $extra;
        $type = $p->_color_type($extra['type'] ?? '', '');
        $title = $p->_b4_text($extra['title'] ?? '', $extra);
        $has_title = strlen($title) > 0;
        $show = (bool) ($extra['show'] ?? true);
        $urgent = in_array($type, ['danger', 'warning'], true);
        $a = [
            'class' => $this->_cls('toast', $show ? 'show' : '', $has_title ? '' : 'align-items-center', $type ? 'text-bg-' . $type : '', ! $has_title && $type ? 'border-0' : '', $extra['class_add'] ?? ''),
            'id' => $extra['id'] ?? '',
            'role' => $urgent ? 'alert' : 'status',
            'aria-live' => $urgent ? 'assertive' : 'polite',
            'aria-atomic' => 'true',
            'data-bs-autohide' => (isset($extra['autohide']) && ! $extra['autohide']) ? 'false' : '',
            'data-bs-delay' => isset($extra['delay']) ? (string) (int) $extra['delay'] : '',
            'data-yf-toast' => $show ? 'show' : '',
        ] + $p->_b4_pass($extra);
        // these colours have white text, and a toast without header has its close button on it
        $white = ! $has_title && in_array($type, ['primary', 'secondary', 'success', 'danger', 'dark'], true);
        $close = ($extra['no_close'] ?? null) ? '' : '<button type="button" class="' . $this->_cls('btn-close', $white ? 'btn-close-white' : '', $has_title ? '' : 'me-2 m-auto')
            . '" data-bs-dismiss="toast" aria-label="' . t('Close') . '"></button>';
        asset('yf_bs5_toasts');
        $inner = $has_title
            ? '<div class="toast-header"><strong class="me-auto">' . $title . '</strong>' . $close . '</div><div class="toast-body">' . $body . '</div>'
            : '<div class="d-flex"><div class="toast-body">' . $body . '</div>' . $close . '</div>';
        return '<div' . $p->_b4_attrs($a) . '>' . $inner . '</div>';
    }
public function toast_container($extra = [])

Options of yf_html::toast_container().

    public function toast_container($extra = [])
    {
        $p = $this->_parent;
        $extra = (array) $extra;
        $positions = [
            'top-start' => 'top-0 start-0',
            'top-center' => 'top-0 start-50 translate-middle-x',
            'top-end' => 'top-0 end-0',
            'middle-start' => 'top-50 start-0 translate-middle-y',
            'middle-center' => 'top-50 start-50 translate-middle',
            'middle-end' => 'top-50 end-0 translate-middle-y',
            'bottom-start' => 'bottom-0 start-0',
            'bottom-center' => 'bottom-0 start-50 translate-middle-x',
            'bottom-end' => 'bottom-0 end-0',
        ];
        $position = $positions[$extra['position'] ?? ''] ?? $positions['top-end'];
        $toasts = $extra['toasts'] ?? '';
        asset('yf_bs5_toasts');
        $a = ['class' => $this->_cls('toast-container position-fixed p-3', $position, $extra['class_add'] ?? ''), 'id' => $extra['id'] ?? '', 'data-yf-toasts' => '1'] + $p->_b4_pass($extra);
        return '<div' . $p->_b4_attrs($a) . '>' . (is_array($toasts) ? implode(PHP_EOL, $toasts) : (string) $toasts) . '</div>';
    }

tooltip

public function tooltip($text = '', $extra = [])
    public function tooltip($text = '', $extra = [])
    {
        if (is_array($text)) {
            $extra = (array) $extra + $text;
            $text = '';
        }
        if ( ! is_array($extra)) {
            $extra = [];
        }
        foreach ($this->POPOVER_LEGACY_DATA as $opt) {
            if (isset($extra['data-' . $opt])) {
                $extra['data-bs-' . $opt] = ($extra['data-bs-' . $opt] ?? null) ?: $extra['data-' . $opt];
                unset($extra['data-' . $opt]);
            }
        }
        $extra['text'] = ($extra['text'] ?? null) ?: $text;
        $icon = ($extra['icon'] ?? null) ?: 'fa fa-question-circle';
        $extra['class'] = (($extra['class'] ?? null) ?: 'yf_tip') . (($extra['class_add'] ?? null) ? ' ' . $extra['class_add'] : '');
        $extra['data-bs-content'] = ($extra['data-bs-content'] ?? null) ?: _prepare_html($extra['text']);
        $extra['data-bs-toggle'] = ($extra['data-bs-toggle'] ?? null) ?: 'popover';
        $extra['data-bs-trigger'] = ($extra['data-bs-trigger'] ?? null) ?: 'hover focus';
        $extra['data-bs-container'] = ($extra['data-bs-container'] ?? null) ?: 'body';
        $extra['data-bs-html'] = ($extra['data-bs-html'] ?? null) ?: 'true';
        $extra['attr'] = (array) ($extra['attr'] ?? []) + ['tabindex' => '0'];
        asset('yf_bs5_tooltips');
        return ( ! ($extra['no_nbsp'] ?? null) ? ' ' : '') . '<span' . _attrs($extra, ['id', 'class', 'style']) . '><i class="' . $icon . '"></i></span>';
    }

well

public function well($body = '', $extra = [])
    public function well($body = '', $extra = [])
    {
        $extra = (array) $extra;
        $extra['id'] = ($extra['id'] ?? null) ?: $this->_id('well');
        return '<div class="' . $this->_cls('p-3 mb-3 bg-body-tertiary border rounded', $extra['class'] ?? '') . '" id="' . $extra['id'] . '">' . $body . '</div>';
    }

Methods starting with _ hooks and helpers; not routable as page actions

_aria_text

public function _aria_text($html)

Text of HTML for an attribute (aria-label): tags dropped, entities decoded, then escaped again with the template braces.

    public function _aria_text($html)
    {
        return _htmlchars(_escape_tpl_braces(trim(html_entity_decode(strip_tags((string) $html), ENT_QUOTES | ENT_HTML5, 'UTF-8'))));
    }

_badge

public function _badge($v)
    public function _badge($v)
    {
        return ' <span class="badge text-bg-' . (($v['class_badge'] ?? null) ?: 'info') . '">' . $v['badge'] . '</span>';
    }

_class_attr

public function _class_attr($class, &$add_str = '')

` class="$class"`, or '' with $class merged into the class attribute $add_str already has (a second class attribute would be ignored).

    public function _class_attr($class, &$add_str = '')
    {
        $add_str = (string) $add_str;
        if (preg_match('~(?:^|\s)class=(["\'])(.*?)\1~i', $add_str, $m)) {
            if ( ! in_array($class, preg_split('~\s+~', $m[2]), true)) {
                $add_str = str_replace($m[0], substr($m[0], 0, -strlen($m[2]) - 1) . trim($m[2] . ' ' . $class) . $m[1], $add_str);
            }
            return '';
        }
        return ' class="' . $class . '"';
    }

_cls

public function _cls(...$classes)

Space-joined non-empty class names.

    public function _cls(...$classes)
    {
        return implode(' ', array_filter(array_map('trim', array_map('strval', $classes)), 'strlen'));
    }

_id

public function _id($name)

Next value of the shared yf_html id counter $name, as "<name>_<n>".

    public function _id($name)
    {
        $ids = &$this->_parent->_ids;
        $ids[$name] = ($ids[$name] ?? 0) + 1;
        return $name . '_' . $ids[$name];
    }

_init

public function _init()
    public function _init()
    {
        $this->_parent = _class('html');
    }
public function _modal_dialog_class($extra)

Classes of .modal-dialog: size (sm, lg, xl), fullscreen (true, or a breakpoint sm...xxl: fullscreen below it), centered, scrollable.

  • $extra array
    public function _modal_dialog_class($extra)
    {
        $size = in_array($extra['size'] ?? '', ['sm', 'lg', 'xl'], true) ? 'modal-' . $extra['size'] : '';
        $fs = $extra['fullscreen'] ?? null;
        $bp = is_string($fs) ? preg_replace('~-down$~', '', $fs) : '';
        $fs = in_array($bp, ['sm', 'md', 'lg', 'xl', 'xxl'], true) ? 'modal-fullscreen-' . $bp . '-down' : (in_array($fs, [true, 1, '1'], true) ? 'modal-fullscreen' : '');
        return $this->_cls('modal-dialog', $size, $fs, ! empty($extra['centered']) ? 'modal-dialog-centered' : '', ! empty($extra['scrollable']) ? 'modal-dialog-scrollable' : '');
    }
public function _pagination_item($key, $link, $text, $extra)

One pagination() item: a link, the current page (`current`) or a disabled entry (`disabled`).

  • $key int|string
  • $link string
  • $text string
  • $extra array
    public function _pagination_item($key, $link, $text, $extra)
    {
        $state = ($this->_parent ?: html())->_pagination_state($key, $extra);
        if ($state === 'disabled') {
            return '<li class="page-item disabled"><span class="page-link">' . $text . '</span></li>';
        }
        if ($state === 'active') {
            return '<li class="page-item active" aria-current="page"><a class="page-link" href="' . $link . '">' . $text . '</a></li>';
        }
        return '<li class="page-item"><a class="page-link" href="' . $link . '">' . $text . '</a></li>';
    }