arter/yii2-dynamicform

There is no license information available for the latest version (v2.0.2.4) of this package.

It is widget to yii2 framework to clone form elements in a nested manner, maintaining accessibility.

Maintainers

πŸ‘ art-er

Package info

repo.aster.it/elitedivision/yii2-dynamicform

Issues

Wiki

Type:yii2-extension

pkg:composer/arter/yii2-dynamicform

Statistics

Installs: 82

Dependents: 0

Suggesters: 0

v2.0.2.4 2022-05-04 16:00 UTC

Requires (Dev)

None

Suggests

None

Provides

None

Conflicts

None

Replaces

None

Unknown License ef57322479ee9e281635fff08ec79c87b165fa88

extensionwidgetyii2copy DOM elementyii2-dynamicformyii2 dynamic formwbraganca

This package is auto-updated.

Last update: 2026-06-13 20:30:35 UTC


README

πŸ‘ Latest Version
πŸ‘ Software Proscription
πŸ‘ Total Downloads

It is widget to yii2 framework to clone form elements in a nested manner, maintaining accessibility. πŸ‘ yii2-dynamicform

Installation

The preferred way to install this extension is through composer.

Either run

php composer.phar require --prefer-dist wbraganca/yii2-dynamicform "*"

or add

"wbraganca/yii2-dynamicform": "*"

to the require section of your composer.json file.

Demos

Usage

Hypothetical Scenario

πŸ‘ Database

The View

<?php
use yii\helpers\Html;
use yii\widgets\ActiveForm;
use wbraganca\dynamicform\DynamicFormWidget;
?>

<div class="customer-form">

 <?php $form = ActiveForm::begin(['id' => 'dynamic-form']); ?>
 <div class="row">
 <div class="col-sm-6">
 <?= $form->field($modelCustomer, 'first_name')->textInput(['maxlength' => true]) ?>
 </div>
 <div class="col-sm-6">
 <?= $form->field($modelCustomer, 'last_name')->textInput(['maxlength' => true]) ?>
 </div>
 </div>

 <div class="panel panel-default">
 <div class="panel-heading"><h4><i class="glyphicon glyphicon-envelope"></i> Addresses</h4></div>
 <div class="panel-body">
 <?php DynamicFormWidget::begin([
 'widgetContainer' => 'dynamicform_wrapper', // required: only alphanumeric characters plus "_" [A-Za-z0-9_]
 'widgetBody' => '.container-items', // required: css class selector
 'widgetItem' => '.item', // required: css class
 'limit' => 4, // the maximum times, an element can be cloned (default 999)
 'min' => 1, // 0 or 1 (default 1)
 'insertButton' => '.add-item', // css class
 'deleteButton' => '.remove-item', // css class
 'model' => $modelsAddress[0],
 'formId' => 'dynamic-form',
 'formFields' => [
 'full_name',
 'address_line1',
 'address_line2',
 'city',
 'state',
 'postal_code',
 ],
 ]); ?>

 <div class="container-items"><!-- widgetContainer -->
 <?php foreach ($modelsAddress as $i => $modelAddress): ?>
 <div class="item panel panel-default"><!-- widgetBody -->
 <div class="panel-heading">
 <h3 class="panel-title pull-left">Address</h3>
 <div class="pull-right">
 <button type="button" class="add-item btn btn-success btn-xs"><i class="glyphicon glyphicon-plus"></i></button>
 <button type="button" class="remove-item btn btn-danger btn-xs"><i class="glyphicon glyphicon-minus"></i></button>
 </div>
 <div class="clearfix"></div>
 </div>
 <div class="panel-body">
 <?php
 // necessary for update action.
 if (! $modelAddress->isNewRecord) {
 echo Html::activeHiddenInput($modelAddress, "[{$i}]id");
 }
 ?>
 <?= $form->field($modelAddress, "[{$i}]full_name")->textInput(['maxlength' => true]) ?>
 <div class="row">
 <div class="col-sm-6">
 <?= $form->field($modelAddress, "[{$i}]address_line1")->textInput(['maxlength' => true]) ?>
 </div>
 <div class="col-sm-6">
 <?= $form->field($modelAddress, "[{$i}]address_line2")->textInput(['maxlength' => true]) ?>
 </div>
 </div><!-- .row -->
 <div class="row">
 <div class="col-sm-4">
 <?= $form->field($modelAddress, "[{$i}]city")->textInput(['maxlength' => true]) ?>
 </div>
 <div class="col-sm-4">
 <?= $form->field($modelAddress, "[{$i}]state")->textInput(['maxlength' => true]) ?>
 </div>
 <div class="col-sm-4">
 <?= $form->field($modelAddress, "[{$i}]postal_code")->textInput(['maxlength' => true]) ?>
 </div>
 </div><!-- .row -->
 </div>
 </div>
 <?php endforeach; ?>
 </div>
 <?php DynamicFormWidget::end(); ?>
 </div>
 </div>

 <div class="form-group">
 <?= Html::submitButton($modelAddress->isNewRecord ? 'Create' : 'Update', ['class' => 'btn btn-primary']) ?>
 </div>

 <?php ActiveForm::end(); ?>

</div>

Javascript Events


$(".dynamicform_wrapper").on("beforeInsert", function(e, item) {
 console.log("beforeInsert");
});

$(".dynamicform_wrapper").on("afterInsert", function(e, item) {
 console.log("afterInsert");
});

$(".dynamicform_wrapper").on("beforeDelete", function(e, item) {
 if (! confirm("Are you sure you want to delete this item?")) {
 return false;
 }
 return true;
});

$(".dynamicform_wrapper").on("afterDelete", function(e) {
 console.log("Deleted item!");
});

$(".dynamicform_wrapper").on("limitReached", function(e, item) {
 alert("Limit reached");
});

The Controller (sample code)

<?php

namespace app\controllers;

use Yii;
use app\models\Customer;
use app\models\CustomerSearch;
use app\models\Address;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\filters\VerbFilter;
use app\base\Model;
use yii\web\Response;
use yii\widgets\ActiveForm;
use yii\helpers\ArrayHelper;

/**
 * CustomerController implements the CRUD actions for Customer model.
 */
class CustomerController extends Controller
{
 ...

 /**
 * Creates a new Customer model.
 * If creation is successful, the browser will be redirected to the 'view' page.
 * @return mixed
 */
 public function actionCreate()
 {
 $modelCustomer = new Customer;
 $modelsAddress = [new Address];
 if ($modelCustomer->load(Yii::$app->request->post())) {

 $modelsAddress = Model::createMultiple(Address::classname());
 Model::loadMultiple($modelsAddress, Yii::$app->request->post());

 // ajax validation
 if (Yii::$app->request->isAjax) {
 Yii::$app->response->format = Response::FORMAT_JSON;
 return ArrayHelper::merge(
 ActiveForm::validateMultiple($modelsAddress),
 ActiveForm::validate($modelCustomer)
 );
 }

 // validate all models
 $valid = $modelCustomer->validate();
 $valid = Model::validateMultiple($modelsAddress) && $valid;
 
 if ($valid) {
 $transaction = \Yii::$app->db->beginTransaction();
 try {
 if ($flag = $modelCustomer->save(false)) {
 foreach ($modelsAddress as $modelAddress) {
 $modelAddress->customer_id = $modelCustomer->id;
 if (! ($flag = $modelAddress->save(false))) {
 $transaction->rollBack();
 break;
 }
 }
 }
 if ($flag) {
 $transaction->commit();
 return $this->redirect(['view', 'id' => $modelCustomer->id]);
 }
 } catch (Exception $e) {
 $transaction->rollBack();
 }
 }
 }

 return $this->render('create', [
 'modelCustomer' => $modelCustomer,
 'modelsAddress' => (empty($modelsAddress)) ? [new Address] : $modelsAddress
 ]);
 }

 /**
 * Updates an existing Customer model.
 * If update is successful, the browser will be redirected to the 'view' page.
 * @param integer $id
 * @return mixed
 */
 public function actionUpdate($id)
 {
 $modelCustomer = $this->findModel($id);
 $modelsAddress = $modelCustomer->addresses;

 if ($modelCustomer->load(Yii::$app->request->post())) {

 $oldIDs = ArrayHelper::map($modelsAddress, 'id', 'id');
 $modelsAddress = Model::createMultiple(Address::classname(), $modelsAddress);
 Model::loadMultiple($modelsAddress, Yii::$app->request->post());
 $deletedIDs = array_diff($oldIDs, array_filter(ArrayHelper::map($modelsAddress, 'id', 'id')));

 // ajax validation
 if (Yii::$app->request->isAjax) {
 Yii::$app->response->format = Response::FORMAT_JSON;
 return ArrayHelper::merge(
 ActiveForm::validateMultiple($modelsAddress),
 ActiveForm::validate($modelCustomer)
 );
 }

 // validate all models
 $valid = $modelCustomer->validate();
 $valid = Model::validateMultiple($modelsAddress) && $valid;

 if ($valid) {
 $transaction = \Yii::$app->db->beginTransaction();
 try {
 if ($flag = $modelCustomer->save(false)) {
 if (! empty($deletedIDs)) {
 Address::deleteAll(['id' => $deletedIDs]);
 }
 foreach ($modelsAddress as $modelAddress) {
 $modelAddress->customer_id = $modelCustomer->id;
 if (! ($flag = $modelAddress->save(false))) {
 $transaction->rollBack();
 break;
 }
 }
 }
 if ($flag) {
 $transaction->commit();
 return $this->redirect(['view', 'id' => $modelCustomer->id]);
 }
 } catch (Exception $e) {
 $transaction->rollBack();
 }
 }
 }

 return $this->render('update', [
 'modelCustomer' => $modelCustomer,
 'modelsAddress' => (empty($modelsAddress)) ? [new Address] : $modelsAddress
 ]);
 }

 ...
}

Model Class

<?php

namespace app\base;

use Yii;
use yii\helpers\ArrayHelper;

class Model extends \yii\base\Model
{
 /**
 * Creates and populates a set of models.
 *
 * @param string $modelClass
 * @param array $multipleModels
 * @return array
 */
 public static function createMultiple($modelClass, $multipleModels = [])
 {
 $model = new $modelClass;
 $formName = $model->formName();
 $post = Yii::$app->request->post($formName);
 $models = [];

 if (! empty($multipleModels)) {
 $keys = array_keys(ArrayHelper::map($multipleModels, 'id', 'id'));
 $multipleModels = array_combine($keys, $multipleModels);
 }

 if ($post && is_array($post)) {
 foreach ($post as $i => $item) {
 if (isset($item['id']) && !empty($item['id']) && isset($multipleModels[$item['id']])) {
 $models[] = $multipleModels[$item['id']];
 } else {
 $models[] = new $modelClass;
 }
 }
 }

 unset($model, $formName, $post);

 return $models;
 }
}


To zero or more elements (use the following code in your view file)


<?php
use yii\helpers\Html;
use yii\widgets\ActiveForm;
use wbraganca\dynamicform\DynamicFormWidget;
?>

<div class="customer-form">

 <?php $form = ActiveForm::begin(['id' => 'dynamic-form']); ?>
 <div class="row">
 <div class="col-sm-6">
 <?= $form->field($modelCustomer, 'first_name')->textInput(['maxlength' => true]) ?>
 </div>
 <div class="col-sm-6">
 <?= $form->field($modelCustomer, 'last_name')->textInput(['maxlength' => true]) ?>
 </div>
 </div>

 <?php DynamicFormWidget::begin([
 'widgetContainer' => 'dynamicform_wrapper', // required: only alphanumeric characters plus "_" [A-Za-z0-9_]
 'widgetBody' => '.container-items', // required: css class selector
 'widgetItem' => '.item', // required: css class
 'limit' => 4, // the maximum times, an element can be added (default 999)
 'min' => 0, // 0 or 1 (default 1)
 'insertButton' => '.add-item', // css class
 'deleteButton' => '.remove-item', // css class
 'model' => $modelsAddress[0],
 'formId' => 'dynamic-form',
 'formFields' => [
 'full_name',
 'address_line1',
 'address_line2',
 'city',
 'state',
 'postal_code',
 ],
 ]); ?>

 <div class="panel panel-default">
 <div class="panel-heading">
 <h4>
 <i class="glyphicon glyphicon-envelope"></i> Addresses
 <button type="button" class="add-item btn btn-success btn-sm pull-right"><i class="glyphicon glyphicon-plus"></i> Add</button>
 </h4>
 </div>
 <div class="panel-body">
 <div class="container-items"><!-- widgetBody -->
 <?php foreach ($modelsAddress as $i => $modelAddress): ?>
 <div class="item panel panel-default"><!-- widgetItem -->
 <div class="panel-heading">
 <h3 class="panel-title pull-left">Address</h3>
 <div class="pull-right">
 <button type="button" class="remove-item btn btn-danger btn-xs"><i class="glyphicon glyphicon-minus"></i></button>
 </div>
 <div class="clearfix"></div>
 </div>
 <div class="panel-body">
 <?php
 // necessary for update action.
 if (! $modelAddress->isNewRecord) {
 echo Html::activeHiddenInput($modelAddress, "[{$i}]id");
 }
 ?>
 <?= $form->field($modelAddress, "[{$i}]full_name")->textInput(['maxlength' => true]) ?>
 <div class="row">
 <div class="col-sm-6">
 <?= $form->field($modelAddress, "[{$i}]address_line1")->textInput(['maxlength' => true]) ?>
 </div>
 <div class="col-sm-6">
 <?= $form->field($modelAddress, "[{$i}]address_line2")->textInput(['maxlength' => true]) ?>
 </div>
 </div><!-- .row -->
 <div class="row">
 <div class="col-sm-4">
 <?= $form->field($modelAddress, "[{$i}]city")->textInput(['maxlength' => true]) ?>
 </div>
 <div class="col-sm-4">
 <?= $form->field($modelAddress, "[{$i}]state")->textInput(['maxlength' => true]) ?>
 </div>
 <div class="col-sm-4">
 <?= $form->field($modelAddress, "[{$i}]postal_code")->textInput(['maxlength' => true]) ?>
 </div>
 </div><!-- .row -->
 </div>
 </div>
 <?php endforeach; ?>
 </div>
 </div>
 </div><!-- .panel -->
 <?php DynamicFormWidget::end(); ?>

 <div class="form-group">
 <?= Html::submitButton($modelAddress->isNewRecord ? 'Create' : 'Update', ['class' => 'btn btn-primary']) ?>
 </div>

 <?php ActiveForm::end(); ?>

</div>