Roundup Tracker

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     &lt;disabled /script&gt;""" % 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)


CategoryJavascript CategoryDetectors