forms: rewrite jsonp select widget with separate js & template (#31506)

This commit is contained in:
Frédéric Péters 2019-03-18 14:53:39 +01:00
parent f841ceb34f
commit 416f4c5b6b
5 changed files with 93 additions and 129 deletions

View File

@ -3875,19 +3875,9 @@ def test_form_jsonp_item_field(http_requests, pub):
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
assert 'url: "http://remote.example.net/jsonp"' in resp.body
assert 'data-select2-url="http://remote.example.net/jsonp"' in resp.body
assert 'select2.min.js' in resp.body
formdef.fields = [
fields.ItemField(id='1', label='string', type='item',
data_source={'type': 'jsonp', 'value': '[var_XXX]'}),
]
formdef.store()
resp = get_app(pub).get('/test/')
assert 'url: function(' in resp.body
assert "wcs_base_url = '[var_XXX]';" in resp.body
def test_form_string_regex_field_submit(pub):
formdef = create_formdef()
formdef.fields = [fields.StringField(id='0', label='string', type='string',

View File

@ -1790,60 +1790,27 @@ class RankedItemsWidget(CompositeWidget):
class JsonpSingleSelectWidget(Widget):
template_name = 'qommon/forms/widgets/select_jsonp.html'
def __init__(self, name, value=None, url=None, **kwargs):
Widget.__init__(self, name, value=value, **kwargs)
super(JsonpSingleSelectWidget, self).__init__(name, value=value, **kwargs)
self.url = url
def add_media(self):
get_response().add_javascript(['jquery.js', 'select2.js'])
get_response().add_javascript(['jquery.js', '../../i18n.js', 'qommon.forms.js', 'select2.js'])
get_response().add_css_include('../js/select2/select2.css')
def render_content(self):
def get_display_value(self):
if self.value is None:
value = None
else:
value = htmlescape(self.value)
r = TemplateIO(html=True)
attrs = {'id': 'form_' + self.name}
r += htmltag('select', name=self.name, value=value, **attrs)
r += htmltext('</select>')
attrs = {'id': 'form_' + self.name + '_display'}
if value and get_session().jsonp_display_values:
key = '%s_%s' % (self.url, value)
if key in get_session().jsonp_display_values:
attrs['value'] = get_session().jsonp_display_values.get(key)
r += htmltag('input', xml_end=True, type="hidden", name=self.name + '_display', **attrs)
initial_display_value = attrs.get('value')
# init select2 widget
allowclear = ""
if not self.required:
allowclear = "placeholder: '...', allowClear: true,"
r += htmltext("""
<script id="script_%(id)s">
$(document).ready(function() {
var wcs_select2_%(id)s = $("#form_%(id)s").select2({
minimumInputLength: 1,
%(allowclear)s
language: {
errorLoading: function() { return "%(errorloading)s"; },
noResults: function () { return "%(nomatches)s"; },
inputTooShort: function (input, min) { return "%(tooshort)s"; },
loadingMore: function () { return "%(loadmore)s"; },
searching: function () { return "%(searching)s"; },
},
ajax: {
""" % {'id': self.name,
'allowclear': allowclear,
'errorloading': _('The results could not be loaded'),
'nomatches': _('No matches found'),
'tooshort': _('Please enter more characters'),
'loadmore': _('Loading more results...'),
'searching': _('Searching...')})
if not value or not get_session().jsonp_display_values:
return None
key = '%s_%s' % (self.url, value)
return get_session().jsonp_display_values.get(key)
def get_select2_url(self):
if Template.is_template_string(self.url):
vars = get_publisher().substitutions.get_context_variables()
# skip variables that were not set (None)
@ -1851,81 +1818,7 @@ var wcs_select2_%(id)s = $("#form_%(id)s").select2({
url = misc.get_variadic_url(self.url, vars, encode_query=False)
else:
url = self.url
if not '[var_' in url:
# if the url is not parametric, set the url directly
r += htmltext("""url: "%(url)s",""" % {'url': url})
else:
# otherwise declare that as a function.
r += htmltext("""url: function() { return $(this).data('select2').options.options.ajax.url; },""")
# setting up the select2 widget continues here
r += htmltext("""
delay: 250,
dataType: 'json',
data: function (params) {
return {
q: params.term,
page_limit: 10
};
},
processResults: function (data, params) {
return {results: data.data};
}
},
formatResult: function(result) { return result.text; }
});""")
if '[var_' in url:
# if this is a parametric url, store template url and hook to the
# appropriate onchange event to give the url to select2
r += htmltext("""
$("#form_%(id)s").data('select2').options.wcs_base_url = '%(url)s';
""" % {'id': self.name, 'url': url})
variables = re.findall(r'\[(var_.+?)\]', url)
r += htmltext("""
function url_replace_%(id)s() {
var url = $("#form_%(id)s").data('select2').options.wcs_base_url;""" % {'id': self.name})
for variable in variables:
r += htmltext("""
selector = '#' + $('#%(variable)s').data('valuecontainerid');
url = url.replace('[%(variable)s]', $(selector).val() || '');""" % {'variable': variable})
r += htmltext("""
$("#form_%(id)s").data('select2').options.options.ajax.url = url;
$("#form_%(id)s").data('select2').results.clear();
}
""" % {'id': self.name} )
for variable in variables:
r += htmltext("""
$('#%(variable)s').change(url_replace_%(id)s);
$('#%(variable)s').change();
""" % {'id': self.name, 'variable': variable})
# finish setting up select2, update the _display hidden field with the
# selected text
r += htmltext("""
$("#form_%(id)s").change(function() {
var text = $("#form_%(id)s").next().find('.select2-selection__rendered').text();
if ($("#form_%(id)s").next().find('.select2-selection__rendered .select2-selection__clear').length) {
text = text.slice(1);
}
$('#form_%(id)s_display').val(text);
});
""" % {'id': self.name})
if initial_display_value:
r += htmltext("""
var option = $('<option>').attr('value', "%(value)s").text("%(text)s");
option.appendTo(wcs_select2_%(id)s);
wcs_select2_%(id)s.val("%(value)s").trigger('change');
$("#form_%(id)s").select2("data", {id: "%(value)s", text: "%(text)s"});
""" % {'id': self.name, 'value': self.value, 'text': initial_display_value})
r += htmltext("""});</script>""")
return r.getvalue()
return url
def parse(self, request=None):
if request and request.form.get(self.name) and request.form.get(self.name + '_display'):

View File

@ -118,4 +118,71 @@ $(function() {
$(this).parents('form').trigger('wcs:change', {modified_field: modified_field});
});
$('form div[data-live-source]').parents('form').trigger('wcs:change');
/* searchable select */
$('select[data-select2-url]').each(function(i, elem) {
var required = $(elem).data('required');
// create an additional hidden field to hold the label of the selected
// option, it is necessary as the server may not have any knowledge of
// possible options.
var $input_display_value = $('<input>', {
type: 'hidden',
name: $(elem).attr('name') + '_display',
value: $(elem).data('initial-display-value')
});
$input_display_value.insertAfter($(elem));
var options = {
minimumInputLength: 1,
formatResult: function(result) { return result.text; },
language: {
errorLoading: function() { return WCS_I18N.s2_errorloading; },
noResults: function () { return WCS_I18N.s2_nomatches; },
inputTooShort: function (input, min) { return WCS_I18N.s2_tooshort; },
loadingMore: function () { return WCS_I18N.s2_loadmore; },
searching: function () { return WCS_I18N.s2_searching; }
}
};
if (!required) {
options.placeholder = '...';
options.allowClear = true;
}
options.ajax = {
delay: 250,
dataType: 'json',
data: function(params) {
return {q: params.term, page_limit: 10};
},
processResults: function (data, params) {
return {results: data.data};
},
url: function() {
var url = $(elem).data('select2-url');
url = url.replace(/\[var_.+?\]/g, function(match, g1, g2) {
// compatibility: if there are [var_...] references in the URL
// replace them by looking for other select fields on the same
// page.
var related_select = $('#' + match.slice(1, -1));
var value_container_id = $(related_select).data('valuecontainerid');
return $('#' + value_container_id).val() || '';
});
return url;
}
};
var select2 = $(elem).select2(options);
$(elem).on('change', function() {
// update _display hidden field with selected text
var text = $(elem).find(':selected').first().text();
$input_display_value.val(text);
});
if ($input_display_value.val()) {
// if the _display hidden field was created with an initial value take it
// and create a matching <option> in the real <select> widget, and use it
// to set select2 initial state.
var option = $('<option></option>', {value: $(elem).data('value')});
option.appendTo($(elem));
option.text($input_display_value.val());
select2.val($(elem).data('value')).trigger('change');
$(elem).select2('data', {id: $(elem).data('value'), text: $(elem).data('initial-display-value')});
}
});
});

View File

@ -0,0 +1,9 @@
{% extends "qommon/forms/widget.html" %}
{% block widget-control %}
<select id="form_{{widget.name}}" name="{{widget.name}}"
data-select2-url="{{widget.get_select2_url}}"
{% if widget.value %}data-value="{{ widget.value }}"{% endif %}
data-required="{% if widget.is_required %}true{% endif %}"
data-initial-display-value="{{widget.get_display_value|default_if_none:''}}">
</select>
{% endblock %}

View File

@ -372,6 +372,11 @@ class RootDirectory(Directory):
'map_zoom_in': _('Zoom in'),
'map_zoom_out': _('Zoom out'),
'map_display_position': _('Display my position'),
's2_errorloading': _('The results could not be loaded'),
's2_nomatches': _('No matches found'),
's2_tooshort': _('Please enter more characters'),
's2_loadmore': _('Loading more results...'),
's2_searching': _('Searching...'),
}
return 'WCS_I18N = %s;\n' % json.dumps(strings)