Issues with using js interop for blazor hybrid app - javascript

When I'm trying to call a js function named initMap via interop in a hybrid blazor app, I keep getting the following error:
Could not find 'initMap' in 'window'
I have the following code:
TestApp.Windows\wwwroot\index.html
...
<head>
<script src="../js/initGoogleMap.js"></script>
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=&callback=initMap"></script>
</head>
...
TestApp.Windows\wwwroot\js\initGoogleMap.js
window.initGoogleMap = {
initMap: function () {
const latLng = new google.maps.LatLng(40.716948, -74.003563);
const options = {
zoom: 14,
center: latLng,
mapTypeId: google.maps.MapTypeId.ROADMAP
}
const map = new google.maps.Map(document.getElementById("map"), options)
}
};
TestApp\WebUI\Pages\Index.razor
#page "/"
#inject IJSRuntime JSRuntime
<h1>Hello, world!</h1>
Welcome to your new app.
<div id="map" style="height:500px; width:100%;"></div>
#code{
protected override async Task OnAfterRenderAsync(bool firstRender) {
if (firstRender)
{
Console.WriteLine("first render");
await JSRuntime.InvokeVoidAsync("initMap");
}
}
}
Any ideas why this error occurs? I tried several small tweaks but to no avail. Any help would be greatly appreciated!

Try await JSRuntime.InvokeVoidAsync("initGoogleMap.initMap");
https://learn.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/call-javascript-from-dotnet?view=aspnetcore-5.0#capture-references-to-elements

Most likely the static files are not copied in the working directory. Here are some steps to deal with the issue:
In the common project make sure all static resources are set to
"copy if newer" (in the file's properties)
In index.html file, please set the file path to match the following pattern:
"_content/<PROJECT_NAME>/path/to/the/file.js"
In your case that would be:
"_content/TestApp/js/initGoogleMap.js"
Delete all bin and obj folders in every project and rebuild whole solution.
If issue persists for Android, please Uninstall the app from the android emulator and repeat step 3.
P.S. Here's a link to the issue in Blazor Binding's repo: https://github.com/dotnet/MobileBlazorBindings/issues/211

Related

What is the meaning of empty export {} in vanilla javascript/HTML

In the Google Maps Javascript API example, I see they had something like this in the HTML:
<script type="module" src="./index.ts"></script>
and an empty export statement at the end of the TS/JS scripts.
let map;
function initMap() {
map = new google.maps.Map(document.getElementById("map"), {
center: { lat: -34.397, lng: 150.644 },
zoom: 8,
});
}
window.initMap = initMap;
export {};
I don't see any examples or mentioning of empty exports on MDN, so I was wondering if anyone knew how it works. How does the script know to run it if the export is empty.
This looks like something in TypeScript; it has nothing to do with JavaScript.
If either of the cases below occurs, then you will need an import/export in the file.
The TypeScript file is being called with the flag below.
--isolatedModules
The tsconfig.json file has the following key and value.
{
"isolatedModules": true
}
According to typescriptlang.org, it states:
If isolatedModules is set, all implementation files must be modules (which means it has some form of import/export). An error occurs if any file isn’t a module.
If you try to run the TypeScript file with the --isolatedModules flag, you get an error like below.
'index.ts' cannot be compiled under '--isolatedModules' because it is considered a global script file. Add an import, export, or an empty 'export {}' statement to make it a module.
As the error states above, the simplest way to fix the issue without adding any unnecessary import statements and/or export statements, it is easiest to export an empty object ({}), like so.
export {};
In summary, the empty object export will not do anything in JavaScript (or TypeScript, without the --isolatedModules flag). However, it comes in handy when running with the --isolatedModules flag.
The Google Maps JavaScript API example might be getting ready for this scenario, in case someone copied-and-pasted the code, so that they wouldn't get an error.

How to make multiple Svelte windows in Electron?

For my electron app, I would like to open another Svelte-window (or load different windows/components depending on the startup variables).
So let's say I used this tutorial to set up a basic structure, with my App.svelte looking like this:
<script>
const openLauncher = () => {
window.api.openWindow("launcher");
};
</script>
<button on:click={openLauncher}>Open Launcher</button>
As you can see, I added an IPC function to open a new window. The corresponding index.js:
const { app, BrowserWindow, ipcMain } = require("electron");
const { join } = require("path");
app.on("ready", () => {
const mainWindow = new BrowserWindow({
webPreferences: {
preload: join(__dirname, "./preload.js"),
}
});
mainWindow.loadFile(join(__dirname, "../public/index.html"));
});
ipcMain.on("openLauncher", (event, args) => {
const launcher = new BrowserWindow({
webPreferences: {
preload: join(__dirname, "./preload.js"),
}
});
launcher.loadFile(join(__dirname, "../public/launcher.html"));
});
preload.js:
const { contextBridge, ipcRenderer } = require("electron");
const API = {
openWindow: (obj) => ipcRenderer.send("openLauncher", obj),
}
contextBridge.exposeInMainWorld("api", API);
This does work and opens a new window with the launcher.html, but I can't figure out how to get the Svelte components working in that new file.
One idea I had was modifying main.js file so that the body component changes, like so:
import App from './App.svelte';
import LauncherApp from './LauncherApp.svelte';
const bodyID = document.getElementsByTagName('body')[0].id;
const app = {};
if (bodyID == "index") {
app = new App({
target: document.body,
});
}
else if (bodyID == "launcher") {
app = new LauncherApp({
target: document.body,
});
}
export default app;
This works for the main window (i.e. if I switch the IDs, it loads the correct component at startup) but since it doesn't load any Svelte whe opening the new window, this doesn't work.
So I would really appreciate any ideas on how to get Svelte to load for new/different windows/html-files! And if there is a way to do this with SvelteKit, even better!
Thank you in advance!
The obvious quick fix I see is using an #if block on bodyID in App.svelte containing two components, MainApp (content of App.svelte) and LauncherApp, and then simply changing bodyID depending on in which mode you are.
When using sveltekit I think it would make sense to treat LauncherApp as a separate route (I believe this is the only way to have "separated" pages with sveltekit, though I am not 100%). So when opening a new window you navigate the new instance of your application to the LauncherApp route. If you don't want the same base layout as in the main app, you can add a __layout.reset.svelte file.
I don't know why your solution didn't work, it was quite elegant.
As this post helped me alot to create seperate windows using Svelte and Electron, i just had to create my account after lurking Stack Overflow for years. I think i've cracked your problem without the "hackery" workaround.
I got it working without the #IF statements in my original App.svelte. This is how i did it:
My main.js in the src map (renderer side) is as follows:
import App from './App.svelte';
import Window2 from './Window2.svelte';
let bodyID = document.getElementsByTagName('body')[0].id;
let app;
if (bodyID == "index"){
app = new App({
target: document.body,
});
}
else if (bodyID == "window2"){
app = new Window2({
target: document.body,
});
}
export default app;
I think however that the real magic happens in my index.html and Window2.html. I made mine using the excellent YouTube videos provided by Tylerlaceby.
Basicly, the index.js in the main folder (so the Electron main js) opens the window with the following lines:
const main_window = new BrowserWindow({//Your settings
});
main_window.loadFile(join(__dirname, "../public/index.html"));
main_window.on("ready-to-show", main_window.show);
And the index.html that is in the public folder contains the following head:
`
<title>Main Window</title>
<link rel='icon' type='image/png' href='./favicon.ico'>
<link rel='stylesheet' href='global.css'>
<link rel='stylesheet' href='build/bundle.css'>
<script defer src='build/bundle.js'></script>
The body is empty, but has the id attached which i use in the main.js renderer side script.
I believe that the header in the index.html loads the build folder after the electron app has been build, containing all of your converted Svelte files.
The Window2.html is the same. The secondary window loads it the same way as the main window does in the second code block from the main.js on the electron side but refers to the Window2.html. The Window2.html contains a body with the ID Window2.
If the above is not the solution, it could also be because i use let instead of const. Once a variable has been assigned to const, it cannot be changed which might explain why it works the first time, but not the second time.
I'm sorry if i did not get all the formatting and refering to previous awnsers correctly. I'm still using to learn Stackoverflow but was eager to share how i made it work for me.

Javascript Classes with Cordova is not working

I'm using JS classes in Cordova application like:
`
class CustomerController {
constructor() {
this.cs = new CustomerServices();
this.customerModelObj = new CustomerModel();
}
}
Then, I called the customerController.js as <script src="customerController.js"></script> in myindex.html`. Then I instantiated the class
`try{
var customerObj = new CustomerController()
}catch(error){
alert(error);
}`
I'm getting an error which is CustomerController is undefined when I build and run the android app.
What I have tried also, I used the crosswalk plugin to replace the original webview as I read it's by chromium and supporting new JS features with no luck.
I have upgraded Cordova to the latest version, created a new project, migrated my project files to the new project and installed ionic-webview plugin. Worked like a charm!
You might face some UI issues and need to put some efforts on it.

Module Not Found error in Vue.js single page component using google maps

I'm attempting to pull in a Google Map from the Google Maps API into my Vue.js application. I have retrieved my generated API key and I am trying to display the map. When I run the app, Atom throws a Module not found error message and the browser displays: Can't resolve 'https://maps.googleapis.com/maps/api/js?key=MYAPIKEY&callback=initMap' in 'C:\Atom Projects\Project\client\src\components' Any idea what I'm doing wrong?
MyComponent.vue
<template>
<div>
<div id="map">
</div>
</div>
</template>
<script>
function initMap () {
var options = {
zoom: 8,
center:{lat:39.9612,lng:-82.9988}
}
var map = new google.maps.Map(document.getElementById('map'), options);
}
</script>
<script async defer type="text/javascript"
src="https://maps.googleapis.com/maps/api/js?key=MYAPIKEY&callback=initMap">
</script>
<style>
#map{
height: 400px;
width: 100%;
}
</style>
Full Error from Browser
./src/components/MyComponent.vue
Module not found: Error: Can't resolve 'https://maps.googleapis.com/maps/api/js?key=MYAPIKEY&callback=initMap' in 'C:\Atom Projects\MyProject\client\src\components'
# ./src/components/MyComponent.vue 8:0-131 9:0-144
# ./src/router/index.js
# ./src/main.js
# multi (webpack)-dev-server/client?http://localhost:8081 webpack/hot/dev-server ./src/main.js
You can't add external <script>s to single file components like that.
The usual approach to cases like yours is to use one of the many libs that integrate vue and google maps.
Some of these libs make available new custom elements for you to use:
xkjyeah/vue-google-maps, for instance, creates the <gmap-map>;
Akryum/vue-googlemaps create the <googlemaps-map>;
GuillaumeLeclerc/vue-google-maps creates the <map>.
All of them have simple details to setting the key and instantiating maps. Refer to their page for examples and usage.
Another one that takes a more low-level approach is Js-GoogleMapsLoader. I'm not saying this one is better than the others -- overall those that are specifics to vue probably will give you an easier integration in the long run --, but since it does not have an example with Vue, here's how you could use it:
run npm install --save google-maps
change MyComponent.vue to:
<template>
<div>
<div id="map">
</div>
</div>
</template>
<script>
import GoogleMapsLoader from 'google-maps'
export default {
mounted: function () {
GoogleMapsLoader.KEY = 'qwertyuiopasdfghjklzxcvbnm';
GoogleMapsLoader.load(function(google) {
var options = {
zoom: 8,
center:{ lat: 39.9612, lng: -82.9988 }
}
var map = new google.maps.Map(document.getElementById('map'), options);
});
}
}
</script>
<style>
#map{
height: 400px;
width: 100%;
}
</style>
And none of the options above need the additional <script> tag.

how to import dojo modules in es6

I'm developing a library that uses ArcGIS which based on dojo framework.
ArcGIS has main file that we include in the <script> Tag. And then we simple require their modules using require('esri/map'....
My es6 library has allot of modules and part of them need to use ArcGIS modules.
I'm using webpack and babel to bundle and transform the code. I'm not bundling ArcGIS in my library single file. I'm expecting my clients to include esri adding <script> and then include my bundle using <script>.
There I've already met an obstacle - dojo multiple define... So I've made another js excluded from the bundle that loads my bundle file using dojo require that already exist (Cause arcgis is up already and loaded dojo).
Now, the second problem I don't manage to solve is to load other ArcGIS AMD modules..
I have my class MyMap.js
export default MyMap {
constructor() {
// Adding here code to create ArcGIS Map
// this.map = new esriMap....
}
}
esriMap does not exist and must be loaded. In a simple application we would do this to make it happen
require([
"esri/Map",
"esri/views/MapView"
], function(Map, MapView) {
var map = new Map({
basemap: "streets"
});
var view = new MapView({
container: "viewDiv",
map: map,
zoom: 4,
center: [15, 65]
});
});
but this does not work.
someone ?
for using dojo's require we need simply to write window.require and it will work.
without writing window. it is failing in the build process Can't resolve 'esri/Map'
For esri users who wants to use some nice loader - check out this one
code example:
export default class myMap {
constructor(div) {
window.require(['esri/Map', 'esri/views/MapView'], (esriMap, esriMapView) => {
const map = new esriMap({
basemap: "streets"
});
const view = new esriMapView({
container: div,
map: map,
zoom: 4,
center: [15, 65]
});
});
}
}

Categories

Resources