Search code examples
htmlpolymer2-way-object-databinding

How to make data-binding between non-child elements work in Polymer


I'm making a remake of my website in Polymer. I'm currently trying to make two-way data binding work, for being able that the corresponding menu element becames selected when an iron-pages becames selected, and vice-versa. The code I'm currently using is:

<body class="fullbleed roboto">
    <paper-drawer-panel force-narrow>
        <paper-header-panel mode="standard" main>
            <paper-toolbar class="amber">
                <paper-icon-button icon="menu" paper-drawer-toggle></paper-icon-button>
                <div class="title">Charles Milette's Personnal Website</div>
            </paper-toolbar>
            <iron-pages selected="{{selected}}">
                <div id="content">Home Content</div>
                <div id="content">Page 1 Content</div>
            </iron-pages>
        </paper-header-panel>
        <div drawer>
            <img src="img/drawer.png" />
            <paper-menu selected="{{selected}}">
                <paper-item><iron-icon icon="home"></iron-icon>Home</paper-item>
                <paper-item>Page 1</paper-item>
            </paper-menu>
        </div>
    </paper-drawer-panel>
</body>

But, evidentually, it doesn't works, because data bindings only works between child and parent elements, as explained here.
I think attrForSelected may do what I need to, but I'm not sure how to use it.
Using some event listeners to listen for properties change and/or clicks can also do it, but I'm searching for a better way to do it (kinda like data binding).

Is there any better way than that? What is it?
If attrForSelected is what I search, how do I use it?

I'd appreciate if you also give me a code snippet.


Solution

  • This answer is essentially the same as Scott's, with a little more explanation of dom-bind and an example of how you would change pages from within the main content.

    JS Bin

    <body>
      <!-- dom-bind template is the key here. you can use it do declare properties
           at the scope of the template. any polymer elements that are children to
           to the template have access to the template's properties -->
      <template is="dom-bind" id="app">
        <paper-drawer-panel force-narrow>
          <paper-header-panel mode="standard" main>
            <paper-toolbar class="amber">
              <paper-icon-button icon="menu" paper-drawer-toggle></paper-icon-button>
              <div class="title">Website</div>
            </paper-toolbar>
            <!-- attr-for-selected="page" means "when the value of 'selected'
                 changes, select the <section> whose 'page' attribute equals
                 the value of 'selected' 
    
                 when attr-for-selected is omitted, then 'selected' just
                 equals the index of the page
    
                 the 'selected' property is scoped to the dom-bind, so you can think
                 of it as document.querySelector('#app').selected 
    
                 you only need one-way data-binding here ('[[selected]]') 
                 because <iron-pages> doesn't modify the 'selected' property -->
            <iron-pages attr-for-selected="page" selected="[[selected]]">
              <section page="home">
                <p>value of selected: {{selected}}</p>
                <!-- check out how this ordinary button has access to polymer's 
                     on-* declarative events, thanks to dom-bind! -->
                <button on-tap="about">go to about page</button>
              </section>
              <section page="about">
                <p>value of selected: {{selected}}</p>
                <button on-tap="home">go to home page</button>
              </section>
            </iron-pages>
          </paper-header-panel>
          <div drawer>
            <!-- attr-for-selected works mostly the same here as it did above, 
                 with one difference. when you select a <paper-item>, then the 
                 'selected' property is set to the value of the 'page' attribute
    
                 you need two-way data binding here ('{{selected}}') because we are
                 modifying the value of 'selected'
            -->
            <paper-menu attr-for-selected="page" selected="{{selected}}">
              <paper-item page="home">home</paper-item>
              <paper-item page="about">about</paper-item>
            </paper-menu>
          </div>
        </paper-drawer-panel>
      </template>
      <script>
    
        // you can access dom-bind properties imperatively, too  
        (function(document) {
          var app = document.querySelector('#app');
          // the app shows the 'about' page first even though it's the second menu 
          // item because we are setting app.selected imperatively when DOM is ready
          app.selected = 'about';
          // changing the selected page is as simply as changing the 
          // value of app.selected
          app.about = function() {
            app.selected = 'about';
          };
          app.home = function() {
            app.selected = 'home';
          };
        }) (document);
      </script>
    </body>