/*** KeymanWeb 2.0 Copyright 2014 Tavultesoft Pty Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ***/ /****************************************************************** * 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); } } // TODO: The following is copied from old control focus... not sure how much is needed keymanweb._ActiveControl = null; keymanweb._LastActiveElement = target; for(var Ln=0; Ln < keymanweb._Controls.length; Ln++) // I1511 - array prototype extended if(keymanweb._Controls[Ln].LControl == target) { keymanweb._ActiveControl = keymanweb._Controls[Ln]; break; } if(keymanweb._ActiveControl != null && keymanweb._ActiveControl.LDefaultInternalName != null) { if(!keymanweb._JustActivatedKeymanWebUI) { keymanweb._SetActiveKeyboard(keymanweb._ActiveControl.LDefaultInternalName,'',true); } else keymanweb._ActiveControl.LDefaultInternalName = keymanweb._ActiveKeyboard == null ? '' : keymanweb._ActiveKeyboard['KI']; } //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=0; // 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.base.addEventListener('resize',function(e){keymanweb.updateInput(xx);},false);})(x); // And copy the text content keymanweb.setText(x,x.base.value,null); } } /********************************************************* * * End of main touch-device initialization. * *********************************************************/ /** * Function setupDesktopPage * Scope Private * Description Save list of inputs for non-touch devices (desktop browsers) */ keymanweb.setupDesktopPage = function() { for(var k=0; k<2; k++) { var ipList=document.getElementsByTagName(k==0?'INPUT':'TEXTAREA'); for(var n=0;n 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 /** * 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]; 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) { e = keymanweb._GetLastActiveElement(); if(e) { e = e.ownerDocument; if(e) e = e.parentWindow; if(!e) return null; e = e.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 _IsAttached * Scope Private * @param {Object} Pelem Element to be tested * @return {number} Returns 1 if attached, else 0 * Description Tests whether or not KeymanWeb is attached to element */ keymanweb._IsAttached = function(Pelem) { for(var i = 0; i < keymanweb._AttachedElements.length; i++) if(keymanweb._AttachedElements[i] == Pelem) return 1; return 0; } /** * Function attachToControl * Scope Public * @param {Object} Pelem Element to which KMW will be attached * Description Attaches KMW to control (or IFrame) */ keymanweb['attachToControl'] = keymanweb.attachToControl = function(Pelem) { // Check that the element is neither readonly nor disabled for KeymanWeb var ro=Pelem.attributes['readonly'],cn=Pelem.className; if(typeof ro == 'object' && ro.value != 'false' ) return; if(typeof cn == 'string' && cn.indexOf('kmw-disabled') >= 0) return; if(Pelem.tagName.toLowerCase() == 'iframe') keymanweb._AttachToIframe(Pelem); else { util.attachDOMEvent(Pelem,'focus', keymanweb._ControlFocus); util.attachDOMEvent(Pelem,'blur', keymanweb._ControlBlur); Pelem.onkeypress = keymanweb._KeyPress; Pelem.onkeydown = keymanweb._KeyDown; Pelem.onkeyup = keymanweb._KeyUp; } // I1596 - attach to controls dynamically if(!keymanweb._IsAttached(Pelem)) keymanweb._AttachedElements.push(Pelem); } /** * Function _AttachToIframe * Scope Private * @param {Object} 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 { 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 { keymanweb._AttachToControls(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 Private * @param {Object} Pelem Element to be disabled * Description Disable KMW control element */ keymanweb.DisableControl = function(Pelem) { var Ln, Lc; for(Ln=0; Ln 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(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; keymanweb._ActiveControl = null; keymanweb._LastActiveElement = Ltarg; for(Ln=0; Ln < keymanweb._Controls.length; Ln++) // I1511 - array prototype extended if(keymanweb._Controls[Ln].LControl == Ltarg) { keymanweb._ActiveControl = keymanweb._Controls[Ln]; break; } if(keymanweb._ActiveControl != null && keymanweb._ActiveControl.LDefaultInternalName != null) { if(!keymanweb._JustActivatedKeymanWebUI) { keymanweb._SetActiveKeyboard(keymanweb._ActiveControl.LDefaultInternalName,'',true); } else keymanweb._ActiveControl.LDefaultInternalName = keymanweb._ActiveKeyboard == null ? '' : keymanweb._ActiveKeyboard['KI']; } //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._ActiveControl); // 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; keymanweb._LastActiveElement = Ltarg; if(keymanweb._ActiveControl != null && keymanweb._ActiveControl.LDefaultInternalName != null) if(keymanweb._ActiveKeyboard == null) keymanweb._ActiveControl.LDefaultInternalName = ''; else keymanweb._ActiveControl.LDefaultInternalName = keymanweb._ActiveKeyboard['KI']; keymanweb._ActiveControl = null; 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 laoding 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 * @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) { 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; s.Lmodifiers = (e.shiftKey ? 0x10 : 0) | (e.ctrlKey ? (e.ctrlLeft ? 0x20 : 0x20) : 0) | (e.altKey ? (e.altLeft ? 0x40 : 0x40) : 0); // I3363 (Build 301) //s.LisVirtualKey = (e.charCode != null && (e.charCode == 0 || (s.Lmodifiers & 0x60) != 0)) || e.type != 'keypress'; //s.LisVirtualKeyCode = false; s.LisVirtualKeyCode = (typeof e.charCode != 'undefined' && e.charCode != null && (e.charCode == 0 || (s.Lmodifiers & 0x60) != 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); var LkeyPress = keymanweb._KeyPress; /** * Function _KeyPress * Scope Private * @param {Event} e event * Description Temporarily disable keypress event handling TODO: does this really work??? objects are passed by reference so should be OK */ keymanweb._KeyPress = function(e) {}; Pelem.dispatchEvent(Levent); Levent=document.createEvent('KeyboardEvent'); Levent.initKeyEvent('keypress',true,true,null,false,false,false,false,8,0); Pelem.dispatchEvent(Levent); keymanweb._KeyPress = LkeyPress; } } } /** * 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 || (keymanweb._ActiveControl != null && !keymanweb._ActiveControl.LEnabled)) 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); if(Levent == null) return true; switch(Levent.Lcode) { case 8: keymanweb._DeadKeys = []; break; // I3318 (always clear deadkeys after backspace) case 16: case 17: case 18: keymanweb._NotifyKeyboard(Levent.Lcode,Levent.Ltarg,1); return osk._UpdateVKShift(Levent, Levent.Lcode-15, 1); // I2187 } // 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(); 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) { var Levent; if(!keymanweb._Enabled || keymanweb._DisableInput || keymanweb._ActiveKeyboard == null || (keymanweb._ActiveControl != null && !keymanweb._ActiveControl.LEnabled)) 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(); 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); 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: case 17: case 18: keymanweb._NotifyKeyboard(Levent.Lcode,Levent.Ltarg,0); return osk._UpdateVKShift(Levent, Levent.Lcode-15, 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; // 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,lgCode; // Set default language code if(arguments.length < 2 || (!PLgCode)) lgCode='---'; else lgCode=PLgCode; // Check that the saved keyboard is currently registered for(n=0; n= keymanweb._KeyboardStubs.length)) { PInternalName=keymanweb._KeyboardStubs[0]['KI']; lgCode=keymanweb._KeyboardStubs[0]['KLC']; } // Save name of keyboard (with language code) as a cookie if(arguments.length > 2 && saveCookie) keymanweb.saveCurrentKeyboard(PInternalName,lgCode); // 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) k = k0; if(k) { if(typeof(k['KLC']) != 'undefined') lg = k['KLC']; else if(typeof(k['LanguageCode']) != 'undefined') lg = k['LanguageCode']; } return ((lg == 'cmn') || (lg == 'jpn') || (lg == 'kor')); } keymanweb.isCJK = keymanweb['isCJK']; // I3363 (Build 301) /** * Allow to change active keyboard by (internal) keyboard name * * @param {string} PInternalName Internal name * @param {string} PLgCode Language code */ keymanweb['setActiveKeyboard'] = function(PInternalName,PLgCode) { //TODO: This does not make sense: the callbacks should be in _SetActiveKeyboard, not here, // since this is always called FROM the UI, which should not need notification. // If UI callbacks are needed at all, they should be within _SetActiveKeyboard keymanweb.doBeforeKeyboardChange(PInternalName,PLgCode); keymanweb._SetActiveKeyboard(PInternalName,PLgCode,true); if(keymanweb._LastActiveElement != null) keymanweb._FocusLastActiveElement(); keymanweb.doKeyboardChange(PInternalName,PLgCode); } /** * Function getActiveKeyboard * Scope Public * @return {string} Name of active keyboard * Description Return internal name of currently active keyboard */ keymanweb['getActiveKeyboard'] = function() { if(keymanweb._ActiveKeyboard == null) return ''; return keymanweb._ActiveKeyboard['KI']; } /** * Function getActiveLanguage * Scope Public * @return {string} language code * Description Return language code for currently selected language */ keymanweb['getActiveLanguage'] = function() { if(keymanweb._ActiveStub == null) return ''; return keymanweb._ActiveStub['KLC']; } //TODO: find all references to next three routines and disambiguate!! /** * Get keyboard meta data for the selected keyboard and language * * @param {string} PInternalName Internal name of keyboard * @param {string=} PlgCode language code * @return {Object} Details of named keyboard * **/ keymanweb['getKeyboard'] = function(PInternalName,PlgCode) { var Ln, Lrn; for(Ln=0; Ln 0 && typeof(arg)=='object' && arg != null) { for(var p in opt) { if(arg.hasOwnProperty(p)) opt[p] = arg[p]; } } // Get default root path and device options if(opt['root'] == '') opt['root'] = keymanweb.rootPath; // Use root-relative paths for resources and keyboards unless set by page source if(opt['resources'] == '') opt['resources']=opt['root']; if(opt['keyboards'] == '') opt['keyboards']=opt['root']+'kbd/'; if(opt['fonts'] == '') opt['fonts']=opt['root']+'font/'; */ // Set default device options keymanweb.setDefaultDeviceOptions(opt); // Only do remainder of initialization once! if(keymanweb['initialized']) return; // Do not initialize until the document has been fully loaded if(document.readyState !== 'complete') { window.setTimeout(function(){keymanweb.init(arg);},50); return; } keymanweb._MasterDocument = window.document; // Initialize and protect input elements for touch-screen devices (but never for apps) // NB: now set disabled=true rather than readonly, since readonly does not always // prevent element from getting focus, e.g. within a LABEL element. // c.f. http://kreotekdev.wordpress.com/2007/11/08/disabled-vs-readonly-form-fields/ if(device.touchable) { eTextArea=document.getElementsByTagName("textarea"); eInput=document.getElementsByTagName("input"); for(i=0; i