diff --git a/web/samples/index.html b/web/samples/index.html index 6e58f6588e..9ce9605dac 100644 --- a/web/samples/index.html +++ b/web/samples/index.html @@ -23,5 +23,9 @@

Test uncompiled Keymanweb

Test fully compiled Keymanweb

Full multilingual (compiled) Keymanweb

+

Uncompiled Keymanweb - test desktop MutationObserver functionality

+

Uncompiled Keymanweb - light test for touch-based MutationObserver functionality

+

Uncompiled Keymanweb - test/stress-test touch-based MutationObserver functionality

+

Uncompiled Keymanweb - test touch-based keyboards with heterogenous layer structures/row counts.

diff --git a/web/samples/issue53.html b/web/samples/issue-53.html similarity index 97% rename from web/samples/issue53.html rename to web/samples/issue-53.html index c40ca5d94f..12714abb39 100644 --- a/web/samples/issue53.html +++ b/web/samples/issue-53.html @@ -51,8 +51,8 @@ - - + + diff --git a/web/samples/issue53.js b/web/samples/issue-53.js similarity index 100% rename from web/samples/issue53.js rename to web/samples/issue-53.js diff --git a/web/samples/issue-62.html b/web/samples/issue-62.html index 0dd68d437c..12124a0e06 100644 --- a/web/samples/issue-62.html +++ b/web/samples/issue-62.html @@ -46,7 +46,8 @@ diff --git a/web/samples/issue-63.html b/web/samples/issue-63.html new file mode 100644 index 0000000000..33efbefcc4 --- /dev/null +++ b/web/samples/issue-63.html @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + KeymanWeb Sample Page - Uncompiled Source + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

KeymanWeb Sample Page - Uncompiled Source

+ +
+ +
+

This version is used to test KeymanWeb's ability to cope with page mutations.

+ + + + + + + +
+
+

Return to testing home page

+ + + + + + + diff --git a/web/samples/issue-63.js b/web/samples/issue-63.js new file mode 100644 index 0000000000..caaccb14ca --- /dev/null +++ b/web/samples/issue-63.js @@ -0,0 +1,156 @@ +// JavaScript Document samplehdr.js: Keyboard management for KeymanWeb demonstration pages + +/* + The keyboard name and/or ISO language code must be specified for each keyboard that is to be available. + If the same keyboard is used for several languages, it must be listed for each + language, but the keyboard itself will only be loaded once. + If two (or more) keyboards are to be available for a given language, both must be listed. + Any number of keyboards may be specified in one or more calls. + Keyboard paths may be absolute (with respect to the server root) or relative to the keyboards option path. + The actual keyboard object will be downloaded asynchronously when first selected for use. + + Each argument to addKeyboards() is a string, for example: + european2 loads the current version of the Eurolatin 2 keyboard (for its default language) + european2@fra loads the current version of the Eurolatin 2 keyboard for French + european2@fra@1.2 loads version 1.2 of the Eurolatin 2 keyboard for French + + Argument syntax also supports the following extensions: + @fra load the current version of the default keyboard for French + @fra$ load all available keyboards (current version) for French + + Each call to addKeyboards() requires a single call to the remote server, + (unless all keyboards listed are local and fully specified) so it is better + to use multiple arguments rather than separate function calls. + + Calling addKeyboards() with no arguments returns a list of *all* available keyboards. + The Toolbar (desktop browser) UI is best suited for allowing users to select + the appropriate language and keyboard in this case. + + Keyboards may also be specified by language name using addKeyboardsForLanguage() + for example: + keymanweb.addKeyboardsForLanguage('Burmese'); + + Appending $ to the language name will again cause all available keyboards for that + language to be loaded rather than the default keyboard. + + The first call to addKeyboardsForLanguage() makes an additional call to the + keyman API to load the current list of keyboard/language associations. + + In this example, the following function loads the indicated keyboards, + and is called when the page loads. +*/ + + function loadKeyboards() + { + var kmw=tavultesoft.keymanweb; + + // The first keyboard added will be the default keyboard for touch devices. + // For faster loading, it may be best for the default keybaord to be + // locally sourced. + kmw.addKeyboards({id:'us',name:'English',language:{id:'eng',name:'English'}, + filename:'./us-1.0.js'}); + + // Add more keyboards to the language menu, by keyboard name, + // keyboard name and language code, or just the ISO 639 language code. + kmw.addKeyboards('french','european2@swe','european2@nor','@heb'); + + // Add a keyboard by language name. Note that the name must be spelled + // correctly, or the keyboard will not be found. (Using ISO codes is + // usually easier.) + kmw.addKeyboardsForLanguage('Dzongkha'); + + // Add a fully-specified, locally-sourced, keyboard with custom font + kmw.addKeyboards({id:'lao_2008_basic',name:'Lao Basic', + language:{ + id:'lao',name:'Lao',region:'Asia', + font:{family:'LaoWeb',source:['../font/saysettha_web.ttf','../font/saysettha_web.woff','../font/saysettha_web.eot']} + }, + filename:'./lao_2008_basic.js' + }); + + // The following two optional calls should be delayed until language menus are fully loaded: + // (a) a specific mapped input element input is focused, to ensure that the OSK appears + // (b) a specific keyboard is loaded, rather than the keyboard last used. + //window.setTimeout(function(){kmw.setActiveElement('ta1',true);},2500); + //window.setTimeout(function(){kmw.setActiveKeyboard('Keyboard_french','fra');},3000); + + // Note that locally specified keyboards will be listed before keyboards + // requested from the remote server by user interfaces that do not order + // keyboards alphabetically by language. + } + + { + var inputCounter = 1; + var removalCounter = 1; + var removedElements = []; + } + + function addInputs() + { + var masterDiv = document.getElementById('DynamicTextboxes'); + + var newTextArea = document.createElement("textarea"); + + var i = inputCounter++; + + newTextArea.id = 'ta' + i; + newTextArea.className = 'test'; + newTextArea.placeholder = "Dynamic area #" + i + "!"; + + var newInput = document.createElement("input"); + newInput.id = 'in' + i; + newInput.className = 'test'; + newInput.placeholder = "Dynamic area #" + i + "!"; + + //masterDiv.appendChild(newTextArea); + //masterDiv.appendChild(newInput); + + var newDiv = document.createElement("div"); + newDiv.id = "dynamic_div" + i; + + //newDiv.appendChild(newTextArea); + newDiv.appendChild(newInput); + + masterDiv.appendChild(newDiv); + } + + function removeInputs() + { + var parent = document.getElementById('DynamicTextboxes'); + var div = document.getElementById("dynamic_div" + removalCounter); + if(div) + { + removalCounter++; + parent.removeChild(div); + removedElements.push(div); + } + } + + function restoreInputs() + { + var masterDiv = document.getElementById('DynamicTextboxes'); + removalCounter = 1; + + for(var i = 0; i < removedElements.length; i++) + { + masterDiv.appendChild(removedElements[i]); + } + + removedElements = []; + } + + function addIFrame() + { + var masterDiv = document.getElementById('DynamicTextboxes'); + + var frame = document.createElement("iframe"); + frame.height = "100"; + frame.src = "issue-29-iframe.html"; + frame.onload = function() {console.log('Original onload!');}; + + //masterDiv.appendChild(frame); + + var newDiv = document.createElement("div"); + newDiv.appendChild(frame); + masterDiv.appendChild(newDiv); + } \ No newline at end of file diff --git a/web/samples/issue58/issue58.html b/web/samples/issue53/issue58.html similarity index 100% rename from web/samples/issue58/issue58.html rename to web/samples/issue53/issue58.html diff --git a/web/samples/issue58/issue58.js b/web/samples/issue53/issue58.js similarity index 100% rename from web/samples/issue58/issue58.js rename to web/samples/issue53/issue58.js diff --git a/web/samples/issue58/layered_debug_keyboard-1.0.js b/web/samples/issue53/layered_debug_keyboard-1.0.js similarity index 100% rename from web/samples/issue58/layered_debug_keyboard-1.0.js rename to web/samples/issue53/layered_debug_keyboard-1.0.js diff --git a/web/samples/issue58/layered_debug_keyboard-1.0.json b/web/samples/issue53/layered_debug_keyboard-1.0.json similarity index 100% rename from web/samples/issue58/layered_debug_keyboard-1.0.json rename to web/samples/issue53/layered_debug_keyboard-1.0.json diff --git a/web/samples/issue58/layered_debug_keyboard-1.0_load.js b/web/samples/issue53/layered_debug_keyboard-1.0_load.js similarity index 100% rename from web/samples/issue58/layered_debug_keyboard-1.0_load.js rename to web/samples/issue53/layered_debug_keyboard-1.0_load.js diff --git a/web/samples/issue58/layered_debug_keyboard-layout.js b/web/samples/issue53/layered_debug_keyboard-layout.js similarity index 100% rename from web/samples/issue58/layered_debug_keyboard-layout.js rename to web/samples/issue53/layered_debug_keyboard-layout.js diff --git a/web/samples/issue58/layered_debug_keyboard.ico b/web/samples/issue53/layered_debug_keyboard.ico similarity index 100% rename from web/samples/issue58/layered_debug_keyboard.ico rename to web/samples/issue53/layered_debug_keyboard.ico diff --git a/web/samples/issue58/layered_debug_keyboard.kmn b/web/samples/issue53/layered_debug_keyboard.kmn similarity index 100% rename from web/samples/issue58/layered_debug_keyboard.kmn rename to web/samples/issue53/layered_debug_keyboard.kmn diff --git a/web/samples/issue58/layered_debug_keyboard.kpj b/web/samples/issue53/layered_debug_keyboard.kpj similarity index 100% rename from web/samples/issue58/layered_debug_keyboard.kpj rename to web/samples/issue53/layered_debug_keyboard.kpj diff --git a/web/samples/issue58/layered_debug_keyboard.kvk b/web/samples/issue53/layered_debug_keyboard.kvk similarity index 100% rename from web/samples/issue58/layered_debug_keyboard.kvk rename to web/samples/issue53/layered_debug_keyboard.kvk diff --git a/web/source/keymanweb.js b/web/source/keymanweb.js index f6a510cea2..519ebb3d2f 100644 --- a/web/source/keymanweb.js +++ b/web/source/keymanweb.js @@ -858,11 +858,7 @@ ); for(var n=0;n= t.length) i=i-t.length; @@ -2836,7 +2848,7 @@ // 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') { @@ -3622,20 +3634,50 @@ inputElementAdditions.push(addedNode); } } + + for(j = 0; j < mutation.removedNodes.length; j++) { + var removedNode = mutation.removedNodes[j]; + var lcTagName = removedNode.tagName ? removedNode.tagName.toLowerCase() : ""; + + var inputElementRemovals = []; + + // Will need to handle this in case of child elements in a newly-added element with child elements. + if(removedNode.getElementsByTagName) { + inputElementRemovals = inputElementRemovals.concat( + util.arrayFromNodeList(removedNode.getElementsByTagName('input')), + util.arrayFromNodeList(removedNode.getElementsByTagName('textarea')), + util.arrayFromNodeList(removedNode.getElementsByTagName('iframe')) + ); + } + // After all mutations have been handled, we need to recompile our .sortedInputs array. + } } for(var k = 0; k < inputElementAdditions.length; k++) { keymanweb._MutationAdditionObserved(inputElementAdditions[k]); } - // There also exists a 'mutation.removedNodes' array. We'll need to address that for issue #63, and its - // respective _MutationRemovalObserved should be called here. + for(k = 0; k < inputElementRemovals.length; k++) { + 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) { - keymanweb.listInputs(); + 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.updateInput(keymanweb.sortedInputs[k]['kmw_ip']); + } + } + }, 1); + } } }); @@ -3679,20 +3721,24 @@ } } } else { - if(Pelem.tagName.toLowerCase() != 'iframe') { - if(keymanweb.isKMWInput(Pelem)) { - var x = keymanweb.setupTouchElement(Pelem); + keymanweb.setupTouchElement(Pelem); + } + } - if(x) { - // if needed, let the timeout variable be: keymanweb.touchInputAddedTimer - window.setTimeout(function() { - keymanweb.updateInput(x); - }, 1); - } - } else { - keymanweb.setupNontouchElement(Pelem); - } - } // no 'else' - the touch-device implementation never handled iframes. + // 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) { + element = Pelem['kmw_ip']; + + // We get weird repositioning errors if we don't remove our simulated input element - and permanently. + Pelem.parentNode.removeChild(element); + delete Pelem['kmw_ip']; + } + + var index = keymanweb.inputList.indexOf(Pelem); + if(index != -1) { + keymanweb.inputList.splice(index, 1); } }