434 lines
14 KiB
PHP
434 lines
14 KiB
PHP
|
<?php
|
||
|
namespace nur\v\bs3;
|
||
|
|
||
|
use nur\A;
|
||
|
use nur\b\UserException;
|
||
|
use nur\b\ValueException;
|
||
|
use nur\md;
|
||
|
use nur\str;
|
||
|
use nur\v\base\TagsManager;
|
||
|
use nur\v\fo;
|
||
|
use nur\v\model\IFormManager;
|
||
|
use nur\v\model\INavbarManager;
|
||
|
use nur\v\page;
|
||
|
use nur\v\v;
|
||
|
|
||
|
class Bs3NavbarManager extends TagsManager implements INavbarManager {
|
||
|
const TYPE_FIXED = "fixed-top";
|
||
|
const TYPE_STATIC = "static-top";
|
||
|
const TYPE_NORMAL = "normal";
|
||
|
|
||
|
static final function get_class_style(?string $navbar): array {
|
||
|
if ($navbar === null) $navbar = self::TYPE_NORMAL;
|
||
|
$class = false;
|
||
|
$style = false;
|
||
|
switch ($navbar) {
|
||
|
case "fixed-top":
|
||
|
case "fixed":
|
||
|
case "f":
|
||
|
$class = "navbar-fixed-top";
|
||
|
$style = '<style>body { margin-top: 70px; }</style>';
|
||
|
break;
|
||
|
case "static-top":
|
||
|
case "static":
|
||
|
case "s":
|
||
|
$class = "navbar-static-top";
|
||
|
break;
|
||
|
case "normal":
|
||
|
case "n":
|
||
|
break;
|
||
|
default:
|
||
|
throw new ValueException("$navbar: invalid navbar type");
|
||
|
}
|
||
|
return [$class, $style];
|
||
|
}
|
||
|
|
||
|
private static final function get_align_class($align): ?string {
|
||
|
if ($align === null) return null;
|
||
|
switch ($align) {
|
||
|
case "navbar-left":
|
||
|
case "align-left":
|
||
|
case "left":
|
||
|
case "l":
|
||
|
$align_class = "navbar-left";
|
||
|
break;
|
||
|
case "navbar-right":
|
||
|
case "align-right":
|
||
|
case "right":
|
||
|
case "r":
|
||
|
$align_class = "navbar-right";
|
||
|
break;
|
||
|
default:
|
||
|
throw new UserException("$align: invalid align");
|
||
|
}
|
||
|
return $align_class;
|
||
|
}
|
||
|
|
||
|
const NAVBAR_OPTIONS_SCHEMA = [
|
||
|
"navbar" => ["?string", self::TYPE_NORMAL, "type de barre de navigation"],
|
||
|
"container" => ["?string", _container::CONTAINER_NORMAL, "type de container"],
|
||
|
"brand" => ["?string", null, "Marque"],
|
||
|
"show_brand" => [null, true, "Faut-il afficher la marque?"],
|
||
|
"class" => ["?string", null, "classe CSS"],
|
||
|
];
|
||
|
|
||
|
protected $formManager;
|
||
|
|
||
|
function setFormManager(IFormManager $formManager): void {
|
||
|
$this->formManager = $formManager;
|
||
|
}
|
||
|
|
||
|
function getFormManager(): IFormManager {
|
||
|
$formManager = $this->formManager;
|
||
|
if ($formManager !== null) return $formManager;
|
||
|
else return fo::manager();
|
||
|
}
|
||
|
|
||
|
function start($options=null): array {
|
||
|
md::ensure_schema($options, self::NAVBAR_OPTIONS_SCHEMA);
|
||
|
|
||
|
[$navbarClass, $navbarStyle] = self::get_class_style($options["navbar"]);
|
||
|
$class = $options["class"];
|
||
|
$containerClass = _container::get_class($options["container"]);
|
||
|
|
||
|
$brand = $options["brand"];
|
||
|
$showBrand = $options["show_brand"];
|
||
|
if (!$brand) {
|
||
|
$brand = false;
|
||
|
} elseif ($showBrand && $showBrand !== "asis") {
|
||
|
$brand = v::span(["class" => "navbar-brand", $brand]);
|
||
|
}
|
||
|
|
||
|
$start = [
|
||
|
$navbarStyle,
|
||
|
v::sdiv([
|
||
|
"class" => ["navbar navbar-default", $navbarClass, $class],
|
||
|
"role" => "navigation",
|
||
|
]),
|
||
|
v::sdiv(["class" => $containerClass]),
|
||
|
v::div([
|
||
|
"class" => "navbar-header",
|
||
|
v::tag("button", [
|
||
|
"class" => "navbar-toggle",
|
||
|
"data-toggle" => "collapse",
|
||
|
"data-target" => ".navbar-collapse",
|
||
|
v::span(["class" => "sr-only", "Toggle navigation"]),
|
||
|
v::span(["class" => "icon-bar"]),
|
||
|
v::span(["class" => "icon-bar"]),
|
||
|
v::span(["class" => "icon-bar"]),
|
||
|
]),
|
||
|
$brand,
|
||
|
]),
|
||
|
v::sdiv(["class" => "navbar-collapse collapse"]),
|
||
|
];
|
||
|
$end = [
|
||
|
v::ediv(),
|
||
|
v::ediv(),
|
||
|
v::ediv(),
|
||
|
];
|
||
|
$content = $this->push("navbar", $start, $end);
|
||
|
#$this->getFormManager()->push(["type" => Bs3FormManager::TYPE_NAVBAR]);
|
||
|
return $content;
|
||
|
}
|
||
|
|
||
|
function end(): array {
|
||
|
$content = $this->pop("navbar");
|
||
|
#$this->getFormManager()->pop();
|
||
|
return $content;
|
||
|
}
|
||
|
|
||
|
function started(): bool {
|
||
|
return $this->haveMark("navbar");
|
||
|
}
|
||
|
|
||
|
const NAV_OPTIONS_SCHEMA = [
|
||
|
"type" => ["?string", "nav", "type de section"],
|
||
|
"align" => ["?string", "left", "alignement de la section"],
|
||
|
"class" => ["?string", null, "classe CSS"],
|
||
|
];
|
||
|
|
||
|
function startNav($options=null): array {
|
||
|
md::ensure_schema($options, self::NAV_OPTIONS_SCHEMA);
|
||
|
$type = $options["type"];
|
||
|
switch ($type) {
|
||
|
case "nav":
|
||
|
case "n":
|
||
|
$typeClass = "navbar-nav";
|
||
|
break;
|
||
|
case "form":
|
||
|
case "f":
|
||
|
$typeClass = "navbar-form";
|
||
|
break;
|
||
|
default:
|
||
|
throw new UserException("$type: invalid type");
|
||
|
}
|
||
|
$alignClass = self::get_align_class($options["align"]);
|
||
|
$class = $options["class"];
|
||
|
$start = v::sul([
|
||
|
"class" => ["nav", $typeClass, $alignClass, $class],
|
||
|
]);
|
||
|
$end = v::eul();
|
||
|
|
||
|
$content = null;
|
||
|
A::append_nn($content, $this->pop("nav"));
|
||
|
A::append_nn($content, $this->push("nav", $start, $end));
|
||
|
return $content;
|
||
|
}
|
||
|
|
||
|
const NAV_ITEM_SCHEMA = [
|
||
|
"value" => [null, null, "la valeur de l'élément"],
|
||
|
"item" => ["string", "text", "type d'élément"],
|
||
|
];
|
||
|
|
||
|
const ITEM_BRAND = "brand";
|
||
|
const ITEM_TEXT = "text";
|
||
|
const ITEM_LINK = "link";
|
||
|
const ITEM_BUTTON = "button";
|
||
|
const ITEM_MENU = "menu";
|
||
|
const ITEM_LITERAL = "literal";
|
||
|
|
||
|
function navItem($item): array {
|
||
|
md::ensure_schema($item, self::NAV_ITEM_SCHEMA);
|
||
|
$type = $item["item"];
|
||
|
$inNav = $this->haveMark("nav");
|
||
|
switch ($type) {
|
||
|
case self::ITEM_BRAND:
|
||
|
return $this->navBrand($item, $inNav);
|
||
|
case self::ITEM_TEXT:
|
||
|
case "t":
|
||
|
#$item["type"] = self::ITEM_TEXT;
|
||
|
return $this->navText($item, $inNav);
|
||
|
case self::ITEM_LINK:
|
||
|
case "l":
|
||
|
$item["type"] = self::ITEM_LINK;
|
||
|
return $this->navLink($item);
|
||
|
case self::ITEM_BUTTON:
|
||
|
case "b":
|
||
|
$item["type"] = self::ITEM_BUTTON;
|
||
|
if (!$inNav) return $this->navLink($item);
|
||
|
break;
|
||
|
case self::ITEM_MENU:
|
||
|
case "m":
|
||
|
#$item["type"] = self::ITEM_MENU;
|
||
|
return $this->navMenu($item, $inNav);
|
||
|
case self::ITEM_LITERAL:
|
||
|
case "asis":
|
||
|
#$item["type"] = self::ITEM_LITERAL;
|
||
|
return $item["value"];
|
||
|
}
|
||
|
throw new ValueException("$type: invalid item type");
|
||
|
}
|
||
|
|
||
|
const NAV_BRAND_SCHEMA = [
|
||
|
"value" => [null, null, "la marque à afficher"],
|
||
|
"class" => ["?array", null, "classe CSS de l'élément"],
|
||
|
# class: type ?array pour le moment, à remplacer par "?items"
|
||
|
"style" => ["?string", null, "style CSS de l'élément"],
|
||
|
"tag" => ["?string", null, "tag à utiliser pour l'élément"],
|
||
|
"attrs" => ["?array", null, "attributs HTML génériques de l'élément"],
|
||
|
"brand" => [null, null, "la marque à afficher"],
|
||
|
# hors nav
|
||
|
"align" => ["?string", null, "alignement de l'élément"],
|
||
|
];
|
||
|
|
||
|
function navBrand($brand, bool $inNav): array {
|
||
|
md::ensure_schema($brand, self::NAV_BRAND_SCHEMA);
|
||
|
A::replace_n_indirect($brand, "value", "brand");
|
||
|
$tag = $brand["tag"];
|
||
|
if ($tag === null) $tag = $inNav? "li": "span";
|
||
|
$alignClass = self::get_align_class($brand["align"]);
|
||
|
return v::tag($tag, [
|
||
|
"class" => ["navbar-brand", $alignClass, $brand["class"]],
|
||
|
"style" => $brand["style"],
|
||
|
$brand["attrs"],
|
||
|
q($brand["value"]),
|
||
|
]);
|
||
|
}
|
||
|
|
||
|
const NAV_TEXT_SCHEMA = [
|
||
|
"value" => [null, null, "le texte à afficher"],
|
||
|
"class" => ["?array", null, "classe CSS de l'élément"],
|
||
|
# class: type ?array pour le moment, à remplacer par "?items"
|
||
|
"style" => ["?string", null, "style CSS de l'élément"],
|
||
|
"tag" => ["?string", null, "tag à utiliser pour l'élément"],
|
||
|
"attrs" => ["?array", null, "attributs HTML génériques de l'élément"],
|
||
|
"text" => [null, null, "le texte à afficher"],
|
||
|
# hors nav
|
||
|
"align" => ["?string", null, "alignement de l'élément"],
|
||
|
];
|
||
|
|
||
|
function navText($text, bool $inNav): array {
|
||
|
md::ensure_schema($text, self::NAV_TEXT_SCHEMA);
|
||
|
A::replace_n_indirect($text, "value", "text");
|
||
|
$tag = $text["tag"];
|
||
|
if ($tag === null) $tag = $inNav? "li": "p";
|
||
|
$alignClass = self::get_align_class($text["align"]);
|
||
|
return v::tag($tag, [
|
||
|
"class" => ["navbar-text", $alignClass, $text["class"]],
|
||
|
"style" => $text["style"],
|
||
|
$text["attrs"],
|
||
|
q($text["value"]),
|
||
|
]);
|
||
|
}
|
||
|
|
||
|
const NAV_LINK_SCHEMA = [
|
||
|
"url" => ["?string", null, "l'url du lien, à adapter avec page::bu()"],
|
||
|
"value" => [null, null, "le texte à afficher"],
|
||
|
"active" => ["?bool", false, "le lien est-il actif?"],
|
||
|
"show_active" => ["?bool", false, "faut-il afficher le lien comme actif (même s'il n'est pas actif)?"],
|
||
|
"accesskey" => ["?string", null, "touche d'accès rapide"],
|
||
|
"target" => ["?string", null, "destination du lien"],
|
||
|
"params" => ["?array", null, "des paramètres à ajouter à l'url le cas échéant"],
|
||
|
"class" => ["?array", null, "classe CSS du lien"],
|
||
|
# class: type ?array pour le moment, à remplacer par "?items"
|
||
|
"style" => ["?string", null, "style CSS du lien"],
|
||
|
"attrs" => ["?array", null, "attributs HTML génériques du lien"],
|
||
|
"text" => [null, null, "le texte à afficher"],
|
||
|
"literal_url" => ["?string", null, "l'url du lien, à utiliser sans modification"],
|
||
|
# hors nav, i.e type button
|
||
|
"align" => ["?string", null, "alignement de l'élément"],
|
||
|
];
|
||
|
|
||
|
private static function ensure_link(array &$link): void {
|
||
|
A::replace_n_indirect($link, "value", "text");
|
||
|
A::replace_n_indirect($link, "value", "url");
|
||
|
}
|
||
|
private static function is_sep(array $link): bool {
|
||
|
$value = $link["value"];
|
||
|
$url = $link["url"];
|
||
|
if ($url !== null) $sep = $url;
|
||
|
elseif ($value !== null) $sep = $value;
|
||
|
else $sep = null;
|
||
|
if (is_array($sep)) $sep = str::join("", $sep);
|
||
|
$literal_url = $link["literal_url"];
|
||
|
return $sep !== null && $literal_url == null && preg_match('/^-{3,}/', $sep);
|
||
|
}
|
||
|
private function _navSep(array $link): array {
|
||
|
return v::li([
|
||
|
"role" => "separator",
|
||
|
"class" => "divider",
|
||
|
$link["attrs"],
|
||
|
]);
|
||
|
}
|
||
|
private function _navLinkShared(array $link): array {
|
||
|
$url = $link["url"];
|
||
|
$params = $link["params"];
|
||
|
$literal_url = $link["literal_url"];
|
||
|
if ($literal_url !== null) $url = $literal_url;
|
||
|
elseif ($url === null) $url = "#";
|
||
|
else $url = page::bu($url, $params);
|
||
|
|
||
|
return v::a([
|
||
|
"href" => $url,
|
||
|
"accesskey" => $link["accesskey"],
|
||
|
"target" => $link["target"],
|
||
|
"class" => $link["class"],
|
||
|
"style" => $link["style"],
|
||
|
$link["attrs"],
|
||
|
q($link["value"]),
|
||
|
]);
|
||
|
}
|
||
|
private function _navLink(array $link): array {
|
||
|
return v::li([
|
||
|
"class" => ["active" => $link["active"] || $link["show_active"]],
|
||
|
$this->_navLinkShared($link),
|
||
|
]);
|
||
|
}
|
||
|
private function _navButton(array $button): array {
|
||
|
$a = $this->_navLinkShared($button);
|
||
|
return $a;
|
||
|
}
|
||
|
|
||
|
function navLink($link): array {
|
||
|
md::ensure_schema($link, self::NAV_LINK_SCHEMA);
|
||
|
self::ensure_link($link);
|
||
|
switch ($link["type"]) {
|
||
|
case self::ITEM_LINK:
|
||
|
if (self::is_sep($link)) return $this->_navSep($link);
|
||
|
else return $this->_navLink($link);
|
||
|
case self::ITEM_BUTTON:
|
||
|
$alignClass = self::get_align_class($link["align"]);
|
||
|
$active = $link["active"];
|
||
|
$class = $link["class"];
|
||
|
if ($active !== null) {
|
||
|
$activeClass = $active ? "btn-primary" : "btn-default";
|
||
|
} elseif ($class === null) {
|
||
|
$activeClass = "btn-default";
|
||
|
} else {
|
||
|
$activeClass = null;
|
||
|
}
|
||
|
$link["class"] = ["navbar-btn btn", $activeClass, $alignClass, $class];
|
||
|
return $this->_navButton($link);
|
||
|
default:
|
||
|
return [];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const NAV_MENU_SCHEMA = [
|
||
|
"value" => [null, null, "le texte à afficher"],
|
||
|
"links" => ["?array[]", null, "les liens à afficher"],
|
||
|
"active" => ["?bool", null, "le lien est-il actif?"],
|
||
|
"class" => ["?array", null, "classe CSS du lien"],
|
||
|
# class: type ?array pour le moment, à remplacer par "?items"
|
||
|
"style" => ["?string", null, "style CSS du lien"],
|
||
|
"attrs" => ["?array", null, "attributs HTML génériques du lien"],
|
||
|
"text" => [null, null, "le texte à afficher"],
|
||
|
# hors nav
|
||
|
"align" => ["?string", null, "alignement de l'élément"],
|
||
|
];
|
||
|
|
||
|
function navMenu($menu): array {
|
||
|
md::ensure_schema($menu, self::NAV_MENU_SCHEMA);
|
||
|
A::replace_n_indirect($menu, "value", "text");
|
||
|
$active = $menu["active"];
|
||
|
$navLinks = [];
|
||
|
foreach ($menu["links"] as $link) {
|
||
|
$itemType = A::get($link, "item");
|
||
|
if ($itemType == self::ITEM_TEXT) {
|
||
|
# support minimal du type texte
|
||
|
$text = $link;
|
||
|
md::ensure_schema($text, self::NAV_TEXT_SCHEMA);
|
||
|
$navLinks[] = v::p([
|
||
|
"class" => ["navbar-text", $text["class"]],
|
||
|
"style" => $text["style"],
|
||
|
$text["attrs"],
|
||
|
q($text["value"]),
|
||
|
]);
|
||
|
} elseif ($itemType == self::ITEM_LITERAL) {
|
||
|
# support minimal du type literal
|
||
|
$literal = $link;
|
||
|
md::ensure_schema($literal, self::NAV_ITEM_SCHEMA);
|
||
|
$navLinks[] = $literal["value"];
|
||
|
} else {
|
||
|
md::ensure_schema($link, self::NAV_LINK_SCHEMA);
|
||
|
self::ensure_link($link);
|
||
|
if (self::is_sep($link)) {
|
||
|
$navLinks[] = $this->_navSep($link);
|
||
|
} else {
|
||
|
$navLinks[] = $this->_navLink($link);
|
||
|
}
|
||
|
if ($active === null && $link["active"]) {
|
||
|
$active = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return v::li([
|
||
|
"class" => ["dropdown", $active? "active": null],
|
||
|
v::a([
|
||
|
"href" => "#", "role" => "button",
|
||
|
"class" => "dropdown-toggle", "data-toggle" => "dropdown",
|
||
|
"aria-haspopup" => "true", "aria-expanded" => "false",
|
||
|
q($menu["value"]),
|
||
|
" <span class=\"caret\"></span>",
|
||
|
]),
|
||
|
v::ul(["class" => "dropdown-menu", $navLinks]),
|
||
|
]);
|
||
|
}
|
||
|
|
||
|
function endNav(): array {
|
||
|
$endnav = $this->pop("nav");
|
||
|
if ($endnav === null) $endnav = [];
|
||
|
return $endnav;
|
||
|
}
|
||
|
}
|