• Модальное окно на React JS

    bycycle

    При создании любого приложения, рано или поздно возникает необходимость запросить подтверждение от пользователя (особенно полезно, если вы пишете веб-интерфейс управления ядерным реактором).

    И хотя в арсенале любого фронтенд-разработчика есть простая как топор функция confirm(), будем честны: она страшна как смертный грех, по-разному выглядит в разных браузерах и запросто может разрушить весь эффект от вашего крутого дизайна. Выход один — использовать собственное модальное окно. Всё крайне просто, если вы используете какой-нибудь jQuery и императивно командуете DOM-элементом, в котором оно описано, тем более что и готовых решений существует множество. А вот если вы используете React JS с его парадигмой реактивного программирования, решение может быть достаточно хитрым из-за особенностей связи между родительскими и дочерними компонентами.

    Для начала создадим компонент React для модального окна:

    # app/ui-components/modal.jsx
    var React = require('react');
    var $ = require('jquery');
    
    var Modal = React.createClass({
      getInitialState: function () {
        return {
          visible: false,
          cancel_title: this.props.cancel_title ? this.props.cancel_title : 'Отмена',
          action_title: this.props.action_title ? this.props.action_title : 'ОК',
          title: '',
          text: ''
        };
      },
      // Обработчик закрытия модального окна, вызовет обработчик отказа
      close: function () {
        this.setState({
          visible: false
        }, function () {
          return this.promise.reject();
        });
      },
      // Обработчик действия модального окна, вызовет обработчик действия
      action: function () {
        this.setState({
          visible: false
        }, function () {
          return this.promise.resolve();
        });
      },
      // Обработчик открытия модального окна. Возвращает promise
      // ( при желании, можно передавать также названия кнопок ) 
      open: function (text, title = '') {
        this.setState({
          visible: true,
          title: title,
          text: text
        });
    
        // promise необходимо обновлять при каждом новом запуске окна
        this.promise = new $.Deferred();
        return this.promise;
      },
      render: function () {
    
        var modalClass = this.state.visible ? "modal fade in" : "modal fade";
        var modalStyles = this.state.visible ? {display: "block"} : {};
        var backdrop = this.state.visible ? (
          <div className="modal-backdrop fade in" onClick={this.close} />
        ) : null;
    
        var title = this.state.title ? (
          <div className="modal-header">
            <h4 className="modal-title">{this.state.title}</h4>
          </div>
        ) : null;
    
        return (
          <div className={modalClass} style={modalStyles}>
            {backdrop}
            <div className="modal-dialog">
              <div className="modal-content">
                {title}
                <div className="modal-body">
                  <p>{this.state.text}</p>
                </div>
                <div className="modal-footer">
                  <button 
                    type="button" 
                    className="btn btn-default" 
                    onClick={this.close}
                  >
                    {this.state.cancel_title}
                  </button>
                  <button 
                    type="button" 
                    className="btn btn-primary" 
                    onClick={this.action}
                  >
                    {this.state.action_title}
                  </button>
                </div>
              </div>
            </div>
          </div>
        );
      }
    });
    
    module.exports = Modal;

    Встраиваем наш новый компонент на родительский:

    # app/screens/sample.js
    var React = require('react');
    var Layout = require('ui-components/layouts/layout');
    var Modal = require('ui-components/modal');
    
    var SampleScreen = React.createClass({
      modal: function () {
        return this.refs.modal;
      },
      popup: function () {
        this.refs.modal().open("Вы уверены?")
          .then(function() {
            // Действие
          })
          .fail(function() {
            // Отмена
          });
      },
      render: function() {
        return (
          <Layout>
            <div className="row">
              <a onClick={this.popup} className="btn btn-primary">Popup</a>
            </div>
            <Modal ref="modal"/>
          </Layout>
        );
      }
    });
    
    module.exports = SampleScreen;

    В принципе, можно использовать вместо refs передачу метода SampleScreen в Modal посредством props, но отладчик React выдает на такие действия warning (хотя всё работает).