src.dualinventive.com/mtinfo/dist/webroot/rc-4.05/html/javascript/form_elements.js

1337 lines
39 KiB
JavaScript

/** \file html/javascript/form_elements.js
* \brief DI webinterface JavaScript for the form elements
* \author Jack Weeland, Core|Vision
* \version $Revision: 26458 $
* \date $Date: 2016-03-21 14:36:35 +0100 (Mon, 21 Mar 2016) $
*
* JavaScript functions for the form elements
*
*/
/**
* Globals
*/
var shiftKeyDown = false;
var ctrlKeyDown = false;
// Safari needs special handling (seemingly all versions, but show Safari fix
// their drag&drop bug, version testing must be added below)
// Erm.. Google Chrome also includes a Safari version number
var safariDragDropBug = (navigator.userAgent.indexOf('Safari/') != -1) && (navigator.userAgent.indexOf('Chrome/') == -1);
// Mobile browser with touch events?
var isMobile = (navigator.userAgent.indexOf('Mobile') != -1);
/**
* Compatibility with older versions of IE
*/
function addEventHandler(element, event, handler)
{
if( element.addEventListener )
element.addEventListener(event, handler);
else if( element.attachEvent )
element.attachEvent('on' + event, handler);
}
function removeEventHandler(element, event, handler)
{
if( element.removeEventListener )
element.removeEventListener(event, handler);
else if( element.detachEvent )
element.detachEvent(event, handler);
}
/**
* Foldable div (and similar) via PHP
*/
function Expand(name)
{
var state = getElement(name);
if( state.value == 1 ) {
state.value = 0;
}
else {
state.value = 1;
}
onSubmit('expand','',1);
}
/**
* Selection box (support for 'print_selection()')
*/
// The selection list is an UL element and UL elements do not receive
// the input focus, so we need to emulate that using the global event
// handlers at the bottom of this file
var selection_box_focus;
var selection_box_open;
var selection_box_state;
// JavaScripts breaks down after a "recall", where the inner HTML of
// the content div is replaced and the JS objects forthe selection
// boxes and left/right boxes are recreated using the same name
// (the events don't work after that)
var selection_boxes = [];
var left_right_boxes = [];
// drag&drop selection and move for left/right boxes
var selection_drag = null, selection_move = null;
/**
* Selection box (support for 'print_selection()')
*/
function SelectionBox(name, type, icon_collapsed, icon_expanded)
{
this.name = name;
this.type = type;
this.multiSelect = false;
this.inLeftRightBox = false;
this.iconCollapsed = icon_collapsed;
this.iconExpanded = icon_expanded;
this.selectedItem = -1;
selection_boxes[this.name] = this;
this.addEventListener = function(e, handler)
{
addEventHandler(getElement(this.name + ":select"), e, handler);
}
// Copy the selected value (in the list) to the edit box
this.CopyValue = function()
{
var form_data = getElement(this.name);
form_data.value = getElement(this.name + ':editbox').value;
}
this.isDropDown = function()
{
return (this.type == 'dropdown' || this.type == 'dropdownlist');
}
this.ShowDropDown = function()
{
if( selection_box_open && selection_box_open.name != this.name ) {
selection_box_open.HideDropDown();
}
getElement(this.name + ':dropdownlist').style.display = 'block';
getElement(this.name + ':button').src = this.iconExpanded;
selection_box_open = this;
}
this.HideDropDown = function()
{
getElement(this.name + ':dropdownlist').style.display = 'none';
getElement(this.name + ':button').src = this.iconCollapsed;
selection_box_open = null;
}
this.OnClickDropDown = function(e)
{
if( getElement(this.name + ':dropdownlist').style.display == 'block' ) {
this.HideDropDown();
}
else {
this.ShowDropDown();
this.ShowSelected();
}
}
this.OnClickEditBox = function()
{
getElement(this.name + ':button').onclick();
}
// Show which list is focussed
this.FocusList = function(set_focus)
{
if( !this.isDropDown() ) {
var list = getElement(this.name + ':select');
if( list ) changeClass(list, "focus", set_focus);
}
}
// Get index of item at Y position 'p'
this.ItemAt = function(p)
{
var list = getElement(this.name + ':select');
var r_list = list.getBoundingClientRect();
for( var i = 0, index = 0; i < list.childNodes.length; i++ ) {
var list_item = list.childNodes[i];
if( list_item.tagName ) {
if( list_item.tagName.toLowerCase() == 'li' && list_item.id ) {
var r = list_item.getBoundingClientRect();
if( p >= (r.top - r_list.top) && p <= (r.bottom - r_list.top) )
return index;
index++;
}
else if( list_item.tagName.toLowerCase() == 'a' ) {
index++;
}
}
}
return -1;
}
// Get first selected item
this.GetFirstSelected = function()
{
var list = getElement(this.name + ':select');
for( var i = 0, index = 0; i < list.childNodes.length; i++ ) {
var list_item = list.childNodes[i];
if( list_item.tagName ) {
if( list_item.tagName.toLowerCase() == 'li' && list_item.id ) {
if( list_item.className == 'selected' )
return index;
index++;
}
else if( list_item.tagName.toLowerCase() == 'a' ) {
index++;
}
}
}
return -1;
}
// Get a listbox item by index
this.GetItem = function(item_index)
{
var list = getElement(this.name + ':select');
for( var i = 0, index = 0; i < list.childNodes.length; i++ ) {
var list_item = list.childNodes[i];
if( list_item.tagName && ((list_item.tagName.toLowerCase() == 'li' && list_item.id) || list_item.tagName.toLowerCase() == 'a') ) {
if( index == item_index )
return list_item;
index++;
}
}
return -1;
}
// Get the index for a listbox item
this.GetItemIndex = function(item)
{
var list = getElement(this.name + ':select');
for( var i = 0, index = 0; i < list.childNodes.length; i++ ) {
var list_item = list.childNodes[i];
if( list_item.tagName && ((list_item.tagName.toLowerCase() == 'li' && list_item.id) || list_item.tagName.toLowerCase() == 'a') ) {
if( list_item.id == item.id )
return index;
index++;
}
}
return -1;
}
// Get the total number of (visible) items
this.GetItemCount = function(visible_only)
{
var list = getElement(this.name + ':select');
for( var i = 0, count = 0; i < list.childNodes.length; i++ ) {
var list_item = list.childNodes[i];
if( list_item.tagName && ((list_item.tagName.toLowerCase() == 'li' && list_item.id) || list_item.tagName.toLowerCase() == 'a') ) {
// update selected items string
if( !visible_only || list_item.style.display != 'none' ) {
count++;
}
}
}
return count;
}
// Get all (visible) items
this.GetItems = function(visible_only)
{
var list = getElement(this.name + ':select');
var items = [];
for( var i = 0, index = 0; i < list.childNodes.length; i++ ) {
var list_item = list.childNodes[i];
// NB: fillers don't have an 'id', real items do
if( list_item.tagName && ((list_item.tagName.toLowerCase() == 'li' && list_item.id) || list_item.tagName.toLowerCase() == 'a') ) {
// update selected items string
if( (!visible_only || list_item.style.display != 'none') && list_item.className != 'filler' ) {
items.push(index);
}
index++;
}
}
return items;
}
// Get the selected items
this.GetSelectedItems = function()
{
var list = getElement(this.name + ':select');
var selected_items = [];
for( var i = 0, index = 0; i < list.childNodes.length; i++ ) {
var list_item = list.childNodes[i];
if( list_item.tagName && ((list_item.tagName.toLowerCase() == 'li' && list_item.id) || list_item.tagName.toLowerCase() == 'a') ) {
// update selected items string
if( list_item.className == 'selected' && list_item.style.display != 'none' ) {
selected_items.push(index);
}
index++;
}
}
return selected_items;
}
// Get the value for an item
this.GetItemValue = function(list_item)
{
if( typeof(list_item) == 'number' ) {
list_item = this.GetItem(list_item);
}
if( !list_item ) return false;
var prefix = this.name.length + 6; // format: 'name:item:value'
return list_item.id.substr(prefix);
}
// Get the display string for an item
this.GetItemString = function(list_item)
{
if( typeof(list_item) == 'number' ) {
list_item = this.GetItem(list_item);
}
if( !list_item ) return false;
var s = list_item.innerHTML;
if( s == "&nbsp;" ) {
// special marker for an empty <li>; the browser won't show empty <li>'s,
// but copying it to the edit box will show the text "&nbsp;" instead
// of an empty string
return "";
}
else return s;
}
// Select a item(s) that match 'search_str'
this.SelectValue = function(search_str)
{
var list = getElement(this.name + ':select');
var done = false;
for( var i = 0, index = 0; i < list.childNodes.length; i++ ) {
var list_item = list.childNodes[i];
if( list_item.tagName && ((list_item.tagName.toLowerCase() == 'li' && list_item.id) || list_item.tagName.toLowerCase() == 'a') ) {
// NB: to fix; should look at the inner 'li' of an 'a' element
if( !list_item.style || list_item.style.display != 'none' ) {
if( !done && this.GetItemString(list_item).substr(0, search_str.length) == search_str ) {
this.SelectItem(list_item, true);
// select one (this) or multiple items
done = !this.multiSelect;
}
else {
// no match; unselect
this.SelectItem(list_item, false);
}
}
index++;
}
}
//. not found
return false;
}
// Select a single item
this.SelectItem = function(list_item, select_not_deselect)
{
if( typeof(list_item) == 'number' ) {
list_item = this.GetItem(list_item);
}
if( !list_item ) return false;
if( (!list_item.style || list_item.style.display != 'none') && list_item.className != 'disabled' ) {
if( select_not_deselect )
list_item.className = 'selected';
else
list_item.className = null;
}
return true;
}
this.isSelected = function(list_item)
{
if( typeof(list_item) == 'number' ) {
list_item = this.GetItem(list_item);
}
if( !list_item ) return false;
return list_item.className == 'selected' && (!list_item.style || list_item.style.display != 'none');
}
this.ShowItem = function(list_item, show)
{
if( typeof(list_item) == 'number' ) {
list_item = this.GetItem(list_item);
}
if( !list_item ) return false;
list_item.style.display = (show ? 'block' : 'none');
return true;
}
this.isVisible = function(list_item)
{
if( typeof(list_item) == 'number' ) {
list_item = this.GetItem(list_item);
}
if( !list_item ) return false;
return list_item.style.display != 'none';
}
this.Select = function(selected_item, update_only)
{
var form_data = getElement(this.name);
var list = getElement(this.name + ':select');
// select the selected item or augment the current selection for a multi-select box
var start, end;
// grow the selection up or down?
if( !this.multiSelect || !shiftKeyDown ) {
start = end = selected_item;
}
else if( selected_item < this.selectedItem ) {
start = selected_item;
end = this.selectedItem;
}
else {
start = this.selectedItem;
end = selected_item;
}
// select the items in the listbox; create a list (string) of the selected
// items on the fly
var selected_items = '', sep = '';
for( var i = 0, index = 0; i < list.childNodes.length; i++ ) {
var list_item = list.childNodes[i];
if( list_item.tagName ) {
if( list_item.tagName.toLowerCase() == 'li' && list_item.id ) {
// hidden items can't be selected
if( list_item.style.display != 'none' ) {
if( index >= start && index <= end ) {
if( this.multiSelect && ctrlKeyDown && !shiftKeyDown && list_item.className == 'selected' )
list_item.className = null;
else if( list_item.className != 'disabled' )
list_item.className = 'selected';
}
else if( (!this.multiSelect || !ctrlKeyDown) && list_item.className != 'disabled' ) {
list_item.className = null;
}
// update selected items string
if( list_item.className == 'selected' ) {
selected_items += sep + this.GetItemValue(list_item);
sep = ',';
}
}
else if( list_item.className != 'disabled' ) {
list_item.className = null;
}
index++;
}
else if( list_item.tagName.toLowerCase() == 'a' ) {
// direct jump, but we must count it
index++;
}
}
}
// copy the string for the selected value to the edit box
// (a dropdown list should never be a multi-select box and we can't bother to check)
if( !update_only ) {
if( this.isDropDown() ) {
var editbox = getElement(this.name + ':editbox');
if( editbox ) {
editbox.value = this.GetItemString(selected_item);
}
}
// copy the selected value to the hidden control (the value of the emulated 'select' element)
if( this.type == 'dropdown' ) {
// combobox; copy the selected string and not the item values
form_data.value = this.GetItemString(selected_item);
}
else {
form_data.value = selected_items;
}
// save the selected item
this.selectedItem = selected_item;
if( this.isDropDown() ) this.HideDropDown();
}
}
this.SelectFrom = function(start, end)
{
var form_data = getElement(this.name);
var list = getElement(this.name + ':select');
// swap start/end?
if( !this.multiSelect ) {
// not a multi-select box; only select the first item
end = start;
}
else if( end != -1 && end < start ) {
var p = start;
start = end;
end = p;
}
// select all items
var selected_items = '', sep = '';
for( var i = 0, index = 0; i < list.childNodes.length; i++ ) {
var list_item = list.childNodes[i];
if( list_item.tagName ) {
if( list_item.tagName.toLowerCase() == 'li' && list_item.id ) {
// hidden items can't be selected
if( (!list_item.style || list_item.style.display != 'none') && index >= start && (index <= end || end == -1) ) {
list_item.className = 'selected';
// update selected items string
selected_items += sep + this.GetItemValue(list_item);
sep = ',';
}
else if( list_item.className != 'disabled' && !ctrlKeyDown ) {
list_item.className = null;
}
index++;
}
else if( list_item.tagName.toLowerCase() == 'a' ) {
// direct jump, but we must count it
index++;
}
}
}
}
this.ShowSelected = function()
{
var list = getElement(this.name + ':select');
if(
list &&
(!this.isDropDown() || getElement(this.name + ':dropdownlist').style.display != 'none')
) {
for( var i = 0; i < list.childNodes.length; i++ ) {
var list_item = list.childNodes[i];
if( list_item.className == 'selected' ) {
// scroll to the item, minding the offset from the list ('ul' element)
// to its parent (the 'div' element)
var offset = list_item.offsetTop;
offset -= list.offsetTop;
offset -= (list.clientHeight / 2);
if( offset < list.parentNode.scrollTop || offset > list.parentNode.scrollTop + list.clientHeight ) {
list.parentNode.scrollTop = offset;
}
}
}
}
}
this.Initialize = function()
{
this.ShowSelected();
if( selection_box_focus && selection_box_focus.name == this.name ) {
this.FocusList(true);
}
}
}
/**
* Left/right selection box (support for 'print_left_right_control()')
*/
function LeftRightBox(name, options, left_box, right_box)
{
this.name = name;
this.options = options.split(',');
this.leftBox = left_box;
this.rightBox = right_box;
left_right_boxes[this.name] = this;
// indicate that the selection boxes are part of a left/right box
this.leftBox.inLeftRightBox = true;
this.rightBox.inLeftRightBox = true;
this.OnClickLtr = function(e) {
if( this.options && this.options.indexOf('all') != -1 ) {
this.DeselectAll();
}
else {
this.DeselectItems();
}
return false;
}
this.OnClickRtl = function(e) {
if( this.options && this.options.indexOf('all') != -1 ) {
this.SelectAll();
}
else {
this.SelectItems();
}
return false;
}
// Filter items in the right listbox
this.UpdateFilter = function()
{
var filter_str = getElement(name + ":filter").value;
var search_regex = new RegExp(filter_str, "i");
for( var i = 0; i < this.rightBox.GetItemCount(); i++ ) {
this.rightBox.ShowItem(i, this.rightBox.GetItemString(i).search(search_regex) != -1 && !this.leftBox.isVisible(i));
}
}
// Update the selection in the hidden control
this.UpdateSelection = function(event)
{
// get the visible items
var selections = this.leftBox.GetItems(true);
// and build a comma separated string from their id's
var selection_str = '', sep = '';
for( var i = 0; i < selections.length; i++ ) {
selection_str += sep + this.leftBox.GetItemValue(selections[i]);
sep = ',';
}
getElement(name + '_left_ids').value = selection_str;
// get the "invisible" items
var selections = this.rightBox.GetItems(true);
// and build a comma separated string from their id's
var selection_str = '', sep = '';
for( var i = 0; i < selections.length; i++ ) {
selection_str += sep + this.rightBox.GetItemValue(selections[i]);
sep = ',';
}
getElement(name + '_right_ids').value = selection_str;
// remove selections from the listboxes
this.leftBox.Select(-1);
this.rightBox.Select(-1);
// only one of the events is fired
if( event == 'ltr' && this.onltr ) eval(this.onltr);
else if( event == 'rtl' && this.onrtl ) eval(this.onrtl);
else if( this.onchange && event ) eval(this.onchange);
}
// Move items from the right to the left listbox (i.e., select them)
this.SelectItems = function() {
var selections = this.rightBox.GetSelectedItems();
for( var i = 0; i < selections.length; i++ ) {
// unselect item
this.rightBox.SelectItem(selections[i], false);
// "move" it from right to left
this.rightBox.ShowItem(selections[i], false);
this.leftBox.ShowItem(selections[i], true);
}
// update the hidden input that contains the "left" selections
this.UpdateSelection('rtl');
}
// Move items from the left to the right listbox (i.e. deselect them)
this.DeselectItems = function() {
var selections = this.leftBox.GetSelectedItems();
for( var i = 0; i < selections.length; i++ ) {
// unselect item
this.leftBox.SelectItem(selections[i], false);
// "move" it from left to right
this.leftBox.ShowItem(selections[i], false);
this.rightBox.ShowItem(selections[i], true);
}
// update the hidden input that contains the "left" selections
this.UpdateSelection('ltr');
}
this.SelectAll = function() {
for( var i = 0; i < this.rightBox.GetItemCount(); i++ ) {
this.leftBox.SelectItem(i, true);
this.rightBox.SelectItem(i, false);
}
// update the hidden input that contains the "left" selections
this.UpdateSelection('rtl');
}
this.DeselectAll = function() {
for( var i = 0; i < this.rightBox.GetItemCount(); i++ ) {
this.leftBox.SelectItem(i, false);
this.rightBox.SelectItem(i, true);
}
// update the hidden input that contains the "left" selections
this.UpdateSelection('ltr');
}
}
/**
* Tree view (support for 'print_treeview()')
*/
function TreeView(name, icon_collapsed, icon_expanded, icon_empty)
{
this.name = name;
this.iconCollapsed = icon_collapsed;
this.iconExpanded = icon_expanded;
this.iconEmtpy = icon_empty;
this.HideSubtree = function(id)
{
var list_element = getElement(this.name + "[" + id + "][list]");
var image_element = getElement(this.name + "[" + id + "][img]");
var state_element = getElement(this.name + "[" + id + "]");
image_element.src = this.iconCollapsed;
list_element.style.display = 'none';
state_element.value = 0;
}
this.ShowSubtree = function(id)
{
var list_element = getElement(this.name + "[" + id + "][list]");
var image_element = getElement(this.name + "[" + id + "][img]");
var state_element = getElement(this.name + "[" + id + "]");
image_element.src = this.iconExpanded;
list_element.style.display = 'block';
state_element.value = 1;
}
this.OnClickExpand = function(id)
{
var list_element = getElement(this.name + "[" + id + "][list]");
var state = list_element.style.display;
// the state is "nothing" by default
if( !state || state == 'block' ) {
this.HideSubtree(id);
}
else {
this.ShowSubtree(id);
}
}
}
/**
* Global event handlers
*/
// Global click events
addEventHandler(document, 'click', function(e) {
if( !e ) e = window.event;
var target = (e.target ? e.target : e.srcElement);
var target_name = (target ? (target.name ? target.name : target.id) : false);
// click outside a 'SelectionBox'? hide its dropdown list
if( selection_box_open ) {
if(
!target_name ||
target_name.substr(0, selection_box_open.name.length) != selection_box_open.name
) {
selection_box_open.HideDropDown();
}
}
// selection box clicked? pointless on a mobile device...
if( !isMobile ) {
if( selection_box_focus ) {
// unhighlight the previously focused box
selection_box_focus.FocusList(false);
}
if( (selection_box_focus = selection_boxes[target_name.substr(0, target_name.indexOf(':'))]) ) {
// show focus for the newly selected box
selection_box_focus.FocusList(true);
}
}
return false;
});
// Monitor the state of the shift and ctrl keys
addEventHandler(document, 'keydown', function(e) {
if( !e ) e = window.event;
shiftKeyDown = e.shiftKey;
ctrlKeyDown = e.ctrlKey;
return true;
});
addEventHandler(document, 'keyup', function(e) {
if( !e ) e = window.event;
shiftKeyDown = e.shiftKey;
ctrlKeyDown = e.ctrlKey;
var keyCode = (e.which ? e.which : (e.key ? e.key.charCodeAt(0) : -1));
if( selection_box_focus ) {
// the selection list is a UL element and those don't receive keyboard focus,
// so the hocus pocus below (and in the global 'click' event handler) is
// needed emulate the keyboard focus
// Ctrl+A handler
if( ctrlKeyDown && keyCode == 65 /* 'a' */ ) {
selection_box_focus.SelectFrom(0, -1);
e.stopPropagation();
e.preventDefault();
return false;
}
}
return true;
});
/**
* Event handlers and helpers for drag and drop functionality
*/
// Highlight the drop target; primarily used for Safari (which has a bug) and
// mobile browsers, but it gives a nice effect in other browsers as well
function dragShowAction(target, css_action, highlight)
{
var target_name = (target ? (target.name ? target.name : target.id) : false);
if( target_name ) {
if( (p = target_name.indexOf(':')) != -1 ) target_name = target_name.substr(0, p);
var list = getElement(target_name + ':select');
if( list ) changeClass(list, css_action, highlight);
}
}
// De-highlight the drop source
function dragHighlight(target, highlight)
{
return dragShowAction(target, "dragdrop_target", highlight);
}
// De-highlight the drop source
function dragShowDrag(target, highlight)
{
return dragShowAction(target, "dragdrop_source", highlight);
}
// Start a drag and drop operation
// Returns the selection box where the drag and drop starts on success,
// true if the event is handled or false if not
function dragStart(source)
{
var source_name = (source ? (source.name ? source.name : source.id) : false);
if( source_name ) {
var selection_box = selection_boxes[source_name.substr(0, source_name.indexOf(':'))];;
if( selection_box && selection_box.multiSelect ) {
// initialize the drag s/m
if( selection_box.isSelected(source) && selection_box.inLeftRightBox ) {
// true drag and drop between the left and right boxes
// in Google Chrome, the drag&drop is broken (the e.dataTransfer
// is invalid after the "dragstart")
selection_move = { source: selection_box, started: false };
dragShowDrag(selection_move.source, true);
return selection_box;
}
else {
selection_drag = { selection_box: selection_box, list_item: source, start: selection_box.GetItemIndex(source), started: false };
// select this item as well
selection_drag.selection_box.Select(selection_drag.start, true);
// let the "default behaviour" proceed; this "bug" is what actually makes
// the selection-by-dragging work :-)
return true;
}
}
}
// not handled
return false;
}
function dragDrop(source, target)
{
var source_name = source.name;
var target_name = (target ? (target.name ? target.name : target.id) : false);
if( (p = target_name.indexOf(':')) != -1 ) target_name = target_name.substr(0, p);
var t_offset, s_offset;
// we only allow drag&drop for the left/right controls and the user can only drag a
// selection from the left to right box or v.v. of the same left/right control
if(
(target_name.slice((t_offset = -5)) == '_left' || target_name.slice((t_offset = -6)) == '_right') &&
(source_name.slice((s_offset = -5)) == '_left' || source_name.slice((s_offset = -6)) == '_right') &&
(target_name.slice(0, t_offset) == source_name.slice(0, s_offset)) &&
t_offset != s_offset // left->right or right->left
) {
dragHighlight(target, false);
dragShowDrag(source, false);
var left_right_box = left_right_boxes[target_name.slice(0, t_offset)];
if( left_right_box ) {
if( t_offset == -5 ) {
//right to left
left_right_box.OnClickRtl(null);
}
else {
// left to right
left_right_box.OnClickLtr(null);
}
return true;
}
}
// not handled
return false;
}
function dragCancel(source)
{
dragShowDrag(source, false);
}
function dragTest(source, target)
{
var source_name = source.name;
var target_name = (target ? (target.name ? target.name : target.id) : false);
if( (p = target_name.indexOf(':')) != -1 ) target_name = target_name.substr(0, p);
var t_offset, s_offset;
if( selection_move && selection_move.target && selection_move.target.name != target_name ) {
dragHighlight(selection_move.target, false);
}
// we only allow drag&drop for the left/right controls and the user can only drag a
// selection from the left to right box or v.v. of the same left/right control
if(
(target_name.slice((t_offset = -5)) == '_left' || target_name.slice((t_offset = -6)) == '_right') &&
(source_name.slice((s_offset = -5)) == '_left' || source_name.slice((s_offset = -6)) == '_right') &&
(target_name.slice(0, t_offset) == source_name.slice(0, s_offset)) &&
t_offset != s_offset // left->right or right->left
) {
dragHighlight(target, true);
// fix for Safari (see the 'dragend' handler below); save off the drop target
selection_move.target = { name: target_name };
return true;
}
else {
dragHighlight(selection_move.target, false);
selection_move.target = null;
}
// invalid target
return false;
}
// Scroll the selection list, when needed, when the user is selecting
// list items by dragging
function dragScroll(selection_box, mouse_x, mouse_y)
{
var list = getElement(selection_box.name + ':select');
var r = list.parentNode.getBoundingClientRect();
var index;
if( mouse_y < r.top ) {
var pos = list.parentNode.scrollTop - (1 + ((r.top - mouse_y) / 8));
if( pos < 0 ) list.parentNode.scrollTop = 0;
else list.parentNode.scrollTop = pos;
index = selection_box.ItemAt(list.parentNode.scrollTop);
}
else if( mouse_y > r.bottom ) {
var pos = list.parentNode.scrollTop + (1 + ((mouse_y - r.bottom) / 8));
var max_pos = list.parentNode.scrollTop + r.height;
if( pos > max_pos ) list.parentNode.scrollTop = max_pos;
else list.parentNode.scrollTop = pos;
index = selection_box.ItemAt(list.parentNode.scrollTop + r.height);
}
if( index >= 0 ) selection_box.SelectFrom(selection_drag.start, index);
}
addEventHandler(document, 'mouseup', function(e) {
// selection by dragging active? if so, clear the "select by drag" state
if( selection_drag ) {
selection_drag = null;
}
return true;
});
addEventHandler(document, "mouseover", function(e) {
// selection by dragging active?
if( selection_drag ) {
if( !e ) e = window.event;
var target = (e.target ? e.target : e.srcElement);
// select all items from the "start" to this one
var index = selection_drag.selection_box.GetItemIndex(target);
if( index != -1 ) selection_drag.selection_box.SelectFrom(selection_drag.start, index);
}
return true;
});
addEventHandler(document, 'mousemove', function(e) {
if( selection_drag ) {
dragScroll(selection_drag.selection_box, e.clientX, e.clientY);
}
return true;
});
addEventHandler(document, 'dragstart', function(e) {
if( !e ) e = window.event;
var target = (e.target ? e.target : e.srcElement);
var drag_started = dragStart(target);
if( drag_started ) {
if( typeof drag_started == 'object' ) {
// normal HTML5 drag-and-drop; default behaviour wanted
e.dataTransfer.setData('Text', drag_started.name);
return true;
}
else {
// select-by-drag; do _not_ set 'e.dataTransfer' and prevent the default
// behaviour of HTML5 drag-and-drop
e.preventDefault();
return false;
}
}
// old behavour: return 'true' when the default behaviour must proceed
return !drag_started;
});
addEventHandler(document, 'dragover', function(e) {
if( !e ) e = window.event;
var target = (e.target ? e.target : e.srcElement);
var source = { name: e.dataTransfer.getData('Text') };
// fix for Google Chrome (the e.dataTransfer is lost after 'dragstrart')
if( !source.name && selection_move && selection_move.source ) source = selection_move.source;
var drag_over = dragTest(source, target);
if( drag_over ) {
// funnily enough, we must _prevent_ the drag-and-drop default behaviour
// to show the "you can drop here" cursor
e.preventDefault();
}
// old behavour: return 'true' when the default behaviour must proceed
return !drag_over;
});
addEventHandler(document, 'drop', function(e) {
if( !e ) e = window.event;
var target = (e.target ? e.target : e.srcElement);
var source = { name: e.dataTransfer.getData('Text') };
// fix for Google Chrome (the e.dataTransfer is lost after 'dragstrart')
if( !source.name && selection_move && selection_move.source ) source = selection_move.source;
var drag_dropped = dragDrop(source, target);
if( drag_dropped ) e.preventDefault();
// old behavour: return 'true' when the default behaviour must proceed
return !drag_dropped;
});
addEventHandler(document, 'dragend', function(e) {
if( !e ) e = window.event;
var source = { name: e.dataTransfer.getData('Text') };
// fix for Google Chrome (the e.dataTransfer is lost after 'dragstrart')
if( !source.name && selection_move && selection_move.source ) source = selection_move.source;
// bug in Safari: it doesn't receive the 'ondrop' event
// however, it does receive the 'ondragend' event, _but_ the "target" of this event
// is actually the source, so we need to remember the actual target in the 'ondragover'
// event handler
if( safariDragDropBug && selection_move && selection_move.target ) {
dragDrop(source, selection_move.target);
dragHighlight(selection_move.target, false);
}
else if( source ) {
dragCancel(source);
}
// clear "move" state
selection_move = null;
return true;
});
/**
* Touch handlers (Android, iPad)
*/
// State
var touch_state = null;
function touchKillTimer()
{
if( touch_state ) {
if( touch_state.timer ) {
clearTimeout(touch_state.timer);
touch_state.timer = null;
}
}
}
function touchStartTimer(source, touch)
{
var source_name = (source ? (source.name ? source.name : source.id) : false);
if( source_name ) {
var selection_box = selection_boxes[source_name.substr(0, source_name.indexOf(':item:'))];;
if( selection_box ) {
// kill previous timer, when present
touchKillTimer();
touch_state = {
target: source,
touch: { clientX: touch.clientX, clientY: touch.clientY },
timer: setTimeout(
// start dragging after a "tap and hold" time-out of 500ms
function() {
var drag_started = dragStart(touch_state.target);
if( drag_started ) {
// fat finger detection to prevent that multiple items are selected, even
// without actual dragging
if( selection_drag ) {
selection_drag.dikke_vinger = { clientX: touch_state.touch.clientX, clientY: touch_state.touch.clientY };
}
}
},
500
)
};
return touch_state;
}
}
return false;
}
addEventHandler(document, 'touchstart', function(e) {
if( !e ) e = window.event;
var target = (e.target ? e.target : e.srcElement);
var target_name = (target ? (target.name ? target.name : target.id) : false);
var selection_box = false;
if( target_name ) selection_box = selection_boxes[target_name.substr(0, target_name.indexOf(':'))];
// selection box selected and not a multi-touch event
if( e.changedTouches.length == 1 && selection_box ) {
var touch = e.changedTouches[0];
if(
selection_box.inLeftRightBox && // true drag and drop is only supported for the left/right boxes
selection_box.isSelected(target)
) {
dragStart(target);
touch_state = {
target: target,
touch: { clientX: touch.clientX, clientY: touch.clientY },
timer: null
};
e.preventDefault();
return false;
}
else {
// do _not_ cancel the event, as this will disable the default scroll behaviour
// NB: on an iPad, not cancelling the "touchstart" event will trigger a "click"
// event after "touchend"; the PHP code will therefore not install the default
// "onclick" handler to select an item (a click is handled in the "touchend"
// handler)
touch_state = touchStartTimer(target, touch);
}
}
return true;
});
addEventHandler(document, 'touchmove', function(e) {
if( !e ) e = window.event;
var touch = e.changedTouches[0];
// iPad doesn't have a 'radius' in its touch evnet
var radiusX = useDefault(touch.radiusX, 16);
var radiusY = useDefault(touch.radiusX, 16);
// is the user scrolling the list? if so, clear the drag/touch state
if(
touch_state &&
(
Math.abs(touch_state.touch.clientY - touch.clientY) >= radiusY ||
Math.abs(touch_state.touch.clientX - touch.clientX) >= radiusX
)
) {
if( touch_state.timer ) touchKillTimer();
if( selection_move ) {
selection_move.started = true;
}
else if( selection_drag ) {
selection_drag.started = true;
}
}
if( selection_move ) {
var target = document.elementFromPoint(touch.clientX, touch.clientY);
var source = selection_move.source;
dragTest(source, target);
e.preventDefault();
return false;
}
else if( selection_drag ) {
var list = getElement(selection_drag.selection_box.name + ':select');
var r = list.parentNode.getBoundingClientRect();
if(
touch.clientY >= r.top && touch.clientY <= r.bottom &&
(
!selection_drag.dikke_vinger ||
Math.abs(selection_drag.dikke_vinger.clientY - touch.clientY) >= radiusY ||
Math.abs(selection_drag.dikke_vinger.clientX - touch.clientX) >= radiusX
)
) {
// clear fat finger state
selection_drag.dikke_vinger = null;
// select all items from the "start" to this one
var r_list = list.getBoundingClientRect();
var index = selection_drag.selection_box.ItemAt(touch.clientY - r_list.top);
selection_drag.selection_box.SelectFrom(selection_drag.start, index);
}
else {
// scroll the list when the mouse pointer is outside this list
dragScroll(selection_drag.selection_box, touch.clientX, touch.clientY);
}
e.preventDefault();
return false;
}
return true;
});
addEventHandler(document, 'touchend', function(e) {
if( !e ) e = window.event;
var touch = e.changedTouches[0];
if( touch_state && touch_state.timer ) {
// iPad doesn't have a 'radius' in its touch event
var radiusX = useDefault(touch.radiusX, 16);
var radiusY = useDefault(touch.radiusX, 16);
// is it actually a click? if so, pretend that the drag was started
// and handle the click event below
// NB: mostly to avoid so hitches of the iPad
if(
Math.abs(touch_state.touch.clientY - touch.clientY) <= radiusY ||
Math.abs(touch_state.touch.clientX - touch.clientX) <= radiusX
) {
dragStart(touch_state.target);
// 'selection_drag' is initialized above, with 'started' set to false
}
touchKillTimer();
}
if( selection_move ) {
// drop if the target is valid
if( selection_move.target ) {
dragDrop(selection_move.source, selection_move.target);
}
else {
dragCancel(selection_move.source);
if( !selection_move.started ) {
// a "click" really
var list = getElement(selection_move.source.name + ':select');
var r_list = list.getBoundingClientRect();
var index = selection_move.source.ItemAt(touch.clientY - r_list.top);
selection_move.source.Select(index, false);
}
}
e.preventDefault();
}
else if( selection_drag ) {
var list = getElement(selection_drag.selection_box.name + ':select');
var r_list = list.getBoundingClientRect();
var index = selection_drag.selection_box.ItemAt(touch.clientY - r_list.top);
if( !selection_drag.started ) {
// a "click" really...
selection_drag.selection_box.Select(index, false);
}
e.preventDefault();
}
// clear drag-and-drop state
if( selection_move && selection_move.target ) {
dragHighlight(selection_move.target, false);
}
selection_drag = null;
selection_move = null;
return true;
});
addEventHandler(document, 'touchcancel', function(e) {
if( touch_state ) touchKillTimer();
if( selection_move ) {
if( selection_move.target ) dragHighlight(selection_move.target, false);
if( selection_move.source ) dragShowDrag(selection_move.source, true);
}
selection_drag = null;
selection_move = null;
return true;
});