Когда мы начинаем писать приложение с графическим интерфейсом пользователя специально с помощью javascript, его очень легко неорганизовывать и тесно связывать с экранными виджетами. И очень сложно понять реальные требования клиентов (бизнес-логику). Но, используя шаблон проектирования Model-View-Presenter, мы можем легко разделить UI (где взаимодействует клиент), логику представления и бизнес-логику. Поскольку название предполагает, что сначала докладчик, поэтому мы начинаем думать с докладчиком, затем мы думаем о взаимодействии событий намерений пользователя с моделью и так далее.

MVP улучшает разделение проблем в логике представления:

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

Таким образом, основная цель MVP - разделение различных аспектов кода. В основном в javascript есть 3 основных таких аспекта:

  1. Манипуляции с DOM
  2. Обработка событий
  3. Связь с сервером (вызовы Ajax и т. Д.)

Для каждой из этих проблем есть соответствующий термин от MVP:

  1. Обработка событий = ведущий
  2. DOM Manipulation = Просмотр
  3. Вызовы AJAX = Модель

Но есть некоторое правило при определении MVP: представление всегда должно вызывать события, которые имеют значение для бизнес-логики или, в частности, событий намерений пользователя. Оно не должно напрямую вызывать ведущего, потому что наша цель - разделить представление и ведущего, верно.

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

Таким образом, только докладчик может напрямую отдавать команду представлению и модели, поскольку он знает намерения пользователя.

Это можно понять по диаграмме, представленной ниже:

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

Давайте рассмотрим простой пример отправки пользовательской формы. А также мы хотим изменить некоторую информацию перед отправкой формы. Допустим, мы хотим запрашивать имя и фамилию пользователя в разных входных данных, но хотим отправить на сервер полное имя.

Итак, здесь пользователь намерен зарегистрироваться.

Мы будем использовать Riotjs, который поможет нам использовать паттерн наблюдателя.

Используя Riotjs, мы можем легко вызвать событие и подписаться на него. С его помощью мы также можем уведомить подписчика.

вот базовый скелетный код:

Базовый HTML:

<form action=”user” method=”post” id=”user-form”>
 <input name=”first_name”/>
 <input name=”last_name”/>
 <button type=”submit”> register </button>
</form>
 
<h3 id=”full-name”></h3>

Машинописный код:

module UserRegistration {
 declare var $: any;
 declare var riot: any;
 
 interface SerializeUserInformation {}
 
 interface RegisterUser {}
 
 export class View implements SerializeUserInformation { }
 
 export class Presenter {
 constructor(view: SerializeUserInformation, model: RegisterUser){}
 }
 
 export class Model implements RegisterUser {}
}

Поэтому мы просто объявляем модуль UserRegistration, который будет содержать нашу триаду (MVP). Также попробуйте дать своим интерфейсам имена в соответствии с намерениями пользователя или реальной бизнес-логикой. Таким образом, имя интерфейса достаточно ясно, чтобы определить функциональность, которую будет выполнять реализующий класс. Теперь давайте сначала отобразим полное имя (это своего рода проверка, то есть мы будем проверять нашу форму только на уровне представления), которое вставил пользователь.

module UserRegistration {
  declare var $: any;
  declare var riot: any;
 
  interface SerializeUserInformation {
     whenUserRegister(callback: () => void): void;
  }
 
  interface RegisterUser {}
 
  export class View implements SerializeUserInformation {
    fullName: string;
    constructor() {
      this.registerWithForm();
    }
    whenUserRegister() {}
    private showFullName() {
      this.fullName = 
          $(“#first-name”).val() + “ “ + $(“#last-name”).val();
      $(“#full-name”).html(this.fullName);
    }
    private registerWithForm() {
      var _this = this;
      $(“#user-form”).submit(function(e) {
        e.preventDefault();
        _this.showFullName();
      });
    }
  }
  export class Presenter {
    constructor(
      view: SerializeUserInformation, model: RegisterUser) {}
  }
 
  export class Model implements RegisterUser {}
}

Теперь сначала мы зарегистрируем нашего докладчика в whenUserRegister и whenUserIsSaved. Поскольку представление и модель будут транслировать события, они не знают, как они обрабатываются, но что бы ни случилось, они не несут ответственности (SRP), поскольку это ответственность ведущего.

module UserRegistration {
  declare var $: any;
  declare var riot: any;
  interface SerializeUserInformation {
    whenUserRegister(callback: (userData: JSON) => void): void;
    showWelcomeMessage();
  }
  interface RegisterUser {
    saveUser(userData: JSON): void;
    whenUserIsSaved(callback: () => void): void;
  }
  export class View implements SerializeUserInformation {
    fullName: string;
    announcer = riot.observable(this);
    
    constructor(){
      this.registerWithForm();
    }
    
    whenUserRegister(callback: (userData: JSON) => void) {
      console.log("user is registered");
      this.announcer.on("whenUserRegister", callback);
    }
    showWelcomeMessage(){
      console.log("welcome user");
    }
    
    private showFullName() {
      this.fullName = $("#first-name").val() + " " + $("#last-name").val();
      $("#full-name").html(this.fullName);
    }
    private registerWithForm(){
      var _this = this;
      $("#user-form").submit(function(e) {
        e.preventDefault();
        _this.showFullName();
        _this.announcer.trigger("whenUserRegister", {fullName:
          _this.fullName});
      });
    }
  }
  export class Presenter {
    
    constructor(
      private view: SerializeUserInformation, 
      private model: RegisterUser) { 
      view.whenUserRegister(this.saveUserCallback());
      model.whenUserIsSaved(this.showWelcomeMessage());
    }
    saveUserCallback() {
      //call the model the same way
      return (userData: JSON) => {
        console.log(this);
        this.model.saveUser(userData);
        console.log("data is transferred");
      }
    }
    showWelcomeMessage(){
      return () => {
        console.log("hey i am called");
        this.view.showWelcomeMessage();
      }
      //call view to render welcome message;
    }
  }
  export class Model implements RegisterUser {
  
    announcer = riot.observable(this);
  
    constructor(){}
    saveUser(userData: JSON){
      var _this = this;
      $.ajax({
        type: 'POST',
        url: $(this).attr("action"),
        data: JSON.stringify(userData),
        success: function(data) {
          _this.announcer.trigger("whenUserIsSaved");
        },
        contentType: "application/json",
        dataType: 'json'
      });
    }
    whenUserIsSaved(callback: () => void){
      this.announcer.on("whenUserIsSaved", callback);
    }
  }
}

Вы можете проверить код в этом.

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