8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-12-26 08:53:46 +00:00

Merge branch 'massive_use_bft_tag' into 'master'

Massive use bft tag

See merge request rezo/re2o!22
This commit is contained in:
Maël Kervella 2017-10-19 21:50:57 +02:00
commit 869ddb5623
3 changed files with 647 additions and 410 deletions

View file

@ -111,8 +111,6 @@ from .models import (
) )
from users.models import User from users.models import User
from preferences.models import GeneralOption, OptionalMachine from preferences.models import GeneralOption, OptionalMachine
from re2o.templatetags.massive_bootstrap_form import hidden_id, input_id
from re2o.utils import ( from re2o.utils import (
all_active_assigned_interfaces, all_active_assigned_interfaces,
all_has_access, all_has_access,
@ -192,11 +190,13 @@ def generate_ipv4_mbf_param( form, is_type_tt ):
i_engine = { 'ipv4': generate_ipv4_engine( is_type_tt ) } i_engine = { 'ipv4': generate_ipv4_engine( is_type_tt ) }
i_match_func = { 'ipv4': generate_ipv4_match_func( is_type_tt ) } i_match_func = { 'ipv4': generate_ipv4_match_func( is_type_tt ) }
i_update_on = { 'ipv4': [f_type_id( is_type_tt )] } i_update_on = { 'ipv4': [f_type_id( is_type_tt )] }
i_gen_select = { 'ipv4': False }
i_mbf_param = { i_mbf_param = {
'choices': i_choices, 'choices': i_choices,
'engine': i_engine, 'engine': i_engine,
'match_func': i_match_func, 'match_func': i_match_func,
'update_on': i_update_on 'update_on': i_update_on,
'gen_select': i_gen_select
} }
return i_mbf_param return i_mbf_param

View file

@ -19,11 +19,18 @@
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 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 import template
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.forms import TextInput from django.forms import TextInput
from django.forms.widgets import Select from django.forms.widgets import Select
from bootstrap3.templatetags.bootstrap3 import bootstrap_form
from bootstrap3.utils import render_tag from bootstrap3.utils import render_tag
from bootstrap3.forms import render_field from bootstrap3.forms import render_field
@ -113,12 +120,29 @@ def massive_bootstrap_form(form, mbf_fields, *args, **kwargs):
A dict of list of ids that the values depends on. The engine A dict of list of ids that the values depends on. The engine
and the typeahead properties are recalculated and reapplied. and the typeahead properties are recalculated and reapplied.
Example : Example :
'addition' : { 'update_on' : {
'field_A' : [ 'id0', 'id1', ... ] , 'field_A' : [ 'id0', 'id1', ... ] ,
'field_B' : ... , '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 See boostrap_form_ for other arguments
**Usage**:: **Usage**::
@ -146,6 +170,11 @@ def massive_bootstrap_form(form, mbf_fields, *args, **kwargs):
[ '<field1>': '<update_on1>' [ '<field1>': '<update_on1>'
[, '<field2>': '<update_on2>' [, '<field2>': '<update_on2>'
[, ... ] ] ] [, ... ] ] ]
} ],
[, 'gen_select': {
[ '<field1>': '<gen_select1>'
[, '<field2>': '<gen_select2>'
[, ... ] ] ]
} ] } ]
} ] } ]
[ <standard boostrap_form parameters> ] [ <standard boostrap_form parameters> ]
@ -156,279 +185,189 @@ def massive_bootstrap_form(form, mbf_fields, *args, **kwargs):
{% massive_bootstrap_form form 'ipv4' choices='[...]' %} {% 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', {}) param = kwargs.pop('mbf_param', {})
exclude = param.get('exclude', '').split(',') self.choices = param.get('choices', {})
choices = param.get('choices', {}) self.engine = param.get('engine', {})
engine = param.get('engine', {}) self.match_func = param.get('match_func', {})
match_func = param.get('match_func', {}) self.update_on = param.get('update_on', {})
update_on = param.get('update_on', {}) self.gen_select = param.get('gen_select', {})
hidden_fields = [h.name for h in form.hidden_fields()] 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.gen_select.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( 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.' 'can\'t be rendered with massive_bootstrap_form.'
).format( ).format(
f_name=f_name, f_name=name_
form=form
) )
) )
multiple = f_value.widget.allow_multiple_selected # Name of the field
f_bound = f_value.get_bound_field( form, f_name ) self.name = name_
# Django field object
self.field = field_
# Bound Django field associated with field
self.bound = bound_
f_value.widget = TextInput( # Id for the main visible input
attrs = { self.input_id = self.bound.auto_id
'name': 'mbf_'+f_name, # Id for a hidden input used to store the value
'placeholder': f_value.empty_label self.hidden_id = self.input_id + '_hidden'
} # Id for another div containing hidden inputs and script
) self.div2_id = self.input_id + '_div'
html += render_field(
f_value.get_bound_field( form, f_name ),
*args,
**kwargs
)
if multiple : # Should the standard select should be generated
content = mbf_js( self.gen_select = gen_select_
f_name, # Is it select with multiple values possible (use of tokenfield)
f_value, self.multiple = self.field.widget.allow_multiple_selected
f_bound, # JS for the choices variable (user specified or default)
multiple, self.choices = choices_ or self.default_choices()
choices, # JS for the engine variable (typeahead) (user specified or default)
engine, self.engine = engine_ or self.default_engine()
match_func, # JS for the matching function (typeahead) (user specified or default)
update_on self.match_func = match_func_ or self.default_match_func()
) # JS for the datasets variable (typeahead) (user specified or default)
else : self.datasets = self.default_datasets()
content = hidden_tag( f_bound, f_name ) + mbf_js( # Ids of other fields to bind a reset/reload with when changed
f_name, self.update_on = update_on_ or []
f_value,
f_bound, # Whole HTML code to insert in the template
multiple, self.html = ""
choices, # JS code in the script tag
engine, self.js_script = ""
match_func, # Input tag to display instead of select
update_on self.replace_input = None
)
html += render_tag( # Other bootstrap_form arguments to render the fields
'div', self.args = args_
content = content, self.kwargs = kwargs_
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: else:
html += render_field(
f_value.get_bound_field( form, f_name ),
*args,
**kwargs
)
return mark_safe( html )
def input_id( f_bound ) :
""" The id of the HTML input element """
return f_bound.auto_id
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 hidden_tag( f_bound, f_name ):
""" The HTML hidden input element """
return render_tag(
'input',
attrs={
'id': hidden_id( f_bound ),
'name': f_bound.html_name,
'type': 'hidden',
'value': f_bound.value() or ""
}
)
def mbf_js( f_name, f_value, f_bound, multiple,
choices_, engine_, match_func_, update_on_ ) :
""" The whole script to use """
choices = ( mark_safe( choices_[f_name] ) if f_name in choices_.keys()
else default_choices( f_value ) )
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 multiple :
js_content = (
'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", {create} );'
'$( "#{input_id}" ).bind( "tokenfield:edittoken", {edit} );'
'$( "#{input_id}" ).bind( "tokenfield:removetoken", {remove} );'
'{updates}'
'$( "#{input_id}" ).ready( function() {{'
'setup_{f_name}();'
'{init_input}'
'}} );'
).format(
f_name = f_name,
choices = choices,
engine = engine,
input_id = input_id( f_bound ),
datasets = default_datasets( f_name, match_func ),
create = tokenfield_create( f_name, f_bound ),
edit = tokenfield_edit( f_name, f_bound ),
remove = tokenfield_remove( f_name, f_bound ),
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 ]
),
init_input = tokenfield_init_input( f_name, f_bound ),
)
else :
js_content = (
'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", {select} );'
'$( "#{input_id}" ).bind( "typeahead:change", {change} );'
'{updates}'
'$( "#{input_id}" ).ready( function() {{'
'setup_{f_name}();'
'{init_input}'
'}} );'
).format(
f_name = f_name,
choices = choices,
engine = engine,
input_id = input_id( f_bound ),
datasets = default_datasets( f_name, match_func ),
select = typeahead_select( f_bound ),
change = typeahead_change( f_bound ),
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 ]
),
init_input = typeahead_init_input( f_name, f_bound ),
)
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 '""'
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 ) :
""" The JS script creating the variable choices_<fieldname> """
return '[{objects}]'.format( return '[{objects}]'.format(
objects=','.join( objects=','.join(
['{{key:{k},value:"{v}"}}'.format( ['{{key:{k},value:"{v}"}}'.format(
k=choice[0] if choice[0] != '' else '""', k=choice[0] if choice[0] != '' else '""',
v=choice[1] v=choice[1]
) for choice in f_value.choices ] ) for choice in self.field.choices]
) )
) )
def default_engine ( f_name ) :
""" The JS script creating the variable engine_<field_name> """ def default_engine(self):
""" Default JS code of the variable engine_<field_name> """
return ( return (
'new Bloodhound({{' 'new Bloodhound({{'
' datumTokenizer: Bloodhound.tokenizers.obj.whitespace("value"),' ' datumTokenizer: Bloodhound.tokenizers.obj.whitespace("value"),'
' queryTokenizer: Bloodhound.tokenizers.whitespace,' ' queryTokenizer: Bloodhound.tokenizers.whitespace,'
'local: choices_{f_name},' ' local: choices_{name},'
' identify: function(obj) {{ return obj.key; }}' ' identify: function(obj) {{ return obj.key; }}'
'}})' '}})'
).format( ).format(
f_name = f_name name=self.name
) )
def default_datasets( f_name, match_func ) :
""" The JS script creating the datasets to use with typeahead """ def default_datasets(self):
""" Default JS script of the datasets to use with typeahead """
return ( return (
'{{' '{{'
' hint: true,' ' hint: true,'
@ -437,34 +376,269 @@ def default_datasets( f_name, match_func ) :
'}},' '}},'
'{{' '{{'
' display: "value",' ' display: "value",'
'name: "{f_name}",' ' name: "{name}",'
' source: {match_func}' ' source: {match_func}'
'}}' '}}'
).format( ).format(
f_name = f_name, name=self.name,
match_func = match_func match_func=self.match_func
) )
def default_match_func ( f_name ) :
""" The JS script creating the matching function to use with typeahed """ def default_match_func(self):
""" Default JS code of the matching function to use with typeahed """
return ( return (
'function ( q, sync ) {{' 'function ( q, sync ) {{'
' if ( q === "" ) {{' ' if ( q === "" ) {{'
'var first = choices_{f_name}.slice( 0, 5 ).map(' ' var first = choices_{name}.slice( 0, 5 ).map('
' function ( obj ) {{ return obj.key; }}' ' function ( obj ) {{ return obj.key; }}'
' );' ' );'
'sync( engine_{f_name}.get( first ) );' ' sync( engine_{name}.get( first ) );'
' }} else {{' ' }} else {{'
'engine_{f_name}.search( q, sync );' ' engine_{name}.search( q, sync );'
' }}' ' }}'
'}}' '}}'
).format( ).format(
f_name = f_name name=self.name
) )
def typeahead_select( f_bound ):
""" The JS script creating the function triggered when an item is def render(self):
selected through typeahead """ """ 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': 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}'
'}} );'
)
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 ( return (
'function(evt, item) {{' 'function(evt, item) {{'
' $( "#{hidden_id}" ).val( item.key );' ' $( "#{hidden_id}" ).val( item.key );'
@ -472,12 +646,13 @@ def typeahead_select( f_bound ):
' return item;' ' return item;'
'}}' '}}'
).format( ).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 def typeahead_change(self):
(i.e. looses focus and value has changed since the moment it gained focus """ 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 ( return (
'function(evt) {{' 'function(evt) {{'
@ -487,53 +662,98 @@ def typeahead_change( f_bound ):
' }}' ' }}'
'}}' '}}'
).format( ).format(
input_id = input_id( f_bound ), input_id=self.input_id,
hidden_id = hidden_id( f_bound ) 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 ( return (
'function(evt) {{' 'function(evt) {{'
' var k = evt.attrs.key;' ' var k = evt.attrs.key;'
' if (!k) {{' ' if (!k) {{'
' var data = evt.attrs.value;' ' var data = evt.attrs.value;'
' var i = 0;' ' var i = 0;'
'while ( i<choices_{f_name}.length &&' ' while ( i<choices_{name}.length &&'
'choices_{f_name}[i].value !== data ) {{' ' choices_{name}[i].value !== data ) {{'
' i++;' ' i++;'
' }}' ' }}'
'if ( i === choices_{f_name}.length ) {{ return false; }}' ' if ( i === choices_{name}.length ) {{ return false; }}'
'k = choices_{f_name}[i].key;' ' k = choices_{name}[i].key;'
' }}' ' }}'
' var new_input = document.createElement("input");' ' var new_input = document.createElement("input");'
' new_input.type = "hidden";' ' new_input.type = "hidden";'
' new_input.id = "{hidden_id}_"+k.toString();' ' new_input.id = "{hidden_id}_"+k.toString();'
' new_input.value = k.toString();' ' new_input.value = k.toString();'
'new_input.name = "{name}";' ' new_input.name = "{html_name}";'
'$( "#{div_id}" ).append(new_input);' ' $( "#{div2_id}" ).append(new_input);'
'}}' '}}'
).format( ).format(
f_name = f_name, name=self.name,
hidden_id = hidden_id( f_bound ), hidden_id=self.hidden_id,
name = f_bound.html_name, html_name=self.bound.html_name,
div_id = custom_div_id( f_bound ) 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 ( return (
'function(evt) {{' 'function(evt) {{'
' var k = evt.attrs.key;' ' var k = evt.attrs.key;'
' if (!k) {{' ' if (!k) {{'
' var data = evt.attrs.value;' ' var data = evt.attrs.value;'
' var i = 0;' ' var i = 0;'
'while ( i<choices_{f_name}.length &&' ' while ( i<choices_{name}.length &&'
'choices_{f_name}[i].value !== data ) {{' ' choices_{name}[i].value !== data ) {{'
' i++;' ' i++;'
' }}' ' }}'
'if ( i === choices_{f_name}.length ) {{ return true; }}' ' if ( i === choices_{name}.length ) {{ return true; }}'
'k = choices_{f_name}[i].key;' ' k = choices_{name}[i].key;'
' }}' ' }}'
' var old_input = document.getElementById(' ' var old_input = document.getElementById('
' "{hidden_id}_"+k.toString()' ' "{hidden_id}_"+k.toString()'
@ -541,24 +761,25 @@ def tokenfield_edit( f_name, f_bound ):
' old_input.parentNode.removeChild(old_input);' ' old_input.parentNode.removeChild(old_input);'
'}}' '}}'
).format( ).format(
f_name = f_name, name=self.name,
hidden_id = hidden_id( f_bound ) 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 ( return (
'function(evt) {{' 'function(evt) {{'
' var k = evt.attrs.key;' ' var k = evt.attrs.key;'
' if (!k) {{' ' if (!k) {{'
' var data = evt.attrs.value;' ' var data = evt.attrs.value;'
' var i = 0;' ' var i = 0;'
'while ( i<choices_{f_name}.length &&' ' while ( i<choices_{name}.length &&'
'choices_{f_name}[i].value !== data ) {{' ' choices_{name}[i].value !== data ) {{'
' i++;' ' i++;'
' }}' ' }}'
'if ( i === choices_{f_name}.length ) {{ return true; }}' ' if ( i === choices_{name}.length ) {{ return true; }}'
'k = choices_{f_name}[i].key;' ' k = choices_{name}[i].key;'
' }}' ' }}'
' var old_input = document.getElementById(' ' var old_input = document.getElementById('
' "{hidden_id}_"+k.toString()' ' "{hidden_id}_"+k.toString()'
@ -566,7 +787,23 @@ def tokenfield_remove( f_name, f_bound ):
' old_input.parentNode.removeChild(old_input);' ' old_input.parentNode.removeChild(old_input);'
'}}' '}}'
).format( ).format(
f_name = f_name, name=self.name,
hidden_id = hidden_id( f_bound ) 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)

View file

@ -53,7 +53,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% massive_bootstrap_form machineform 'user' %} {% massive_bootstrap_form machineform 'user' %}
{% endif %} {% endif %}
{% if interfaceform %} {% if interfaceform %}
{% if i_bft_param %} {% if i_mbf_param %}
{% massive_bootstrap_form interfaceform 'ipv4,machine' mbf_param=i_mbf_param %} {% massive_bootstrap_form interfaceform 'ipv4,machine' mbf_param=i_mbf_param %}
{% else %} {% else %}
{% massive_bootstrap_form interfaceform 'ipv4,machine' %} {% massive_bootstrap_form interfaceform 'ipv4,machine' %}