Skip to content

Commit 3839f42

Browse files
authored
Merge pull request #3148 from Laravel-Backpack/refactor-repeatable
[4.1] Fix repeatable initialization
2 parents 044ffb3 + b84ec1a commit 3839f42

File tree

2 files changed

+52
-14
lines changed

2 files changed

+52
-14
lines changed

src/resources/views/crud/fields/ckeditor.blade.php

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,23 @@
4242
<script src="{{ asset('packages/ckeditor/adapters/jquery.js') }}"></script>
4343
<script>
4444
function bpFieldInitCKEditorElement(element) {
45-
// remove any previous CKEditors from right next to the textarea
46-
// element.siblings("[id^='cke_editor']").remove();
4745
46+
47+
//when removing ckeditor field from page html the instance is not properly deleted.
48+
//this event is triggered in repeatable on deletion so this field can intercept it
49+
//and properly delete the instances so it don't throw errors of unexistent elements in page that has initialized ck instances.
50+
element.on('backpack_field.deleted', function(e) {
51+
$ck_instance_name = element.siblings("[id^='cke_editor']").attr('id');
52+
53+
//if the instance name starts with cke_ it was an auto-generated name from ckeditor
54+
//that happens because in repeatable we stripe the field names used by ckeditor, so it renders a random name
55+
//that starts with cke_
56+
if($ck_instance_name.startsWith('cke_')) {
57+
$ck_instance_name = $ck_instance_name.substr(4);
58+
}
59+
//we fully destroy the instance when element is deleted from the page.
60+
CKEDITOR.instances[$ck_instance_name].destroy(true);
61+
});
4862
// trigger a new CKEditor
4963
element.ckeditor(element.data('options'));
5064
}

src/resources/views/crud/fields/repeatable.blade.php

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,13 @@
2121
<p class="help-block text-muted text-sm">{!! $field['hint'] !!}</p>
2222
@endif
2323

24-
<div class="container-repeatable-elements">
25-
<div class="col-md-12 well repeatable-element row m-1 p-2">
24+
25+
26+
<div class="container-repeatable-elements">
27+
<div data-repeatable-holder="{{ $field['name'] }}"></div>
28+
29+
@push('before_scripts')
30+
<div class="col-md-12 well repeatable-element row m-1 p-2" data-repeatable-identifier="{{ $field['name'] }}">
2631
@if (isset($field['fields']) && is_array($field['fields']) && count($field['fields']))
2732
<button type="button" class="close delete-element"><span aria-hidden="true">×</span></button>
2833
@foreach($field['fields'] as $subfield)
@@ -38,8 +43,11 @@
3843

3944
@endif
4045
</div>
46+
@endpush
4147

4248
</div>
49+
50+
4351
<button type="button" class="btn btn-outline-primary btn-sm ml-1 add-repeatable-element-button">+ {{ $field['new_item_label'] ?? trans('backpack::crud.new_item') }}</button>
4452

4553
@include('crud::fields.inc.wrapper_end')
@@ -81,10 +89,12 @@
8189
/**
8290
* Takes all inputs and makes them an object.
8391
*/
84-
function repeatableInputToObj(container) {
92+
function repeatableInputToObj(container_name) {
8593
var arr = [];
8694
var obj = {};
8795
96+
var container = $('[data-repeatable-holder={{ $field['name'] }}]');
97+
8898
container.find('.well').each(function () {
8999
$(this).find('input, select, textarea').each(function () {
90100
if ($(this).data('repeatable-input-name')) {
@@ -102,8 +112,11 @@ function repeatableInputToObj(container) {
102112
* The method that initializes the javascript on this field type.
103113
*/
104114
function bpFieldInitRepeatableElement(element) {
115+
116+
var field_name = element.attr('name');
117+
105118
// element will be a jQuery wrapped DOM node
106-
var container = element.siblings('.container-repeatable-elements');
119+
var container = $('[data-repeatable-identifier='+field_name+']');
107120
108121
// make sure the inputs no longer have a "name" attribute,
109122
// so that the form will not send the inputs as request variables;
@@ -118,14 +131,14 @@ function bpFieldInitRepeatableElement(element) {
118131
$(this).removeAttr("name");
119132
}
120133
$(this).attr('data-repeatable-input-name', name_attr)
121-
// .val('');
122134
});
123135
124136
// make a copy of the group of inputs in their default state
125137
// this way we have a clean element we can clone when the user
126138
// wants to add a new group of inputs
127-
var field_group_clone = container.find('.repeatable-element:first').clone();
128-
container.find('.repeatable-element').remove();
139+
//sconsole.log(container.find('.repeatable-element:first'));
140+
var field_group_clone = container.clone();
141+
container.remove();
129142
130143
element.parent().find('.add-repeatable-element-button').click(function(){
131144
newRepeatableElement(container, field_group_clone);
@@ -143,11 +156,11 @@ function bpFieldInitRepeatableElement(element) {
143156
144157
if (element.closest('.modal-content').length) {
145158
element.closest('.modal-content').find('.save-block').click(function(){
146-
element.val(JSON.stringify(repeatableInputToObj(container)));
159+
element.val(JSON.stringify(repeatableInputToObj(field_name)));
147160
})
148161
} else if (element.closest('form').length) {
149162
element.closest('form').submit(function(){
150-
element.val(JSON.stringify(repeatableInputToObj(container)));
163+
element.val(JSON.stringify(repeatableInputToObj(field_name)));
151164
return true;
152165
})
153166
}
@@ -157,9 +170,20 @@ function bpFieldInitRepeatableElement(element) {
157170
* Adds a new field group to the repeatable input.
158171
*/
159172
function newRepeatableElement(container, field_group, values) {
173+
174+
var field_name = container.data('repeatable-identifier');
160175
var new_field_group = field_group.clone();
161176
177+
//this is the container for this repeatable group that holds it inside the main form.
178+
var container_holder = $('[data-repeatable-holder='+field_name+']');
179+
162180
new_field_group.find('.delete-element').click(function(){
181+
new_field_group.find('input, select, textarea').each(function(i, el) {
182+
//we trigger this event so fields can intercept when they are beeing deleted from the page
183+
//implemented because of ckeditor instances that stayed around when deleted from page
184+
//introducing unwanted js errors and high memory usage.
185+
$(el).trigger('backpack_field.deleted');
186+
});
163187
$(this).parent().remove();
164188
});
165189
@@ -179,9 +203,9 @@ function newRepeatableElement(container, field_group, values) {
179203
}
180204
});
181205
}
182-
183-
container.append(new_field_group);
184-
initializeFieldsWithJavascript(new_field_group);
206+
//we push the fields to the correct container in page.
207+
container_holder.append(new_field_group);
208+
initializeFieldsWithJavascript(container_holder);
185209
}
186210
</script>
187211
@endpush

0 commit comments

Comments
 (0)