/** \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 == " " ) { // special marker for an empty
  • ; the browser won't show empty
  • 's, // but copying it to the edit box will show the text " " 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; });