Chrome memory leak on dispatchEvent - javascript

const focusEvent = new FocusEvent('focus', {
bubbles: true
});
// Trigger the input value in the search box
const inputEvent = new InputEvent('input', {
bubbles: true
});
// Send enter
const keyEvent = new KeyboardEvent('keydown', {
code: 'Enter',
key: 'Enter',
keyCode: 13,
view: window,
bubbles: true
});
let search = document.querySelector('#side > div._1Ra05 > div > label > div > div._1awRl.copyable-text.selectable-text');
function searchList(name = "") {
try {
search.textContent = name;
search.dispatchEvent(focusEvent);
search.dispatchEvent(inputEvent);
search.dispatchEvent(keyEvent);
search.removeEventListener('focus', focusEvent);
search.removeEventListener('input', inputEvent);
search.removeEventListener('keydown', keyEvent);
} catch { console.log(error); };
}
I'm using puppeteer and inside await page.evaluate(async ({ i have a loop that calls the function searchList on each 100 ms, the problem is the browser mem usage is growing constantly, after 30min its over 1GB of ram.
I discovered that whos causing it is the search.dispatchEvent lines, when i comment they the mem does not increase.
I "tried" to solve it adding:
search.removeEventListener('focus', focusEvent);
search.removeEventListener('input', inputEvent);
search.removeEventListener('keydown', keyEvent);
But it did not change anything in relation to the memory increasing over time, does someone have any idea what else i could try to stop or release the memory growing?

Related

MutationObserver in MutationObserver Callback

I was adding a MutationObserver in my JavaScript code and applied it to a specific element in an iFrame. The detection works fine but now I need a second observer. After the first click in the iFrame the whole DOM of it changes and that's the moment where the first observer comes in and detects any other clicks on the navigation elements in the iFrame. Additionally to that I need to observe one div element and if it's text will be changed or not. So the idea was to create a second observer after a navigation button is clicked but it seems not to work since the second observer doesn't give any output on my console.
Here is the JavaScript code:
$('iframe').on('load', function() {
let observer = new MutationObserver(mutationRecords => {
let completed = false;
var progress = $('iframe').contents().find('.overview-sidebar__header .progress-bar__percentage-bottom').first();
let clickedClass = mutationRecords[0].target.attributes[0].nodeValue;
if(clickedClass == 'transition-group') {
console.log(progress.text());
console.log(Math.ceil(Date.now() / 1000));
} else if(clickedClass == 'page-wrap' && !mutationRecords[0].nextSibling) {
let secondObserver = new MutationObserver(mutationRecords => { console.log('Test') });
secondObserver .observe($('iframe').contents().find('.transition-group').first()[0], {
childList: true,
subtree: true
});
console.log(Math.ceil(Date.now() / 1000));
$('iframe').contents().find('.section-lists .lesson-lists__item').each(function(index) {
console.log(index);
console.log($(this).find('.lesson-link__name > span').first().text());
console.log($(this).find('.lesson-link__progress title').text().split('%')[0]);
});
}
});
observer.observe($('iframe').contents().find('.transition-group').first()[0], {
childList: true,
subtree: true
});
});
The secondObserver in the else if branch is the problem here. Anyone has an idea how to solve this?

How do I use 'click' and 'dbclick' events on one element?

I have a tag element for a web page. when clicking once, one logic is executed, when clicking twice, another. However, the DOM only responds to one click and fires immediately, no matter how quickly you try to fire a double click event.
How do I use these two events on one element?
export const createTag = (name) => {
const tag = document.createElement('span');
tag.innerHTML = name;
tag.addEventListener('dblclick', () => {
tags.removeChild(tag);
storeTags.splice(storeTags.indexOf(name), 1);
});
tag.addEventListener('click', () => {
search.value = tag.innerHTML;
const keyboardEvent = new KeyboardEvent('keypress', {
code: 'Enter',
key: 'Enter',
charCode: 13,
keyCode: 13,
view: window,
bubbles: true,
});
storeTags.splice(storeTags.indexOf(name), 1);
storeTags.unshift(name);
search.dispatchEvent(keyboardEvent);
});
return tag;
};
The basic idea to achieve this would be to add some small delay to the click operation, and cancel that if a dblclick is received. This does lead to reduced responsiveness of your UI, as your application is now having to explicitly distinguish between single and double clicks in a kind of "wait and see" approach. This can also be unreliable as the user may set any length of time as their double-click threshold in their device's Accessibility settings.
However, if these issues are deemed not to be enough of a concern, the "best of the worst" way to do this would be to manually implement double-click checks.
export const createTag = (name) => {
const tag = document.createElement('span');
tag.innerHTML = name;
const clickHander = () => {
search.value = tag.innerHTML;
const keyboardEvent = new KeyboardEvent('keypress', {
code: 'Enter',
key: 'Enter',
charCode: 13,
keyCode: 13,
view: window,
bubbles: true,
});
storeTags.splice(storeTags.indexOf(name), 1);
storeTags.unshift(name);
search.dispatchEvent(keyboardEvent);
};
const dblClickHandler = () => {
tags.removeChild(tag);
storeTags.splice(storeTags.indexOf(name), 1);
};
let clickTimer;
tag.addEventListener('click', () => {
if( clickTimer) {
clearTimeout(clickTimer);
dblClickHandler();
clickTimer = null;
}
else {
clickTimer = setTimeout(() => {
clickHandler();
clickTimer = null;
}, 500);
}
});
return tag;
};
Adjust the timer as you feel appropriate. Smaller numbers will lead to more responsive clicks, but will make double-clicking harder for people with reduced mobility.
I would really strongly recommend using a different UI here. Overloading clicks in this way is a really bad idea in general.

Mapbox add popup on hover (layer), close on mouseleave, but keep open on popup hover

I implemented the example just fine:
https://docs.mapbox.com/mapbox-gl-js/example/popup-on-hover/
but I'm struggling to keep the popup open, when hovering the popup (which means leaving the symbol layer) BUT closing the popup when leaving it.
I tried several combinations of event handlers (even a mousemove handler), but since the layer is drawn on canvas, but popup is an domNode I didnt find a solution.
Anyone know where is is implemented, or how to do it?
My Goal is similar behaviour like googla maps on web:
Sample Code from Docs:
// Add a layer showing the places.
map.addLayer({
'id': 'places',
'type': 'circle',
'source': 'places',
'paint': {
'circle-color': '#4264fb',
'circle-radius': 6,
'circle-stroke-width': 2,
'circle-stroke-color': '#ffffff'
}
});
// Create a popup, but don't add it to the map yet.
const popup = new mapboxgl.Popup({
closeButton: false,
closeOnClick: false
});
map.on('mouseenter', 'places', (e) => {
// Change the cursor style as a UI indicator.
map.getCanvas().style.cursor = 'pointer';
// Copy coordinates array.
const coordinates = e.features[0].geometry.coordinates.slice();
const description = e.features[0].properties.description;
// Ensure that if the map is zoomed out such that multiple
// copies of the feature are visible, the popup appears
// over the copy being pointed to.
while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
}
// Populate the popup and set its coordinates
// based on the feature found.
popup.setLngLat(coordinates).setHTML(description).addTo(map);
});
map.on('mouseleave', 'places', () => {
map.getCanvas().style.cursor = '';
popup.remove();
});
Recently I have the same problem.
My workaround is to have a function that will close the popup when the userleaves the icon layer or the popup.
If before the timeout is fullfilled the user enters in the popup content, clear the timeout function. (The timeout function will also be called when the user leave popup content).
// TEST modal variables
// popupTEST is the popup where the content will be insert
popupTEST = new mapboxgl.Popup({
closeButton: false,
closeOnClick: false,
className: "popupTEST"
});
// The close of the modal will be via timeout in order to stop removing in when hover the popUp content
popupTESTCloseTimeout = null;
....
this.map.on("mouseenter", "test", (e) => {
this.map.getCanvas().style.cursor = "pointer";
// Clear close timeout and delete posible popup
this.clearPopupTESTCloseTimeout();
this.popupTEST.remove();
if (e && e.features && e.features.length > 0 && e.features[0].properties) {
const popupcontent = document.createElement("div");
// Create a content using the properties ...
// Is a react component, but you can use plain javascript
const Contents = () => (
<TestModalContent
properties={properties}
onMouseEnter={this.clearPopupOACCloseTimeout}
onMouseLeave={this.closePopupOACWithTimeout}
/>
);
ReactDOM.render(Contents(), popupcontent);
this.popupTEST.setLngLat([long, lat]).setMaxWidth("none").setDOMContent(popupcontent).addTo(_this.map);
}
});
this.map.on("mouseleave", "sit-TEST", () => {
this.map.getCanvas().style.cursor = "";
this.closePopupTESTWithTimeout();
});
closePopupTESTWithTimeout = () => {
this.popupTESTCloseTimeout = setTimeout(() => this.popupTEST.remove(), 500);
}
clearPopupTESTCloseTimeout = () => {
if (this.popupTESTCloseTimeout) {
clearTimeout(this.popupTESTCloseTimeout);
this.popupTESTCloseTimeout = null;
}
}
Solution for now:
setting misc. variables on misc. events, then checking in a mousemove handler
const popupDom = document.getElementsByClassName("project-marker-popup");
const popup = new mapboxgl.Popup({
className: "project-marker-popup",
maxWidth: "300px",
});
let popUpIsOpen = false;
let mouseOnCircles = false;
// ...
window.addEventListener(
"mousemove",
debounce(250, (e) => {
const mouseOnPopup = e.target.closest(".project-marker-popup");
console.log("mouse on popup", mouseOnPopup);
console.log("popup is open", popUpIsOpen);
console.log("mouse on circles", mouseOnCircles);
if (mouseOnPopup || mouseOnCircles) {
console.log("doing nothing");
return;
} else {
if (popup.remove()) {
popUpIsOpen = false;
} else {
console.error("couldnt close popup");
}
}
})
);
// ...
For folks who are okay with the popup overlapping the feature, I've had success with the default style and its bit of overlap of the point and hijacking the map level onMouseMove, this works because when a user is hovering a popup, onMouseMove isn't firing.
const [popupFeature, setPopupFeature] = useState(null)
const onFeatureHover = (f) => setPopupFeature(f)
handleMouseMove(e) {
e.features.length ? onFeatureHover(e.features[0]) :
setPopupFeature(null)
return (
<Map
onMouseMove={(e) => handleMouseMove(e)}
/>
)

Undefined blot inserted after embedding custom inline blot in Quill editor

I am working on twitter like user mentions for Quill editor.
My custom blot code is
import Quill from 'quill';
const Base = Quill.import('blots/inline');
export default class MentionBlot extends Base {
static create(data) {
const node = super.create(data.name);
node.innerHTML = `#${data.name}`;
node.setAttribute('contenteditable', false);
node.setAttribute('name', data.name);
node.setAttribute('id', data.id);
return node;
}
MentionBlot.blotName = 'usermention';
MentionBlot.tagName = 'aumention';
I am displaying users list in dropdown. When ever one of the user name is clicked, I am embedding the user as #User in quill editor.
this is the click event I had written for it. The thing I am doing here is I am replacing the text user entered after # with the custom Inline blot.
searchResult.forEach(element => {
element.addEventListener('click', e => {
e.preventDefault();
e.stopPropagation();
const quillEditor = window.editor;
const content = quillEditor.getText();
const quillRange = quillEditor.selection.savedRange; // cursor position
if (!quillRange || quillRange.length != 0) return;
const cursorPosition = quillRange.index;
let mentionStartAt = 0;
let lengthToBeDeleted = 0;
for (let i = cursorPosition - 1; i >= 0; --i) {
const char = content[i];
if (char == '#') {
mentionStartAt = i;
lengthToBeDeleted += 1;
break;
} else {
lengthToBeDeleted += 1;
}
}
const data = {
name: element.innerHTML,
id: element.getAttribute('id')
};
quillEditor.deleteText(mentionStartAt, lengthToBeDeleted);
quillEditor.insertEmbed(mentionStartAt, 'usermention', data, 'api');
const cursorAfterDelete =
quillEditor.selection.savedRange.index + element.innerHTML.length;
quillEditor.insertText(cursorAfterDelete + 1, ' ', 'api');
quillEditor.setSelection(cursorAfterDelete + 2, 'api');
quillEditor.format('usermention', false, 'api');
});
});
}
Until here, everything is working like charm but the issue I am facing is after inserting the embed usermention blot, If the user enters Enter button on Keyboard to go to new line, Quill's handleEnter() function is getting triggered and it is inserting #undefined usermention blot to the editor.
After executing the above function, my editor state is this.
When I press enter to go to new line, this is the debug state of handleEnter() function - Quill
#undefined usermention got inserted into the editor. I want the user to enter new line.
When the user presses Enter, I understood that quill.format() is returning usermention:true. But if the user presses Enter after typing few more characters, it is taking him to new line and in this case quill.format() is empty.
Can some one please help me in this regard. Thank you.
Reference: https://quilljs.com/docs/modules/keyboard/
Handling enter with Quill keyboard binding is easier than adding addLister to it, the below method helps you to handle whenever the enter event fires in the quill editor
var quill = new Quill('#editor', modules: {
keyboard: {
bindings: bindings
}}});
var bindings = {
handleEnter: {
key: '13', // enter keycode
handler: function(range, context) {
//You can get the context here
}
}
};
I hope the above answer suits your needs.

Detect when input box filled by keyboard and when by barcode scanner.

How I can programmatically detect when text input filled by typing on keyboard and when it filled automatically by bar-code scanner?
I wrote this answer, because my Barcode Scanner Motorola LS1203 generated keypress event, so I can't use Utkanos's solution.
My solution is:
var BarcodeScanerEvents = function() {
this.initialize.apply(this, arguments);
};
BarcodeScanerEvents.prototype = {
initialize: function() {
$(document).on({
keyup: $.proxy(this._keyup, this)
});
},
_timeoutHandler: 0,
_inputString: '',
_keyup: function (e) {
if (this._timeoutHandler) {
clearTimeout(this._timeoutHandler);
this._inputString += String.fromCharCode(e.which);
}
this._timeoutHandler = setTimeout($.proxy(function () {
if (this._inputString.length <= 3) {
this._inputString = '';
return;
}
$(document).trigger('onbarcodescaned', this._inputString);
this._inputString = '';
}, this), 20);
}
};
Adapted the super useful Vitall answer above to utilize an IIFE instead of prototyping, in case anyone just seeing this now is into that.
This also uses the 'keypress' event instead of keyup, which allowed me to reliably use KeyboardEvent.key, since KeyboardEvent.which is deprecated now. I found this to work for barcode scanning as well as magnetic-strip card swipes.
In my experience, handling card swipes with keyup caused me to do extra work handling 'Shift' keycodes e.g. a Shift code would be followed by the code representing '/', with the intended character being '?'. Using 'keypress' solved this as well.
(function($) {
var _timeoutHandler = 0,
_inputString = '',
_onKeypress = function(e) {
if (_timeoutHandler) {
clearTimeout(_timeoutHandler);
}
_inputString += e.key;
_timeoutHandler = setTimeout(function () {
if (_inputString.length <= 3) {
_inputString = '';
return;
}
$(e.target).trigger('altdeviceinput', _inputString);
_inputString = '';
}, 20);
};
$(document).on({
keypress: _onKeypress
});
})($);
Well a barcode won't fire any key events so you could do something like:
$('#my_field').on({
keypress: function() { typed_into = true; },
change: function() {
if (typed_into) {
alert('type');
typed_into = false; //reset type listener
} else {
alert('not type');
}
}
});
Depending on when you want to evaluate this, you may want to do this check not on change but on submit, or whatever.
you can try following example, using jQuery plugin https://plugins.jquery.com/scannerdetection/
Its highly configurable, time based scanner detector. It can be used as solution for prefix/postfix based, time based barcode scanner.
Tutorial for usage and best practices, as well discussed about various Barcode Scanner Models and how to deal with it. http://a.kabachnik.info/jquery-scannerdetection-tutorial.html
$(window).ready(function(){
//$("#bCode").scannerDetection();
console.log('all is well');
$(window).scannerDetection();
$(window).bind('scannerDetectionComplete',function(e,data){
console.log('complete '+data.string);
$("#bCode").val(data.string);
})
.bind('scannerDetectionError',function(e,data){
console.log('detection error '+data.string);
})
.bind('scannerDetectionReceive',function(e,data){
console.log('Recieve');
console.log(data.evt.which);
})
//$(window).scannerDetection('success');
<input id='bCode'type='text' value='barcode appears here'/>
For ES6 2019 version of Vitall answer.
const events = mitt()
class BarcodeScaner {
initialize = () => {
document.addEventListener('keypress', this.keyup)
if (this.timeoutHandler) {
clearTimeout(this.timeoutHandler)
}
this.timeoutHandler = setTimeout(() => {
this.inputString = ''
}, 10)
}
close = () => {
document.removeEventListener('keypress', this.keyup)
}
timeoutHandler = 0
inputString = ''
keyup = (e) => {
if (this.timeoutHandler) {
clearTimeout(this.timeoutHandler)
this.inputString += String.fromCharCode(e.keyCode)
}
this.timeoutHandler = setTimeout(() => {
if (this.inputString.length <= 3) {
this.inputString = ''
return
}
events.emit('onbarcodescaned', this.inputString)
this.inputString = ''
}, 10)
}
}
Can be used with react hooks like so:
const ScanComponent = (props) => {
const [scanned, setScanned] = useState('')
useEffect(() => {
const barcode = new BarcodeScaner()
barcode.initialize()
return () => {
barcode.close()
}
}, [])
useEffect(() => {
const scanHandler = code => {
console.log(code)
setScanned(code)
}
events.on('onbarcodescaned', scanHandler)
return () => {
events.off('onbarcodescaned', scanHandler)
}
}, [/* here put dependencies for your scanHandler ;) */])
return <div>{scanned}</div>
}
I use mitt from npm for events, but you can use whatever you prefer ;)
Tested on Zebra DS4208
The solution from Vitall only works fine if you already hit at least one key. If you don't the first character will be ignored (if(this._timeoutHandler) returns false and the char will not be appended).
If you want to begin scanning immediately you can use the following code:
var BarcodeScanerEvents = function() {
this.initialize.apply(this, arguments);
};
BarcodeScanerEvents.prototype = {
initialize : function() {
$(document).on({
keyup : $.proxy(this._keyup, this)
});
},
_timeoutHandler : 0,
_inputString : '',
_keyup : function(e) {
if (this._timeoutHandler) {
clearTimeout(this._timeoutHandler);
}
this._inputString += String.fromCharCode(e.which);
this._timeoutHandler = setTimeout($.proxy(function() {
if (this._inputString.length <= 3) {
this._inputString = '';
return;
}
$(document).trigger('onbarcodescaned', this._inputString);
this._inputString = '';
}, this), 20);
}
};
If you can set a prefix to your barcode scanner I suggests this (I changed a bit the Vitall code):
var BarcodeScanner = function(options) {
this.initialize.call(this, options);
};
BarcodeScanner.prototype = {
initialize: function(options) {
$.extend(this._options,options);
if(this._options.debug) console.log("BarcodeScanner: Initializing");
$(this._options.eventObj).on({
keydown: $.proxy(this._keydown, this),
});
},
destroy: function() {
$(this._options.eventObj).off("keyup",null,this._keyup);
$(this._options.eventObj).off("keydown",null,this._keydown);
},
fire: function(str){
if(this._options.debug) console.log("BarcodeScanner: Firing barcode event with string: "+str);
$(this._options.fireObj).trigger('barcode',[str]);
},
isReading: function(){
return this._isReading;
},
checkEvent: function(e){
return this._isReading || (this._options.isShiftPrefix?e.shiftKey:!e.shiftKey) && e.which==this._options.prefixCode;
},
_options: {timeout: 600, prefixCode: 36, suffixCode: 13, minCode: 32, maxCode: 126, isShiftPrefix: false, debug: false, eventObj: document, fireObj: document},
_isReading: false,
_timeoutHandler: false,
_inputString: '',
_keydown: function (e) {
if(this._input.call(this,e))
return false;
},
_input: function (e) {
if(this._isReading){
if(e.which==this._options.suffixCode){
//read end
if(this._options.debug) console.log("BarcodeScanner: Read END");
if (this._timeoutHandler)
clearTimeout(this._timeoutHandler);
this._isReading=false;
this.fire.call(this,this._inputString);
this._inputString='';
}else{
//char reading
if(this._options.debug) console.log("BarcodeScanner: Char reading "+(e.which));
if(e.which>=this._options.minCode && e.which<=this._options.maxCode)
this._inputString += String.fromCharCode(e.which);
}
return true;
}else{
if((this._options.isShiftPrefix?e.shiftKey:!e.shiftKey) && e.which==this._options.prefixCode){
//start reading
if(this._options.debug) console.log("BarcodeScanner: Start reading");
this._isReading=true;
this._timeoutHandler = setTimeout($.proxy(function () {
//read timeout
if(this._options.debug) console.log("BarcodeScanner: Read timeout");
this._inputString='';
this._isReading=false;
this._timeoutHandler=false;
}, this), this._options.timeout);
return true;
}
}
return false;
}
};
If you need you customize timeout, suffix, prefix, min/max ascii code readed:
new BarcodeScanner({timeout: 600, prefixKeyCode: 36, suffixKeyCode: 13, minKeyCode: 32, maxKeyCode: 126});
I also added the isShiftPrefix option to use for example the $ char as prefix with these options: new BarcodeScanner({prefixKeyCode: 52, isShiftPrefix: true});
This is a fiddle: https://jsfiddle.net/xmt76ca5/
You can use a "onkeyup" event on that input box. If the event has triggered then you can called it "Input from Keyboard".
$(window).ready(function(){
//$("#bCode").scannerDetection();
console.log('all is well');
$(window).scannerDetection();
$(window).bind('scannerDetectionComplete',function(e,data){
console.log('complete '+data.string);
$("#bCode").val(data.string);
})
.bind('scannerDetectionError',function(e,data){
console.log('detection error '+data.string);
})
.bind('scannerDetectionReceive',function(e,data){
console.log('Recieve');
console.log(data.evt.which);
})
//$(window).scannerDetection('success');
<input id='bCode'type='text' value='barcode appears here'/>
Hi I have and alternative solution for evaluate a result of the bar code scanner without use of jQuery, first you need and input text that have a focus the moment that the barcode scanner is works
<input id="input_resultado" type="text" />
The code in JavaScript is:
var elmInputScan = document.getElementById('input_resultado');
elmInputScan.addEventListener('keypress', function (e){
clearInterval( timer_response );
timer_response = setTimeout( "onInputChange()", 10);
});
When the barcode scanner input the text call serveral times to the keypress event, but only I interested to the final result, for this reason I use the timer. That's all, you can process the value into the onInputChange function.
function onInputChange() {
console.log( document.getElementById('input_resultado').value );
}
document.addEventListener("keypress", function (e) {
if (e.target.tagName !== "INPUT") {
// it's your scanner
}
});
None of the solutions worked for me because I don't want to focus on an input. I want the result page(item details page) to keep listening for the scanner to scan next item. My scanner fires the keypress event so this worked like a charm for me.
var inputTemp = '';
var inputTempInterval = setInterval(function() {
// change 5 as minimum length of the scan code
if (inputTemp.length >= 5) {
var detected = inputTemp;
inputTemp = '';
clearInterval(inputTempInterval); // stop listening if you don't need anymore
onScannerTrigger(detected);
} else {
inputTemp = '';
}
}, 100);
$(window).keypress(function(e){
inputTemp += String.fromCharCode(e.which);
});
function onScannerTrigger(scannedCode) {
console.log(scannedCode);
// do your stuff
}
I have published a lightweight JS package which doesn't rely on jQuery or input fields. It simple looks at the timing of the keyPress-events to determine wether it was a barcode scanner or regular input.
https://www.npmjs.com/package/#itexperts/barcode-scanner
import {BarcodeScanner} from "#itexperts/barcode-scanner";
let options = {
timeOut: 130,
characterCount: 13
}
let barcodeScanner = new BarcodeScanner(options);
barcodeScanner.addEventListener('scan', function(e){
let barcode = e.detail;
console.log(barcode);
});
I highly recommend this js plugin https://github.com/axenox/onscan.js
It's easy to use and has tones of options to meet your need.
<script src="path-to-onScan.js"></script>
<script>
// Initialize with options
onScan.attachTo(document, {
suffixKeyCodes: [13], // enter-key expected at the end of a scan
reactToPaste: true, // Compatibility to built-in scanners in paste-mode (as opposed to keyboard-mode)
onScan: function(sCode, iQty) { // Alternative to document.addEventListener('scan')
console.log('Scanned: ' + iQty + 'x ' + sCode);
},
onKeyDetect: function(iKeyCode){ // output all potentially relevant key events - great for debugging!
console.log('Pressed: ' + iKeyCode);
}
});
</script>

Categories

Resources