CanCascadeFields.php 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. <?php
  2. namespace Dcat\Admin\Form\Field;
  3. use Dcat\Admin\Admin;
  4. use Dcat\Admin\Form;
  5. use Illuminate\Support\Arr;
  6. /**
  7. * @property Form $form
  8. */
  9. trait CanCascadeFields
  10. {
  11. /**
  12. * @var array
  13. */
  14. protected $conditions = [];
  15. /**
  16. * @var array
  17. */
  18. protected $cascadeGroups = [];
  19. /**
  20. * @param $operator
  21. * @param $value
  22. * @param $closure
  23. *
  24. * @return $this
  25. */
  26. public function when($operator, $value, $closure = null)
  27. {
  28. if (func_num_args() == 2) {
  29. $closure = $value;
  30. $value = $operator;
  31. $operator = $this->getDefaultOperator();
  32. }
  33. $this->formatValues($operator, $value);
  34. $this->addDependents($operator, $value, $closure);
  35. return $this;
  36. }
  37. protected function getDefaultOperator()
  38. {
  39. if ($this instanceof MultipleSelect || $this instanceof Checkbox) {
  40. return 'in';
  41. }
  42. return '=';
  43. }
  44. /**
  45. * @param string $operator
  46. * @param mixed $value
  47. */
  48. protected function formatValues(string $operator, &$value)
  49. {
  50. if (in_array($operator, ['in', 'notIn'])) {
  51. $value = Arr::wrap($value);
  52. }
  53. if (is_array($value)) {
  54. $value = array_map('strval', $value);
  55. } else {
  56. $value = strval($value);
  57. }
  58. }
  59. /**
  60. * @param string $operator
  61. * @param mixed $value
  62. * @param \Closure $closure
  63. */
  64. protected function addDependents(string $operator, $value, \Closure $closure)
  65. {
  66. $this->conditions[] = compact('operator', 'value', 'closure');
  67. $this->form->cascadeGroup($closure, [
  68. 'column' => $this->column(),
  69. 'index' => count($this->conditions) - 1,
  70. 'class' => $this->getCascadeClass($value, $operator),
  71. ]);
  72. }
  73. /**
  74. * @param mixed $value
  75. *
  76. * @return string
  77. */
  78. protected function getCascadeClass($value, string $operator)
  79. {
  80. if (is_array($value)) {
  81. $value = implode('-', $value);
  82. }
  83. $map = [
  84. '=' => '0',
  85. '>' => '1',
  86. '<' => '2',
  87. '!=' => '3',
  88. 'in' => '4',
  89. 'notIn' => '5',
  90. '>=' => '6',
  91. '<=' => '7',
  92. 'has' => '8',
  93. ];
  94. return sprintf('cascade-%s-%s-%s', $this->getElementClassString(), $value, $map[$operator]);
  95. }
  96. /**
  97. * Add cascade scripts to contents.
  98. *
  99. * @return void
  100. */
  101. protected function addCascadeScript()
  102. {
  103. if (empty($this->conditions)) {
  104. return;
  105. }
  106. $cascadeGroups = collect($this->conditions)->map(function ($condition) {
  107. return [
  108. 'class' => $this->getCascadeClass($condition['value'], $condition['operator']),
  109. 'operator' => $condition['operator'],
  110. 'value' => $condition['value'],
  111. ];
  112. })->toJson();
  113. $script = <<<JS
  114. (function () {
  115. var compare = function (a, b, o) {
  116. if (! $.isArray(b)) {
  117. return operator_table[o](a, b)
  118. }
  119. if (o === '!=') {
  120. var result = true;
  121. for (var i in b) {
  122. if (! operator_table[o](a, b[i])) {
  123. result = false;
  124. break;
  125. }
  126. }
  127. return result;
  128. }
  129. for (var i in b) {
  130. if (operator_table[o](a, b[i])) {
  131. return true;
  132. }
  133. }
  134. };
  135. var operator_table = {
  136. '=': function(a, b) {
  137. if ($.isArray(a) && $.isArray(b)) {
  138. return $(a).not(b).length === 0 && $(b).not(a).length === 0;
  139. }
  140. return String(a) === String(b);
  141. },
  142. '>': function(a, b) {
  143. return a > b;
  144. },
  145. '<': function(a, b) {
  146. return a < b;
  147. },
  148. '>=': function(a, b) { return a >= b; },
  149. '<=': function(a, b) { return a <= b; },
  150. '!=': function(a, b) {
  151. return ! operator_table['='](a, b);
  152. },
  153. 'in': function(a, b) { return Dcat.helpers.inObject(a, String(b), true); },
  154. 'notIn': function(a, b) { return ! Dcat.helpers.inObject(a, String(b), true); },
  155. 'has': function(a, b) { return Dcat.helpers.inObject(b, String(b), true); },
  156. };
  157. var cascade_groups = {$cascadeGroups}, event = '{$this->cascadeEvent}';
  158. $('{$this->getElementClassSelector()}').on(event, function (e) {
  159. {$this->getFormFrontValue()}
  160. cascade_groups.forEach(function (event) {
  161. var group = $('div.cascade-group.'+event.class);
  162. if (compare(checked, event.value, event.operator)) {
  163. group.removeClass('d-none');
  164. } else {
  165. group.addClass('d-none');
  166. }
  167. });
  168. }).trigger(event);
  169. })();
  170. JS;
  171. Admin::script($script);
  172. }
  173. /**
  174. * @return string
  175. */
  176. protected function getFormFrontValue()
  177. {
  178. switch (get_class($this)) {
  179. case Select::class:
  180. case MultipleSelect::class:
  181. return 'var checked = $(this).val();';
  182. case Radio::class:
  183. return <<<JS
  184. var checked = $('{$this->getElementClassSelector()}:checked').val();
  185. JS;
  186. case Checkbox::class:
  187. return <<<JS
  188. var checked = $('{$this->getElementClassSelector()}:checked').map(function(){
  189. return $(this).val();
  190. }).get();
  191. JS;
  192. default:
  193. throw new \InvalidArgumentException('Invalid form field type');
  194. }
  195. }
  196. }