8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-11-27 07:02:26 +00:00

Refactoring complet du mbf tag + pep8 du tag

Le tag se base maintenant sur des classes pour avoir un code plus clair
This commit is contained in:
Maël Kervella 2017-10-18 01:04:39 +00:00
parent e2d55b89c1
commit 09a32d3085
2 changed files with 584 additions and 454 deletions

View file

@ -54,7 +54,6 @@ from .forms import EditOuverturePortListForm, EditOuverturePortConfigForm
from .models import IpType, Machine, Interface, IpList, MachineType, Extension, Mx, Ns, Domain, Service, Service_link, Vlan, Nas, Text, OuverturePortList, OuverturePort
from users.models import User
from preferences.models import GeneralOption, OptionalMachine
from re2o.templatetags.massive_bootstrap_form import hidden_id, input_id
from re2o.utils import all_active_assigned_interfaces, all_has_access
from re2o.views import form

View file

@ -19,11 +19,18 @@
# 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 bootstrap3.templatetags.bootstrap3 import bootstrap_form
from bootstrap3.utils import render_tag
from bootstrap3.forms import render_field
@ -178,131 +185,297 @@ def massive_bootstrap_form(form, mbf_fields, *args, **kwargs):
{% massive_bootstrap_form form 'ipv4' choices='[...]' %}
"""
fields = mbf_fields.split(',')
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', {})
exclude = param.get('exclude', '').split(',')
choices = param.get('choices', {})
engine = param.get('engine', {})
match_func = param.get('match_func', {})
update_on = param.get('update_on', {})
gen_select = param.get('gen_select', {})
hidden_fields = [h.name for h in form.hidden_fields()]
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 = ''
# HTML code to insert inside a template
self.html = ""
for f_name, f_value in form.fields.items() :
if not f_name in exclude :
if f_name in fields and not f_name in hidden_fields :
if not isinstance(f_value.widget, Select) :
def render(self):
""" HTML code for the fully rendered form with all the necessary form
"""
for name, field in self.form.fields.items():
if not name in self.exclude:
if name in self.fields and not name 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.update_on.get(name, True),
*self.args,
**self.kwargs
)
self.html += mbf_field.render()
else:
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} from {form} is not a Select and'
('Field named {f_name} is not a Select and'
'can\'t be rendered with massive_bootstrap_form.'
).format(
f_name=f_name,
form=form
f_name=name_
)
)
multiple = f_value.widget.allow_multiple_selected
f_bound = f_value.get_bound_field( form, f_name )
# Name of the field
self.name = name_
# Django field object
self.field = field_
# Bound Django field associated with field
self.bound = bound_
if gen_select.get(f_name, True) :
html += render_field(
f_bound,
*args,
**kwargs
)
# 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'
f_value.widget = TextInput(
attrs = {
'name': 'mbf_'+f_name,
'placeholder': f_value.empty_label
}
)
replace_input = render_field(
f_value.get_bound_field( form, f_name ),
*args,
**kwargs
)
# 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 []
if not gen_select.get(f_name, True) :
html += replace_input
# 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
content = mbf_js(
f_name,
f_value,
f_bound,
multiple,
replace_input,
choices,
engine,
match_func,
update_on,
gen_select
)
if not multiple and not gen_select.get(f_name, True) :
content += hidden_tag( f_bound, f_name )
# Other bootstrap_form arguments to render the fields
self.args = args_
self.kwargs = kwargs_
html += render_tag(
'div',
content = content,
attrs = { 'id': custom_div_id( f_bound ) }
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:
html += render_field(
f_value.get_bound_field( form, f_name ),
*args,
**kwargs
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]
)
)
return mark_safe( html )
def input_id( f_bound ) :
""" The id of the HTML input element """
return f_bound.auto_id
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 hidden_id( f_bound ):
""" The id of the HTML hidden input element """
return input_id( f_bound ) + '_hidden'
def custom_div_id( f_bound ):
""" The id of the HTML div element containing values and script """
return input_id( f_bound ) + '_div'
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 hidden_tag( f_bound, f_name ):
""" The HTML hidden input element """
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': self.field.empty_label
}
)
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': hidden_id( f_bound ),
'name': f_bound.html_name,
'id': self.hidden_id,
'name': self.bound.html_name,
'type': 'hidden',
'value': f_bound.value() or ""
'value': self.bound.value() or ""
}
)
def mbf_js( f_name, f_value, f_bound, multiple, replace_input,
choices_, engine_, match_func_, update_on_, gen_select_ ) :
""" The whole script to use """
gen_select = gen_select_.get( f_name, True )
def gen_full_js(self):
""" Generate the full script tag containing the JS code """
self.create_js()
self.fill_js()
self.get_script()
choices = ( mark_safe( choices_[f_name] ) if f_name in choices_.keys()
else default_choices( f_value, f_bound, gen_select ) )
engine = ( mark_safe( engine_[f_name] ) if f_name in engine_.keys()
else default_engine ( f_name ) )
match_func = ( mark_safe( match_func_[f_name] )
if f_name in match_func_.keys() else default_match_func( f_name ) )
update_on = update_on_[f_name] if f_name in update_on_.keys() else []
if gen_select :
if multiple :
js_content = (
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}'
@ -321,7 +494,7 @@ def mbf_js( f_name, f_value, f_bound, multiple, replace_input,
'}} );'
)
else:
js_content = (
self.js_script = (
'$( "#{input_id}" ).ready( function() {{'
' var choices_{f_name} = {choices};'
' {del_select}'
@ -340,8 +513,8 @@ def mbf_js( f_name, f_value, f_bound, multiple, replace_input,
'}} );'
)
else:
if multiple :
js_content = (
if self.multiple:
self.js_script = (
'var choices_{f_name} = {choices};'
'var engine_{f_name};'
'var setup_{f_name} = function() {{'
@ -359,7 +532,7 @@ def mbf_js( f_name, f_value, f_bound, multiple, replace_input,
'}} );'
)
else:
js_content = (
self.js_script = (
'var choices_{f_name} ={choices};'
'var engine_{f_name};'
'var setup_{f_name} = function() {{'
@ -376,201 +549,96 @@ def mbf_js( f_name, f_value, f_bound, multiple, replace_input,
'}} );'
)
js_content = js_content.format(
f_name = f_name,
choices = choices,
del_select = del_select( f_bound, replace_input ),
gen_hidden = gen_hidden( f_bound ),
engine = engine,
input_id = input_id( f_bound ),
datasets = default_datasets( f_name, match_func ),
typ_select = typeahead_select( f_bound ),
typ_change = typeahead_change( f_bound ),
tok_create = tokenfield_create( f_name, f_bound ),
tok_edit = tokenfield_edit( f_name, f_bound ),
tok_remove = tokenfield_remove( f_name, f_bound ),
typ_updates = ''.join( [ (
'$( "#{u_id}" ).change( function() {{'
'setup_{f_name}();'
'{reset_input}'
'}} );'
).format(
u_id = u_id,
reset_input = typeahead_reset_input( f_bound ),
f_name = f_name
) for u_id in update_on ]
),
tok_updates = ''.join( [ (
'$( "#{u_id}" ).change( function() {{'
'setup_{f_name}();'
'{reset_input}'
'}} );'
).format(
u_id = u_id,
reset_input = tokenfield_reset_input( f_bound ),
f_name = f_name
) for u_id in update_on ]
),
tok_init_input = tokenfield_init_input( f_name, f_bound ),
typ_init_input = typeahead_init_input( f_name, f_bound ),
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()
)
return render_tag( 'script', content=mark_safe( js_content ) )
def typeahead_init_input( f_name, f_bound ) :
""" The JS script to init the fields values """
init_key = f_bound.value() or '""'
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 (
'$( "#{input_id}" ).typeahead("val", {init_val});'
'$( "#{hidden_id}" ).val( {init_key} );'
).format(
input_id = input_id( f_bound ),
init_val = '""' if init_key == '""' else
'engine_{f_name}.get( {init_key} )[0].value'.format(
f_name = f_name,
init_key = init_key
),
init_key = init_key,
hidden_id = hidden_id( f_bound )
)
def typeahead_reset_input( f_bound ) :
""" The JS script to reset the fields values """
return (
'$( "#{input_id}" ).typeahead("val", "");'
'$( "#{hidden_id}" ).val( "" );'
).format(
input_id = input_id( f_bound ),
hidden_id = hidden_id( f_bound )
)
def tokenfield_init_input( f_name, f_bound ) :
""" The JS script to init the fields values """
init_key = f_bound.value() or '""'
return (
'$( "#{input_id}" ).tokenfield("setTokens", {init_val});'
).format(
input_id = input_id( f_bound ),
init_val = '""' if init_key == '""' else (
'engine_{f_name}.get( {init_key} ).map('
'function(o) {{ return o.value; }}'
')').format(
f_name = f_name,
init_key = init_key
),
init_key = init_key,
)
def tokenfield_reset_input( f_bound ) :
""" The JS script to reset the fields values """
return (
'$( "#{input_id}" ).tokenfield("setTokens", "");'
).format(
input_id = input_id( f_bound ),
)
def default_choices( f_value, f_bound, gen_select ) :
""" The JS script creating the variable choices_<fieldname> """
if gen_select :
c = ( '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 = input_id( f_bound )
)
else :
c = '[{objects}]'.format(
objects = ','.join(
[ '{{key:{k},value:"{v}"}}'.format(
k = choice[0] if choice[0] != '' else '""',
v = choice[1]
) for choice in f_value.choices ]
)
)
return c
def del_select( f_bound, replace_input ) :
""" The JS script to delete the select if it has been generated
and replace it with an input. """
return ( 'var p = $("#{select_id}").parent()[0];'
'var p = $("#{select_id}").parent()[0];'
'var new_input = `{replace_input}`;'
'p.innerHTML = new_input;'
).format(
select_id = input_id( f_bound ),
replace_input = replace_input
select_id=self.input_id,
replace_input=self.replace_input
)
def gen_hidden( f_bound ):
""" The JS script to add a hidden tag to store the value. """
return ( 'var d = $("#{custom_div_id}")[0];'
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 = "{name}";'
'i.name = "{html_name}";'
'i.value = "";'
'i.type = "hidden";'
'd.appendChild(i);'
).format(
custom_div_id = custom_div_id( f_bound ),
hidden_id = hidden_id( f_bound ),
name = f_bound.html_name
div2_id=self.div2_id,
hidden_id=self.hidden_id,
html_name=self.bound.html_name
)
def default_engine ( f_name ) :
""" The JS script creating the variable engine_<field_name> """
def typeahead_init_input(self):
""" JS code to init the fields values """
init_key = self.bound.value() or '""'
return (
'new Bloodhound({{'
'datumTokenizer: Bloodhound.tokenizers.obj.whitespace("value"),'
'queryTokenizer: Bloodhound.tokenizers.whitespace,'
'local: choices_{f_name},'
'identify: function(obj) {{ return obj.key; }}'
'}})'
'$( "#{input_id}" ).typeahead("val", {init_val});'
'$( "#{hidden_id}" ).val( {init_key} );'
).format(
f_name = f_name
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 default_datasets( f_name, match_func ) :
""" The JS script creating the datasets to use with typeahead """
def typeahead_reset_input(self):
""" JS code to reset the fields values """
return (
'{{'
'hint: true,'
'highlight: true,'
'minLength: 0'
'}},'
'{{'
'display: "value",'
'name: "{f_name}",'
'source: {match_func}'
'}}'
'$( "#{input_id}" ).typeahead("val", "");'
'$( "#{hidden_id}" ).val( "" );'
).format(
f_name = f_name,
match_func = match_func
input_id=self.input_id,
hidden_id=self.hidden_id
)
def default_match_func ( f_name ) :
""" The JS script creating the matching function to use with typeahed """
return (
'function ( q, sync ) {{'
'if ( q === "" ) {{'
'var first = choices_{f_name}.slice( 0, 5 ).map('
'function ( obj ) {{ return obj.key; }}'
');'
'sync( engine_{f_name}.get( first ) );'
'}} else {{'
'engine_{f_name}.search( q, sync );'
'}}'
'}}'
).format(
f_name = f_name
)
def typeahead_select( f_bound ):
""" The JS script creating the function triggered when an item is
selected through typeahead """
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 );'
@ -578,12 +646,13 @@ def typeahead_select( f_bound ):
' return item;'
'}}'
).format(
hidden_id = hidden_id( f_bound )
hidden_id=self.hidden_id
)
def typeahead_change( f_bound ):
""" The JS script creating the function triggered when an item is changed
(i.e. looses focus and value has changed since the moment it gained focus
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) {{'
@ -593,53 +662,98 @@ def typeahead_change( f_bound ):
' }}'
'}}'
).format(
input_id = input_id( f_bound ),
hidden_id = hidden_id( f_bound )
input_id=self.input_id,
hidden_id=self.hidden_id
)
def tokenfield_create( f_name, f_bound ):
""" The JS script triggered when a new token is created in tokenfield. """
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_{f_name}.length &&'
'choices_{f_name}[i].value !== data ) {{'
' while ( i<choices_{name}.length &&'
' choices_{name}[i].value !== data ) {{'
' i++;'
' }}'
'if ( i === choices_{f_name}.length ) {{ return false; }}'
'k = choices_{f_name}[i].key;'
' 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 = "{name}";'
'$( "#{div_id}" ).append(new_input);'
' new_input.name = "{html_name}";'
' $( "#{div2_id}" ).append(new_input);'
'}}'
).format(
f_name = f_name,
hidden_id = hidden_id( f_bound ),
name = f_bound.html_name,
div_id = custom_div_id( f_bound )
name=self.name,
hidden_id=self.hidden_id,
html_name=self.bound.html_name,
div2_id=self.div2_id
)
def tokenfield_edit( f_name, f_bound ):
""" The JS script triggered when a token is edited in tokenfield. """
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_{f_name}.length &&'
'choices_{f_name}[i].value !== data ) {{'
' while ( i<choices_{name}.length &&'
' choices_{name}[i].value !== data ) {{'
' i++;'
' }}'
'if ( i === choices_{f_name}.length ) {{ return true; }}'
'k = choices_{f_name}[i].key;'
' if ( i === choices_{name}.length ) {{ return true; }}'
' k = choices_{name}[i].key;'
' }}'
' var old_input = document.getElementById('
' "{hidden_id}_"+k.toString()'
@ -647,24 +761,25 @@ def tokenfield_edit( f_name, f_bound ):
' old_input.parentNode.removeChild(old_input);'
'}}'
).format(
f_name = f_name,
hidden_id = hidden_id( f_bound )
name=self.name,
hidden_id=self.hidden_id
)
def tokenfield_remove( f_name, f_bound ):
""" The JS script trigggered when a token is removed from tokenfield. """
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_{f_name}.length &&'
'choices_{f_name}[i].value !== data ) {{'
' while ( i<choices_{name}.length &&'
' choices_{name}[i].value !== data ) {{'
' i++;'
' }}'
'if ( i === choices_{f_name}.length ) {{ return true; }}'
'k = choices_{f_name}[i].key;'
' if ( i === choices_{name}.length ) {{ return true; }}'
' k = choices_{name}[i].key;'
' }}'
' var old_input = document.getElementById('
' "{hidden_id}_"+k.toString()'
@ -672,7 +787,23 @@ def tokenfield_remove( f_name, f_bound ):
' old_input.parentNode.removeChild(old_input);'
'}}'
).format(
f_name = f_name,
hidden_id = hidden_id( f_bound )
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)