/*** KeymanWeb 10.0 Copyright 2017 SIL International ***/ // If KMW is already initialized, the KMW script has been loaded more than once. We wish to prevent resetting the // KMW system, so we use the fact that 'initialized' is only 1 / true after all scripts are loaded for the initial // load of KMW. if(!window['tavultesoft']['keymanweb']['initialized']) { /****************************************************************** * Main Keyman Web Module * * Code enclosed as an anonymous function to protect name space * ******************************************************************/ (function() { // Declare KeymanWeb, OnScreen Keyboard and Util objects var keymanweb=window['tavultesoft']['keymanweb'],osk=keymanweb['osk'],util=keymanweb['util'],device=util.device; /** * Function debug * Scope Private * @param {(string|Object)} s string (or object) to print * Description Simple debug display (upper right of screen) * Extended to support multiple arguments May 2015 */ keymanweb['debug']=keymanweb.debug=function(s){ var p; if(keymanweb.debugElement == null) { var d=document.createElement('DIV'),ds=d.style; ds.position='absolute';ds.width='30%';ds.maxHeight='50%';ds.top='0';ds.right='0'; ds.minHeight='50px'; ds.border='1px solid blue'; ds.whiteSpace='pre-line';ds.overflowY='scroll'; p=document.createElement('P'); p.id='debug_output';p.style.margin='2px'; d.appendChild(p); document.body.appendChild(d); keymanweb.debugElement=p; } if((p=document.getElementById('debug_output')) == null) return; if(arguments.length == 0) if(typeof p.textContent != 'undefined') p.textContent=''; else p.innerHTML=''; else { var ts=new Date().toTimeString().substr(3,5),t=ts+' ',t1,k,m,sx; for(k=0; k 0) t = t + '; '; sx = arguments[k]; if(typeof sx == 'object') { if(sx == null) { t = t + 'null'; } else { t1 = ''; for(m in sx) { if(t1.length > 0) t1 = t1 + ', '; t1 = t1 + m + ':'; switch(typeof sx[m]) { case 'string': case 'number': case 'boolean': t1 = t1 + sx[m]; break; default: t1 = t1 + typeof sx[m]; break; } if(t1.length > 1024) { t1 = t1.substr(0,1000)+'...'; break; } } if(t1.length > 0) t = t + '{' + t1 + '}'; } } else { t = t + sx; } } // Truncate if necessary to avoid memory problems if(t.length > 1500) t = t.substr(0,1500) + ' (more)'; if(typeof p.textContent != 'undefined') p.textContent=t+'\n'+p.textContent; else p.innerHTML=t+'
'+p.innerHTML; } } keymanweb.debugElement=null; var dbg=keymanweb.debug; /** * Function addEventListener * Scope Public * @param {string} event event to handle * @param {function(Event)} func event handler function * @return {boolean} value returned by util.addEventListener * Description Wrapper function to add and identify KeymanWeb-specific event handlers */ keymanweb['addEventListener'] = keymanweb.addEventListener = function(event, func) { return util.addEventListener('kmw.'+event, func); } /** * Function setUpTouchDevice * Scope Private * Description Initialize event handling and duplicate input fields for touch-input devices */ keymanweb.setupTouchDevice = function() { /** * Ideally, OSK behaviour should emulate internal keyboard, but * it is not possible to do that while allowing native scrolling. * The compromise adopted is that a touchstart or touchmove event * on any part of the page other than an input element or the OSK * itself will temporarily hide the OSK until the touchend or * window.scroll event is fired. Hiding the OSK in this way seems * less disturbing than having it move as the page is scrolled. * * All of this may be better if we can reliably get the y-offset * from the CSS transform and apply that to repositioning the OSK * using a timed event loop. */ /** * Close OSK and remove simulated caret on losing focus */ keymanweb.cancelInput = function() { keymanweb._ActiveElement=null; keymanweb.hideCaret(); osk.hideNow(); }; /** * Handle losing focus from simulated input field * * @param {Event} e event */ keymanweb.setBlur = function(e) { // This works OK for iOS, but may need something else for other platforms if(('relatedTarget' in e) && e.relatedTarget) { if(e.relatedTarget.nodeName != 'DIV' || e.relatedTarget.className.indexOf('keymanweb-input') == -1) { keymanweb.cancelInput(); return; } } //Hide the OSK if(!keymanweb.focusing) keymanweb.cancelInput(); } /** * Handle receiving focus by simulated input field * * @param {Event=} e event */ keymanweb.setFocus=function(e) { //e.stopPropagation(); // not doing anything useful, no child elements //e.preventDefault(); // prevents user selection or scrolling, may be better if they are allowed? keymanweb.focusing=true; keymanweb.focusTimer=window.setTimeout(function(){keymanweb.focusing=false;},1000); var tEvent=e; if(e) { if(typeof e.touches == 'object') tEvent=e.touches[0]; } // Allow external code to set focus and thus display the OSK on touch devices if required (KMEW-123) else { tEvent={clientX:0,clientY:0} // Will usually be called from setActiveElement, which should define _LastActiveElement if(keymanweb._LastActiveElement) tEvent.target = keymanweb._LastActiveElement['kmw_ip']; // but will default to first input or text area on page if _LastActiveElement is null else tEvent.target = keymanweb.sortedInputs[0]['kmw_ip']; } var touchX=tEvent.clientX,touchY=tEvent.clientY,tTarg=tEvent.target,scroller; // Identify the scroller element if(tTarg.nodeName == 'SPAN') scroller=tTarg.parentNode; else if(tTarg.className != null && tTarg.className.indexOf('keymanweb-input') >= 0) scroller=tTarg.firstChild; else scroller=tTarg; // And the actual target element var target=scroller.parentNode; // Move the caret and refocus if necessary if(keymanweb._ActiveElement != target) { // Hide the KMW caret keymanweb.hideCaret(); keymanweb._ActiveElement=target; // The issue here is that touching a DIV does not actually set the focus for iOS, even when enabled to accept focus (by setting tabIndex=0) // We must explicitly set the focus in order to remove focus from any non-KMW input target.focus(); //Android native browsers may not like this, but it is needed for Chrome, Safari } // Correct element directionality if required keymanweb._SetTargDir(target); // What we really want to do is to blur any active element that is not a KMW input, // but the following line does not work as might be expected, even though the correct element is referenced. // It is as though blur is ignored if focus is supposed to have been moved, even if it has not in fact been moved? //if(document.activeElement.nodeName != 'DIV' && document.activeElement.nodeName != 'BODY') document.activeElement.blur(); // Refresh the internal keyboard flag state keymanweb.useInternalKeyboard=false; // And display the OSK if not already visible if(osk.ready && !osk._Visible) osk._Show(); // If clicked on DIV, set caret to end of text if(tTarg.nodeName == 'DIV' ) { var x,cp; x=util._GetAbsoluteX(scroller.firstChild); if(target.dir == 'rtl') { x += scroller.firstChild.offsetWidth; cp=(touchX > x ? 0 : 100000); } else cp=(touchX touchY && cp > cpMin && cp != cpMax) {cpMax=cp; cp=Math.round((cp+cpMin)/2);} else if(y < touchY-yRow && cp < cpMax && cp != cpMin) {cpMin=cp; cp=Math.round((cp+cpMax)/2);} else break; keymanweb.setTextCaret(target,cp); } while(util._GetAbsoluteY(caret)-dy > touchY && cp > cpMin)keymanweb.setTextCaret(target,--cp); while(util._GetAbsoluteY(caret)-dy < touchY-yRow && cp < cpMax) keymanweb.setTextCaret(target,++cp); } // Caret repositioning for horizontal scrolling of RTL text if(target.dir == 'rtl') { for(iLoop=0; iLoop<16; iLoop++) { x=util._GetAbsoluteX(caret); //left of caret if(x < touchX && cp > cpMin && cp != cpMax) {cpMax=cp; cp=Math.round((cp+cpMin)/2);} else if(x > touchX && cp < cpMax && cp != cpMin) {cpMin=cp; cp=Math.round((cp+cpMax)/2);} else break; keymanweb.setTextCaret(target,cp); } while(util._GetAbsoluteX(caret) < touchX && cp > cpMin) keymanweb.setTextCaret(target,--cp); while(util._GetAbsoluteX(caret) > touchX && cp < cpMax) keymanweb.setTextCaret(target,++cp); } // Caret repositioning for horizontal scrolling of standard (LTR) text else { for(iLoop=0; iLoop<16; iLoop++) // assumes fields shorter than 2**16 characters { x=util._GetAbsoluteX(caret); //left of caret if(x > touchX && cp > cpMin && cp != cpMax) { cpMax=cp; cp=Math.round((cp+cpMin)/2); } else if(x < touchX && cp < cpMax && cp != cpMin) { cpMin=cp; cp=Math.round((cp+cpMax)/2); } else break; keymanweb.setTextCaret(target,cp); } while(util._GetAbsoluteX(caret) > touchX && cp > cpMin) keymanweb.setTextCaret(target,--cp); while(util._GetAbsoluteX(caret) < touchX && cp < cpMax) keymanweb.setTextCaret(target,++cp); } } /** * This event will trigger before keymanweb.setBlur is triggered. Now that we're allowing independent keyboard settings * for controls, we have to act here to preserve the outgoing control's keyboard settings. * * If we 'just activated' the KeymanWeb UI, we need to save the new keyboard change as appropriate. */ var keyboardID = keymanweb._ActiveKeyboard ? keymanweb._ActiveKeyboard['KI'] : ''; if(keymanweb._LastActiveElement && keymanweb._LastActiveElement._kmwAttachment.keyboard != null) { keymanweb._LastActiveElement._kmwAttachment.keyboard = keyboardID; keymanweb._LastActiveElement._kmwAttachment.languageCode = keymanweb.getActiveLanguage(); } else { keymanweb.globalKeyboard = keyboardID; keymanweb.globalLanguageCode = keymanweb.getActiveLanguage(); } // With the attachment API update, we now directly track the old legacy control behavior. keymanweb._LastActiveElement = target; /** * If we 'just activated' the KeymanWeb UI, we need to save the new keyboard change as appropriate. * If not, we need to activate the control's preferred keyboard. */ keyboardID = keymanweb._ActiveKeyboard == null ? '' : keymanweb._ActiveKeyboard['KI']; if(keymanweb._LastActiveElement._kmwAttachment.keyboard != null) { keymanweb.setActiveKeyboard(keymanweb._LastActiveElement._kmwAttachment.keyboard, keymanweb._LastActiveElement._kmwAttachment.languageCode); } else { keymanweb.setActiveKeyboard(keymanweb.globalKeyboard, keymanweb.globalLanguageCode); } //TODO: the logic of the following line doesn't look right!! Both variables are true, but that doesn't make sense! //_Debug(keymanweb._IsIEEditableIframe(Ltarg,1) + '...' +keymanweb._IsMozillaEditableIframe(Ltarg,1)); if(!keymanweb._IsIEEditableIframe(target,1) || !keymanweb._IsMozillaEditableIframe(target,1)) { keymanweb._DisableInput = 1; return; } keymanweb._DisableInput = 0; if(!keymanweb._JustActivatedKeymanWebUI) { keymanweb._DeadKeys = []; keymanweb._NotifyKeyboard(0,target,1); // I2187 } if(!keymanweb._JustActivatedKeymanWebUI && keymanweb._SelectionControl != target) keymanweb._IsActivatingKeymanWebUI = 0; keymanweb._JustActivatedKeymanWebUI = 0; keymanweb._SelectionControl = target; } /** * Create a caret to be appended to the scroller of the focussed input field. * The caret is appended to the scroller so that it will automatically be clipped * when the user manually scrolls it outside the element boundaries. * It is positioned exactly over the hidden span that is inserted between the * text spans before and after the insertion point. */ keymanweb.caret=document.createElement('DIV'); var cs=keymanweb.caret.style; cs.position='absolute'; cs.height='16px'; // default height, actual height set from element properties cs.width='2px'; cs.backgroundColor='blue'; cs.border='none'; cs.left=cs.top='0px'; // actual position set relative to parent when displayed cs.display='block'; cs.visibility='hidden'; cs.zIndex='9998'; // immediately below the OSK /** * Position the caret at the start of the second span within the scroller * * @param {Object} e input DIV element (copy of INPUT or TEXTAREA) */ keymanweb.showCaret=function(e) { if(!e || !e.firstChild || (e.firstChild.childNodes.length<3)) return; var scroller=e.firstChild,cs=keymanweb.caret.style,sp2=scroller.childNodes[1]; // Attach the caret to this scroller and position it over the caret span if(keymanweb.caret.parentNode != scroller) scroller.appendChild(keymanweb.caret); cs.left=sp2.offsetLeft+'px'; cs.top=sp2.offsetTop+'px'; cs.height=(sp2.offsetHeight-1)+'px'; cs.visibility='hidden'; // best to wait for timer to display caret // Scroll into view if required keymanweb.scrollBody(e); // Display and position the scrollbar if necessary keymanweb.setScrollBar(e); } /** * Display and position a scrollbar in the input field if needed * * @param {Object} e input DIV element (copy of INPUT or TEXTAREA) */ keymanweb.setScrollBar=function(e) { // Display the scrollbar if necessary. Added TEXTAREA condition to correct rotation issue KMW-5. Fixed for 310 beta. var scroller=e.childNodes[0],sbs=e.childNodes[1].style; if((scroller.offsetWidth > e.offsetWidth || scroller.offsetLeft < 0) && (e.base.nodeName != 'TEXTAREA')) { sbs.height='4px'; sbs.width=100*(e.offsetWidth/scroller.offsetWidth)+'%'; sbs.left=100*(-scroller.offsetLeft/scroller.offsetWidth)+'%'; sbs.top='0'; sbs.visibility='visible'; } else if(scroller.offsetHeight > e.offsetHeight || scroller.offsetTop < 0) { sbs.width='4px'; sbs.height=100*(e.offsetHeight/scroller.offsetHeight)+'%'; sbs.top=100*(-scroller.offsetTop/scroller.offsetHeight)+'%'; sbs.left='0'; sbs.visibility='visible'; } else { sbs.visibility='hidden'; } } /** * Description Toggle state of caret in simulated input field */ keymanweb.flashCaret = function() { if(device.touchable && keymanweb._ActiveElement != null) { var cs=keymanweb.caret.style; if(cs.visibility != 'visible') cs.visibility='visible'; else cs.visibility='hidden'; } } /** * Hide caret in simulated input field, update underlying INPUT or TEXTAREA */ keymanweb.hideCaret = function() { var e=keymanweb._LastActiveElement,s=null; if(e && e.className != null && e.className.indexOf('keymanweb-input') >= 0) { // Always copy text back to underlying field on blur e.base.value = keymanweb.getText(e); // And set the scroller caret to the end of the element content keymanweb.setText(e,null,100000); // Set the element scroll to zero (or max for RTL INPUT) var ss=e.firstChild.style; if(e.base.nodeName == 'TEXTAREA') ss.top='0'; else { if(e.base.dir == 'rtl') ss.left=(e.offsetWidth-e.firstChild.offsetWidth-8)+'px'; else ss.left='0'; } // And hide the caret and scrollbar if(keymanweb.caret.parentNode) keymanweb.caret.parentNode.removeChild(keymanweb.caret); keymanweb.caret.style.visibility='hidden'; if(e.childNodes.length > 1 ) e.childNodes[1].style.visibility='hidden'; } } // Start the caret flash timer keymanweb.timerId = window.setInterval(keymanweb.flashCaret,500); /** * Insert text into simulated input field at indicated character position * * @param {Object} e simulated input field DIV * @param {?string} t text to insert in element * @param {?number} cp caret position (characters) */ keymanweb.setText = function(e,t,cp) { if(e && e.childNodes.length > 0) { var d=e.firstChild,tLen=0; if(d.childNodes.length >= 3) { var s1=d.childNodes[0],s2=d.childNodes[2],t1,t2; // Read current text if null passed (for caret positioning) if(t === null) { t1=(typeof(s1.textContent) == 'string' ? s1.textContent : s1.innerText); t2=(typeof(s2.textContent) == 'string' ? s2.textContent : s2.innerText); t=t1+t2; } if(cp < 0) cp = 0; //if(typeof t._kmwLength == 'undefined') return; tLen=t._kmwLength(); if(cp === null || cp > tLen) cp=tLen; t1=t._kmwSubstr(0,cp); t2=t._kmwSubstr(cp); if(typeof(s1.textContent) == 'string') s1.textContent=t1; else s1.innerText=t1; if(typeof(s2.textContent) == 'string') s2.textContent=t2; else s2.innerText=t2; } } keymanweb.updateBaseElement(e,tLen); // KMW-3, KMW-29 } /** * Set content, visibility, background and borders of input and base elements (KMW-3,KMW-29) * * @param {Object} e input element * @param {number} n length of text in field */ keymanweb.updateBaseElement=function(e,n) { e.base.value=keymanweb.getText(e); //KMW-29 e.style.backgroundColor=(n==0?'transparent':window.getComputedStyle(e.base,null).backgroundColor); if(device.OS == 'iOS') { e.base.style.visibility=(n==0?'visible':'hidden'); } } /** * Get simulated input field content * * @param {Object} e simulated input field DIV * @return {string} entire text in simulated input field */ keymanweb.getText=function(e) { if(e == null) return ''; return (typeof(e.textContent) == 'string' ? e.textContent : e.innerText); } /** * Get text up to the caret from simulated input field * * @param {Object} e simulated input field DIV * @return {string} Context for simulated input field */ keymanweb.getTextBeforeCaret=function(e) { if(e && e.childNodes.length > 1) { var d=e.firstChild; if(d.childNodes.length > 0) { var s1=d.childNodes[0]; if('textContent' in s1) return s1.textContent; if('innerText' in s1) return s1.innerText; } } return ''; } /** * Replace text up to the caret in the simulated input field * * @param {Object} e simulated input field DIV * @param {string} t string to insert */ keymanweb.setTextBeforeCaret=function(e,t) { if(e && e.childNodes.length > 0) { var d=e.firstChild,tLen=0; if(d.childNodes.length > 1) { var s1=d.childNodes[0],s2=d.childNodes[2]; // Collapse (trailing) whitespace to a single space for INPUT fields (also prevents wrapping) if(e.base.nodeName != 'TEXTAREA') t=t.replace(/\s+$/,' '); if('textContent' in s1) s1.textContent=t; else if('innerText' in s1) s1.innerText=t; // Test total length in order to control base element visibility tLen=t.length; if('textContent' in s2) tLen=tLen+s2.textContent.length; else if('innerText' in s2) tLen=tLen+s2.innerText.length; } } // Update the base element then scroll into view if necessary keymanweb.updateBaseElement(e,tLen); //KMW-3, KMW-29 keymanweb.scrollInput(e); } /** * Description Get current position of caret in simulated input field * * @param {Object} e simulated input field DIV * @return {number} caret character position in simulated input field */ keymanweb.getTextCaret=function(e) { return keymanweb.getTextBeforeCaret(e)._kmwLength(); } /** * Set current position of caret in simulated input field then display the caret * * @param {Object} e element (object) or element id (string) of simulated input field * @param {number} cp caret position (character index) */ keymanweb.setTextCaret=function(e,cp) { keymanweb.setText(e,null,cp); keymanweb.showCaret(e); } /** * Handle the touch move event for an input element * * @param {Event} e touchmove event */ keymanweb.dragInput=function(e) { // Prevent dragging window e.preventDefault(); e.stopPropagation(); // Identify the target from the touch list or the event argument (IE 10 only) var target=(typeof e.targetTouches == 'object' ? e.targetTouches[0].target : e.target); if(target == null) return; // Identify the input element from the touch event target (touched element may be contained by input) if(target.className == null || target.className.indexOf('keymanweb-input') < 0) target=target.parentNode; if(target.className == null || target.className.indexOf('keymanweb-input') < 0) target=target.parentNode; if(target.className == null || target.className.indexOf('keymanweb-input') < 0) return; var x=(typeof e.touches == 'object' ? e.touches[0].screenX : e.screenX), y=(typeof e.touches == 'object' ? e.touches[0].screenY : e.screenY); // Allow content of input elements to be dragged horizontally or vertically if(typeof keymanweb.firstTouch == 'undefined' || keymanweb.firstTouch == null) keymanweb.firstTouch={x:x,y:y}; else { var x0=keymanweb.firstTouch.x,y0=keymanweb.firstTouch.y, scroller=target.firstChild,dx,dy,x1; if(target.base.nodeName == 'TEXTAREA') { var yOffset=parseInt(scroller.style.top,10); if(isNaN(yOffset)) yOffset=0; dy=y0-y; if(dy < -4 || dy > 4) { scroller.style.top=(yOffset 4) { // Limit dragging beyond the defined text (to avoid dragging the text completely out of view) var xMin=0,xMax=util._GetAbsoluteX(target)+target.offsetWidth-scroller.offsetWidth-32; if(target.base.dir == 'rtl')xMin=16; else xMax=xMax-24; x1=xOffset-dx; if(x1 > xMin) x1=xMin; if(x1 < xMax) x1=xMax; scroller.style.left=x1+'px'; keymanweb.firstTouch.x=x; } } } keymanweb.setScrollBar(target); } /** * Scroll the input field horizontally (INPUT base element) or * vertically (TEXTAREA base element) to bring the caret into view * as text is entered or deleted form an element * * @param {Object} e simulated input field object with focus */ keymanweb.scrollInput=function(e) { if(!e || !e.firstChild || e.className == null || e.className.indexOf('keymanweb-input') < 0 ) return; var scroller=e.firstChild; if(scroller.childNodes.length < 3) return; // Get the actual absolute position of the caret and the element var s2=scroller.childNodes[1], cx=util._GetAbsoluteX(s2),cy=util._GetAbsoluteY(s2), ex=util._GetAbsoluteX(e),ey=util._GetAbsoluteY(e), x=parseInt(scroller.style.left,10), y=parseInt(scroller.style.top,10), dx=0,dy=0; // Scroller offsets must default to zero if(isNaN(x)) x=0; if(isNaN(y)) y=0; // Scroll input field vertically if necessary if(e.base.nodeName == 'TEXTAREA') { var rowHeight=Math.round(e.offsetHeight/e.base.rows); if(cy < ey) dy=cy-ey; if(cy > ey+e.offsetHeight-rowHeight) dy=cy-ey-e.offsetHeight+rowHeight; if(dy != 0)scroller.style.top=(y ex+e.offsetWidth-12) dx=cx-ex-e.offsetWidth+12; if(dx != 0)scroller.style.left=(x x.base.rows+1) { x.base.rows=calcRows; } } ds.width=xs.width; ds.minHeight=xs.height; } else { ds.minWidth=xs.width; ds.height=xs.height; } x.base.style.visibility='hidden'; // hide by default: KMW-3 // Add an explicit event listener to allow the duplicated input element // to be adjusted for any changes in base element location or size // This will be called for each element after any rotation, as well as after user-initiated changes // It has to be wrapped in an anonymous function to preserve scope and be applied to each element. (function(xx){ xx._kmwResizeHandler = function(e){ /* A timeout is needed to let the base element complete its resizing before our * simulated element can properly resize itself. * * Not doing this causes errors if the input elements are resized for whatever reason, such as * changing languages to a text with greater height. */ window.setTimeout(function (){ keymanweb.updateInput(xx); }, 1); }; xx.base.addEventListener('resize', xx._kmwResizeHandler, false); })(x); // And copy the text content keymanweb.setText(x,x.base.value,null); return true; } /** * Function disableTouchElement * Scope Private * @param {Element} Pelem An input or textarea element from the page. * Description Destroys the simulated input element for the specified INPUT or TEXTAREA and reverts * back to desktop-style 'enablement' for the base control. */ keymanweb.disableTouchElement = function(Pelem) { // Do not check for the element being officially disabled - it's also used for detachment. // Touch doesn't worry about iframes. if(Pelem.tagName.toLowerCase() == 'iframe') { return; // If/when we do support this, we'll need an iframe-level manager for it. } if(Pelem['kmw_ip']) { var index = keymanweb.inputList.indexOf(Pelem['kmw_ip']); if(index != -1) { keymanweb.inputList.splice(index, 1); } Pelem.style.visibility='visible'; // hide by default: KMW-3 Pelem.disabled = false; Pelem.removeEventListener('resize', Pelem['kmw_ip']._kmwResizeHandler); // Disable touch-related handling code. keymanweb.disableInputElement(Pelem['kmw_ip']); // We get weird repositioning errors if we don't remove our simulated input element - and permanently. Pelem.parentNode.removeChild(Pelem['kmw_ip']); delete Pelem['kmw_ip']; } keymanweb.setupNonKMWTouchElement(Pelem); } /** * Function nonKMWTouchHandler * Scope Private * Description A handler for KMW-touch-disabled elements when operating on touch devices. */ keymanweb.nonKMWTouchHandler = function(x) { keymanweb.focusing=false; clearTimeout(keymanweb.focusTimer); osk.hideNow(); } /** * Function setupNonKMWTouchElement * Scope Private * @param {Element} x A child element of document. * Description Performs handling for the specified disabled input element on touch-based systems. */ keymanweb.setupNonKMWTouchElement = function(x) { x.addEventListener('touchstart', keymanweb.nonKMWTouchHandler, false); // Signify that touch isn't enabled on the control. if(keymanweb.isAttached(x)) { x._kmwAttachment.touchEnabled = false; } } } /********************************************************* * * End of main touch-device initialization. * *********************************************************/ /** * Function enableInputElement * Scope Private * @param {Element} Pelem An element from the document to be enabled with full KMW handling. * @param {boolean=} isAlias A flag that indicates if the element is a simulated input element for touch. * Description Performs the basic enabling setup for one element and adds it to the inputList if it is an input element. * Note that this method is called for both desktop and touch control routes; the touch route calls it from within * enableTouchElement as it must first establish the simulated touch element to serve as the alias "input element" here. * Note that the 'kmw-disabled' property is managed by the MutationObserver and by the surface API calls. */ keymanweb.enableInputElement = function(Pelem, isAlias) { var baseElement = isAlias ? Pelem['base'] : Pelem; if(!keymanweb.isKMWDisabled(baseElement)) { if(Pelem.tagName.toLowerCase() == 'iframe') { keymanweb._AttachToIframe(Pelem); } else { baseElement.className = baseElement.className ? baseElement.className + ' keymanweb-font' : 'keymanweb-font'; keymanweb.inputList.push(Pelem); util.attachDOMEvent(baseElement,'focus', keymanweb._ControlFocus); util.attachDOMEvent(baseElement,'blur', keymanweb._ControlBlur); // TODO: Rework KMW to properly support physical keyboard use while in touch mode properly. // As not all the proper infrastructure exists (regarding certain keyboard stores/rules), // we leave physical keystroke input disabled when in touch mode. if(!isAlias) { // These need to be on the actual input element, as otherwise the keyboard will disappear on touch. Pelem.onkeypress = keymanweb._KeyPress; Pelem.onkeydown = keymanweb._KeyDown; Pelem.onkeyup = keymanweb._KeyUp; } } } }; /** * Function disableInputElement * Scope Private * @param {Element} Pelem An element from the document to be enabled with full KMW handling. * @param {boolean=} isAlias A flag that indicates if the element is a simulated input element for touch. * Description Inverts the process of enableInputElement, removing all event-handling from the element. * Note that the 'kmw-disabled' property is managed by the MutationObserver and by the surface API calls. */ keymanweb.disableInputElement = function(Pelem, isAlias) { var baseElement = isAlias ? Pelem['base'] : Pelem; // Do NOT test for pre-disabledness - we also use this to fully detach without officially 'disabling' via kmw-disabled. if(Pelem.tagName.toLowerCase() == 'iframe') { keymanweb._DetachFromIframe(Pelem); } else { var cnIndex = baseElement.className.indexOf('keymanweb-font'); if(cnIndex > 0 && !isAlias) { // See note about the alias below. baseElement.className = baseElement.className.replace('keymanweb-font', '').trim(); } // Remove the element from our internal input tracking. var index = keymanweb.inputList.indexOf(Pelem); if(index > -1) { keymanweb.inputList.splice(index, 1); } if(!isAlias) { // See note about the alias below. util.detachDOMEvent(baseElement,'focus', keymanweb._ControlFocus); util.detachDOMEvent(baseElement,'blur', keymanweb._ControlBlur); } // These need to be on the actual input element, as otherwise the keyboard will disappear on touch. Pelem.onkeypress = null; Pelem.onkeydown = null; Pelem.onkeyup = null; } // If we're disabling an alias, we should fully enable the base version. (Thinking ahead to toggleable-touch mode.) if(isAlias) { keymanweb.inputList.push(baseElement); baseElement.onkeypress = keymanweb._KeyPress; baseElement.onkeydown = keymanweb._KeyDown; baseElement.onkeyup = keymanweb._KeyUp; } return; }; /** * Get the user-specified (or default) font for the first mapped input or textarea element * before applying any keymanweb styles or classes * * @return {string} **/ keymanweb.getBaseFont = function() { var ipInput=document.getElementsByTagName('INPUT'), ipTextArea=document.getElementsByTagName('TEXTAREA'), n=0,fs,fsDefault='Arial,sans-serif'; if(ipInput.length == 0 && ipTextArea.length == 0) n=0; else if(ipInput.length > 0 && ipTextArea.length == 0) n=1; else if(ipInput.length == 0 && ipTextArea.length > 0) n=2; else if(ipInput[0].offsetTop < ipTextArea[0].offsetTop) n=1; else if(ipInput[0].offsetTop > ipTextArea[0].offsetTop) n=2; else if(ipInput[0].offsetLeft < ipTextArea[0].offsetLeft) n=1; else if(ipInput[0].offsetLeft > ipTextArea[0].offsetLeft) n=2; switch(n) { case 0: fs=fsDefault; case 1: fs=util.getStyleValue(ipInput[0],'font-family'); case 2: fs=util.getStyleValue(ipTextArea[0],'font-family'); } if(typeof(fs) == 'undefined' || fs == 'monospace') fs=fsDefault; return fs; } /** * Input element stub functions, redefined when a touch device is initialized */ /** * Function stub, for desktop browsers * * @param {Event=} e event */ keymanweb.setFocus = function(e){}; keymanweb.timerID = null; /** * Function stub, for desktop browsers * * @param {Object} e element (object) or element id (string) of simulated input field * @return {string} entire text in simulated input field */ keymanweb.getText = function(e){return '';}; /** * Function stub, for desktop browsers * * @param {Object} e element (object) or element id (string) of simulated input field * @param {?string} t text to insert in element * @param {?number} cp caret position (characters) */ keymanweb.setText = function(e,t,cp){}; /** * Function stub, for desktop browsers * * @return {string} */ keymanweb.getTextBeforeCaret = function(){return '';}; /** * Function stub, for desktop browsers * * @param {Object} e element (object) or element id (string) of simulated input field * @return {string} Context for simulated input field */ keymanweb.setTextBeforeCaret = function(e){}; /** * Function stub, for desktop browsers * * @param {Object} e element (object) or element id (string) of simulated input field * @return {number} caret character position in simulated input field */ keymanweb.getTextCaret = function(e){return 0;}; /** * Function stub, for desktop browsers * * @param {Object} e element (object) or element id (string) of simulated input field * @param {number} cp caret character position in simulated input field */ keymanweb.setTextCaret = function(e,cp){}; /** * Function stub, for desktop browser */ keymanweb.hideCaret = function(){}; /** * Function stub, for desktop browsers */ keymanweb.flashCaret = function(){}; /** * Function stub, for desktop browsers * * @param {Object} x element */ keymanweb.updateInput=function(x){}; // End of I3363 (Build 301) additions /** * Function resetContext * Scope Public * Description Revert OSK to default layer and clear any deadkeys and modifiers */ keymanweb['resetContext'] = keymanweb.resetContext = function() { osk.layerId = 'default'; keymanweb._DeadkeyResetMatched(); keymanweb._DeadkeyDeleteMatched(); keymanweb.cachedContext.reset(); keymanweb._ResetVKShift(); osk._Show(); }; /** * Exposed function to load keyboards by name. One or more arguments may be used * * @param {string|Object} x keyboard name string or keyboard metadata JSON object * */ keymanweb['addKeyboards'] = function(x) { if(arguments.length == 0) keymanweb.keymanCloudRequest('',false); else keymanweb.addKeyboardArray(arguments); } /** * Build 362: addKeyboardArray() link to Cloud. One or more arguments may be used * * @param {string|Object} x keyboard name string or keyboard metadata JSON object * */ keymanweb.addKeyboardArray = function(x) { // Store all keyboard meta-data for registering later if called before initialization if(!keymanweb['initialized']) { for(var k=0; k 0) { var pList=x[i].split('@'),lList=[''],tEntry; if(pList[0].toLowerCase() == 'english') pList[0] = 'us'; if(pList.length > 1) { lList=pList[1].split(','); } for(j=0; j 2) tEntry['version']=pList[2]; // If we've already registered or requested a stub for this keyboard-language pairing, // don't bother with a cloud request. if(isUniqueRequest(tEntry)) { keymanweb.cloudList.push(tEntry); } } } if(typeof(x[i]) == 'object' && x[i] != null) { // Register any local keyboards immediately: // - must specify filename, keyboard name, language codes, region codes // - no request will be sent to cloud if(typeof(x[i]['filename']) == 'string') { if(!keymanweb.addStub(x[i])) alert('To use a custom keyboard, you must specify file name, keyboard name, language, language code and region code.'); } else { lList=x[i]['language']; //Array or single entry? if(typeof(lList.length) == 'number') { for(j=0; j 1) && byLanguage ? 'languages' : 'keyboards') +'?jsonp=tavultesoft.keymanweb.register'; // Set callback timer tFlag='&timerid='+window.setTimeout( function(){keymanweb.serverUnavailable(cmd);} ,10000); Lscript.charset="UTF-8"; Lscript.src = URL+cmd+tFlag; Lscript.type = 'text/javascript'; try { document.body.appendChild(Lscript); } catch(ex) { document.getElementsByTagName('head')[0].appendChild(Lscript); } } /** * Add default or all keyboards for a given language * * @param {string} arg Language name (multiple arguments allowed) **/ keymanweb['addKeyboardsForLanguage'] = function(arg) { keymanweb.addLanguageKeyboards(arguments); } /** * Add default or all keyboards for a given language * * @param {Object} languages Array of language names **/ keymanweb.addLanguageKeyboards = function(languages) { var i,j,lgName,ll=keymanweb.languageList,cmd,first,addAll; // Defer registering keyboards by language until the language list has been loaded if(ll == null) { first = (keymanweb.languagesPending.length == 0); for(i=0; i 9) rIndex = 0; else rIndex = region-1; } else if(typeof(region) == 'string') { var list = (region.length == 2 ? keymanweb.regionCodes : keymanweb.regions); for(var i=0; i=0; j--) { if('Keyboard_'+arguments[i] == ss[j]['KI'] && ss.length > 1) { ss.splice(j,1); break; } } } // Always reset to the first remaining keyboard keymanweb._SetActiveKeyboard(ss[0]['KI'],ss[0]['KLC'],true); // then update the UI keyboard menu keymanweb.doKeyboardUnregistered(); } /** * Browser dependent initialization */ if(document.selection) // only defined for IE { var appVer=navigator.appVersion; // Legacy support variables if(typeof(document.createEventObject)=='undefined' && (appVer.indexOf('MSIE 5.0') >= 0 || appVer.indexOf('MSIE 4.0') >= 0 || appVer.indexOf('MSIE 3.0') >= 0)) keymanweb.legacy=1; else if(appVer.indexOf('MSIE 6.0') >= 0) keymanweb._IE = 6; else if(appVer.indexOf('MSIE 7.0') >= 0) keymanweb._IE = 7; else if(appVer.indexOf('MSIE 8.0') >= 0) keymanweb._IE = 8; if(keymanweb._IE && document.compatMode=='BackCompat') keymanweb._IE = 6; } // I732 START - Support for European underlying keyboards #1 if(typeof(window['KeymanWeb_BaseLayout']) !== 'undefined') osk._BaseLayout = window['KeymanWeb_BaseLayout']; else osk._BaseLayout = 'us'; keymanweb._BrowserIsSafari = (navigator.userAgent.indexOf('AppleWebKit') >= 0); // I732 END - Support for European underlying keyboards #1 /** * Function _GetEventObject * Scope Private * @param {Event=} e Event object if passed by browser * @return Event object * Description Gets the event object from the window when using Internet Explorer * and handles getting the event correctly in frames */ keymanweb._GetEventObject=function(e) // I2404 - Attach to controls in IFRAMEs { if (!e) { e = window.event; if(!e) { var elem = keymanweb._GetLastActiveElement(); if(elem) { elem = elem.ownerDocument; if(elem) { elem = elem.parentWindow; } if(!elem) { return null; } e = elem.event; } } } return e; } /** * Function _push * Scope Private * @param {Array} Parray Array * @param {*} Pval Value to be pushed or appended to array * @return {Array} Returns extended array * Description Push (if possible) or append a value to an array */ keymanweb._push = function(Parray, Pval) { if(Parray.push) Parray.push(Pval); else Parray=Parray.concat(Pval); return Parray; } /** * Function attachToControl * Scope Public * @param {Element} Pelem Element to which KMW will be attached * Description Attaches KMW to control (or IFrame) */ keymanweb['attachToControl'] = keymanweb.attachToControl = function(Pelem) { if(keymanweb.isAttached(Pelem)) { return; // We're already attached. } if(keymanweb.isKMWInput(Pelem)) { keymanweb.setupElementAttachment(Pelem); if(!keymanweb.isKMWDisabled(Pelem)) { if(device.touchable) { keymanweb.enableTouchElement(Pelem); } else { keymanweb.enableInputElement(Pelem); } } else { if(device.touchable) { keymanweb.setupNonKMWTouchElement(Pelem); } } } else if(device.touchable) { keymanweb.setupNonKMWTouchElement(Pelem); } } /** * Function detachFromControl * Scope Public * @param {Element} Pelem Element from which KMW will detach * Description Detaches KMW from a control (or IFrame) */ keymanweb['detachFromControl'] = keymanweb.detachFromControl = function(Pelem) { if(!keymanweb.isAttached(Pelem)) { return; // We never were attached. } // #1 - if element is enabled, disable it. But don't manipulate the 'kmw-disabled' tag. if(keymanweb.isKMWInput(Pelem)) { // Is it already disabled? if(!keymanweb.isKMWDisabled(Pelem)) { keymanweb._DisableControl(Pelem); } } // #2 - clear attachment data. keymanweb.clearElementAttachment(Pelem); } /** * Function _AttachToIframe * Scope Private * @param {Element} Pelem IFrame to which KMW will be attached * Description Attaches KeymanWeb to IFrame */ keymanweb._AttachToIframe = function(Pelem) { try { var Lelem=Pelem.contentWindow.document; /* editable Iframe */ if(Lelem) { if(Lelem.parentWindow) { // Internet Explorer if(Lelem.designMode.toLowerCase() == 'on' || Lelem.body.isContentEditable) // I1295 - fix non-attachment for some forms of IFRAMEs { // I1480 - Attach to IFRAME instead of document util.attachDOMEvent(Pelem,'focus', keymanweb._ControlFocus); util.attachDOMEvent(Pelem,'blur', keymanweb._ControlBlur); util.attachDOMEvent(Lelem,'keydown', keymanweb._KeyDown); // I2404 - Update for attaching to elements within IFRAMEs, don't attach to read-only IFRAMEs util.attachDOMEvent(Lelem,'keypress', keymanweb._KeyPress); util.attachDOMEvent(Lelem,'keyup', keymanweb._KeyUp); if(!keymanweb.legacy) { // I1481 - Attach to the selectionchange in the iframe (and do a selchange to get the new selection) /* IE: call _SelectionChange when the user changes the selection */ util.attachDOMEvent(Lelem, 'selectionchange', keymanweb._SelectionChange); keymanweb._SelectionChange(); } } else { // Lelem is the IFrame's internal document; set 'er up! keymanweb._SetupDocument(Lelem); } } else { if(Lelem.designMode.toLowerCase() == 'on') { // Mozilla // I2404 - Attach to IFRAMEs child objects, only editable IFRAMEs here util.attachDOMEvent(Lelem,'focus', keymanweb._ControlFocus); util.attachDOMEvent(Lelem,'blur', keymanweb._ControlBlur); util.attachDOMEvent(Lelem,'keydown', keymanweb._KeyDown); util.attachDOMEvent(Lelem,'keypress', keymanweb._KeyPress); util.attachDOMEvent(Lelem,'keyup', keymanweb._KeyUp); } else { // Lelem is the IFrame's internal document; set 'er up! keymanweb._SetupDocument(Lelem); // I2404 - Manage IE events in IFRAMEs } } } } catch(err) { // do not attempt to attach to the iframe as it is from another domain - XSS denied! } } /** * Function _DetachFromIframe * Scope Private * @param {Element} Pelem IFrame to which KMW will be attached * Description Detaches KeymanWeb from an IFrame */ keymanweb._DetachFromIframe = function(Pelem) { try { var Lelem=Pelem.contentWindow.document; /* editable Iframe */ if(Lelem) { if(Lelem.parentWindow) { // Internet Explorer if(Lelem.designMode.toLowerCase() == 'on' || Lelem.body.isContentEditable) // I1295 - fix non-attachment for some forms of IFRAMEs { // I1480 - Attach to IFRAME instead of document util.detachDOMEvent(Pelem,'focus', keymanweb._ControlFocus); util.detachDOMEvent(Pelem,'blur', keymanweb._ControlBlur); util.detachDOMEvent(Lelem,'keydown', keymanweb._KeyDown); // I2404 - Update for attaching to elements within IFRAMEs, don't attach to read-only IFRAMEs util.detachDOMEvent(Lelem,'keypress', keymanweb._KeyPress); util.detachDOMEvent(Lelem,'keyup', keymanweb._KeyUp); if(!keymanweb.legacy) { // I1481 - Attach to the selectionchange in the iframe (and do a selchange to get the new selection) /* IE: call _SelectionChange when the user changes the selection */ util.detachDOMEvent(Lelem, 'selectionchange', keymanweb._SelectionChange); keymanweb._SelectionChange(); } } else { // Lelem is the IFrame's internal document; set 'er up! keymanweb._ClearDocument(Lelem); } } else { if(Lelem.designMode.toLowerCase() == 'on') { // Mozilla // I2404 - Attach to IFRAMEs child objects, only editable IFRAMEs here util.detachDOMEvent(Lelem,'focus', keymanweb._ControlFocus); util.detachDOMEvent(Lelem,'blur', keymanweb._ControlBlur); util.detachDOMEvent(Lelem,'keydown', keymanweb._KeyDown); util.detachDOMEvent(Lelem,'keypress', keymanweb._KeyPress); util.detachDOMEvent(Lelem,'keyup', keymanweb._KeyUp); } else { // Lelem is the IFrame's internal document; set 'er up! keymanweb._ClearDocument(Lelem); // I2404 - Manage IE events in IFRAMEs } } } } catch(err) { // do not attempt to attach to the iframe as it is from another domain - XSS denied! } } /** * Function GetEnabled * Scope Private * @return {boolean} True if KMW enabled * Description Test if KMW enabled */ keymanweb.GetEnabled = function() { return keymanweb._Enabled; } /** * Function SetEnabled * Scope Private * @param {(boolean|number)} Pvalue True to enable KMW * Description Enable or disable KMW */ keymanweb.SetEnabled = function(Pvalue) { if(Pvalue) Pvalue=1; else Pvalue=0; if(keymanweb._Enabled != Pvalue) { keymanweb._Enabled = Pvalue; if((!Pvalue) && keymanweb['HideInterface']) keymanweb['HideInterface'](); //JMD 3/9/10 } } /** * Function disableControl * Scope Public * @param {Element} Pelem Element to be disabled * Description Disables a KMW control element */ keymanweb['disableControl'] = keymanweb.disableControl = function(Pelem) { if(!keymanweb.isAttached(Pelem)) { console.warn("KeymanWeb is not attached to element " + Pelem); } var cn = Pelem.className; if(cn.indexOf('kmw-disabled') < 0) { // if not already explicitly disabled... Pelem.className = cn ? cn + ' kmw-disabled' : 'kmw-disabled'; } // The rest is triggered within MutationObserver code. // See keymanweb._EnablementMutationObserverCore. } /** * Function enableControl * Scope Public * @param {Element} Pelem Element to be disabled * Description Disables a KMW control element */ keymanweb['enableControl'] = keymanweb.enableControl = function(Pelem) { if(!keymanweb.isAttached(Pelem)) { console.warn("KeymanWeb is not attached to element " + Pelem); } var cn = Pelem.className; var tagIndex = cn.indexOf('kmw-disabled'); if(tagIndex >= 0) { // if already explicitly disabled... Pelem.className = cn.replace('kmw-disabled', '').trim(); } // The rest is triggered within MutationObserver code. // See keymanweb._EnablementMutationObserverCore. } /** * Function _DisableControl * Scope Private * @param {Element} Pelem Element to be disabled * Description Disable KMW control element */ keymanweb._DisableControl = function(Pelem) { if(keymanweb.isAttached(Pelem)) { // Only operate on attached elements! if(keymanweb._LastActiveElement == Pelem || keymanweb._LastActiveElement == Pelem['kmw_ip']) { keymanweb._LastActiveElement = null; keymanweb.setActiveKeyboard(keymanweb.globalKeyboard, keymanweb.globalLanguageCode); keymanweb['osk']._Hide(); } if(device.touchable) { keymanweb.disableTouchElement(Pelem); keymanweb.setupNonKMWTouchElement(Pelem); // If a touch alias was removed, chances are it's gonna mess up our touch-based layout scheme, so let's update the touch elements. window.setTimeout(function() { keymanweb.listInputs(); for(var k = 0; k < keymanweb.sortedInputs.length; k++) { if(keymanweb.sortedInputs[k]['kmw_ip']) { keymanweb.updateInput(keymanweb.sortedInputs[k]['kmw_ip']); } } }, 1); } else { keymanweb.listInputs(); // Fix up our internal input ordering scheme. } keymanweb.disableInputElement(Pelem); } } /** * Function _EnableControl * Scope Private * @param {Element} Pelem Element to be enabled * Description Enable KMW control element */ keymanweb._EnableControl = function(Pelem) { if(keymanweb.isAttached(Pelem)) { // Only operate on attached elements! if(device.touchable) { keymanweb.enableTouchElement(Pelem); // If we just added a new input alias, some languages will mess up our touch-based layout scheme // if we don't update the touch elements. window.setTimeout(function() { keymanweb.listInputs(); for(var k = 0; k < keymanweb.sortedInputs.length; k++) { if(keymanweb.sortedInputs[k]['kmw_ip']) { keymanweb.updateInput(keymanweb.sortedInputs[k]['kmw_ip']); } } }, 1); } else { keymanweb.enableInputElement(Pelem); } } } /** * Function setKeyboardForControl * Scope Public * @param {Element} Pelem Control element * @param {string|null=} Pkbd Keyboard (Clears the set keyboard if set to null.) * @param {string|null=} Plc Language Code * Description Set default keyboard for the control */ keymanweb['setKeyboardForControl'] = keymanweb.setKeyboardForControl = function(Pelem, Pkbd, Plc) { /* pass null for kbd to specify no default, or '' to specify the default system keyboard. */ if(Pkbd !== null && Pkbd !== undefined) { var index = Pkbd.indexOf("Keyboard_"); if(index < 0 && Pkbd != '') { Pkbd = "Keyboard_" + Pkbd; } } else { Plc = null; } if(Pelem.tagName.toLowerCase() == "iframe") { console.warn("'keymanweb.setKeyboardForControl' cannot set keyboard on iframes."); return; } if(!keymanweb.isAttached(Pelem)) { console.error("KeymanWeb is not attached to element " + Pelem); return; } else { Pelem._kmwAttachment.keyboard = Pkbd; Pelem._kmwAttachment.languageCode = Plc; // If Pelem is the focused element/active control, we should set the keyboard in place now. // 'kmw_ip' is the touch-alias for the original page's control. if((keymanweb._LastActiveElement == Pelem || keymanweb._LastActiveElement == Pelem['kmw_ip']) && keymanweb._LastActiveElement) { if(Pkbd != null && Plc != null) { // Second part necessary for Closure. keymanweb.setActiveKeyboard(Pkbd, Plc); } } } } /** * Function getKeyboardForControl * Scope Public * @param {Element} Pelem Control element * @return {string|null} The independently-managed keyboard for the control. * Description Returns the keyboard ID of the current independently-managed keyboard for this control. * If it is currently following the global keyboard setting, returns null instead. */ keymanweb['getKeyboardForControl'] = keymanweb.getKeyboardForControl = function(Pelem) { if(!keymanweb.isAttached(Pelem)) { console.error("KeymanWeb is not attached to element " + Pelem); return null; } else { return Pelem._kmwAttachment.keyboard; // Should we have a version for the language code, too? } } /** * Set focus to last active target element (browser-dependent) */ keymanweb['focusLastActiveElement'] = keymanweb._FocusLastActiveElement = function() { if(!keymanweb._LastActiveElement) return; keymanweb._JustActivatedKeymanWebUI = 1; if(keymanweb._IsMozillaEditableIframe(keymanweb._LastActiveElement,0)) keymanweb._LastActiveElement.defaultView.focus(); // I3363 (Build 301) else if(keymanweb._LastActiveElement.focus) keymanweb._LastActiveElement.focus(); } /** * Get the last active target element *before* KMW activated (I1297) * * @return {Object} */ keymanweb['getLastActiveElement'] = keymanweb._GetLastActiveElement = function() { return keymanweb._LastActiveElement; } /** * Set the active input element directly optionally setting focus * * @param {Object|string} e element id or element * @param {boolean=} setFocus optionally set focus (KMEW-123) **/ keymanweb['setActiveElement']=keymanweb.setActiveElement=function(e,setFocus) { if(typeof(e) == 'string') e=document.getElementById(e); keymanweb._ActiveElement=keymanweb._LastActiveElement=e; // Allow external focusing KMEW-123 if(arguments.length > 1 && setFocus) { if(device.touchable) keymanweb.setFocus(); else keymanweb['focusLastActiveElement'](); } } /** * Function getUIState * Scope Public * @return {Object.} * Description Return object with activation state of UI: * activationPending (bool): KMW being activated * activated (bool): KMW active */ keymanweb['getUIState'] = keymanweb.getUIState = function() { var p={}; p['activationPending'] = p.activationPending = keymanweb._IsActivatingKeymanWebUI; p['activated'] = p.activated = keymanweb._JustActivatedKeymanWebUI; return p; } /** * Set or clear the IsActivatingKeymanWebUI flag (exposed function) * * @param {(boolean|number)} state Activate (true,false) */ keymanweb['activatingUI'] = function(state) { keymanweb._IsActivatingKeymanWebUI = (state ? 1 : 0); } //TODO: add more complete description of what ControlFocus really does /** * Respond to KeymanWeb-aware input element receiving focus * * @param {Event} e Event object * @return {boolean} always true (?) */ keymanweb._ControlFocus = function(e) { var Ltarg, Ln; if(!keymanweb._Enabled) return true; e = keymanweb._GetEventObject(e); // I2404 - Manage IE events in IFRAMEs if(!e) return true; if (e.target) Ltarg = e.target; else if (e.srcElement) Ltarg = e.srcElement; else return true; // Prevent any action if a protected input field if(device.touchable && (Ltarg.className == null || Ltarg.className.indexOf('keymanweb-input') < 0)) return true; // Or if not a remappable input field var en=Ltarg.nodeName.toLowerCase(); if(en == 'input') { var et=Ltarg.type.toLowerCase(); if(!(et == 'text' || et == 'search')) return true; } else if((device.touchable || !Ltarg.isContentEditable) && en != 'textarea') { return true; } keymanweb._ActiveElement=Ltarg; // I3363 (Build 301) if (Ltarg.nodeType == 3) // defeat Safari bug Ltarg = Ltarg.parentNode; var LfocusTarg = Ltarg; // Ensure that focussed element is visible above the keyboard if(device.touchable && (Ltarg.className == null || Ltarg.className.indexOf('keymanweb-input') < 0)) keymanweb.scrollBody(Ltarg); if(Ltarg.tagName=='IFRAME') //**TODO: check case reference { keymanweb._AttachToIframe(Ltarg); Ltarg=Ltarg.contentWindow.document; } //??keymanweb._Selection = null; // We condition on 'priorElement' below as a check to allow KMW to set a default active keyboard. var priorElement = keymanweb._LastActiveElement; keymanweb._LastActiveElement = Ltarg; var keyboardID = keymanweb._ActiveKeyboard == null ? '' : keymanweb._ActiveKeyboard['KI']; if(keymanweb._LastActiveElement._kmwAttachment.keyboard != null) { if(!keymanweb._JustActivatedKeymanWebUI) { keymanweb.setActiveKeyboard(keymanweb._LastActiveElement._kmwAttachment.keyboard, keymanweb._LastActiveElement._kmwAttachment.languageCode); } else { keymanweb._LastActiveElement._kmwAttachment.keyboard = keyboardID; keymanweb._LastActiveElement._kmwAttachment.languageCode = keymanweb.getActiveLanguage(); } } else if(priorElement) { // Avoids complications with initialization and recent detachments. if(!keymanweb._JustActivatedKeymanWebUI) { keymanweb.setActiveKeyboard(keymanweb.globalKeyboard, keymanweb.globalLanguageCode); } else { keymanweb.globalKeyboard = keyboardID; keymanweb.globalLanguageCode = keymanweb.getActiveLanguage(); } } //TODO: the logic of the following line doesn't look right!! Both variables are true, but that doesn't make sense! //_Debug(keymanweb._IsIEEditableIframe(Ltarg,1) + '...' +keymanweb._IsMozillaEditableIframe(Ltarg,1)); if(!keymanweb._IsIEEditableIframe(Ltarg,1) || !keymanweb._IsMozillaEditableIframe(Ltarg,1)) { keymanweb._DisableInput = 1; return true; } keymanweb._DisableInput = 0; if(!keymanweb._JustActivatedKeymanWebUI) { keymanweb._DeadKeys = []; keymanweb._NotifyKeyboard(0,Ltarg,1); // I2187 } if(!keymanweb._JustActivatedKeymanWebUI && keymanweb._SelectionControl != Ltarg) keymanweb._IsActivatingKeymanWebUI = 0; keymanweb._JustActivatedKeymanWebUI = 0; keymanweb._SelectionControl = Ltarg; Ltarg._KeymanWebSelectionStart = Ltarg._KeymanWebSelectionEnd = null; // I3363 (Build 301) // Set element directionality (but only if element is empty) keymanweb._SetTargDir(Ltarg); //Execute external (UI) code needed on focus if required keymanweb.doControlFocused(LfocusTarg,keymanweb._LastActiveElement); // Force display of OSK for touch input device, or if a CJK keyboard, to ensure visibility of pick list if(device.touchable) { osk._Enabled=1; } else { // Conditionally show the OSK when control receives the focus if(osk.ready) { if(keymanweb.isCJK()) osk._Enabled=1; if(osk._Enabled) osk._Show(); else osk._Hide(false); } } // TODO: This is possibly the main sequencing issue, as ControlFocus may get called before other elements // are ready, or keyboards downloaded. Need some way to ensure that the OSK is displayed when everything is ready. // We have slightly different focus management on IE to other browsers. This is because // IE has problems with loss of focus when clicking on another element. This can probably // be resolved in the future by using MouseDown and MouseUp events instead of Click events // on the VK elements but for now we just use the smoother interaction on Firefox and Chrome, // and let IE do the focus dance. This means some careful management of the // IsActivatingKeymanWebUI and LastActiveElement variables, so be careful with any changes // to these. // if(keymanweb._IE) keymanweb._LastActiveElement = null; // I2498 - KeymanWeb OSK does not accept clicks in FF when using automatic UI return true; } /** * Function _IsIEEditableIframe * Scope Private * @param {Object} Pelem Iframe element * {boolean|number} PtestOn 1 to test if frame content is editable (TODO: unclear exactly what this is doing!) * @return {boolean} * Description Test if element is an IE editable IFrame */ keymanweb._IsIEEditableIframe = function(Pelem,PtestOn) { var Ldv, Lvalid = Pelem && (Ldv=Pelem.tagName) && Ldv.toLowerCase() == 'body' && (Ldv=Pelem.ownerDocument) && Ldv.parentWindow; return (!PtestOn && Lvalid) || (PtestOn && (!Lvalid || Pelem.isContentEditable)); } /** * Function _IsMozillaEditableIframe * Scope Private * @param {Object} Pelem Iframe element * @param {boolean|number} PtestOn 1 to test if 'designMode' is 'ON' * @return {boolean} * Description Test if element is a Mozilla editable IFrame */ keymanweb._IsMozillaEditableIframe = function(Pelem,PtestOn) { var Ldv, Lvalid = Pelem && (Ldv=Pelem.defaultView) && Ldv.frameElement; return (!PtestOn && Lvalid) || (PtestOn && (!Lvalid || Ldv.document.designMode.toLowerCase()=='on')); } /** * Respond to KMW losing focus on event * * @param {Event} e Event object * @return {boolean} Always true (?) */ keymanweb._ControlBlur = function(e) { var Ltarg; if(!keymanweb._Enabled) return true; e = keymanweb._GetEventObject(e); // I2404 - Manage IE events in IFRAMEs if(!e) return true; if (e.target) Ltarg = e.target; else if (e.srcElement) Ltarg = e.srcElement; else return true; keymanweb._ActiveElement = null; // I3363 (Build 301) // Hide the touch device input caret, if applicable I3363 (Build 301) if(device.touchable) keymanweb.hideCaret(); if (Ltarg.nodeType == 3) // defeat Safari bug Ltarg = Ltarg.parentNode; if(Ltarg.tagName=='IFRAME') Ltarg=Ltarg.contentWindow.document; if (Ltarg.setSelectionRange) { //Ltarg._KeymanWebSelectionStart = Ltarg.selectionStart; //Ltarg._KeymanWebSelectionEnd = Ltarg.selectionEnd; Ltarg._KeymanWebSelectionStart = Ltarg.value._kmwCodeUnitToCodePoint(Ltarg.selectionStart); //I3319 Ltarg._KeymanWebSelectionEnd = Ltarg.value._kmwCodeUnitToCodePoint(Ltarg.selectionEnd); //I3319 } ////keymanweb._SelectionControl = null; var keyboardID = keymanweb._ActiveKeyboard ? keymanweb._ActiveKeyboard['KI'] : ''; if(keymanweb._LastActiveElement && keymanweb._LastActiveElement._kmwAttachment.keyboard != null) { keymanweb._LastActiveElement._kmwAttachment.keyboard = keyboardID; keymanweb._LastActiveElement._kmwAttachment.languageCode = keymanweb.getActiveLanguage(); } else { keymanweb.globalKeyboard = keyboardID; keymanweb.globalLanguageCode = keymanweb.getActiveLanguage(); } // Now that we've handled all prior-element maintenance, update the 'last active element'. keymanweb._LastActiveElement = Ltarg; /* If the KeymanWeb UI is active as a user changes controls, all UI-based effects should be restrained to this control in case * the user is manually specifying languages on a per-control basis. */ keymanweb._JustActivatedKeymanWebUI = 0; if(!keymanweb._IsActivatingKeymanWebUI) keymanweb._NotifyKeyboard(0,Ltarg,0); // I2187 e = keymanweb._GetEventObject(e); // I2404 - Manage IE events in IFRAMEs //TODO: is this really needed again???? keymanweb.doControlBlurred(Ltarg,e,keymanweb._IsActivatingKeymanWebUI); // Hide the OSK when the control is blurred, unless the UI is being temporarily selected if(osk.ready && !keymanweb._IsActivatingKeymanWebUI) osk._Hide(false); return true; } /**************************************************************** * * Provide for external processing on events * ***************************************************************/ /** * Function doControlBlurred * Scope Private * @param {Object} _target element losing focus * @param {Event} _event event object * @param {(boolean|number)} _isActivating activation state * @return {boolean} * Description Execute external (UI) code needed on blur */ keymanweb.doControlBlurred = function(_target,_event,_isActivating) { var p={}; p['target']=_target; p['event']=_event; p['isActivating']=_isActivating; return util.callEvent('kmw.controlblurred',p); } /** * Function doControlFocused * Scope Private * @param {Object} _target element gaining focus * @param {Object} _activeControl currently active control * @return {boolean} * Description Execute external (UI) code needed on focus */ keymanweb.doControlFocused = function(_target,_activeControl) { var p={}; p['target']=_target; p['activeControl']=_activeControl; return util.callEvent('kmw.controlfocused',p); } /** * Execute external (UI) code needed on registering keyboard, used * to update each UIs language menu * * Note that the argument object is not at present used by any UI, * since the menu is always fully recreated when needed, but the arguments * remain defined to allow for possible use in future (Aug 2014) * * @param {string} _internalName * @param {string} _language * @param {string} _keyboardName * @param {string} _languageCode * @return {boolean} */ keymanweb.doKeyboardRegistered = function(_internalName,_language,_keyboardName,_languageCode) { var p={'internalName':_internalName,'language':_language,'keyboardName':_keyboardName,'languageCode':_languageCode}; return util.callEvent('kmw.keyboardregistered',p); } /** * Execute external (UI) code to rebuild menu when deregistering keyboard * * @return {boolean} */ keymanweb.doKeyboardUnregistered = function() { var p={}; return util.callEvent('kmw.keyboardregistered',p); } /** * Execute external (UI) code needed on loading keyboard * * @param {string} _internalName * @return {boolean} */ keymanweb.doKeyboardLoaded = function(_internalName) { var p={}; p['keyboardName']=_internalName; return util.callEvent('kmw.keyboardloaded',p); } /** * Function doBeforeKeyboardChange * Scope Private * @param {string} _internalName * @param {string} _languageCode * @return {boolean} * Description Execute external (UI) code needed before changing keyboard */ keymanweb.doBeforeKeyboardChange = function(_internalName,_languageCode) { var p={}; p['internalName']=_internalName; p['languageCode']=_languageCode; return util.callEvent('kmw.beforekeyboardchange',p); } /** * Execute external (UI) code needed *after* changing keyboard * * @param {string} _internalName * @param {string} _languageCode * @param {boolean=} _indirect * @return {boolean} */ keymanweb.doKeyboardChange = function(_internalName,_languageCode,_indirect) { var p={}; p['internalName']=_internalName; p['languageCode']=_languageCode; p['indirect']=(arguments.length > 2 ? _indirect : false); return util.callEvent('kmw.keyboardchange',p); } /** * Function doUnloadOSK * Scope Private * @return {boolean} * Description Execute external (UI) code if any needed after unloading OSK (probably not required) */ keymanweb.doUnloadOSK = function() { var p={}; return util.callEvent('kmw.unloadosk',p); } /** * Function doLoadUI * Scope Private * @return {boolean} * Description Execute UI initialization code after loading the UI */ keymanweb.doLoadUI = function() { var p={}; return util.callEvent('kmw.loaduserinterface',p); } /** * Function doUnloadUI * Scope Private * @return {boolean} * Description Execute UI cleanup code before unloading the UI (may not be required?) */ keymanweb.doUnloadUI = function() { var p={}; return util.callEvent('kmw.unloaduserinterface',p); } /***************************************************************************** * * Provide for handling the initial focus event differently * The first focus event can happen before we get the WindowLoad, * e.g. if the page activates a control on WindowLoad itself, * so trap that and run it through to the page * *****************************************************************************/ /** * Function _BubbledFocus * Scope Private * @param {Event} e Event object * Description Respond to KMW receiving bubbled focus on event (TODO: may not be needed, not currently doing anything) */ keymanweb._BubbledFocus = function(e) { /*KeymanWeb._WindowLoad(e);*/ } if (window.addEventListener) window.addEventListener('focus', keymanweb._BubbledFocus, true); /** * Function _GetKeyEventProperties * Scope Private * @param {Event} e Event object * @param {boolean=} keyState true if call results from a keyDown event, false if keyUp, undefined if keyPress * @return {Object.} KMW keyboard event object: * Description Get object with target element, key code, shift state, virtual key state * Ltarg=target element * Lcode=keyCode * Lmodifiers=shiftState * LisVirtualKeyCode e.g. ctrl/alt key * LisVirtualKey e.g. Virtual key or non-keypress event */ keymanweb._GetKeyEventProperties = function(e, keyState) { var s = new Object(); e = keymanweb._GetEventObject(e); // I2404 - Manage IE events in IFRAMEs if(!e) return null; if(e.cancelBubble === true) return null; // I2457 - Facebook meta-event generation mess -- two events generated for a keydown in Facebook contentEditable divs if (e.target) s.Ltarg = e.target; else if (e.srcElement) s.Ltarg = e.srcElement; else return null; if (s.Ltarg.nodeType == 3) // defeat Safari bug s.Ltarg = s.Ltarg.parentNode; if (e.keyCode) s.Lcode = e.keyCode; else if (e.which) s.Lcode = e.which; else return null; // Stage 1 - track the true state of the keyboard's modifiers. var osk = keymanweb['osk'], prevModState = keymanweb.modStateFlags, curModState = 0x0000; var ctrlEvent = false, altEvent = false; switch(s.Lcode) { case osk.keyCodes['K_CTRL']: // The 3 shorter "K_*CTRL" entries exist in some legacy keyboards. case osk.keyCodes['K_LCTRL']: case osk.keyCodes['K_RCTRL']: case osk.keyCodes.K_CONTROL: case osk.keyCodes.K_LCONTROL: case osk.keyCodes.K_RCONTROL: ctrlEvent = true; break; case osk.keyCodes['K_LMENU']: // The 2 "K_*MENU" entries exist in some legacy keyboards. case osk.keyCodes['K_RMENU']: case osk.keyCodes.K_ALT: case osk.keyCodes.K_LALT: case osk.keyCodes.K_RALT: altEvent = true; break; } /** * Two separate conditions exist that should trigger chiral modifier detection. Examples below use CTRL but also work for ALT. * * 1. The user literally just pressed CTRL, so the event has a valid `location` property we can utilize. * Problem: its layer isn't presently activated within the OSK. * * 2. CTRL has been held a while, so the OSK layer is valid, but the key event doesn't tell us the chirality of the active CTRL press. * Bonus issue: RAlt simulation may cause erasure of this location property, but it should ONLY be empty if pressed in this case. * We default to the 'left' variants since they're more likely to exist and cause less issues with RAlt simulation handling. * * In either case, `e.ctrlKey` is set to true, but as a result does nothing to tell us which case is active. * * `e.location != 0` if true matches condition 1 and matches condition 2 if false. */ curModState |= (e.shiftKey ? 0x10 : 0); if(e.ctrlKey) { curModState |= ((e.location != 0 && ctrlEvent) ? (e.location == 1 ? osk.modifierCodes.LCTRL : osk.modifierCodes.RCTRL) : // Condition 1 prevModState & 0x0003); // Condition 2 } if(e.altKey) { curModState |= ((e.location != 0 && altEvent) ? (e.location == 1 ? osk.modifierCodes.LALT : osk.modifierCodes.RALT) : // Condition 1 prevModState & 0x000C); // Condition 2 } // Stage 2 - detect state key information. It can be looked up per keypress with no issue. s.Lstates = 0; if(e.getModifierState("CapsLock")) { s.Lstates = osk.modifierCodes.CAPS; } else { s.Lstates = osk.modifierCodes.NO_CAPS; } if(e.getModifierState("NumLock")) { s.Lstates |= osk.modifierCodes.NUM_LOCK; } else { s.Lstates |= osk.modifierCodes.NO_NUM_LOCK; } if(e.getModifierState("ScrollLock") || e.getModifierState("Scroll")) { // "Scroll" for IE9. s.Lstates |= osk.modifierCodes.SCROLL_LOCK; } else { s.Lstates |= osk.modifierCodes.NO_SCROLL_LOCK; } // We need these states to be tracked as well for proper OSK updates. curModState |= s.Lstates; // Stage 3 - Set our modifier state tracking variable and perform basic AltGr-related management. s.LmodifierChange = keymanweb.modStateFlags != curModState; keymanweb.modStateFlags = curModState; // For European keyboards, not all browsers properly send both key-up events for the AltGr combo. var altGrMask = osk.modifierCodes.RALT | osk.modifierCodes.LCTRL; if((prevModState & altGrMask) == altGrMask && (curModState & altGrMask) != altGrMask) { // We just released AltGr - make sure it's all released. curModState &= ~ altGrMask; } // Perform basic filtering for Windows-based ALT_GR emulation on European keyboards. if(curModState & osk.modifierCodes.RALT) { curModState &= ~osk.modifierCodes.LCTRL; } // Stage 4 - map the modifier set to the appropriate keystroke's modifiers. if(keymanweb.isChiral()) { s.Lmodifiers = curModState & osk.modifierBitmasks.CHIRAL; // Note for future - embedding a kill switch here or in keymanweb.osk.emulatesAltGr would facilitate disabling // AltGr / Right-alt simulation. if(osk.emulatesAltGr() && (s.Lmodifiers & osk.modifierBitmasks.ALT_GR_SIM) == osk.modifierBitmasks.ALT_GR_SIM) { s.Lmodifiers ^= osk.modifierBitmasks.ALT_GR_SIM; s.Lmodifiers |= osk.modifierCodes.RALT; } } else { // No need to sim AltGr here; we don't need chiral ALTs. s.Lmodifiers = (e.shiftKey ? 0x10 : 0) | ((curModState & (osk.modifierCodes.LCTRL | osk.modifierCodes.RCTRL)) ? 0x20 : 0) | ((curModState & (osk.modifierCodes.LALT | osk.modifierCodes.RALT)) ? 0x40 : 0); } // The 0x6F used to be 0x60 - this adjustment now includes the chiral alt and ctrl modifiers in that check. s.LisVirtualKeyCode = (typeof e.charCode != 'undefined' && e.charCode != null && (e.charCode == 0 || (s.Lmodifiers & 0x6F) != 0)); s.LisVirtualKey = s.LisVirtualKeyCode || e.type != 'keypress'; return s; } /** * Function _SelectionChange * Scope Private * @return {boolean} * Description Respond to selection change event */ keymanweb._SelectionChange = function() { if(keymanweb._IgnoreNextSelChange) { keymanweb._IgnoreNextSelChange--; } else { var Ls=document.selection; if(Ls.type.toLowerCase()!='control') // && document.selection.createRange().parentElement() == keymanweb._SelectionControl) // && window.event.srcElement == keymanweb._SelectionControl) { var Lrange=Ls.createRange(); if(!keymanweb._Selection || !keymanweb._Selection.isEqual(Lrange)) { keymanweb._Selection = Lrange; /* Delete deadkeys for IE when certain keys pressed */ keymanweb._DeadKeys = []; } } } return true; } // TODO: find out if _FindCaret is still needed for current FireFox, delete if not /** * Function _FindCaret * Scope Private * @param {Object} Pelem element * Description Work around a problem with scrolling text boxes and input boxes in Firefox, not needed for other browsers */ keymanweb._FindCaret = function(Pelem) // I779 { if(!Pelem.createTextRange && Pelem.selectionStart) { var Levent=document.createEvent('KeyboardEvent'); if(Levent.initKeyEvent) { Levent.initKeyEvent('keypress',true,true,null,false,false,false,false,0,32); Levent._kmw_block = true; // Tell keymanweb._KeyPress to ignore this simulated event. // Nasty problems arise from the simulated keystroke if we don't temporarily block the handler. Pelem.dispatchEvent(Levent); Levent=document.createEvent('KeyboardEvent'); Levent.initKeyEvent('keypress',true,true,null,false,false,false,false,8,0); Levent._kmw_block = true; // Tell keymanweb._KeyPress to ignore this simulated event. Pelem.dispatchEvent(Levent); } } } /** * Function _NotifyKeyboard * Scope Private * @param {number} _PCommand event code (16,17,18) or 0 * @param {Object} _PTarget target element * @param {number} _PData 1 or 0 * Description Notifies keyboard of keystroke or other event */ keymanweb._NotifyKeyboard = function(_PCommand,_PTarget,_PData) // I2187 { if(keymanweb._ActiveKeyboard != null && typeof(keymanweb._ActiveKeyboard['KNS']) == 'function') keymanweb._ActiveKeyboard['KNS'](_PCommand,_PTarget,_PData); } /** * Function _KeyDown * Scope Private * @param {Event} e event * @return {boolean} * Description Processes keydown event and passes data to keyboard. */ keymanweb._KeyDown = function(e) { var Ldv,eClass=''; keymanweb._KeyPressToSwallow = 0; if(!keymanweb._Enabled || keymanweb._DisableInput || keymanweb._ActiveKeyboard == null) { return true; } // Prevent mapping element is readonly or tagged as kmw-disabled var el=util.eventTarget(e); if(device.touchable) { if(el && typeof el.kmwInput != 'undefined' && el.kmwInput == false) { return true; } } else if(el && el.className.indexOf('kmw-disabled') >= 0) { return true; } // Or if OSK not yet ready (for any reason) if(!osk.ready) { return true; } // Get event properties var Levent = keymanweb._GetKeyEventProperties(e, true); if(Levent == null) { return true; } switch(Levent.Lcode) { case 8: keymanweb._DeadKeys = []; break; // I3318 (always clear deadkeys after backspace) case 16: //"K_SHIFT":16,"K_CONTROL":17,"K_ALT":18 case 17: case 18: case 20: //"K_CAPS":20, "K_NUMLOCK":144,"K_SCROLL":145 case 144: case 145: // For eventual integration - we bypass an OSK update for physical keystrokes when in touch mode. keymanweb._NotifyKeyboard(Levent.Lcode,Levent.Ltarg,1); if(!device.touchable) { return osk._UpdateVKShift(Levent, Levent.Lcode-15, 1); // I2187 } else { return true; } } if(Levent.LmodifierChange) { keymanweb._NotifyKeyboard(0,Levent.Ltarg,1); osk._UpdateVKShift(Levent, 0, 1); } // I1207 if((Ldv=Levent.Ltarg.ownerDocument) && (Ldv=Ldv.selection) && (Levent.Lcode<33 || Levent.Lcode>40)) { Ldv.createRange().select(); } if(!window.event) { // I1466 - Convert the - keycode on mnemonic as well as positional layouts // FireFox, Mozilla Suite if(keymanweb._VKMap_FF_IE['k'+Levent.Lcode]) { Levent.Lcode=keymanweb._VKMap_FF_IE['k'+Levent.Lcode]; } } //else //{ // Safari, IE, Opera? //} if(!keymanweb._ActiveKeyboard['KM']) { // Positional Layout var LeventMatched=0; /* 13/03/2007 MCD: Swedish: Start mapping of keystroke to US keyboard */ var Lbase=keymanweb._VKMap[osk._BaseLayout]; if(Lbase && Lbase['k'+Levent.Lcode]) { Levent.Lcode=Lbase['k'+Levent.Lcode]; } /* 13/03/2007 MCD: Swedish: End mapping of keystroke to US keyboard */ if(typeof(keymanweb._ActiveKeyboard['KM'])=='undefined' && !(Levent.Lmodifiers & 0x60)) { // Support version 1.0 KeymanWeb keyboards that do not define positional vs mnemonic var Levent2={Lcode:keymanweb._USKeyCodeToCharCode(Levent),Ltarg:Levent.Ltarg,Lmodifiers:0,LisVirtualKey:0}; if(keymanweb.callKeyboardStartGroup(Levent2.Ltarg,Levent2)) { LeventMatched=1; } } LeventMatched = LeventMatched || keymanweb.callKeyboardStartGroup(Levent.Ltarg,Levent); // Support backspace in simulated input DIV from physical keyboard where not matched in rule I3363 (Build 301) if(Levent.Lcode == 8 && !LeventMatched && Levent.Ltarg.className != null && Levent.Ltarg.className.indexOf('keymanweb-input') >= 0) { keymanweb.KO(1,keymanweb._LastActiveElement,""); } } else { // Mnemonic layout if(Levent.Lcode == 8) { // I1595 - Backspace for mnemonic keymanweb._KeyPressToSwallow = 1; if(!keymanweb.callKeyboardStartGroup(Levent.Ltarg,Levent)) { keymanweb.KO(1,keymanweb._LastActiveElement,""); // I3363 (Build 301) } return false; //added 16/3/13 to fix double backspace on mnemonic layouts on desktop } else { keymanweb._KeyPressToSwallow = 0; } } if(!LeventMatched && Levent.Lcode >= 96 && Levent.Lcode <= 111) { // Number pad, numlock on // _Debug('KeyPress NumPad code='+Levent.Lcode+'; Ltarg='+Levent.Ltarg.tagName+'; LisVirtualKey='+Levent.LisVirtualKey+'; _KeyPressToSwallow='+keymanweb._KeyPressToSwallow+'; keyCode='+(e?e.keyCode:'nothing')); if(Levent.Lcode < 106) { var Lch = Levent.Lcode-48; } else { Lch = Levent.Lcode-64; } keymanweb.KO(0, Levent.Ltarg, String._kmwFromCharCode(Lch)); //I3319 LeventMatched = 1; } if(LeventMatched) { keymanweb._FindCaret(Levent.Ltarg); //I779 if(e && e.preventDefault) { e.preventDefault(); e.stopPropagation(); } keymanweb._KeyPressToSwallow = (e?e.keyCode:0); return false; } else { keymanweb._KeyPressToSwallow = 0; } if(Levent.Lcode == 8) { /* Backspace - delete deadkeys, also special rule if desired? */ // This is needed to prevent jumping to previous page, but why??? // I3363 (Build 301) if(Levent.Ltarg.className != null && Levent.Ltarg.className.indexOf('keymanweb-input') >= 0) { return false; } } return true; } keymanweb.callKeyboardStartGroup = function(Ltarg, Levent) { keymanweb._CachedSelectionStart = null; // I3319 keymanweb._DeadkeyResetMatched(); // I3318 keymanweb.cachedContext.reset(); return keymanweb._ActiveKeyboard['gs'](Ltarg, Levent); } /** * Function _KeyPress * Scope Private * @param {Event} e event * @return {boolean} * Description Processes keypress event (does not pass data to keyboard) */ keymanweb._KeyPress = function(e) { if(e._kmw_block) { // A custom event property added for bugfix-oriented simulated keypress events. return false; } var Levent; if(!keymanweb._Enabled || keymanweb._DisableInput || keymanweb._ActiveKeyboard == null) { return true; } Levent = keymanweb._GetKeyEventProperties(e); if(Levent == null || Levent.LisVirtualKey) { return true; } // _Debug('KeyPress code='+Levent.Lcode+'; Ltarg='+Levent.Ltarg.tagName+'; LisVirtualKey='+Levent.LisVirtualKey+'; _KeyPressToSwallow='+keymanweb._KeyPressToSwallow+'; keyCode='+(e?e.keyCode:'nothing')); /* I732 START - 13/03/2007 MCD: Swedish: Start positional keyboard layout code: prevent keystroke */ if(!keymanweb._ActiveKeyboard['KM']) { if(!keymanweb._KeyPressToSwallow) { return true; } if(Levent.Lcode < 0x20 || (keymanweb._BrowserIsSafari && (Levent.Lcode > 0xF700 && Levent.Lcode < 0xF900))) { return true; } e = keymanweb._GetEventObject(e); // I2404 - Manage IE events in IFRAMEs if(e) { e.returnValue = false; } return false; } /* I732 END - 13/03/2007 MCD: Swedish: End positional keyboard layout code */ if(keymanweb._KeyPressToSwallow || keymanweb.callKeyboardStartGroup(Levent.Ltarg,Levent)) { keymanweb._KeyPressToSwallow=0; if(e && e.preventDefault) { e.preventDefault(); e.stopPropagation(); } keymanweb._FindCaret(Levent.Ltarg); // I779 return false; } keymanweb._KeyPressToSwallow=0; return true; } /** * Function _KeyUp * Scope Private * @param {Event} e event * @return {boolean} * Description Processes keyup event and passes event data to keyboard */ keymanweb._KeyUp = function(e) { var Levent = keymanweb._GetKeyEventProperties(e, false); if(Levent == null || !osk.ready) return true; switch(Levent.Lcode) { case 13: if(Levent.Ltarg.nodeName == 'TEXTAREA') break; // For input fields, move to next input element if(Levent.Ltarg.type == 'search' || Levent.Ltarg.type == 'submit') Levent.Ltarg.form.submit(); else keymanweb.moveToNext(false); return true; case 16: //"K_SHIFT":16,"K_CONTROL":17,"K_ALT":18 case 17: case 18: case 20: //"K_CAPS":20, "K_NUMLOCK":144,"K_SCROLL":145 case 144: case 145: keymanweb._NotifyKeyboard(Levent.Lcode,Levent.Ltarg,0); return osk._UpdateVKShift(Levent, Levent.Lcode-15, 1); // I2187 } if(Levent.LmodifierChange){ keymanweb._NotifyKeyboard(0,Levent.Ltarg,0); osk._UpdateVKShift(Levent, 0, 1); // I2187 } // I736 start var Ldv; if((Ldv=Levent.Ltarg.ownerDocument) && (Ldv=Ldv.selection) && Ldv.type != 'control') // I1479 - avoid createRange on controls { Ldv=Ldv.createRange(); //if(Ldv.parentElement()==Levent.Ltarg) //I1505 keymanweb._Selection = Ldv; } // I736 end //if(document.selection && document.selection.type=='Text') keymanweb._Selection=document.selection.createRange(); //if(!KeymanWeb._Enabled) return true; //if (!e) e = window.event; return false; } //TODO: check return of _KeyUp - what happens if returning true or false ?? what if null returned? /** * Move focus to next (or previous) input or text area element on TAB * Uses list of actual input elements * * Note that _ActiveElement on touch devices returns the DIV that overlays * the input element, not the element itself. * * @param {number|boolean} bBack Direction to move (0 or 1) */ keymanweb.moveToNext=function(bBack) { var i,t=keymanweb.sortedInputs, activeBase=keymanweb._ActiveElement; if(t.length == 0) return; // For touchable devices, get the base element of the DIV if(device.touchable) activeBase=activeBase.base; // Identify the active element in the list of inputs ordered by position for(i=0; i= t.length) i=i-t.length; if(i < 0) i=i+t.length; // Move to the selected element if(device.touchable) { // Set focusing flag to prevent OSK disappearing keymanweb.focusing=true; var target=t[i]['kmw_ip']; // Focus if next element is non-mapped if(typeof(target) == 'undefined') { t[i].focus(); } // Or reposition the caret on the input DIV if mapped else { keymanweb._ActiveElement=keymanweb._LastActiveElement=target; keymanweb.setTextCaret(target,10000); keymanweb.scrollInput(target); } } // Behaviour for desktop browsers else { t[i].focus(); } } /** * Move focus to user-specified element * * @param {string|Object} e element or element id * **/ keymanweb['moveToElement'] = function(e) { var i; if(typeof(e) == 'string') e=document.getElementById(e); if(device.touchable && e['kmw_ip']) e['kmw_ip'].focus(); else e.focus(); } /** * Change active keyboard to keyboard selected by (internal) name and language code * * Test if selected keyboard already loaded, and simply update active stub if so. * Otherwise, insert a script to download and insert the keyboard from the repository * or user-indicated file location. * * @param {string} PInternalName * @param {string=} PLgCode * @param {boolean=} saveCookie */ keymanweb._SetActiveKeyboard = function(PInternalName,PLgCode,saveCookie) { var n,Ln; // Set default language code if(arguments.length < 2 || (!PLgCode)) { PLgCode='---'; } // Check that the saved keyboard is currently registered for(n=0; n= keymanweb._KeyboardStubs.length)) { PInternalName=keymanweb._KeyboardStubs[0]['KI']; PLgCode=keymanweb._KeyboardStubs[0]['KLC']; } // Save name of keyboard (with language code) as a cookie if(arguments.length > 2 && saveCookie) keymanweb.saveCurrentKeyboard(PInternalName,PLgCode); // Check if requested keyboard and stub are currently active if(keymanweb._ActiveStub && keymanweb._ActiveKeyboard && keymanweb._ActiveKeyboard['KI'] == PInternalName && keymanweb._ActiveStub['KI'] == PInternalName //this part of test should not be necessary, but keep anyway && keymanweb._ActiveStub['KLC'] == PLgCode && !keymanweb.mustReloadKeyboard ) return; // Check if current keyboard matches requested keyboard, but not stub if(keymanweb._ActiveKeyboard && (keymanweb._ActiveKeyboard['KI'] == PInternalName)) { // If so, simply update the active stub for(Ln=0; Ln 0) { var Ps = keymanweb._KeyboardStubs[0]; keymanweb._SetActiveKeyboard(Ps['KI'], Ps['KLC'], true); } } loadingStub.asyncLoader.timer = window.setTimeout(loadingStub.asyncLoader.callback, 10000); } //Display the loading delay bar (Note: only append 'keyboard' if not included in name.) if(!keymanweb.isEmbedded) { util.wait('Installing keyboard
' + kbdName); } // Installing the script immediately does not work reliably if two keyboards are // loaded in succession if there is any delay in downloading the script. // It works much more reliably if deferred (KMEW-101, build 356) // The effect of a delay can also be tested, for example, by setting the timeout to 5000 window.setTimeout(function(){keymanweb.installKeyboard(loadingStub);},0); } keymanweb._ActiveStub=keymanweb._KeyboardStubs[Ln]; return; } } keymanweb._SetTargDir(keymanweb._LastActiveElement); // I2077 - LTR/RTL timing } var Pk=keymanweb._ActiveKeyboard; // I3319 if(Pk !== null) // I3363 (Build 301) String.kmwEnableSupplementaryPlane(Pk && ((Pk['KS'] && (Pk['KS'] == 1)) || (Pk['KN'] == 'Hieroglyphic'))); // I3319 // Initialize the OSK (provided that the base code has been loaded) osk._Load(); } /** * Install a keyboard script that has been downloaded from a keyboard server * * @param {Object} kbdStub keyboard stub to be loaded. * **/ keymanweb.installKeyboard = function(kbdStub) { var Lscript = util._CreateElement('SCRIPT'); Lscript.charset="UTF-8"; // KMEW-89 Lscript.type = 'text/javascript'; var kbdFile = kbdStub['KF']; var kbdLang = kbdStub['KL']; var kbdName = kbdStub['KN']; // Add a handler for cases where the new