[],
'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' => '= ',
'force-tag-close' => ' ?>'
] + $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();
}
}