<?php declare(strict_types=1); namespace Arcanedev\Html\Elements; use Arcanedev\Html\Contracts\Elements\HtmlElement as HtmlElementContract; use Arcanedev\Html\Exceptions\{InvalidHtmlException, MissingTagException}; use Illuminate\Support\{Collection, HtmlString, Str}; use Illuminate\Support\Traits\Macroable; /** * Class HtmlElement * * @author ARCANEDEV <arcanedev.maroc@gmail.com> * * @method \Arcanedev\Html\Elements\HtmlElement|mixed attributeIf(bool $condition, string $attribute, mixed $value = null) * @method \Arcanedev\Html\Elements\HtmlElement|mixed attributeUnless(bool $condition, string $attribute, mixed $value = null) * @method \Arcanedev\Html\Elements\HtmlElement|mixed attributeIfNotNull(mixed $valueToCheck, string $attribute, mixed $value = null) */ abstract class HtmlElement implements HtmlElementContract { /* ----------------------------------------------------------------- | Traits | ----------------------------------------------------------------- */ use Concerns\HasAttributes, Concerns\HasChildElements, Concerns\HasConditionalMethods, Macroable { __call as __callMacro; } /* ----------------------------------------------------------------- | Properties | ----------------------------------------------------------------- */ /** * The tag type. * * @var string */ protected $tag; /* ----------------------------------------------------------------- | Constructor | ----------------------------------------------------------------- */ /** * HtmlElement constructor. */ public function __construct() { $this->initAttributes(); $this->initChildren(); } /** * Make a html element. * * @return $this */ public static function make() { return new static; } /* ----------------------------------------------------------------- | Setters & Getters | ----------------------------------------------------------------- */ /** * Get the tag type. * * @return string * * @throws \Arcanedev\Html\Exceptions\MissingTagException */ protected function getTag() { if (empty($this->tag)) { throw MissingTagException::onClass(static::class); } return $this->tag; } /** * Set an id attribute. * * @param string $id * * @return $this */ public function id($id) { return $this->attribute('id', $id); } /** * Get the class attribute. * * @return \Arcanedev\Html\Entities\Attributes\ClassAttribute */ public function classList() { return $this->getAttributes()->classList(); } /** * Add a class (alias). * * @param iterable|string $class * * @return $this */ public function class($class) { return tap(clone $this, function (HtmlElement $elt) use ($class) { $elt->getAttributes()->addClass($class); }); } /** * Push a class to the list. * * @param string $class * * @return $this */ public function pushClass($class) { return tap(clone $this, function (HtmlElement $elt) use ($class) { $elt->classList()->push($class); }); } /** * Set the style attribute. * * @param array|string $style * * @return $this */ public function style($style) { if (is_array($style)) { $style = implode('; ', array_map(function ($value, $attribute) { return "{$attribute}: {$value}"; }, $style, array_keys($style))); } return $this->attribute('style', $style); } /** * Set the data attribute. * * @param array|string $name * @param mixed $value * * @return $this */ public function data($name, $value = null) { $attributes = Collection::make( is_array($name) ? $name : [$name => $value] )->mapWithKeys(function ($mapValue, $mapKey) { return ["data-{$mapKey}" => $mapValue]; }); return $this->attributes($attributes); } /** * Set the text. * * @param string $text * @param bool $doubleEncode * * @return $this */ public function text($text, $doubleEncode = true) { return $this->html(e($text, $doubleEncode)); } /** * Add an html child/children. * * @param string|null $html * * @return $this * * @throws \Arcanedev\Html\Exceptions\InvalidHtmlException */ public function html($html) { if ($this->isVoidElement()) { throw InvalidHtmlException::onTag($this->getTag()); } return $this->setNewChildren($html); } /** * Open the html element. * * @return \Illuminate\Support\HtmlString */ public function open() { $attributes = $this->getAttributes(); $html = $attributes->isNotEmpty() ? "<{$this->getTag()} {$attributes->render()}>" : "<{$this->getTag()}>"; return new HtmlString( $html . $this->getChildren()->toHtml() ); } /** * Close the html element. * * @return \Illuminate\Support\HtmlString */ public function close() { return new HtmlString( $this->isVoidElement() ? '' : "</{$this->getTag()}>" ); } /** * Render the element to HtmlString object. * * @return \Illuminate\Support\HtmlString */ public function render() { return new HtmlString($this->toHtml()); } /** * Render the element to string. * * @return string */ public function toHtml() { return $this->open()->toHtml(). $this->close()->toHtml(); } /* ----------------------------------------------------------------- | Check Methods | ----------------------------------------------------------------- */ /** * Check if the tag is a void element. * * @return bool */ public function isVoidElement(): bool { return in_array($this->getTag(), [ 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr', ]); } /* ----------------------------------------------------------------- | Magic Methods | ----------------------------------------------------------------- */ /** * Render the element to string (magic method). * * @return string */ public function __toString() { return $this->toHtml(); } /** * Dynamically handle calls to the class. * Check for methods finishing by If or fallback to Macroable. * * @param string $name * @param array $arguments * * @return mixed */ public function __call($name, array $arguments = []) { if (Str::endsWith($name, $this->supportedConditions)) { foreach ($this->supportedConditions as $condition) { if (method_exists($this, $method = str_replace($condition, '', $name))) { return $this->callConditionalMethod($condition, $method, $arguments); } } } return $this->__callMacro($name, $arguments); } /** * Clone the object. */ public function __clone() { $this->attributes = clone $this->attributes; $this->children = clone $this->children; } }