What is the way to test if something exists in ClojureScript ? For instance, I am trying to access the browser geolocation API. In javascript, I would do a simple check like that :
// check for Geolocation support
if (navigator.geolocation) {
console.log('Geolocation is supported!');
}
else {
console.log('Geolocation is not supported for this Browser/OS version yet.');
}
But translating it to ClojureScript, I get an error :
(if (js/navigator.geolocation) ;; Uncaught TypeError: navigator.geolocation is not a function
(println "Geolocation is supported")
(println "Geolocation is not supported"))
What is the proper way to check browser capabilities in ClojureScript ?
There is multiple options:
exists?:
http://dev.clojure.org/jira/browse/CLJS-495
Only exists in clojurescript and not clojure.
If you look at the macro in core.cljc you'll see it's just a if( typeof ... !== 'undefined' ).
Example use :
(if (exists? js/navigator.geolocation)
(println "Geolocation is supported"))
(println "Geolocation is not supported"))
(js-in "geolocation" js/window) which expands to "geolocation" in windows.
(undefined? js/window.geolocation) which expands to void 0 === window.geolocation
IMO, the right one is js-in.
For now, I am using :
(if (not (nil? js/navigator.geolocation))
(println "Geolocation is supported")
(println "Geolocation is not supported"))
Not sure if this idiomatic/cover all use cases, I would gladly accept another answer with a proper explanation.
Related
I have created a reproduction of this bug here (ugly use of Aurelia but to prove the point): https://jberggren.github.io/GoogleAureliaBugReproduce/
If I load Google API and try to list my files in Google Drive my code derived from Googles quickstart works fine. If I use the same code after loading Aurelia I get a script error from gapi stating
Uncaught Error: arrayForEach was called with a non array value
at Object._.Sa (cb=gapi.loaded_0:382)
at Object._.eb (cb=gapi.loaded_0:402)
at MF (cb=gapi.loaded_0:723)
at Object.HF (cb=gapi.loaded_0:722)
at Object.list (cb=gapi.loaded_0:40)
at listFiles (index.js:86)
...
When debugging it seems to be some sort of array check (Chroms says 'native code') that failes after Aurelia is loaded. In my search for an answer I found two other people with the same problem but no solution (Aurelia gitter question, SO Question). Don't know if to report this to the Aurelia team, Google or where the actual problem lays.
Help me SO, you are my only hope.
This is not a perfect solution but works.
aurelia-binding
https://github.com/aurelia/binding/blob/master/src/array-observation.js
Aurelia overrides Array.prototype.* for some reasons.
gapi (especially spreadsheets)
Gapi lib checks to make sure that is it native code or not.
// example
const r = /\[native code\]/
r.test(Array.prototype.push)
conclusion
So, we have to monkey patching.
gapi.load('client:auth2', async () => {
await gapi.client.init({
clientId: CLIENT_ID,
discoveryDocs: ['https://sheets.googleapis.com/$discovery/rest?version=v4'],
scope: 'https://www.googleapis.com/auth/spreadsheets',
});
// monkey patch
const originTest = RegExp.prototype.test;
RegExp.prototype.test = function test(v) {
if (typeof v === 'function' && v.toString().includes('__array_observer__.addChangeRecord')) {
return true;
}
return originTest.apply(this, arguments);
};
});
I'm using [cljsjs/localforage "1.2.10-0"]
When I do:
(.setItem (.localforage js/window) "mynumber" (clj->js {:number (.-value number)}))
I get this error in WebIDE console:
TypeError: *TypeError: window.localforage is not a function*
If I test it in firefox browser, it says the same, whilst if I introduce in the console window.localforage.setItem() it works :|
BONUS: each time I compile with lein cljsbuild auto I get this:
*Upstream deps.cljs found on classpath. {:foreign-libs [{
:file "cljsjs/localforage/development/localforage.inc.js",
:provides ["cljsjs.localforage"],
:file-min "cljsjs/localforage/production/localforage.min.inc.js"
}],
:externs ["cljsjs/localForage/common/localforage.ext.js"]}
This is an EXPERIMENTAL FEATURE and is not guarenteed to remain
stable in future versions.*
well, seems the correct calling manner is (.-localforage js/window) [note the dash]
(.setItem (.-localforage js/window) "mynumber" (clj->js {:number (.-value number)}))
so far so good :D
Is there a way to detect if a visitor to my site is running Chromium as opposed to Google Chrome? Even basic UA sniffing (which I know is bad practice) would suffice for my particular case, but it appears that Chromium and Chrome share the same UA string – is that correct? Is there any other way that I can differentiate between the two?
Note:
This no longer works, because now all Chrome-based-navigators have all plugins.
New Chromium-versions do have the PDF-plugin, too.
But they also have Chromium-plugins, so if any plugin starts with "Chromium", it's Chromium:
function isChromium() {
for (var i = 0, u = "Chromium", l = u.length; i < navigator.plugins.length; i++) {
if (navigator.plugins[i].name != null && navigator.plugins[i].name.substr(0, l) === u)
return true;
}
return false;
}
Also, use this to identify Microsoft Chredge (aka. Anaheim)
function isEdg() {
for (var i = 0, u = "Microsoft Edg", l = u.length; i < navigator.plugins.length; i++) {
if (navigator.plugins[i].name != null && navigator.plugins[i].name.substr(0, l) === u)
return true;
}
return false;
}
Chrome ships with a built-in PDF reader, Chromium doesn't.
You could detect this by using JavaScript:
function isChrome() { // Actually, isWithChromePDFReader
for (var i=0; i<navigator.plugins.length; i++)
if (navigator.plugins[i].name == 'Chrome PDF Viewer') return true;
return false;
}
This method is not 100% reliable, because users can copy the PDF reader binary from Chrome to their Chromium directory, see this answer on Ask Ubuntu.
There's almost no difference between Chromium and Chrome (certainly not in the rendering or JavaScript engine), so why do you want to spot the difference?
Starting with Chromium 84 there's a new method called User-Agent Client Hints reference
You can check if the userAgentData property exists and look for brand data. It will return an array that looks something like this.
[{
"brand": " Not;A Brand",
"version": "99"
}, {
"brand": "Google Chrome",
"version": "91"
}, {
"brand": "Chromium",
"version": "91"
}]
userAgentData.brands will contain varying values in a varying order, so don't rely on something appearing at a certain index. Instead check if the property exists in the array.
if (navigator.userAgentData) {
let vendors = window.navigator.userAgentData.brands;
if (vendors.filter(e => e.brand === 'Google Chrome').length > 0) {
console.log('Chrome')
} else {
console.log('Chromium')
}
}
Here is a variation to Paul W.'s answer that works for Chromium version 42 and above:
function isChromium() { // Actually, isWithChromiumPDFReader
for (var i=0; i<navigator.plugins.length; i++)
if (navigator.plugins[i].name == 'Chromium PDF Viewer') return true;
return false;
}
This of course only works if the plugin has not been disabled by the user.
Here is another way, using SpeechSynthesis feature.
Google Chrome Browser ships TTS voices, where Chromium browsers (incl. Brave) do not. Voices can be installed manually, with espeak (on linux) however the Google voices all start with Google, where the manually installed voices do not. As far as I know the Chrome voices are propriety, not free.
The collection of voices is an Array where each voices looks like this:
{
voiceURI: "Google Deutsch",
name: "Google Deutsch",
lang: "de-DE",
localService: false,
default: true
}
We just need to find one who's name/URI starts with Google ...
function hasGoogleVoices() {
return window.speechSynthesis.getVoices()
.some(v => /^google/i.test(v.name));
}
(Tested on Linux for Chrome, Brave, Chromium and Firefox)
Please can someone check Safari and Windows. Thx.
Could not comment on https://stackoverflow.com/a/68428992/14238203 Josh Answer.
On latest Chrome and Chromium (Oct 2021) some of the solutions returns true for both, so I had to find a different solution.
I took https://stackoverflow.com/a/63724166/14238203 fliptopbox code and implmented Josh answer.
const isChrome = navigator.userAgentData.brands.some((v) => /^google/i.test(v.brand));
The issue with Josh answer is that if you try this when just loading a page, the getVoices() returns empty array until all the voices are loaded (page finished loading)
A promise solution to that here - https://stackoverflow.com/a/59786665/14238203
For my use case it was a bit cumbersome with the getVoices() so I used the user agent hints solution.
Im using ace editor and mootools.
I have just updated my mootools to version 1.4.5 and when i click/drag on the editor i get js exception:
Uncaught TypeError: Cannot read property 'clientX' of undefined
Please help... Thanks
Instead of removing the bind method always I think I found a workaround. It seems that only a couple of Mootools builds have this issue. So I use this code to fix them:
if (this.MooTools.build=='ab8ea8824dc3b24b6666867a2c4ed58ebb762cf0') {
delete Function.prototype.bind;
Function.implement({
/*<!ES5-bind>*/
bind: function(that){
var self = this,
args = arguments.length > 1 ? Array.slice(arguments, 1) : null,
F = function(){};
var bound = function(){
var context = that, length = arguments.length;
if (this instanceof bound){
F.prototype = self.prototype;
context = new F;
}
var result = (!args && !length)
? self.call(context)
: self.apply(context, args && length ? args.concat(Array.slice(arguments)) : args || arguments);
return context == that ? result : context;
};
return bound;
},
/*</!ES5-bind>*/
});
}
The benefit is that I can fix it for each build separately. The drawback is clearly to have mootools code shipped with my own code. But currently I see no other option and since I code for Joomla I'm pretty sure that there is a limited number of mootools versions in use.
i solve it for my situation!
i've delete some lines from mootools.js:
1) bind:function(e){var a=this,b=arguments.length>1?Array.slice(arguments,1):null,d=function(){};var c=function(){var g=e,h=arguments.length;if(this instanceof c){d.prototype=a.prototype;
g=new d;}var f=(!b&&!h)?a.call(g):a.apply(g,b&&h?b.concat(Array.slice(arguments)):b||arguments);return g==e?f:g;};return c;}
2) delete Function.prototype.bind;
(i don't need mouse binding in mootools so it's revelation for me!)
This is a very strange error. During debugging I didn't get the error. But as soon as I removed the breakpoint it appeared again. I finally solved the issue by adding the latest version of mootools 1.4.5 to joomla. The error was immediately gone.
i have a problem about HTML5 geolocation feature. I use the code below to get location data. I use "enableHighAccuracy: false" option to work with Cell Based GPS feature. Accurancy is low but response it too fast. But some people always use Built-in GPS with their mobile phone, so this code does not work for them. Bu if i change accurency option as "enableHighAccuracy: true" it works for them. But this time, the code uses only built-in GPS. not CELL based GPS.
The question -> How can i do that : First, try to get position from Built-in GPS with timeout (e.g. 5000ms ) if position cannot be got in this time just look for Cell Based position for timeout (e.g. 10000ms) if position cannot be get in this time, return an error message .
Here is the code that i use now.
Thanks in advance.
function getLocationfromGoogle() {
navigator.geolocation.getCurrentPosition(
function(pos) {
$("#lat_field").val(pos.coords.latitude);
$("#long_field").val(pos.coords.longitude);
var geocoder = new google.maps.Geocoder();
var latLng = new google.maps.LatLng(pos.coords.latitude,pos.coords.longitude);
geocoder.geocode({ 'latLng': latLng}, function (results, status) {
if (status == google.maps.GeocoderStatus.OK) {
//console.log(results[0].formatted_address);
$("#adresim").val(results[0].formatted_address);
}
else {
alert('Google convertion is not succesfully done.');
}
});
},function error(msg){
alert('Please enable your GPS position future.');
},{maximumAge:600000, timeout:5000, enableHighAccuracy: false}
);
}
You should also be aware that the implementation of this varies from phone OS to phone OS - what works on Android may or may not work on iOS, BlackBerry, WindowsPhone, etc.
You're almost there, you just need to:
Specify enableHighAccuracy: true (you have it set to false)
Handle the timeout error case in the error handler. If the error from the high accuracy query is timeout, then try it again with enableHighAccuracy: false.
Have a look at this sample code.
You should also note that when testing this on a few devices, it returns location derived from WiFi even when enableHighAccuracy: true.
The code mentioned here: http://jsfiddle.net/CvSW4/ did not work for me during error handling.
The reason is that the error functions accept a parameter named 'position' but use an object in the functions called 'error'.
function errorCallback_highAccuracy(position) { ... }
function errorCallback_lowAccuracy(position) { ... }
The solution to fix this was to switch the error methods to accept the input value as a parameter named 'error' and not 'position', since the error callbacks do not accept a position and throw an error object instead.
function errorCallback_highAccuracy(error) { ... }
function errorCallback_lowAccuracy(error) { ... }
I mention it here, because I could not post on the resulting example page and also, this is the location where I linked through to find the code sample mentioned above.