iron-doc-viewer.html 9.91 KB
<!--
@license
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->

<link rel="import" href="../polymer/polymer.html">
<link rel="import" href="../marked-element/marked-element.html">
<link rel="import" href="../paper-button/paper-button.html">
<link rel="import" href="../paper-styles/color.html">
<link rel="import" href="../paper-styles/shadow.html">
<link rel="import" href="../paper-styles/typography.html">
<link rel="import" href="../prism-element/prism-highlighter.html">
<link rel="import" href="iron-doc-property.html">
<link rel="import" href="iron-doc-viewer-styles.html">

<!--
Renders documentation describing an element's API.

`iron-doc-viewer` renders element and behavior descriptions as extracted by
[Hydrolysis](https://github.com/PolymerLabs/hydrolysis). You can provide them
either via binding...

    <iron-doc-viewer descriptor="{{elementDescriptor}}"></iron-doc-viewer>

...or by placing the element descriptor in JSON as the text content of an
`iron-doc-viewer`:

    <iron-doc-viewer>
      {
        "is": "awesome-sauce",
        "properties": [
          {"name": "isAwesome", "type": "boolean", "desc": "Is it awesome?"},
        ]
      }
    </iron-doc-viewer>

However, be aware that due to current limitations in Polymer 0.8, _changes_ to
the text content will not be respected, only the initial value will be loaded.
If you wish to update the documented element, please set it via the `descriptor`
property.

@demo demo/index.html Basic Demo
-->

<dom-module id="iron-doc-viewer">
  <template>
    <style include="iron-doc-viewer-styles"></style>

    <prism-highlighter></prism-highlighter>

    <section id="summary" class="card" hidden$="[[!descriptor.desc]]">
      <marked-element markdown="{{descriptor.desc}}">
        <div class="markdown-html"></div>
      </marked-element>
    </section>

    <nav id="api">
      <header>API Reference</header>
      <paper-button id="togglePrivate"
        on-tap="_togglePrivate">{{_privateToggleLabel}}</paper-button>
    </nav>

    <section id="[[_formatAnchor(prefix,'properties')]]" class="card" hidden$="{{_noneToShow(_showPrivate,_properties)}}">
      <header><a href="#[[_formatAnchor(prefix,'properties')]]" class="deeplink">Properties</a></header>
      <template is="dom-repeat" items="{{_properties}}" hidden$="{{!_properties.length}}">
        <iron-doc-property anchor-id="[[_formatAnchor(prefix,'property',item.name)]]" descriptor="{{item}}"></iron-doc-property>
      </template>
    </section>

    <section id="[[_formatAnchor(prefix,'methods')]]" class="card"  hidden$="{{_noneToShow(_showPrivate,_methods)}}">
      <header><a href="#[[_formatAnchor(prefix,'methods')]]" class="deeplink">Methods</a></header>
      <template is="dom-repeat" items="{{_methods}}">
        <iron-doc-property anchor-id="[[_formatAnchor(prefix,'method',item.name)]]" descriptor="{{item}}"></iron-doc-property>
      </template>
    </section>

    <section id="[[_formatAnchor(prefix,'events')]]" class="card" hidden$="{{_noneToShow(_showPrivate,_events)}}">
      <header><a href="#[[_formatAnchor(prefix,'events')]" class="deeplink">Events</a></header>
      <template is="dom-repeat" items="{{_events}}">
        <iron-doc-property anchor-id="[[_formatAnchor(prefix,'event',item.name)]]" descriptor="{{item}}"></iron-doc-property>
      </template>
    </section>

    <section id="[[_formatAnchor(prefix,'behaviors')]]" class="card" hidden$="{{_hideBehaviors(_behaviors)}}">
      <header><a href="#[[_formatAnchor(prefix,'behaviors')]]" class="deeplink">Behaviors</a></header>
      <template is="dom-repeat" items="{{_behaviors}}">
        <p on-click="_broadcastBehavior">{{item}}</p>
      </template>
    </section>
  </template>

  <script>
    (function() {
      Polymer({
        is: 'iron-doc-viewer',

        properties: {
          /**
           * The [Hydrolysis](https://github.com/PolymerLabs/hydrolysis)-generated
           * element descriptor to display details for.
           *
           * Alternatively, the element descriptor can be provided as JSON via the text content
           * of this element.
           *
           * @type {hydrolysis.ElementDescriptor}
           */
          descriptor: {
            type: Object,
            observer: '_descriptorChanged',
          },

          /**
           * Prefix for fragment identifiers used in anchors.
           * For static routing `iron-component-page` can
           * set this to a string identifying the current component.
           */
          prefix: {
            type: String,
            value: ''
          },

          /** Whether private properties should be hidden or shown. */
          _showPrivate: {
            type:     Boolean,
            value:    false,
            observer: '_showPrivateChanged',
          },

          /** The label to show for the Private API toggle. */
          _privateToggleLabel: String,

          /**
           * Broadcast when another component is clicked on
           * @param {String} detail name of the component
           * iron-doc-viewer container should load component if possible
           * @event iron-doc-viewer-component-selected
           */
        },

        ready: function() {
          var jsonDescriptor = this._loadJson();
          // Note that this is only an error during element creation. You are free
          // to stomp over the descriptor after it is ready.
          if (jsonDescriptor && this.descriptor) {
            console.error(
                this,
                'received both a bound descriptor:', this.descriptor,
                'and JSON descriptor:', this._jsonDescriptor,
                'Please provide only one');
            throw new Error(
                '<iron-doc-viewer> accepts either a bound or JSON descriptor; not both');
          }

          if (jsonDescriptor) {
            this.descriptor = jsonDescriptor;
          }
        },

        /**
         * Loads a hydrolysis element descriptor (as JSON) from the text content of
         * this element, if present.
         *
         * @return {hydrolysis.ElementDescriptor} The parsed descriptor, or `null`.
         */
        _loadJson: function() {
          var textContent = '';
          Array.prototype.forEach.call(Polymer.dom(this).childNodes, function(node) {
            textContent = textContent + node.textContent;
          });
          textContent = textContent.trim();
          if (textContent === '') return null;

          try {
            return JSON.parse(textContent);
          } catch(error) {
            console.error('Failure when parsing JSON:', textContent, error);
            throw error;
          }
        },

        /** Converts `descriptor` into our template-friendly `_model`. */
        _descriptorChanged: function() {
          if (!this.descriptor) return;

          // Split the documented properties between functions and other types.
          var properties = [];
          var methods    = [];

          for (var i = 0, property; property = this.descriptor.properties[i]; i++) {
            (property.type === 'Function' ? methods : properties).push(property);
          }
          this._properties = properties;
          this._methods    = methods;
          this._events     = this.descriptor.events || [];
          this._behaviors  = this.descriptor.behaviors || [];

          this.toggleAttribute('abstract', this.descriptor.abstract);
        },

        /**
         * Scrolls to the currently selected anchor, as identified
         * by the URL hash. Whichever element or script is in charge
         * of routing should call this method on initial page load and
         * on hashchange events.
         */
        scrollToAnchor: function(hash) {
          // ToDo: handle linking to private members
          if (hash && hash.length > 1) {
            // ensure all dom-repeats have rendered.
            Polymer.dom.flush();
            var anchorId = window.location.hash.slice(1);
            var elementToFocus = this.$$('[anchor-id="' + anchorId + '"]');
            if (elementToFocus) {
              elementToFocus.scrollIntoView();
            }
          }
        },

        _collapsedChanged: function() {
          this._collapseToggleLabel = this._collapsed ? 'expand' : 'collapse';

          // Bound values aren't exposed to dom-repeat's scope.
          var properties = this.querySelectorAll('iron-doc-property');
          for (var i = 0, property; property = properties[i]; i++) {
            property.collapsed = this._collapsed;
          }
        },

        _toggleCollapsed: function() {
          this._collapsed = !this._collapsed;
        },

        _showPrivateChanged: function() {
          this._privateToggleLabel = (this._showPrivate ? 'hide' : 'show') + ' private API';
          this.toggleClass('show-private', this._showPrivate);
        },

        _togglePrivate: function() {
          this._showPrivate = !this._showPrivate;
        },

        _noneToShow: function(showPrivate, items) {
          for (var i = 0; i < items.length; i++) {
            if (showPrivate || !items[i].private) return false;
          }
          return true;
        },

        _formatAnchor: function(prefix, type, membername) {
          var suffix = membername ? '-' + membername : '';
          return prefix + type + suffix;
        },

        _hideBehaviors: function(behaviors) {
          return behaviors === null || behaviors.length === 0;
        },

        _broadcastBehavior: function(ev) {
          this.fire('iron-doc-viewer-component-selected', ev.target._templateInstance.item);
        }
      });
    })();
  </script>
</dom-module>