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 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;
 | 
						|
  }
 | 
						|
}
 |