'acme-dashboard']; * * protected $tag = 'div'; * * protected function assemble() * { * // ... * $this->add($content); * } * } * ``` */ abstract class BaseHtmlElement extends HtmlDocument { /** * List of void elements which must not contain end tags or content * * If {@link $isVoid} is null, this property should be used to decide whether the content and end tag has to be * rendered. * * @var array * * @see https://www.w3.org/TR/html5/syntax.html#void-elements */ protected static $voidElements = [ 'area' => 1, 'base' => 1, 'br' => 1, 'col' => 1, 'embed' => 1, 'hr' => 1, 'img' => 1, 'input' => 1, 'link' => 1, 'meta' => 1, 'param' => 1, 'source' => 1, 'track' => 1, 'wbr' => 1 ]; /** @var Attributes */ protected $attributes; /** @var bool Whether possible attribute callbacks have been registered */ protected $attributeCallbacksRegistered = false; /** @var bool|null Whether the element is void. If null, void check should use {@link $voidElements} */ protected $isVoid; /** @var array You may want to set default attributes when extending this class */ protected $defaultAttributes; /** @var string Tag of element. Set this property in order to provide the element's tag when extending this class */ protected $tag; /** * Get the attributes of the element * * @return Attributes */ public function getAttributes() { if ($this->attributes === null) { $default = $this->getDefaultAttributes(); if (empty($default)) { $this->attributes = new Attributes(); } else { $this->attributes = Attributes::wantAttributes($default); } $this->ensureAttributeCallbacksRegistered(); } return $this->attributes; } /** * Set the attributes of the element * * @param Attributes|array|null $attributes * * @return $this */ public function setAttributes($attributes) { $this->attributes = Attributes::wantAttributes($attributes); $this->attributeCallbacksRegistered = false; $this->ensureAttributeCallbacksRegistered(); return $this; } /** * Set the attribute with the given name and value * * If the attribute with the given name already exists, it gets overridden. * * @param string $name The name of the attribute * @param string|bool|array $value The value of the attribute * * @return $this */ public function setAttribute($name, $value) { $this->getAttributes()->set($name, $value); return $this; } /** * Add the given attributes * * @param Attributes|array $attributes * * @return $this */ public function addAttributes($attributes) { $this->getAttributes()->add($attributes); return $this; } /** * Get the default attributes of the element * * @return array */ public function getDefaultAttributes() { return $this->defaultAttributes; } /** * Get the tag of the element * * Since HTML Elements must have a tag, this method throws an exception if the element does not have a tag. * * @return string * * @throws RuntimeException If the element does not have a tag */ final public function getTag() { $tag = $this->tag(); if (! $tag) { throw new RuntimeException('Element must have a tag'); } return $tag; } /** * Set the tag of the element * * @param string $tag * * @return $this */ public function setTag($tag) { $this->tag = $tag; return $this; } /** * Get whether the element is void * * The default void detection which checks whether the element's tag is in the list of void elements according to * https://www.w3.org/TR/html5/syntax.html#void-elements. * * If you want to override this behavior, use {@link setVoid()}. * * @return bool */ public function isVoid() { if ($this->isVoid !== null) { return $this->isVoid; } $tag = $this->getTag(); return isset(self::$voidElements[$tag]); } /** * Set whether the element is void * * You may use this method to override the default void detection which checks whether the element's tag is in the * list of void elements according to https://www.w3.org/TR/html5/syntax.html#void-elements. * * If you specify null, void detection is reset to its default behavior. * * @param bool|null $void * * @return $this */ public function setVoid($void = true) { $this->isVoid = $void === null ?: (bool) $void; return $this; } /** * Ensure that possible attribute callbacks have been registered * * Note that this method is automatically called in {@link getAttributes()} and {@link setAttributes()}. * * @return $this */ public function ensureAttributeCallbacksRegistered() { if (! $this->attributeCallbacksRegistered) { $this->attributeCallbacksRegistered = true; $this->registerAttributeCallbacks($this->attributes); } return $this; } /** * Render the content of the element to HTML * * @return string */ public function renderContent() { return parent::renderUnwrapped(); } /** * Get whether the closing tag should be rendered * * @return bool True for void elements, false otherwise */ public function wantsClosingTag() { // TODO: There is more. SVG and MathML namespaces return ! $this->isVoid(); } /** * Use this element to wrap the given document * * @param HtmlDocument $document * * @return $this */ public function wrap(HtmlDocument $document) { $document->addWrapper($this); return $this; } /** * Internal method for accessing the tag * * You may override this method in order to provide the tag dynamically * * @return string */ protected function tag() { return $this->tag; } /** * Register attribute callbacks * * Override this method in order to register attribute callbacks in concrete classes. */ protected function registerAttributeCallbacks(Attributes $attributes) { } public function addHtml(ValidHtml ...$content) { $this->ensureAssembled(); parent::addHtml(...$content); return $this; } /** * @inheritdoc * * @throws RuntimeException If the element does not have a tag or is void but has content */ public function renderUnwrapped() { $this->ensureAssembled(); $tag = $this->getTag(); $content = $this->renderContent(); $attributes = $this->getAttributes()->render(); if (strlen($this->contentSeparator)) { $length = strlen($content); if ($length > 0) { if ($content[0] === '<') { $content = $this->contentSeparator . $content; $length++; } if ($content[$length - 1] === '>') { $content .= $this->contentSeparator; } } } if (! $this->wantsClosingTag()) { if (strlen($content)) { throw new RuntimeException('Void elements must not have content'); } return sprintf('<%s%s />', $tag, $attributes); } return sprintf( '<%s%s>%s', $tag, $attributes, $content, $tag ); } }