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