Source: main/webapp/modules/InputSink.js

  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. var Guacamole = Guacamole || {};
  20. /**
  21. * A hidden input field which attempts to keep itself focused at all times,
  22. * except when another input field has been intentionally focused, whether
  23. * programatically or by the user. The actual underlying input field, returned
  24. * by getElement(), may be used as a reliable source of keyboard-related events,
  25. * particularly composition and input events which may require a focused input
  26. * field to be dispatched at all.
  27. *
  28. * @constructor
  29. */
  30. Guacamole.InputSink = function InputSink() {
  31. /**
  32. * Reference to this instance of Guacamole.InputSink.
  33. *
  34. * @private
  35. * @type {!Guacamole.InputSink}
  36. */
  37. var sink = this;
  38. /**
  39. * The underlying input field, styled to be invisible.
  40. *
  41. * @private
  42. * @type {!Element}
  43. */
  44. var field = document.createElement('textarea');
  45. field.style.position = 'fixed';
  46. field.style.outline = 'none';
  47. field.style.border = 'none';
  48. field.style.margin = '0';
  49. field.style.padding = '0';
  50. field.style.height = '0';
  51. field.style.width = '0';
  52. field.style.left = '0';
  53. field.style.bottom = '0';
  54. field.style.resize = 'none';
  55. field.style.background = 'transparent';
  56. field.style.color = 'transparent';
  57. // Keep field clear when modified via normal keypresses
  58. field.addEventListener("keypress", function clearKeypress(e) {
  59. field.value = '';
  60. }, false);
  61. // Keep field clear when modofied via composition events
  62. field.addEventListener("compositionend", function clearCompletedComposition(e) {
  63. if (e.data)
  64. field.value = '';
  65. }, false);
  66. // Keep field clear when modofied via input events
  67. field.addEventListener("input", function clearCompletedInput(e) {
  68. if (e.data && !e.isComposing)
  69. field.value = '';
  70. }, false);
  71. // Whenever focus is gained, automatically click to ensure cursor is
  72. // actually placed within the field (the field may simply be highlighted or
  73. // outlined otherwise)
  74. field.addEventListener("focus", function focusReceived() {
  75. window.setTimeout(function deferRefocus() {
  76. field.click();
  77. field.select();
  78. }, 0);
  79. }, true);
  80. /**
  81. * Attempts to focus the underlying input field. The focus attempt occurs
  82. * asynchronously, and may silently fail depending on browser restrictions.
  83. */
  84. this.focus = function focus() {
  85. window.setTimeout(function deferRefocus() {
  86. field.focus(); // Focus must be deferred to work reliably across browsers
  87. }, 0);
  88. };
  89. /**
  90. * Returns the underlying input field. This input field MUST be manually
  91. * added to the DOM for the Guacamole.InputSink to have any effect.
  92. *
  93. * @returns {!Element}
  94. * The underlying input field.
  95. */
  96. this.getElement = function getElement() {
  97. return field;
  98. };
  99. // Automatically refocus input sink if part of DOM
  100. document.addEventListener("keydown", function refocusSink(e) {
  101. // Do not refocus if focus is on an input field
  102. var focused = document.activeElement;
  103. if (focused && focused !== document.body) {
  104. // Only consider focused input fields which are actually visible
  105. var rect = focused.getBoundingClientRect();
  106. if (rect.left + rect.width > 0 && rect.top + rect.height > 0)
  107. return;
  108. }
  109. // Refocus input sink instead of handling click
  110. sink.focus();
  111. }, true);
  112. };