Piotr Labudda пре 5 година
родитељ
комит
f450a15346
5 измењених фајлова са 352 додато и 6 уклоњено
  1. 47 2
      SE/se-lib/UI.php
  2. 27 4
      SE/se-lib/UI/Alert.php
  3. 101 0
      SE/se-lib/UI/Node.php
  4. 18 0
      SE/se-lib/UI/NodeSelfClosing.php
  5. 159 0
      SE/se-lib/UI/Table.php

+ 47 - 2
SE/se-lib/UI.php

@@ -2,6 +2,8 @@
 
 Lib::loadClass('Theme');
 require_once dirname(__FILE__) . '/' . 'UITagInterface.php';
+require_once dirname(__FILE__) . '/' . 'UI/Node.php'; // TODO: UI::node - replacement for UI::h
+require_once dirname(__FILE__) . '/' . 'UI/NodeSelfClosing.php';
 
 class UI {
 
@@ -384,7 +386,8 @@ class UI {
 				'class' => 'btn ' . V::get('class', 'btn-default', $params),
 				'style' => V::get('style', '', $params)
 			],
-			(!empty($params['title'])) ? ['title' => $params['title']] : []
+			(!empty($params['title'])) ? ['title' => $params['title']] : [],
+			(!empty($params['onclick'])) ? ['onclick' => $params['onclick']] : []
 		), $label);
 		return self::h('form', [
 			'action' => V::get('action', '', $params),
@@ -476,7 +479,49 @@ class UI {
 			}
 		");
 	}
-	public static function h($tagName, $params = [], $childrens = []) {
+	// @return UI_Node({tagName, params, childrens}): struct HTML tag
+	public static function node($tagName = null, $params = null, $childrens = null) {
+		if (null === $tagName && empty($params)) return new UI_Node(null, null, $childrens);
+		if ('p5:' === substr($tagName, 0, 3)) return self::customNode($tagName, $params, $childrens);
+
+		switch ($tagName) {
+			case 'hr': // Defines a single change in line of content
+			case 'br': // Defines Single line breaks
+			case 'input': // Defines a input tag
+			case 'link': // Define link connection between two files
+			case 'area': // Defines clickable area inside a image map
+			case 'base': // Defines specific base or root path
+			case 'col': // Defines column properties within each column
+			case 'embed': // That adds or embeds other elements such as Flash
+			case 'img': // Defines a image
+			case 'keygen': // Defines reference from data after the form is submitted
+			case 'meta': // Defines meta data inside the HTML web page
+			case 'param': // Defines passing parameters for an embedded project
+			case 'source': // Defines source for media files
+			case 'track': // Define text track for video or audio files
+			case 'wbr': // Define word break opportunity within a text for adding a line-break
+			case 'command': // Defines a command that users can invoke
+			case 'menuitem': // Defines command or menu item that users invoke from popup menu
+			{
+				return new UI_NodeSelfClosing($tagName, $params);
+			}
+			default:
+			{
+				return new UI_Node($tagName, $params, $childrens);
+			}
+		}
+	}
+	public static function customNode($tagName, $params = null, $childrens = null) {
+		if ('p5:' === substr($tagName, 0, 3)) {
+			$nodeClassName = "UI_" . substr($tagName, 3);
+			if (!class_exists($nodeClassName)) Lib::tryLoadClass($nodeClassName);
+			if (!class_exists($nodeClassName)) throw new Exception("Not implemented custom tag '{$tagName}'");
+			return new $nodeClassName($tagName, $params, $childrens);
+		}
+		throw new Exception("Not implemented custom tag prefix '{$tagName}'");
+	}
+
+	public static function h($tagName, $params = [], $childrens = []) { // TODO: return (string)UI::node($tagName, $params, $childrens);
 		if (null === $tagName && empty($params)) return self::hChildrens($childrens);
 		$emptyTags = [];
 		$emptyTags[] = 'hr';

+ 27 - 4
SE/se-lib/UI/Alert.php

@@ -1,7 +1,6 @@
 <?php
 
-// UI::hAttributes($params);
-// UI::hChildrens($childrens);
+require_once dirname(__FILE__) . '/' . 'Node.php';
 
 // - props[type] - string success|info|warning|danger|error
 // <div class="alert alert-success" role="alert">...</div>
@@ -9,7 +8,31 @@
 // <div class="alert alert-warning" role="alert">...</div>
 // <div class="alert alert-danger" role="alert">...</div>
 
-class UI_Alert implements UITagInterface {
+class UI_Alert extends UI_Node implements UITagInterface {
+
+	public $tagName, $props, $childrens;
+
+	function __construct($tagName = null, $props = null, $childrens = null) {
+		parent::__construct($tagName, $props, $childrens);
+
+		// TODO: convert to base html tagName with childrens
+		//       or modify __toString() (NOTE: require implement tagName in frontend with name: 'P5UI__Alert' (prefix: 'p5:' -> 'P5UI__'))
+		$DO_CONVERT_TO_NATIVE_HTML_TAGS = true;
+		if ($DO_CONVERT_TO_NATIVE_HTML_TAGS) {
+			$type = self::convertType($props['type']);
+			unset($this->props['type']);
+			$cls = "alert alert-{$type}";
+			if (!empty($this->props['class'])) {
+				if (is_array($this->props['class'])) $this->props['class'][] = "{$cls}";
+				$this->props['class'] .= " {$cls}";
+			} else {
+				$this->props['class'] = $cls;
+			}
+			$this->tagName = 'div';
+		} else { // modify __toString() (NOTE: require implement tagName in frontend with name: 'P5UI__Alert' (prefix: 'p5:' -> 'P5UI__'))
+
+		}
+	}
 
 	/**
 	 * @param string $tagName = 'p5:Alert'
@@ -34,4 +57,4 @@ class UI_Alert implements UITagInterface {
 		}
 	}
 
-}
+}

+ 101 - 0
SE/se-lib/UI/Node.php

@@ -0,0 +1,101 @@
+<?php
+
+class UI_Node {
+
+	public $tagName, $props, $childrens;
+
+	function __construct($tagName = null, $props = null, $childrens = null) {
+		$this->tagName = $tagName;
+		$this->props = $props;
+		$this->childrens = $childrens;
+	}
+
+	function __toString() {
+		return '<' . $this->tagName . $this->attributesToString() . '>' . $this->childrensToString() . '</' . $this->tagName . '>';
+	}
+
+	function toReactNode() { // @return array(3): [ tagName, attributes, childrens ] @see p5UI__buildDom
+		return [
+			(string)$this->tagName,
+			!$this->props ? [] : $this->props,
+			$this->childrensToReactNodes(),
+		];
+	}
+
+	// 'reactNode' => [ 'div', [ 'class' => "container AjaxFrmHorizontalEdit", 'style' => [ "max-width" => "940px" ] ], [
+	// 	[ 'h4', [ 'style' => [ "padding-bottom" => "3px", "border-bottom" => "1px solid #ddd" ] ], [
+	// 		"Edycja rekordu Nr {$record['ID']}",
+	// 		[ 'small', [ 'class' => "pull-right valign-btns-bottom" ], [ $rowFunctionsOut ] ],
+	// 	] ],
+	// 	[ 'P5UI__FeatureEditForm', [
+	// 		'class' => "", 'action' => "", 'method' => "post",
+	// 		'id' => "EDIT_FRM_{$this->_htmlID}", // TODO: rm - use React nodes // TODO: $this->_htmlID not exists!
+	// 		'ajaxSaveUrl' => "{$syncUrl}&_task=editSaveAjax", // TODO:? &_hash={$this->_htmlID}
+	// 		'namespace' => $acl->getNamespace(),
+	// 		'idRecord' => $record['ID'],
+	// 		'tableLabelHtml' => $tblLabel,
+	// 	], [
+	// 		[ 'fieldset', [ 'style' => [ "padding-bottom" => "100px" ] ], $jsFields ] // fieldset
+	// 	] ] // form
+	// ] ] // .container
+
+	function attributesToString() {
+		if (!$this->props) return '';
+		$attrs = '';
+		foreach ($this->props as $k => $v) {
+			if (is_array($v)) {
+				$attrs .= " {$k}=\"" . implode(" ", $v) . "\"";
+			} else if (true === $v) { // eg. open => true : 'open'
+				$attrs .= " {$k}";
+			} else if (false === $v) { // skip if false value
+			} else if (!empty($v)) {
+				$attrs .= " {$k}=\"{$v}\"";
+			}
+		}
+
+		return $attrs;
+	}
+
+	function scalarChildToString($child) {
+		if (null === $child) return '';
+		if (is_scalar($child)) {
+			if (is_float($child) && !$child) return '0.0';
+			// TODO: is price -> '0.00'
+			// if (is_null($child)) return 'NULL'; // ??
+			return (string)$child;
+		}
+	}
+
+	function childrensToReactNodes() {
+		if (null === $this->childrens) return '';
+		if (is_scalar($this->childrens)) return $this->scalarChildToString($this->childrens);
+		if (is_object($this->childrens) && $this->childrens instanceof UI_Node) return [ $this->childrens->toReactNode() ];
+		if (!is_array($this->childrens)) throw new Exception("Unsupported children type");
+
+		return array_map(
+			function ($child) {
+				if (null === $child) return null;
+				if (is_scalar($child)) return $this->scalarChildToString($child);
+				if (is_object($child)) return $child->toReactNode(); // TODO: is class UI_Node
+				return (string)$child; // or throw new Exception('Not implemented UI_Node child type');
+			},
+			$this->childrens
+		);
+	}
+	function childrensToString() {
+		if (null === $this->childrens) return '';
+		if (is_scalar($this->childrens)) return $this->scalarChildToString($this->childrens);
+		if (is_object($this->childrens) && $this->childrens instanceof UI_Node) return (string)$this->childrens;
+		// if (!is_array($this->childrens)) throw new Exception("Unsupported children type");
+		if (!is_array($this->childrens)) trigger_error("Unsupported children type: " . var_export($this->childrens, true), E_USER_WARNING);
+		if (!is_array($this->childrens)) return '';
+		return array_reduce(
+			$this->childrens,
+			function ($ret, $child) {
+				return "{$ret}{$child}"; // $child->__toString()
+			},
+			""
+		);
+	}
+
+}

+ 18 - 0
SE/se-lib/UI/NodeSelfClosing.php

@@ -0,0 +1,18 @@
+<?php
+
+require_once dirname(__FILE__) . '/' . 'Node.php';
+
+class UI_NodeSelfClosing extends UI_Node {
+
+	public $tagName, $props;
+
+	function __construct($tagName = null, $props = null) {
+		$this->tagName = $tagName;
+		$this->props = $props;
+	}
+
+	function __toString() {
+		return '<' . $this->tagName . $this->attributesToString() . '/>';
+	}
+
+}

+ 159 - 0
SE/se-lib/UI/Table.php

@@ -0,0 +1,159 @@
+<?php
+
+require_once dirname(__FILE__) . '/' . 'Node.php';
+
+class UI_Table extends UI_Node { // TODO:??? implements UITagInterface {
+
+	public $tagName, $props, $childrens;
+
+	function __construct($tagName = null, $props = null, $childrens = null) {
+		// parent::__construct($tagName, $props, $childrens);
+		if (!empty($childrens)) throw new Exception("Childrens not allowed for Node 'p5:Table'");
+
+		$this->tagName = 'table';
+		$this->props = [
+			'class' => 'table table-bordered table-hover',
+		];
+		$args = [ // default values
+			'empty_msg' => 'Brak danych',
+			'disable_lp' => false,
+			'cell_padding' => 2,
+			'cols_help' => [],
+			'hidden_cols' => [],
+			'tbody_attributes' => [],
+			'list_col_class' => [], // [ colName => class ] from $props['@class[{$colName}]']
+			'list_col_style' => [], // [ colName => style ] from $props['@style[{$colName}]']
+		];
+		foreach ($props as $key => $value) {
+			switch ($key) {
+				case 'cols': $args['cols'] = $value; break;
+				case 'rows': $args['rows'] = $value; break;
+				case 'cols_help': $args['cols_help'] = $value; break;
+				case 'cols_label': $args['cols_label'] = $value; break;
+				case 'caption': $args['caption'] = $value; break;
+				case 'cell_padding': $args['cell_padding'] = (int)$value; break;
+				case 'disable_lp': $args['disable_lp'] = (bool)$value; break;
+				case 'hidden_cols': $args['hidden_cols'] = $value; break;
+				case 'empty_msg': $args['empty_msg'] = $value; break;
+				case '@tbody.id': $args['tbody_attributes']['id'] = $value; break;
+				case '@class': {
+					$this->props['class'] = $value;
+				} break;
+				case '__html_id': {
+					$this->props['id'] = $value;
+				} break;
+				default: {
+					if ('@class[' === substr($key, 0, strlen('@class['))) {
+						$colName = substr($key, strlen('@class['), -1);
+						$args['list_col_class'][$colName] = $value;
+					} else if ('@style[' === substr($key, 0, strlen('@style['))) {
+						$colName = substr($key, strlen('@style['), -1);
+						$args['list_col_style'][$colName] = $value;
+					} else {
+						$this->props[$key] = $value;
+					}
+				} break;
+			}
+		}
+		$this->childrens = null;
+
+		$countCols = 1;
+		if (empty($args['cols']) && !empty($args['rows'])) {
+			$firstRow = array();
+			foreach ($args['rows'] as $row) {
+				$firstRow = $row;
+				break;
+			}
+			$args['cols'] = array_filter(
+				array_keys((array)$firstRow),
+				function ($col) {
+					return ('@' != substr($col, 0, 1));
+				}
+			);
+		}
+		$countCols = count($args['cols']);
+		$countCols = ($args['disable_lp']) ? $countCols : $countCols + 1;
+		{ // $help
+			$help = array();
+			foreach ($args['cols'] as $name) {
+				$helpMsg = V::get($name, '', $args['cols_help']);
+				if (empty($helpMsg)) continue;
+				$help[$name] = UI::node('i', [
+					'class' => "glyphicon glyphicon-question-sign",
+					'title' => $helpMsg
+				]);
+			}
+		}
+		{ // $label
+			$label = array();
+			foreach ($args['cols'] as $name) {
+				$label[$name] = V::get($name, $name, $args['cols_label']);
+			}
+		}
+
+		if (!empty($args['caption'])) $this->childrens[] = UI::node('caption', [], $args['caption']);
+
+		if (!empty($args['cols'])) {
+			$this->childrens[] = UI::node('thead', null, [
+				UI::node('tr', null, array_merge(
+					$args['disable_lp']
+					?	[]
+					:	[ UI::node('th', [ 'style' => [ 'padding' => "{$args['cell_padding']}px" ] ], "Lp.") ]
+					,
+					array_map(function ($colName) use ($args, $label, $help) {
+						if (in_array($colName, $args['hidden_cols'])) return null;
+
+						return UI::node('th', [
+							'class' => (empty($args['list_col_class'][$colName])) ? '' : $args['list_col_class'][$colName],
+							'style' => "padding:{$args['cell_padding']}px" . (empty($args['list_col_style'][$colName]) ? '' : ";{$args['list_col_style'][$colName]}"),
+							// 'style' => array_merge([ 'padding' => "{$args['cell_padding']}px" ],
+							// 	empty($args['list_col_style'][$colName]) ? [] : $args['list_col_style'][$colName] // TODO: fix style to Array
+							// )
+						], [
+							$label[$colName],
+							(!empty($help[$colName])) ? ' ' . $help[$colName] : '',
+						]);
+					}, $args['cols'])
+				)),
+			]);
+		}
+
+		$this->childrens[] = UI::node('tbody', $args['tbody_attributes'],
+			(empty($args['rows']))
+			?	[
+					UI::node('tr', [], [
+						UI::node('td', [ 'style' => [ 'padding' => "{$args['cell_padding']}px" ], 'colspan' => $countCols ], $args['empty_msg']),
+					]),
+				]
+			:	array_map(function ($row, $lp) use ($args) {
+					$trAttrs = array();
+					if (!empty($row['@onClick'])) $trAttrs['onClick'] = $row['@onClick'];
+					if (!empty($row['@class'])) $trAttrs['class'] = $row['@class'];
+					if (!empty($row['@style'])) $trAttrs['style'] = $row['@style']; // TODO: fix style to Array
+					if (!empty($row['@data'])) foreach ($row['@data'] as $k => $v) $trAttrs["data-{$k}"] = $v;
+
+					return UI::node('tr', $trAttrs, array_merge(
+						($args['disable_lp'])
+						?	[]
+						:	[
+								UI::node('th', [ 'style' => [ 'padding' => "{$args['cell_padding']}px", 'color' => "#ccc" ] ], $lp),
+							]
+						,
+						array_map(function ($colName) use ($row, $args) {
+							if (in_array($colName, $args['hidden_cols'])) return null;
+
+							$rowAttrs = [ 'style' => "padding:{$args['cell_padding']}px" ];
+							// $rowAttrs = [ 'style' => [ 'padding' => "{$args['cell_padding']}px" ] ];
+							if (!empty($row["@onClick[{$colName}]"])) $rowAttrs['onClick'] = $row["@onClick[{$colName}]"];
+							if (!empty($row["@class[{$colName}]"])) $rowAttrs['class'] = $row["@class[{$colName}]"];
+							if (!empty($row["@style[{$colName}]"])) $rowAttrs['style'] .= "; " . $row["@style[{$colName}]"];
+							// if (!empty($row["@style[{$colName}]"])) $rowAttrs['style'] = array_merge($rowAttrs['style'], $row["@style[{$colName}"]); // TODO: fix style to Array
+
+							return UI::node('td', $rowAttrs, V::get($colName, '', $row));
+						}, $args['cols'])
+					));
+				}, $args['rows'], range(1, count($args['rows'])))
+		);
+	}
+
+}