Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to update polymer element bindings after it was removed from DOM and inserted again

Assume we have container polymer element and another dummy polymer element. Container polymer element has div block for inserting another polymers.

container_polymer.html

<polymer-element name='container-polymer'>
  <template>
    <div id="container">
    </div>
    <button on-click="{{first}}">show first</button>
    <button on-click="{{firstPrepared}}">show first prepared</button>
    <button on-click="{{second}}">show second</button>
  </template>
  <script type="application/dart" src="container_polymer.dart">
  </script>
</polymer-element>

There are three buttons for inserting dummy polymers:

  1. First button inserts first dummy polymer into container.
  2. Second button also inserts first dummy polymer, and calls prepareElement() on this polymer.
  3. Third button inserts second dummy polymer into container

container_polymer.dart

import 'package:polymer/polymer.dart';
import 'dart:html';
import 'dummy_polymer.dart';

@CustomTag('container-polymer')
class ContainerPolymer extends PolymerElement {
  PolymerElement firstPolymer, secondPolymer, currentPolymer;
  Element container;

  ContainerPolymer.created() : super.created();

  void enteredView() {
    super.enteredView();
    container = $['container'];
  }

  void first(Event e, var detail, Node target) {
    showFirst(false);
  }

  void firstPrepared(Event e, var detail, Node target) {
    showFirst(true);
  }

  void showFirst(bool prepare) {
    if (firstPolymer == null) {
      DummyPolymer dummyPolymer = new Element.tag("dummy-polymer");
      dummyPolymer.title = "first";
      firstPolymer = dummyPolymer;
    }

    if (currentPolymer != firstPolymer) {
      if (secondPolymer != null) {
        secondPolymer.remove();
      }

      if (prepare) {
        firstPolymer.prepareElement();  
      }
      currentPolymer = firstPolymer;
      container.children.add(firstPolymer); 
    }
  }

  void second(Event e, var detail, Node target){
    if (currentPolymer != secondPolymer) {
      DummyPolymer dummyPolymer = new Element.tag("dummy-polymer");
      dummyPolymer.title = "second";
      secondPolymer = dummyPolymer;
      if (firstPolymer != null) {
        firstPolymer.remove();
      }

      currentPolymer = secondPolymer;
      container.children.add(secondPolymer);
    }
  }

}

Dummy polymer has several observable properties in order to test binding works. When you click inside this polymer it changes background color of root div, background color of title div and increases counter. Also it has input for detecting if state of polymer element was changed and block with white background for testing usage of parent style.

dummy_polymer.html

<polymer-element name='dummy-polymer'>
  <template>
    <div style="width: 500px; height: 300px; background-color: {{color}}" on-click="{{changeColor}}">
      <div id="title">
        <h1>{{title}}</h1>
        <span>Clicks: {{clicks}}</span>
      </div>
      <input type="text" />
      <div class="external">Parent style block: background should be white</div>
    </div>

  </template>

  <script type="application/dart" src="dummy_polymer.dart">
  </script>

</polymer-element>

dummy_polymer.dart

import 'package:polymer/polymer.dart';
import 'dart:html';

@CustomTag('dummy-polymer')
class DummyPolymer extends PolymerElement {
  @observable String color = "red";
  @observable String title;
  @observable num clicks = 0;
  Element titleElement;

  DummyPolymer.created() : super.created() {
    var root = getShadowRoot('dummy-polymer');
    root.applyAuthorStyles = true;
  }

  void enteredView() {
    super.enteredView();
    titleElement = $['title'];
  }

  void changeColor(Event e, var detail, Node target){
    clicks++;

    if (color == "red") {
      color = "green";
    } 
    else if (color == "green") {
      color = "blue";
    }
    else {
      color = "red";
    }
    titleElement.style.backgroundColor = color;
  }

}

Test page hosted here http://dart-style-binding-test.herokuapp.com/

So, to reproduce my issue do following:

  1. Click 'show first' button. Make sure clicking inside changes background color and increases counter.
  2. Type something into input field
  3. Click 'show second'
  4. Click 'show first'. Make sure first polymer has the same state as before: background color, counter and input field text.
  5. Click inside first polymer. Now it doesn't change background color and counter, but it changes background color of area at the top.

Binding to observable properties doesn't work anymore. But on-click handler works, you can see it when background color of top area changing. It's implemented by changing element's backgroundColor property directly:

titleElement.style.backgroundColor = color;

So, my question is: how to update binding mechanism properly after existing polymer was inserted into DOM again?

There is one way to do it dirty using 'show first prepared' button. It calls prepareElement() on first element before inserting into container. So, do following:

  1. Click 'show first' button. Make sure clicking inside changes background color and increases counter.
  2. Type something into input field
  3. Click 'show second'
  4. Click 'show first prepared'. You can see input element is empty, but counter and background color are up to date. (If you run this demo in dartium also block with white color will change its background color to parent's because parent style isn't applied)
  5. Click inside first polymer. Now it changes background color and counter. Bindings work properly, but we lose text inside input field.

Note: after first element had been inserted with 'show first prepared' button, bindings works well even after first element was inserted with 'show first' button.

Code here https://github.com/petalvlad/dart-style-binding-test

like image 230
Alex Petropavlovsky Avatar asked Dec 27 '13 17:12

Alex Petropavlovsky


1 Answers

I have not tried it myself but it seems reasonable. I hope the author doesn't mind coping his answer from Detached observables when re-using element.

Having looked into the polymer.js information bit I have found that there is a cancelUnbindAll function which must be called when the element is created or a preventDispose property set to true.

For anyone that might need to do the same, in the Dart implementation you must call cancelUnbindAll in the detached function after the super call, as follows:

void detached()
{
    super.detached();
    this.cancelUnbindAll(preventCascade: true);
}

Alternatively, you can simply override the preventDispose property in your custom element:

bool get preventDispose => true;
like image 105
Günter Zöchbauer Avatar answered Sep 19 '22 16:09

Günter Zöchbauer