text: /* Copyright 2012 Mozilla Foundation
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  /* globals removeNullCharacters */
  
  'use strict';
  
  /**
   * @typedef {Object} PDFOutlineViewOptions
   * @property {HTMLDivElement} container - The viewer element.
   * @property {Array} outline - An array of outline objects.
   * @property {IPDFLinkService} linkService - The navigation/linking service.
   */
  
  /**
   * @class
   */
  const PDFOutlineView = (function PDFOutlineViewClosure() {
    /**
     * @constructs PDFOutlineView
     * @param {PDFOutlineViewOptions} options
     */
    function PDFOutlineView(options) {
      this.container = options.container;
      this.outline = options.outline;
      this.linkService = options.linkService;
      this.lastToggleIsShow = true;
    }
  
    PDFOutlineView.prototype = {
      reset: function PDFOutlineView_reset() {
        const container = this.container;
        while (container.firstChild) {
          container.removeChild(container.firstChild);
        }
        this.lastToggleIsShow = true;
      },
  
      /**
       * @private
       */
      _dispatchEvent: function PDFOutlineView_dispatchEvent(outlineCount) {
        const event = document.createEvent('CustomEvent');
        event.initCustomEvent('outlineloaded', true, true, {
          outlineCount,
        });
        this.container.dispatchEvent(event);
      },
  
      /**
       * @private
       */
      _bindLink: function PDFOutlineView_bindLink(element, item) {
        const linkService = this.linkService;
        element.href = linkService.getDestinationHash(item.dest);
        element.onclick = function goToDestination(e) {
          linkService.navigateTo(item.dest);
          return false;
        };
      },
  
      /**
       * Prepend a button before an outline item which allows the user to toggle
       * the visibility of all outline items at that level.
       *
       * @private
       */
      _addToggleButton: function PDFOutlineView_addToggleButton(div) {
        const toggler = document.createElement('div');
        toggler.className = 'outlineItemToggler';
        toggler.onclick = function (event) {
          event.stopPropagation();
          toggler.classList.toggle('outlineItemsHidden');
  
          if (event.shiftKey) {
            const shouldShowAll = !toggler.classList.contains('outlineItemsHidden');
            this._toggleOutlineItem(div, shouldShowAll);
          }
        }.bind(this);
        div.insertBefore(toggler, div.firstChild);
      },
  
      /**
       * Toggle the visibility of the subtree of an outline item.
       *
       * @param {Element} root - the root of the outline (sub)tree.
       * @param {boolean} state - whether to show the outline (sub)tree. If false,
       *   the outline subtree rooted at |root| will be collapsed.
       *
       * @private
       */
      _toggleOutlineItem: function PDFOutlineView_toggleOutlineItem(root, show) {
        this.lastToggleIsShow = show;
        const togglers = root.querySelectorAll('.outlineItemToggler');
        for (let i = 0, ii = togglers.length; i < ii; ++i) {
          togglers[i].classList[show ? 'remove' : 'add']('outlineItemsHidden');
        }
      },
  
      /**
       * Collapse or expand all subtrees of the outline.
       */
      toggleOutlineTree: function PDFOutlineView_toggleOutlineTree() {
        this._toggleOutlineItem(this.container, !this.lastToggleIsShow);
      },
  
      render: function PDFOutlineView_render() {
        const outline = this.outline;
        let outlineCount = 0;
  
        this.reset();
  
        if (!outline) {
          this._dispatchEvent(outlineCount);
          return;
        }
  
        const fragment = document.createDocumentFragment();
        const queue = [{parent: fragment, items: this.outline}];
        let hasAnyNesting = false;
        while (queue.length > 0) {
          const levelData = queue.shift();
          for (let i = 0, len = levelData.items.length; i < len; i++) {
            const item = levelData.items[i];
            const div = document.createElement('div');
            div.className = 'outlineItem';
            const element = document.createElement('a');
            this._bindLink(element, item);
            element.textContent = removeNullCharacters(item.title);
            div.appendChild(element);
  
            if (item.items.length > 0) {
              hasAnyNesting = true;
              this._addToggleButton(div);
  
              const itemsDiv = document.createElement('div');
              itemsDiv.className = 'outlineItems';
              div.appendChild(itemsDiv);
              queue.push({parent: itemsDiv, items: item.items});
            }
  
            levelData.parent.appendChild(div);
            outlineCount++;
          }
        }
        if (hasAnyNesting) {
          this.container.classList.add('outlineWithDeepNesting');
        }
  
        this.container.appendChild(fragment);
  
        this._dispatchEvent(outlineCount);
      },
    };
  
    return PDFOutlineView;
  })();
  