body { margin-top: 70px; }'; 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"]), " ", ]), v::ul(["class" => "dropdown-menu", $navLinks]), ]); } function endNav(): array { $endnav = $this->pop("nav"); if ($endnav === null) $endnav = []; return $endnav; } }