Parcourir la source

added hGetFormItem in UI and Typespecial

Piotr Labudda il y a 8 ans
Parent
commit
8f3ad9c0b9
3 fichiers modifiés avec 469 ajouts et 55 suppressions
  1. 70 14
      SE/se-lib/Typespecial.php
  2. 48 0
      SE/se-lib/TypespecialVariable.php
  3. 351 41
      SE/se-lib/UI.php

+ 70 - 14
SE/se-lib/Typespecial.php

@@ -639,6 +639,68 @@ jQuery('#typeahead-{$fName}').typeahead({
 		return $out;
 	}
 
+	public function hGetFormItem($acl, $fieldNamespace, $tblID, $fName, $selValue = '', $params = array(), $record = null) {
+		$jsonAllowCreate = 'true';
+		switch ($this->getFieldName()) {
+			case 'L_APPOITMENT_USER':
+			case 'T_TELBOX_NEIGHBOUR_IN_ID':
+			case 'CRM_LISTA_ZASOBOW_ID':
+			case 'ID_ZASOB':
+			case 'ID_BILLING_USERS':
+			case 'ID_PROJECT':
+			case 'ID_DEVICE':
+				$jsonAllowCreate = 'false';
+				break;
+		}
+		if (array_key_exists('allowCreate', $params)) $jsonAllowCreate = ($params['allowCreate'])? 'true' : 'false';
+
+		$tsValue = V::get('typespecialValue', '', $params);
+		if (!empty($selValue) && !empty($tsValue)) {
+			// $tsValue = "{$selValue}: {$tsValue}"; // TODO: only in TypespecialVariable - skip in Typespecial
+		}
+
+		$options = [];
+		if (!empty($selValue)) {
+			$tsSelectedItemLabel = (!empty($tsValue))? $tsValue : $selValue;
+			//$out .= '<option value="' . $selValue . '" selected="selected" typespecial="'.$tsSelectedItemLabel.'">' . $tsSelectedItemLabel . '</option>';
+			{// fix prefix
+				$prefix = "{$selValue}";
+				$prefixLen = strlen($prefix);
+				if (strlen($tsSelectedItemLabel) > $prefixLen && $prefix != substr($tsSelectedItemLabel, 0, $prefixLen)) {
+					if (' ' == substr($tsSelectedItemLabel, 0, 1)) {
+						$tsSelectedItemLabel = "{$prefix}{$tsSelectedItemLabel}";
+					} else {
+						$tsSelectedItemLabel = "{$prefix}: {$tsSelectedItemLabel}";
+					}
+				}
+			}
+			// $out .= '<option value="' . $selValue . '" selected="selected">' . $tsSelectedItemLabel . '</option>';
+			$options[] = [
+				'id' => $selValue,
+				'name' => $tsSelectedItemLabel,
+			];
+		}
+
+		DBG::log(['$selValue'=>$selValue, '$tsValue'=>$tsValue, '$options'=>$options], 'array', "DBG TS selected value");
+
+		return [ 'P5UI__Typespecial', array_filter([
+			'idField' => $this->fldID,
+			'fieldNamespace' => $fieldNamespace,
+			'fieldName' => $fName,
+			'frmFldName' => V::get('formFieldName', $fName, $params),
+			'create' => $jsonAllowCreate,
+			'ajaxDataUrlBase' => V::get('ajaxDataUrlBase', "index-ajax.php?_cls=TableAjax&_zasobID={$tblID}&_task=TYPESPECIAL&fldID={$this->fldID}", $params),
+			'options' => $options,
+			'selected' => $selValue,
+			'type' => "Typespecial",
+			'preload' => true,
+		], function ($value) { return null !== $value; }),
+			(!empty($options))
+				? [ [ 'option', [ 'value' => $options[0]['id'] ], $options[0]['name'] ] ]
+				: []
+		];
+	}
+
 	public function showFormItem($tblID, $fName, $selValue = '', $params = array(), $record = null) {
 		$out = '';
 
@@ -692,6 +754,8 @@ jQuery('#typeahead-{$fName}').typeahead({
 		$ajaxDataUrlBase = "index-ajax.php?_cls=TableAjax&_zasobID={$tblID}&_task=TYPESPECIAL&fldID={$this->fldID}";
 		$ajaxDataUrlBase = V::get('ajaxDataUrlBase', $ajaxDataUrlBase, $params);
 
+		$frmFldName = V::get('formFieldName', $fName, $params);
+
 		$out .= '<script>' . "
 (function(){
 	var fldNode=jQuery('#{$fName}'), tsNode=jQuery('#ts-{$fName}');
@@ -704,7 +768,7 @@ jQuery('#typeahead-{$fName}').typeahead({
 	if (fldNode.parent().hasClass('show-last-value')) {
 		fldNode.parent().hide();
 	}
-	tsNode.attr('name', '{$fName}');
+	tsNode.attr('name', '{$frmFldName}');
 	tsNode.attr('tabindex', fldNode.attr('tabindex'));
 
 
@@ -717,9 +781,8 @@ jQuery('#typeahead-{$fName}').typeahead({
 		create: {$jsonAllowCreate},
 		delimiter: ';',
 		dataAttr: 'typespecial',
-//		preload: true,
-		options: {$optionsJson},
 		preload: true,
+		options: {$optionsJson},
 		render: {
 			item: function(item, escape) {
 				//console.log('item', item);
@@ -795,9 +858,6 @@ jQuery('#typeahead-{$fName}').typeahead({
 				},
 				success: function(res) {
 					var i, prefix, prefixLen;
-					// res.sort(function(a, b) {// TODO: use \$order from reqeust
-					// 	if (!a['\$order'])
-					// })
 					for (i in res) {
 						prefix = '' + res[i].id;
 						prefixLen = prefix.length;
@@ -1055,18 +1115,14 @@ if(V::get('DBG_TS', 0, $_GET) > 1){echo'<pre style="max-height:200px;overflow:au
 			}
 		}
 		if (!$tblFound) {
-			$db = DB::getDB();
-			$sql = "select z.`ID` as tbl_id
+			$tblFound = (object)DB::getPDO()->fetchFirst("
+				select z.`ID` as tbl_id
 					, z.`PARENT_ID` as db_id
 					, z.`DESC` as tbl_name
 				from `CRM_LISTA_ZASOBOW` as z
 				where z.`ID`={$tblId}
-			";
-			$res = $db->query($sql);
-			if ($r = $db->fetch($res)) {
-				$tblFound = $r;
-				if(V::get('DBG_TS', 0, $_GET) > 1){echo'<pre style="max-height:200px;overflow:auto;border:1px solid orange;text-align:left;">Found 2 tblFound ('.$tblId.') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($tblFound);echo'</pre>';}// TODO: RMME
-			}
+			");
+			if(V::get('DBG_TS', 0, $_GET) > 1){echo'<pre style="max-height:200px;overflow:auto;border:1px solid orange;text-align:left;">Found 2 tblFound ('.$tblId.') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($tblFound);echo'</pre>';}// TODO: RMME
 		}
 		if (!$tblFound) {
 			if(V::get('DBG_TS', 0, $_GET) > 1){echo'<pre style="max-height:200px;overflow:auto;border:1px solid orange;text-align:left;">No tblFound! ('.$tblId.') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($_SESSION['Typespecial_Cache']['sqlTablesInfo']);echo'</pre>';}// TODO: RMME

+ 48 - 0
SE/se-lib/TypespecialVariable.php

@@ -64,6 +64,54 @@ class TypespecialVariable extends TypespecialBase {
 		return $values;
 	}
 
+	public function hGetFormItem($acl, $fieldNamespace, $tblID, $fName, $selValue = '', $params = array(), $record = null) {
+		$jsonAllowCreate = 'true';
+		switch ($this->fldName) {
+			case 'L_APPOITMENT_USER':
+			case 'A_ADM_COMPANY':
+			case 'A_CLASSIFIED':
+			case 'DEFAULT_ACL_GROUP':
+			case 'VERSION_GIT':
+			case '__USER_ID':
+			case '__USER_LOGIN':
+				$jsonAllowCreate = 'false';
+				break;
+		}
+		if (array_key_exists('allowCreate', $params)) $jsonAllowCreate = ($params['allowCreate'])? 'true' : 'false';
+
+		$tsValue = V::get('typespecialValue', '', $params);
+		if (!empty($selValue) && !empty($tsValue)) {
+			$tsValue = "{$selValue}: {$tsValue}"; // TODO: only in TypespecialVariable - skip in Typespecial
+		}
+
+		$options = [];
+		if (!empty($selValue)) {
+			$tsVal = (!empty($tsValue))? $tsValue : $selValue;
+			//$out .= '<option value="' . $selValue . '" selected="selected" typespecial="'.$tsVal.'">' . $tsVal . '</option>';
+			$out .= '<option value="' . $selValue . '" selected="selected">' . $tsVal . '</option>';
+			$options[] = [
+				'id' => $selValue,
+				'name' => ($tsValue)? $tsValue : $selValue
+			];
+		}
+
+		return [ 'P5UI__Typespecial', array_filter([
+			'idField' => $this->fldID,
+			'fieldNamespace' => $fieldNamespace,
+			'fieldName' => $fName,
+			'frmFldName' => V::get('formFieldName', $fName, $params),
+			'create' => $jsonAllowCreate,
+			'ajaxDataUrlBase' => V::get('ajaxDataUrlBase', "index-ajax.php?_cls=TableAjax&_zasobID={$tblID}&_task=TYPESPECIAL&fldID={$this->fldID}", $params),
+			'options' => $options,
+			'type' => "TypespecialVariable",
+			'preload' => false,
+		], function ($value) { return null !== $value; }),
+			(!empty($options))
+				? [ [ 'option', [ 'value' => $options[0]['id'] ], $options[0]['name'] ] ]
+				: []
+		];
+	}
+
 	public function showFormItem($tblID, $fName, $selValue = '', $params = array(), $record = null) {
 		$out = '';
 

+ 351 - 41
SE/se-lib/UI.php

@@ -104,11 +104,11 @@ class UI {
 
 	/**
 	 * $params - Array
-	 *   $params['caption'] (optional) -> <caption>...</caption>
-	 *   $params['cols'] (optional) -> cols, if not set read from first row
-	 *   $params['rows'] -> rows, if not set - empty table
-	 *   $params['rows'] -> rows, if not set - empty table
-	 *   $params['disable_lp'] -> disable lp. col
+	 *	 $params['caption'] (optional) -> <caption>...</caption>
+	 *	 $params['cols'] (optional) -> cols, if not set read from first row
+	 *	 $params['rows'] -> rows, if not set - empty table
+	 *	 $params['rows'] -> rows, if not set - empty table
+	 *	 $params['disable_lp'] -> disable lp. col
 	 */
 	public static function table($params) {
 		$cols = V::get('cols', array(), $params);
@@ -155,7 +155,7 @@ class UI {
 		if (!empty($cols)) {
 			self::startTag('thead', null); echo "\n";
 			self::startTag('tr', null); echo "\n";
-			if ($showLp) { self::tag('th', [ 'style' => "padding:2px" ], "Lp.");  echo "\n"; }
+			if ($showLp) { self::tag('th', [ 'style' => "padding:2px" ], "Lp.");	echo "\n"; }
 			foreach ($cols as $colName) {
 				if (in_array($colName, $hiddenCols)) continue;
 				echo self::h('th', [ 'style' => "padding:2px" ], [
@@ -349,41 +349,41 @@ class UI {
 	public static function hButtonAjaxJsFunction() {
 		echo UI::h('script', [], "
 			function p5UI__hButtonAjax(n, eventNamespace, url, query) {
-			  var dbg = " . ( DBG::isActive() ? 1 : 0 ) . ";
-			  var jqNode = jQuery(n);
-			  var state = {
-			    href: url || n.href,
-			    data: query || ''
-			  }
-			  jQuery(document).trigger('p5UIBtnAjax:' + eventNamespace + ':click', [n, state])
-			  if (jqNode.hasClass('disabled')) { // bootstrap already prevent this action
-			    if (dbg) console.log('WARNING: btn disabled - waiting for response - Cancel?')
-			    return false
-			  }
-			  jqNode.addClass('disabled btn-loading')
-
-			  window.fetch(state.href, {
-			    method: 'POST',
-			    headers: {
-			      'Content-Type': 'application/x-www-form-urlencoded' // query string
-			    },
-			    credentials: 'same-origin',
-			    body: state.data // new URLSearchParams(state.data)
-			  }).then(function(response) {
-			    return response.json()
-			  }).then(function(payload) {
-			    jqNode.removeClass('disabled btn-loading');
-			    jQuery(document).trigger(eventNamespace + ':response', [n, payload]);
-			  }).catch(function(e) {
-			    jQuery(document).trigger(eventNamespace + ':response', [n, 'error' + e]);
-			    jqNode.removeClass('disabled btn-loading');
-			    p5UI__notifyAjaxCallback({
-			      type: 'error',
-			      msg: 'Request error ' + e
-			    });
-			    console.log('loadDataAjax:fetch: ERR:', e);
-			  })
-			  return false;
+				var dbg = " . ( DBG::isActive() ? 1 : 0 ) . ";
+				var jqNode = jQuery(n);
+				var state = {
+					href: url || n.href,
+					data: query || ''
+				}
+				jQuery(document).trigger('p5UIBtnAjax:' + eventNamespace + ':click', [n, state])
+				if (jqNode.hasClass('disabled')) { // bootstrap already prevent this action
+					if (dbg) console.log('WARNING: btn disabled - waiting for response - Cancel?')
+					return false
+				}
+				jqNode.addClass('disabled btn-loading')
+
+				window.fetch(state.href, {
+					method: 'POST',
+					headers: {
+						'Content-Type': 'application/x-www-form-urlencoded' // query string
+					},
+					credentials: 'same-origin',
+					body: state.data // new URLSearchParams(state.data)
+				}).then(function(response) {
+					return response.json()
+				}).then(function(payload) {
+					jqNode.removeClass('disabled btn-loading');
+					jQuery(document).trigger(eventNamespace + ':response', [n, payload]);
+				}).catch(function(e) {
+					jQuery(document).trigger(eventNamespace + ':response', [n, 'error' + e]);
+					jqNode.removeClass('disabled btn-loading');
+					p5UI__notifyAjaxCallback({
+						type: 'error',
+						msg: 'Request error ' + e
+					});
+					console.log('loadDataAjax:fetch: ERR:', e);
+				})
+				return false;
 			}
 		");
 	}
@@ -431,4 +431,314 @@ class UI {
 		);
 	}
 
+	/**
+	 * @param $taskPerm - 'C', 'W'
+	 */
+	public static function hGetFormItem($acl, $fieldName, $taskPerm, $fieldID, $fName, $fValue, $params = array(), $record = null) {
+		Lib::loadClass('Typespecial');
+
+		if (!$acl->isAllowed($fieldID, $taskPerm, $record)) {
+			switch ($taskPerm) {
+				case 'R': return "Brak uprawnień do odczytu";
+				case 'W': return "Brak uprawnień do zapisu";
+				default: return "Brak uprawnień do tego pola ({$taskPerm})";
+			}
+		}
+
+		if ($fieldName == 'ID') return ''; // TODO: hide primaryKey?
+
+		$colType = $acl->getFieldTypeById($fieldID);
+		if (!$colType) return "Error - unknown type";
+
+		$html = new stdClass();
+		$html->_params = array();
+		$html->tag = 'input';
+		$html->childrens = [];
+		$html->attrs = array();
+		$html->attrs['id'] = $fName;
+		$html->attrs['name'] = $fName;
+		$html->attrs['type'] = 'text';
+		$html->attrs['value'] = htmlspecialchars($fValue);
+		if (isset($params['tabindex'])) {
+			$html->attrs['tabindex'] = $params['tabindex'];
+		}
+		if (!$acl->hasFieldPerm($fieldID, $taskPerm)) {
+			$html->attrs['disabled'] = 'disabled';
+		}
+		$maxGrid = V::get('maxGrid', 10, $params);
+
+		if (substr($colType['type'], 0, 3) == 'int'
+				|| substr($colType['type'], 0, 7) == 'tinyint'
+				|| substr($colType['type'], 0, 8) == 'smallint'
+				|| substr($colType['type'], 0, 6) == 'bigint'
+		) {
+			//$h->Type_value = (int)str_replace(array(' ','(',')'), '', substr($h->Type, 4));
+			$html->attrs['type'] = 'number';
+			$html->attrs['class'][] = 'input-small';
+		}
+		else if (substr($colType['type'], 0, 6) == 'double') {
+			$html->attrs['type'] = 'text';
+			$html->attrs['class'][] = 'input-small';
+		}
+		else if (substr($colType['type'], 0, 7) == 'decimal') {
+			$html->attrs['type'] = 'text';
+			$html->attrs['class'][] = 'input-small';
+		}
+		else if (substr($colType['type'], 0, 7) == 'varchar'
+				|| substr($colType['type'], 0, 4) == 'char'
+			) {
+			//$h->Type_value = (int)str_replace(array(' ','(',')'), '', substr($h->Type, 8));
+			$html->attrs['type'] = 'text';
+			$maxLength = (int)str_replace(array(' ','(',')'), '', substr($colType['type'], strpos($colType['type'], '(') + 1, -1));
+			if ($maxLength > 0) {
+				$html->attrs['maxlength'] = $maxLength;
+			}
+			$valLength = strlen($fValue);
+			if (isset($params['widthClass'])) {
+				if ($params['widthClass'] == 'inside-modal') {
+					$html->attrs['style'] = 'width:98%;';
+				} else {
+					$html->attrs['style'] = 'width:98%;';
+				}
+			} else {
+				/*
+				if ($maxLength < 11) {
+					$html->attrs['class'][] = 'span2';
+				} else if ($maxLength < 31) {
+					$html->attrs['class'][] = 'span5';
+				} else if ($maxLength < 51) {
+					$html->attrs['class'][] = (8 <= $maxGrid)? 'span8' : "span{$maxGrid}";
+				} else if ($maxLength < 101) {
+					$html->attrs['class'][] = (10 <= $maxGrid)? 'span10' : "span{$maxGrid}";
+				} else {
+					$html->attrs['class'][] = (12 <= $maxGrid)? 'span12' : "span{$maxGrid}";
+				}
+				*/
+			}
+
+			if ($maxLength > 255) {// Fix for long varchar - use textarea
+				$html->tag = 'textarea';
+				$html->childrens[] = htmlspecialchars($fValue);
+				$html->attrs['rows'] = '3';
+				unset($html->attrs['type']);
+				unset($html->attrs['value']);
+			}
+		}
+		else if (substr($colType['type'], 0, 4) == 'date') {
+			$testDatePicker = true;
+			if ($testDatePicker) {
+				$html->attrs['type'] = 'text';
+				$html->_params[] = 'date';
+				if (substr($colType['type'], 0, 8) == 'datetime') {
+					$html->attrs['class'][] = 'se_type-datetime';// datetimepicker';
+					$html->attrs['data-format'] = 'yyyy-MM-dd hh:mm';
+					$html->attrs['maxlength'] = 19;
+				} else {
+					$html->attrs['class'][] = 'se_type-date';// datetimepicker';
+					$html->attrs['maxlength'] = 10;
+				}
+				if (substr($html->attrs['value'], 0, 10) == '0000-00-00') {
+					$html->attrs['value'] = '';
+				}
+			} else {
+				$html->attrs['type'] = 'date';
+			}
+		}
+		else if ($colType['type'] == 'time') {
+			$testDatePicker = true;
+			if ($testDatePicker) {
+				$html->attrs['type'] = 'text';
+				$html->_params[] = 'time';
+				$html->attrs['class'][] = 'se_type-time';// datetimepicker';
+				$html->attrs['data-format'] = 'hh:mm:ss';
+				$html->attrs['maxlength'] = 8;
+				if (substr($html->attrs['value'], 0, 8) == '00:00:00') {
+					$html->attrs['value'] = '';
+				}
+			} else {
+				$html->attrs['type'] = 'time';
+			}
+		}
+		else if ($colType['type'] == 'timestamp') {
+			$testDatePicker = true;
+			if ($testDatePicker) {
+				$html->attrs['type'] = 'text';
+				$html->_params[] = 'date';
+				$html->attrs['class'][] = 'se_type-datetime';// datetimepicker';
+				$html->attrs['data-format'] = 'yyyy-MM-dd hh:mm';
+				$html->attrs['maxlength'] = 19;
+				if (substr($html->attrs['value'], 0, 10) == '0000-00-00') {
+					$html->attrs['value'] = '';
+				}
+			} else {
+				$html->attrs['type'] = 'date';
+			}
+		}
+		else if (substr($colType['type'], 0, 4) == 'enum') {
+			unset($html->attrs['type']);
+			unset($html->attrs['value']);
+			$html->tag = 'select';
+
+			$values = explode(',', str_replace(array('(',')',"'",'"'), '', substr($colType['type'], 5)));
+			$selValue = $fValue;
+			if (empty($selValue) && $selValue !== '0' && !empty($colType['default'])) {
+				if ($taskPerm == 'C') {
+					$selValue = $colType['default'];
+				} else if ($taskPerm == 'W' && $acl->isAllowed($fieldID, 'R', $record)) {
+					$selValue = $colType['default'];
+				}
+			}
+
+			$html->childrens[] = [ 'option', [ 'value' => "" ], "" ];
+			if (!empty($selValue) && !in_array($selValue, $values)) {
+				$html->childrens[] = [ 'option', [ 'value' => $selValue, 'selected' => "selected" ], $selValue ];
+			}
+			foreach ($values as $val) {
+				$html->childrens[] = [ 'option', array_merge(
+					[ 'value' => $val ],
+					($selValue == $val)
+						? [ 'selected' => "selected" ]
+						: []
+				), $val ];
+			}
+		}
+		else if (substr($colType['type'], 0, 4) == 'text'
+			|| substr($colType['type'], 0, 8) == 'tinytext'
+			|| substr($colType['type'], 0, 10) == 'mediumtext'
+			|| substr($colType['type'], 0, 8) == 'longtext'
+		) {
+			$html->tag = 'textarea';
+			$html->childrens[] = htmlspecialchars($fValue);
+			if (isset($params['widthClass'])) {
+				if ($params['widthClass'] == 'inside-modal') {
+					$html->attrs['style'] = 'width:98%;';
+				} else {
+					$html->attrs['style'] = 'width:98%;';
+				}
+			} else {
+				//$html->attrs['class'][] = (8 <= $maxGrid)? 'span8' : "span{$maxGrid}";
+			}
+			$html->attrs['rows'] = '3';
+			unset($html->attrs['type']);
+			unset($html->attrs['value']);
+		}
+		else if ('polygon' == $colType['type']) { return '...'; }// Wielokąt
+		else if ('multipolygon' == $colType['type']) { return '...'; }// Zbiór wielokątów
+		else if ('linestring' == $colType['type']) { return '...'; }// Krzywa z interpolacji liniowej pomiędzy punktami
+		else if ('point' == $colType['type']) { return '...'; }// Punkt w przestrzeni 2-wymiarowej
+		else if ('geometry' == $colType['type']) { return '...'; }// Typy, które mogą przechowywać geometrię dowolnego typu
+		else if ('multipoint' == $colType['type']) { return '...'; }// Zbiór punktów
+		else if ('multilinestring' == $colType['type']) { return '...'; }// Zbiór krzywych z interpolacji liniowej pomiędzy punktami
+		else if ('geometrycollection' == $colType['type']) { return '...'; }// Zbiór obiektów geometrycznych dowolnego typu
+		else {
+			return 'unknown Type "'.$colType['type'].'"';
+		}
+		$html->attrs['class'][] = 'form-control';
+		if (!empty($html->attrs['class'])) $html->attrs['class'] = implode(" ", $html->attrs['class']);
+		$nodeHtml = (in_array($html->tag, array('select', 'textarea')))
+			? [ $html->tag, $html->attrs, $html->childrens ]
+			: $nodeHtml = [ $html->tag, $html->attrs ]
+		;
+
+		if (in_array('date', $html->_params)) {
+			$nodeHtml = [ 'div', [ 'class' => "input-group" ], [
+				$nodeHtml,
+				[ 'span', [ 'class' => "input-group-addon" ], [
+					[ 'span', [ 'class' => "glyphicon glyphicon-calendar" ] ]
+				] ]
+			] ];
+		}
+		else if (in_array('time', $html->_params)) {
+			$nodeHtml = [ 'div', [ 'class' => "input-group" ], [
+				$nodeHtml,
+				[ 'span', [ 'class' => "input-group-addon" ], [
+					[ 'span', [ 'class' => "glyphicon glyphicon-time" ] ]
+				] ]
+			] ];
+		}
+
+		if (true == V::get('appendBack', '', $params)
+				&& !in_array('date', $html->_params)
+				&& !in_array('time', $html->_params)
+			 ) {
+			if ($html->tag == 'input' && $taskPerm == 'W') {
+				$nodeHtml = [ 'div', [ 'class' => "input-group show-last-value" ], [
+					$nodeHtml,
+					[ 'span', [ 'class' => "input-group-addon button-appendBack", 'title' => htmlspecialchars($fValue) ], [
+						[ 'span', [ 'class' => "glyphicon glyphicon-arrow-left" ] ]
+					] ]
+				] ];
+			}
+		}
+
+		$typeSpecial = Typespecial::getInstance($fieldID, $fieldName);
+		if ($typeSpecial) {
+			$tsParams = array();
+			$tsValue = V::get('typespecialValue', '', $params);
+			if (!empty($tsValue)) {
+				$tsParams['typespecialValue'] = $tsValue;
+			}
+			$nodeHtml = [ 'div', [ 'class' => "field-with-typespecial" ], [
+				$nodeHtml,
+				$typeSpecial->hGetFormItem($acl, $fieldName, $acl->_zasobID, $fName, $fValue, $tsParams, $record),
+			] ];
+		}
+
+		return $nodeHtml;
+	}
+
+	public static function convertHtmlToArray($html) {
+		$nodes = [];
+		// <a href="index.php?_route=Users&_task=userGroups&usrLogin=michal.podejko">ustal stanowisko</a>
+		// [ 'a', [ 'href' => "index.php?_route=Users&_task=userGroups&usrLogin=michal.podejko" ], "ustal stanowisko" ]
+
+		$DBG = 0;
+		$pos = 0;
+		// TODO: while (true)
+		if ('<' === substr($html, $pos, 1)) { // parse tag
+			$tagName = ''; $attrs = []; $content = [];
+			$endTagOpen = strpos($html, '>', $pos + 1);
+			$endTagName = min(strpos($html, ' ', $pos + 1), $endTagOpen); // '<tag>' or '<tag attr="..">'
+			if (false === $pos) throw new Exception("Error Processing Html - missing tagName");
+			$tagName = substr($html, $pos + 1, $endTagName - $pos - 1);
+			if($DBG){echo "\ntagName: '{$tagName}'";}
+			if ('>' === substr($html, $endTagName, 1)) {
+
+			} else if (' ' === substr($html, $endTagName, 1)) {
+				if (false === $endTagOpen) throw new Exception("Error Processing Html - missing open tag end char");
+				$attrs = UI::convertHtmlAttrsToArray(trim(substr($html, $endTagName + 1, $endTagOpen - $endTagName - 1)));
+			} else {
+				throw new Exception("Error Processing Html - unexpected end tag name char '" . substr($html, $endTagName, 1) . "'");
+			}
+			if($DBG){echo "\nattrs: '" . json_encode($attrs), "'";}
+			// TODO: empty tags '<br>', '<br/>', '<br />', '<input ... />', '<input ... >', img, hr, etc.
+			// TODO: nested same tags '<tagName> ... <tagName> ... </tagName> ... </tagName>'
+			$closeTagStart = strpos($html, "</{$tagName}>", $endTagOpen + 1);
+			if (false === $closeTagStart) throw new Exception("Error Processing Html - missing close tagName");
+			if($DBG){echo "\nDBG \$endTagOpen: " . substr($html, $endTagOpen, 5) . "...";}
+			if($DBG){echo "\nDBG \$endTagOpen: " . substr($html, $endTagOpen) . ".EOL";}
+			if($DBG){echo "\nDBG \$closeTagStart strpos(\$html, '</{$tagName}>', {$endTagOpen} + 1) = '{$closeTagStart}': " . substr($html, $closeTagStart, 5) . "...";}
+			$content = substr($html, $endTagOpen + 1, $closeTagStart - $endTagOpen - 1);
+			$tag = [ $tagName, $attrs, $content ];
+			if($DBG){echo "\n\$tag: ";print_r($tag);}
+			$nodes = $tag;
+		}
+		return $nodes;
+	}
+	public static function convertHtmlAttrsToArray($strAttrs) {
+		$attrs = [];
+		if (!preg_match_all('((\w+)=\"([^"]*)\")', $strAttrs, $matches)) {
+			// echo "DBG:: empty attrs or wrong syntax";
+			return [];
+		}
+		$total = (count($matches) - 1) / 2;
+		// echo "\n\$matches (total = {$total}) = ";print_r($matches);
+		for ($i = 0; $i < $total; $i++) {
+			$idx = $i * 2 + 1;
+			// echo "\n\$attrs[ '{$matches[$idx][0]}' ] = '{$matches[$idx+1][0]}';";
+			$attrs[ $matches[ $idx ][0] ] = $matches[ $idx + 1 ][0];
+		}
+		return $attrs;
+	}
+
 }