Installing a JavaScript Event Handler in a Form
This How-to applies to: Any version.
This how-to is really more of a code example than a how-to. It shows the python script I used in a form to handle two dynamic functions:
- Update an on-screen total whenever a new selection was made from any multiple-selection widget on the form;
- Make a multi-selection widget act more like a radio button widget by turning off all the other options whenever an option is checked. (The mutiple-selection widget was being displayed as a list of check boxes.)
Be warned. The code isn't going to make sense if you don't know JavaScript and Python. It's presented to demonstrate some techniques rather than as a recipe.The basics of the form are that it had several multiple-selection widgets, each displayed as a list of check boxes. The options of each multiple-selection widget were meant to be mutually exclusive. Each option also had a price as the last text in text in the label (e.g., "choose me to pay $50").
The code below was installed as a python script, then called from the Header Injection field of the form folder's [overrides] panel.
It has two major parts:
- It constructs the JavaScript text for two arrays; one connects prices with ids, the other is a list of lists of mutually exclusive ids.
- It returns a big chunk of prewritten JavaScript into which the price and exclude arrays are interpolated. As a bonus, it also includes a little bit of CSS to style the presentation of the dynamically totalled price.
And, let me add that I regard this script as basically a hack. It isn't meant to demonstrate good coding form; it just solves a single problem. It's meant to be read for the techniques it uses, not reproduced in your application.
The Script
# get our list of fields from the form folder
fields = context.fgFields()
# iterate the fields, looking for MultiSelectionWidgets
pitems = []
for field in fields:
if field.getWidgetName() == 'MultiSelectionWidget':
# get all the values of the field; extract their prices
# throw them into pitems in the format of an element of a
# JavaScript array
fname = field.getName()
count = 1
for flab in field.Vocabulary(context).values():
try:
price = flab[flab.rindex('$')+1:]
except:
price = '0'
pitems.append( "['%s_%d', %s]" % (fname, count, price) )
count += 1
prices = ',\n'.join(pitems)
pitems = []
for field in fields:
if field.getWidgetName() == 'MultiSelectionWidget':
fname = field.getName()
flen = len(field.Vocabulary(context))
for idx in range(1, flen+1):
current = '%s_%d' % (fname, idx)
exc = ['%s_%d' % (fname, idy) for idy in range(1, flen+1)]
exc.remove(current)
pitems.append( "'%s' : %s" % (current, exc) )
excludes = ',\n'.join(pitems)
return \
"""
<script type="text/javascript">
var sform = {}
sform.priceArray = [%s];
sform.excludes = {%s};
sform.docalc = function(e) {
var total=0;
var i, j;
var item, nitem;
var toExclude;
var elem;
// get element from event
if (e) {
elem = e.target;
} else {
// IE compatibility
var e = window.event;
if (e) {
elem = e.srcElement;
}
}
// do excludes
if (elem && elem.checked) {
elemid = elem.id;
var exs = sform.excludes[elemid];
if (exs) {
for (i=0; i < exs.length; i++) {
document.getElementById(exs[i]).checked = false;
}
}
}
// do totals
for (i=0; i < sform.priceArray.length; i++) {
item = document.getElementById(sform.priceArray[i][0]);
if (item.checked) {
total += sform.priceArray[i][1];
}
}
// update the displayed total
document.getElementById('gtotal').innerHTML = '$'+total;
}
// create a function that will install our click handler
// and do the first update
sform.symf_setup = function() {
var eid, item;
for (var i=0; i < sform.priceArray.length; i++) {
eid = sform.priceArray[i][0];
item = document.getElementById(eid);
item.onclick = sform.docalc;
}
sform.docalc();
}
// register our function to run on page display
registerPloneFunction(sform.symf_setup);
</script>
<!-- inject some CSS as well -->
<style>
#gtotal_table {
width: 100%%;
border-bottom: 1px solid black;
margin-bottom: 1em;
}
#gtotal_table th {
font-weight: bold;
font-size: large;
text-align: left;
}
#gtotal_table td {
text-align: right;
text-decoration: overline;
font-weight: bold;
font-size: large;
}
</style>
""" % (prices, excludes)