Все шаблоны, которые мы использовали до сих пор для запуска диалогов с Polymer. Победитель: используйте миксины для повторного использования бизнес-логики и экземпляров диалогов.

Паттерн # 1: декларативные диалоги

Многие начальные примеры Polymer объявляют диалоги в шаблоне элемента:

<my-dialog id="dialog"></my-dialog>
<paper-button on-tap="open">Open</paper-button
...
open() {
  this.$.dialog.open();
}

Обратной стороной является то, что шаблон диалогового окна всегда отмечается при запуске, независимо от того, открывает ли пользователь диалоговое окно или нет.

Паттерн # 2: создание динамического диалога

Создание диалога, когда пользователь нажимает кнопку, позволяет избежать этой проблемы:

<paper-button on-tap="open">Open</paper-button
...
open() {
  const dialog = new MyDialog();
  Polymer.dom(document.body).appendChild(dialog);
  // Wait until the dialog is added to the DOM
  setTimeout(() => this.$.dialog.open(), 1);
}

Объяснение: Мы добавляем диалоговое окно в тело документа вместо текущего элемента, чтобы предотвратить проблемы с разбиением на слои (отображение вашего диалогового окна под вашим приложением). Имейте в виду, что запускающий элемент не получит никаких событий, запускаемых диалоговым окном, потому что диалоговое окно больше не является его дочерним элементом.

Основная проблема с этим подходом заключается в том, что теперь мы создаем новый диалог всякий раз, когда пользователь нажимает кнопку.

Паттерн # 3: синглтон, созданный ленивым способом

Мы можем избежать множественных экземпляров, лениво создав синглтон:

<paper-button on-tap="open">Open</paper-button
...
open() {
  this.getDialog().then(dialog => dialog.open());
}
getDialog() {
  if (this.dialog) {
    return Promise.resolve(this.dialog);
  }
  this.dialog = new MyDialog();
  Polymer.dom(document.body).appendChild(this.dialog);
  return new Promise(resolve => {
    setTimeout(() => resolve(this.dialog), 1);
  };
}

Explanation: Мы инкапсулируем отложенное создание в методе asynchronize, чтобы получить простой в использовании API на основе Promise.

Это наш любимый паттерн, если мы хотим запустить диалог ровно из одного элемента. Все становится сложнее, если вам нужно запускать диалог из нескольких элементов, потому что вам придется копировать и вставлять приведенные выше строки снова и снова.

Паттерн №4: Поделиться запускающим кодом через миксины

Миксины - отличный способ поделиться стартовым кодом:

MyDialogMixin = parent => class MyDialogMixin extends parent {
  openMyDialog() {
    this.getMyDialogInstance().then(dialog => dialog.open());
  }
  getMyDialogInstance() {
    if (MyDialogMixin.dialog) {
      return Promise.resolve(MyDialogMixin.dialog);
    }
    MyDialogMixin.dialog = new MyDialog();
    Polymer.dom(document.body).appendChild(MyDialogMixin.dialog);
    return new Promise(resolve => {
      setTimeout(() => resolve(MyDialogMixin.dialog), 1);
    };
  }
}

Каждый элемент теперь может легко запускать диалоги с помощью метода mixin:

<paper-button on-tap="open">Open</paper-button
...
class MyElement extends MyDialogMixin(Polymer.Element) {
  open() {
    this.openMyDialog();
  }
  ...

Важное замечание: Убедитесь, что имя диалогового окна включено в метод openXXX() и getXXXInstance(). Это позволяет избежать конфликтов имен при добавлении нескольких миксинов диалогов к элементу.

Паттерн 5: перенесите бизнес-логику в миксин

Мы нашли миксины идеальным местом для всей бизнес-логики, связанной с диалогом: подготовка данных для диалога, сохранение данных из диалога и т. Д.

MyDialogMixin = parent => class MyDialogMixin extends parent {
  openMyDialog(itemId) {
    this.getMyDialogInstance().then(dialog => {
      // Load data in dialog before opening
      dialog.item = ReduxStore.getState().itemsById[itemId];
      dialog.open();
    });
  }
  getMyDialogInstance() {
    if (MyDialogMixin.dialog) {
      return Promise.resolve(MyDialogMixin.dialog);
    }
    MyDialogMixin.dialog = new MyDialog();
    // Update state if the user clicks [save] button in dialog
    MyDialogMixin.dialog.addEventListener('save', e => {
      const {update} = e.detail;
      const action = {
        type: 'item/updated',
        update,
      };
      ReduxStore.dispatch(action);
    });
  
    Polymer.dom(document.body).appendChild(MyDialogMixin.dialog);
    return new Promise(resolve => {
      setTimeout(() => resolve(MyDialogMixin.dialog), 1);
    };
  }
}

Паттерн # 6: потребление событий через миксины

Как упоминалось выше, добавление диалогового окна в document.body предотвращает получение запускающим элементом событий, запускаемых диалоговым окном. Миксины также позволяют нам это вернуть:

MyDialogMixin = parent => class MyDialogMixin extends parent {
  ...
  getMyDialogInstance() {
    ...
    MyDialogMixin.dialog.addEventListener('save', e => {
      const {update} = e.detail;
      this.dispatchEvent(new CustomEvent('item-saved', {
        details: {update},
      });
    });
    ...
  }
}

Теперь вы можете использовать эти события из запускающего элемента, например:

ready() {
  this.addEventListener('item-save', e => console.log('Saved!'));
}

Прежде чем использовать это, вам нужно тщательно продумать, что произойдет, когда событие будет запущено: наш диалог - синглтон. Это означает, что прослушиватель событий будет вызываться независимо от того, какой элемент запустил диалог. Иногда это то, что вам нужно. Часто это не так.

Паттерн # 7: обратные вызовы для конкретных событий запуска

Чтобы этого избежать, просто передайте обратный вызов методу open в миксине:

MyDialogMixin = parent => class MyDialogMixin extends parent {
  openMyDialog(callback) {
    this.getMyDialogInstance().then(dialog => {
      MyDialogMixin.callback = callback;
      dialog.open();
    });
  }
  getMyDialogInstance() {
    if (MyDialogMixin.dialog) {
      return Promise.resolve(MyDialogMixin.dialog);
    }
MyDialogMixin.dialog = new MyDialog();
    MyDialogMixin.dialog.addEventListener('save', e => {
      const {update} = e.detail;
      MyDialogMixin.callback(update);
    });
  
    Polymer.dom(document.body).appendChild(MyDialogMixin.dialog);
    return new Promise(resolve => {
      setTimeout(() => resolve(MyDialogMixin.dialog), 1);
    };
  }
}

Объяснение: мы используем тот факт, что диалог может быть только один раз на экране (в противном случае мы не могли бы использовать синглтон для самого элемента диалога).

Теперь запускающий элемент может предоставить обратный вызов, который вызывается только тогда, когда диалог был открыт самим элементом:

class MyElement extends MyDialogMixin(Polymer.Element) {
  open() {
    this.openMyDialog(e => console.log('Saved'));
  }
...

Удачного кодирования!

Фото: Марку Иоахим