Skip to content

js-interop:accessing anonymous objects #26386

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

Closed
parnham opened this issue May 3, 2016 · 12 comments
Closed

js-interop:accessing anonymous objects #26386

parnham opened this issue May 3, 2016 · 12 comments
Labels
closed-obsolete Closed as the reported issue is no longer relevant type-bug Incorrect behavior (everything from a crash to more subtle misbehavior) web-dart2js

Comments

@parnham
Copy link

parnham commented May 3, 2016

I'm working on a package to interface with a javascript library that has a reasonably large API footprint. It's mostly going well and I've been able to implement dart equivalents of most of their examples, however there has been one problem that I've been unable to figure out:

There are a few places in the javascript library where they use anonymous objects as storage for arbitrary items. In effect they are using it like a Map<String, dynamic> but unfortunately I cannot seem to find a way to treat it as such via interop.

I'm aware of, and have successfully used, the @anonymous attribute for sending structured anonymous objects to the js library but in this case I need to read a property that does not have a fixed structure. For example:

@JS()
class Foo
{
    external Map<String, dynamic> get items;
    external set items(Map<String, dynamic> value);

    external Foo();
}

where items is the object in question.

I have also found JsObject in 'dart:js' but this does not appear to be usable in the context of 'package:js'. Perhaps allowing something like this would be helpful:

@JS()
class Foo
{
    external JsObject get items;
    external set items(JsObject value);

    external Foo();
}

Am I missing something, or is this a feature that would need to be added to 'package:js'?

@a14n
Copy link
Contributor

a14n commented May 3, 2016

You can't use Map as argument or return value. You can look at #25053 (comment) to have an example of how to create arbitrary js objects (aka. js map).

@parnham
Copy link
Author

parnham commented May 3, 2016

Does that mean I will also need to use Object.getOwnPropertyDescriptor() to read values out of that object?
I guess I'll have to write some helper functions, although a user of my library will have to know that they must use them against a few specific properties.

Perhaps if there were some way to mixin helper functions to the @anonymous class it would make it clearer to use. Alternatively the underlying JsObjectIImpl could provide [] operators when a flag is set in the @JS() attribute (disabled by default to avoid performance issues where the ability to access a JS object by key is not required).

@a14n
Copy link
Contributor

a14n commented May 4, 2016

I never tried a mixin with a @JS annotated class. I'll be surprised if it works as mixins are only the insertion of an parent class. Let me know if it works.

@parnham
Copy link
Author

parnham commented May 4, 2016

I couldn't figure out a way to do it, as far as I can tell there is no way to add helper functionality to @JS classes. Instead I went with this slightly less user-friendly solution:

@JS()
library pixi.dynamic;
import 'package:js/js.dart';

@JS()
@anonymous
class DynamicSource {}

@JS()
@anonymous
class DynamicDescription
{
    external get value;

    external factory DynamicDescription({bool configurable, bool enumerable, bool writable, value });
}

// Interop to ES5+ functions that will allow us to get/set arbitrary properties on
// anonymous javascript objects.
@JS('Object.defineProperty')
external void defineProperty(object, String property, DynamicDescription description);

@JS('Object.getOwnPropertyDescriptor')
external DynamicDescription getOwnPropertyDescriptor(object, String property);

// A helper class for dealing with proxying anonymous JS objects that are to be
// used as maps and therefore do not have a fixed structure that can be defined
// ahead of time.
class Dynamic<T>
{
    DynamicSource source;

    Dynamic(this.source);

    T operator [](String key)
    {
        return getOwnPropertyDescriptor(this.source, key)?.value;
    }

    operator []=(String key, T value)
    {
        defineProperty(this.source, key, new DynamicDescription(
            value: value, writable: true, enumerable: true, configurable: true
        ));

        return value;
    }
}

Given my initial example it would be used as follows

@JS()
class Foo
{
    external DynamicSource get items;
    external set items(DynamicSource value);

    external Foo();
}


void main()
{
    // Retrieve an instance of Foo created somewhere in the external JS library
    var foo = getFooFromSomewhere();

    // Wrap the DynamicSource member so that it can be treated like a map 
    var items = new Dynamic<int>(foo.items);

    print(items['mykey']);
    items['mykey'] = 8;
}

@parnham parnham closed this as completed May 5, 2016
@parnham
Copy link
Author

parnham commented Jan 13, 2018

Sorry to re-open this issue, but I was playing around with the library again and found that the following works with the dartdevc (pub serve) but does not work when built (pub build).

@JS()
library pixi.dynamic;

import 'package:js/js.dart';
import 'package:js/js_util.dart';

abstract class Dynamic<T>
{
  T operator [](String key)
  {
    return getProperty(this, key) as T;
  }

  void operator []=(String key, T value)
  {
    setProperty(this, key, value);
  }
}

@JS() @anonymous
class Resources extends Dynamic<Resource> {}

When run via the dartdevc an object returned by an external JS library and treated as class Resources allows the use of the operators defined in Dynamic<T> but pub build reports the error message Js-interop class 'Resources' cannot extend from the non js-interop class 'Dynamic'.

@parnham parnham reopened this Jan 13, 2018
@vsmenon
Copy link
Member

vsmenon commented Jan 16, 2018

@jacob314

Likely this should be an error with dartdevc too.

@jacob314
Copy link
Member

This should be an error. The code size and performance implications for dart2js without strong mode are bad to the point where it should be prohibited.

@vsmenon vsmenon added the type-bug Incorrect behavior (everything from a crash to more subtle misbehavior) label Jan 16, 2018
@vsmenon
Copy link
Member

vsmenon commented Jan 16, 2018

Ok, filed as a DDC bug.

@parnham
Copy link
Author

parnham commented Jan 16, 2018

Thanks. It's a shame because it was a really neat way of tidying up my interop library. I guess I'll have to stick to using wrapper classes instead.
Is there any possibility that we'll be able to extend @JS() classes in the future?

@jacob314
Copy link
Member

Yes. Once Dart2JS takes full advantage of strong mode we should be able to make @js classes a lot more powerful without taking a performance hit.

@parnham
Copy link
Author

parnham commented Jan 16, 2018

Excellent! I look forward to it

@matanlurey matanlurey added the closed-obsolete Closed as the reported issue is no longer relevant label Jun 22, 2018
@matanlurey
Copy link
Contributor

package:js/js_util.dart is a reasonable workaround until we have a new JS interop plan.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-obsolete Closed as the reported issue is no longer relevant type-bug Incorrect behavior (everything from a crash to more subtle misbehavior) web-dart2js
Projects
None yet
Development

No branches or pull requests

8 participants