Your support helps keep this blog running! Secure payments via Paypal and Stripe.
The wbraganca/yii2-dynamicform
extension is one of the most popular Yii2 tools for handling tabular input forms. It makes it easy to add and remove rows dynamically for related models, like invoice line items or order details.
However, if you’ve ever used it in an update form, you may have run into a frustrating bug:
- When adding a new row, the first row gets cloned with its data.
- The hidden primary key (
id
) field is also copied, so Yii2 thinks the new row is an update to the first record instead of inserting a new one. - As a result, only the last new row is saved, and it overwrites the first row.
Let’s fix that.
Table of Contents
Why this happens
Looking at the extension’s source code (yii2-dynamic-form.js
), new rows are created by cloning the first row in the container:
$toclone = $(widgetOptions.template);
$newclone = $toclone.clone(false, false);
This means every input, including hidden IDs and values, is carried into the new row. Unless those values are cleared, Yii2 will treat them as existing records.
The Fix: Clear Values After Cloning
The solution is simple: reset all cloned input values and clear the hidden primary key field before inserting the new row. Here’s the modified _addItem
function:
var _addItem = function(widgetOptions, e, $elem) {
var count = _count($elem, widgetOptions);
if (count < widgetOptions.limit) {
$toclone = $(widgetOptions.template);
$newclone = $toclone.clone(false, false);
// clear all cloned field values
$newclone.find('input, textarea, select').each(function () {
var type = $(this).attr('type');
if (type === 'checkbox' || type === 'radio') {
$(this).prop('checked', false);
} else {
$(this).val('');
}
});
// clear PK hidden field
$newclone.find('input[type="hidden"][name$="[id]"]').val('');
if (widgetOptions.insertPosition === 'top') {
$elem.closest('.' + widgetOptions.widgetContainer)
.find(widgetOptions.widgetBody).prepend($newclone);
} else {
$elem.closest('.' + widgetOptions.widgetContainer)
.find(widgetOptions.widgetBody).append($newclone);
}
_updateAttributes(widgetOptions);
_restoreSpecialJs(widgetOptions);
_fixFormValidaton(widgetOptions);
$elem.closest('.' + widgetOptions.widgetContainer)
.triggerHandler(events.afterInsert, $newclone);
} else {
$elem.closest('.' + widgetOptions.widgetContainer)
.triggerHandler(events.limitReached, widgetOptions.limit);
}
};
Note
If you use the minified version of the yii2-dynamic-form.js file, you will need to regenerate yii2-dynamic-form.min.js. If you have Yii2 cache enabled, you will need to clear it to see the changes (“web/assets”).
Result
With this fix:
- Every new row is inserted blank (no copied values).
- The hidden
id
field is cleared, so Yii2 knows it’s a new record. - Saving works as expected: new rows are inserted, existing rows are updated, and nothing is overwritten.
Conclusion
This small tweak makes yii2-dynamicform
work reliably in both create and update scenarios. If you’re maintaining projects that rely on this extension, patching the JavaScript is essential to avoid data corruption.
You can either:
- Patch the
_addItem
function directly inyii2-dynamic-form.js
, or - Hook into the
afterInsert
event and add your cleanup code there.
For production systems, I recommend maintaining a patched copy of yii2-dynamic-form.js
to ensure consistent behavior across all forms.
Your support helps keep this blog running! Secure payments via Paypal and Stripe.