Skip to content

Commit 7ea250a

Browse files
committed
separate ListRenderer and DivRenderer for BC reasons
1 parent c33eadb commit 7ea250a

File tree

2 files changed

+454
-87
lines changed

2 files changed

+454
-87
lines changed

src/renderers/DivRenderer.php

Lines changed: 399 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,399 @@
1+
<?php
2+
3+
/**
4+
* @link https://github.com/unclead/yii2-multiple-input
5+
* @copyright Copyright (c) 2014 unclead
6+
* @license https://github.com/unclead/yii2-multiple-input/blob/master/LICENSE.md
7+
*/
8+
9+
namespace unclead\multipleinput\renderers;
10+
11+
use yii\base\InvalidConfigException;
12+
use yii\db\ActiveRecordInterface;
13+
use yii\helpers\ArrayHelper;
14+
use yii\helpers\Html;
15+
use unclead\multipleinput\components\BaseColumn;
16+
use yii\helpers\UnsetArrayValue;
17+
18+
/**
19+
* Class DivRenderer is a list renderer who use divs
20+
* @package unclead\multipleinput\renderers
21+
*/
22+
class DivRenderer extends BaseRenderer
23+
{
24+
/**
25+
* @return mixed
26+
* @throws InvalidConfigException
27+
*/
28+
protected function internalRender()
29+
{
30+
$content = [];
31+
32+
$content[] = $this->renderHeader();
33+
$content[] = $this->renderBody();
34+
$content[] = $this->renderFooter();
35+
36+
$options = [];
37+
Html::addCssClass($options, 'multiple-input-list list-renderer');
38+
39+
if ($this->isBootstrapTheme()) {
40+
Html::addCssClass($options, 'form-horizontal');
41+
}
42+
43+
$content = Html::tag('div', implode("\n", $content), $options);
44+
45+
return Html::tag('div', $content, [
46+
'id' => $this->id,
47+
'class' => 'multiple-input'
48+
]);
49+
}
50+
51+
/**
52+
* Renders the header.
53+
*
54+
* @return string
55+
*/
56+
public function renderHeader()
57+
{
58+
if (!$this->isAddButtonPositionHeader()) {
59+
return '';
60+
}
61+
62+
$options = ['class' => 'list-cell__button'];
63+
$layoutConfig = array_merge([
64+
'buttonAddClass' => $this->isBootstrapTheme() ? 'col-sm-offset-9 col-sm-3' : '',
65+
], $this->layoutConfig);
66+
Html::addCssClass($options, $layoutConfig['buttonAddClass']);
67+
68+
return Html::tag('div', $this->renderAddButton(), $options);
69+
}
70+
71+
/**
72+
* Renders the footer.
73+
*
74+
* @return string
75+
*/
76+
public function renderFooter()
77+
{
78+
if (!$this->isAddButtonPositionFooter()) {
79+
return '';
80+
}
81+
82+
$options = ['class' => 'list-cell__button'];
83+
$layoutConfig = array_merge([
84+
'buttonAddClass' => $this->isBootstrapTheme() ? 'col-sm-offset-9 col-sm-3' : '',
85+
], $this->layoutConfig);
86+
Html::addCssClass($options, $layoutConfig['buttonAddClass']);
87+
88+
return Html::tag('div', $this->renderAddButton(), $options);
89+
}
90+
91+
/**
92+
* Renders the body.
93+
*
94+
* @return string
95+
* @throws \yii\base\InvalidConfigException
96+
* @throws \yii\base\InvalidParamException
97+
*/
98+
protected function renderBody()
99+
{
100+
$rows = [];
101+
102+
if ($this->data) {
103+
$j = 0;
104+
foreach ($this->data as $index => $item) {
105+
if ($j++ <= $this->max) {
106+
$rows[] = $this->renderRowContent($index, $item);
107+
} else {
108+
break;
109+
}
110+
}
111+
for ($i = $j; $i < $this->min; $i++) {
112+
$rows[] = $this->renderRowContent($i);
113+
}
114+
} elseif ($this->min > 0) {
115+
for ($i = 0; $i < $this->min; $i++) {
116+
$rows[] = $this->renderRowContent($i);
117+
}
118+
}
119+
120+
return implode("\n", $rows);
121+
}
122+
123+
/**
124+
* Renders the row content.
125+
*
126+
* @param int $index
127+
* @param ActiveRecordInterface|array $item
128+
* @return mixed
129+
*/
130+
private function renderRowContent($index = null, $item = null)
131+
{
132+
$elements = [];
133+
$columnIndex = 0;
134+
foreach ($this->columns as $column) {
135+
/* @var $column BaseColumn */
136+
$column->setModel($item);
137+
$elements[] = $this->renderCellContent($column, $index, $columnIndex++);
138+
}
139+
140+
$content = Html::tag('div', implode("\n", $elements), $this->prepareRowOptions($index, $item));
141+
if ($index !== null) {
142+
$content = str_replace('{' . $this->getIndexPlaceholder() . '}', $index, $content);
143+
}
144+
145+
return $content;
146+
}
147+
148+
/**
149+
* Prepares the row options.
150+
*
151+
* @param int $index
152+
* @param ActiveRecordInterface|array $item
153+
* @return array
154+
*/
155+
protected function prepareRowOptions($index, $item)
156+
{
157+
if (is_callable($this->rowOptions)) {
158+
$options = call_user_func($this->rowOptions, $item, $index, $this->context);
159+
} else {
160+
$options = $this->rowOptions;
161+
}
162+
163+
Html::addCssClass($options, 'multiple-input-list__item');
164+
165+
return $options;
166+
}
167+
168+
/**
169+
* Renders the cell content.
170+
*
171+
* @param BaseColumn $column
172+
* @param int|null $index
173+
* @param int|null $columnIndex
174+
* @return string
175+
* @throws \Exception
176+
*/
177+
public function renderCellContent($column, $index, $columnIndex = null)
178+
{
179+
$id = $column->getElementId($index);
180+
$name = $column->getElementName($index);
181+
182+
/**
183+
* This class inherits iconMap from BaseRenderer
184+
* If the input to be rendered is a drag column, we give it the appropriate icon class
185+
* via the $options array
186+
*/
187+
$options = ['id' => $id];
188+
if (substr($id, -4) === 'drag') {
189+
$options = ArrayHelper::merge($options, ['class' => $this->iconMap['drag-handle']]);
190+
}
191+
$input = $column->renderInput($name, $options, [
192+
'id' => $id,
193+
'name' => $name,
194+
'indexPlaceholder' => $this->getIndexPlaceholder(),
195+
'index' => $index,
196+
'columnIndex' => $columnIndex,
197+
'context' => $this->context,
198+
]);
199+
200+
if ($column->isHiddenInput()) {
201+
return $input;
202+
}
203+
204+
$layoutConfig = array_merge([
205+
'offsetClass' => $this->isBootstrapTheme() ? 'col-sm-offset-3' : '',
206+
'labelClass' => $this->isBootstrapTheme() ? 'col-sm-3' : '',
207+
'wrapperClass' => $this->isBootstrapTheme() ? 'col-sm-6' : '',
208+
'errorClass' => $this->isBootstrapTheme() ? 'col-sm-offset-3 col-sm-6' : '',
209+
], $this->layoutConfig);
210+
211+
Html::addCssClass($column->errorOptions, $layoutConfig['errorClass']);
212+
213+
$hasError = false;
214+
$error = '';
215+
216+
if ($index !== null) {
217+
$error = $column->getFirstError($index);
218+
$hasError = !empty($error);
219+
}
220+
221+
$wrapperOptions = [];
222+
223+
if ($hasError) {
224+
Html::addCssClass($wrapperOptions, 'has-error');
225+
}
226+
227+
Html::addCssClass($wrapperOptions, $layoutConfig['wrapperClass']);
228+
229+
$options = [
230+
'class' => "field-$id list-cell__$column->name" . ($hasError ? ' has-error' : '')
231+
];
232+
233+
if ($this->isBootstrapTheme()) {
234+
Html::addCssClass($options, 'form-group');
235+
}
236+
237+
if (is_callable($column->columnOptions)) {
238+
$columnOptions = call_user_func($column->columnOptions, $column->getModel(), $index, $this->context);
239+
} else {
240+
$columnOptions = $column->columnOptions;
241+
}
242+
243+
$options = array_merge_recursive($options, $columnOptions);
244+
245+
$content = Html::beginTag('div', $options);
246+
247+
if (empty($column->title)) {
248+
Html::addCssClass($wrapperOptions, $layoutConfig['offsetClass']);
249+
} else {
250+
$labelOptions = ['class' => $layoutConfig['labelClass']];
251+
if ($this->isBootstrapTheme()) {
252+
Html::addCssClass($labelOptions, 'control-label');
253+
}
254+
255+
$content .= Html::label($column->title, $id, $labelOptions);
256+
}
257+
258+
$content .= Html::tag('div', $input, $wrapperOptions);
259+
260+
// first line
261+
if ($columnIndex == 0) {
262+
if ($this->max !== $this->min) {
263+
$content .= $this->renderActionColumn($index);
264+
}
265+
if ($this->cloneButton) {
266+
$content .= $this->renderCloneColumn();
267+
}
268+
}
269+
270+
if ($column->enableError) {
271+
$content .= "\n" . $column->renderError($error);
272+
}
273+
274+
$content .= Html::endTag('div');
275+
276+
return $content;
277+
}
278+
279+
/**
280+
* Renders the action column.
281+
*
282+
* @param null|int $index
283+
* @param null|ActiveRecordInterface|array $item
284+
* @return string
285+
*/
286+
private function renderActionColumn($index = null, $item = null)
287+
{
288+
$content = $this->getActionButton($index) . $this->getExtraButtons($index, $item);
289+
290+
$options = ['class' => 'list-cell__button'];
291+
$layoutConfig = array_merge([
292+
'buttonActionClass' => $this->isBootstrapTheme() ? 'col-sm-offset-0 col-sm-2' : '',
293+
], $this->layoutConfig);
294+
Html::addCssClass($options, $layoutConfig['buttonActionClass']);
295+
296+
return Html::tag('div', $content, $options);
297+
}
298+
299+
/**
300+
* Renders the clone column.
301+
*
302+
* @return string
303+
*/
304+
private function renderCloneColumn()
305+
{
306+
307+
$options = ['class' => 'list-cell__button'];
308+
$layoutConfig = array_merge([
309+
'buttonCloneClass' => $this->isBootstrapTheme() ? 'col-sm-offset-0 col-sm-1' : '',
310+
], $this->layoutConfig);
311+
Html::addCssClass($options, $layoutConfig['buttonCloneClass']);
312+
313+
return Html::tag('div', $this->renderCloneButton(), $options);
314+
}
315+
316+
private function getActionButton($index)
317+
{
318+
if ($index === null || $this->min === 0) {
319+
return $this->renderRemoveButton();
320+
}
321+
322+
$index++;
323+
if ($index < $this->min) {
324+
return '';
325+
}
326+
327+
if ($index === $this->min) {
328+
return $this->isAddButtonPositionRow() ? $this->renderAddButton() : '';
329+
}
330+
331+
return $this->renderRemoveButton();
332+
}
333+
334+
private function renderAddButton()
335+
{
336+
$options = [
337+
'class' => 'multiple-input-list__btn js-input-plus',
338+
];
339+
Html::addCssClass($options, $this->addButtonOptions['class']);
340+
341+
return Html::tag('div', $this->addButtonOptions['label'], $options);
342+
}
343+
344+
/**
345+
* Renders remove button.
346+
*
347+
* @return string
348+
*/
349+
private function renderRemoveButton()
350+
{
351+
$options = [
352+
'class' => 'multiple-input-list__btn js-input-remove',
353+
];
354+
Html::addCssClass($options, $this->removeButtonOptions['class']);
355+
356+
return Html::tag('div', $this->removeButtonOptions['label'], $options);
357+
}
358+
359+
/**
360+
* Renders clone button.
361+
*
362+
* @return string
363+
*/
364+
private function renderCloneButton()
365+
{
366+
$options = [
367+
'class' => 'multiple-input-list__btn js-input-clone',
368+
];
369+
Html::addCssClass($options, $this->cloneButtonOptions['class']);
370+
371+
return Html::tag('div', $this->cloneButtonOptions['label'], $options);
372+
}
373+
374+
/**
375+
* Returns template for using in js.
376+
*
377+
* @return string
378+
*
379+
* @throws \yii\base\InvalidConfigException
380+
*/
381+
protected function prepareTemplate()
382+
{
383+
return $this->renderRowContent();
384+
}
385+
386+
/**
387+
* Returns an array of JQuery sortable plugin options for DivRenderer
388+
* @return array
389+
*/
390+
protected function getJsSortableOptions()
391+
{
392+
return ArrayHelper::merge(parent::getJsSortableOptions(),
393+
[
394+
'containerSelector' => '.list-renderer',
395+
'itemPath' => new UnsetArrayValue,
396+
'itemSelector' => '.multiple-input-list__item',
397+
]);
398+
}
399+
}

0 commit comments

Comments
 (0)