[], 'class' => [], ]; public array $childs = []; /** * supported options are for now : * * key value uses * -------------------------------------------------------------------------------------------- * tag-type single Only render a single tag. Cannot contains any childrens * force-tag-open tag Control the way the opening tag is rendered * force-tag-close tag-close " " " " closing " " " * no-attr Attributes will not be rendered * no-id ID is not automatically given (* may be removed *) * escape-tag-end If you need to echo the Node inside another string, it may be useful to escape the "/" char using "\/" */ public array $options = []; public $selected = null; public string $content = ""; protected ?UiQuery $kwery = null; public function __construct(? string $tag = null) { if ( ! static::$config ) { static::pushConfigArray( include(dirname(__FILE__) . "/taglist.php") ); } if ($tag !== null) { $this->tag = $tag; } } public static function pushConfigArray(array $array) : void { static::$config = array_replace_recursive(static::$config, $array); } public static function createStylesheet(string $href, $attributes = [], $options = []) : self { return static::create('link', $attributes + [ 'href' => $href, 'rel' => "stylesheet", 'type' => "text/css" ], [ 'tag-type' => 'single' ] + $options); } public static function createScript(string $src, array $attributes = [], array $options = []) : self { return static::create('script', $attributes + [ 'src' => $src, 'type' => 'text/javascript', ], $options); } public static function createPhp(array $attributes = [], array $options = []) : self { return static::create('', $attributes, [ 'force-tag-open' => ' ' ?>' ] + $options); } public static function createPhpEcho(array $attributes = [], array $options = []) : self { return static::create('', $attributes, [ 'force-tag-open' => ' ' ?>' ] + $options); } public static function createSpan(string $text, array $attributes = [], array $options = []) : self { return static::create('span', $attributes, $options)->text($text); } public static function create(string $tag = "div", array $attributes = [], array $options = []) : self { $obj = new static(); if ( strpos($tag, '.') !== false ) { $obj->attributes['class'] = explode('.', $tag); $tag = array_shift($obj->attributes['class']); } $obj->tag = $tag; $obj->name = $name; if ( $attributes ) { $obj->attributes($attributes); } if ( false !== ( $custom = static::$config["tags"][$tag] ?? false ) ) { if ( $custom['tag'] ?? false ) { $obj->tag = $custom['tag']; } if ( $custom['attributes'] ?? false ) { $obj->attributes($custom['attributes']); } if ( $custom['options'] ?? false ) { $obj->options = array_replace_recursive($obj->options, $custom['options']); } } return $obj; } public function createIn(string $tag, array $attributes = []) : self { $element = static::create($tag, $attributes); $this->append($element); return $element; } public function addClass(string $classname) : self { $this->attributes['class'] = $classname; return $this; } public function removeClass($classname) { if ( false !== $key = array_search(strtolower($classname), array_map('strtolower', $this->attributes['class'])) ) { unset($this->attributes['class'][$key]); } return $this; } public function option(string $var, $value = 1) : self { $this->options[$var] = $value; return $this; } public function render() : string { $attributesList = []; foreach ( $this->attributes as $key => $value ) { if ( is_array($value) ) { if ( empty($value) ) continue; if ($key === 'style') { $style = []; foreach($value as $k2 => $v2 ) { $style[] = "$k2:$v2"; } $attributesList[] = "$key=\"".implode(';', $style).'"'; } else { $attributesList[] = implode(' ', $value); } } else if ( !is_numeric($key) ) { $value = htmlspecialchars($value); # will output something like $attributesList[] = "$key=\"$value\""; } else { # will output something like $attributesList[] = $value; } } $content = ""; foreach ( $this->childs as $item ) { if ( is_object($item) ) { $content .= $item->render(); } else if ( is_string($item) ) { $content .= $item; } } if ( in_array('no-tag', $this->options, true) ) { return $this->content . $content; } else { $compiledAttributes = (count($attributesList) && ! in_array('no-attr', $this->options, true) ? " " . implode(" ", $attributesList) : ""); # Force the node to contain a certain opening tag (php is a good example) if ( in_array('force-tag-open', $this->options, true) ) { $opentag = $this->options['force-tag-open'] . $compiledAttributes; } else { $opentag = $this->tag ? "<{$this->tag}" . $compiledAttributes . ">" : ""; } if ( $this->options['tag-type'] ?? false ) { if ( $this->options['tag-type'] === "single" ) { $closetag = ""; } } elseif ( in_array('force-tag-close', $this->options, true) ) { $closetag = $this->options['force-tag-close']; } else { $closetag = $this->tag ? "<" . (in_array('escape-tag-end', $this->options) ? "\/" : "/" ) . "{$this->tag}>" : ""; } return $opentag . $this->content . $content . $closetag; } } public function html($set = null) { if ($set !== null) { $this->content = $set; return $this; } return $this->content; } public function text(?string $set = null) { if ($set !== null) { $this->content = htmlspecialchars( $set, \ENT_NOQUOTES ); return $this; } return $this->content; } public function append( ...$arguments ) : self { return $this->insert( static::INSERT_MODE_APPEND, ...$arguments); } public function prepend( ...$arguments ) : self { return $this->insert( static::INSERT_MODE_PREPEND, ...( is_array($arguments) ? array_reverse($arguments) : $arguments) ); } protected function insert(int $mode, ...$elements) : self { foreach($elements as $item) { switch($mode) { case static::INSERT_MODE_APPEND: array_push($this->childs, $item); break; case static::INSERT_MODE_PREPEND: array_unshift($this->childs, $item); break; } } return $this; } public function attributes(array $attributes) : self { foreach($attributes as $key => $value) { switch ((string) $key) { case 'class': $this->addClass($value); break; case 'style' : if ( is_string($value) ) { foreach(array_filter(explode(";", $value)) as $vars) { list($key, $value) = explode(':', $vars); $this->css($key, $value); } } elseif ( is_array($value) ) { foreach($value as $var => $val) { $this->css($var, $val); } } break; default: if ( is_numeric($key) ) { $this->attributes[$key] = $value; } elseif ( is_array($this->attributes[$key] ?? false) ) { $this->attributes[$key] = array_replace($value, $this->attributes[$key]); } else { $this->attributes[$key] = $value; } break; } } return $this; } public function hasAttributes($key) { return isset( $this->attributes[$key] ); } /** * Recursive function, allowing both $array as param and $key, $value */ public function css(...$arguments) { foreach($arguments as $item) { if ( is_array($item) ) { foreach($item as $key => $value) { $this->css($key, $value); } } else { $this->attributes['style'][$arguments[0]] = $arguments[1]; } } return $this; } public function hasClass(string $className) : bool { return array_search(strtolower($className), array_map('strtolower', $this->attributes['class']), true); } public function delete() { } public function count() { return count($this->childs); } public function jsonSerialize() : mixed { return [ 'tag' => $this->tag, 'attr' => $this->attributes, 'childs' => $this->childs, 'options' => $this->options, ]; } #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { if ( is_numeric($offset) ) { return $this->selected[$offset] = $value; } elseif ( is_null($offset) ) { return $this->childs[] = $value; } else { return $this->childs[$offset] = $value; } } public function offsetExists($query) : bool { return count($this->kwery()->find($query)) > 0; } public function offsetUnset($query) : void { $this->kwery()->find($query)->remove(); } public function offsetGet($query) : self { return $this->kwery()->find($query); } public function rewind() : void { reset($this->childs); } public function current() : self { return current($this->childs); } public function key() : int { return key($this->childs); } #[\ReturnTypeWillChange] public function next() : self { return next($this->childs); } public function valid() : bool { return ! in_array(key($this->childs), [ NULL, FALSE ], true); } public static function isNode($obj) : bool { return $obj instanceof UiElement; } public function __toString() : string { return $this->render(); } }