Create binding based on external JS - ffi

In this post, the autor teaches how to make a binding from a NodeJS library to Reason. However, I want to create a binding for Google Maps Javascript API, which can't be installed through NPM. Rather, it's usually loaded in the bottom of <body> with <script> tag.
Also, Google Maps Javascript API only exports it's functions inside the function that has been passed as argument in url (callback=funcName). Will this work in Reason the same way as in raw JS?
How can I make this binding?

The API is installed as a global, so you'd just bind to them as ordinary globals. And since Reason functions generate ordinary JavaScript functions the following is more or less equivalent to the example in the documentation you've linked:
type map;
[#bs.new] [#bs.scope ("google", "maps")] external make : (Dom.element, Js.t({..})) => map = "Map";
let initMap = () => {
let map = make(mapElement, {
"center": { "lat": -34.397, "lng": 150.644 },
"zoom": 0
});
};

Related

Trouble implementing Google Analytics gtag.js in React-based Chrome Extension

My problem in a nutshell: The window object that gtag.js operates on and the window object available in my react context (a content.js context) are different objects, and so I can't write events from my react code -- meaning I can't use analytics in my extension.
More deets:
In react <script> tags can't be loaded directly for various reasons. So I've change the documentation implementation:
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'GA_MEASUREMENT_ID');
</script>
To
export const gtag = (...args) => {
window.dataLayer.push(args)
}
export const loadAnalytics = (ga_property) => {
const script = windowdocument.createElement("script")
script.src = `https://www.googletagmanager.com/gtag/js?id=${ga_property}`
script.async = true
window.document.body.appendChild(script)
window.dataLayer = window.dataLayer || []
gtag('js', new Date())
gtag('config', ga_property, { 'transport_type': 'beacon'})
gtag('event',
'test', {
event_category: 'lookup',
event_label: 'test'
}
)
}
...
componentDidMount() {
loadAnalytics("UA-175XXXXXX-1")
}
I've come to understand through much research and gnashing of teeth that the window object in my content.js and the window object that is acted on in gtag.js once it is loaded are not the same object, and are intentionally "shadows" of each other, but still separate objects. From the documentation:
"Content scripts live in an isolated world, allowing a content script to makes changes to its JavaScript environment without conflicting with the page or additional content scripts.
Isolated worlds do not allow for content scripts, the extension, and the web page to access any variables or functions created by the others."
From what I can tell this seems to be irreconcilable without a re-write of the gtag.js source.
For reasons I still don't understand this code which references window.document
const script = window.document.createElement("script")
script.src = `https://www.googletagmanager.com/gtag/js?id=${ga_property}`
script.async = true
window.document.body.appendChild(script)
And this code in the same file which references window.document
export const gtag = (...args) => {
window.dataLayer.push(args)
}
End up pointing to two different window objects.
This post seems to reinforce that these two contexts can't communicated directly in terms of objects and functions (only messages).
For gtag.js to work in an extension, I'd need to be able to call window.dataLayer.push(...) on the window of the main web page from inside my chrome extension. And that doesn't seem possible.
Any bright ideas out there as to how to either:
Make gtag.js be loaded in the proper window.document and/or refer to the content.js context of window
or
be able to access the window object of the main page from the content.js context
Since extension code can have multiple contexts, it would be wise use the principle of separation of concerns to avoid multiple document issue altogether.
When developing extensions it is advised to run majority of your code in the background, to make use of the separate JavaScript runtime allocated for your code by the browser (and avoid slowing down the pages user is visiting or the code which appears as your extension UI). Additionally, in most cases, it is a good idea to ship the code you want to run packaged within the extension bundle. If you want to load an external resource, to your background script "document", you can use XHR and eval to execute code.
When code is executed in the background, it is available to your extension UI and content scripts using the extension and DOM messaging protocols.
First, initialize your extension in the context of your background script(s).
Then, register a message handler which will evaluate messages sent by other extension code and look for a key (usually message.type) that identifies message as carrying analytics data (usually message.payload).
Read the content of the messages that match the criteria in the handler, and use the supplied information to invoke analytics APIs.
Finally, send analytics events occurring in your UI or content scripts as messages to the background script.
This way your background script is the only place where analytics is set up, clearing up document ambiguity, your code is cleaner, because there is only one place where analytics code is accessed and the extension runs smoother because it's UI and content scripts don't need to load or know about analytics code.

There are too many active WebGL contexts on this page, the oldest context will be lost. [dedup.min.js]

I am getting this wiered error only on Safari browser. Don't know why.
I am using AngularJS 1.3.x.
So how do I detect which libraries may be using this.
And why only in safari I get this error ?
Is there a way via JS to enable or disable WebGL ?
Put this at the top of your HTML?
<script>
HTMLCanvasElement.prototype.getContext = (function(origFn) {
var bannedTypes = {
"webgl": true,
"webgl2": true,
"experimental-webgl":, true,
};
return function() {
var type = arguments[0];
return bannedTypes[type]
? null
: origFn.apply(this, arguments);
};
}(HTMLCanvasElement.prototype.getContext));
</script>
As long as this appears before any other scripts it should block webgl.
The script above changes replaces the function someCanvas.getContext so that when some other JavaScript tries to create a "webgl" context it returns null which means creating the context fails. Otherwise if JavaScript asks for a different kind of context it calls the original getContext function.
As long as this script is executed first it should prevent other JavaScript on the page from creating a webgl context. It won't prevent JavaScript in iframes from creating contexts. You'd need to add the same solution to each iframe.

Lazy load google maps before constructing class in angular factory

I have a module that uses google maps to make a map, get the users location and place a marker on the map where they are. I wanted a custom animated marker so I had to make a factory initializing a custom OverlayView Class. I also wanted the map loaded asynchronously so I created a factory like this
to handle loading google maps.
I can load the map fine by doing this:
initializer.gMapReady.then(function(){
//call service method to make map
});
But I dont know how to use my initializer for my OverlayView factory that looks like this:
.factory('locMarker', function() {
LocationMarker.prototype = new google.maps.OverlayView(); // This returns the error, google is not defined
function LocationMarker(opts) {
this.setValues(opts);
}
LocationMarker.prototype.draw = function() {
//
// Code for drawing the marker on the map
//
}
};
return LocationMarker;
})
Whenever I load the page the map shows up fine but my custom marker never displays. I get a reference error, google is not defined, that points to the line LocationMarker.prototype = new google.maps.OverlayView(); If I don't lazy load google maps and just add the script to the index page everything runs fine.
Is there a way for me to call my maps initializer from within the locMarker factory so that google maps is loaded before it tries to construct the class?
Update:
As estus mentioned in the comments, I could use a resolver in the states/routes but I am trying to make this module not require that if possible.

Getting hold of loaded javascript objects in Protractor

In our application we load requirejs, which in return loads angularjs, and also other javascript modules. I am wondering if there any way to get hole of these LOADED modules (angularjs, javascript modules) in protractor test. Note, we want the instance that is loaded by the browser when running Protractor, we don't want to create instance by ourselves.
Any suggestion or example?
Thanks in advance.
Nick Tomlin's answer is what you can do if a module returns serializable data structure as a value. You call require and call with the module's value the callback that executeAsyncScript gives you to allow returning asynchronous values. This will work, for instance, if your module returns "foo" or { foo: 'bar' } or structures that are generally serializable.
However, it won't always work. Complex modules cannot be retrieved that way. Roughly speaking you should expect what you send through executeScript and executeAsyncScript and what they return to have the same limitations as JSON.stringify does. One major exception is that Selenium will wrap DOM objects returned from these calls into a structure that allows to identify them on the script side, and that allows passing them back to the browser. (Then again, there are limitations there too. This is why you get stale element exceptions, for instance.)
If you try to retrieve modules that export functions, you'll probably get something but it won't be complete. Try this, for instance:
browser.executeAsyncScript(function () {
arguments[0]({ foo: function () {}});
}).then(function (value) {
console.log(value);
});
The output I get is:
Object { foo: Object {} }
The function has been turned into an empty object.
I do not use angular with require.js, but i'm assuming you could access the require'd angular the same way you would in a module:
var pageAngular = browser.driver.executeAsyncScript(function () {
var callback = arguments[arguments.length - 1];
require(['angular'], function (angular) {
callback(angular);
})
});
The use of executeAsync is necessary here, since AMD modules are loaded asynchronously.
Do note that as #louis noted, the return of executeAsyncScript is going to be a serialized object, not the "live" instance of angular. If you need to interact with angular within the context of your page, you should do so within the callback torequire.
Something like this should do it:
var angular = browser.driver.executeScript("return window.angular;");

TypeError: window.initMap is not a function

I am following this tutorial, basically copy all the code
https://developers.google.com/maps/documentation/javascript/tutorial
but got an error saying that the initMap function is not a function.
I am using angularjs in my project, could that be causing problems?
I copied the same code into plunker and it works just fine...
What are the possible issues?
Actually the error is being generated by the initMap in the Google's api script
<script async defer
src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap">
</script>
so basically when the Google Map API is loaded then the initMap function is executed.
If you don't have an initMap function, then the initMap is not a function error is generated.
So basically what you have to do is one of the following options:
to create an initMap function
replace the callback function with one of your own that created for the same purpose but named it something else
replace the &callback=angular.noop if you just want an empty function() (only if you use angular)
My explanation of this problem:
The .....&callback=initMap" async defer></script> makes that script load async (in parallel with DOM) and after load - execute initMap function. NOTICE that initMap() in global scope! We can't declare our initMap() after google's script because it should already be at the moment of finish async load. Although we can't load our script before this, because we need google's function to execute ours. This is like vicious circle.
Three solutions:
1'st and worst: make google's script load synchronously
remove async defer
remove &callback=initMap in src attribute
put <script tag with your code after google's script
2'nd and best: just do this =)
leave .....&callback=initMap" async defer></script> as is
and put google's <script tag after your script
write in your script
function initMap() {} // now it IS a function and it is in global
$(() => {
initMap = function() {
// your code like...
var map = new google.maps.Map(document.getElementById('map'), {/*your code*/});
// and other stuff...
}
})
this allow you load google's script async and run yours just after that
3'rd and strange: it will work... some times =)
do same but simply write in global scope
function initMap() {
// your code
}
and if you write it in global scope, that will work regardless of what
which code loads faster, your (sync) or google's (async). More often your wins
I have been struggling for several days with this very popular in the last few months issue - "initMap is not a function".
Those two threads helped me:
How to make a callback to Google Maps init in separate files of a web app
Defer attribute doesn't work with Google Maps API?
Why does the map open sometimes and sometimes not. It depends on several factors like speed of connection, environment, etc. Because the initialization function sometimes runs after the google maps API kicks in, that's why the map is not displayed and the browser console throws an error. For me removing only the async attribute fixed the issue. The defer attribute stays.
If async is present: The script is executed asynchronously with the rest of the page (the script will be executed while the page continues the parsing)
If async is not present and defer is present: The script is executed when the page has finished parsing
If neither async or defer is present: The script is fetched and executed immediately, before the browser continues parsing the page
Source - http://www.w3schools.com/tags/att_script_defer.asp
Hope that helps. Cheers.
Removing =initMap worked for me:
<script async defer src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback"></script>
The problem has to do with the async attribute in the script tag. The Callback Function is trying to call "initMap()" when it doesn't really exists by the time the request finished.
To solve this I placed the Goole Maps Api Script bellow the script where my initMap function was declared.
Hope this helps
This may seem obvious but just in case: If someone placed the JS code inside $(document).ready like this:
$(document).ready(function() {
... Google Maps JS code ...
}
Then that's the problem because using async defer when loading the Google Maps API library, it will load asynchronously, and when it finishes loading, will look for the callback function, which needs to be available by then.
So, you just need to put the code outside $(document).ready, and:
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap"
async defer></script>
at the very bottom, so your page loads FAST :-)
Solved by adding
<script async defer
src="https://maps.googleapis.com/maps/api/js?key=XXXXXXX&callback=initMap">
<!--
https://developers.google.com/maps/documentation/javascript/examples/map-geolocation
-->
</script>
At the beginning of the same file which contains the rest of the code with function initMap(). It's definitely not the best solution, but it works..
But I think that if you would transform function initMap() to something like var=initMap() and then $(function () ... it would work too.
I am using React and I had mentioned &callback=initMap in the script as below
<script
src="https://maps.googleapis.com/maps/api/js?
key=YOUR_API_KEY&callback=initMap">
</script>
then is just removed the &callback=initMap part now there is no such error as window.initMap is not a function in the console.
your call back method probably is not globally accessible. in my case I'd used transpoiled ES6 codes via webpack which caused my callback method not being global anymore.
Try to attach your callback method explicitly to window like so right after your callback method declaration and see the result
window.initMap = initMap;
it worked for me.
Create initMap method between "" tag or load javascript file before call google api.
<script src="Scripts/main.js"></script>
<script src="https://maps.googleapis.com/maps/api/js?key=abcde&libraries=places&callback=initMap" async defer></script>
For me the main difference was the declaration of the function....
INSTEAD OF
function initMap() {
...
}
THIS WORKED
window.initMap = function () {
...
}
Put this in your html body (taken from the official angular.js website):
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.min.js"></script>
I believe that you were using some older angular.js files since you don't have any issues in plunker and therefore you got the specified error.
turns out it has to do with ng-Route and the order of loading script
wrote a directive and put the API script on top of everything works.
I had a similar error. The answers here helped me figure out what to do.
index.html
<!--The div element for the map -->
<div id="map"></div>
<!--The link to external javascript file that has initMap() function-->
<script src="main.js">
<!--Google api, this calls initMap() function-->
<script async defer src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEYWY&callback=initMap">
</script>
main.js
// This gives error
// The initMap function has not been executed
const initMap = () => {
const mapDisplayElement = document.getElementById('map');
// The address is Uluru
const address = {lat: -25.344, lng: 131.036};
// The zoom property specifies the zoom level for the map. Zoom: 0 is the lowest zoom,and displays the entire earth.
const map = new google.maps.Map(mapDisplayElement, { zoom: 4, center: address });
const marker = new google.maps.Marker({ position: address, map });
};
The answers here helped me figure out a solution.
I used an immediately invoked the function (IIFE ) to work around it.
The error is as at the time of calling the google maps api the initMap() function has not executed.
main.js
// This works
const mapDisplayElement = document.getElementById('map');
// The address is Uluru
// Run the initMap() function imidiately,
(initMap = () => {
const address = {lat: -25.344, lng: 131.036};
// The zoom property specifies the zoom level for the map. Zoom: 0 is the lowest zoom,and displays the entire earth.
const map = new google.maps.Map(mapDisplayElement, { zoom: 4, center: address });
const marker = new google.maps.Marker({ position: address, map });
})();
What actually worked for me was:
In the HTML file, I removed the async attribute from the <script> tag.
In the JavaScript file, I double-checked that the function was outside the $(document).ready function.
Could be your initMap function is in a $(document).ready function. If it is then it won't work, it has to be outside of any other functions.
In addition to #DB.Null's answer, I used Function.prototype as no-op (no-operation) function on #3 instead of angular.noop (I don't have angular in my project).
So this...
<script async defer src="https://maps.googleapis.com/maps/api/js?key=API_KEY_HERE&callback=Function.prototype" type="text/javascript"></script>
In my case there was a formatting issue earlier on, so the error was a consequence of something else. My server was rendering the lat/lon values with commas instead of periods, because of different regional settings.
I removed the callback of "initMap" from the script tag and placed the callback in my JavaScript file.
<script async src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEYWY&callback=initMap"></script>
was changed to
<script async src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEYWY"></script>
So the first JS tag is mine.
<script async type=module src="js/app.js"></script>
<script async src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEYWY"></script>
I called the initMap function from within my JS file by adding the following to my app.js file.
initMap();
This is the top result when you Google 'initMap function is not a function' so although there are a lot of answers already, I will make it very clear for the rookies like me what is happening.
To load a Google Map your page need 3 things:
A <div id="map"></div>
Load the Google Map javascript.
A function to initiate the map.
The div (easy)
The div is easy. The Google Map Javascript is easy but there is a catch.
Load Google Maps Javascript
<script
src="https://maps.googleapis.com/maps/api/jskey=YOURKEY&callback=initMap"
defer
></script>
There are two important parts of this script. defer, and the callback=initMap
First let's talk about defer. The defer attribute causes the callback to execute after the full HTML document has been parsed. This makes sure your div is loaded and ready to be used by the javascript. However, does not mean that the Google Maps Javascript will load after your Javascript, and that is why the error happens sometimes.
The init function
You need to call a function to run the code that sets up the Google Map. The callback makes it easy because once the Google Maps code loads, it will call a function you tell it to load the map.
I'll repeat that because it is important. The callback=initMap is calling the initMap function in your code.
So if your code hasn't loaded by the time the Google Maps javascript loads, it will call initMap but you get the error because it doesn't know what initMap is because your code, where you define initMap hasn't loaded yet.
This can happen seemingly randomly as network times vary. Sometimes you code loads first, and it works. Sometimes your code doesn't load first and Google Maps code loads first and you get the error.
Solution
There are a lot of solutions, but the one that I like is don't set a callback, and init the Map yourself after you know everything is loaded.
This way, we can load Google Maps Javascript async, which is faster since it loads in a separate process and doesn't wait for anything.
<script async
src="https://maps.googleapis.com/maps/api/js?key=YOURKEY">
</script>
Notice how there is no callback set. So Google Map Javascript will not automatically call my init function, I have to call it when I know everything is ready.
We can do that with a window load event listener.
window.addEventListener("load", docReady);
function docReady() {
initMap();
}
The window load event fires after everything including Javascript has loaded.
Of course this might slow the page down as it has to load everything, including images, and then it will fire and initialize the map, but it ensures it will work every time.
As you learn more, you will learn other ways to make sure things load in the correct order. Good luck!
This solution helped me:
this.initMap = () => {};
...
src="https://maps.google.com/maps/api/js?key=YOUR_API_KEY&callback=initMap"
In my case, I had to load the Map on my Wordpress website and the problem was that the Google's api script was loading before the initMap(). Therefore, I solved the problem with a delay:
<script>
function initMap() {
// Your Javascript Codes for the map
...
}
<?php
// Delay for 5 seconds
sleep(5);
?>
</script>
<script async defer src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEYWY&callback=initMap"></script>

Resources