mirror of
https://github.com/keymanapp/keyman.git
synced 2026-09-21 15:17:40 +00:00
Merge pull request #80 from keymanapp/web-63-dynamic-input-removal
Dynamic input removal, other bookkeeping
This commit is contained in:
commit
dcb28ef32d
17 changed files with 345 additions and 37 deletions
|
|
@ -23,5 +23,9 @@
|
|||
<h2><a href="./uncompiled.html">Test uncompiled Keymanweb</a></h2>
|
||||
<h2><a href="./compiled.html">Test fully compiled Keymanweb</a></h2>
|
||||
<h2><a href="./multilingual.html">Full multilingual (compiled) Keymanweb</a></h2>
|
||||
<h2><a href="./issue-29.html">Uncompiled Keymanweb - test desktop MutationObserver functionality</a></h2>
|
||||
<h2><a href="./issue-62.html">Uncompiled Keymanweb - light test for touch-based MutationObserver functionality</a></h2>
|
||||
<h2><a href="./issue-63.html">Uncompiled Keymanweb - test/stress-test touch-based MutationObserver functionality</a></h2>
|
||||
<h2><a href="./issue-53.html">Uncompiled Keymanweb - test touch-based keyboards with heterogenous layer structures/row counts.</a></h2>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -51,8 +51,8 @@
|
|||
</script>
|
||||
|
||||
<!-- Add keyboard management script for local selection of keyboards to use -->
|
||||
<script src="./issue53.js"></script>
|
||||
<script src="./layered_debug_keyboard-1.0_load.js"></script>
|
||||
<script src="./issue-53.js"></script>
|
||||
<script src="./issue53/layered_debug_keyboard-1.0_load.js"></script>
|
||||
|
||||
</head>
|
||||
|
||||
|
|
@ -46,7 +46,8 @@
|
|||
<script>
|
||||
var kmw=window.tavultesoft.keymanweb;
|
||||
kmw.init({
|
||||
resources:'resources'
|
||||
resources:'resources',
|
||||
attachType:'auto'
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
101
web/samples/issue-63.html
Normal file
101
web/samples/issue-63.html
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
|
||||
<!-- Set the viewport width to match phone and tablet device widths -->
|
||||
<meta name="viewport" content="width=device-width,user-scalable=no" />
|
||||
|
||||
<!-- Allow KeymanWeb to be saved to the iPhone home screen -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
|
||||
<!-- Enable IE9 Standards mode -->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
|
||||
<title>KeymanWeb Sample Page - Uncompiled Source</title>
|
||||
|
||||
<!-- Your page CSS -->
|
||||
<style type='text/css'>
|
||||
body {font-family: Tahoma,helvetica;}
|
||||
h3 {font-size: 1em;font-weight:normal;color: darkred; margin-bottom: 4px}
|
||||
.test {font-size: 1.5em; width:80%; min-height:30px; border: 1px solid gray;}
|
||||
#KeymanWebControl {width:50%;min-width:600px;}
|
||||
</style>
|
||||
|
||||
<!-- Insert uncompiled KeymanWeb source scripts -->
|
||||
<script src="../source/kmwstring.js" type="application/javascript"></script>
|
||||
<script src="../source/kmwbase.js" type="application/javascript"></script>
|
||||
<script src="../source/keymanweb.js" type="application/javascript"></script>
|
||||
<script src="../source/kmwosk.js" type="application/javascript"></script>
|
||||
<script src="../source/kmwnative.js" type="application/javascript"></script>
|
||||
<script src="../source/kmwcallback.js" type="application/javascript"></script>
|
||||
<script src="../source/kmwkeymaps.js" type="application/javascript"></script>
|
||||
<script src="../source/kmwlayout.js" type="application/javascript"></script>
|
||||
<script src="../source/kmwinit.js" type="application/javascript"></script>
|
||||
|
||||
<!--
|
||||
For desktop browsers, a script for the user interface must be inserted here.
|
||||
|
||||
Standard UIs are toggle, button, float and toolbar.
|
||||
The toolbar UI is best for any page designed to support keyboards for
|
||||
a large number of languages.
|
||||
-->
|
||||
<script src="../source/kmwuitoggle.js"></script>
|
||||
|
||||
<!-- Initialization: set paths to keyboards, resources and fonts as required -->
|
||||
<script>
|
||||
var kmw=window.tavultesoft.keymanweb;
|
||||
kmw.init({
|
||||
resources:'resources',
|
||||
attachType:'auto'
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Add keyboard management script for local selection of keyboards to use -->
|
||||
<script src="./issue-63.js"></script>
|
||||
|
||||
</head>
|
||||
|
||||
<!-- Sample page HTML -->
|
||||
<body onload='loadKeyboards();'>
|
||||
<h2>KeymanWeb Sample Page - Uncompiled Source</h2>
|
||||
|
||||
<div id='DynamicTextboxes'>
|
||||
<!--
|
||||
The following DIV is used to position the Button or Toolbar User Interfaces on the page.
|
||||
If omitted, those User Interfaces will appear at the top of the document body.
|
||||
(It is ignored by other User Interfaces.)
|
||||
-->
|
||||
<div id='KeymanWebControl'></div>
|
||||
<h3>This version is used to test KeymanWeb's ability to cope with page mutations.</h3>
|
||||
<!--<iframe src="hello-world.html" height=100></iframe>-->
|
||||
<!-- The following is very useful for testing automatic touch input layouts. -->
|
||||
<!--<input id="plaininput" placeholder="Temporary input for testing touch init." />-->
|
||||
<input type='button' id='btn1' onclick='addInputs();' value='Create Inputs!' />
|
||||
<input type='button' id='btn2' onclick='addIFrame();' value='Insert an iframe.' />
|
||||
<input type='button' id='btn3' onclick="removeInputs();" value="Remove inputs!" />
|
||||
<input type='button' id='btn4' onclick="restoreInputs();" value="Restore removed inputs!" />
|
||||
<hr>
|
||||
</div>
|
||||
<h3><a href="../samples/index.html">Return to testing home page</a></h3>
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
<!--
|
||||
*** DEVELOPER NOTE -- FIREFOX CONFIGURATION FOR TESTING ***
|
||||
*
|
||||
* If the URL bar starts with <b>file://</b>, Firefox may not load the font used
|
||||
* to display the special characters used in the On-Screen Keyboard.
|
||||
*
|
||||
* To work around this Firefox bug, navigate to <b>about:config</b>
|
||||
* and set <b>security.fileuri.strict_origin_policy</b> to <b>false</b>
|
||||
* while testing.
|
||||
*
|
||||
* Firefox resolves website-based CSS URI references correctly without needing
|
||||
* any configuration change, so this change should only be made for file-based testing.
|
||||
*
|
||||
***
|
||||
-->
|
||||
</html>
|
||||
156
web/samples/issue-63.js
Normal file
156
web/samples/issue-63.js
Normal file
|
|
@ -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);
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
|
@ -858,11 +858,7 @@
|
|||
);
|
||||
|
||||
for(var n=0;n<ipList.length;n++) {
|
||||
if(keymanweb.isKMWInput(ipList[n])) {
|
||||
keymanweb.setupTouchElement(ipList[n]);
|
||||
} else { // Always hide the OSK for non-mapped inputs
|
||||
keymanweb.setupNontouchElement(ipList[n]);
|
||||
}
|
||||
keymanweb.setupTouchElement(ipList[n]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -870,7 +866,7 @@
|
|||
* Function setupTouchElement
|
||||
* Scope Private
|
||||
* @param {Element} Pelem An input or textarea element from the page.
|
||||
* @return {Element|null}
|
||||
* @return {boolean} Returns true if it creates a simulated input element for Pelem; false if not.
|
||||
* Description Creates a simulated input element for the specified INPUT or TEXTAREA, comprising:
|
||||
* an outer DIV, matching the position, size and style of the base element
|
||||
* a scrollable DIV within that outer element
|
||||
|
|
@ -882,9 +878,14 @@
|
|||
*/
|
||||
keymanweb.setupTouchElement = function(Pelem)
|
||||
{
|
||||
// The specified element must be validated as a touch element to proceed.
|
||||
// Touch doesn't worry about iframes.
|
||||
if(Pelem.tagName.toLowerCase() == 'iframe') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!keymanweb.isKMWInput(Pelem)) {
|
||||
return null;
|
||||
keymanweb.setupNonKMWTouchElement(Pelem);
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -894,11 +895,14 @@
|
|||
if(Pelem['kmw_ip']) {
|
||||
|
||||
if(keymanweb.inputList.indexOf(Pelem['kmw_ip']) != -1) {
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
|
||||
keymanweb.inputList.push(Pelem['kmw_ip']);
|
||||
return Pelem['kmw_ip']; // May need setup elsewhere since it's just been re-added!
|
||||
keymanweb.inputList.push(Pelem['kmw_ip']);
|
||||
|
||||
console.log("Unexpected state - this element's simulated input DIV should have been removed from the page!");
|
||||
|
||||
return true; // May need setup elsewhere since it's just been re-added!
|
||||
}
|
||||
|
||||
// The simulated touch element doesn't already exist? Time to initialize it.
|
||||
|
|
@ -1068,23 +1072,31 @@
|
|||
// It has to be wrapped in an anonymous function to preserve scope and be applied to each element.
|
||||
(function(xx){
|
||||
xx.base.addEventListener('resize',function(e){
|
||||
keymanweb.updateInput(xx);
|
||||
/* A timeout is needed to let the base element complete its resizing before our
|
||||
* simulated element can properly resize itself.
|
||||
*
|
||||
* Not doing this causes errors if the input elements are resized for whatever reason, such as
|
||||
* changing languages to a text with greater height.
|
||||
*/
|
||||
window.setTimeout(function (){
|
||||
keymanweb.updateInput(xx);
|
||||
}, 1);
|
||||
},false);
|
||||
})(x);
|
||||
|
||||
// And copy the text content
|
||||
keymanweb.setText(x,x.base.value,null);
|
||||
|
||||
return x;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function setupNontouchElement
|
||||
* Function setupNonKMWTouchElement
|
||||
* Scope Private
|
||||
* @param {Object} x A child element of document.
|
||||
* Description Performs handling for the specified disabled input element on touch-based systems.
|
||||
*/
|
||||
keymanweb.setupNontouchElement = function(x) {
|
||||
keymanweb.setupNonKMWTouchElement = function(x) {
|
||||
x.addEventListener('touchstart',function() {
|
||||
keymanweb.focusing=false;
|
||||
clearTimeout(keymanweb.focusTimer);
|
||||
|
|
@ -2824,7 +2836,7 @@
|
|||
{
|
||||
if(t[i] == activeBase) break;
|
||||
}
|
||||
|
||||
|
||||
// Find the next (or previous) element in the list
|
||||
if(bBack) i=i-1; else i=i+1;
|
||||
if(i >= 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue