6

Question

In the below example, how do I bind the obj.name variable of the <input> field in <test-element2> to <test-element> ?


Background:

Below is my code. I have two polymer elements. test-element has the data binded to obj.name. test-element2 has an input field which is observed by the function objChanged. Whatever value I change in the input field it changes and prints in test-element2 but the change isn't reflected in test-element. Can any body help get the value reflected to test-element1? I have a solution using this.fire("object-change") for when the text changes but I am looking for a solution without using event listeners.

One more thing is that I need to create an element from the script and it cannot be hardcoded in the HTML DOM.


Code:
<!DOCTYPE html>

<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Demo</title>

    <script src="../../bower_components/webcomponentsjs/webcomponents-lite.js"></script>
    <link rel="import" href="../../bower_components/polymer/polymer.html"/>
</head>

<body>
    <dom-module id="test-element">
        <template>
            <div>Hello <span>{{obj.name}}</span></div>
        </template>

        <script>
            TestElement = Polymer({
                is: "test-element",

                properties: {
                    "obj": {
                        type: Object,
                        notify: true
                    }
                },

                observers: [
                    "objChanged(obj.name)"
                ],
                "objChanged": function() {
                    var that = this;
                    console.log("First element in 1",that.obj);
                }
            });
        </script>
    </dom-module>


    <dom-module id="test-element2">
        <template>
            <input value="{{obj.name::input}}"/>
        </template>

        <script>
            Polymer({
                is: "test-element2",

                properties: {
                    "obj": {
                        type: Object,
                        notify: true,
                        value: {
                            "name": "Charlie"
                        }
                    }
                },

                observers: [
                    "objChanged(obj.name)"
                ],

                ready: function() {
                    var element = new TestElement();
                    element.set("obj", this.obj);
                    this.appendChild(element);
                },

                "objChanged": function() {
                    console.log("changed in test-element2:", this.obj);
                }
            });
        </script>
    </dom-module>


    <test-element2></test-element2>
 </body>
</html>
razorsyntax
  • 339
  • 3
  • 8
  • 42
Harsha Kakumanu
  • 703
  • 1
  • 7
  • 17

5 Answers5

4

If you include <test-element> in the <template> of test-element2 you can avoid using event listeners or observers. In this way test-element2 handles the data binding between the input and <test-element> for you.

Below is a live working example that maintains the obj property as you have set it up in your elements.

<script src="http://www.polymer-project.org/1.0/samples/components/webcomponentsjs/webcomponents.min.js"></script>
<link rel="import" href="http://www.polymer-project.org/1.0/samples/components/polymer/polymer.html">
<dom-module id="test-element">
  <template>
    <div>Hello <span>[[obj.name]]</span>
    </div>
  </template>
  <script>
    TestElement = Polymer({
      is: "test-element",

      properties: {
        "obj": {
          type: Object,
          notify: true
        }
      }
    });
  </script>
</dom-module>
<dom-module id="test-element2">
  <template>
    <input value="{{obj.name::input}}" />
    <test-element obj="[[obj]]"></test-element>
  </template>

  <script>
    Polymer({
      is: "test-element2",

      properties: {
        "obj": {
          type: Object,
          notify: true,
          value: {
            "name": "Charlie"
          }
        }
      }
    });
  </script>
</dom-module>
<test-element2></test-element2>

Currently, imperative data-binding is not supported in Polymer 1.0 outside of <template is="dom-bind">.

I would recommend setting up observers like the example below or adjusting your requirements to include <test-element> in test-element2.

button {
  display: block;
}
<script src="http://www.polymer-project.org/1.0/samples/components/webcomponentsjs/webcomponents.min.js"></script>
<link rel="import" href="http://www.polymer-project.org/1.0/samples/components/polymer/polymer.html">
<dom-module id="test-element">
  <template>
    <div>Hello <span>[[obj.name]]</span>
    </div>
  </template>
  <script>
    TestElement = Polymer({
      is: "test-element",

      properties: {
        obj: {
          type: Object,
          notify: true
        }
      }
    });
  </script>
</dom-module>
<dom-module id="test-element2">
  <template>
    <input value="{{obj.name::input}}" />
  </template>

  <script>
    Polymer({
      is: "test-element2",

      properties: {
        obj: {
          type: Object,
          notify: true,
          value: {
            "name": "Charlie"
          }
        }
      },
      observers: ["objNameChanged(obj.name)"],
      objNameChanged: function(newValue) {
        Polymer.dom(document).querySelectorAll("test-element").forEach(function(element) {
          element.notifyPath("obj.name", newValue);
        });

        Polymer.dom(this.root).querySelectorAll("test-element").forEach(function(element) {
          element.notifyPath("obj.name", newValue);
        });
      }
    });
  </script>
</dom-module>
<test-element2></test-element2>
<button>Add test-element to <em>test-element2</em>
</button>
<button>Add test-element to <em>body</em>
</button>
<script>
  var testElement2 = document.querySelector("test-element2");

  var createTestElement = function(insertPoint) {
    var testElement = new TestElement();
    testElement.notifyPath("obj.name", testElement2.obj.name);

    insertPoint.appendChild(testElement);
  };

  document.querySelector("button:nth-of-type(2)").addEventListener("click", function() {
    createTestElement(Polymer.dom(document).querySelector("body"));
  });

  document.querySelector("button").addEventListener("click", function() {
    createTestElement(Polymer.dom(testElement2.root));
  });
</script>
Community
  • 1
  • 1
coderfin
  • 1,708
  • 19
  • 24
  • Hi Coderfin I need to create an element on user interaction at that point like var element = new TestElement(); Because i am not sure that how many test-element's will be inserted. Your solution is right if the element is already in DOM. Thank you – Harsha Kakumanu Jul 22 '15 at 21:13
  • I updated my answer to reflect the fact that **imperative data-binding** is not currently supported in Polymer 1.0. @user1758013 – coderfin Jul 22 '15 at 23:38
  • Yeah. This snippet and information help me a lot. Thank you @coderfin – Harsha Kakumanu Jul 22 '15 at 23:47
3

If you choose to break out your elements into their own files, you could follow this Plunker example (by nazerke) demonstrating two-way data binding by having one component observing another's property.

Code

index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <script src="http://www.polymer-project.org/1.0/samples/components/webcomponentsjs/webcomponents-lite.min.js"></script>
  <link rel="import" href="parent-element.html">
  <link rel="import" href="first-child.html">
  <link rel="import" href="second-child.html"> </head>

<body>
  <parent-element></parent-element>
</body>

</html>

parent-element.html

<link rel="import" href="http://www.polymer-project.org/1.0/samples/components/polymer/polymer.html">
<dom-module id="parent-element">
  <template>
    <first-child prop={{value}}></first-child>
    <second-child feat1={{prop}}></second-child> In parent-element
    <h1>{{value}}</h1> </template>
  <script>
    Polymer({
      is: "parent-element",
      properties: {
        value: {
          type: String
        }
      },
      valueChanged: function() {
        console.log("value changed");
      }
    });
  </script>
</dom-module>

first-child.html

<link rel="import" href="http://www.polymer-project.org/1.0/samples/components/polymer/polymer.html">
<dom-module id="first-child">
  <template>
    <p>first element.</p>
    <h2>{{prop}}</h2> </template>
  <script>
    Polymer({
      is: "first-child",
      properties: {
        prop: {
          type: String,
          notify: true
        }
      },
      ready: function() {
        this.prop = "property";
      }
    });
  </script>
</dom-module>

second-child.html

<link rel="import" href="http://www.polymer-project.org/1.0/samples/components/polymer/polymer.html">
<dom-module id="second-child">
  <template>
    <p>Second element.</p>
    <h2>{{feat1}}</h2> </template>
  <script>
    Polymer({
      is: "second-child",
      properties: {
        feat1: {
          type: String,
          notify: true,
          value: "initial value"
        }
      },
      ready: function() {
        this.addEventListener("feat1-changed", this.myAct);
      },
      myAct: function() {
        console.log("feat1-changed ", this.feat1);
      }
    });
  </script>
</dom-module>
Let Me Tink About It
  • 15,156
  • 21
  • 98
  • 207
1

If you choose to break out your elements into their own files, you could use <iron-meta> to two-way data bind as described here:

Example Code:

<iron-meta key="info" value="foo/bar"></iron-meta>
...
meta.byKey('info').getAttribute('value').

or

document.createElement('iron-meta').byKey('info').getAttribute('value');

or

<template>
  ...
  <iron-meta id="meta"></iron-meta>
  ...
  this.$.meta.byKey('info').getAttribute('value');
  ....
</template>
Let Me Tink About It
  • 15,156
  • 21
  • 98
  • 207
1

If you choose to break out your elements into their own files, you could use <iron-localstorage> to two-way data bind as described here.

Example Code:

<dom-module id="ls-sample">
  <iron-localstorage name="my-app-storage"
    value="{{cartoon}}"
    on-iron-localstorage-load-empty="initializeDefaultCartoon"
  ></iron-localstorage>
</dom-module>

<script>
  Polymer({
    is: 'ls-sample',
    properties: {
      cartoon: {
        type: Object
      }
    },
    // initializes default if nothing has been stored
    initializeDefaultCartoon: function() {
      this.cartoon = {
        name: "Mickey",
        hasEars: true
      }
    },
    // use path set api to propagate changes to localstorage
    makeModifications: function() {
      this.set('cartoon.name', "Minions");
      this.set('cartoon.hasEars', false);
    }
  });
</script>
Let Me Tink About It
  • 15,156
  • 21
  • 98
  • 207
0
<dom-module id="test-element">
  <template>
    <div>Hello <span>{{name}}</span></div>
  </template>
  <script>
    Polymer({
      is: "test-element",

      properties: {
        name: String
      }
    });
  </script>
</dom-module>

<dom-module id="test-element2">
  <template>
    <input value="{{name::input}}"/>
    <test-element name="[[name]]"></test-element>
  </template>
  <script>
    Polymer({
      is: "test-element2",

      properties: {
        name: String
      }
    });
  </script>
</dom-module>
Zikes
  • 5,888
  • 1
  • 30
  • 44
  • Hi Zikes I need to create an element on user interaction at that point like var element = new TestElement(); Because i am not sure that how many test-element's will be inserted. Your solution is right if the element is already in DOM. Thank you – Harsha Kakumanu Jul 22 '15 at 21:14