josi-asae / polymer

Polymer Style guide

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

FamilySearch Polymer Style guide

A mostly reasonable approach to Polymer and Web Components

Table of Contents

  1. General
  2. Naming
  3. Attributes
  4. Events
  5. Properties
  6. Methods
  7. Bindings
  8. Documentation
  9. Accessibility
  10. Resources

General

  • Web Component APIs and attribute names should be consistent with native DOM elements.
  • Follow the single-responsibility principle.
  • Extend existing elements (e.g. PolymerElements) instead of reimplementing the functionality.
  • Fail silently. Components should act like native DOM and should not throw JS errors. Fire an event instead.
  • List all dependencies of the web component since the browser will deduplicate multiple requests for the same file.

Naming

  • Component names must include a dash. The text before the dash is effectively a namespace.
<!-- bad -->
<myComponent></myComponent>

<!-- good -->
<my-component><my-component>
  • Component names should be lowercased and dash separated.

Why? Conforms to native DOM element names.

<!-- bad -->
<Awesome-WebComponent></Awesome-WebComponent>

<!-- good -->
<awesome-web-component></awesome-web-component>
  • Avoid prefixes shorter than three characters.
<!-- bad -->
<fs-component></fs-component>

<!-- good -->
<familysearch-component></familysearch-component>

back to top

Attributes

  • Use attributes to pass data into the web component.
<!-- bad -->
<script>
Polymer({
  is: 'familysearch-component',
  ready: function() {
    this.addEventListener('dataChanged', function(e) {
      // do something with the changed data
    });
  }
});
</script>

<!-- good -->
<familysearch-component data="value"></familysearch-component>
  • Attribute names should be lowercased and dash separated.

Why? Conforms to native DOM attribute names. Also produces camelCased property names in the Polymer object.

<!-- bad -->
<familysearch-component myAttribute="value"></familysearch-component>

<!-- good -->
<familysearch-component my-attribute="value"></familysearch-component>
  • Boolean values should be based on the existence of the attribute and not its value.

Why? Conforms to native DOM attribute properties (e.g. hidden, disabled).

<!-- bad -->
<familysearch-component boolean-attr="false"></familysearch-component>

<!-- good -->
<familysearch-component boolean-attr></familysearch-component>
  • Boolean attributes should not prefix the name with words like "is", "show", or "has" as you would in JavaScript.

Why? Conforms to native DOM attribute property names (e.g. hidden, disabled).

<!-- bad -->
<familysearch-component is-active></familysearch-component>

<!-- good -->
<familysearch-component active></familysearch-component>
  • Boolean attributes that remove or disable functionality should be prefixed with the word "no".

Why? This isn't a pattern used in native DOM elements but conforms to a Polymer standard (e.g. paper-button uses noink to disable the ripple effect).

<!-- bad -->
<familysearch-component disable-touch></familysearch-component>

<!-- good -->
<familysearch-component no-touch></familysearch-component>

back to top

Events

  • Fire events to pass data out of the web component.
<!-- bad -->
<familysearch-component>
  <!-- don't use bindings to modify a parent elements data -->
  <other-component data={{data}}></other-component> 
</familysearch-component>

<!-- good -->
<dom-module id="familysearch-component">
  <template>
    <button on-click="handleClick">Click Me</button>
  </template>

  <script>
    Polymer({
      is: 'familysearch-component',
      handleClick: function(e, detail) {
        this.fire('dataChanged', {clicked: true});
      }
    });
  </script>
</dom-module>
  • Event names should have a prefix strongly related to the name of the element. In most cases, the prefix should be the name of the element (e.g. familysearch-component-change).

Why? Not only will they be uniquely namespaced, but if you use any one of these event names, the event will not propagate through the shadow DOM.

// bad
this.fire('error', new Error());

// good
this.fire('familysearch-component-error', new Error());
  • Event names should end in a base form verb or a noun.
// bad 
this.fire('familysearch-component-data-changed', {});

// good
this.fire('familysearch-component-data-change', {});
this.fire('familysearch-component-upload-success', {});
  • Use this.listen() instead of this.addEventListener().

Why? Works cross platform and provides an unlisten() function to unsubscribe from the event.

<!-- bad -->
<dom-module id="familysearch-component">
  <template>
    <button id="myButton">Click Me</button>
  </template>

  <script>
    Polymer({
      is: 'familysearch-component',
      ready: function() {
        this.$.myButton.addEventListener('click', function(e) {
          console.log('tapped');
        });
      }
    });
  </script>
</dom-module>

<!-- good -->
<dom-module id="familysearch-component">
  <template>
    <button id="myButton">Click Me</button>
  </template>

  <script>
    Polymer({
      is: 'familysearch-component',
      ready: function() {
        this.listen(this.$.myButton, 'tap', 'handleTap');
      },
      handleTap: function() {
        console.log('tapped');
      }
    });
  </script>
</dom-module>
  • Favor declarative event handlers over JS event handlers (e.g. use the on-tap attribute instead of using the listeners property or this.listen()).
<!-- bad -->
<dom-module id="familysearch-component">
  <template>
    <button>Click Me</button>
  </template>

  <script>
    Polymer({
      is: 'familysearch-component',
      listeners: {
        tap: 'handleTap'
      },
      created: function() {
        this.listen(this, 'tap', 'handleTap');
      }
      handleTap: function(e, detail) {
        console.log('tapped');
      },
    });
  </script>
</dom-module>

<!-- good -->
<dom-module id="familysearch-component">
  <template>
    <button on-tap="handleTap">Click Me</button>
  </template>

  <script>
    Polymer({
      is: 'familysearch-component',
      handleTap: function(e, detail) {
        console.log('tapped');
      }
    });
  </script>
</dom-module>

back to top

Properties

  • Property names should be camelCased.

Why? Produces lowercased, dashed separated attribute names in the DOM.

<!-- bad -->
<dom-module id="familysearch-component">
  <script>
    Polymer({
      is: 'familysearch-component',
      properties: {
        'my-var': String
      }
    });
  </script>
</dom-module>

<!-- good -->
<dom-module id="familysearch-component">
  <script>
    Polymer({
      is: 'familysearch-component',
      properties: {
        myVar: String
      }
    });
  </script>
</dom-module>
  • Private properties should be prefixed with an underscore.
<!-- bad -->
<dom-module id="familysearch-component">
  <script>
    Polymer({
      is: 'familysearch-component',
      properties: {
        private: String
      }
    });
  </script>
</dom-module>

<!-- good -->
<dom-module id="familysearch-component">
  <script>
    Polymer({
      is: 'familysearch-component',
      properties: {
        _private: String
      }
    });
  </script>
</dom-module>
  • Define constants outside of the Polymer constructor.
<!-- bad -->
<dom-module id="familysearch-component">
  <script>
    Polymer({
      is: 'familysearch-component',
      properties: {
        CONST: {
          type: String,
          value: 'value'
        }
      }
    });
  </script>
</dom-module>

<!-- good -->
<dom-module id="familysearch-component">
  <script>
    var CONST = 'value';
    
    Polymer({
      is: 'familysearch-component',
    });
  </script>
</dom-module>
  • Wrap the contents of the script tag inside an immediately-invoked function expression (IIFE).

Why? To prevent global variables from being created since the script tag is run in the window scope.

<!-- bad -->
<dom-module id="familysearch-component">
  <script>
  var isNowGlobal = true;
  </script>
</dom-module>

<!-- good -->
<dom-module id="familysearch-component">
  <script>
  (function() {
    var isNowGlobal = false;
  })();
  </script>
</dom-module>

back to top

Methods

  • Method names should be camelCased.
<!-- bad -->
<dom-module id="familysearch-component">
 <script>
   Polymer({
     is: 'familysearch-component',
     'my-method': function() {
       // ...
     }
   });
 </script>
</dom-module>

<!-- good -->
<dom-module id="familysearch-component">
 <script>
   Polymer({
     is: 'familysearch-component',
     myMethod: function() {
       // ...
     }
   });
 </script>
</dom-module>
  • Private methods should be prefixed with an underscore.
<!-- bad -->
<dom-module id="familysearch-component">
  <script>
    Polymer({
      is: 'familysearch-component',
      privateMethod: function() {
        // ...
      }
    });
  </script>
</dom-module>

<!-- good -->
<dom-module id="familysearch-component">
  <script>
    Polymer({
      is: 'familysearch-component',
      _privateMethod: function() {
        // ...
      }
    });
  </script>
</dom-module>

back to top

Bindings

  • Use dom-if to conditionally render large portions of the DOM or DOM that will not toggle between hidden/shown states.
  • Use the hidden$= attribute to conditionally render small portions of the DOM or DOM that will toggle between hidden/shown states.
  • Favor single bindings [[ ]] over double bindings {{ }}.

back to top

Documentation

back to top

Accessibility

  • Follow web component accessibility best practices.
  • use ARIA roles when necessary, test for accessibility.

back to top

Resources

back to top

About

Polymer Style guide