dart-archive / polymer-dart

Polymer support for Dart
https://pub.dartlang.org/packages/polymer
BSD 3-Clause "New" or "Revised" License
181 stars 33 forks source link

Uncaught TypeError: Cannot read property '__dartClass__' of undefined #711

Open pjjjv opened 8 years ago

pjjjv commented 8 years ago

Having trouble registering a propertyChanged observer in Polymer.dart (dart 1.18.1). The method pageChanged is never called when I run pub serve and surf to http://localhost:......web/index.html#/home. I suspect it is not registered in the first place.

Error:

Uncaught TypeError: Cannot read property '__dartClass__' of undefined
  Polymer.Dart.InteropBehavior._propertyChanged 
  Polymer.Bind._modelApi._propertySetter    
  Polymer.Base._addFeature._notifyPath  
  (anonymous function)  
  Polymer.Base._addFeature._notifyListener  
  (anonymous function)  
  Polymer.Base._addFeature.fire 
  Polymer.Base._addFeature._notifyPathUp    
  Polymer.Base._addFeature._notifyPath  
  Polymer.Base._addFeature.set  
  Polymer.__routeQueryParamsChanged 
  Polymer.Base.extend._complexObserverEffect    
  Polymer.Base._addFeature._complexObserverPathEffect   
  Polymer.Base._addFeature._pathEffector    
  Polymer.Base._addFeature._notifyPath  
  Polymer.Base._addFeature.set  
  Polymer.__tailQueryParamsChanged  
  Polymer.Base.extend._complexObserverEffect    
  Polymer.Base._addFeature._complexObserverPathEffect   
  Polymer.Base._addFeature._pathEffector    
  Polymer.__setMulti    
  Polymer.__tryToMatch  
  Polymer.Base.extend._complexObserverEffect    
  Polymer.Bind._modelApi._effectEffects 
  Polymer.Bind._modelApi._propertySetter    
  Polymer.Bind._modelApi.__setProperty  
  Polymer.Base._addFeature._applyEffectValue    
  Polymer.Base.extend._annotationEffect 
  Polymer.Bind._modelApi._effectEffects 
  Polymer.Bind._modelApi._propertySetter    
  setter    
  (anonymous function)  
  Polymer.Base._addFeature._flushHandlers   
  Polymer.Base._addFeature._afterClientsReady   
  Polymer.Base._addFeature._ready   
  Polymer.Base._addFeature._tryReady    
  Polymer.Base._addFeature._initFeatures    
  Polymer.Base.createdCallback  
  (anonymous function)  
  registerDartTypeUpgrader  
  (anonymous function)  

See https://github.com/pjjjv/Akepot/commit/dbf85b3bae3647fbfea78ff6e0a3807fd6e3de1d file app_akepot.dart.

dam0vm3nt commented 8 years ago

Please can you provide more context? Pubspec.yaml to begin with and snippets of code could help diagnose the problem.

pjjjv commented 8 years ago

Here's the pubspec.yaml: https://github.com/pjjjv/Akepot/blob/dbf85b3bae3647fbfea78ff6e0a3807fd6e3de1d/pubspec.yaml. Full source code was linked to above.

pjjjv commented 8 years ago

The error only occurs when I add tail="{{subroute}}" to the app-route element in app-akepot.html.

Some code snippets:

<link rel="import" href="../../packages/polymer/polymer.html">

<dom-module id="app-akepot">
  <style>
    :host {
      display: block;
    }
  </style>
  <link rel="import" type="css" href="app_akepot.css">
  <template>
    <div>Abba</div>
    <login-screen id="loginscreen" layout vertical center-center fit class="{{(signedIn && readyDom) ? '' : 'show'}}" signedin="{{signedIn}}"></login-screen>

    <competences-service id="service" user="{{user}}" signedin="{{signedIn}}" readydom="{{readyDom}}"></competences-service>

    <app-location route="{{route}}" use-hash-as-path></app-location>
    <app-route
        route="{{route}}"
        pattern="/:page"
        data="{{routeData}}"
        tail="{{subroute}}"></app-route>

    <iron-pages role="main" selected="[[page]]" attr-for-selected="name">
      <page-home name="home"></page-home>
      <page-edit name="edit"></page-edit>
      <page-not-found name="not_found"></page-not-found>
    </iron-pages>

    <paper-toast id="toast" text="Connection timed out. Showing limited messages.">
      <paper-button dismissive tabindex="0" autofocus class="flat-button">Retry</paper-button>
    </paper-toast>

  </template>

</dom-module>
@HtmlImport('app_akepot.html')
library akepot.lib.app_akepot;

import 'package:polymer/polymer.dart';
import 'package:web_components/web_components.dart';
import 'package:akepot/login_screen.dart';
import 'package:akepot/competences_service.dart';
import 'package:akepot/pages/page_home.dart';
import 'package:akepot/pages/page_edit.dart';
import 'package:akepot/pages/page_not_found.dart';
import 'package:polymer_elements/app_route.dart';
import 'package:polymer_elements/app_location.dart';
import 'package:polymer_elements/iron_pages.dart';

@PolymerRegister('app-akepot')
class AppAkepot extends PolymerElement {
  @property bool signedIn;
  @property bool readyDom;
  @property User user;
  @property CompetencesService service;
  @Property(observer: 'pageChanged', notify: true) String page;

  AppAkepot.created() : super.created();

  static const int MIN_SPLASH_TIME = 1000;
  static const Duration SPLASH_TIMEOUT =  const Duration(milliseconds: MIN_SPLASH_TIME);

  void ready () {
    service = $$('#service');
  }

  @Observe('routeData.page')
  void routePageChanged(String page) {
    if (page == null || page == ""){
      page = 'home';
    }
    this.page = page;
  }

  @reflectable
  void pageChanged(String page, String oldValue) {
    // load page import on demand.
    Polymer.importHref('pages/page_' + page + '.html');
  }
}
jonboj commented 7 years ago

From slack. app_akepot.htmlhas in <app-route ...> bindings to properties route, routeData and subroute I don't find them in app_akepot.dart, normally shouldn't they be defined here or?

pjjjv commented 7 years ago

I believe that solves it. Thanks for your response in slack.

Totally couldn't make that up from the error message though. Can that be fixed?

jonboj commented 7 years ago

Great. I haven't seen that warnings for this could be activated, maybe there are some option in js polymer.

pjjjv commented 7 years ago

Sorry, I checked superficially. Adding subroute to the dart file does not solve it.

The error message is thrown in:


<script>
  (function() {
    Polymer.Base.originalPolymerCreatedCallback =
        Polymer.Base.createdCallback;
    Polymer.Base.createdCallback = function() {
      if (this.__isPolymerDart__) return;
      this.originalPolymerCreatedCallback();
    };

    Polymer.Dart = {};

    // Placeholder for `undefined` return values. This should eventually go
    // away, once we have dart:js support for `undefined`.
    Polymer.Dart.undefined = {};

    // Used to get an empty constructor function which doesn't call into dart.
    Polymer.Dart.functionFactory = function() { return function() {}; };

    // Generates a function which accesses a particular property on a dart
    // class
    Polymer.Dart.propertyAccessorFactory = function(property, dartGetter) {
      return function() {
        if (this.__cache__) return this.__cache__[property];
        var value = dartGetter(this.__dartClass__);
        if (value === Polymer.Dart.undefined) return undefined;
        return value;
      };
    };

    // Generates a function which sets a particular property on a dart class.
    Polymer.Dart.propertySetterFactory = function(property, dartSetter) {
      return function(value) {
        if (this.__cache__) this.__cache__[property] = value;
        dartSetter(this.__dartClass__, value);
      };
    };

    // Generates a function which invokes a dart function with this or the
    // __dartClass__ proxy if it exists as the first argument, and all other
    // arguments as the second argument.
    Polymer.Dart.invokeDartFactory = function(dartFunction) {
      return function() {
        var thisArg = this.__dartClass__ ? this.__dartClass__ : this;
        // Must convert `arguments` to an actual array that the js side can
        // recognize.
        var args = [];
        for (var i = 0; i < arguments.length; i++) {
          args[i] = arguments[i];
        }
        var value = dartFunction(thisArg, args);
        if (value === Polymer.Dart.undefined) return undefined;
        return value;
      }
    };

    // TODO(jakemac): This shouldn't be necessary, but it is today
    // https://github.com/dart-lang/sdk/issues/24371
    Polymer.Dart.serialize = Polymer.Base.serialize;
    Polymer.Dart.deserialize = Polymer.Base.deserialize;

    Polymer.Dart.InteropBehavior = {
      // Secret hook into property changes. Pretty hacky but its more efficient
      // than using a JsProxy object for the element.
      _propertyChanged: function(path, newValue, oldValue) {
        var parts = this._getPathParts(path);
        var prop = parts.splice(parts.length - 1, 1)[0];

        // Get the model which is being updated.
        var thisArg = this.get(parts);

        // If the property to update is an array key, we need to get the index
        // for that item instead.
        if (prop.charAt(0) == '#') {
          // Ouch, O(n) operation :(. Also doesn't support duplicates :(.
          prop = thisArg.indexOf(Polymer.Collection.get(thisArg).getItem(prop));
        }

        // Grab the __dartClass__ if we already have one
        thisArg = thisArg.__dartClass__ || thisArg; **<<<<<------error message thrown here**

        // Finally, update things on the dart side.
        Polymer.Dart.propertyChanged(thisArg, prop, newValue);
      },

      serialize: function(value, type) {
        return Polymer.Dart.serialize(value, type);
      }
    }
  })();
</script>
pjjjv commented 7 years ago

Here's with the debugger variables. I do have an event "signedIn" elsewhere in my code.


    Polymer.Dart.InteropBehavior = {
      // Secret hook into property changes. Pretty hacky but its more efficient
      // than using a JsProxy object for the element.
      _propertyChanged: function(path, newValue, oldValue) {   path = "signedIn", newValue = false, oldValue = undefined
1562
        var parts = this._getPathParts(path);  parts = []
1563
        var prop = parts.splice(parts.length - 1, 1)[0];    prop = "signedIn"
1564
​
1565
        // Get the model which is being updated.
1566
        var thisArg = this.get(parts);   thisArg = competences-service#service.style-scope.app-akepot, parts = []
1567
​
1568
        // If the property to update is an array key, we need to get the index
1569
        // for that item instead.
1570
        if (prop.charAt(0) == '#') {  prop = "signedIn"
1571
          // Ouch, O(n) operation :(. Also doesn't support duplicates :(.
1572
          prop = thisArg.indexOf(Polymer.Collection.get(thisArg).getItem(prop));   prop = "signedIn", thisArg = competences-service#service.style-scope.app-akepot
1573
        }

        // Grab the __dartClass__ if we already have one
        thisArg = thisArg.__dartClass__ || thisArg;

        // Finally, update things on the dart side.
        Polymer.Dart.propertyChanged(thisArg, prop, newValue);
      },
pjjjv commented 7 years ago

It gets into the same trouble when path = "route.__queryParams".

jonboj commented 7 years ago

@pjjjv The newpolymer branch contains the latest and updated version of the code https://github.com/pjjjv/Akepot/tree/newpolymer ?

dam0vm3nt commented 7 years ago

Try initializing fields bounded to app-route tag attributrs with {}.

pjjjv commented 7 years ago

@jonboj correct

pjjjv commented 7 years ago

@dam0vm3nt Indeed, I debugged the propertyChanged function. After I uncommented the signedIn parts from my code, the only remaining variable that it stumbles over is "subroute". If I remove this, then finally, my pageChanged method is triggered. But the reason the javascript code stumbles over subroute is that it does not find it on AppAkepot. Your solution of setting "subroute ={};" for initialization solves that, and this works too. Thanks @dam0vm3nt.

Now, the standard app-route element seems to be easily high on difficult data bindings involving structured data and leads to observing path changes internally in Polymer.dart.

Is this necessary initialization documented somewhere? Does it apply to most polymer elements?

jonboj commented 7 years ago

@pjjjv Just looking in the code at branch newpolymer The routeDatais treated as a structured binded data. https://github.com/pjjjv/Akepot/blob/newpolymer/lib/app_akepot.dart#L37

routeData is declared as dynamic type https://github.com/pjjjv/Akepot/blob/newpolymer/lib/app_akepot.dart#L21

Polymer does a lot of instrumentation of the binded properties, since dynamic is like so fare unknown type, I suspect this will not work with a dynamic. I think before doing more bug search on <app-route> it is worth to get some confirmation that bind to dynamic works.


Update Did a test with binding to dynamic with structured data. Works ok, also an @Observe method is correct triggered.

dam0vm3nt commented 7 years ago

This is a bug on polymer_interop imho. This bug is triggered by those componente because they notify null values that usually doesn't happen. The code in polymer_interop doesn't check for null and this leads the error. I'm going to send a patch to polymer_interop but don't know if it gets accepted soon because at the moment the maintainer seams to be away

dam0vm3nt commented 7 years ago

hi, can you try adding the following overrides to you yaml and see if the problem is gone ?


 polymer:
    version:  "^1.0.0-rc.18.exp.6"
    hosted:
      name: polymer
      url: http://pub.drafintech.it:5001
 polymer_interop:
    version: "^1.0.0-rc.10.exp.8"
    hosted:
      name: polymer
      url: http://pub.drafintech.it:5001

This is a polymer version corresponding to the latest PR I've submitted. It contains also an new strategy for converting to and from JS that should be more performant for lists and maps.

pjjjv commented 7 years ago

@dam0vm3nt These PRs solve it as far as I can tell. The error is gone. (I had trouble reproducing for a while so the circumstance might be slightly different).

dam0vm3nt commented 7 years ago

fixed by https://github.com/dart-lang/polymer_interop/pull/47