David Voyles June 25th, 2015

WebDev: How to Access Member Functions in Polymer Elements

I’m working on a silly Polymer project right now, which parses a Pokémon database and returns a picture of a Pokémon, then speaks the creature’s name over your speakers. Here’s the source code for my project. It’s my first time using Polymer, and I’m certainly getting snagged in a few spots. Most recently, it was trying to return member functions of a Polymer object that I created. Took me forever to figure this out, so I wanted to share it with you in this tutorial. ploymerelements_DE Sidenote: you can also search for my more detailed write-up on Web Components here.

The Wrong Way

I have a Web Component which looks like this:
<x-radial-buttons id="radial-button-template"></x-radial-buttons>
If I try to access it by its ID….
var  temp = document.querySelector("#radial-button-template");
// returns <x-radial-buttons id=”radial-button-template”></x-radial-buttons>
But I cannot access any of the function it. They return “undefined”.  So if I tried this:
var  temp = document.querySelector("#radial-button-template");
temp.getFirstElement  // returns undefined

Why is This Happening?

The reason behind this is due to the Shadow DOM’s encapsulation. It is both a gift and a curse. In this case, I am accessing the element, and not the shadowRoot, which will expose the public methods attached to the Shadow DOM object. In the next step, you will see how I can access the member functions in my custom element, as well as how I can reutrn nodes that lie even deeper in my web component. Rob Dobson of Google’s Polymer team explains this well in his blog post. Eric Bidleman goes into even more detail in his advanced Shadow DOM article. I highly suggest taking the time to read these over to better understand how this version of the DOM works.

One Way of Doing it…

var btn = document.querySelector("x-radial-buttons"); Notice that I’m not using the hash symbol (#) to access the element, as though it were an ID. Instead, you need to simply refer to the name of the polymer-element itself: document.querySelector("x-radial-buttons"); so now I can write:
var temp = document.querySelector("x-radial-buttons");
// ALSO returns <x-radial-buttons id="radial-button-template"></x-radial-buttons>
Now I can access all of the members:
var temp = document.querySelector("x-radial-buttons");
  temp.getFirstElement
// returns <paper-radio-button label="English-US" id="paper_radio_btn_en-US" on-click="{{ changeAccentUS }}" role="radio" tabindex="0" aria-checked="false" aria-label="English-US"></paper-radio-button>
Therefore, I suggest not assigning an ID to your polymer-element at all. As Rob made clear in the comments below, you can query for a custom element however you want (via ID, class, attr, or element name) and get the same thing. Here’s his example: http://jsbin.com/qikaya/2/edit

Another Way of Doing it…

You can also grab the ID of a polymer element and access the member functions. This is done by ‘polymer-ready’ event. As the docs describe it:
Polymer parses element definitions and handles their upgrade asynchronously. If you prematurely fetch the element from the DOM before it has a chance to upgrade, you’ll be working with a plain HTMLElement, instead of your custom element.
And this is exactly the issue I was running into earlier. I was trying to grab functions within my polymer-element before polymer had a chance to upgrade it.  Here’s an example:
<head>
  <link rel="import" href="path/to/x-foo.html">
  </head>
  <body>
  <x-foo></x-foo>
  <script>
  window.addEventListener('polymer-ready', function(e) {
  var xFoo = document.querySelector('x-foo');
  xFoo.barProperty = 'baz';
  });
  </script>
</body>
In conclusion, as long as you wrap the functions you are trying to call in the polymer-ready event, you should be good to go, and can call functions from your polymer-element.

Here is How I am Using it

<link href="bower_components/polymer/polymer.html" rel="import">
<link rel="import" href="../paper-radio-group/paper-radio-group.html">
<link rel="import" href="../paper-radio-button/paper-radio-button.html">
<polymer-element name="x-radial-buttons">
    <!-- Shadow DOM -->
  <template>
  <style>
  #paper_radio_group {
  position: relative;
  width: 440px;
  height: 50px;
  }
  </style>
  <paper-radio-group selected="English-GB" valueattr="label" selectedindex="1" id="paper_radio_group">
  <paper-radio-button label="English-US" id="paper_radio_btn_en-US" on-click="{{ changeAccentUS }}"></paper-radio-button>
  <paper-radio-button checked label="English-GB" id="paper_radio_btn_en-GB" on-click="{{ changeAccentGB }}"></paper-radio-button>
  <paper-radio-button label="Spanish" id="paper_radio_btn_es-ES" on-click="{{ changeAccentES }}"></paper-radio-button>
  </paper-radio-group>
  </template> <!-- /Shadow DOM -->
    <script>
  Polymer('x-radial-buttons', {
  /* -- Attributes ------------------------------------------------ */
  currentAccent: 'en-US',
  //accentArr: ['en-US', 'en-GB, es-ES'],
  created: function() {
  accentArr = ['en-US', 'en-GB', 'es-ES'];
  },
  
  /* -- Methods --------------------------------------------------- */
  getCurrentAccent: function() {
  return currentAccent;
  },
  getFirstElement: function () {
  console.log(this.$.paper_radio_group.querySelector("#paper_radio_btn_en-US"));
  return this.$.paper_radio_group.querySelector("#paper_radio_btn_en-US");
  },
  getSecondElement: function () {
  return this.$.paper_radio_group.querySelector("#paper_radio_btn_en-GB");
  },
  getThirdElement: function () {
  return this.$.paper_radio_group.querySelector("#paper_radio_btn_es-ES");
  },
  changeAccentUS: function(event, detail, sender) {
  console.log("Changed accent to American " + this.currentAccent);
  currentAccent = accentArr[0];
  //PokémonApp.changeAccent();
  },
  changeAccentGB: function() {
  console.log("Changed accent to British " + this.currentAccent);
  //currentAccent = accentArr[1];
  PokémonApp.changeAccent();
  },
  changeAccentES: function () {
  console.log("Changed accent to Spanish " + this.currentAccent);
  currentAccent =  accentArr[2];
  PokémonApp.changeAccent();
  }
  });
  </script>
  </polymer-element>
(function (PokémonApp) {
         // Grab inputs and button for speech-to-text
  var form                 = document.querySelector('#player-form'),
  input                = document.querySelector('#player-input'),
  playerElement        = document.querySelector('#player-element'),
  xPokémon             = document.querySelector('#x-Pokémon'),
  btnChangeAccent      = document.querySelector('#btn-change-accent'),
  radialButtonTemplate = document.querySelector("#radial-button-template"),
  playerAccent         = playerElement.getAttribute("accent");
         // Take text from input & set it as the text that the speaker will say.
  // Set the name of the Pokémon, which angular will then grab from the Pokémon DB 
  input.addEventListener('input', function (e) {
  playerElement.setAttribute('text', input.value);
  xPokémon.name = input.value;
  });

         // Say the text when button is pressed
  form.addEventListener('submit', function (e) {
  e.preventDefault();
  playerElement.speak();
  var btn = document.querySelector("x-radial-buttons");
  btn.getFirstElement();
  });

}(window.PokémonApp = window.PokémonApp || {}));
<header>
  <h1>article header h1</h1>
  <p>This web app takes advantage of Web Components and Polymer to enable new HTML features in the browser.</p>
  <p>
  In this particular case, we are using <a href="https://github.com/passy/x-Pokémon">
  the x-Pokémon web component </a> to pull the images from a database, as well as the 
  <a href="http://zenorocha.github.io/voice-elements/">voice-elements web component</a> to speak the name of the Pokémon we entered.                                                     
  </p>
  <h2>Change the accent</h2>
  <x-radial-buttons id="radial-button-template"></x-radial-buttons>
</header>

More Learning on JavaScript

It might surprise you a bit, but Microsoft has a bunch of free learning on many open source JavaScript topics and we’re on a mission to create a lot more with Microsoft Edge coming. Here’s our team’s broader learning series on HTML, CSS, and JS:
This article is part of the web dev tech series from Microsoft. We’re excited to share Microsoft Edge and the new EdgeHTML rendering engine with you. Get free virtual machines or test remotely on your Mac, iOS, Android, or Windows device @ http://dev.modern.ie/
(dpe)

David Voyles

Dave Voyles is a Technical Evangelist for Microsoft. He spends a lot of time writing games, writing about games, and writing about how to write games for the game dev community, Read his blog or follow him on Twitter @davevoyles.

One comment

Leave a Reply

Your email address will not be published. Required fields are marked *