EER Diagram
Source Code - View: _form.php
<?php
use yii\helpers\Html;
use yii\bootstrap\ActiveForm;
use wbraganca\dynamicform\DynamicFormWidget;
?>
<div class="customer-form">
<?php $form = ActiveForm::begin([
'enableClientValidation' => false,
'enableAjaxValidation' => true,
'validateOnChange' => true,
'validateOnBlur' => false,
'options' => [
'enctype' => 'multipart/form-data',
'id' => 'dynamic-form'
]
]); ?>
<?= $form->field($modelCatalogOption, 'name')->textInput(['maxlength' => true]) ?>
<div class="padding-v-md">
<div class="line line-dashed"></div>
</div>
<?= $this->render('_form_option_values', [
'form' => $form,
'modelCatalogOption' => $modelCatalogOption,
'modelsOptionValue' => $modelsOptionValue
]) ?>
<div class="form-group">
<?= Html::submitButton($modelCatalogOption->isNewRecord ? 'Create' : 'Update', ['class' => 'btn btn-primary']) ?>
</div>
<?php ActiveForm::end(); ?>
</div>
Source Code - View: _form_option_values.php
<?php
use yii\helpers\Html;
use yii\helpers\Url;
use yii\jui\JuiAsset;
use yii\web\JsExpression;
use kartik\widgets\FileInput;
use app\modules\yii2extensions\models\Image;
use wbraganca\dynamicform\DynamicFormWidget;
?>
<div id="panel-option-values" class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-check-square-o"></i> Option values</h3>
</div>
<?php DynamicFormWidget::begin([
'widgetContainer' => 'dynamicform_wrapper',
'widgetBody' => '.form-options-body',
'widgetItem' => '.form-options-item',
'min' => 1,
'insertButton' => '.add-item',
'deleteButton' => '.delete-item',
'model' => $modelsOptionValue[0],
'formId' => 'dynamic-form',
'formFields' => [
'name',
'img'
],
]); ?>
<table class="table table-bordered table-striped margin-b-none">
<thead>
<tr>
<th style="width: 90px; text-align: center"></th>
<th class="required">Option value name</th>
<th style="width: 188px;">Image</th>
<th style="width: 90px; text-align: center">Actions</th>
</tr>
</thead>
<tbody class="form-options-body">
<?php foreach ($modelsOptionValue as $index => $modelOptionValue): ?>
<tr class="form-options-item">
<td class="sortable-handle text-center vcenter" style="cursor: move;">
<i class="fa fa-arrows"></i>
</td>
<td class="vcenter">
<?= $form->field($modelOptionValue, "[{$index}]name")->label(false)->textInput(['maxlength' => 128]); ?>
</td>
<td>
<?php if (!$modelOptionValue->isNewRecord): ?>
<?= Html::activeHiddenInput($modelOptionValue, "[{$index}]id"); ?>
<?= Html::activeHiddenInput($modelOptionValue, "[{$index}]image_id"); ?>
<?= Html::activeHiddenInput($modelOptionValue, "[{$index}]deleteImg"); ?>
<?php endif; ?>
<?php
$modelImage = Image::findOne(['id' => $modelOptionValue->image_id]);
$initialPreview = [];
if ($modelImage) {
$pathImg = Yii::$app->fileStorage->baseUrl . '/' . $modelImage->path;
$initialPreview[] = Html::img($pathImg, ['class' => 'file-preview-image']);
}
?>
<?= $form->field($modelOptionValue, "[{$index}]img")->label(false)->widget(FileInput::classname(), [
'options' => [
'multiple' => false,
'accept' => 'image/*',
'class' => 'optionvalue-img'
],
'pluginOptions' => [
'previewFileType' => 'image',
'showCaption' => false,
'showUpload' => false,
'browseClass' => 'btn btn-default btn-sm',
'browseLabel' => ' Pick image',
'browseIcon' => '<i class="glyphicon glyphicon-picture"></i>',
'removeClass' => 'btn btn-danger btn-sm',
'removeLabel' => ' Delete',
'removeIcon' => '<i class="fa fa-trash"></i>',
'previewSettings' => [
'image' => ['width' => '138px', 'height' => 'auto']
],
'initialPreview' => $initialPreview,
'layoutTemplates' => ['footer' => '']
]
]) ?>
</td>
<td class="text-center vcenter">
<button type="button" class="delete-item btn btn-danger btn-xs"><i class="fa fa-minus"></i></button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot>
<tr>
<td colspan="3"></td>
<td><button type="button" class="add-item btn btn-success btn-sm"><span class="fa fa-plus"></span> New</button></td>
</tr>
</tfoot>
</table>
<?php DynamicFormWidget::end(); ?>
</div>
<?php
$js = <<<'EOD'
$(".optionvalue-img").on("filecleared", function(event) {
var regexID = /^(.+?)([-\d-]{1,})(.+)$/i;
var id = event.target.id;
var matches = id.match(regexID);
if (matches && matches.length === 4) {
var identifiers = matches[2].split("-");
$("#optionvalue-" + identifiers[1] + "-deleteimg").val("1");
}
});
var fixHelperSortable = function(e, ui) {
ui.children().each(function() {
$(this).width($(this).width());
});
return ui;
};
$(".form-options-body").sortable({
items: "tr",
cursor: "move",
opacity: 0.6,
axis: "y",
handle: ".sortable-handle",
helper: fixHelperSortable,
update: function(ev){
$(".dynamicform_wrapper").yiiDynamicForm("updateContainer");
}
}).disableSelection();
EOD;
JuiAsset::register($this);
$this->registerJs($js);
?>
Source Code - Controller
<?php
namespace app\modules\yii2extensions\controllers;
use Yii;
use yii\filters\VerbFilter;
use yii\helpers\ArrayHelper;
use yii\web\NotFoundHttpException;
use yii\web\Response;
use yii\widgets\ActiveForm;
use app\base\Model;
use app\base\Controller;
use app\modules\yii2extensions\models\CatalogOption;
use app\modules\yii2extensions\models\Image;
use app\modules\yii2extensions\models\OptionValue;
use app\modules\yii2extensions\models\query\CatalogOptionQuery;
/**
* DynamicformDemo1Controller implements the CRUD actions for CatalogOption model.
*/
class DynamicformDemo2Controller extends Controller
{
/**
* Lists all CatalogOption models.
* @return mixed
*/
public function actionIndex()
{
$searchModel = new CatalogOptionQuery();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
return $this->render('index', [
'searchModel' => $searchModel,
'dataProvider' => $dataProvider,
]);
}
/**
* Displays a single CatalogOption model.
* @param integer $id
* @return mixed
*/
public function actionView($id)
{
$model = $this->findModel($id);
$optionValues = $model->optionValues;
return $this->render('view', [
'model' => $model,
'optionValues' => $optionValues,
]);
}
/**
* Creates a new CatalogOption model.
* If creation is successful, the browser will be redirected to the 'view' page.
* @return mixed
*/
public function actionCreate()
{
$modelCatalogOption = new CatalogOption;
$modelsOptionValue = [new OptionValue];
if ($modelCatalogOption->load(Yii::$app->request->post())) {
$modelsOptionValue = Model::createMultiple(OptionValue::classname());
Model::loadMultiple($modelsOptionValue, Yii::$app->request->post());
foreach ($modelsOptionValue as $index => $modelOptionValue) {
$modelOptionValue->sort_order = $index;
$modelOptionValue->img = \yii\web\UploadedFile::getInstance($modelOptionValue, "[{$index}]img");
}
// ajax validation
if (Yii::$app->request->isAjax) {
Yii::$app->response->format = Response::FORMAT_JSON;
return ArrayHelper::merge(
ActiveForm::validateMultiple($modelsOptionValue),
ActiveForm::validate($modelCatalogOption)
);
}
// validate all models
$valid = $modelCatalogOption->validate();
$valid = Model::validateMultiple($modelsOptionValue) && $valid;
if ($valid) {
$transaction = \Yii::$app->db->beginTransaction();
try {
if ($flag = $modelCatalogOption->save(false)) {
foreach ($modelsOptionValue as $modelOptionValue) {
$modelOptionValue->catalog_option_id = $modelCatalogOption->id;
if (($flag = $modelOptionValue->save(false)) === false) {
$transaction->rollBack();
break;
}
}
}
if ($flag) {
$transaction->commit();
return $this->redirect(['view', 'id' => $modelCatalogOption->id]);
}
} catch (Exception $e) {
$transaction->rollBack();
}
}
}
return $this->render('create', [
'modelCatalogOption' => $modelCatalogOption,
'modelsOptionValue' => (empty($modelsOptionValue)) ? [new OptionValue] : $modelsOptionValue
]);
}
/**
* Updates an existing CatalogOption model.
* If update is successful, the browser will be redirected to the 'view' page.
* @param integer $id
* @return mixed
*/
public function actionUpdate($id)
{
$modelCatalogOption = $this->findModel($id);
$modelsOptionValue = $modelCatalogOption->optionValues;
if ($modelCatalogOption->load(Yii::$app->request->post())) {
$oldIDs = ArrayHelper::map($modelsOptionValue, 'id', 'id');
$modelsOptionValue = Model::createMultiple(OptionValue::classname(), $modelsOptionValue);
Model::loadMultiple($modelsOptionValue, Yii::$app->request->post());
$deletedIDs = array_diff($oldIDs, array_filter(ArrayHelper::map($modelsOptionValue, 'id', 'id')));
foreach ($modelsOptionValue as $index => $modelOptionValue) {
$modelOptionValue->sort_order = $index;
$modelOptionValue->img = \yii\web\UploadedFile::getInstance($modelOptionValue, "[{$index}]img");
}
// ajax validation
if (Yii::$app->request->isAjax) {
Yii::$app->response->format = Response::FORMAT_JSON;
return ArrayHelper::merge(
ActiveForm::validateMultiple($modelsOptionValue),
ActiveForm::validate($modelCatalogOption)
);
}
// validate all models
$valid = $modelCatalogOption->validate();
$valid = Model::validateMultiple($modelsOptionValue) && $valid;
if ($valid) {
$transaction = \Yii::$app->db->beginTransaction();
try {
if ($flag = $modelCatalogOption->save(false)) {
if (!empty($deletedIDs)) {
$flag = OptionValue::deleteByIDs($deletedIDs);
}
if ($flag) {
foreach ($modelsOptionValue as $modelOptionValue) {
$modelOptionValue->catalog_option_id = $modelCatalogOption->id;
if (($flag = $modelOptionValue->save(false)) === false) {
$transaction->rollBack();
break;
}
}
}
}
if ($flag) {
$transaction->commit();
return $this->redirect(['view', 'id' => $modelCatalogOption->id]);
}
} catch (Exception $e) {
$transaction->rollBack();
}
}
}
return $this->render('update', [
'modelCatalogOption' => $modelCatalogOption,
'modelsOptionValue' => (empty($modelsOptionValue)) ? [new OptionValue] : $modelsOptionValue
]);
}
/**
* Deletes an existing CatalogOption model.
* If deletion is successful, the browser will be redirected to the 'index' page.
* @param integer $id
* @return mixed
*/
public function actionDelete($id)
{
$model = $this->findModel($id);
$optonValuesIDs = ArrayHelper::map($model->optionValues, 'id', 'id');
OptionValue::deleteByIDs($optonValuesIDs);
$name = $model->name;
if ($model->delete()) {
Yii::$app->session->setFlash('success', 'Record <strong>"' . $name . '"</strong> deleted successfully.');
}
return $this->redirect(['index']);
}
/**
* Finds the CatalogOption model based on its primary key value.
* If the model is not found, a 404 HTTP exception will be thrown.
* @param integer $id
* @return CatalogOption the loaded model
* @throws NotFoundHttpException if the model cannot be found
*/
protected function findModel($id)
{
if (($model = CatalogOption::findOne($id)) !== null) {
return $model;
} else {
throw new NotFoundHttpException('The requested page does not exist.');
}
}
}