mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-13 12:19:25 +00:00
2646 lines
97 KiB
TypeScript
2646 lines
97 KiB
TypeScript
// Includes KMW-added property declaration extensions for HTML elements.
|
|
/// <reference path="kmwexthtml.ts" />
|
|
// Includes KMW string extension declarations.
|
|
/// <reference path="kmwstring.ts" />
|
|
|
|
/***
|
|
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['keyman']['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['keyman'],osk=keymanweb['osk'],util=keymanweb['util'], device=util.device;
|
|
var kbdInterface=keymanweb['interface'];
|
|
|
|
/**
|
|
* 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<arguments.length; k++)
|
|
{
|
|
if(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+'<br />'+p.innerHTML;
|
|
|
|
}
|
|
}
|
|
keymanweb.debugElement=null;
|
|
var dbg=keymanweb.debug;
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
|
|
keymanweb.touchAliasing = new TouchHandlers(keymanweb);
|
|
|
|
}
|
|
|
|
/*********************************************************
|
|
*
|
|
* End of main touch-device initialization.
|
|
*
|
|
*********************************************************/
|
|
|
|
keymanweb.touchAliasing = new AliasStubs();
|
|
|
|
/**
|
|
* 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'>('input'),
|
|
ipTextArea=document.getElementsByTagName<'textarea'>('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 {
|
|
var firstInput = ipInput[0];
|
|
var firstTextArea = ipTextArea[0];
|
|
|
|
if(firstInput.offsetTop < firstTextArea.offsetTop) {
|
|
n=1;
|
|
} else if(firstInput.offsetTop > firstTextArea.offsetTop) {
|
|
n=2;
|
|
} else if(firstInput.offsetLeft < firstTextArea.offsetLeft) {
|
|
n=1;
|
|
} else if(firstInput.offsetLeft > firstTextArea.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;
|
|
}
|
|
|
|
// Probably should relocate this definition somewhere.
|
|
keymanweb.timerID = null;
|
|
|
|
/**
|
|
* Function resetContext
|
|
* Scope Public
|
|
* Description Revert OSK to default layer and clear any deadkeys and modifiers
|
|
*/
|
|
keymanweb['resetContext'] = keymanweb.resetContext = function() {
|
|
osk.layerId = 'default';
|
|
|
|
kbdInterface.clearDeadkeys();
|
|
kbdInterface.resetContextCache();
|
|
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.keyboardManager.keymanCloudRequest('',false);
|
|
} else {
|
|
keymanweb.keyboardManager.addKeyboardArray(arguments);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add default or all keyboards for a given language
|
|
*
|
|
* @param {string} arg Language name (multiple arguments allowed)
|
|
**/
|
|
keymanweb['addKeyboardsForLanguage'] = function(arg)
|
|
{
|
|
keymanweb.keyboardManager.addLanguageKeyboards(arguments);
|
|
}
|
|
|
|
/**
|
|
* Call back from cloud for adding keyboard metadata
|
|
*
|
|
* @param {Object} x metadata object
|
|
**/
|
|
keymanweb['register'] = function(x) {
|
|
keymanweb.keyboardManager.register(x);
|
|
}
|
|
|
|
/**
|
|
* Build 362: removeKeyboards() remove keyboard from list of available keyboards
|
|
*
|
|
* @param {string} x keyboard name string
|
|
*
|
|
*/
|
|
keymanweb['removeKeyboards'] = function(x) {
|
|
return keymanweb.keyboardManager.removeKeyboards(x);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Browser dependent initialization
|
|
*/
|
|
if(document.selection) // only defined for IE
|
|
{
|
|
var appVer=navigator.appVersion;
|
|
// Legacy support variables
|
|
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|null} 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'] = function(Pelem: HTMLElement) {
|
|
keymanweb.domManager.attachToControl(Pelem);
|
|
}
|
|
|
|
/**
|
|
* Function detachFromControl
|
|
* Scope Public
|
|
* @param {Element} Pelem Element from which KMW will detach
|
|
* Description Detaches KMW from a control (or IFrame)
|
|
*/
|
|
keymanweb['detachFromControl'] = function(Pelem: HTMLElement) {
|
|
keymanweb.domManager.detachFromControl(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);
|
|
|
|
// 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);
|
|
|
|
// 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.domManager.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.domManager.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.domManager.isAttached(Pelem)) { // Only operate on attached elements!
|
|
if(keymanweb._LastActiveElement == Pelem || keymanweb._LastActiveElement == Pelem['kmw_ip']) {
|
|
keymanweb._LastActiveElement = null;
|
|
keymanweb.keyboardManager.setActiveKeyboard(keymanweb.globalKeyboard, keymanweb.globalLanguageCode);
|
|
keymanweb['osk']._Hide();
|
|
}
|
|
|
|
if(device.touchable) {
|
|
keymanweb.domManager.disableTouchElement(Pelem);
|
|
keymanweb.domManager.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.touchAliasing.updateInput(keymanweb.sortedInputs[k]['kmw_ip']);
|
|
}
|
|
}
|
|
}, 1);
|
|
} else {
|
|
keymanweb.listInputs(); // Fix up our internal input ordering scheme.
|
|
}
|
|
|
|
keymanweb.domManager.disableInputElement(Pelem);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Function _EnableControl
|
|
* Scope Private
|
|
* @param {Element} Pelem Element to be enabled
|
|
* Description Enable KMW control element
|
|
*/
|
|
keymanweb._EnableControl = function(Pelem) {
|
|
if(keymanweb.domManager.isAttached(Pelem)) { // Only operate on attached elements!
|
|
if(device.touchable) {
|
|
keymanweb.domManager.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.touchAliasing.updateInput(keymanweb.sortedInputs[k]['kmw_ip']);
|
|
}
|
|
}
|
|
}, 1);
|
|
} else {
|
|
keymanweb.domManager.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.domManager.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.keyboardManager.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.domManager.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.touchAliasing.setFocus();
|
|
else keymanweb['focusLastActiveElement']();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Function getUIState
|
|
* Scope Public
|
|
* @return {Object.<string,(boolean|number)>}
|
|
* Description Return object with activation state of UI:
|
|
* activationPending (bool): KMW being activated
|
|
* activated (bool): KMW active
|
|
*/
|
|
keymanweb['getUIState'] = keymanweb.getUIState = function() {
|
|
var p={
|
|
activationPending: keymanweb._IsActivatingKeymanWebUI,
|
|
activated: keymanweb._JustActivatedKeymanWebUI
|
|
};
|
|
p['activationPending'] = p.activationPending;
|
|
p['activated'] = p.activated;
|
|
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
|
|
Ltarg = util.eventTarget(e);
|
|
if (Ltarg == null) {
|
|
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.touchAliasing.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;
|
|
|
|
if(keymanweb._JustActivatedKeymanWebUI) {
|
|
keymanweb._BlurKeyboardSettings();
|
|
} else {
|
|
keymanweb._FocusKeyboardSettings(priorElement ? false : true);
|
|
}
|
|
|
|
// Always do the common focus stuff, instantly returning if we're in an editable iframe.
|
|
if(keymanweb._CommonFocusHelper(Ltarg)) {
|
|
return true;
|
|
};
|
|
|
|
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.keyboardManager.isCJK()) {
|
|
osk._Enabled=1;
|
|
}
|
|
if(osk._Enabled) {
|
|
osk._Show();
|
|
} else {
|
|
osk._Hide(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Function _BlurKeyboardSettings
|
|
* Description Stores the last active element's keyboard settings. Should be called
|
|
* whenever a KMW-enabled page element loses control.
|
|
*/
|
|
keymanweb._BlurKeyboardSettings = function() {
|
|
var keyboardID = keymanweb.keyboardManager.activeKeyboard ? keymanweb.keyboardManager.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();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Function _FocusKeyboardSettings
|
|
* @param {boolean} blockGlobalChange A flag indicating if the global keyboard setting should be ignored for this call.
|
|
* Description Restores the newly active element's keyboard settings. Should be called
|
|
* whenever a KMW-enabled page element gains control, but only once the prior
|
|
* element's loss of control is guaranteed.
|
|
*/
|
|
keymanweb._FocusKeyboardSettings = function(blockGlobalChange) {
|
|
if(keymanweb._LastActiveElement._kmwAttachment.keyboard != null) {
|
|
keymanweb.keyboardManager.setActiveKeyboard(keymanweb._LastActiveElement._kmwAttachment.keyboard,
|
|
keymanweb._LastActiveElement._kmwAttachment.languageCode);
|
|
} else if(!blockGlobalChange) {
|
|
keymanweb.keyboardManager.setActiveKeyboard(keymanweb.globalKeyboard, keymanweb.globalLanguageCode);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Function _CommonFocusHelper
|
|
* @param {Element} target
|
|
* @returns {boolean}
|
|
* Description Performs common state management for the various focus events of KeymanWeb.
|
|
* The return value indicates whether (true) or not (false) the calling event handler
|
|
* should be terminated immediately after the call.
|
|
*/
|
|
keymanweb._CommonFocusHelper = function(target) {
|
|
//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 true;
|
|
}
|
|
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;
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
Ltarg = util.eventTarget(e);
|
|
if (Ltarg == null) {
|
|
return true;
|
|
}
|
|
|
|
keymanweb._ActiveElement = null; // I3363 (Build 301)
|
|
|
|
// Hide the touch device input caret, if applicable I3363 (Build 301)
|
|
if(device.touchable) {
|
|
keymanweb.touchAliasing.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._BlurKeyboardSettings();
|
|
|
|
// 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);
|
|
}
|
|
|
|
/**
|
|
* 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 _GetEventKeyCode
|
|
* Scope Private
|
|
* @param {Event} e Event object
|
|
* Description Finds the key code represented by the event.
|
|
*/
|
|
keymanweb._GetEventKeyCode = function(e) {
|
|
if (e.keyCode) {
|
|
return e.keyCode;
|
|
} else if (e.which) {
|
|
return e.which;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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.<string,*>} 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) {
|
|
class KeyEvent {
|
|
Ltarg: any;
|
|
Lcode: any;
|
|
Lstates: any;
|
|
LmodifierChange: any;
|
|
Lmodifiers: any;
|
|
LisVirtualKeyCode: any;
|
|
LisVirtualKey: any;
|
|
};
|
|
|
|
var s = new KeyEvent();
|
|
|
|
e = keymanweb._GetEventObject(e); // I2404 - Manage IE events in IFRAMEs
|
|
s.Ltarg = util.eventTarget(e);
|
|
if (s.Ltarg == null) {
|
|
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 (s.Ltarg.nodeType == 3) {// defeat Safari bug
|
|
s.Ltarg = s.Ltarg.parentNode;
|
|
}
|
|
|
|
s.Lcode = keymanweb._GetEventKeyCode(e);
|
|
if (s.Lcode == null) {
|
|
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 */
|
|
kbdInterface.clearDeadkeys();
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
var activeKeyboard = keymanweb.keyboardManager.activeKeyboard;
|
|
|
|
if(activeKeyboard != null && typeof(activeKeyboard['KNS']) == 'function') {
|
|
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='';
|
|
var activeKeyboard = keymanweb.keyboardManager.activeKeyboard;
|
|
|
|
keymanweb._KeyPressToSwallow = 0;
|
|
if(!keymanweb._Enabled || keymanweb._DisableInput || 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:
|
|
kbdInterface.clearDeadkeys();
|
|
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(!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(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(kbdInterface.processKeystroke(util.physicalDevice, Levent2.Ltarg,Levent2)) {
|
|
LeventMatched=1;
|
|
}
|
|
}
|
|
|
|
LeventMatched = LeventMatched || kbdInterface.processKeystroke(util.physicalDevice,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) {
|
|
kbdInterface.output(1,keymanweb._LastActiveElement,"");
|
|
}
|
|
} else {
|
|
// Mnemonic layout
|
|
if(Levent.Lcode == 8) { // I1595 - Backspace for mnemonic
|
|
keymanweb._KeyPressToSwallow = 1;
|
|
if(!kbdInterface.processKeystroke(util.physicalDevice,Levent.Ltarg,Levent)) {
|
|
kbdInterface.output(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;
|
|
}
|
|
kbdInterface.output(0, Levent.Ltarg, String._kmwFromCharCode(Lch)); //I3319
|
|
|
|
LeventMatched = 1;
|
|
}
|
|
|
|
if(LeventMatched) {
|
|
if(e && e.preventDefault) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
}
|
|
keymanweb._KeyPressToSwallow = (e?keymanweb._GetEventKeyCode(e):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;
|
|
}
|
|
}
|
|
|
|
if(typeof(Levent.Ltarg.base) != 'undefined') {
|
|
// Simulated touch elements have no default text-processing - we need to rely on a strategy similar to
|
|
// that of the OSK here.
|
|
var ch = osk.defaultKeyOutput('',Levent.Lcode,Levent.Lmodifiers,false,Levent.Ltarg);
|
|
if(ch) {
|
|
kbdInterface.output(0, Levent.Ltarg, ch);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 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.keyboardManager.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.keyboardManager.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 || kbdInterface.processKeystroke(util.physicalDevice,Levent.Ltarg,Levent)) {
|
|
keymanweb._KeyPressToSwallow=0;
|
|
if(e && e.preventDefault) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
}
|
|
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;
|
|
}
|
|
|
|
if(Levent.Ltarg.base && Levent.Ltarg.base.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);
|
|
if(!device.touchable) {
|
|
return osk._UpdateVKShift(Levent, Levent.Lcode-15, 1); // I2187
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
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
|
|
|
|
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++) {
|
|
if(t[i] == activeBase) break;
|
|
}
|
|
|
|
// Find the next (or previous) element in the list
|
|
i = bBack ? i-1 : i+1;
|
|
// Treat the list as circular, wrapping the index if necessary.
|
|
i = i >= t.length ? i-t.length : i;
|
|
i = i < 0 ? i+t.length : i;
|
|
|
|
// 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();
|
|
} else { // Or reposition the caret on the input DIV if mapped
|
|
keymanweb._ActiveElement=keymanweb._LastActiveElement=target;
|
|
keymanweb.touchAliasing.setTextCaret(target,10000);
|
|
keymanweb.touchAliasing.scrollInput(target); // mousedown check
|
|
target.focus();
|
|
}
|
|
} else { // Behaviour for desktop browsers
|
|
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();
|
|
}
|
|
|
|
/**
|
|
* Function isCJK
|
|
* Scope Public
|
|
* @param {Object=} k0
|
|
* @return {boolean}
|
|
* Description Tests if active keyboard (or optional argument) uses a pick list (Chinese, Japanese, Korean, etc.)
|
|
* (This function accepts either keyboard structure.)
|
|
*/
|
|
keymanweb['isCJK'] = function(k0) {
|
|
return keymanweb.keyboardManager.isCJK(k0);
|
|
}
|
|
|
|
/**
|
|
* Function isChiral
|
|
* Scope Public
|
|
* @param {string|Object=} k0
|
|
* @return {boolean}
|
|
* Description Tests if the active keyboard (or optional argument) uses chiral modifiers.
|
|
*/
|
|
keymanweb.isChiral = keymanweb['isChiral'] = function(k0) {
|
|
if(typeof(k0) == "string") {
|
|
k0 = keymanweb.keyboardManager.getKeyboardByID(k0);
|
|
}
|
|
|
|
return !!(keymanweb.getKeyboardModifierBitmask(k0) & keymanweb['osk'].modifierBitmasks.IS_CHIRAL);
|
|
}
|
|
|
|
/**
|
|
* Function getKeyboardModifierBitmask
|
|
* Scope Private
|
|
* @param {Object=} k0
|
|
* @return {number}
|
|
* Description Obtains the currently-active modifier bitmask for the active keyboard.
|
|
*/
|
|
keymanweb.getKeyboardModifierBitmask = function(k0) {
|
|
var k=keymanweb.keyboardManager.activeKeyboard;
|
|
|
|
if(arguments.length > 0 && typeof k0 != 'undefined') {
|
|
k = k0;
|
|
}
|
|
|
|
if(!k) {
|
|
return 0x0000;
|
|
}
|
|
|
|
if(k['KMBM']) {
|
|
return k['KMBM'];
|
|
}
|
|
|
|
return osk.modifierBitmasks['NON_CHIRAL'];
|
|
}
|
|
|
|
/**
|
|
* Allow to change active keyboard by (internal) keyboard name
|
|
*
|
|
* @param {string} PInternalName Internal name
|
|
* @param {string} PLgCode Language code
|
|
*/
|
|
keymanweb['setActiveKeyboard'] = function(PInternalName,PLgCode) {
|
|
keymanweb.keyboardManager.setActiveKeyboard(PInternalName,PLgCode);
|
|
}
|
|
|
|
/**
|
|
* Function getActiveKeyboard
|
|
* Scope Public
|
|
* @return {string} Name of active keyboard
|
|
* Description Return internal name of currently active keyboard
|
|
*/
|
|
keymanweb['getActiveKeyboard'] = function() {
|
|
return keymanweb.keyboardManager.getActiveKeyboardName();
|
|
}
|
|
|
|
/**
|
|
* Function getActiveLanguage
|
|
* Scope Public
|
|
* @return {string} language code
|
|
* Description Return language code for currently selected language
|
|
*/
|
|
keymanweb['getActiveLanguage'] = keymanweb.getActiveLanguage = function()
|
|
{
|
|
if((<KeymanBase>keymanweb).keyboardManager.activeStub == null) return '';
|
|
return (<KeymanBase>keymanweb).keyboardManager.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;
|
|
|
|
var kbdList = keymanweb.keyboardManager.getDetailedKeyboards();
|
|
|
|
for(Ln=0; Ln < kbdList.length; Ln++) {
|
|
Lrn = kbdList[Ln];
|
|
|
|
if(Lrn['InternalName'] == PInternalName || Lrn['InternalName'] == "Keyboard_" + PInternalName) {
|
|
if(arguments.length < 2) {
|
|
return Lrn;
|
|
}
|
|
|
|
if(Lrn['LanguageCode'] == PlgCode) {
|
|
return Lrn;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Get array of available keyboard stubs
|
|
*
|
|
* @return {Array} Array of available keyboards
|
|
*
|
|
*/
|
|
keymanweb['getKeyboards'] = function() {
|
|
return keymanweb.keyboardManager.getDetailedKeyboards();
|
|
}
|
|
|
|
/**
|
|
* Function _WindowUnload
|
|
* Scope Private
|
|
* Description Remove handlers before detaching KMW window
|
|
*/
|
|
keymanweb._WindowUnload = function()
|
|
{
|
|
// Allow the UI to release its own resources
|
|
keymanweb.doUnloadUI();
|
|
|
|
// Allow the OSK to release its own resources
|
|
if(osk.ready) osk._Unload(); // I3363 (Build 301)
|
|
|
|
keymanweb._LastActiveElement = 0;
|
|
}
|
|
|
|
// Complete page initialization only after the page is fully loaded, including any embedded fonts
|
|
// This avoids the need to use a timer to test for the fonts
|
|
|
|
util.attachDOMEvent(window, 'load',function(e){
|
|
//keymanweb.completeInitialization();
|
|
// Always return to top of page after a page reload
|
|
document.body.scrollTop=0;
|
|
if(typeof document.documentElement != 'undefined') document.documentElement.scrollTop=0;
|
|
},false);
|
|
|
|
// Attach this handler to window unload event
|
|
util.attachDOMEvent(window, 'unload', keymanweb._WindowUnload,false); // added fourth argument (default value)
|
|
|
|
/**
|
|
* Function _GetDocumentEditables
|
|
* Scope Private
|
|
* @param {Element} Pelem HTML element
|
|
* @return {Array<Element>} A list of potentially-editable controls. Further filtering [as with isKMWInput() and
|
|
* isKMWDisabled()] is required.
|
|
*/
|
|
keymanweb._GetDocumentEditables = function(Pelem) {
|
|
var possibleInputs = [];
|
|
|
|
if(Pelem.tagName) {
|
|
var tagName = Pelem.tagName.toLowerCase();
|
|
if(tagName == 'input' || tagName == 'textarea' || tagName == 'iframe') {
|
|
possibleInputs.push(Pelem);
|
|
}
|
|
} else if(Pelem.nodeName == "#text") {
|
|
return [];
|
|
}
|
|
|
|
// Constructing it like this also allows for individual element filtering for the auto-attach MutationObserver without errors.
|
|
if(Pelem.getElementsByTagName) {
|
|
/**
|
|
* Function LiTmp
|
|
* Scope Private
|
|
* @param {string} _colon type of element
|
|
* @return {Array<Element>} array of elements of specified type
|
|
* Description Local function to get list of editable controls
|
|
*/
|
|
var LiTmp = function(_colon){
|
|
return util.arrayFromNodeList(Pelem.getElementsByTagName(_colon));
|
|
};
|
|
|
|
// Note that isKMWInput() will block IFRAME elements as necessary for touch-based devices.
|
|
possibleInputs = possibleInputs.concat(LiTmp('INPUT'), LiTmp('TEXTAREA'), LiTmp('IFRAME'));
|
|
}
|
|
|
|
// Not all active browsers may support the method, but only those that do would work with contenteditables anyway.
|
|
if(Pelem.querySelectorAll) {
|
|
possibleInputs = possibleInputs.concat(util.arrayFromNodeList(Pelem.querySelectorAll('[contenteditable]')));
|
|
}
|
|
|
|
if(Pelem.isContentEditable) {
|
|
possibleInputs.push(Pelem);
|
|
}
|
|
|
|
return possibleInputs;
|
|
}
|
|
|
|
/**
|
|
* Function _SetupDocument
|
|
* Scope Private
|
|
* @param {Element} Pelem - the root element of a document, including IFrame documents.
|
|
* Description Used to automatically attach KMW to editable controls, regardless of control path.
|
|
*/
|
|
keymanweb._SetupDocument = function(Pelem) { // I1961
|
|
var possibleInputs = keymanweb._GetDocumentEditables(Pelem);
|
|
|
|
for(var Li = 0; Li < possibleInputs.length; Li++) {
|
|
var input = possibleInputs[Li];
|
|
|
|
// It knows how to handle pre-loaded iframes appropriately.
|
|
keymanweb.domManager.attachToControl(possibleInputs[Li]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Function _ClearDocument
|
|
* Scope Private
|
|
* @param {Element} Pelem - the root element of a document, including IFrame documents.
|
|
* Description Used to automatically detach KMW from editable controls, regardless of control path.
|
|
* Mostly used to clear out all controls of a detached IFrame.
|
|
*/
|
|
keymanweb._ClearDocument = function(Pelem) { // I1961
|
|
var possibleInputs = keymanweb._GetDocumentEditables(Pelem);
|
|
|
|
for(var Li = 0; Li < possibleInputs.length; Li++) {
|
|
var input = possibleInputs[Li];
|
|
|
|
// It knows how to handle pre-loaded iframes appropriately.
|
|
keymanweb.domManager.detachFromControl(possibleInputs[Li]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return a path that has is always terminated by a slash
|
|
*
|
|
* @param {string} p folder path
|
|
* @return {string}
|
|
**/
|
|
keymanweb.fixPath = function(p)
|
|
{
|
|
if(p.length == 0) return p;
|
|
var q=p.substr(p.length-1,1);
|
|
if(q == '/' || q == '\\') return p;
|
|
return p+'/';
|
|
}
|
|
|
|
|
|
/**
|
|
* Function Initialization
|
|
* Scope Public
|
|
* @param {Object} arg object array of user-defined properties
|
|
* Description KMW window initialization
|
|
*/
|
|
keymanweb['init'] = keymanweb.init = function(arg)
|
|
{
|
|
var i,j,c,e,p,eTextArea,eInput,opt,dTrailer,ds;
|
|
|
|
// Local function to convert relative to absolute URLs
|
|
// with respect to the source path, server root and protocol
|
|
var fixPath = function(p) {
|
|
if(p.length == 0) return p;
|
|
|
|
// Add delimiter if missing
|
|
if(p.substr(p.length-1,1) != '/') p = p+'/';
|
|
|
|
// Absolute
|
|
if((p.replace(/^(http)s?:.*/,'$1') == 'http')
|
|
|| (p.replace(/^(file):.*/,'$1') == 'file'))
|
|
return p;
|
|
|
|
// Absolute (except for protocol)
|
|
if(p.substr(0,2) == '//')
|
|
return keymanweb.protocol+p;
|
|
|
|
// Relative to server root
|
|
if(p.substr(0,1) == '/')
|
|
return keymanweb.rootPath+p.substr(1);
|
|
|
|
// Otherwise, assume relative to source path
|
|
return keymanweb.srcPath+p;
|
|
}
|
|
|
|
// Explicit (user-defined) parameter initialization
|
|
opt=keymanweb.options;
|
|
if(typeof(arg) == 'object' && arg !== null)
|
|
{
|
|
for(p in opt)
|
|
{
|
|
if(arg.hasOwnProperty(p)) opt[p] = arg[p];
|
|
}
|
|
}
|
|
|
|
// Get default paths and device options
|
|
if(opt['root'] != '') keymanweb.rootPath = fixPath(opt['root']);
|
|
|
|
// Keyboards and fonts are located with respect to the server root by default
|
|
//if(opt['keyboards'] == '') opt['keyboards'] = keymanweb.rootPath+'keyboard/';
|
|
//if(opt['fonts'] == '') opt['fonts'] = keymanweb.rootPath+'font/';
|
|
|
|
// Resources are located with respect to the engine by default
|
|
if(opt['resources'] == '') opt['resources'] = keymanweb.srcPath;
|
|
|
|
// Convert resource, keyboard and font paths to absolute URLs
|
|
opt['resources'] = fixPath(opt['resources']);
|
|
opt['keyboards'] = fixPath(opt['keyboards']);
|
|
opt['fonts'] = fixPath(opt['fonts']);
|
|
|
|
// Set element attachment type
|
|
if(opt['attachType'] == '') opt['attachType'] = 'auto';
|
|
|
|
/*
|
|
|
|
// Only initialize options the first time init is called
|
|
if(typeof(keymanweb.options['resources']) == 'undefined')
|
|
{
|
|
var root='',fontSource='',resources='',keyboards='';
|
|
|
|
// Get values of global variables if defined
|
|
if(typeof(window['KeymanWeb_Root']) == 'string') root=window['KeymanWeb_Root'];
|
|
if(typeof(window['KeymanWeb_Resources']) == 'string') resources=window['KeymanWeb_Resources'];
|
|
if(typeof(window['KeymanWeb_Keyboards']) == 'string') keyboards=window['KeymanWeb_Keyboards'];
|
|
if(typeof(window['KeymanWeb_FontUriBasePath']) == 'string') fontSource=window['KeymanWeb_FontUriBasePath'];
|
|
|
|
var opt={};
|
|
opt['root']=root;
|
|
opt['resources']=resources;
|
|
opt['keyboards']=keyboards;
|
|
opt['fonts'] = fontSource;
|
|
|
|
if(typeof(window['KeymanWeb_AttachType']) == 'string')
|
|
opt['attachType']=window['KeymanWeb_AttachType'];
|
|
else
|
|
opt['attachType'] = (device.touchable ? 'manual' : 'auto');
|
|
|
|
if(typeof(window['KeymanWeb_FloatUI']) == 'object')
|
|
opt['ui'] = window['KeymanWeb_FloatUI'];
|
|
else
|
|
opt['ui'] = '';
|
|
|
|
if(device.touchable) opt['ui'] = 'none';
|
|
|
|
keymanweb.options = opt;
|
|
}
|
|
else
|
|
{
|
|
var opt=keymanweb.options;
|
|
}
|
|
|
|
// Update the option if required by a subsequent call
|
|
if(arguments.length > 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;
|
|
|
|
/**
|
|
* Initialization of touch devices and browser interfaces must be done
|
|
* after all resources are loaded, during final stage of initialization
|
|
*
|
|
*/
|
|
|
|
// Treat Android devices as phones if either (reported) screen dimension is less than 4"
|
|
if(device.OS == 'Android')
|
|
{
|
|
// Determine actual device characteristics I3363 (Build 301)
|
|
// TODO: device.dpi may no longer be needed - if so, get rid of it.
|
|
device.dpi = util.getDPI(); //TODO: this will not work when called from HEAD!!
|
|
device.formFactor=((screen.height < 4.0*device.dpi) || (screen.width < 4.0*device.dpi)) ? 'phone' : 'tablet';
|
|
}
|
|
|
|
if (window.removeEventListener)
|
|
window.removeEventListener('focus', keymanweb._BubbledFocus, true);
|
|
|
|
// Set exposed initialization flag member for UI (and other) code to use
|
|
keymanweb.setInitialized(1);
|
|
|
|
// Finish keymanweb and OSK initialization once all necessary resources are available
|
|
osk.prepare();
|
|
|
|
// Create and save the remote keyboard loading delay indicator
|
|
util.prepareWait();
|
|
|
|
// Register deferred keyboard stubs (addKeyboards() format)
|
|
keymanweb.keyboardManager.registerDeferredStubs();
|
|
|
|
// Initialize the desktop UI
|
|
keymanweb.initializeUI();
|
|
|
|
// Register deferred keyboards
|
|
keymanweb.keyboardManager.registerDeferredKeyboards();
|
|
|
|
// Exit initialization here if we're using an embedded code path.
|
|
if(keymanweb.isEmbedded) {
|
|
if(!keymanweb.keyboardManager.setDefaultKeyboard()) {
|
|
console.error("No keyboard stubs exist - cannot initialize keyboard!");
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Determine the default font for mapped elements
|
|
keymanweb.appliedFont=keymanweb.baseFont=keymanweb.getBaseFont();
|
|
|
|
// Add orientationchange event handler to manage orientation changes on mobile devices
|
|
// Initialize touch-screen device interface I3363 (Build 301)
|
|
if(device.touchable) {
|
|
keymanweb.handleRotationEvents();
|
|
keymanweb.setupTouchDevice();
|
|
}
|
|
// Initialize browser interface
|
|
|
|
if(keymanweb.options['attachType'] != 'manual') {
|
|
keymanweb._SetupDocument(document.documentElement);
|
|
}
|
|
|
|
// Create an ordered list of all input and textarea fields
|
|
keymanweb.listInputs();
|
|
|
|
// Initialize the OSK and set default OSK styles
|
|
// Note that this should *never* be called before the OSK has been initialized.
|
|
// However, it possibly may be called before the OSK has been fully defined with the current keyboard, need to check.
|
|
//osk._Load();
|
|
|
|
//document.body.appendChild(osk._Box);
|
|
|
|
//osk._Load(false);
|
|
|
|
// I3363 (Build 301)
|
|
if(device.touchable)
|
|
{
|
|
// Handle OSK touchend events (prevent propagation)
|
|
osk._Box.addEventListener('touchend',function(e){e.stopPropagation();},false);
|
|
|
|
// Add a blank DIV to the bottom of the page to allow the bottom of the page to be shown
|
|
dTrailer=document.createElement('DIV'); ds=dTrailer.style;
|
|
ds.width='100%';ds.height=(screen.width/2)+'px';
|
|
document.body.appendChild(dTrailer);
|
|
|
|
// On Chrome, scrolling up or down causes the URL bar to be shown or hidden
|
|
// according to whether or not the document is at the top of the screen.
|
|
// But when doing that, each OSK row top and height gets modified by Chrome
|
|
// looking very ugly. Itwould be best to hide the OSK then show it again
|
|
// when the user scroll finishes, but Chrome has no way to reliably report
|
|
// the touch end event after a move. c.f. http://code.google.com/p/chromium/issues/detail?id=152913
|
|
// The best compromise behaviour is simply to hide the OSK whenever any
|
|
// non-input and non-OSK element is touched.
|
|
if(device.OS == 'Android' && navigator.userAgent.indexOf('Chrome') > 0)
|
|
{
|
|
keymanweb.hideOskWhileScrolling=function(e)
|
|
{
|
|
if(typeof(osk._Box) == 'undefined') return;
|
|
if(typeof(osk._Box.style) == 'undefined') return;
|
|
|
|
// The following tests are needed to prevent the OSK from being hidden during normal input!
|
|
p=e.target.parentNode;
|
|
if(typeof(p) != 'undefined' && p != null)
|
|
{
|
|
if(p.className.indexOf('keymanweb-input') >= 0) return;
|
|
if(p.className.indexOf('kmw-key-') >= 0) return;
|
|
if(typeof(p.parentNode) != 'undefined')
|
|
{
|
|
p=p.parentNode;
|
|
if(p.className.indexOf('keymanweb-input') >= 0) return;
|
|
if(p.className.indexOf('kmw-key-') >= 0) return;
|
|
}
|
|
}
|
|
osk.hideNow();
|
|
}
|
|
document.body.addEventListener('touchstart',keymanweb.hideOskWhileScrolling,false);
|
|
}
|
|
else
|
|
{
|
|
keymanweb.conditionallyHideOsk = function()
|
|
{
|
|
// Should not hide OSK if simply closing the language menu (30/4/15)
|
|
if(keymanweb.hideOnRelease && !osk.lgList) osk.hideNow();
|
|
keymanweb.hideOnRelease=false;
|
|
}
|
|
keymanweb.hideOskIfOnBody = function(e)
|
|
{
|
|
keymanweb.touchY=e.touches[0].screenY;
|
|
keymanweb.hideOnRelease=true;
|
|
}
|
|
keymanweb.cancelHideIfScrolling = function(e)
|
|
{
|
|
var y=e.touches[0].screenY,y0=keymanweb.touchY;
|
|
if(y-y0 > 5 || y0-y < 5) keymanweb.hideOnRelease = false;
|
|
}
|
|
document.body.addEventListener('touchstart',keymanweb.hideOskIfOnBody,false);
|
|
document.body.addEventListener('touchmove',keymanweb.cancelHideIfScrolling,false);
|
|
document.body.addEventListener('touchend',keymanweb.conditionallyHideOsk,false);
|
|
}
|
|
}
|
|
|
|
//document.body.appendChild(keymanweb._StyleBlock);
|
|
|
|
// IE: call _SelectionChange when the user changes the selection
|
|
if(document.selection)
|
|
util.attachDOMEvent(document, 'selectionchange', keymanweb._SelectionChange);
|
|
|
|
// Restore and reload the currently selected keyboard, selecting a default keyboard if necessary.
|
|
keymanweb.keyboardManager.restoreCurrentKeyboard();
|
|
|
|
/* Setup of handlers for dynamically-added and (eventually) dynamically-removed elements.
|
|
* Reference: https://developer.mozilla.org/en/docs/Web/API/MutationObserver
|
|
*
|
|
* We place it here so that it loads after most of the other UI loads, reducing the MutationObserver's overhead.
|
|
* Of course, we only want to dynamically add elements if the user hasn't enabled the manual attachment option.
|
|
*/
|
|
|
|
var observationTarget = document.querySelector('body'), observationConfig;
|
|
if(keymanweb.options['attachType'] != 'manual') { //I1961
|
|
keymanweb.mutationObserver = new MutationObserver(keymanweb._AutoAttachObserverCore);
|
|
|
|
observationConfig = { childList: true, subtree: true};
|
|
keymanweb.mutationObserver.observe(observationTarget, observationConfig);
|
|
}
|
|
|
|
/**
|
|
* Setup of handlers for dynamic detection of the kmw-disabled class tag that controls enablement.
|
|
*/
|
|
observationConfig = { subtree: true, attributes: true, attributeOldValue: true, attributeFilter: ['class', 'readonly']};
|
|
new MutationObserver(keymanweb._EnablementMutationObserverCore).observe(observationTarget, observationConfig);
|
|
|
|
// Set exposed initialization flag to 2 to indicate deferred initialization also complete
|
|
keymanweb.setInitialized(2);
|
|
}
|
|
|
|
keymanweb._EnablementMutationObserverCore = function(mutations) {
|
|
for(var i=0; i < mutations.length; i++) {
|
|
var mutation = mutations[i];
|
|
|
|
// ( ? : ) needed as a null check.
|
|
var disabledBefore = mutation.oldValue ? mutation.oldValue.indexOf('kmw-disabled') >= 0 : false;
|
|
var disabledAfter = mutation.target.className.indexOf('kmw-disabled') >= 0;
|
|
|
|
if(disabledBefore && !disabledAfter) {
|
|
keymanweb._EnableControl(mutation.target);
|
|
} else if(!disabledBefore && disabledAfter) {
|
|
keymanweb._DisableControl(mutation.target);
|
|
}
|
|
|
|
// 'readonly' triggers on whether or not the attribute exists, not its value.
|
|
if(!disabledAfter && mutation.attributeName == "readonly") {
|
|
var readonlyBefore = mutation.oldValue ? mutation.oldValue != null : false;
|
|
var readonlyAfter = mutation.target.readOnly;
|
|
|
|
if(readonlyBefore && !readonlyAfter) {
|
|
keymanweb._EnableControl(mutation.target);
|
|
} else if(!readonlyBefore && readonlyAfter) {
|
|
keymanweb._DisableControl(mutation.target);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
keymanweb._AutoAttachObserverCore = function(mutations) {
|
|
var inputElementAdditions = [];
|
|
var inputElementRemovals = [];
|
|
|
|
for(var i=0; i < mutations.length; i++) {
|
|
var mutation = mutations[i];
|
|
|
|
for(var j=0; j < mutation.addedNodes.length; j++) {
|
|
inputElementAdditions = inputElementAdditions.concat(keymanweb._GetDocumentEditables(mutation.addedNodes[j]));
|
|
}
|
|
|
|
for(j = 0; j < mutation.removedNodes.length; j++) {
|
|
inputElementRemovals = inputElementRemovals.concat(keymanweb._GetDocumentEditables(mutation.removedNodes[j]));
|
|
}
|
|
}
|
|
|
|
for(var k = 0; k < inputElementAdditions.length; k++) {
|
|
if(keymanweb.domManager.isKMWInput(inputElementAdditions[k])) { // Apply standard element filtering!
|
|
keymanweb._MutationAdditionObserved(inputElementAdditions[k]);
|
|
}
|
|
}
|
|
|
|
for(k = 0; k < inputElementRemovals.length; k++) {
|
|
if(keymanweb.domManager.isKMWInput(inputElementRemovals[k])) { // Apply standard element filtering!
|
|
keymanweb._MutationRemovalObserved(inputElementRemovals[k]);
|
|
}
|
|
}
|
|
|
|
/* After all mutations have been handled, we need to recompile our .sortedInputs array, but only
|
|
* if any have actually occurred.
|
|
*/
|
|
if(inputElementAdditions.length || inputElementRemovals.length) {
|
|
if(!device.touchable) {
|
|
keymanweb.listInputs();
|
|
} else if(device.touchable) { // If something was added or 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.touchAliasing.updateInput(keymanweb.sortedInputs[k]['kmw_ip']);
|
|
}
|
|
}
|
|
}, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Function _MutationAdditionObserved
|
|
* Scope Private
|
|
* @param {Element} Pelem A page input, textarea, or iframe element.
|
|
* Description Used by the MutationObserver event handler to properly setup any elements dynamically added to the document post-initialization.
|
|
*
|
|
*/
|
|
keymanweb._MutationAdditionObserved = function(Pelem) {
|
|
if(Pelem.tagName.toLowerCase() == 'iframe' && !device.touchable) {
|
|
//Problem: the iframe is loaded asynchronously, and we must wait for it to load fully before hooking in.
|
|
|
|
var attachFunctor = function() { // Triggers at the same time as iframe's onload property, after its internal document loads.
|
|
keymanweb.domManager.attachToControl(Pelem);
|
|
};
|
|
|
|
Pelem.addEventListener('load', attachFunctor);
|
|
|
|
/* If the iframe has somehow already loaded, we can't expect the onload event to be raised. We ought just
|
|
* go ahead and perform our callback's contents.
|
|
*
|
|
* keymanweb.domManager.attachToControl() is now idempotent, so even if our call 'whiffs', it won't cause long-lasting
|
|
* problems.
|
|
*/
|
|
if(Pelem.contentDocument.readyState == 'complete') {
|
|
attachFunctor();
|
|
}
|
|
} else {
|
|
keymanweb.domManager.attachToControl(Pelem);
|
|
}
|
|
}
|
|
|
|
// Used by the mutation event handler to properly decouple any elements dynamically removed from the document.
|
|
keymanweb._MutationRemovalObserved = function(Pelem) {
|
|
var element = Pelem;
|
|
if(device.touchable) {
|
|
keymanweb.domManager.disableTouchElement(Pelem);
|
|
}
|
|
|
|
keymanweb.domManager.disableInputElement(Pelem); // Remove all KMW event hooks, styling.
|
|
keymanweb.domManager.clearElementAttachment(element); // Memory management & auto de-attachment upon removal.
|
|
}
|
|
|
|
// Create an ordered list of all text and search input elements and textarea elements
|
|
// except any tagged with class 'kmw-disabled'
|
|
// TODO: email and url types should perhaps use default keyboard only
|
|
keymanweb.listInputs = function() {
|
|
var i,eList=[],
|
|
t1=document.getElementsByTagName<'input'>('input'),
|
|
t2=document.getElementsByTagName<'textarea'>('textarea');
|
|
|
|
for(i=0; i<t1.length; i++) {
|
|
switch(t1[i].type) {
|
|
case 'text':
|
|
case 'search':
|
|
case 'email':
|
|
case 'url':
|
|
if(t1[i].className.indexOf('kmw-disabled') < 0) {
|
|
eList.push({ip:t1[i],x:util._GetAbsoluteX(t1[i]),y:util._GetAbsoluteY(t1[i])});
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
for(i=0; i<t2.length; i++) {
|
|
if(t2[i].className.indexOf('kmw-disabled') < 0)
|
|
eList.push({ip:t2[i],x:util._GetAbsoluteX(t2[i]),y:util._GetAbsoluteY(t2[i])});
|
|
}
|
|
|
|
/**
|
|
* Local function to sort by screen position
|
|
*
|
|
* @param {Object} e1 first object
|
|
* @param {Object} e2 second object
|
|
* @return {number} y-difference between object positions, or x-difference if y values the same
|
|
*/
|
|
var xySort=function(e1,e2)
|
|
{
|
|
if(e1.y != e2.y) return e1.y-e2.y;
|
|
return e1.x-e2.x;
|
|
}
|
|
|
|
// Sort elements by Y then X
|
|
eList.sort(xySort);
|
|
|
|
// Create a new list of sorted elements
|
|
var tList=[];
|
|
for(i=0;i<eList.length;i++)
|
|
tList.push(eList[i].ip);
|
|
|
|
// Return the sorted element list
|
|
keymanweb.sortedInputs=tList;
|
|
}
|
|
|
|
/**
|
|
* Initialize the desktop user interface as soon as it is ready
|
|
**/
|
|
keymanweb.initializeUI = function()
|
|
{
|
|
if(typeof(keymanweb['ui']['initialize'])=='function')
|
|
{
|
|
keymanweb['ui']['initialize']();
|
|
// Display the OSK (again) if enabled, in order to set its position correctly after
|
|
// adding the UI to the page
|
|
osk._Show();
|
|
}
|
|
else
|
|
window.setTimeout(keymanweb.initializeUI,1000);
|
|
}
|
|
/**
|
|
* Test if caret position is determined from the active element, or
|
|
* from the synthesized overlay element (touch devices)
|
|
*
|
|
* @return {boolean}
|
|
**/
|
|
keymanweb.isPositionSynthesized = function()
|
|
{
|
|
return device.touchable;
|
|
}
|
|
|
|
/**
|
|
* Function _SelPos
|
|
* Scope Private
|
|
* @param {Object} Pelem Element
|
|
* @return {number} Selection start
|
|
* Description Get start of selection (with supplementary plane modifications)
|
|
*/
|
|
keymanweb._SelPos = function(Pelem)
|
|
{
|
|
var Ldoc, Ldv, isMSIE=(util._GetIEVersion()<999); // I3363 (Build 301)
|
|
|
|
if(keymanweb.isPositionSynthesized())
|
|
return keymanweb.getTextCaret(Pelem);
|
|
|
|
if(Pelem._KeymanWebSelectionStart)
|
|
return Pelem._KeymanWebSelectionStart;
|
|
|
|
// Mozilla, IE9
|
|
else if (Pelem.setSelectionRange)
|
|
return Pelem.value.substr(0,Pelem.selectionStart)._kmwLength();
|
|
|
|
// contentEditable elements, Mozilla midas
|
|
else if((Ldv=Pelem.ownerDocument) && (Ldv=Ldv.defaultView) && Ldv.getSelection
|
|
&& Pelem.ownerDocument.designMode.toLowerCase() == 'on') {
|
|
var Lsel = Ldv.getSelection();
|
|
if(Lsel.focusNode.nodeType == 3)
|
|
return Lsel.focusNode.substringData(0,Lsel.focusOffset)._kmwLength();
|
|
}
|
|
|
|
// IE8 and earlier
|
|
else if(isMSIE)
|
|
{
|
|
// Get position within input or textarea element
|
|
if(typeof(Pelem.value) == 'string') {
|
|
var ss=keymanweb.getInputSelection(Pelem);
|
|
return Pelem.value.substr(0,ss.start)._kmwLength();
|
|
}
|
|
|
|
// Get position within content-editable region
|
|
if(Pelem.body) Ldoc=Pelem; else Ldoc=Pelem.ownerDocument; // I1481 - integration with rich editors not working 100%
|
|
|
|
if(Ldoc) Ldv=Ldoc.selection; else return 0;
|
|
|
|
var Lrange = Ldv.createRange();
|
|
Lrange.moveStart('textedit',-1);
|
|
return Lrange.text._kmwLength();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Old code without SMP mods
|
|
|
|
keymanweb._SelPos = function(Pelem)
|
|
{
|
|
var Ldv;
|
|
if(Pelem._KeymanWebSelectionStart) return Pelem._KeymanWebSelectionStart;
|
|
else if (Pelem.setSelectionRange)
|
|
return Pelem.selectionStart;
|
|
else if((Ldv=Pelem.ownerDocument) && (Ldv=Ldv.defaultView) && Ldv.getSelection && Pelem.ownerDocument.designMode.toLowerCase() == 'on') // && Pelem.tagName == 'HTML')
|
|
{
|
|
var Lsel = Ldv.getSelection();
|
|
if(Lsel.focusNode.nodeType == 3) return Lsel.focusOffset;
|
|
}
|
|
return 0;
|
|
}*/
|
|
|
|
/**
|
|
* Function getInputSelection
|
|
* Scope Private
|
|
* @param {Object} el element
|
|
* @return {Object.<string,number>} selection start
|
|
* Description Get input selection for all(?) browsers, per Tim Down
|
|
* http://stackoverflow.com/questions/3053542/how-to-get-the-start-and-end-points-of-selection-in-text-area/3053640#3053640
|
|
* But only works for input fields, not for content editable fields!!!
|
|
**/
|
|
keymanweb.getInputSelection = function(el)
|
|
{
|
|
var start = 0, end = 0, normalizedValue = '', range, textInputRange, len = 0, endRange;
|
|
|
|
if(typeof el.selectionStart == "number" && typeof el.selectionEnd == "number") {
|
|
start = el.selectionStart; end = el.selectionEnd;
|
|
} else {
|
|
range = document.selection.createRange();
|
|
|
|
if(range && range.parentElement() == el) {
|
|
len = el.value.length;
|
|
normalizedValue = el.value.replace(/\r\n/g, "\n");
|
|
|
|
// Create a working TextRange that lives only in the input
|
|
textInputRange = el.createTextRange();
|
|
textInputRange.moveToBookmark(range.getBookmark());
|
|
|
|
// Check if the start and end of the selection are at the very end of the input,
|
|
// since moveStart/moveEnd doesn't return what we want in those cases
|
|
endRange = el.createTextRange();
|
|
endRange.collapse(false);
|
|
|
|
if(textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
|
|
start = end = len;
|
|
} else {
|
|
start = -textInputRange.moveStart("character", -len);
|
|
start += normalizedValue.slice(0, start).split("\n").length - 1;
|
|
|
|
if(textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
|
|
end = len;
|
|
} else {
|
|
end = -textInputRange.moveEnd("character", -len);
|
|
end += normalizedValue.slice(0, end).split("\n").length - 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return {start: start, end: end};
|
|
}
|
|
// *** I3319 Supplementary Plane modifications - end new code
|
|
|
|
/**
|
|
* Set target element text direction (LTR or RTL), but only if the element is empty
|
|
*
|
|
* If the element base directionality is changed after it contains content, unless all the text
|
|
* has the same directionality, text runs will be re-ordered which is confusing and causes
|
|
* incorrect caret positioning
|
|
*
|
|
* @param {Object} Ptarg Target element
|
|
*/
|
|
keymanweb._SetTargDir = function(Ptarg) {
|
|
var elDir=((keymanweb.keyboardManager.activeKeyboard != null) && (keymanweb.keyboardManager.activeKeyboard['KRTL'])) ? 'rtl' : 'ltr';
|
|
if(Ptarg) {
|
|
if(device.touchable) {
|
|
if(Ptarg.textContent.length == 0) {
|
|
Ptarg.base.dir=Ptarg.dir=elDir;
|
|
keymanweb.touchAliasing.setTextCaret(Ptarg,10000);
|
|
}
|
|
} else {
|
|
if(typeof Ptarg.value == "string") {
|
|
if(Ptarg.value.length == 0) {
|
|
Ptarg.dir=elDir;
|
|
}
|
|
} else if(typeof Ptarg.textContent == "string" && Ptarg.textContent.length == 0) { // As with contenteditable DIVs, for example.
|
|
Ptarg.dir=elDir;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reset OSK shift states when entering or exiting the active element
|
|
**/
|
|
keymanweb._ResetVKShift = function()
|
|
{
|
|
if(!keymanweb._IsActivatingKeymanWebUI)
|
|
{
|
|
if(osk._UpdateVKShift) osk._UpdateVKShift(null,15,0); //this should be enabled !!!!! TODO
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Function addHotKey
|
|
* Scope Public
|
|
* @param {number} keyCode
|
|
* @param {number} shiftState
|
|
* @param {function(Object)} handler
|
|
* Description Add hot key handler to array of document-level hotkeys triggered by key up event
|
|
*/
|
|
keymanweb['addHotKey'] = keymanweb.addHotKey = function(keyCode,shiftState,handler)
|
|
{
|
|
// Test if existing handler for this code and replace it if so
|
|
for(var i=0; i<keymanweb._HotKeys.length; i++)
|
|
{
|
|
if(keymanweb._HotKeys[i].Code == keyCode && keymanweb._HotKeys[i].Shift == shiftState)
|
|
{
|
|
keymanweb._HotKeys[i].Handler = handler; return;
|
|
}
|
|
}
|
|
// Otherwise add it to the array
|
|
keymanweb._HotKeys.push({Code:keyCode,Shift:shiftState,Handler:handler});
|
|
}
|
|
|
|
/**
|
|
* Function removeHotKey
|
|
* Scope Public
|
|
* @param {number} keyCode
|
|
* @param {number} shiftState
|
|
* Description Remove a hot key handler from array of document-level hotkeys triggered by key up event
|
|
*/
|
|
keymanweb['removeHotKey'] = keymanweb.removeHotKey = function(keyCode,shiftState)
|
|
{
|
|
for(var i=0; i<keymanweb._HotKeys.length; i++)
|
|
{
|
|
if(keymanweb._HotKeys[i].Code == keyCode && keymanweb._HotKeys[i].Shift == shiftState)
|
|
{
|
|
keymanweb._HotKeys.splice(i,1); return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Function _ProcessHotKeys
|
|
* Scope Private
|
|
* @param {Event} e event
|
|
* Description Passes control to handlers according to the hotkey pressed
|
|
*/
|
|
keymanweb._ProcessHotKeys = function(e) {
|
|
if(!e) {
|
|
e = window.event;
|
|
}
|
|
|
|
var _Lcode = keymanweb._GetEventKeyCode(e);
|
|
if(_Lcode == null) {
|
|
return null;
|
|
}
|
|
|
|
// Removed testing of e.shiftKey==null I3363 (Build 301)
|
|
var _Lmodifiers =
|
|
(e.shiftKey ? 0x10 : 0) |
|
|
(e.ctrlKey ? (e.ctrlLeft ? 0x20 : 0x20) : 0) |
|
|
(e.altKey ? (e.altLeft ? 0x40 : 0x40) : 0);
|
|
|
|
for(var i=0; i<keymanweb._HotKeys.length; i++) {
|
|
if(_Lcode == keymanweb._HotKeys[i].Code) {
|
|
if(_Lmodifiers == keymanweb._HotKeys[i].Shift) {
|
|
keymanweb._HotKeys[i].Handler();
|
|
e.returnValue = 0;
|
|
if(e && e.preventDefault) e.preventDefault();
|
|
e.cancelBubble = true;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
util.attachDOMEvent(document, 'keyup', keymanweb._ProcessHotKeys,false);
|
|
|
|
/**
|
|
* Gets the cookie for the name and language code of the most recently active keyboard
|
|
*
|
|
* Defaults to US English, but this needs to be user-set in later revision (TODO)
|
|
*
|
|
* @return {string} InternalName:LanguageCode
|
|
**/
|
|
keymanweb['getSavedKeyboard'] = function() {
|
|
return keymanweb.keyboardManager.getSavedKeyboard();
|
|
}
|
|
|
|
util.attachDOMEvent(window, 'focus', keymanweb._ResetVKShift,false); // I775
|
|
util.attachDOMEvent(window, 'blur', keymanweb._ResetVKShift,false); // I775
|
|
|
|
// Initialize supplementary plane string extensions
|
|
String.kmwEnableSupplementaryPlane(false);
|
|
|
|
})();
|
|
}
|
|
|