Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

GWT-Platform основы работы с презентерами

Anna | 4.06.2014 | нет комментариев
Всем програжителям добродушного времени суток!

Я начинающий Java-программист и так уж получилось, что свою карьеру я начинаю с разработки серьезного приложения на GWT. На прогре достаточно много статей на тему GWT, впрочем отчего-то вовсе нет информации о восхитительном фреймворке GWT-Platform. Детально познакомиться с данным фреймворком дозволено здесь, а я расскажу лаконично об основах работы на примере простого приложения.

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

Если нажать на кнопку в навбаре, то откроется либо левая часть приложения, либо правая с бессмысленным текстом.

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




Выходит, для начала нам необходимо сделать GWT план в IDE. Для работы с GWTP нам понадобится добавить в план библиотеки: guice-2.0.jar, guice-3.0.jar, gwtp-all-1.0.jar, aopalliance.jar, guice-assistedinject-3.0.jar. Также я добавил gwt-bootstrap-2.2.2.0-SNAPSHOT.jar Дабы добавить “красоты” приложению.

Дозволено установить в Eclipse gwt-platform плагин. Он крепко облегчает жизнь. С его поддержкой дозволено создавать как новые планы, так и связки презентер-вью. Качается по этой ссылке: plugin.gwt-platform.googlecode.com/hg/update

Приступим:
Необходимо сделать клиентский модуль и Ginjector. Если приложение создавать с поддержкой плагина, то они будут сделаны механически:
В способе configure () мы будем биндить наши презентеры с интерфейсами и имплементацией их вью.

public class ClientModule extends AbstractPresenterModule {

	@Override
	protected void configure() {
		install(new DefaultModule(ClientPlaceManager.class));

                          bindPresenter(MainPagePresenter.class, MainPagePresenter.MyView.class,
				MainPageView.class, MainPagePresenter.MyProxy.class);

		bindConstant().annotatedWith(DefaultPlace.class).to(NameTokens.main);
	}
}
         @GinModules({ DispatchAsyncModule.class, ClientModule.class })
         public interface ClientGinjector extends Ginjector {

	EventBus getEventBus();

	PlaceManager getPlaceManager();

	Provider<MainPagePresenter> getMainPagePresenter();
}

Дальше наша точка входа: Здесь мы говорим нашему placemanager перейти на нынешнюю страницу (place). То есть если у нас в адресной строке браузера был введен какой-то токен, определяющий нужный place, то мы попадем туда. Безусловно при условии что мы имеем доступ. (За это может отвечать скажем GateKeeper).

public class HabraTest implements EntryPoint {

	private final ClientGinjector ginjector = GWT.create(ClientGinjector.class);

	@Override
	public void onModuleLoad() {
		DelayedBindRegistry.bind(ginjector);	
		ginjector.getPlaceManager().revealCurrentPlace();
	}
}

Не буду останавливаться внимание на работе с place. На прогре теснее было много восхитительных статей по GWT. Скажем эта.

Я же покажу как дозволено создавать небольшие GWT приложения без применения place (вернее с одним place).

Для начала сделаем основной презентер нашего приложения:

public class MainPagePresenter extends
	Presenter<MainPagePresenter.MyView, MainPagePresenter.MyProxy> implements MainPageUiHandlers, FirstPageEvent.Handler{

	public interface MyView extends View, HasUiHandlers<MainPageUiHandlers> {
	}

	// идентификаторы слотов для вставки соответствующего презентера
	public final static Object SLOT_FIRST_PAGE =  new Object();
	public final static Object SLOT_SECOND_PAGE =  new Object();

	//вложенные  презентеры
	private FirstPagePresenter firstPagePresenter;
	private SecondPagePresenter secondPagePresenter;	

	@ProxyStandard
	@NameToken(NameTokens.main)
	public interface MyProxy extends ProxyPlace<MainPagePresenter> {
	}

	private EventBus eventBus;
	private final PlaceManager placeManager;

	// инжектим вложенные презентеры
	@Inject
	public MainPagePresenter(final EventBus eventBus, final MyView view, 
			FirstPagePresenter firstPagePresenter,
			SecondPagePresenter secondPagePresenter,
			final MyProxy proxy, final PlaceManager placeManager) {
		super(eventBus, view, proxy);
		this.placeManager = placeManager;
		this.firstPagePresenter =  firstPagePresenter;
		this.secondPagePresenter =  secondPagePresenter;
		this.eventBus =  eventBus;
		// назначаем себя обработчиком событий  вью
		getView().setUiHandlers(this);
		eventBus.addHandler(FirstPageEvent.getType(), this);
	}

	// внедряем себя в основной презентер приложения
	@Override
	protected void revealInParent() {
		RevealRootContentEvent.fire(this, this);
	}

	@Override
	protected void onBind() {
		super.onBind();
		// по умолчанию  будет загружена первая страница
		getView().setInSlot(SLOT_FIRST_PAGE, firstPagePresenter);	
	}

	// вызывается при нажатии левой кнопки в MainPageView
	@Override
	public void onRightBtnClicked() {
		showRightContent();
		MainPageEvent mainPageEvent =  new MainPageEvent( MainPageEvent.Action.SHOW_LOREM_IPSUM);
		eventBus.fireEvent(mainPageEvent);	
	}
	// подобно при нажатии правой
	@Override
	public void onLeftBtnClicked() {
		showLeftContent();

	}	

	public void showLeftContent() {
		removeFromSlot(SLOT_SECOND_PAGE, secondPagePresenter);
		getView().setInSlot(SLOT_FIRST_PAGE, firstPagePresenter);		
	}

	public void showRightContent() {
		removeFromSlot(SLOT_FIRST_PAGE, firstPagePresenter);
		getView().setInSlot(SLOT_SECOND_PAGE, secondPagePresenter);	
	}

	@Override
	public void onFirstPageEvent(FirstPageEvent event) {
		// закрываем левый контент и открываем правый, в тот, что через эвент передаем имя и фамилию
		showRightContent();
		MainPageEvent mainPageEvent =  new MainPageEvent( MainPageEvent.Action.SHOW_FORM_RESULT, event.getFirstName(),                          event.getLastName());    
		eventBus.fireEvent(mainPageEvent);

	}	
}

Обратите внимание на то что мы заинжектили в конструкторе FirstPagePresenter firstPagePresenter, SecondPagePresenter secondPagePresenter.
Это будут presenter — виджеты представляющие левую и правую часть приложения (то есть в теории отдельные страницы);

В GWTP есть три основных типа презентеров:

  • Перезентеры, которые являются еще и place
  • Перезентеры-виджеты(PresenterWidget)
  • Презентеры-виджеты, представляющие собой Popup окно

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

Для смены «страниц» мы будем применять систему слотов и презентер-виджеты размещенные в слоты.
Презентер-виджет это презентер тот, что не непременно является синглтоном. Он может имет уйма самостоятельных instance.
Вследствие системе слотов мы можем беспредельно вкладывать презентеры внутри других презентеров. Дабы разместить презентер-виджет в иной презентер, нам нужно определить слоты в родительском презентере и переопределить способ setInSlot() во вью родительского презентера.

В классе MainPagePresenter видно что слот это легко Object:

	public final static Object SLOT_FIRST_PAGE =  new Object();
	public final static Object SLOT_SECOND_PAGE =  new Object();

В соответствующем вью определяем правила вставки презентеров в слот:

public class MainPageView extends ViewWithUiHandlers<MainPageUiHandlers> implements MainPagePresenter.MyView {

	// основная панель приложения
	@UiField HTMLPanel main;
	// навигационная панель
	@UiField ResponsiveNavbar navbar;

	// кнопки навигации
	@UiField Button firstPageBtn, secondPageBtn;

	private static MainPageViewUiBinder uiBinder = GWT
			.create(MainPageViewUiBinder.class);

	interface MainPageViewUiBinder extends UiBinder<Widget, MainPageView> {
	}

	// колонки для вставки контента 
	@UiField Column leftColumn, rightColumn;

	@Inject
	public MainPageView() {	
		uiBinder.createAndBindUi(this);
		navbar.setInverse(true);
		//обработчики для кнопок 
		firstPageBtn.addClickHandler(new  ClickHandler() {		
			@Override
			public void onClick(ClickEvent event) {
				getUiHandlers().onLeftBtnClicked();		
			}
		});

		secondPageBtn.addClickHandler(new  ClickHandler() {		
			@Override
			public void onClick(ClickEvent event) {
				getUiHandlers().onRightBtnClicked();		
			}
		});			
	}

	@Override
	public Widget asWidget() {
		return main;
	}

	// переопределяем способ вставки презентеров в слот
	@Override
	public void setInSlot(Object slot, IsWidget content) {
		if(slot == MainPagePresenter.SLOT_FIRST_PAGE ) {
			leftColumn.add(content);
		}
		else if(slot == MainPagePresenter.SLOT_SECOND_PAGE ){
			rightColumn.add(content);
		}
		else {
			super.setInSlot(slot, content);
		}		
	}

	// подобно переопределяем способ удаления из слота
	@Override
	public void removeFromSlot(Object slot, IsWidget content) {
		if(slot == MainPagePresenter.SLOT_FIRST_PAGE ) {
			leftColumn.remove(content);
		}
		else if(slot == MainPagePresenter.SLOT_SECOND_PAGE ){
			rightColumn.remove(content);
		}
		else {
			super.removeFromSlot(slot, content);
		}	
	}
}

Все достаточно легко: setInSlot() принимает в себя презентер и соответствующий ему слот.
Мы легко указываем в какой виджет разместить данный презентер. В данном случае это две бутстраповские колонки leftColumn и rightColumn.
Правда я повторюсь в данном случае уместней было бы помещать все в одну колонку, Дабы имитировать переход по страницам.

Дальше наши презентер-виджеты и их вью:

public class FirstPagePresenter extends
		PresenterWidget<FirstPagePresenter.MyView> implements FirstPageUiHandlers, PopupEvent.Handler{

	public interface MyView extends View, HasUiHandlers<FirstPageUiHandlers> {
	}

	// попап с формой
	FirstPagePopupPresenter firstPagePopupPresenter;
	EventBus eventBus;

	@Inject
	public FirstPagePresenter(final EventBus eventBus, final MyView view,
			FirstPagePopupPresenter firstPagePopupPresenter ) {
		super(eventBus, view);
		this.firstPagePopupPresenter =  firstPagePopupPresenter;
		this.eventBus =  eventBus;
		getView().setUiHandlers(this);
		// назначаем себя хендлером PopupEvent
		eventBus.addHandler(PopupEvent.getType(), this);
	}

	@Override
	public void onShowFormBtnClicked() {
		// показываем всплывающее окно с формой
		showForm(true);		
	}

	private void showForm(boolean show) {
		if(show){
		addToPopupSlot(firstPagePopupPresenter, true);
		firstPagePopupPresenter.getView().show();
		}
		else {
			removeFromPopupSlot(firstPagePopupPresenter);
		}		
	}

	@Override
	public void onPopupEvent(PopupEvent event) {
		showForm(false);
		eventBus.fireEvent(new FirstPageEvent(event.getFirstName(), event.getLastName()));		
	}
}

Обратите внимание что я заинжектил некоторый FirstPagePopupPresenter firstPagePopupPresenter.(код будет ниже). Это наше всплывающее окно с формой. Подобно дозволено инжектить всякие презентер-виджеты в любом числе и с всякий вложенностью. Основное не нарушать иерархию.

public class FirstPageView extends ViewWithUiHandlers<FirstPageUiHandlers> implements
		FirstPagePresenter.MyView {

	private final Widget widget;
	@UiField Button showFormBtn;

	public interface Binder extends UiBinder<Widget, FirstPageView> {
	}

	@Inject
	public FirstPageView(final Binder binder) {
		widget = binder.createAndBindUi(this);

		showFormBtn.addClickHandler(new ClickHandler() {			
			@Override
			public void onClick(ClickEvent event) {
				getUiHandlers().onShowFormBtnClicked();				
			}
		});
	}

	@Override
	public Widget asWidget() {
		return widget;
	}
}

Во вью особенно ничего увлекательного, помимо того, что оно наследует типизированный класс ViewWithUiHandlers.
Так как мы не хотим нарушать тезисов MVP, то и не можем обращаться к презентеру напрямую из вью( напротив можем). Для этого мы используем интерфейсы. О нажатии кнопки мы информируем с поддержкой getUiHandlers().onShowFormBtnClicked();

public interface FirstPageUiHandlers extends UiHandlers{

	void onShowFormBtnClicked();
}

getUiHandlers() возвращает нам интерфейс FirstPageUiHandlers, в котором мы указываем способы, которые обязаны быть реализованы в соответствующем презентере. Безусловно что презентер должен имплементировать данный интерфейс, а вложенный в него интерфейс MyView должен наследовать типизированный интерфейс HasUiHandlers. И основное не позабыть в презентере назначить себя обработчиком для событий вью — getView().setUiHandlers(this);

Дальше презентер и соответствующий вью 2-й страницы:

public class SecondPagePresenter extends
		PresenterWidget<SecondPagePresenter.MyView> implements MainPageEvent.Handler {

	public interface MyView extends View {
		void showLoremIpsum();
		void showFormInfo(String firstName, String lastName);
	}

	EventBus eventBus;

	@Inject
	public SecondPagePresenter(final EventBus eventBus, final MyView view) {
		super(eventBus, view);
		this.eventBus =  eventBus;
		eventBus.addHandler(MainPageEvent.getType(), this);
	}

	@Override
	public void onMainPageEvent(MainPageEvent event) {
		switch(event.getAction()) {
		case SHOW_FORM_RESULT:
			showFormInfoWidget(event.getFirstName(), event.getLastName());
			break;
		case SHOW_LOREM_IPSUM:
			showLoremIpsumWidget();
			break;
		default:
			break;		
		}		
	}

	private void showLoremIpsumWidget() {
		getView().showLoremIpsum();		
	}

	private void showFormInfoWidget(String firstName, String lastName) {
		getView().showFormInfo( firstName, lastName);		
	}
public class SecondPageView extends ViewImpl implements
		SecondPagePresenter.MyView {

	private final Widget widget;

	@UiField FlowPanel contentPanel;

	public interface Binder extends UiBinder<Widget, SecondPageView> {
	}

	@Inject
	public SecondPageView(final Binder binder) {
		widget = binder.createAndBindUi(this);
	}

	@Override
	public Widget asWidget() {
		return widget;
	}

	@Override
	public void showLoremIpsum() {
		contentPanel.clear();
		contentPanel.add(new LoremIpsumWidget());				
	}

	@Override
	public void showFormInfo(String firstName, String lastName) {
		contentPanel.clear();
		contentPanel.add(new FormInfoWidget(firstName, lastName));	
	}
}

Здесь особенно ничего увлекательного и нового для разработчика на GWT. Общение между презентерами происходит посредством стандартных эвентов ( GwtEvent ).

И наконец попап с формой:

public class FirstPagePopupPresenter extends
		PresenterWidget<FirstPagePopupPresenter.MyView> implements PopupUiHandlers {

	public interface MyView extends PopupView , HasUiHandlers<PopupUiHandlers>{
	}

	EventBus eventBus;

	@Inject
	public FirstPagePopupPresenter(final EventBus eventBus, final MyView view) {
		super(eventBus, view);
		this.eventBus =  eventBus;
		getView().setUiHandlers(this);
	}

	@Override
	public void onSubmitBtnClicked(String firstName, String lastName) {
		eventBus.fireEvent(new PopupEvent(firstName, lastName));		
	}
}
public class FirstPagePopupView extends PopupViewWithUiHandlers<PopupUiHandlers> implements
		FirstPagePopupPresenter.MyView {

	@UiField PopupPanel main;
	@UiField Button submitBtn;
	@UiField TextBox firstName, lastName;

	public interface Binder extends UiBinder<Widget, FirstPagePopupView> {
	}

	@Inject
	public FirstPagePopupView(final EventBus eventBus, final Binder binder) {
		super(eventBus);
		binder.createAndBindUi(this);
                           main.setAnimationEnabled(true);
                           main.setModal(true);
                           main.setGlassEnabled(true);
                           submitBtn.addClickHandler(new ClickHandler() {

			@Override
			public void onClick(ClickEvent event) {
				getUiHandlers().onSubmitBtnClicked(firstName.getValue(), lastName.getValue());				
			}
		});
	}

	@Override
	public Widget asWidget() {
		return main;
	}
}

Как видно попап тоже является презентер-виджетом, но интерфейс его вью должен наследовать PopupView. И еще основная панель вью должна быть непременно PopupPanel, ну либо преемником данного класса. Еще одно различие от обыкновенных презентер-виджетов — Дабы показать попап на странице не необходим слот и панель для вставки. Довольно применять способ addToPopupSlot();

Также во всех связках презентер-вью использован uibinder. Соответствующие *ui.xml файлы я не выкладываю. Там в тезисе ничего для GWT-разработчиков увлекательного нет.

Сам план будет доступен некоторое время по этому адресу

И так а теперь пробежимся по плану что бы описать что происходит и как связаны презентеры между собой:

При загрузке MainPagePresenter в переопределенном способе onBind() мы сразу же ставим в слот презентер первой страницы:

	@Override
	protected void onBind() {
		super.onBind();
		getView().setInSlot(SLOT_FIRST_PAGE, firstPagePresenter);	
	}

(Про жизненный цикл презентеров и способы onBind(), onUnbind, onReveal(), onReset(), onHide() я бы хотел рассказать в дальнейшей статье.)

Соответственно в левой части экрана возникает вью FirstPagePresenter’a. При клике на кнопку мы вызываем имплементацию способа onShowFormBtnClicked() в FirstPagePresenter описанного в интерфейсе FirstPageUiHandlers

Вызов:

		showFormBtn.addClickHandler(new ClickHandler() {			
			@Override
			public void onClick(ClickEvent event) {
				getUiHandlers().onShowFormBtnClicked();				
			}
		});

в FirstPagePresenter’ e происходит следующее:

		addToPopupSlot(firstPagePopupPresenter, true);

Мы сетим презентер попапа в слот. Как я теснее упоминал, для попапов слот не необходимо определять. Исключительное условие, что презентер из которого вызывается попап должен сам находится в слоте родителя и так дальше по цепочке. 2-й параметр в способе addToPopupSlot() указывает центровать ли попап в окне приложения(способ имеет несколько перегрузок и данный параметр в всеобщем-то необязателен).

Позже того как попап возникает в окне, мы можем ввести туда какие-то данные и нажать кнопку подтверждения. Дальше по аналогичной схеме вью попапа через getUiHandlers() вызывает обработчик в своем презентере, а тот в свою очередь кидает через EventBus эвент, на тот, что подписан FirstPagePresenter (если кому-то увлекательно, то про эвенты в GWT я бы мог рассказать в дальнейшей заметке):

	@Override
	public void onPopupEvent(PopupEvent event) {
		showForm(false);
		eventBus.fireEvent(new FirstPageEvent(event.getFirstName(), event.getLastName()));		
	}

Вначале в способе showForm() мы удаляем попап из слота:

removeFromPopupSlot(firstPagePopupPresenter);

После этого кидаем новейший эвент ( сейчас это FirstPageEvent) дальше. На него подписан наш MainPagePresenter:

	@Override
	public void onFirstPageEvent(FirstPageEvent event) {
		// закрываем левый контент и открываем правый , в тот, что  через эвент передаем имя и фамилию
		showRightContent();
		MainPageEvent mainPageEvent =  new MainPageEvent( MainPageEvent.Action.SHOW_FORM_RESULT, event.getFirstName(),     event.getLastName());
		eventBus.fireEvent(mainPageEvent);
	}

Получив его MainPagePresenter удаляет из слота первую страницу и вставляет вторую:

	public void showRightContent() {
		removeFromSlot(SLOT_FIRST_PAGE, firstPagePresenter);
		getView().setInSlot(SLOT_SECOND_PAGE, secondPagePresenter);	
	}

Дальше он полылает теснее MainPageEvent дальше. В него сетит не только имя и фамилию, но и Action.

Наш SecondPagePresenter получив эвент в способе onMainPageEvent() решает что показать на странице. В данном случае это обыкновенные виджеты без презентеров.

	@Override
	public void onMainPageEvent(MainPageEvent event) {
		switch(event.getAction()) {
		case SHOW_FORM_RESULT:
			showFormInfoWidget(event.getFirstName(), event.getLastName());
			break;
		case SHOW_LOREM_IPSUM:
			showLoremIpsumWidget();
			break;
		default:
			break;		
		}		
	}

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

  • Мы не нарушаем тезисов MVP — вью ничего не должно знать о своем презентере
  • Поделив приложение на модули код становится reusable и больше эластичным

Также наверно некоторые возмутятся — для чего такие длинные цепочки передачи эвентов? Впрочем дозволено подметить, что получив эвент, презентер, перед отправкой дальнейшего, делает какие-либо операции. Скажем, удаляет непотребные больше презентеры либо обрабатывает как-то полученные данные.

В всеобщем верю данная заметка окажется кому-либо пригодной и он обратит свой взгляд в сторону GWT-Platform.

PS: Умоляю помилования за некую сумбурность повествования и допустимые ошибки. Это мой 1-й пост на IT-тематику. Обоснованная критика и советы дюже приветствуются.

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB