cytoscape.js "e.originalEvent.x" with different behavior on browsers - javascript

I'm trying to build a crossplatform tool with cytoscape.js, wich includes mobile devices, but I'm facing some problems when using the "tap" function to add a node:
cy.on('tap', function(e) {
if(e.cyTarget === cy) {
if($("input:radio[id=btnAddNode]")[0].checked) {
var idNum = cy.nodes().size(),
setID = idNum.toString(),
offset = $cy.offset(),
position = {
x: e.originalEvent.x - offset.left,
y: e.originalEvent.y - offset.top
};
console.log("e.originalEvent.x: " + e.originalEvent.x);
console.log("offset.left: " + offset.left);
console.log("e.originalEvent.y: " + e.originalEvent.y);
console.log("offset.top: " + offset.top);
cy.add([{
group: "nodes",
data: {
id: "n" + setID
},
renderedPosition: {
x: position.x,
y: position.y
},
}]);
} else {
//show node data
}
}
});
On Chrome for Windows 7 (v.32) and also on Safari it is working as expected.
On IE 9 it's inserting the node about 30px higher and 5px left than the point where we click (?!)
But on Firefox (v.26) and Safari for iOS it's not working, it put's the node on the top-left corner of the canvas. Firefox debug says that the "originalEvent.x" and "originalEvent.y" properties are undefined
"e.originalEvent.x: undefined"
"offset.left: 8"
"e.originalEvent.y: undefined"
"offset.top: 55.66667175292969"
So, is this property obsolete? If so, what should be the best way to improve this code?
Thanks in advance.
EDIT: Almost solved, noticed that using e.originalEvent.pageX and e.originalEvent.pageY instead works well over all desktop browsers and on Windows Phone 8, except on iOS :(

Though the events in Cytoscape.js are similar to jQuery events, they are not jQuery events. Properties like pageX are available through e.originalEvent, though they are not normalised to e. In the upcoming v2.1 (or if you make zip in the repo), events have been updated with e.cyPosition and e.cyRenderedPosition. These values respectively provide the model position and rendered (on screen, relative to cy.js instance) position.
Also note that I don't think your approach would work on iOS etc even using jQuery events, because touch events aren't normalised. e.cyPosition and e.cyRenderedPosition do support touch events, though.

Related

How to get the e.screenX/Y relative to the host screen on window.onmousemove when using a secondary monitor?

I thought this one would be easy, but it turns out it's not.
Take the following situation: a primary desktop on the bottom, and a secondary desktop above that primary desktop.
The objective is to get the mouse X and Y position relative to the screen hosting the window on the secondary desktop. The naive approach would be:
const onMouseMove = e => {
const hostScreenX = e.screenX;
const hostScreenY = e.screenY;
document.body.innerHTML = `${hostScreenX}x${hostScreenY}`;
};
window.addEventListener("mousemove", onMouseMove, false);
Turns out this returns a negative number for screenY when running on the secondary screen (the top one). The primary screen is seen as the "zero boundary" and everything before that is in a negative space.
We can account for this using screen.availTop and screen.availLeft:
const onMouseMove = e => {
const hostScreenX = e.screenX - window.screen.availLeft;
const hostScreenY = e.screenY - window.screen.availTop;
document.body.innerHTML = `${hostScreenX}x${hostScreenY}`;
};
window.addEventListener("mousemove", onMouseMove, false);
Much better, except it's actually incorrect: because the availTop space includes the space of the permanent operating system UI (on MacOS the toolbar, and on Windows the toolbar if you moved the toolbar from the default bottom to the top), we are incorrectly subtracting the toolbar space.
E.g. on MacOS in chrome when moving the mouse to the far top this would give us a hostScreenY of 80, whereas it should be 103 (the macos toolbar is 23 pixels).
You might then think that we can solve for this by adding back the difference in height - availHeight:
const onMouseMove = e => {
const hostScreenX = e.screenX - window.screen.availLeft + (window.screen.width - window.screen.availWidth);
const hostScreenY = e.screenY - window.screen.availTop + (window.screen.height - window.screen.availHeight);
document.body.innerHTML = `${hostScreenX}x${hostScreenY}`;
};
window.addEventListener("mousemove", onMouseMove, false);
And indeed this seems to be perfect on MacOS.
The problem is that this assumes that the toolbar of the operating system is on the top of the screen, which is usually not true on Windows (though sometimes it is if you move your toolbar to the top). As a result, the values are offset by the toolbar space.
How can we get the screenX and screenY relative to the screen hosting the current window when using secondary (or even tertiary) monitors on the mousemove event in Javascript?
According to my research, it is currently not possible with JavaScript to get differentiated information about screens and screen sizes, not to mention location and/or size of OS-specific stuff like the macOS menu bar.
See for example Testing for multiple screens with javascript, where the best idea is to make educated guesses.
I tried the MediaDevices API (mediaDevices.enumerateDevices), but that doesn't reveal information about screen devices:
And I fear that we don't get this info anytime soon, at least without asking for users consent, because the browser vendors go to great lengths to prevent browser fingerprinting (see amiunique.org for an explanation).
If you can package your code as an Electron application you can access all info needed with the screen API:
Electron REPL, for example:
$ npx electron --interactive
> const { screen } = require('electron')
undefined
> screen.getAllDisplays()
[
{
id: 69733632,
bounds: { x: 0, y: 0, width: 1440, height: 900 },
workArea: { x: 0, y: 23, width: 1440, height: 877 },
accelerometerSupport: 'unknown',
monochrome: false,
colorDepth: 30,
colorSpace: '{primaries:SMPTEST432_1, transfer:IEC61966_2_1, matrix:RGB, range:FULL}',
depthPerComponent: 10,
size: { width: 1440, height: 900 },
workAreaSize: { width: 1440, height: 877 },
scaleFactor: 2,
rotation: 0,
internal: false,
touchSupport: 'unknown'
}
]
(I currently have no second display attached)
With bounds: { x: 0, y: 0, width: 1440, height: 900 } (and workArea: ...) you can calculate the size and position of the elements occupying screen space, like menu bars, toolbars or docks.
If you could package your code in a Chrome extension, you can use the
system.display API, getInfo() ⇾ workArea
Is this solution suitable for your requirements?
It also works with 2nd monitor on the left of the 1st one, with the toolbar of the operating system on the right side of 1st monitor.
const onMouseMove = e => {
const { screen } = window;
const { availLeft, availTop } = screen;
const hostScreenX=e.screenX-availLeft+ (availLeft ? (screen.width - screen.availWidth) : 0);
const hostScreenY=e.screenY-availTop+ (availTop ? (screen.height - screen.availHeight) : 0);
document.body.innerHTML = `${devicePixelRatio} ${hostScreenX}x${hostScreenY} ` +
`${hostScreenX * devicePixelRatio}x${hostScreenY * devicePixelRatio}`;
};
window.addEventListener("mousemove", onMouseMove, false);
P.S.
I have only a Windows 10 PC, I could test this only on my platform
Edit: If we need the coordinates in actual screen resolution we can use the devicePixelRatio multiplayer. The problem is that devicePixelRatio could change between screens. If we need to know the coordinates only till the mouse pointer is in the screen hosting our window, no problem; if we need to continue getting the coordinates on the other screens, many other problems came in the scene. I suggest to discuss them in future edits if really required.

Why is a Safari page breaking iOS rendering?

I know the title is not that explanatory but here is the story: I am developing a browser game, mostly using JavaScript and the Mapbox library.
Everything works well on desktop, Android and iOS but one problem appears on iOS: after letting the game run for a few minutes the phone suddenly begins to have graphic artifacts and display most of the text scrambled.
Here are some photos of what the phone begins too look like:
My question is: what exactly in my code can cause this? A memory leak? (LE: it turned out to actually be a memory leak)
The real question is: How comes that you can almost brick the entire phone by simply browsing a web page? Shouldn't Safari stop this, or at least the iOS ?
This is not a problem with this specific device, as this problem can be reproduced on different iPhone devices. (I'm not so sure about different iOS versions).
How I can reproduce the error:
Open the game (inside Safari).
Let it run for 3-4 minutes.
Slide down the notification center and everything goes crazy.
I have added a YouTube video showing how I can reproduce the error (on my iPhone 5C).
It seems that the issue first appears in the notification center (if you swipe down the menu from the top).
As for now, this problem seems to only occur on iPhone 5C iOS 9.2.1 (13D15). It also occurs on the new iOS 9.3 version.
In order to fix this issue I have to:
Close the Safari application (in which the game tab is open).
Lock the phone. After unlocking it everything is back to normal.
Some details about the game itself:
The game shows a Mapbox map and some units over it (markers).
A Node.js server runs at 1 tick/second and after each tick the updated game state is sent to the browser through Socket.io.
Every time the browser receives the game state it updates the markers accordingly.
*The game might also update markers if you zoom in or out or if you select them.
EDIT2:
Found the memory leak (as expected). After fixing this leak (check for undefined _icon) the issue no longer occurs. This means, that somewhere along those lines the Safari/iOS bug is triggered.
Here is what exactly was being called each tick, for each unit that was clustered (was hidden and grouped with others inside a MarkerCluster):
var $icon = $(marker._icon); // marker._icon is undefined because of the clustering
$icon.html('');
$icon.append($('<img class="markerIcon" src="' + options.iconUrl + '" />'));
var iconX = 10;
var iconY = -10;
var iconOffset = 0;
for(var v in this.icons) {
this.icons[v].css('z-index', + $icon.css('z-index') + 1);
this.icons[v].css('transform', 'translate3d(' + iconX + 'px,'
+ (iconY + iconOffset) + 'px,' + '0px)');
iconOffset += 20;
this.icons[v].appendTo($icon);
}
// Fire rate icons
this.attackRateCircle = $('<div class="circle"></div>');
this.attackRateCircle.circleProgress({
value: 0,
size: 16,
fill: { color: "#b5deff" },
emptyFill: 'rgba(0, 0, 0, 0.5)',
startAngle: -Math.PI / 2,
thickness: 4,
animation: false,
});
this.attackRateCircle.hide();
// Create and display the healthbar
this.healthBar = $('<div>').addClass('healthBar ');
this.healthBar.css('z-index', $icon.css('z-index'));
this.healthBarFill = $('<span class="fill">');
this.healthBar.append(this.healthBarFill);
$icon.append(this.healthBar);
$icon.append(this.attackRateCircle);
And this is the icons array:
this.icons = {
attack_order: $('<img src="img/attack.png" class="status_icon">'),
attack: $('<img src="img/damage.png" class="status_icon icon_damage">'),
hit: $('<img src="img/hit.png" class="status_icon icon_hit">'),
};
circleProgress call is from this library: https://github.com/kottenator/jquery-circle-progress
DEMO
Yay, I have been able to create a jsFiddle that reproduces the bug: https://jsfiddle.net/cte55cz7/14/
Open on Safari on iPhone 5C and wait a couple of minutes. On iPhone 6 and iPad mini the page crashes (as expected due to the memory leak)
Here's the same code in a HasteBin, for anyone who doesn't want to run it.
This memory leaks is probably due to how 'WebKit’s JS Engine' works [safari webkit-javascript llvm]
and really looks like to be a virtual memory buffer-overflow, having a direct impact on the remaining RAM (shared and used also by iOS to store User Interface graphical elements)
Relatively to the piece of code:
"[...]finding jQuery memory leaks is easy. Check the size of $.cache. If it’s too large, inspect it and see which entries stay and why. [...]" (http://javascript.info/tutorial/memory-leaks)
Let me expect that it is relative to this for loop :
for(var v in this.icons) {
this.icons[v].css('z-index', + $icon.css('z-index') + 1);
this.icons[v].css('transform', 'translate3d(' + iconX + 'px,'
+ (iconY + iconOffset) + 'px,' + '0px)');
iconOffset += 20;
this.icons[v].appendTo($icon);
}
Assuming inspection is done, and also assuming the fact that you find the entries, you may want to clean the data manually with removeData()
or you may use first $elem.detach() and then put $(elem).remove() in setTimeout.

I can get audio.currentTime but not set it (in Google Chrome)

This is driving me crazy. Here is the code I use to set current time:
$("#audio").click(function(e) {
e.preventDefault();
mr_waveform_skip(e)
});
function mr_waveform_skip(event) {
clientX = event.clientX;
left = event.currentTarget.offsetLeft;
clickoffset = clientX - left;
percent = clickoffset/event.currentTarget.offsetWidth
audio_duration = audio_element.duration;
duration_seek = percent*audio_duration;
audio_element.currentTime = duration_seek;
// audio_element.currentTime = 10;
console.log('CLICK: ' + duration_seek + ' Element: ' + audio_element + ' CurrentTime: ' + audio_element.currentTime);
}
I cannot seem to set audio_element.currentTime only get it!
And worse, it works in fireFox! Chrome restarts at 0, no matter what.
This is what the above code produces in Firefox console:
CLICK: 63.82905432385121 Element: [object HTMLAudioElement] CurrentTime: 3.849546
And in Chrome:
CLICK: 63.82905432385121 Element: [object HTMLAudioElement] CurrentTime: 3.849546
See? The same one! We can see that Chromes sees the HTML audio element (since it can get the value). If I do audio_element.currentTime = 10; it still does not work (in Chrome, Firefox loyally restarts at 10)..
Your code works perfectly when I tested it with with an Ogg file from Wikipedia.. If that really is all of your code, then this problem appears to be caused by some kind of corruption or unexpected format in your media file.
You will not be able to fix this problem with code; you will need to produce a new media file that your browser can process correctly. Perhaps try using a different piece of audio software (or different settings) to produce or re-process the media file.
If the format isn't the problem:
I've bumped into this problem in Chrome a couple of times today. Wasted so much time trying to solve it. (Version 55.x)
The solution is to restart Chrome. After countless page refreshes the value is suddenly not set. It's probably some cache bug. I would stress that using a development server supporting range requests is also a good idea.
If I'm not running Node or Django I use : https://github.com/danvk/RangeHTTPServer

OpenLayers latitude inaccurately captured in Webkit Mobile browsers

I am programming a page with a Map where I need to capture the location of the Tap/Click on a map and store the coordinates. I am using OpenLayers js. On desktop browsers (IE/FF/Chrome), this is working fine. On mobile devices, the tap is getting captured correctly on the default Android browser (both in real devices and emulators).
However on mobile webkit browsers (iPhone Safari & Android Chrome Beta), we are having a problem where the tap is getting captured for a few pixels higher (towards the north) of the actual tap. The error is not fixed (so, I can't just add 100 to the event's xy to recalibrate the top.)
Here is the code I am using as the clickhandler:
OpenLayers.Control.ClickHandler = OpenLayers.Class(OpenLayers.Control, {
defaultHandlerOptions: {
'single': true,
'double': false,
'pixelTolerance': 0,
'stopSingle': false,
'stopDouble': false
},
initialize: function(options) {
this.handlerOptions = OpenLayers.Util.extend(
{}, this.defaultHandlerOptions
);
OpenLayers.Control.prototype.initialize.apply(
this, arguments
);
this.handler = new OpenLayers.Handler.Click(
this, {
'click': this.trigger
}, this.handlerOptions
);
},
trigger: function(e) {
that.inputLonLat_EPSG4326 = null;
var lonlat = that.inputMap.getLonLatFromViewPortPx(e.xy);
that.logMessage("XY " + e.xy);
that.logMessage("LonLoat " + lonlat);
that.inputMarkers.clearMarkers();
that.inputMarkers.addMarker(new OpenLayers.Marker(lonlat,that.icons["green"].clone()));
lonlat.transform(that.inputMap.getProjectionObject(), new OpenLayers.Projection("EPSG:4326"));
that.inputLonLat_EPSG4326 = lonlat;
// For the moderation sections
$('#alertLatitude').val(that.inputLonLat_EPSG4326.lat);
$('#alertLongitude').val(that.inputLonLat_EPSG4326.lon);
//lonLat2.transform(that.inputMap.getProjectionObject(), new OpenLayers.Projection("EPSG:4326"));
that.logMessage("Stored lat " + that.inputLonLat_EPSG4326.lat);
that.logMessage("Stored lon " + that.inputLonLat_EPSG4326.lon);
that.callFunction(that.inputMapListener, e);
}
});
Should I be doing anything differently? Has anybody else seen the inaccuracy problem on mobile webkit browsers while using OpenLayers?
I finally found the reason this is happening. It seems that on webkit mobile browsers, the x,y that the library seems to be getting (or deriving) is based on the page and not on the element in which the map is housed. Hence the calculations are off. It seems that the inaccuracy can't be solved by adding the xy of the map element or some such DOM-based figure too (I tried it.)
I solved it by housing the Map in an IFrame and then have the iFrame housed within the map element. This way the x,y as received by the map click handler is accurate within the iFrame and hence the lat, long is also accurate. Since both the parent and iFrame are from the same domain, there are no issues communicating back and forth.
To complete the context, the iFrame based solution is definitely compatible with BB 9 & above, Android Chrome, Android Default and iPhone Safari, as tested by me.
Check out the solution at - http://suat1.vocanic.net//saralee/apps/dengue_alert/ and the iFrame at http://suat1.vocanic.net//saralee/apps/dengue_alert/mobileMap.php (WIP versions liable to change or break with time)

javascript, simulate keyboard events, work on chrome(webkit)

in FF, i've used this code:
if (keyCount == lineLimit) {
// method in FF, no Chrome
var mock = document.createEvent("KeyboardEvent"); // or KeysEvent
mock.initKeyEvent("keypress",true,true,null,false,false,false,false,14,0);
var x = document.getElementById('InputCategory');
// rise height before Enter
$(this).height(div_height + font_height + offset_height);
// mock Enter
x.dispatchEvent(mock);
// init keyCount
keyCount = 0;
}
it works, but could not be effective on webkit-based browsers like chrome.
so i asked google and found keyboard event is one of the DOM Level 3 events,here is an aticle: http://www.w3.org/TR/DOM-Level-3-Events/
then i knew /* initKeyboardEvent / is not supported on chrome & safari, / initUIEvent */ i've tried, it didn't work also.
Do virtual keyboard events reall can be simulated on chrome ? plesase help me :)
This works, but it's not generating a keypress-event, rather a text-insert event.
var te = document.createEvent('TextEvent');
te.initTextEvent('textInput', true, true, window, 'test');
<element>.dispatchEvent(te);
That inserts the word 'test' at the end of the input (in your case you'd probably replace that by \n.

Categories

Resources