Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

js-interop: document limitations with ES6 #39995

Open
MarvinHannott opened this issue Jan 6, 2020 · 9 comments
Open

js-interop: document limitations with ES6 #39995

MarvinHannott opened this issue Jan 6, 2020 · 9 comments
Labels
area-web Use area-web for Dart web related issues, including the DDC and dart2js compilers and JS interop. web-js-interop Issues that impact all js interop

Comments

@MarvinHannott
Copy link

Describe the issue you're seeing
External getters don't work with JS let and const declarations.
It does work, however, with var declaration.

Does it happen in Dartium or when compiled to JavaScript?
Compile to JavaScript with webdev build.

  • Dart SDK version: use dart --version
    2.7
  • pkg/js version: look at pubspec.lock
    0.6.1+1

Failing code:

// example.js
const greetings = "Hello World";
// example.dart
@JS()
external String get greetings;
// main.dart
import 'example.dart' as example;
void main(){
  print(example.greetings) // prints 'null'
}

Working code:

// example.js
var greetings = "Hello World";
// main.dart
import 'example.dart' as example;
void main(){
  print(example.greetings) // prints 'Hello World'
}
@keertip keertip added area-web Use area-web for Dart web related issues, including the DDC and dart2js compilers and JS interop. web-js-interop Issues that impact all js interop labels Jan 6, 2020
@natebosch
Copy link
Member

The problem is that with var the variable is available through window., but it isn't available with either let or const. https://stackoverflow.com/a/28776236/374798

We either need to document that these aren't accessible through Dart, or find a new way to access them.

cc @sigmundch

@MarvinHannott
Copy link
Author

The problem is that with var the variable is available through window., but it isn't available with either let or const. https://stackoverflow.com/a/28776236/374798

We either need to document that these aren't accessible through Dart, or find a new way to access them.

cc @sigmundch

You are right, that didn't occure to me. And the same goes for ES2015 classes as I have noticed. It might be worth mentioning that only ES5 is fully supported and that Babel should be used.

@sigmundch sigmundch changed the title js-interop: external getters only work with global var declarations js-interop: document limitations with ES6 Jun 12, 2020
@sigmundch
Copy link
Member

minor update: I just updated the title and merged a few issues here to track them all in one place.

ES6 let, class, and const are not hoisted to the window scope so they are not visible to JS-interop. We need to properly document what are the recommended ways to access these symbols.

See also #40102, #35737

You are right, that didn't occure to me. And the same goes for ES2015 classes as I have noticed. It might be worth mentioning that only ES5 is fully supported and that Babel should be used.

Note that you shouldn't have to convert to ES5 with babel, it would be sufficient to expose the symbols that can't be hoisted. For example:

class MyClass {...}
window.MyClass = MyClass;

would work too.

@RaghuMudem
Copy link

RaghuMudem commented May 4, 2021

I am looking for the solution for the issue #35737. It directed me here for more discussion on the issue. Is there any suggestion for that issue solution.

Sorry actual issue has been marked as closed. So keeping the comment here for solution.

Thanks in advance.

@sigmundch
Copy link
Member

@RaghuMudem - the example in #35737 was exporting an ES6 class:

export class HelloWorld {
  sayHello() {
      return "HelloWorld"
  }
}

So the suggestion in #39995 (comment) applies here. That is, add in JavaScript an extra line that actually exposes the class definition as a top-level member on the window:

export class HelloWorld {
  sayHello() {
      return "HelloWorld"
  }
}
window.HelloWorld = HelloWorld;

This will make it possible for JS-interop to find that definition.

Was this the pattern you were looking for?

@RaghuMudem
Copy link

RaghuMudem commented May 5, 2021

Hi @sigmundch, Thanks for your reply.
I am loading a html content with video element in a IFrameElement using HtmlElementView. In my Javascript method, i am trying to get the video element by document.getElementById('video');. But it always giving me null.

And the same video is able to find initApp method. initApp method is called with the following code.

document.addEventListener('DOMContentLoaded', initApp);

And my complete javascript code is here. I also added comments to make it clear on the problematic areas.

var PlayerHandler = /** @class */ (function () {
    function PlayerHandler() {}

    PlayerHandler.prototype.bark = function () {
        console.log("bark ..");
    };
        PlayerHandler.prototype.videoTest = function (url) {
              const video = document.getElementById('video'); **// This is always return null.**
              console.log("videoTest video "+video +" window.video "+window.video); **//window.video also null. Wanted video object here**
        };
    return PlayerHandler;
}());
var myModule = { PlayerHandler: PlayerHandler };
window.PlayerHandler = PlayerHandler;

function initApp() {
     initPlayer();
}

async function initPlayer() {
  // Create a Player instance.
  const video = document.getElementById('video'); **//This always returns video element**
  window.video = video;
  console.log('initPlayer video '+video);

}
document.addEventListener('DOMContentLoaded', initApp);

Here is the console logs:

initPlayer video [object HTMLVideoElement]
bark ..
videoTest video null window.video null

Would be very helpful if anyone can suggest me to get the video element in the places mentioned above.

@RaghuMudem
Copy link

Hi @sigmundch, Thanks for your reply.
I am loading a html content with video element in a IFrameElement using HtmlElementView. In my Javascript method, i am trying to get the video element by document.getElementById('video');. But it always giving me null.

And the same video is able to find initApp method. initApp method is called with the following code.

document.addEventListener('DOMContentLoaded', initApp);

And my complete javascript code is here. I also added comments to make it clear on the problematic areas.

var PlayerHandler = /** @class */ (function () {
    function PlayerHandler() {}

    PlayerHandler.prototype.bark = function () {
        console.log("bark ..");
    };
        PlayerHandler.prototype.videoTest = function (url) {
              const video = document.getElementById('video'); **// This is always return null.**
              console.log("videoTest video "+video +" window.video "+window.video); **//window.video also null. Wanted video object here**
        };
    return PlayerHandler;
}());
var myModule = { PlayerHandler: PlayerHandler };
window.PlayerHandler = PlayerHandler;

function initApp() {
     initPlayer();
}

async function initPlayer() {
  // Create a Player instance.
  const video = document.getElementById('video'); **//This always returns video element**
  window.video = video;
  console.log('initPlayer video '+video);

}
document.addEventListener('DOMContentLoaded', initApp);

Here is the console logs:

initPlayer video [object HTMLVideoElement]
bark ..
videoTest video null window.video null

Would be very helpful if anyone can suggest me to get the video element in the places mentioned above.

I found a workaround for this. Posting the clue here for others who has the similar problem.

Description about the solution:

  1. I have wrapped my iframe with in html.DivElement(). I have given a id for the html.DivElement(). Example: final div = html.DivElement()..id = 'player-iframe-root';
  2. Given a id for iframe also. Lets say iframe id is html.IFrameElement()..id = 'player-iframe'
  3. Inside iframe my video element look like. <video id="video" controls autoplay></video>
  4. Here is my complete div with iframe code written in dart program:
_element = html.IFrameElement()
      ..style.border = 'none'
      ..id = 'player-iframe'
      ..srcdoc =
      """ <!DOCTYPE html>
          <html>
            <head>
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <style>
            video {
              width: 100%;
              height: auto;
            }
            </style>
            </head>
            <body style="background-color:white;">
              <video id="video" controls autoplay></video>
                            <!-- Your application source: -->
              <script src="../../../web/player-handler.js"></script>
            </body>
          </html>
      """;
    final div = html.DivElement()..id = 'player-iframe-root';

    final container = div..append(_element);
    // ignore:undefined_prefixed_name
    ui.platformViewRegistry.registerViewFactory(
      'player-$textureId',
          (int viewId) => container,
    );
  1. Added this content into HtmlElementView widget in my dart widget.
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: null,
      body: Container(
        child: HtmlElementView(
              viewType: 'player-$textureId',
            ),
      ),
    );
  }
  1. And fetching the video element inside my js intero call on videoTest() method call.
class PlayerHandler {

    videoTest() {
    /*The tree order of the html should be <div id=player-iframe-root><iframe id=player-iframe><video/></iframe></div> .
    And this html is created dynamically in dart code as mentioned in point 4 */
         var div = document.getElementById('player-iframe-root');
         var iframe = div.querySelector("#player-iframe");
         this._video = iframe.contentWindow.document.getElementsByTagName("video")[0]; **//Video element available here and ready to use :)**
        console.log('this._video '+this._video);
    }
}
window.PlayerHandler = PlayerHandler;
  1. My js-interop is here for reference.
@JS()
library playerHandler;

import 'package:js/js.dart';

@JS("PlayerHandler")
class PlayerHandler {
  external factory PlayerHandler();
  external void videoTest();
}

Hope this clue can help for someone who is working for Flutter-web-iframe-video-js-interop combination :)

@PoojaAggarwal01
Copy link

I am getting dart.global.MyClass is not a constructor if I add script elements dynamically but works fine if script is added directly to index.html.

I have requirement to add scripts dynamically only. Can you please help me with the same?

@sigmundch
Copy link
Member

@MaryaBelanger @srujzs - FYI - another user hit this issue again this week. It may be worth adding a section about this in some of our upcoming site changes (maybe under some kind of FAQ?)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-web Use area-web for Dart web related issues, including the DDC and dart2js compilers and JS interop. web-js-interop Issues that impact all js interop
Projects
None yet
Development

No branches or pull requests

6 participants