props.class.php 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. <?php
  2. /**
  3. * The props class file of zin of ZenTaoPMS.
  4. *
  5. * @copyright Copyright 2023 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
  6. * @author Hao Sun <sunhao@easycorp.ltd>
  7. * @package zin
  8. * @version $Id
  9. * @link https://www.zentao.net
  10. */
  11. namespace zin;
  12. use zin\utils\classlist;
  13. use zin\utils\styleset;
  14. require_once dirname(__DIR__) . DS . 'utils' . DS . 'dataset.class.php';
  15. require_once dirname(__DIR__) . DS . 'utils' . DS . 'classlist.class.php';
  16. require_once dirname(__DIR__) . DS . 'utils' . DS . 'styleset.class.php';
  17. /**
  18. * Manage properties for html element and widgets
  19. */
  20. class props extends \zin\utils\dataset
  21. {
  22. /**
  23. * Style property
  24. *
  25. * @access public
  26. * @var styleset
  27. */
  28. public $style;
  29. /**
  30. * Class property
  31. *
  32. * @access public
  33. * @var classlist
  34. */
  35. public $class;
  36. /**
  37. * @var mixed[]
  38. */
  39. public static $booleanAttrs = array('allowfullscreen', 'async', 'autofocus', 'autoplay', 'checked', 'controls', 'default', 'defer', 'disabled', 'formnovalidate', 'inert', 'ismap', 'itemscope', 'loop', 'multiple', 'muted', 'nomodule', 'novalidate', 'open', 'playsinline', 'readonly', 'required', 'reversed', 'selected');
  40. /**
  41. * Create properties instance
  42. *
  43. * @access public
  44. * @param array $props - Properties list array
  45. */
  46. public function __construct($props = array())
  47. {
  48. $this->style = new styleset();
  49. $this->class = new classlist();
  50. parent::__construct($props);
  51. }
  52. /**
  53. * Method for sub class to modify value on setting it
  54. *
  55. * @access public
  56. * @param string $prop - Property name or properties list
  57. * @param mixed $value - Property value
  58. */
  59. protected function setVal($prop, $value)
  60. {
  61. if($prop === 'class') $this->class->set($value);
  62. elseif($prop === 'style') $this->style->set($value);
  63. elseif(str_starts_with($prop, '~')) $this->style->set(substr($prop, 1), $value);
  64. elseif($prop === '--') $this->style->cssVar($value);
  65. elseif(str_starts_with($prop, '--')) $this->style->cssVar(substr($prop, 2), $value);
  66. elseif(str_starts_with($prop, ':')) $this->set('data-' . substr($prop, 1), $value);
  67. elseif(str_starts_with($prop, '@')) $this->bindEvent(substr($prop, 1), $value);
  68. else parent::setVal($prop, $value);
  69. return $this;
  70. }
  71. /**
  72. * @return mixed
  73. * @param string $prop
  74. */
  75. protected function getVal($prop)
  76. {
  77. if($prop === 'class' || $prop === '.')
  78. {
  79. if(!$this->class->count()) return null;
  80. return $this->class->toStr();
  81. }
  82. if($prop === 'style' || $prop === '~')
  83. {
  84. if(!$this->style->getCount(true)) return null;
  85. return $this->style->toStr();
  86. }
  87. return parent::getVal($prop);
  88. }
  89. /**
  90. * @param string|string[] $name
  91. * @param mixed $value
  92. */
  93. public function reset($name, $value = null)
  94. {
  95. if(is_array($name))
  96. {
  97. foreach($name as $n) $this->reset($n);
  98. return;
  99. }
  100. if($name === 'class') return $this->class->clear();
  101. if($name === 'style') return $this->style->clear();
  102. $this->remove($name);
  103. if($value) $this->setVal($name, $value);
  104. }
  105. /**
  106. * @param string|mixed[] $name
  107. * @param mixed $handler
  108. */
  109. public function bindEvent($name, $handler = null)
  110. {
  111. if(is_array($name))
  112. {
  113. foreach($name as $key => $value) $this->bindEvent($key, $value);
  114. return;
  115. }
  116. $events = parent::getVal("@$name");
  117. if(is_null($events)) $events = array();
  118. if(is_array($handler)) $events = array_merge($events, $handler);
  119. else $events[] = $handler;
  120. parent::setVal("@$name", $events);
  121. }
  122. public function events()
  123. {
  124. $events = array();
  125. foreach($this->storedData as $name => $value)
  126. {
  127. if(str_starts_with($name, '@')) $events[substr($name, 1)] = $value;
  128. }
  129. return $events;
  130. }
  131. public function hasEvent()
  132. {
  133. foreach($this->storedData as $name => $value)
  134. {
  135. if(str_starts_with($name, '@') && $name !== '@init') return true;
  136. }
  137. return false;
  138. }
  139. /**
  140. * Convert props to html string
  141. *
  142. * Example:
  143. *
  144. * // Properties data map:
  145. * $map = array(
  146. * 'id' => 'sayHelloBtn',
  147. * 'data-title' => 'Say "Hello"!',
  148. * 'data-content' => null,
  149. * 'data-show' => true,
  150. * );
  151. * // Output string: id="sayHelloBtn" data-title="Say &quot;Hello&quot;!" data-show="true"
  152. *
  153. * @access public
  154. * @param mixed[]|string $skipProps
  155. */
  156. public function toStr($skipProps = array())
  157. {
  158. if(is_string($skipProps)) $skipProps = explode(',', $skipProps);
  159. $pairs = array();
  160. if($this->class->count()) $pairs[] = 'class="' . $this->class->toStr() . '"';
  161. if($this->style->getCount(true)) $pairs[] = 'style="' . $this->style->toStr() . '"';
  162. $initCode = array();
  163. foreach($this->storedData as $name => $value)
  164. {
  165. /* Handle boolean attributes */
  166. if(in_array($name, static::$booleanAttrs)) $value = $value ? true : null;
  167. /* Skip any null value or events setting */
  168. if($value === null || in_array($name, $skipProps)) continue;
  169. if($name === 'zui-init' || str_starts_with($name, '@'))
  170. {
  171. $initCode[] = is_array($value) ? implode("\n", $value) : $value;
  172. continue;
  173. }
  174. /* Convert non-string to json */
  175. if(($value === true || $value === '') && !str_starts_with($name, 'data-'))
  176. {
  177. $pairs[] = $name;
  178. }
  179. else
  180. {
  181. if(!is_string($value)) $value = json_encode($value);
  182. $pairs[] = $name . '="' . static::encodeValue($value, str_starts_with($name, 'zui-create-')) . '"';
  183. }
  184. }
  185. if($initCode) $pairs[] = 'zui-init="$element.off(\'.zin.on\');' . static::encodeValue(implode(';', $initCode)) . '"';
  186. return implode(' ', $pairs);
  187. }
  188. /**
  189. * @param bool $skipEvents
  190. */
  191. public function toJSON($skipEvents = false)
  192. {
  193. $data = $this->storedData;
  194. $styleData = $this->style->get();
  195. if(!empty($styleData)) $data['style'] = $styleData;
  196. if(!empty($this->class->toJSON())) $data['class'] = $this->class->toStr();
  197. if($skipEvents)
  198. {
  199. foreach($data as $name => $value)
  200. {
  201. if(str_starts_with($name, '@')) unset($data[$name]);
  202. }
  203. }
  204. return $data;
  205. }
  206. /**
  207. * @param mixed[]|string $skipProps
  208. * @param bool $skipFalse
  209. */
  210. public function skip($skipProps = array(), $skipFalse = false)
  211. {
  212. if(is_string($skipProps)) $skipProps = explode(',', $skipProps);
  213. $data = $this->toJSON();
  214. foreach($data as $name => $value)
  215. {
  216. if($value === null || in_array($name, $skipProps)) unset($data[$name]);
  217. if($skipFalse && $value === false) unset($data[$name]);
  218. }
  219. return $data;
  220. }
  221. /**
  222. * @param mixed[]|string $firstListProps
  223. */
  224. public function split($firstListProps = array())
  225. {
  226. if(is_string($firstListProps)) $firstListProps = explode(',', $firstListProps);
  227. $data = $this->toJSON();
  228. $firstList = array();
  229. $restList = array();
  230. foreach($data as $name => $value)
  231. {
  232. if(in_array($name, $firstListProps)) $firstList[$name] = $value;
  233. else $restList[$name] = $value;
  234. }
  235. return array($firstList, $restList);
  236. }
  237. /**
  238. * @param mixed[]|string $pickProps
  239. */
  240. public function pick($pickProps = array())
  241. {
  242. if(is_string($pickProps)) $pickProps = explode(',', $pickProps);
  243. $data = $this->toJSON();
  244. foreach($data as $name => $value)
  245. {
  246. if($value === null || !in_array($name, $pickProps)) unset($data[$name]);
  247. }
  248. return $data;
  249. }
  250. /**
  251. * Clone a new instance
  252. *
  253. * @access public
  254. * @return props
  255. */
  256. public function copy()
  257. {
  258. $props = new props($this->storedData);
  259. $props->style = clone $this->style;
  260. $props->class = clone $this->class;
  261. return $props;
  262. }
  263. /**
  264. * @param mixed $value
  265. * @param bool $doubleEncode
  266. */
  267. public static function encodeValue($value, $doubleEncode = false)
  268. {
  269. return htmlspecialchars($value, ENT_COMPAT | ENT_SUBSTITUTE | ENT_HTML5, null, $doubleEncode);
  270. }
  271. }