PageRenderTime 28ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/select2/Plugin.class.php

https://github.com/bgerp/bgerp
PHP | 445 lines | 255 code | 94 blank | 96 comment | 63 complexity | ca4f4125d5184d500637f2de3f625470 MD5 | raw file
  1. <?php
  2. /**
  3. * Плъгин за превръщане на keylist полетата в select2
  4. *
  5. * @category bgerp
  6. * @package selec2
  7. *
  8. * @author Yusein Yuseinov <yyuseinov@gmail.com>
  9. * @copyright 2006 - 2015 Experta OOD
  10. * @license GPL 3
  11. *
  12. * @since v 0.1
  13. */
  14. class select2_Plugin extends core_Plugin
  15. {
  16. /**
  17. * Името на hidden полето
  18. */
  19. protected static $hiddenName = 'select2';
  20. /**
  21. * Дали да може да се въвежда повече от 1 елемент
  22. */
  23. protected static $isMultiple = true;
  24. /**
  25. * Името на класа на елементите, за които ще се стартира плъгина
  26. */
  27. protected static $className = 'select2';
  28. /**
  29. * Дали може да се изчистват всичките записи едновременно
  30. */
  31. protected static $allowClear = true;
  32. /**
  33. * Минималния брой елементи над които да се стартира select2
  34. */
  35. protected static $minItems = 1;
  36. /**
  37. * Броя на опциите, преди обработка
  38. */
  39. protected static $suggCnt = null;
  40. /**
  41. * Изпълнява се преди рендирането на input
  42. *
  43. * @param type_Keylist $invoker
  44. * @param core_ET $tpl
  45. * @param string $name
  46. * @param string|array|NULL $value
  47. * @param array $attr
  48. */
  49. public function on_BeforeRenderInput(&$invoker, &$tpl, $name, &$value, &$attr = array())
  50. {
  51. // Премамахваме от масива елемента от hidden полето
  52. if (is_array($value) && isset($value[self::$hiddenName])) {
  53. unset($value[self::$hiddenName]);
  54. $value1 = array();
  55. foreach ($value as $id => $v) {
  56. $value1[$v] = $v;
  57. }
  58. $value = $value1;
  59. }
  60. ht::setUniqId($attr);
  61. if (!isset($invoker->suggestions)) {
  62. $invoker->prepareSuggestions();
  63. if (!$invoker->suggestions && isset($invoker->options)) {
  64. $invoker->suggestions = $invoker->options;
  65. }
  66. }
  67. self::$suggCnt = countR($invoker->suggestions);
  68. $maxSuggestions = $invoker->getMaxSuggestions();
  69. // Ако няма да се показват всички възможност стойности, а ще се извличат по AJAX
  70. if (!$invoker->params['parentId'] && (self::$suggCnt > $maxSuggestions)) {
  71. // Подготвяме опциите за кеширане
  72. self::setHandler($invoker, $value);
  73. $cSugg = self::prepareSuggestionsForCache($invoker);
  74. core_Cache::set('keylist', $invoker->handler, $cSugg, 20, $invoker->params['mvc']);
  75. // Ако има избрани стойности, винаги да са включени в опциите и да се показват най-отгоре
  76. $sValArr = array();
  77. if (isset($value)) {
  78. $vArr = $invoker->toArray($value);
  79. foreach ($vArr as $v) {
  80. $sValArr[$v] = $invoker->suggestions[$v];
  81. unset($invoker->suggestions[$v]);
  82. }
  83. }
  84. // Опитваме се да покажем толкова на брой опции, колкото са зададени
  85. $rSugg = $maxSuggestions - countR($sValArr);
  86. if ($rSugg <= 0) {
  87. $rSugg = $maxSuggestions;
  88. }
  89. $invoker->suggestions = array_slice($invoker->suggestions, 0, $rSugg, true);
  90. // Ако последният елемент е група, премахваме от списъка
  91. $endElement = end($invoker->suggestions);
  92. if ($rSugg > 2 && is_object($endElement) && $endElement->group) {
  93. array_pop($invoker->suggestions);
  94. }
  95. if (!empty($sValArr)) {
  96. $invoker->suggestions = $sValArr + $invoker->suggestions;
  97. }
  98. }
  99. }
  100. /**
  101. * Изпълнява се след рендирането на input
  102. *
  103. * @param type_Keylist $invoker
  104. * @param core_ET $tpl
  105. * @param string $name
  106. * @param string|array|NULL $value
  107. * @param array $attr
  108. */
  109. public function on_AfterRenderInput(&$invoker, &$tpl, $name, $value, &$attr = array())
  110. {
  111. if ($invoker->params['isReadOnly']) {
  112. return ;
  113. }
  114. $minItems = isset($invoker->params['select2MinItems']) ? $invoker->params['select2MinItems'] : self::$minItems;
  115. $optArr = isset($invoker->suggestions) ? $invoker->suggestions : $invoker->options;
  116. $cnt = self::$suggCnt;
  117. if (!isset($cnt)) {
  118. if (isset($invoker->suggestions)) {
  119. $cnt = countR($invoker->suggestions);
  120. } else {
  121. $cnt = countR($invoker->options);
  122. }
  123. }
  124. // Ако нямаме JS или има много малко предложения - не правим нищо
  125. if (Mode::is('javascript', 'no') || (($cnt) <= $minItems)) {
  126. return ;
  127. }
  128. // Ако все още няма id
  129. if (!$attr['id']) {
  130. $attr['id'] = str::getRand('aaaaaaaa');
  131. }
  132. $options = new ET();
  133. $mustCloseGroup = false;
  134. // Ако е дървовидна структура
  135. $parentIdName = $invoker->params['parentId'];
  136. if ($parentIdName) {
  137. // Подготовка на данните
  138. $keys = '';
  139. foreach($optArr as $id => $title) {
  140. $keys .= ($keys ? ',' : '') . $id;
  141. }
  142. $mvc = &cls::get($invoker->params['mvc']);
  143. $query = $mvc->getQuery();
  144. $query->show($parentIdName);
  145. $dataPup = array();
  146. $dataL = array();
  147. while($rec = $query->fetch("#id IN ({$keys})")) {
  148. if ($rec->{$parentIdName}) {
  149. $dataPup[$rec->id] = $rec->{$parentIdName};
  150. $dataL[$rec->id] = 2;
  151. $dataNonLeaf[$rec->{$parentIdName}] = $rec->{$parentIdName};
  152. }
  153. }
  154. // Определяме нивото в зависимост от parentId
  155. foreach ($dataPup as $id => $pId) {
  156. if ($dataL[$pId]) {
  157. $mCnt = 20;
  158. while (true) {
  159. if ($dataL[$pId]) {
  160. $dataL[$id]++;
  161. $pId = $dataPup[$pId];
  162. if (!--$mCnt) break;
  163. } else {
  164. break;
  165. }
  166. }
  167. }
  168. }
  169. }
  170. // Преобразуваме опциите в селекти
  171. foreach ((array) $optArr as $key => $val) {
  172. $optionsAttrArr = array();
  173. if (is_object($val)) {
  174. if ($val->group) {
  175. if ($mustCloseGroup) {
  176. $options->append("</optgroup>\n");
  177. }
  178. $val->title = htmlspecialchars($val->title);
  179. $options->append("<optgroup label=\"{$val->title}\">\n");
  180. $mustCloseGroup = true;
  181. continue;
  182. }
  183. $optionsAttrArr = $val->attr;
  184. $val = $val->title;
  185. }
  186. $newKey = "|{$key}|";
  187. if (is_array($value)) {
  188. if ($value[$key]) {
  189. $optionsAttrArr['selected'] = 'selected';
  190. }
  191. } else {
  192. if (strstr($value, $newKey)) {
  193. $optionsAttrArr['selected'] = 'selected';
  194. }
  195. }
  196. // Добавяме нужните класове
  197. if ($parentIdName) {
  198. if ($dataPup[$key]) {
  199. $optionsAttrArr['data-pup'] = $dataPup[$key];
  200. $optionsAttrArr['class'] = "l" . $dataL[$key];
  201. } else {
  202. $optionsAttrArr['class'] = "l1";
  203. }
  204. if ($dataNonLeaf[$key]) {
  205. $optionsAttrArr['class'] .= " non-leaf";
  206. }
  207. }
  208. $optionsAttrArr['value'] = $key;
  209. $options->append(ht::createElement('option', $optionsAttrArr, $val));
  210. }
  211. if ($mustCloseGroup) {
  212. $options->append("</optgroup>\n");
  213. }
  214. // Създаваме нов select
  215. $selectAttrArray = array();
  216. if (isset($invoker->params['select2Multiple'])) {
  217. if ($invoker->params['select2Multiple']) {
  218. $selectAttrArray['multiple'] = 'multiple';
  219. }
  220. } elseif (self::$isMultiple) {
  221. $selectAttrArray['multiple'] = 'multiple';
  222. }
  223. $selectAttrArray['class'] = self::$className . ' ' . $attr['class'];
  224. $selectAttrArray['id'] = $attr['id'];
  225. $selectAttrArray['name'] = $name . '[]';
  226. $selectAttrArray['style'] = 'width:100%';
  227. $tpl = ht::createElement('select', $selectAttrArray, $options);
  228. $tpl->append("<input type='hidden' name='{$name}[" . self::$hiddenName . "]' value=1>");
  229. $select = ($attr['placeholder']) ? ($attr['placeholder']) : '';
  230. if ($invoker->params['allowEmpty']) {
  231. $allowClear = true;
  232. } else {
  233. if ($selectAttrArray['multiple']) {
  234. $allowClear = (self::$allowClear) ? (self::$allowClear) : false;
  235. } else {
  236. $allowClear = false;
  237. }
  238. }
  239. $maxSuggestions = $invoker->getMaxSuggestions();
  240. $ajaxUrl = '';
  241. if (!$invoker->params['parentId'] && $cnt > $maxSuggestions) {
  242. self::setHandler($invoker, $value);
  243. $ajaxUrl = toUrl(array($invoker, 'getOptions', 'hnd' => $invoker->handler, 'maxSugg' => $maxSuggestions, 'ajax_mode' => 1));
  244. }
  245. // Добавяме необходимите файлове и стартирам select2
  246. select2_Adapter::appendAndRun($tpl, $attr['id'], $select, $allowClear, null, $ajaxUrl, (boolean)$invoker->params['parentId'], $invoker->params['forceOpen']);
  247. return false;
  248. }
  249. /**
  250. * Задава манипулатор, който ще се използва за кеширане
  251. *
  252. * @param type_Keylist $invoker
  253. * @param string|array|NULL $val
  254. */
  255. protected static function setHandler(&$invoker, $val)
  256. {
  257. if (isset($invoker->handler)) {
  258. return ;
  259. }
  260. $invoker->handler = md5(serialize($invoker->suggestions) . '|' . serialize($val) . '|' . core_Lg::getCurrent());
  261. }
  262. /**
  263. * Подготвяме опциите за кеширане
  264. * Нормализира текста, в който ще се търси
  265. *
  266. * @param type_Keylist $invoker
  267. */
  268. protected static function prepareSuggestionsForCache(&$invoker)
  269. {
  270. $newSugg = array();
  271. foreach ($invoker->suggestions as $key => $sugg) {
  272. if (is_object($sugg)) {
  273. $suggV = $sugg->title;
  274. } else {
  275. $suggV = $sugg;
  276. }
  277. $newSugg[$key]['id'] = trim(preg_replace('/[^a-z0-9\*]+/', ' ', strtolower(str::utf2ascii($suggV))));
  278. $newSugg[$key]['title'] = $sugg;
  279. }
  280. return serialize($newSugg);
  281. }
  282. /**
  283. * Преди преобразуване данните от вербална стойност
  284. *
  285. * @param core_Type $type
  286. * @param string $res
  287. * @param array $value
  288. */
  289. public function on_BeforeFromVerbal($type, &$res, $value)
  290. {
  291. if (!is_array($value)) {
  292. return ;
  293. }
  294. // Преобразуваме масива с данни в keylist поле
  295. $valCnt = countR($value);
  296. if (($valCnt > 1) && (isset($value[self::$hiddenName]))) {
  297. unset($value[self::$hiddenName]);
  298. foreach ($value as $id => $val) {
  299. if (!ctype_digit(trim($id))) {
  300. $type->error = "Некоректен списък ${id} ";
  301. return false;
  302. }
  303. if (!empty($val)) {
  304. $res .= '|' . $val;
  305. }
  306. }
  307. if ($res) {
  308. $res = rtrim($res, '|');
  309. $res = $res . '|';
  310. }
  311. return false;
  312. }
  313. if (($valCnt == 1) && (isset($value[self::$hiddenName]))) {
  314. return false;
  315. }
  316. }
  317. /**
  318. * Връща максималния брой на опциите, които може да се избере
  319. *
  320. * @param type_Key $invoker
  321. * @param int|NULL $res
  322. */
  323. public function on_AfterGetMaxSuggestions($invoker, &$res)
  324. {
  325. setIfNot($res, $invoker->params['maxSuggestions'], core_Setup::get('TYPE_KEY_MAX_SUGGESTIONS', true), 1000);
  326. }
  327. /**
  328. * Отпечатва резултата от опциите в JSON формат
  329. *
  330. * @param type_Key $invoker
  331. * @param string|NULL|core_ET $res
  332. * @param string $action
  333. */
  334. public function on_BeforeAction($invoker, &$res, $action)
  335. {
  336. if ($action != 'getoptions') {
  337. return ;
  338. }
  339. if (!Request::get('ajax_mode')) {
  340. return ;
  341. }
  342. $hnd = Request::get('hnd');
  343. $maxSuggestions = Request::get('maxSugg', 'int');
  344. if (!$maxSuggestions) {
  345. $maxSuggestions = $invoker->getMaxSuggestions();
  346. }
  347. $q = Request::get('q');
  348. select2_Adapter::getAjaxRes('keylist', $hnd, $q, $maxSuggestions);
  349. return false;
  350. }
  351. }