A customisation of classic template to allow superseder and assignedto to be selected by a multiselection option. That makes it more accessibles to user agents not supporting javascript.
In extensions/ directory, put multiselect.py ::
1 def javascript(propname):
2 """ two javascript functions : unselectNone unselect the first 'option'
3 selectNone : unselect all 'option' elements except the first one"""
4
5 return """<disabled script type="text/javascript">
6 <!--
7 var selectelement%s = document.getElementById("multiselect%s")
8 var options%s = selectelement%s.getElementsByTagName('option');
9
10 function unselectNone%s() {
11 options%s[0].selected = false;
12 }
13
14 function selectNone%s() {
15 var l = options%s.length - 1;
16 for (var i = 0; i++<l;) {
17 options%s[i].selected = false;
18 }
19 }
20 //-->
21 <disabled /script>""" % tuple([propname for i in range(9)])
22
23 def sortById(item1, item2):
24 if int(item1.id) > int(item2.id):
25 return 1
26 else:
27 return -1
28
29 def tryTogetAttr(item, attrs):
30 """ try to get attributes of the item
31 get a tuple for all possibles attributes to try if the first element of the
32 tuple correponds to an attribute, returns it. Otherwise, try with others
33 possibilities"""
34
35 attr = None
36 if attrs == (): return None
37 try:
38 attr = item[attrs[0]]
39 except KeyError:
40 attr = tryTogetAttr(item, attrs[1:])
41 else:
42 if not attr:
43 attr = tryTogetAttr(item, attrs[1:])
44 return attr
45
46 def multiselect (db, context, propname, size=5):
47 ''' returns a <select multiple> tag for property '''
48 classname = db.issue._props[propname].classname
49 items = db[classname].list()
50 items.sort(sortById)
51 currents = [item.id for item in context[propname]]
52
53 # open <select>
54 s = ['<select id="multiselect%s" name="%s" multiple="" size="%s">' %
55 (propname,propname, size)]
56 # if no selection is made now, select first line
57 if currents:
58 selected = ''
59 else:
60 selected = 'selected'
61 s.append('<option %s value="0" onclick="selectNone%s()">no \
62 selection</option>' % (selected,propname))
63
64 if classname == context._classname:
65 isuniqueitem = len(items) == 1 and items[0].id == context.id
66 else:
67 isuniqueitem = False
68
69 # <optgroup>
70 if items and not isuniqueitem:
71 s.append('<optgroup label="selection" onclick="unselectNone%s()">' % (propname))
72 for item in items:
73 id = item.id
74 # dont't show itself
75 if classname == context._classname and id == context.id:
76 continue
77 name = tryTogetAttr(item, ('realname', 'username', 'name', 'title'))
78 if item.id in currents:
79 selected = 'selected'
80 else:
81 selected = ''
82 s.append('<option nalue="%s" %s>%s: %s</option>' %
83 (item.id, selected, id, name))
84 s.append('</optgroup>')
85
86 # close <select>
87 s.append('</select>')
88
89 # must be appended after elements
90 # in order to reference elements by their id
91 # when javascript is executed
92 s.append(javascript(propname))
93 return "\n".join(s)
94
95 def init(instance):
96 instance.registerUtil('multiselect', multiselect)
That script will create the multiselection for the property given as argument. We will call the function multiselect from html templates and pass it 'superseder' and 'nosy' property.
in html/issue.item.html, replace (l 50)::
<tr> <th i18n:translate="">Superseder</th> <td> <span tal:replace="structure python:context.superseder.field(showid=1, size=20)" /> <span tal:condition="context/is_edit_ok" tal:replace="structure python:db.issue.classhelp('id,title', property='superseder')" /> <span tal:condition="context/superseder" tal:repeat="sup context/superseder"> <br><span i18n:translate="">View: <a i18n:name="link" tal:content="sup/id" tal:attributes="href string:issue${sup/id}"></a></span> </span> </td>
with::
<tr> <th i18n:translate="">Superseder</th> <td> <span tal:condition="context/is_edit_ok" tal:replace="structure python: utils.multiselect(db, context, 'superseder')">Superseder</span> <br> <span tal:condition="context/superseder" tal:repeat="sup context/superseder"> <br><span i18n:translate="">View: <a i18n:name="link" tal:content="sup/id" tal:attributes="href string:issue${sup/id}"></a></span> </span> </td>
and (l 60)::
<th i18n:translate="">Nosy List</th> <td> <span tal:replace="structure context/nosy/field" /> <span tal:condition="context/is_edit_ok" tal:replace="structure python:db.user.classhelp('username,realname,address', property='nosy', width='600')" /><br> </td>
with::
<th i18n:translate="">Nosy List</th> <td> <span tal:condition="context/is_edit_ok" tal:replace="structure python: utils.multiselect(db, context, 'nosy')">Nosy</span> </td>
As it is a multiselection menu, it is possible to select some properties *and* no properties, which is a non sense. multiselect function creates a javascript that should avoid that, but if javascript is not enabled on the user agent, it is possible to submit a selection with : no selection *and* some selections. Therefore, we have to audit the submission of the form, and consider that if some selections are made, we "uncheck" noselection. It may should be possible to raise an exception and reask for the submission by modifying the auditor.
detectors/clearform.py is ::
1 def clearform(db, cl, nodeid, newvalue):
2 # in html template, for multi we use <select multiple=""/> instead of <input/>
3 # therefore, we assin value="0" to option : no selection
4 # before submission, we have to check if option="0" has been selected.
5 # if "0" and only "0" is selected, no selection has been made, and we have to send []
6 # if "0" and other choices have been made, we assume selecting "0" is an error
7 # In either case, we have to remove "0" from the Multilink list
8
9 for propname in ['superseder', 'nosy']:
10 prop = newvalue.get(propname, [])
11 if '0' in prop:
12 prop.remove('0')
13
14 def init(db):
15 db.issue.audit('create', clearform)
16 db.issue.audit('set', clearform)