Inline relations: "Add another <Model>" breaks dropdown boxes
TomNaessens opened this issue · 15 comments
I have a model which has a relation to another model. This model has some fields, which use the Select2 dropdown singleselect boxes. When using the "Add another " link:
A new row is added to the table. However, when trying to select a value, the dropdown box doesn't "open".
This is the HTML of a working dropdown:
<td class="field-action_owner">
<div class="select2-container" id="s2id_id_membership_set-6-action_owner" style="width: 250px;">
<a href="javascript:void(0)" onclick="return false;" class="select2-choice" tabindex="-1">
<span class="select2-chosen">foo@bar.be</span>
<abbr class="select2-search-choice-close"></abbr>
<span class="select2-arrow"><b></b></span>
</a>
<input class="select2-focusser select2-offscreen" type="text" id="s2id_autogen17">
</div>
<select name="membership_set-6-action_owner" id="id_membership_set-6-action_owner" tabindex="-1" class="select2-offscreen">
<option value="">---------</option>
<option value="1" selected="selected">foo@bar.be</option>
<option value="2">foo@bar.com</option>
</select>
<script>
$("#id_membership_set-6-action_owner").on('select2changed', function(e){
$("#id_membership_set-6-action_owner").select2({"width": "250px"});
}).trigger('select2changed');
</script>
<a href="/admin/auth/user/add/" class="add-another" id="add_id_membership_set-6-action_owner" onclick="return showAddAnotherPopup(this);"> <img src="/django/static/admin/img/icon_addlink.gif" width="10" height="10" alt="Add Another"></a>
</td>
This is the HTML of a faulty dropdownbox:
<td class="field-action_owner">
<div class="select2-container" id="s2id_id_membership_set-7-action_owner" style="width: 250px;">
<a href="javascript:void(0)" onclick="return false;" class="select2-choice" tabindex="-1">
<span class="select2-chosen">---------</span>
<abbr class="select2-search-choice-close"></abbr>
<span class="select2-arrow"><b></b></span>
</a>
<input class="select2-focusser select2-offscreen" type="text" id="s2id_autogen19">
<div class="select2-drop select2-display-none select2-with-searchbox">
<div class="select2-search">
<input type="text" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" class="select2-input">
</div>
<ul class="select2-results"></ul>
</div>
</div>
<select name="membership_set-7-action_owner" id="id_membership_set-7-action_owner" tabindex="-1" class="select2-offscreen">
<option value="" selected="selected">---------</option>
<option value="1">foo@bar.be</option>
<option value="2">foo@bar.com</option>
</select>
<a href="/admin/auth/user/add/" class="add-another" id="add_id_membership_set-7-action_owner" onclick="return showAddAnotherPopup(this);"> <img src="/django/static/admin/img/icon_addlink.gif" width="10" height="10" alt="Add Another"></a>
</td>
The major difference I see is that the <script>
tag is not loaded, which triggers the select2changed
function.
In addition, the faulty dropdown also has a select2-drop
div underneath the focusser.
Hello, and thank you for detailed description of a problem. I will investigate the problem as soon as possible!
My current findings are the following:
Django already creates a hidden row beneath the table for the "extra" instance. The select2 selects works there perfectly. When the user clicks the "add another " button, the row gets duplicated and the IDs change of the "old" hidden row.
The change of IDs probably breaks the select2 handlers, but I yet have to confirm this.
For now i'm trying to build the sample application to reproduce the problem.
Disabling
var updateElementIndex = function(el, prefix, ndx) {
var id_regex = new RegExp("(" + prefix + "-(\\d+|__prefix__))");
var replacement = prefix + "-" + ndx;
if ($(el).attr("for")) {
$(el).attr("for", $(el).attr("for").replace(id_regex, replacement));
}
if (el.id) {
el.id = el.id.replace(id_regex, replacement);
}
if (el.name) {
el.name = el.name.replace(id_regex, replacement);
}
};
from https://github.com/django/django/blob/master/django/contrib/admin/static/admin/js/inlines.js renders a second (but working) box next to the faulty box when a new instance is added.
One very barbaric decision may be something like this:
$(addButton).on('click', function(e){
e.preventDefault();
$('tr.form-row script').each(function(item){
eval(item.text);
})
})
There are three problems with such approach:
- Eval is evil
- It may be a trouble to find correct
tr
element - prefix problem in script is not solved
- Event order is not defined, so this code may be executed first, and code from
inlines.js
second.
As a workaround of problem 3 it is possible to replace prefix with input#id_<model_name>_set-TOTAL_FORMS
.
Still, this approach looks very bad to me.
Another round of debugging led me to the following (although this may be incorrect) conclusion:
The addbutton finds the (working, hidden) templaterow (https://github.com/django/django/blob/master/django/contrib/admin/static/admin/js/inlines.js#L60). It then clones the row (https://github.com/django/django/blob/master/django/contrib/admin/static/admin/js/inlines.js#L61), leaving the IDs (still prefix at this point) and script intact. A bit further, they update the IDs from prefix to the IDs used in the table: https://github.com/django/django/blob/master/django/contrib/admin/static/admin/js/inlines.js#L78. This function however only replaces the name
and id
, leaving the <script>
intact.
The resulting, inserted row looks like this at the point of insertion: (snipped a bit)
...
<div class="select2-container" id="s2id_id_membership_set-16-action_owner" style="width: 250px;"><a href="javascript:void(0)" onclick="return false;" class="select2-choice" tabindex="-1"> <span class="select2-chosen">---------</span><abbr class="select2-search-choice-close"></abbr> <span class="select2-arrow"><b></b></span></a><input class="select2-focusser select2-offscreen" type="text" id="s2id_autogen118"><div class="select2-drop select2-display-none select2-with-searchbox"> <div class="select2-search"> <input type="text" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" class="select2-input"> </div> <ul class="select2-results"> </ul></div></div><select name="membership_set-16-action_owner" id="id_membership_set-16-action_owner" class="select2-offscreen" tabindex="-1">
...
<script>
$("#id_membership_set-__prefix__-action_owner").on('select2changed', function(e){
console.log('id_membership_set-__prefix__-action_owner triggered select2changed');
$("#id_membership_set-__prefix__-action_owner").select2({"width": "250px"});
}).trigger('select2changed');
</script>
So yes, the above script may work if we regex the script to contain the correct selectors.
Sorry, but for a few days i will not be able to work on this problem.
It looks like i haven't got enough time to fix it yet. So, contributions highly appreciated!
Hey,
Here how I did it (this is a first draft, so refactoring required) - https://github.com/bashu/django-easy-select2/commit/2c7ed05497723371eeb06cfeace42b4ff382ecdf
result for StackedInline:
Thank you, bashu! I'll try to get on this in a few days. In general, everything is clear in your implementation, but deleted 'Select2TextMixin' and 'Select2TextInput'. Why?
I was lazy to update them... I will try to fix this tomorrow
I have adopted your code with a bit of refactoring, also fixing Select2TextMixin.
You can see changes: https://github.com/asyncee/django-easy-select2/tree/issue-12
I did some manual testing and it seems everything working fine.
Although project lacks of automated tests, so can you please test it?
Looks good, I approve ;-) Is there a chance you can release minor update on pypi soon?
Updated package to version 1.2.9. Release uploaded to pypi. Thanks to everybody, especially, bashu!