Javascript  |  303行  |  9.51 KB

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

cr.define('cr.ui.dialogs', function() {

  function BaseDialog(parentNode) {
    this.parentNode_ = parentNode;
    this.document_ = parentNode.ownerDocument;

    // The DOM element from the dialog which should receive focus when the
    // dialog is first displayed.
    this.initialFocusElement_ = null;

    // The DOM element from the parent which had focus before we were displayed,
    // so we can restore it when we're hidden.
    this.previousActiveElement_ = null;

    this.initDom_();
  }

  /**
   * Default text for Ok and Cancel buttons.
   *
   * Clients should override these with localized labels.
   */
  BaseDialog.OK_LABEL = '[LOCALIZE ME] Ok';
  BaseDialog.CANCEL_LABEL = '[LOCALIZE ME] Cancel';

  /**
   * Number of miliseconds animation is expected to take, plus some margin for
   * error.
   */
  BaseDialog.ANIMATE_STABLE_DURATION = 500;

  BaseDialog.prototype.initDom_ = function() {
    var doc = this.document_;
    this.container_ = doc.createElement('div');
    this.container_.className = 'cr-dialog-container';
    this.container_.addEventListener('keydown',
                                     this.onContainerKeyDown_.bind(this));
    this.shield_ = doc.createElement('div');
    this.shield_.className = 'cr-dialog-shield';
    this.container_.appendChild(this.shield_);
    this.container_.addEventListener('mousedown',
                                     this.onContainerMouseDown_.bind(this));

    this.frame_ = doc.createElement('div');
    this.frame_.className = 'cr-dialog-frame';
    this.frame_.tabIndex = 0;
    this.container_.appendChild(this.frame_);

    this.title_ = doc.createElement('div');
    this.title_.className = 'cr-dialog-title';
    this.frame_.appendChild(this.title_);

    this.closeButton_ = doc.createElement('div');
    this.closeButton_.className = 'cr-dialog-close';
    this.closeButton_.addEventListener('click',
                                        this.onCancelClick_.bind(this));
    this.frame_.appendChild(this.closeButton_);

    this.text_ = doc.createElement('div');
    this.text_.className = 'cr-dialog-text';
    this.frame_.appendChild(this.text_);

    var buttons = doc.createElement('div');
    buttons.className = 'cr-dialog-buttons';
    this.frame_.appendChild(buttons);

    this.okButton_ = doc.createElement('button');
    this.okButton_.className = 'cr-dialog-ok';
    this.okButton_.textContent = BaseDialog.OK_LABEL;
    this.okButton_.addEventListener('click', this.onOkClick_.bind(this));
    buttons.appendChild(this.okButton_);

    this.cancelButton_ = doc.createElement('button');
    this.cancelButton_.className = 'cr-dialog-cancel';
    this.cancelButton_.textContent = BaseDialog.CANCEL_LABEL;
    this.cancelButton_.addEventListener('click',
                                        this.onCancelClick_.bind(this));
    buttons.appendChild(this.cancelButton_);

    this.initialFocusElement_ = this.okButton_;
  };

  BaseDialog.prototype.onOk_ = null;
  BaseDialog.prototype.onCancel_ = null;

  BaseDialog.prototype.onContainerKeyDown_ = function(event) {
    // Handle Escape.
    if (event.keyCode == 27 && !this.cancelButton_.disabled) {
      this.onCancelClick_(event);
      event.preventDefault();
    }
  };

  BaseDialog.prototype.onContainerMouseDown_ = function(event) {
    if (event.target == this.container_) {
      var classList = this.frame_.classList;
      // Start 'pulse' animation.
      classList.remove('pulse');
      setTimeout(classList.add.bind(classList, 'pulse'), 0);
      event.preventDefault();
    }
  };

  BaseDialog.prototype.onOkClick_ = function(event) {
    this.hide();
    if (this.onOk_)
      this.onOk_();
  };

  BaseDialog.prototype.onCancelClick_ = function(event) {
    this.hide();
    if (this.onCancel_)
      this.onCancel_();
  };

  BaseDialog.prototype.setOkLabel = function(label) {
    this.okButton_.textContent = label;
  };

  BaseDialog.prototype.setCancelLabel = function(label) {
    this.cancelButton_.textContent = label;
  };

  BaseDialog.prototype.setInitialFocusOnCancel = function() {
    this.initialFocusElement_ = this.cancelButton_;
  };

  BaseDialog.prototype.show = function(message, onOk, onCancel, onShow) {
    this.showWithTitle(null, message, onOk, onCancel, onShow);
  };

  BaseDialog.prototype.showHtml = function(title, message,
      onOk, onCancel, onShow) {
    this.text_.innerHTML = message;
    this.show_(title, onOk, onCancel, onShow);
  };

  BaseDialog.prototype.findFocusableElements_ = function(doc) {
    var elements = Array.prototype.filter.call(
        doc.querySelectorAll('*'),
        function(n) { return n.tabIndex >= 0; });

    var iframes = doc.querySelectorAll('iframe');
    for (var i = 0; i < iframes.length; i++) {
      // Some iframes have an undefined contentDocument for security reasons,
      // such as chrome://terms (which is used in the chromeos OOBE screens).
      var contentDoc = iframes[i].contentDocument;
      if (contentDoc)
        elements = elements.concat(this.findFocusableElements_(contentDoc));
    }
    return elements;
  };

  BaseDialog.prototype.showWithTitle = function(title, message,
      onOk, onCancel, onShow) {
    this.text_.textContent = message;
    this.show_(title, onOk, onCancel, onShow);
  };

  BaseDialog.prototype.show_ = function(title, onOk, onCancel, onShow) {
    // Make all outside nodes unfocusable while the dialog is active.
    this.deactivatedNodes_ = this.findFocusableElements_(this.document_);
    this.tabIndexes_ = this.deactivatedNodes_.map(
        function(n) { return n.getAttribute('tabindex'); });
    this.deactivatedNodes_.forEach(
        function(n) { n.tabIndex = -1; });

    this.previousActiveElement_ = this.document_.activeElement;
    this.parentNode_.appendChild(this.container_);

    this.onOk_ = onOk;
    this.onCancel_ = onCancel;

    if (title) {
      this.title_.textContent = title;
      this.title_.hidden = false;
    } else {
      this.title_.textContent = '';
      this.title_.hidden = true;
    }

    var self = this;
    setTimeout(function() {
      // Note that we control the opacity of the *container*, but the top/left
      // of the *frame*.
      self.container_.classList.add('shown');
      self.initialFocusElement_.focus();
      setTimeout(function() {
        if (onShow)
          onShow();
      }, BaseDialog.ANIMATE_STABLE_DURATION);
    }, 0);
  };

  BaseDialog.prototype.hide = function(onHide) {
    // Restore focusability.
    for (var i = 0; i < this.deactivatedNodes_.length; i++) {
      var node = this.deactivatedNodes_[i];
      if (this.tabIndexes_[i] === null)
        node.removeAttribute('tabidex');
      else
        node.setAttribute('tabindex', this.tabIndexes_[i]);
    }
    this.deactivatedNodes_ = null;
    this.tabIndexes_ = null;

    // Note that we control the opacity of the *container*, but the top/left
    // of the *frame*.
    this.container_.classList.remove('shown');

    if (this.previousActiveElement_) {
      this.previousActiveElement_.focus();
    } else {
      this.document_.body.focus();
    }
    this.frame_.classList.remove('pulse');

    var self = this;
    setTimeout(function() {
      // Wait until the transition is done before removing the dialog.
      self.parentNode_.removeChild(self.container_);
      if (onHide)
        onHide();
    }, BaseDialog.ANIMATE_STABLE_DURATION);
  };

  /**
   * AlertDialog contains just a message and an ok button.
   */
  function AlertDialog(parentNode) {
    BaseDialog.apply(this, [parentNode]);
    this.cancelButton_.style.display = 'none';
  }

  AlertDialog.prototype = {__proto__: BaseDialog.prototype};

  AlertDialog.prototype.show = function(message, onOk, onShow) {
    return BaseDialog.prototype.show.apply(this, [message, onOk, onOk, onShow]);
  };

  /**
   * ConfirmDialog contains a message, an ok button, and a cancel button.
   */
  function ConfirmDialog(parentNode) {
    BaseDialog.apply(this, [parentNode]);
  }

  ConfirmDialog.prototype = {__proto__: BaseDialog.prototype};

  /**
   * PromptDialog contains a message, a text input, an ok button, and a
   * cancel button.
   */
  function PromptDialog(parentNode) {
    BaseDialog.apply(this, [parentNode]);
    this.input_ = this.document_.createElement('input');
    this.input_.setAttribute('type', 'text');
    this.input_.addEventListener('focus', this.onInputFocus.bind(this));
    this.input_.addEventListener('keydown', this.onKeyDown_.bind(this));
    this.initialFocusElement_ = this.input_;
    this.frame_.insertBefore(this.input_, this.text_.nextSibling);
  }

  PromptDialog.prototype = {__proto__: BaseDialog.prototype};

  PromptDialog.prototype.onInputFocus = function(event) {
    this.input_.select();
  };

  PromptDialog.prototype.onKeyDown_ = function(event) {
    if (event.keyCode == 13)  // Enter
      this.onOkClick_(event);
  };

  PromptDialog.prototype.show = function(message, defaultValue, onOk, onCancel,
                                        onShow) {
    this.input_.value = defaultValue || '';
    return BaseDialog.prototype.show.apply(this, [message, onOk, onCancel,
                                                  onShow]);
  };

  PromptDialog.prototype.getValue = function() {
    return this.input_.value;
  };

  PromptDialog.prototype.onOkClick_ = function(event) {
    this.hide();
    if (this.onOk_)
      this.onOk_(this.getValue());
  };

  return {
    BaseDialog: BaseDialog,
    AlertDialog: AlertDialog,
    ConfirmDialog: ConfirmDialog,
    PromptDialog: PromptDialog
  };
});