mirror of https://gitlab.federez.net/re2o/re2o
committed by
Gabriel Detraz
1 changed files with 0 additions and 752 deletions
@ -1,752 +0,0 @@ |
|||
# -*- mode: python; coding: utf-8 -*- |
|||
# Re2o est un logiciel d'administration développé initiallement au Rézo Metz. Il |
|||
# se veut agnostique au réseau considéré, de manière à être installable en |
|||
# quelques clics. |
|||
# |
|||
# Copyright © 2017 Maël Kervella |
|||
# |
|||
# This program is free software; you can redistribute it and/or modify |
|||
# it under the terms of the GNU General Public License as published by |
|||
# the Free Software Foundation; either version 2 of the License, or |
|||
# (at your option) any later version. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU General Public License for more details. |
|||
# |
|||
# You should have received a copy of the GNU General Public License along |
|||
# with this program; if not, write to the Free Software Foundation, Inc., |
|||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
|||
|
|||
""" Templatetag used to render massive django form selects into bootstrap |
|||
forms that can still be manipulating even if there is multiple tens of |
|||
thousands of elements in the select. It's made possible using JS libaries |
|||
Twitter Typeahead and Splitree's Tokenfield. |
|||
See docstring of massive_bootstrap_form for a detailed explaantion on how |
|||
to use this templatetag. |
|||
""" |
|||
|
|||
from django import template |
|||
from django.utils.safestring import mark_safe |
|||
from django.forms import TextInput |
|||
from django.forms.widgets import Select |
|||
from django.utils.translation import ugettext_lazy as _ |
|||
from bootstrap3.utils import render_tag |
|||
from bootstrap3.forms import render_field |
|||
|
|||
register = template.Library() |
|||
|
|||
|
|||
@register.simple_tag |
|||
def massive_bootstrap_form(form, mbf_fields, *args, **kwargs): |
|||
""" |
|||
Render a form where some specific fields are rendered using Twitter |
|||
Typeahead and/or splitree's Bootstrap Tokenfield to improve the |
|||
performance, the speed and UX when dealing with very large datasets |
|||
(select with 50k+ elts for instance). |
|||
When the fields specified should normally be rendered as a select with |
|||
single selectable option, Twitter Typeahead is used for a better display |
|||
and the matching query engine. When dealing with multiple selectable |
|||
options, sliptree's Bootstrap Tokenfield in addition with Typeahead. |
|||
For convenience, it accepts the same parameters as a standard bootstrap |
|||
can accept. |
|||
|
|||
**Tag name**:: |
|||
|
|||
massive_bootstrap_form |
|||
|
|||
**Parameters**: |
|||
|
|||
form (required) |
|||
The form that is to be rendered |
|||
|
|||
mbf_fields (optional) |
|||
A list of field names (comma separated) that should be rendered |
|||
with Typeahead/Tokenfield instead of the default bootstrap |
|||
renderer. |
|||
If not specified, all fields will be rendered as a normal bootstrap |
|||
field. |
|||
|
|||
mbf_param (optional) |
|||
A dict of parameters for the massive_bootstrap_form tag. The |
|||
possible parameters are the following. |
|||
|
|||
choices (optional) |
|||
A dict of strings representing the choices in JS. The keys of |
|||
the dict are the names of the concerned fields. The choices |
|||
must be an array of objects. Each of those objects must at |
|||
least have the fields 'key' (value to send) and 'value' (value |
|||
to display). Other fields can be added as desired. |
|||
For a more complex structure you should also consider |
|||
reimplementing the engine and the match_func. |
|||
If not specified, the key is the id of the object and the value |
|||
is its string representation as in a normal bootstrap form. |
|||
Example : |
|||
'choices' : { |
|||
'field_A':'[{key:0,value:"choice0",extra:"data0"},{...},...]', |
|||
'field_B':..., |
|||
... |
|||
} |
|||
|
|||
engine (optional) |
|||
A dict of strings representating the engine used for matching |
|||
queries and possible values with typeahead. The keys of the |
|||
dict are the names of the concerned fields. The string is valid |
|||
JS code. |
|||
If not specified, BloodHound with relevant basic properties is |
|||
used. |
|||
Example : |
|||
'engine' : {'field_A': 'new Bloodhound()', 'field_B': ..., ...} |
|||
|
|||
match_func (optional) |
|||
A dict of strings representing a valid JS function used in the |
|||
dataset to overload the matching engine. The keys of the dict |
|||
are the names of the concerned fields. This function is used |
|||
the source of the dataset. This function receives 2 parameters, |
|||
the query and the synchronize function as specified in |
|||
typeahead.js documentation. If needed, the local variables |
|||
'choices_<fieldname>' and 'engine_<fieldname>' contains |
|||
respectively the array of all possible values and the engine |
|||
to match queries with possible values. |
|||
If not specified, the function used display up to the 10 first |
|||
elements if the query is empty and else the matching results. |
|||
Example : |
|||
'match_func' : { |
|||
'field_A': 'function(q, sync) { engine.search(q, sync); }', |
|||
'field_B': ..., |
|||
... |
|||
} |
|||
|
|||
update_on (optional) |
|||
A dict of list of ids that the values depends on. The engine |
|||
and the typeahead properties are recalculated and reapplied. |
|||
Example : |
|||
'update_on' : { |
|||
'field_A' : [ 'id0', 'id1', ... ] , |
|||
'field_B' : ... , |
|||
... |
|||
} |
|||
|
|||
gen_select (optional) |
|||
A dict of boolean telling if the form should either generate |
|||
the normal select (set to true) and then use it to generate |
|||
the possible choices and then remove it or either (set to |
|||
false) generate the choices variable in this tag and do not |
|||
send any select. |
|||
Sending the select before can be usefull to permit the use |
|||
without any JS enabled but it will execute more code locally |
|||
for the client so the loading might be slower. |
|||
If not specified, this variable is set to true for each field |
|||
Example : |
|||
'gen_select' : { |
|||
'field_A': True , |
|||
'field_B': ... , |
|||
... |
|||
} |
|||
|
|||
See boostrap_form_ for other arguments |
|||
|
|||
**Usage**:: |
|||
|
|||
{% massive_bootstrap_form |
|||
form |
|||
[ '<field1>[,<field2>[,...]]' ] |
|||
[ mbf_param = { |
|||
[ 'choices': { |
|||
[ '<field1>': '<choices1>' |
|||
[, '<field2>': '<choices2>' |
|||
[, ... ] ] ] |
|||
} ] |
|||
[, 'engine': { |
|||
[ '<field1>': '<engine1>' |
|||
[, '<field2>': '<engine2>' |
|||
[, ... ] ] ] |
|||
} ] |
|||
[, 'match_func': { |
|||
[ '<field1>': '<match_func1>' |
|||
[, '<field2>': '<match_func2>' |
|||
[, ... ] ] ] |
|||
} ] |
|||
[, 'update_on': { |
|||
[ '<field1>': '<update_on1>' |
|||
[, '<field2>': '<update_on2>' |
|||
[, ... ] ] ] |
|||
} ], |
|||
[, 'gen_select': { |
|||
[ '<field1>': '<gen_select1>' |
|||
[, '<field2>': '<gen_select2>' |
|||
[, ... ] ] ] |
|||
} ] |
|||
} ] |
|||
[ <standard boostrap_form parameters> ] |
|||
%} |
|||
|
|||
**Example**: |
|||
|
|||
{% massive_bootstrap_form form 'ipv4' choices='[...]' %} |
|||
""" |
|||
|
|||
mbf_form = MBFForm(form, mbf_fields.split(","), *args, **kwargs) |
|||
return mbf_form.render() |
|||
|
|||
|
|||
class MBFForm: |
|||
""" An object to hold all the information and useful methods needed to |
|||
create and render a massive django form into an actual HTML and JS |
|||
code able to handle it correctly. |
|||
Every field that is not listed is rendered as a normal bootstrap_field. |
|||
""" |
|||
|
|||
def __init__(self, form, mbf_fields, *args, **kwargs): |
|||
# The django form object |
|||
self.form = form |
|||
# The fields on which to use JS |
|||
self.fields = mbf_fields |
|||
|
|||
# Other bootstrap_form arguments to render the fields |
|||
self.args = args |
|||
self.kwargs = kwargs |
|||
|
|||
# Fields to exclude form the form rendering |
|||
self.exclude = self.kwargs.get("exclude", "").split(",") |
|||
|
|||
# All the mbf parameters specified byt the user |
|||
param = kwargs.pop("mbf_param", {}) |
|||
self.choices = param.get("choices", {}) |
|||
self.engine = param.get("engine", {}) |
|||
self.match_func = param.get("match_func", {}) |
|||
self.update_on = param.get("update_on", {}) |
|||
self.gen_select = param.get("gen_select", {}) |
|||
self.hidden_fields = [h.name for h in self.form.hidden_fields()] |
|||
|
|||
# HTML code to insert inside a template |
|||
self.html = "" |
|||
|
|||
def render(self): |
|||
""" HTML code for the fully rendered form with all the necessary form |
|||
""" |
|||
for name, field in self.form.fields.items(): |
|||
if name not in self.exclude: |
|||
|
|||
if name in self.fields and name not in self.hidden_fields: |
|||
mbf_field = MBFField( |
|||
name, |
|||
field, |
|||
field.get_bound_field(self.form, name), |
|||
self.choices.get(name, None), |
|||
self.engine.get(name, None), |
|||
self.match_func.get(name, None), |
|||
self.update_on.get(name, None), |
|||
self.gen_select.get(name, True), |
|||
*self.args, |
|||
**self.kwargs |
|||
) |
|||
self.html += mbf_field.render() |
|||
|
|||
else: |
|||
f = field.get_bound_field(self.form, name), self.args, self.kwargs |
|||
self.html += render_field( |
|||
field.get_bound_field(self.form, name), |
|||
*self.args, |
|||
**self.kwargs |
|||
) |
|||
|
|||
return mark_safe(self.html) |
|||
|
|||
|
|||
class MBFField: |
|||
""" An object to hold all the information and useful methods needed to |
|||
create and render a massive django form field into an actual HTML and JS |
|||
code able to handle it correctly. |
|||
Twitter Typeahead is used for the display and the matching of queries and |
|||
in case of a MultipleSelect, Sliptree's Tokenfield is also used to manage |
|||
multiple values. |
|||
A div with only non visible elements is created after the div containing |
|||
the displayed input. It's used to store the actual data that will be sent |
|||
to the server """ |
|||
|
|||
def __init__( |
|||
self, |
|||
name_, |
|||
field_, |
|||
bound_, |
|||
choices_, |
|||
engine_, |
|||
match_func_, |
|||
update_on_, |
|||
gen_select_, |
|||
*args_, |
|||
**kwargs_ |
|||
): |
|||
|
|||
# Verify this field is a Select (or MultipleSelect) (only supported) |
|||
if not isinstance(field_.widget, Select): |
|||
raise ValueError( |
|||
( |
|||
"Field named {f_name} is not a Select and" |
|||
"can't be rendered with massive_bootstrap_form." |
|||
).format(f_name=name_) |
|||
) |
|||
|
|||
# Name of the field |
|||
self.name = name_ |
|||
# Django field object |
|||
self.field = field_ |
|||
# Bound Django field associated with field |
|||
self.bound = bound_ |
|||
|
|||
# Id for the main visible input |
|||
self.input_id = self.bound.auto_id |
|||
# Id for a hidden input used to store the value |
|||
self.hidden_id = self.input_id + "_hidden" |
|||
# Id for another div containing hidden inputs and script |
|||
self.div2_id = self.input_id + "_div" |
|||
|
|||
# Should the standard select should be generated |
|||
self.gen_select = gen_select_ |
|||
# Is it select with multiple values possible (use of tokenfield) |
|||
self.multiple = self.field.widget.allow_multiple_selected |
|||
# JS for the choices variable (user specified or default) |
|||
self.choices = choices_ or self.default_choices() |
|||
# JS for the engine variable (typeahead) (user specified or default) |
|||
self.engine = engine_ or self.default_engine() |
|||
# JS for the matching function (typeahead) (user specified or default) |
|||
self.match_func = match_func_ or self.default_match_func() |
|||
# JS for the datasets variable (typeahead) (user specified or default) |
|||
self.datasets = self.default_datasets() |
|||
# Ids of other fields to bind a reset/reload with when changed |
|||
self.update_on = update_on_ or [] |
|||
|
|||
# Whole HTML code to insert in the template |
|||
self.html = "" |
|||
# JS code in the script tag |
|||
self.js_script = "" |
|||
# Input tag to display instead of select |
|||
self.replace_input = None |
|||
|
|||
# Other bootstrap_form arguments to render the fields |
|||
self.args = args_ |
|||
self.kwargs = kwargs_ |
|||
|
|||
def default_choices(self): |
|||
""" JS code of the variable choices_<fieldname> """ |
|||
|
|||
if self.gen_select: |
|||
return ( |
|||
"function plop(o) {{" |
|||
"var c = [];" |
|||
"for( let i=0 ; i<o.length ; i++) {{" |
|||
" c.push( {{ key: o[i].value, value: o[i].text }} );" |
|||
"}}" |
|||
"return c;" |
|||
'}} ($("#{select_id}")[0].options)' |
|||
).format(select_id=self.input_id) |
|||
|
|||
else: |
|||
return "[{objects}]".format( |
|||
objects=",".join( |
|||
[ |
|||
'{{key:{k},value:"{v}"}}'.format( |
|||
k=choice[0] if choice[0] != "" else '""', v=choice[1] |
|||
) |
|||
for choice in self.field.choices |
|||
] |
|||
) |
|||
) |
|||
|
|||
def default_engine(self): |
|||
""" Default JS code of the variable engine_<field_name> """ |
|||
return ( |
|||
"new Bloodhound({{" |
|||
' datumTokenizer: Bloodhound.tokenizers.obj.whitespace("value"),' |
|||
" queryTokenizer: Bloodhound.tokenizers.whitespace," |
|||
" local: choices_{name}," |
|||
" identify: function(obj) {{ return obj.key; }}" |
|||
"}})" |
|||
).format(name=self.name) |
|||
|
|||
def default_datasets(self): |
|||
""" Default JS script of the datasets to use with typeahead """ |
|||
return ( |
|||
"{{" |
|||
" hint: true," |
|||
" highlight: true," |
|||
" minLength: 0" |
|||
"}}," |
|||
"{{" |
|||
' display: "value",' |
|||
' name: "{name}",' |
|||
" source: {match_func}" |
|||
"}}" |
|||
).format(name=self.name, match_func=self.match_func) |
|||
|
|||
def default_match_func(self): |
|||
""" Default JS code of the matching function to use with typeahed """ |
|||
return ( |
|||
"function ( q, sync ) {{" |
|||
' if ( q === "" ) {{' |
|||
" var first = choices_{name}.slice( 0, 5 ).map(" |
|||
" function ( obj ) {{ return obj.key; }}" |
|||
" );" |
|||
" sync( engine_{name}.get( first ) );" |
|||
" }} else {{" |
|||
" engine_{name}.search( q, sync );" |
|||
" }}" |
|||
"}}" |
|||
).format(name=self.name) |
|||
|
|||
def render(self): |
|||
""" HTML code for the fully rendered field """ |
|||
self.gen_displayed_div() |
|||
self.gen_hidden_div() |
|||
return mark_safe(self.html) |
|||
|
|||
def gen_displayed_div(self): |
|||
""" Generate HTML code for the div that contains displayed tags """ |
|||
if self.gen_select: |
|||
self.html += render_field(self.bound, *self.args, **self.kwargs) |
|||
|
|||
self.field.widget = TextInput( |
|||
attrs={ |
|||
"name": "mbf_" + self.name, |
|||
"placeholder": getattr(self.field, "empty_label", _("Nothing")), |
|||
} |
|||
) |
|||
self.replace_input = render_field(self.bound, *self.args, **self.kwargs) |
|||
|
|||
if not self.gen_select: |
|||
self.html += self.replace_input |
|||
|
|||
def gen_hidden_div(self): |
|||
""" Generate HTML code for the div that contains hidden tags """ |
|||
self.gen_full_js() |
|||
|
|||
content = self.js_script |
|||
if not self.multiple and not self.gen_select: |
|||
content += self.hidden_input() |
|||
|
|||
self.html += render_tag("div", content=content, attrs={"id": self.div2_id}) |
|||
|
|||
def hidden_input(self): |
|||
""" HTML for the hidden input element """ |
|||
return render_tag( |
|||
"input", |
|||
attrs={ |
|||
"id": self.hidden_id, |
|||
"name": self.bound.html_name, |
|||
"type": "hidden", |
|||
"value": self.bound.value() or "", |
|||
}, |
|||
) |
|||
|
|||
def gen_full_js(self): |
|||
""" Generate the full script tag containing the JS code """ |
|||
self.create_js() |
|||
self.fill_js() |
|||
self.get_script() |
|||
|
|||
def create_js(self): |
|||
""" Generate a template for the whole script to use depending on |
|||
gen_select and multiple """ |
|||
if self.gen_select: |
|||
if self.multiple: |
|||
self.js_script = ( |
|||
'$( "#{input_id}" ).ready( function() {{' |
|||
" var choices_{f_name} = {choices};" |
|||
" {del_select}" |
|||
" var engine_{f_name};" |
|||
" var setup_{f_name} = function() {{" |
|||
" engine_{f_name} = {engine};" |
|||
' $( "#{input_id}" ).tokenfield( "destroy" );' |
|||
' $( "#{input_id}" ).tokenfield({{typeahead: [ {datasets} ] }});' |
|||
" }};" |
|||
' $( "#{input_id}" ).bind( "tokenfield:createtoken", {tok_create} );' |
|||
' $( "#{input_id}" ).bind( "tokenfield:edittoken", {tok_edit} );' |
|||
' $( "#{input_id}" ).bind( "tokenfield:removetoken", {tok_remove} );' |
|||
" {tok_updates}" |
|||
" setup_{f_name}();" |
|||
" {tok_init_input}" |
|||
"}} );" |
|||
) |
|||
else: |
|||
self.js_script = ( |
|||
'$( "#{input_id}" ).ready( function() {{' |
|||
" var choices_{f_name} = {choices};" |
|||
" {del_select}" |
|||
" {gen_hidden}" |
|||
" var engine_{f_name};" |
|||
" var setup_{f_name} = function() {{" |
|||
" engine_{f_name} = {engine};" |
|||
' $( "#{input_id}" ).typeahead( "destroy" );' |
|||
' $( "#{input_id}" ).typeahead( {datasets} );' |
|||
" }};" |
|||
' $( "#{input_id}" ).bind( "typeahead:select", {typ_select} );' |
|||
' $( "#{input_id}" ).bind( "typeahead:change", {typ_change} );' |
|||
" {typ_updates}" |
|||
" setup_{f_name}();" |
|||
" {typ_init_input}" |
|||
"}} );" |
|||
) |
|||
else: |
|||
if self.multiple: |
|||
self.js_script = ( |
|||
"var choices_{f_name} = {choices};" |
|||
"var engine_{f_name};" |
|||
"var setup_{f_name} = function() {{" |
|||
" engine_{f_name} = {engine};" |
|||
' $( "#{input_id}" ).tokenfield( "destroy" );' |
|||
' $( "#{input_id}" ).tokenfield({{typeahead: [ {datasets} ] }});' |
|||
"}};" |
|||
'$( "#{input_id}" ).bind( "tokenfield:createtoken", {tok_create} );' |
|||
'$( "#{input_id}" ).bind( "tokenfield:edittoken", {tok_edit} );' |
|||
'$( "#{input_id}" ).bind( "tokenfield:removetoken", {tok_remove} );' |
|||
"{tok_updates}" |
|||
'$( "#{input_id}" ).ready( function() {{' |
|||
" setup_{f_name}();" |
|||
" {tok_init_input}" |
|||
"}} );" |
|||
) |
|||
else: |
|||
self.js_script = ( |
|||
"var choices_{f_name} ={choices};" |
|||
"var engine_{f_name};" |
|||
"var setup_{f_name} = function() {{" |
|||
" engine_{f_name} = {engine};" |
|||
' $( "#{input_id}" ).typeahead( "destroy" );' |
|||
' $( "#{input_id}" ).typeahead( {datasets} );' |
|||
"}};" |
|||
'$( "#{input_id}" ).bind( "typeahead:select", {typ_select} );' |
|||
'$( "#{input_id}" ).bind( "typeahead:change", {typ_change} );' |
|||
"{typ_updates}" |
|||
'$( "#{input_id}" ).ready( function() {{' |
|||
" setup_{f_name}();" |
|||
" {typ_init_input}" |
|||
"}} );" |
|||
) |
|||
|
|||
# Make sure the visible element doesn't have the same name as the hidden elements |
|||
# Otherwise, in the POST request, they collide and an incoherent value is sent |
|||
self.js_script += ( |
|||
'$( "#{input_id}" ).ready( function() {{' |
|||
' $( "#{input_id}" ).attr("name", "mbf_{f_name}");' |
|||
"}} );" |
|||
) |
|||
|
|||
def fill_js(self): |
|||
""" Fill the template with the correct values """ |
|||
self.js_script = self.js_script.format( |
|||
f_name=self.name, |
|||
choices=self.choices, |
|||
del_select=self.del_select(), |
|||
gen_hidden=self.gen_hidden(), |
|||
engine=self.engine, |
|||
input_id=self.input_id, |
|||
datasets=self.datasets, |
|||
typ_select=self.typeahead_select(), |
|||
typ_change=self.typeahead_change(), |
|||
tok_create=self.tokenfield_create(), |
|||
tok_edit=self.tokenfield_edit(), |
|||
tok_remove=self.tokenfield_remove(), |
|||
typ_updates=self.typeahead_updates(), |
|||
tok_updates=self.tokenfield_updates(), |
|||
tok_init_input=self.tokenfield_init_input(), |
|||
typ_init_input=self.typeahead_init_input(), |
|||
) |
|||
|
|||
def get_script(self): |
|||
""" Insert the JS code inside a script tag """ |
|||
self.js_script = render_tag("script", content=mark_safe(self.js_script)) |
|||
|
|||
def del_select(self): |
|||
""" JS code to delete the select if it has been generated and replace |
|||
it with an input. """ |
|||
return ( |
|||
'var p = $("#{select_id}").parent()[0];' |
|||
"var new_input = `{replace_input}`;" |
|||
"p.innerHTML = new_input;" |
|||
).format(select_id=self.input_id, replace_input=self.replace_input) |
|||
|
|||
def gen_hidden(self): |
|||
""" JS code to add a hidden tag to store the value. """ |
|||
return ( |
|||
'var d = $("#{div2_id}")[0];' |
|||
'var i = document.createElement("input");' |
|||
'i.id = "{hidden_id}";' |
|||
'i.name = "{html_name}";' |
|||
'i.value = "";' |
|||
'i.type = "hidden";' |
|||
"d.appendChild(i);" |
|||
).format( |
|||
div2_id=self.div2_id, |
|||
hidden_id=self.hidden_id, |
|||
html_name=self.bound.html_name, |
|||
) |
|||
|
|||
def typeahead_init_input(self): |
|||
""" JS code to init the fields values """ |
|||
init_key = self.bound.value() or '""' |
|||
return ( |
|||
'$( "#{input_id}" ).typeahead("val", {init_val});' |
|||
'$( "#{hidden_id}" ).val( {init_key} );' |
|||
).format( |
|||
input_id=self.input_id, |
|||
init_val='""' |
|||
if init_key == '""' |
|||
else "engine_{name}.get( {init_key} )[0].value".format( |
|||
name=self.name, init_key=init_key |
|||
), |
|||
init_key=init_key, |
|||
hidden_id=self.hidden_id, |
|||
) |
|||
|
|||
def typeahead_reset_input(self): |
|||
""" JS code to reset the fields values """ |
|||
return ( |
|||
'$( "#{input_id}" ).typeahead("val", "");' '$( "#{hidden_id}" ).val( "" );' |
|||
).format(input_id=self.input_id, hidden_id=self.hidden_id) |
|||
|
|||
def typeahead_select(self): |
|||
""" JS code to create the function triggered when an item is selected |
|||
through typeahead """ |
|||
return ( |
|||
"function(evt, item) {{" |
|||
' $( "#{hidden_id}" ).val( item.key );' |
|||
' $( "#{hidden_id}" ).change();' |
|||
" return item;" |
|||
"}}" |
|||
).format(hidden_id=self.hidden_id) |
|||
|
|||
def typeahead_change(self): |
|||
""" JS code of the function triggered when an item is changed (i.e. |
|||
looses focus and value has changed since the moment it gained focus ) |
|||
""" |
|||
return ( |
|||
"function(evt) {{" |
|||
' if ( $( "#{input_id}" ).typeahead( "val" ) === "" ) {{' |
|||
' $( "#{hidden_id}" ).val( "" );' |
|||
' $( "#{hidden_id}" ).change();' |
|||
" }}" |
|||
"}}" |
|||
).format(input_id=self.input_id, hidden_id=self.hidden_id) |
|||
|
|||
def typeahead_updates(self): |
|||
""" JS code for binding external fields changes with a reset """ |
|||
reset_input = self.typeahead_reset_input() |
|||
updates = [ |
|||
( |
|||
'$( "#{u_id}" ).change( function() {{' |
|||
" setup_{name}();" |
|||
" {reset_input}" |
|||
"}} );" |
|||
).format(u_id=u_id, name=self.name, reset_input=reset_input) |
|||
for u_id in self.update_on |
|||
] |
|||
return "".join(updates) |
|||
|
|||
def tokenfield_init_input(self): |
|||
""" JS code to init the fields values """ |
|||
init_key = self.bound.value() or '""' |
|||
return ('$( "#{input_id}" ).tokenfield("setTokens", {init_val});').format( |
|||
input_id=self.input_id, |
|||
init_val='""' |
|||
if init_key == '""' |
|||
else ( |
|||
"engine_{name}.get( {init_key} ).map(" |
|||
" function(o) {{ return o.value; }}" |
|||
")" |
|||
).format(name=self.name, init_key=init_key), |
|||
) |
|||
|
|||
def tokenfield_reset_input(self): |
|||
""" JS code to reset the fields values """ |
|||
return ('$( "#{input_id}" ).tokenfield("setTokens", "");').format( |
|||
input_id=self.input_id |
|||
) |
|||
|
|||
def tokenfield_create(self): |
|||
""" JS code triggered when a new token is created in tokenfield. """ |
|||
return ( |
|||
"function(evt) {{" |
|||
" var k = evt.attrs.key;" |
|||
" if (!k) {{" |
|||
" var data = evt.attrs.value;" |
|||
" var i = 0;" |
|||
" while ( i<choices_{name}.length &&" |
|||
" choices_{name}[i].value !== data ) {{" |
|||
" i++;" |
|||
" }}" |
|||
" if ( i === choices_{name}.length ) {{ return false; }}" |
|||
" k = choices_{name}[i].key;" |
|||
" }}" |
|||
' var new_input = document.createElement("input");' |
|||
' new_input.type = "hidden";' |
|||
' new_input.id = "{hidden_id}_"+k.toString();' |
|||
" new_input.value = k.toString();" |
|||
' new_input.name = "{html_name}";' |
|||
' $( "#{div2_id}" ).append(new_input);' |
|||
"}}" |
|||
).format( |
|||
name=self.name, |
|||
hidden_id=self.hidden_id, |
|||
html_name=self.bound.html_name, |
|||
div2_id=self.div2_id, |
|||
) |
|||
|
|||
def tokenfield_edit(self): |
|||
""" JS code triggered when a token is edited in tokenfield. """ |
|||
return ( |
|||
"function(evt) {{" |
|||
" var k = evt.attrs.key;" |
|||
" if (!k) {{" |
|||
" var data = evt.attrs.value;" |
|||
" var i = 0;" |
|||
" while ( i<choices_{name}.length &&" |
|||
" choices_{name}[i].value !== data ) {{" |
|||
" i++;" |
|||
" }}" |
|||
" if ( i === choices_{name}.length ) {{ return true; }}" |
|||
" k = choices_{name}[i].key;" |
|||
" }}" |
|||
" var old_input = document.getElementById(" |
|||
' "{hidden_id}_"+k.toString()' |
|||
" );" |
|||
" old_input.parentNode.removeChild(old_input);" |
|||
"}}" |
|||
).format(name=self.name, hidden_id=self.hidden_id) |
|||
|
|||
def tokenfield_remove(self): |
|||
""" JS code trigggered when a token is removed from tokenfield. """ |
|||
return ( |
|||
"function(evt) {{" |
|||
" var k = evt.attrs.key;" |
|||
" if (!k) {{" |
|||
" var data = evt.attrs.value;" |
|||
" var i = 0;" |
|||
" while ( i<choices_{name}.length &&" |
|||
" choices_{name}[i].value !== data ) {{" |
|||
" i++;" |
|||
" }}" |
|||
" if ( i === choices_{name}.length ) {{ return true; }}" |
|||
" k = choices_{name}[i].key;" |
|||
" }}" |
|||
" var old_input = document.getElementById(" |
|||
' "{hidden_id}_"+k.toString()' |
|||
" );" |
|||
" old_input.parentNode.removeChild(old_input);" |
|||
"}}" |
|||
).format(name=self.name, hidden_id=self.hidden_id) |
|||
|
|||
def tokenfield_updates(self): |
|||
""" JS code for binding external fields changes with a reset """ |
|||
reset_input = self.tokenfield_reset_input() |
|||
updates = [ |
|||
( |
|||
'$( "#{u_id}" ).change( function() {{' |
|||
" setup_{name}();" |
|||
" {reset_input}" |
|||
"}} );" |
|||
).format(u_id=u_id, name=self.name, reset_input=reset_input) |
|||
for u_id in self.update_on |
|||
] |
|||
return "".join(updates) |
|||
Loading…
Reference in new issue