Revision: 2534
          https://sourceforge.net/p/mrbs/code/2534/
Author:   cimorrison
Date:     2012-10-28 09:12:46 +0000 (Sun, 28 Oct 2012)
Log Message:
-----------
Converted autocomplete fields to use HTML5 <datalist> elements if supported, 
otherwise to fall back to the jQuery UI Autocomplete widget.   Added ability 
for users to define $datalist_options in the config file to force a field to be 
treated as a datalist (only supported for entry.name at the moment).

Modified Paths:
--------------
    mrbs/trunk/web/edit_entry.php
    mrbs/trunk/web/functions.inc
    mrbs/trunk/web/js/general.js.php
    mrbs/trunk/web/js/report.js.php
    mrbs/trunk/web/js.inc
    mrbs/trunk/web/report.php
    mrbs/trunk/web/systemdefaults.inc.php

Modified: mrbs/trunk/web/edit_entry.php
===================================================================
--- mrbs/trunk/web/edit_entry.php       2012-10-27 14:19:33 UTC (rev 2533)
+++ mrbs/trunk/web/edit_entry.php       2012-10-28 09:12:46 UTC (rev 2534)
@@ -179,26 +179,30 @@
 
 function create_field_entry_name($disabled=FALSE)
 {
-  global $name, $select_options, $maxlength, $is_mandatory_field;
+  global $name, $select_options, $datalist_options, $maxlength, 
$is_mandatory_field;
   
   echo "<div id=\"div_name\">\n";
   
   $params = array('label'     => get_vocab("namebooker") . ":",
                   'name'      => 'name',
                   'value'     => $name,
-                  'disabled'  => $disabled);
+                  'disabled'  => $disabled,
+                  'mandatory' => TRUE);
                   
   if (!empty($select_options['entry.name']))
   {
     $params['options']   = $select_options['entry.name'];
-    $params['mandatory'] = $is_mandatory_field['entry.name'];
     generate_select($params);
   }
+  elseif (!empty($datalist_options['entry.name']))
+  {
+    $params['options']   = $datalist_options['entry.name'];
+    generate_datalist($params);
+  }
   else
   {
     // 'mandatory' is there to prevent null input (pattern doesn't seem to be 
triggered until
     // there is something there).
-    $params['mandatory'] = TRUE;
     $params['maxlength'] = $maxlength['entry.name'];
     $params['attributes'] = 'type="text" pattern="' . REGEX_TEXT_POS . '"';
     generate_input($params);

Modified: mrbs/trunk/web/functions.inc
===================================================================
--- mrbs/trunk/web/functions.inc        2012-10-27 14:19:33 UTC (rev 2533)
+++ mrbs/trunk/web/functions.inc        2012-10-28 09:12:46 UTC (rev 2534)
@@ -1006,6 +1006,126 @@
   echo $html;
 }
 
+
+// Generates a datalist element with an associated label
+//
+//   $params    an associative array holding the function parameters:
+//      MANDATORY
+//        'name'          The name of the element.
+//      OPTIONAL
+//        'id'            The id of the element.  Defaults to the same as the 
name.
+//        'label'         The text to be used for the field label.
+//        'options'       An array of options for the select element.   Can be 
a simple
+//                        array or an associative array with value => text 
members for
+//                        each <option> in the <select> element.   Default is 
an empty array.
+//        'force_assoc'   Boolean.  Forces the options array to be treated as 
an
+//                        associative array.  Default FALSE, ie it is treated 
as whatever
+//                        it looks like.  (This parameter is necessary because 
if you
+//                        index an array with strings that look like integers 
then PHP
+//                        casts the keys to integers and the array becomes a 
simple indexed array)
+//        'force_indexed' Boolean.  Forces the options array to be treated as 
a simple
+//                        indexed array, ie just the values are used and the 
keys are
+//                        ignored.   Default FALSE, ie it is treated as 
whatever it looks
+//                        like.
+//        'value'         The value of the input.  Default ''.
+//        'disabled'      Whether the field should be disabled.  Default FALSE
+//        'create_hidden' Boolean.  If TRUE hidden inputs are created if 
'disabled' is set
+//                        Default TRUE
+//        'mandatory'     Whether the field is a required field.  Default FALSE
+//        'attributes'    Can be used for passing other attributes.  Default 
NULL.
+//                        Can be either a simple string or an array of 
attributes
+//
+function generate_datalist($params)
+{
+  // some sanity checking on params
+  foreach (array('label', 'name', 'id', 'options', 'force_assoc', 
'force_indexed', 'value',
+                 'disabled', 'create_hidden', 'mandatory', 'attributes') as 
$key)
+  {
+    if (!isset($params[$key]))
+    {
+      switch ($key)
+      {
+        case 'name':
+          trigger_error('Missing mandatory parameters', E_USER_NOTICE);
+          break;
+        case 'id':
+          $params[$key] = $params['name'];
+          break;
+        case 'options':
+          $params[$key] = array();
+          break;
+        case 'value':
+          $params[$key] = '';
+          break;
+        case 'force_assoc':
+        case 'force_indexed':
+        case 'disabled':
+        case 'mandatory':
+          $params[$key] = FALSE;
+          break;
+        case 'create_hidden':
+          $params[$key] = TRUE;
+          break;
+        default:
+          break;
+      }
+    }
+  }
+  
+  if (isset($params['attributes']) && is_array($params['attributes']))
+  {
+    $params['attributes'] = implode(' ', $params['attributes']);
+  }
+  
+  // generate the HTML
+  // no HTML escaping for the label - it is trusted
+  $html = '';
+  if (isset($params['label']))
+  {
+    $html .= "<label for=\"" .$params['id'] . "\">" . $params['label'] . 
"</label>\n";
+  }
+  $html .= "<input id=\"" . $params['id'] . "\" name=\"" . $params['name'] . 
"\"";
+  $html .= " list=\"" . $params['id'] . "_options\"";
+  $html .= " value=\"" . $params['value'] . "\"";
+  $html .= ($params['disabled']) ? " disabled=\"disabled\"" : "";
+  $html .= ($params['mandatory']) ? " required aria-required=\"true\"" : "";
+  $html .= (isset($params['attributes'])) ? " " . $params['attributes'] : "";
+  $html .= " autocomplete=\"off\">\n";
+  $html .= "<datalist id=\"" . $params['id'] . "_options\">";
+  
+  // Put a <select> wrapper around the options so that browsers that don't
+  // support <datalist> will still have the options in their DOM and then
+  // the JavaScript polyfill can find them and do something with them
+  $html .= "<select style=\"display: none\">\n";
+
+  foreach ($params['options'] as $value => $text)
+  {
+    // We can cope with both associative and ordinary arrays
+    if (!$params['force_assoc'] && !is_assoc($params['options']))
+    {
+      $value = $text;
+    }
+    $html .= "<option";
+    if (!$params['force_indexed'])
+    {
+      $html .= " value=\"" . htmlspecialchars($value) . "\"";
+    }
+    $html .= ">" . htmlspecialchars($text) . "</option>\n";
+  }
+  $html .= "</select>\n";
+  $html .= "</datalist>\n";
+  
+  // and hidden inputs if the input is disabled
+  if ($params['disabled'] && $params['create_hidden'])
+  {
+      $html .= "<input type=\"hidden\" name=\"" . $params['name'] . "\" 
value=\"".
+               htmlspecialchars($params['value']) . "\">\n";
+  }
+  
+  echo $html;
+}
+
+
 // Generate a textarea with an associated label
 //
 //   $params    an associative array holding the function parameters:

Modified: mrbs/trunk/web/js/general.js.php
===================================================================
--- mrbs/trunk/web/js/general.js.php    2012-10-27 14:19:33 UTC (rev 2533)
+++ mrbs/trunk/web/js/general.js.php    2012-10-28 09:12:46 UTC (rev 2534)
@@ -12,6 +12,24 @@
   echo "'use strict';\n";
 }
 
+global $autocomplete_length_breaks;
+
+
+// Function to determine whether the browser supports the HTML5
+// <datalist> element.
+?>
+var supportsDatalist = function supportsDatalist() {
+    <?php
+    // The first two conditions work for most browsers.   The third condition 
is
+    // necessary for Safari, which, certainly for versions up to 6.0, the 
latest at
+    // the time of writing, return true for the first two conditions even 
though
+    // it doesn't support <datalist>.
+    ?>
+    return ('list' in document.createElement('input')) &&
+           ('options' in document.createElement('datalist')) &&
+           (window.HTMLDataListElement !== undefined);
+  };
+<?php
 // 
=================================================================================
 
 // Extend the init() function 
@@ -75,4 +93,78 @@
           }
         });
     });
+    
+   
+  <?php 
+  // Add jQuery UI Autocomplete functionality for those browsers that do not
+  // support the <datalist> element.  (We don't support autocomplete in IE6 and
+  // below because the browser doesn't render the autocomplete box properly - 
it
+  // gets hidden behind other elements.   Although there are fixes for this,
+  // it's not worth it ...)
+  ?> 
+  if (supportsDatalist() || lteIE6)
+  {
+    <?php
+    // If the browser does support the datalist we have to do a bit of tweaking
+    // if we are running Opera.  We normally have the autocomplete atribute set
+    // to off because in most browsers this stops the browser suggesting 
previous
+    // input and confines the list to our options.   However in Opera turning 
off
+    // autocomplete turns off our options as well, so we have to turn it back 
on.
+    ?>
+    if (navigator.userAgent.toLowerCase().indexOf('opera') >= 0)
+    {
+      $('datalist').prev().attr('autocomplete', 'on');
+    }
+  }
+  else
+  {
+    $('datalist').each(function() {
+        var datalist = $(this);
+        var options = [];
+        datalist.parent().find('option').each(function() {
+            var option = {};
+            option.label = $(this).text();
+            option.value = $(this).val();
+            options.push(option);
+          });
+        var minLength = 0;
+        <?php
+        // Work out a suitable value for the autocomplete minLength
+        // option, ie the number of characters that must be typed before
+        // a list of options appears.   We want to avoid presenting a huge 
+        // list of options.
+        if (isset($autocomplete_length_breaks) && 
is_array($autocomplete_length_breaks))
+        {
+          ?>
+          var breaks = [<?php echo implode(',', $autocomplete_length_breaks) 
?>];
+          var nOptions = options.length;
+          var i=0;
+          while ((i<breaks.length) && (nOptions >= breaks[i]))
+          {
+            i++;
+            minLength++;
+          }
+          <?php
+        }
+        ?>
+        var formInput = datalist.prev();
+        formInput.empty().autocomplete({
+            source: options,
+            minLength: minLength
+          });
+        <?php
+        // If the minLength is 0, then the autocomplete widget doesn't do
+        // quite what you might expect and you need to force it to display
+        // the available options when it receives focus
+        ?>
+        if (minLength === 0)
+        {
+          formInput.focus(function() {
+              $(this).autocomplete('search', '');
+            });
+        }
+      });
+  }
+
+
 };

Modified: mrbs/trunk/web/js/report.js.php
===================================================================
--- mrbs/trunk/web/js/report.js.php     2012-10-27 14:19:33 UTC (rev 2533)
+++ mrbs/trunk/web/js/report.js.php     2012-10-28 09:12:46 UTC (rev 2534)
@@ -12,70 +12,6 @@
   echo "'use strict';\n";
 }
 
-// Generates the JavaScript code to turn the input with id $id
-// into an autocomplete box, with options contained in the
-// array $options.  $options can be a simple or an associative array.
-function generate_autocomplete($id, $options)
-{
-  global $autocomplete_length_breaks;
-
-  $js = '';
-
-  // Turn the array into a simple, numerically indexed, array
-  $options = array_values($options);
-  $n_options = count($options);
-  if ($n_options > 0)
-  {
-    // Work out a suitable value for the autocomplete minLength
-    // option, ie the number of characters that must be typed before
-    // a list of options appears.   We want to avoid presenting a huge 
-    // list of options.
-    
-    $min_length = 0;
-    if (isset($autocomplete_length_breaks) && 
is_array($autocomplete_length_breaks))
-    {
-      foreach ($autocomplete_length_breaks as $break)
-      {
-        if ($n_options < $break)
-        {
-          break;
-        }
-        $min_length++;
-      }
-    }
-    // Start forming the array literal
-    // Escape the options
-    for ($i=0; $i < $n_options; $i++)
-    {
-      $options[$i] = escape_js($options[$i]);
-    }
-    $options_string = "'" . implode("','", $options) . "'";
-    // Build the JavaScript.   We don't support autocomplete in IE6 and below
-    // because the browser doesn't render the autocomplete box properly - it
-    // gets hidden behind other elements.   Although there are fixes for this,
-    // it's not worth it ...
-    $js .= "if (!lteIE6)\n";
-    $js .= "{\n";
-    $js .= "  $('#$id').autocomplete({\n";
-    $js .= "    source: [$options_string],\n";
-    $js .= "    minLength: $min_length\n";
-    $js .= "  })";
-    // If the minLength is 0, then the autocomplete widget doesn't do
-    // quite what you might expect and you need to force it to display
-    // the available options when it receives focus
-    if ($min_length == 0)
-    {
-      $js .= ".focus(function() {\n";
-      $js .= "    $(this).autocomplete('search', '');\n";
-      $js .= "  })";
-    }
-    $js .= "  ;\n";
-    $js .= "}\n";
-  }
-
-  return $js;
-}
-
 $user = getUserName();
 $is_admin = (authGetUserLevel($user) >= $max_level);
 
@@ -106,32 +42,6 @@
   
   
   <?php
-  // Make the area match input on the report page into an auto-complete input
-  $options = sql_query_array("SELECT area_name FROM $tbl_area ORDER BY 
area_name");
-  if ($options !== FALSE)
-  {
-    echo generate_autocomplete('areamatch', $options);
-  }
-
-  // Make the room match input on the report page into an auto-complete input
-  // (We need DISTINCT because it's possible to have two rooms of the same name
-  // in different areas)
-  $options = sql_query_array("SELECT DISTINCT room_name FROM $tbl_room ORDER 
BY room_name");
-  if ($options !== FALSE)
-  {
-    echo generate_autocomplete('roommatch', $options);
-  }
-    
-  // Make any custom fields for the entry table that have an array of options
-  // into auto-complete inputs
-  foreach ($select_options as $field => $options)
-  {
-    if (strpos($field, 'entry.') == 0)
-    {
-      echo generate_autocomplete('match_' . substr($field, strlen('entry.')), 
$options);
-    }
-  }
-  
   // We don't support iCal output for the Summary.   So if the Summary button 
is pressed
   // disable the iCal button and, if iCal output is checked, check another 
format.  If the
   // Report button is pressed then re-enable the iCal button.

Modified: mrbs/trunk/web/js.inc
===================================================================
--- mrbs/trunk/web/js.inc       2012-10-27 14:19:33 UTC (rev 2533)
+++ mrbs/trunk/web/js.inc       2012-10-28 09:12:46 UTC (rev 2534)
@@ -94,6 +94,14 @@
   }
   //]]>
 </script>
+
+<?php
+// All pages
+?>
+<script type="text/javascript" src="js/functions.js.php?<?php echo 
$standard_query_string ?>"></script>
+<script type="text/javascript" src="js/datepicker.js.php?<?php echo 
$standard_query_string ?>"></script>
+<script type="text/javascript" src="js/general.js.php?<?php echo 
$standard_query_string ?>"></script>
+
     
 <?php
 // dataTables initialisation
@@ -162,14 +170,8 @@
   <script type="text/javascript" src="js/cell_click.js.php?<?php echo 
$standard_query_string ?>"></script>
   <?php
 }
+?>  
     
-    
-// All pages
-?>
-<script type="text/javascript" src="js/functions.js.php?<?php echo 
$standard_query_string ?>"></script>
-<script type="text/javascript" src="js/datepicker.js.php?<?php echo 
$standard_query_string ?>"></script>
-<script type="text/javascript" src="js/general.js.php?<?php echo 
$standard_query_string ?>"></script>
-    
 <script type="text/javascript">
 
 //<![CDATA[

Modified: mrbs/trunk/web/report.php
===================================================================
--- mrbs/trunk/web/report.php   2012-10-27 14:19:33 UTC (rev 2533)
+++ mrbs/trunk/web/report.php   2012-10-28 09:12:46 UTC (rev 2534)
@@ -6,9 +6,9 @@
 
 function generate_search_criteria(&$vars)
 {
-  global $booking_types;
+  global $booking_types, $select_options;
   global $private_somewhere, $approval_somewhere, $confirmation_somewhere;
-  global $user_level, $tbl_entry;
+  global $user_level, $tbl_entry, $tbl_area, $tbl_room;
   global $field_natures, $field_lengths;
   global $report_search_field_order;
   
@@ -35,22 +35,40 @@
         break;
       
         
-      case 'areamatch':  
+      case 'areamatch':
+        $options = sql_query_array("SELECT area_name FROM $tbl_area ORDER BY 
area_name");
+        if ($options === FALSE)
+        {
+          trigger_error(sql_error(), E_USER_WARNING);
+          fatal_error(FALSE, get_vocab("fatal_db_error"));
+        }
         echo "<div id=\"div_areamatch\">\n";
-        $params = array('label' => get_vocab("match_area") . ':',
-                        'name'  => 'areamatch',
-                        'value' => $vars['areamatch']);
-        generate_input($params);
+        $params = array('label'         => get_vocab("match_area") . ':',
+                        'name'          => 'areamatch',
+                        'options'       => $options,
+                        'force_indexed' => TRUE,
+                        'value'         => $vars['areamatch']);
+        generate_datalist($params);
         echo "</div>\n";
         break;
         
         
       case 'roommatch':
+        // (We need DISTINCT because it's possible to have two rooms of the 
same name
+        // in different areas)
+        $options = sql_query_array("SELECT DISTINCT room_name FROM $tbl_room 
ORDER BY room_name");
+        if ($options === FALSE)
+        {
+          trigger_error(sql_error(), E_USER_WARNING);
+          fatal_error(FALSE, get_vocab("fatal_db_error"));
+        }
         echo "<div id=\"div_roommatch\">\n";
-        $params = array('label' => get_vocab("match_room") . ':',
-                        'name'  => 'roommatch',
-                        'value' => $vars['roommatch']);
-        generate_input($params);
+        $params = array('label'         => get_vocab("match_room") . ':',
+                        'name'          => 'roommatch',
+                        'options'       => $options,
+                        'force_indexed' => TRUE,
+                        'value'         => $vars['roommatch']);
+        generate_datalist($params);
         echo "</div>\n";
         break;
       
@@ -186,7 +204,18 @@
         // Otherwise output a text input
         else
         {
-          generate_input($params);
+          if (isset($select_options["entry.$key"]) && 
!empty($select_options["entry.$key"]))
+          {
+            $params['options'] = $select_options["entry.$key"];
+            // We force the values to be used and not the keys.   We will 
convert
+            // back to values when we construct the SQL query.
+            $params['force_indexed'] = TRUE;
+            generate_datalist($params);
+          }
+          else
+          {
+            generate_input($params);
+          }
         }
         echo "</div>\n";
         break;

Modified: mrbs/trunk/web/systemdefaults.inc.php
===================================================================
--- mrbs/trunk/web/systemdefaults.inc.php       2012-10-27 14:19:33 UTC (rev 
2533)
+++ mrbs/trunk/web/systemdefaults.inc.php       2012-10-28 09:12:46 UTC (rev 
2534)
@@ -425,6 +425,10 @@
 // receives focus.   If the number of options is less than 250 then they will 
be displayed
 // when 1 character is input and so on.    The array can be as long as you 
like.   If it
 // is empty then the options are displayed when 0 characters are input.
+
+// [Note: this variable is only applicable to older browsers that do not 
support the
+// <datalist> element and instead fall back to a JavaScript emulation.   
Browsers that
+// support <datalist> present the options in a scrollable select box]
 $autocomplete_length_breaks = array(25, 250, 2500);
 
 
@@ -513,15 +517,26 @@
 // to 'Coffee, Tea and Biscuits', without having to alter the database.   It 
can also
 // be useful if the database table is being shared with another application.
 // MRBS will auto-detect whether the array is associative.
+
+
+$datalist_options = array();
+// Instead of restricting the user to a fixed set of options using 
$select_options,
+// you can provide a list of options which will be used as suggestions, but the
+// user will also be able to type in their own input.   (MRBS presents these 
using
+// an HTML5 <datalist> element in browsers that support it, falling back to a
+// JavaScript emulation in browsers that don't - except for IE6 and below where
+// an ordinary text input field is presented).
 //
-// If you want to make the select field a mandatory field (see below) then 
include
-// an empty string as one of the values, eg
+// As with $select_options, the array can be either a simple indexed array or 
an
+// associative array, eg array('AL' => 'Alabama', 'AK' => 'Alaska', etc.).   
However
+// some users might find an associative array confusing as the key is entered 
in the input
+// field when the corresponding value is selected.
 //
-//$select_options['entry.catering'] = array(''  => 'Please select one option',
-//                                          'c' => 'Coffee', 
-//                                          's' => 'Sandwiches',
-//                                          'h' => 'Hot Lunch');
+// At the moment $select_options is only supported as follows:
+//     - Entry table: name field
+//     - Users table: custom fields
 
+
 $is_mandatory_field = array();
 // You can define custom entry fields to be mandatory by setting
 // items in the array $is_mandatory_field.   (Note that making a checkbox
------------------------------------------------------------------------------
WINDOWS 8 is here. 
Millions of people.  Your app in 30 days.
Visit The Windows 8 Center at Sourceforge for all your go to resources.
http://windows8center.sourceforge.net/
join-generation-app-and-make-money-coding-fast/
_______________________________________________
Mrbs-commits mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/mrbs-commits

Reply via email to