Changeset 4250


Ignore:
Timestamp:
Apr 25, 2008, 3:41:14 PM (14 years ago)
Author:
Nicklas Nordborg
Message:

References #965: Filtering the list – selection list vs. input box for enumeration type annotations

Prototype is working. Need more Firefox vs. IE checks, css optimization, etc.

Location:
trunk
Files:
1 added
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/clients/web/net/sf/basedb/clients/web/taglib/table/ColumnDef.java

    r4232 r4250  
    6464      enum=...
    6565      enumeration=...
     66      smartenum=true|false
    6667      title=...
    6768      sortable=true|false
     
    233234 
    234235  <tr>
     236    <td>smartenum</td>
     237    <td>false (true for annotations)</td>
     238    <td>no</td>
     239    <td>
     240      If the filter for an enumeration column should be displayed as
     241      a free text field or a selection list. Default is to use a selection
     242      list. Use this option only if both the hidden key and the displayed
     243      value of the enumeration is the same. The "smartness" of this options
     244      is that as the user types text, the browser will dynamically display a list
     245      of options matching the entered text.
     246    </td>
     247  </tr>
     248 
     249  <tr>
    235250    <td>formatter</td>
    236251    <td>-</td>
     
    346361  private Enumeration<String, String> enumeration;
    347362 
     363  private boolean smartEnum;
     364 
    348365  private Formatter<?> formatter;
    349366 
     
    507524  {
    508525    return enumeration;
     526  }
     527 
     528  /**
     529    @since 2.7
     530  */
     531  public void setSmartenum(boolean smartEnum)
     532  {
     533    this.smartEnum = smartEnum;
    509534  }
    510535 
     
    602627        Formatter<Date> dateFormatter = FormatterFactory.getDateFormatter(table.getSc());
    603628       
     629        String inputName = "filter:" + valueType.name() + ":" + getFilterproperty();
    604630        if (getEnumeration() != null)
    605631        {
    606632          String filterValue = filter == null ? "" : Base.getPropertyFilterString(filter, dateFormatter);
    607           sb.append("<select name=\"filter:").append(valueType.name()).append(":").append(getFilterproperty()).append("\"");
    608           sb.append(" onchange=\"Forms.submit(event);\">\n");
    609           sb.append("<option value=\"\">");
    610           Enumeration<String, String> enumeration = getEnumeration();
    611           for (int i = 0; i < enumeration.size(); ++i)
     633          if (smartEnum || isAnnotation)
    612634          {
    613             String selected = filterValue.equals(enumeration.getKey(i).trim()) ? " selected" : "";
    614             sb.append("<option value=\"").append(enumeration.getKey(i)).append("\"").append(selected);
    615             sb.append(">").append(enumeration.getValue(i)).append("\n");
     635            sb.append("<span class=\"smartinput\"><input class=\"text\" type=\"text\"");
     636            sb.append(" name=\"").append(inputName).append("\"");
     637            sb.append(" size=\"20\"");
     638            sb.append(" maxlength=\"").append(PropertyFilter.MAX_VALUE_LENGTH).append("\"");
     639            sb.append(" value=\"").append(filterValue).append("\"");
     640            sb.append(">");
     641            sb.append("<img class=\"smartimage\" src=\"").append(table.getPage().getRoot()).append("images/dropdown.png\"");
     642            sb.append(" onclick=\"SmartEnum.activateAndDisplay('");
     643            sb.append(table.getId()).append("','").append(inputName).append("')\">");
     644            sb.append("</span>");
     645            StringBuilder addSmart = new StringBuilder();
     646            addSmart.append("SmartEnum.enableForField('").append(table.getId()).append("'");
     647            addSmart.append(",'").append(inputName).append("'");
     648            addSmart.append(",[");
     649            for (int i = 0; i < enumeration.size(); ++i)
     650            {
     651              if (i > 0) addSmart.append(",");
     652              addSmart.append("'").append(HTML.javaScriptEncode(enumeration.getKey(i))).append("'");
     653            }
     654            addSmart.append("]);\n");
     655            table.addScript(addSmart.toString());
    616656          }
    617           sb.append("</select>");
     657          else
     658          {
     659            sb.append("<select name=\"").append(inputName).append("\"");
     660            sb.append(" onchange=\"Forms.submit(event);\">\n");
     661            sb.append("<option value=\"\">");
     662            Enumeration<String, String> enumeration = getEnumeration();
     663            for (int i = 0; i < enumeration.size(); ++i)
     664            {
     665              String selected = filterValue.equals(enumeration.getKey(i).trim()) ? " selected" : "";
     666              sb.append("<option value=\"").append(enumeration.getKey(i)).append("\"").append(selected);
     667              sb.append(">").append(enumeration.getValue(i)).append("\n");
     668            }
     669            sb.append("</select>");
     670          }
    618671        }
    619672        else if (valueType == Type.BOOLEAN)
    620673        {
    621674          boolean filterValue = filter == null ? false : Values.getBoolean(filter.getValue());
    622           String radioName = "filter:"+valueType.name()+":"+getFilterproperty();
    623           sb.append("<input type=\"radio\" name=\"").append(radioName).append("\"");
     675          sb.append("<input type=\"radio\" name=\"").append(inputName).append("\"");
    624676          sb.append(filter == null ? "checked" : "");
    625677          sb.append(" value=\"\" onclick=\"Forms.submit(event)\">").append("any");
    626678         
    627           sb.append("<input type=\"radio\" name=\"").append(radioName).append("\"");
     679          sb.append("<input type=\"radio\" name=\"").append(inputName).append("\"");
    628680          sb.append(filter != null && filterValue ? "checked" : "");
    629681          sb.append(" value=\"true\" onclick=\"Forms.submit(event)\">").append("true");
    630682         
    631           sb.append("<input type=\"radio\" name=\"").append(radioName).append("\"");
     683          sb.append("<input type=\"radio\" name=\"").append(inputName).append("\"");
    632684          sb.append(filter != null && !filterValue ? "checked" : "");
    633685          sb.append(" value=\"false\" onclick=\"Forms.submit(event)\">").append("false");
     
    637689          String filterValue = HTML.encodeTags(Base.getPropertyFilterString(filter, dateFormatter));
    638690          sb.append("<input class=\"text\" type=\"text\"");
    639           sb.append(" name=\"filter:").append(valueType.name()).append(":").append(getFilterproperty()).append("\"");
     691          sb.append(" name=\"").append(inputName).append("\"");
    640692          sb.append(" size=\"20\"");
    641693          sb.append(" maxlength=\"").append(PropertyFilter.MAX_VALUE_LENGTH).append("\"");
  • trunk/www/WEB-INF/table.tld

    r3679 r4250  
    200200    </attribute>
    201201    <attribute>
     202      <name>smartenum</name>
     203      <required>false</required>
     204      <rtexprvalue>true</rtexprvalue>
     205    </attribute>
     206    <attribute>
    202207      <name>filtervalue</name>
    203208      <required>false</required>
  • trunk/www/common/plugin/index.jsp

    r3995 r4250  
     1<%@page import="net.sf.basedb.core.signal.SignalReceiver"%>
     2<%@page import="net.sf.basedb.core.signal.LocalSignalReceiver"%>
    13<%-- $Id$
    24  ------------------------------------------------------------------
     
    521523    {
    522524      PluginExecutionRequest executionRequest = pluginResponse.getExecutionRequest(null);
    523       Thread t = new Thread(executionRequest);
     525     
     526      executionRequest.registerSignalReceiver(LocalSignalReceiver.getSignalReceiver("localhost:0"));
     527      Thread t = new Thread(executionRequest, "immediate");
    524528      t.setPriority(Thread.currentThread().getPriority() - 1);
    525529      t.start();
  • trunk/www/include/scripts/main.js

    r4187 r4250  
    286286    var charCode = event.charCode;
    287287    //if (charCode == undefined) charCode = event.which;
    288     if (!charCode && event.keyCode < 32) charCode = event.keyCode;
     288    if (!charCode) charCode = event.keyCode;
     289    if (!charCode) charCode = event.which;
    289290    if (!charCode) charCode = 0;
    290291    return charCode;
     
    294295  this.getEventTarget = function(event)
    295296  {
    296     var target = event.target;
    297     if (!target) target = event.srcElement;
     297    var target = event.srcElement;
     298    if (!target) target = event.target;
    298299    return target;
    299300  }
     
    317318    }
    318319    return height;
     320  }
     321
     322  // Get the position of an element; returns an object with 'left' and 'top' properties
     323  this.getElementPosition = function(theElement)
     324  {
     325    var offsetTrail = theElement;
     326    var offsetTop = 0;
     327    var offsetLeft = 0;
     328    while (offsetTrail)
     329    {
     330      offsetTop += offsetTrail.offsetTop;
     331      offsetLeft += offsetTrail.offsetLeft;
     332      offsetTrail = offsetTrail.offsetParent;
     333    }
     334    return {left: offsetLeft, top:offsetTop};
    319335  }
    320336 
     
    12771293}
    12781294
     1295var SmartEnum = new SmartEnumClass();
     1296/*
     1297  Handles "smart" input fields which can have an attached list of
     1298  enumeration options. As the user types a popup-list is displayed
     1299  with the matching options.
     1300*/
     1301function SmartEnumClass()
     1302{
     1303  this.initialDelay = 300;
     1304  this.repeatDelay = 50;
     1305
     1306  this.activeField = null;
     1307  this.selected = -1;
     1308  this.lastSelected = null;
     1309  this.lastText = '';
     1310
     1311  /*
     1312    Initialise the smart enum system by creating the <div> tag that
     1313    is used to hold the options.
     1314  */
     1315  this.init = function()
     1316  {
     1317    if (this.div) return;
     1318    var div = document.createElement('div');
     1319    div.className = 'smartenum';
     1320    div.style.display = 'none';
     1321    document.body.appendChild(div);
     1322    this.div = div;
     1323    this.debug = document.createElement('div');
     1324    document.body.appendChild(this.debug);
     1325  }
     1326
     1327  /*
     1328    Enable the input field for smart enum. Ignored if
     1329    the form or field doesn't exists, the field is not an
     1330    input field or if there are no options. This method
     1331    changes event handlers for the input field.
     1332    @param frmName The name of the form
     1333    @param fieldName The name of the field
     1334    @param An array object with the options
     1335  */
     1336  this.enableForField = function(frmName, fieldName, options)
     1337  {
     1338    if (!options) return;
     1339    var frm = document.forms[frmName];
     1340    if (!frm) return;
     1341    var field = frm[fieldName];
     1342    if (!field) return;
     1343
     1344    this.init();
     1345    field.smartOptions = options;
     1346    field.setAttribute('autocomplete', 'off');
     1347    Main.addClass(field, 'smartinput');
     1348    field.onfocus = this.onfocus;
     1349    field.onkeyup = this.onkeyup;
     1350    field.onkeydown = this.onkeydown;
     1351    field.onblur = this.onblur;
     1352  }
     1353
     1354  this.activateAndDisplay = function(formName, fieldName)
     1355  {
     1356    var frm = document.forms[formName];
     1357    if (!frm) return;
     1358    var field = frm[fieldName];
     1359    if (!field) return;
     1360   
     1361    if (this.blurTimerId)
     1362    {
     1363      clearTimeout(this.blurTimerId);
     1364      this.blurTimerId = null;
     1365    }
     1366    this.setActive(field);
     1367    this.filterOptions(true);
     1368    this.showOptions();
     1369    var oldfocus = field.onfocus;
     1370    field.onfocus = null;
     1371    field.focus();
     1372    field.onfocus = oldFocus;
     1373  }
     1374
     1375  /*
     1376    Set a field as the active field. All other operations will
     1377    now use this field. Set to null to disable all smart enum.
     1378    Ignored if the field doesn't have any options. This method
     1379    also positions the <div> with the options.
     1380  */
     1381  this.setActive = function(field)
     1382  {
     1383    if (!field || !field.smartOptions) return;
     1384    this.activeField = field;
     1385    var pos = Main.getElementPosition(this.activeField);
     1386    var div = this.div;
     1387    div.style.top = (pos.top + this.activeField.offsetHeight) + 'px';
     1388    div.style.left = (pos.left-2) + 'px';
     1389    div.style.width = (this.activeField.offsetWidth + 10) + 'px';
     1390  }
     1391
     1392  /*
     1393    Hide the enum options.
     1394  */
     1395  this.hideOptions = function()
     1396  {
     1397    this.div.style.display = 'none';
     1398  }
     1399
     1400  /*
     1401    Show the enum options setting the correct position.
     1402  */
     1403  this.showOptions = function()
     1404  {
     1405    this.div.style.display = 'block';
     1406  }
     1407
     1408  /*
     1409    Filter the enum options by the text in the active field.
     1410    The options that passed the filter are added as <div> tags
     1411    to the enum <div>.
     1412    @return TRUE if the filter generated any options, FALSE otherwise
     1413  */
     1414  this.filterOptions = function(allowEmpty)
     1415  {
     1416    if (!this.activeField) return false;
     1417    var text = this.activeField.value.replace(/^[=!<>]*/, '');
     1418    if (text == this.lastMatchedText) return true;
     1419    if (text == '' && !allowEmpty) return false;
     1420   
     1421    var options = this.activeField.smartOptions;
     1422    this.lastMatchedText = text;
     1423    this.selected = -1;
     1424    this.lastSelected = null;
     1425    var matching = '';
     1426    var index = 0;
     1427    for (var i = 0; i < options.length; i++)
     1428    {
     1429      if (options[i].indexOf(text) >= 0)
     1430      {
     1431        matching += '<div class="smartoption" id="smartoption.' + index + '"';
     1432        matching += ' onmouseover="SmartEnum.setSelectedOption(' + index + ')"';
     1433        matching += ' onclick="SmartEnum.submitSelected()">' + options[i] + '</div>';
     1434        index++;
     1435      }
     1436    }
     1437    this.div.innerHTML = matching;
     1438    return true;
     1439  }
     1440
     1441  /*
     1442    Mark a specific enum option as selected. Also makes sure
     1443    that the selected option is within the viewable area.
     1444    @param index The index of the option
     1445  */
     1446  this.setSelectedOption = function(index)
     1447  {
     1448    if (this.lastSelected)
     1449    {
     1450      Main.removeClass(this.lastSelected, 'selected');
     1451      this.lastSelected = null;
     1452    }
     1453    var option = document.getElementById('smartoption.' + index);
     1454    if (option)
     1455    {
     1456      Main.addClass(option, 'selected');
     1457      this.lastSelected = option;
     1458      this.selected = index;
     1459      var optionTop = option.offsetTop;
     1460      var optionBottom = optionTop + option.offsetHeight;
     1461      var parent = option.offsetParent;
     1462      var parentTop = parent.scrollTop;
     1463      var parentBottom = parentTop + parent.offsetHeight;
     1464      if (optionTop < parentTop)
     1465      {
     1466        parent.scrollTop = optionTop;
     1467      }
     1468      else if (optionBottom > parentBottom)
     1469      {
     1470        parent.scrollTop = optionBottom - parent.offsetHeight;
     1471      }
     1472    }
     1473  }
     1474
     1475  /*
     1476    Moves the selection up or down, optionally
     1477    repeating the operation at specified time intervals.
     1478    @param delta The number of options to shift the selection
     1479    @param delayRepeat Number of milliseconds to wait until
     1480      the move is repeated for the first time
     1481    @param repeat Number of milliseconds to wait between
     1482      subsequent repeats
     1483  */
     1484  this.moveSelection = function(delta, delayRepeat, repeat)
     1485  {
     1486    var option = document.getElementById('smartoption.' + (this.selected + delta));
     1487    if (option)
     1488    {
     1489      this.setSelectedOption(this.selected + delta);
     1490    }
     1491    if (delayRepeat && repeat)
     1492    {
     1493      var call = 'SmartEnum.moveSelection(' + delta + ',' + repeat + ',' + repeat + ')';
     1494      this.timerId = setTimeout(call, delayRepeat);
     1495    }
     1496  }
     1497
     1498  /*
     1499    Copies the selected option to the active field and submits
     1500    the form. If no option is selected, just submit the form.
     1501  */
     1502  this.submitSelected = function(event)
     1503  {
     1504    if (!this.activeField) return;
     1505    if (this.lastSelected)
     1506    {
     1507      var text = this.activeField.value;
     1508      var selectedText = this.lastSelected.innerHTML;
     1509      this.activeField.value = text.replace(/^([=!<>]*).*/, '$1' + selectedText);
     1510    }
     1511    this.activeField.form.submit();
     1512  }
     1513
     1514  /*
     1515    Cancels a move selection repeat set in the 'moveSelection' method.
     1516  */
     1517  this.cancelKeyRepeat = function()
     1518  {
     1519    if (this.timerId)
     1520    {
     1521      clearTimeout(this.timerId);
     1522      this.timerId = null;
     1523    }
     1524  }
     1525
     1526  /*
     1527    Called when a smart-enum enabled field receivs
     1528    the focus. This should set the current field to
     1529    the active field.
     1530  */
     1531  this.onfocus = function(event)
     1532  {
     1533    if (!event) event = window.event;
     1534    var field = Main.getEventTarget(event);
     1535    SmartEnum.setActive(field);
     1536  }
     1537 
     1538  /*
     1539    Called when a smart-enum enabled field loses
     1540    the focus. This should hide the options and
     1541    set the current field to null.
     1542  */
     1543  this.onblur = function(event)
     1544  {
     1545    if (!event) event = window.event;
     1546    // Need timout, otherwise the options are hidden and can't receive
     1547    // a possible mouseclick that selects an options and submits the form.
     1548    SmartEnum.blurTimerId = setTimeout('SmartEnum.hideOptions(); SmartEnum.setActive(null);', 100);
     1549  }
     1550 
     1551  /*
     1552    Called when a key is pressed in a smart-enum enabled field.
     1553    This function detects:
     1554    * up/down arrow: moves the selected option
     1555    * enter: submits the form
     1556  */
     1557  this.onkeydown = function(event)
     1558  {
     1559    if (!event) event = window.event;
     1560    var charCode = Main.getEventCharCode(event);
     1561    if (charCode == 38)
     1562    {
     1563      // Up arrow
     1564      SmartEnum.moveSelection(-1, SmartEnum.initialDelay, SmartEnum.repeatDelay);
     1565    }
     1566    else if (charCode == 40)
     1567    {
     1568      // Down arrow
     1569      SmartEnum.moveSelection(1, SmartEnum.initialDelay, SmartEnum.repeatDelay);
     1570    }
     1571    else if (charCode == 13)
     1572    {
     1573      // Enter
     1574      SmartEnum.submitSelected(event);
     1575    }
     1576  }
     1577 
     1578  /*
     1579    Called when a key is released in a smart-enum enabled field.
     1580    This function detects changes to the text and displays matching
     1581    options.
     1582  */
     1583  this.onkeyup = function(event)
     1584  {
     1585    if (!event) event = window.event;
     1586    SmartEnum.cancelKeyRepeat();
     1587    var charCode = Main.getEventCharCode(event);
     1588    if (SmartEnum.filterOptions(charCode == 40 || charCode == 38))
     1589    {
     1590      SmartEnum.showOptions();
     1591    }
     1592    else
     1593    {
     1594      SmartEnum.hideOptions();
     1595    }
     1596  }
     1597 
     1598}
     1599
    12791600var Forms = new FormClass();
    12801601// Functions related to form handling
    12811602function FormClass()
    12821603{
     1604
    12831605  /*
    12841606    Attach this function to the onkeypress attribute of a field to
  • trunk/www/include/styles/main.css

    r3820 r4250  
    348348  padding: 2px;
    349349}
     350
     351.smartenum {
     352  position: absolute;
     353  width: 100px;
     354  height: 200px;
     355  background: #E0E0E0;
     356  border-top: 1px solid #333333;
     357  border-left: 1px solid #333333;
     358  border-right: 1px solid #333333;
     359  border-bottom: 1px solid #333333;
     360  padding: 2px;
     361  z-index: 999;
     362  overflow: auto;
     363}
     364
     365.smartinput {
     366  border-top: 2px groove #999999;
     367  border-left: 2px groove #999999;
     368  border-bottom: 2px ridge #FFFFFF;
     369  border-right: 2px ridge #FFFFFF;
     370 
     371}
     372
     373.smartinput input {
     374  border: 0px;
     375}
     376
     377.smartoption {
     378  cursor: pointer;
     379}
     380
     381.smartimage {
     382  cursor: pointer;
     383  border-left: 1px solid #999999;
     384  background: #ffffff;
     385}
     386     
     387.smartenum .selected {
     388  background: #cccccc;     
     389}
Note: See TracChangeset for help on using the changeset viewer.