Reference Source

coral-spectrum/coral-component-shell/src/scripts/ShellOrganization.js

/**
 * Copyright 2019 Adobe. All rights reserved.
 * This file is licensed to you 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 REPRESENTATIONS
 * OF ANY KIND, either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */

import {List} from '../../../coral-component-list';
import {SelectableCollection} from '../../../coral-collection';
import {Icon} from '../../../coral-component-icon';
import organization from '../templates/organization';
import {transform, commons} from '../../../coral-utils';

const CLASSNAME = '_coral-Shell-orgSwitcher-item';

/**
 @class Coral.Shell.Organization
 @classdesc A Shell Organization component
 @htmltag coral-shell-organization
 @extends {ListItem}
 */
class ShellOrganization extends List.Item {
  /** @ignore */
  constructor() {
    super();

    // Events
    this._delegateEvents({
      'click': '_onClick',
      'key:enter': '_onClick',
      'key:space': '_onClick',
      // Private
      'coral-shell-suborganization:_selectedchanged': '_onItemSelectedChanged'
    });

    const template = {};
    organization.call(template, {Icon});
    commons.extend(this._elements, template);

    // Used for eventing
    this._oldSelection = null;

    // Item handling
    this.items._startHandlingItems(true);
  }

  /**
   The item collection.

   @type {SelectableCollection}
   @readonly
   */
  get items() {
    // Construct the collection on first request:
    if (!this._items) {
      this._items = new SelectableCollection({
        host: this,
        itemTagName: 'coral-shell-suborganization',
        container: this._elements.items,
        onItemAdded: this._onItemAdded,
        onItemRemoved: this._onItemRemoved
      });
    }

    return this._items;
  }

  /**
   Returns the selected workspace.

   @type {HTMLElement}
   @readonly
   */
  get selectedItem() {
    return this.items._getLastSelected();
  }

  /**
   Whether this organization is selected.

   @type {Boolean}
   @default false
   @htmlattribute selected
   @htmlattributereflected
   */
  get selected() {
    return this._selected || false;
  }

  set selected(value) {
    this._selected = transform.booleanAttr(value);
    this._reflectAttribute('selected', this._selected);

    this.setAttribute('aria-selected', this._selected);
    this.classList.toggle('is-selected', this._selected);
    this._elements.checkmark.hidden = !this._selected;

    if (this.items) {
      const selectedItem = this.selectedItem;

      this.classList.toggle('is-child-selected', selectedItem);

      if (!this._selected && selectedItem) {
        // Always de-select children when de-selected
        selectedItem.removeAttribute('selected');
      }
    }

    this.trigger(`${this.tagName.toLowerCase()}:_selectedchanged`);
  }

  /**
   The name of this organization.

   @type {String}
   @default ""
   @htmlattribute name
   @htmlattributereflected
   */
  get name() {
    return this._name || '';
  }

  set name(value) {
    this._name = transform.string(value);
    this._reflectAttribute('name', this._name);
  }

  /** @private */
  _onItemSelectedChanged(event) {
    // Validate the selection only if we're an organization
    if (this.tagName === 'CORAL-SHELL-ORGANIZATION') {
      // Don't stop propagation here as the orgSwitcher listens to the event too
      const item = event.target;
      this._validateSelection(item);
    }
  }

  /** @private */
  _onItemAdded(item) {
    // Move all items into the right place
    this._moveItems();

    this._validateSelection(item);
  }

  /** @private */
  _onItemRemoved(item) {
    this._setParent();

    this._validateSelection(item);
  }

  /** @private */
  _validateSelection(item) {
    // gets the current selection
    const selection = this.items._getAllSelected();
    const selectionCount = selection.length;

    if (selectionCount > 1) {
      for (let i = 0 ; i < selectionCount ; i++) {
        if (selection[i] !== item) {
          // Don't trigger change events
          this._preventTriggeringEvents = true;
          selection[i].removeAttribute('selected');
        }
      }

      // We can trigger change events again
      this._preventTriggeringEvents = false;
    }

    this._triggerChangeEvent();
  }

  /** @private */
  _triggerChangeEvent() {
    const selectedItem = this.selectedItem;
    const oldSelection = this._oldSelection;

    if (!this._preventTriggeringEvents && selectedItem !== oldSelection) {
      this.trigger(`${this.tagName.toLowerCase()}:change`, {
        oldSelection: oldSelection,
        selection: selectedItem
      });

      this._oldSelection = selectedItem;
    }
  }

  /** @private */
  _moveItems() {
    Array.prototype.forEach.call(this.querySelectorAll('coral-shell-suborganization'), (item) => {
      if (!this._elements.items.contains(item)) {
        this._elements.items.appendChild(item);
      }
    });
  }

  /** @private */
  _setParent() {
    const hasChildren = this.items.length !== 0;

    if (hasChildren) {
      this.removeAttribute('role');
      this.removeAttribute('tabindex');
    } else {
      // Be accessible
      this.setAttribute('role', 'button');
      this.setAttribute('tabindex', 0);
    }

    this.classList.toggle('is-parent', hasChildren);
  }

  /** @private */
  _onClick() {
    if (this.items.length !== 0) {
      // You can't be selected if you have sub-organizations
      return;
    }

    this.selected = true;
  }

  /** @ignore */
  static get observedAttributes() {
    return super.observedAttributes.concat(['name', 'selected']);
  }

  /** @ignore */
  render() {
    super.render();

    this.classList.add(CLASSNAME);

    // Move items into the right place
    this._moveItems();

    this.setAttribute('id', this.id || commons.getUID());

    // Support cloneNode
    const items = this.querySelector(`#${this.id} > ._coral-Shell-orgSwitcher-subitems`);
    if (items) {
      items.remove();
    }
    const checkmark = this.querySelector(`#${this.id} > ._coral-Shell-orgSwitcher-checkmark`);
    if (checkmark) {
      checkmark.remove();
    }

    // Render template
    const frag = document.createDocumentFragment();

    frag.appendChild(this._elements.checkmark);
    frag.appendChild(this._elements.items);
    this.appendChild(frag);

    // Don't trigger events once connected
    this._preventTriggeringEvents = true;
    this._validateSelection();
    this._preventTriggeringEvents = false;

    this._oldSelection = this.selectedItem;

    this._setParent();
  }

  /**
   Triggered when a {@link ShellOrganization} selection changed.

   @typedef {CustomEvent} coral-shell-organization:_selectedchanged

   @private
   */
}

export default ShellOrganization;