Хелпикс

Главная

Контакты

Случайная статья





TradingActivityDemo. (NetBeans)



TradingActivityDemo

(NetBeans)

Этап 1

 

1. Устанавливаем и настраиваем необходимое ПО (некоторые детали – в файле TradingActivityDemo_Deployment. doc, этап 1).

2. Создаем БД MySql с именем «trading_activity» из файла сценария “trading_activity_1. sql”, добавляем в таблицы тестовые данные;

3. Создаем первый набор проектов для формирования основного серверного приложения (создается как один проект типа Enterprise application без сборщика проектов Maven – вместо него по умолчанию будет использоваться Ant). EJB-составляющая этого приложения впоследствии будет работать с БД, предоставлять к ней доступ веб- и настольным клиентским приложениям. WEB-составляющая будет выводить публичную часть информации на веб-страницах, которые можно открыть в браузере с любого компьютера, подключенного к локальной сети предприятия и/или к Интернету;

4. В ejb-проекте генерируем классы сущностей на основе БД и фасады к ним (пример есть в каталоге articles, файл «Начало работы с приложениями Java EE. htm», раздел «Создание класса сущностей и фасада сеанса», а также здесь: http: //onedeveloper. ru/article? id=6). Соединение с БД, созданное в среде разработки, служит только для предоставления доступа фреймворку EclipseLink, который используя JPA, автоматически создаст сущности. Для работы самого приложения с БД нужно создать на сервере приложений пул соединений и привязать его к jndi-имени (пункты 8 и 9 из файла TradingActivityDemo_Deployment. doc). Перед началом генерирования сущностей EclipseLink создаст файл персистентности, где будет указано некоторое jndi-имя. В дальнейшем его нужно будет вручную заменить тем именем, которое мы создали на сервере приложений. Инсталлировать, запускать и останавливать сервер приложений, а также вызывать его консоль можно из среды разработки (на панели главного меню: сервис – серверы или в колонке с вкладками «проекты» и «файлы» вкладка «службы» - серверы);

5. Следующий проект будет предназначен для создания библиотеки интерфейса, связывающего основное серверное приложение со специальным настольным приложением-клиентом. Для его создания выполняем в среде разработки: создать проект – Java – Библиотека классов.

6. В ejb-модуль добавляем Session Bean, при создании в мастере выбираем удаленный интерфейс по имени проекта библиотеки классов. В проекте библиотеки появится интерфейс, а в классе Session Bean ejb-проекта – его поддержка.

7. В Session Bean добавляем методы, а в интерфейсе в проекте библиотеки появятся объявления этих методов (либо их нужно будет прописать там вручную);

8. Создаем третий проект – клиентское приложение, сначала оно будет консольным (тип при создании – Enterprise Application Client). В его библиотеки нужно добавить собранный проект библиотеки. Проект может не собираться, если файл подключаемой библиотеки находится в файловой системе по пути, содержащим пробелы.

9. В третьем проекте использовать удаленный метод, например:

 

//клиент

InitialContext initialContext = new InitialContext();

RemoteServiceRemote remoteServiceRemote

= (RemoteServiceRemote) initialContext. lookup(" remote_service" );

System. out. println(((Sale)remoteServiceRemote. getAllSales(). get(0)). getSecurityName());

 

10. В Session Bean внедряем фасад доступа к информации о продажах и реализовываем метод, возвращающий список объектов «продажа», содержащий всю информацию из соответствующей таблицы в реляционной БД:

 

//удаленный сервис в сессионном компоненте

@EJB

SaleFacade saleFacade;

 

@Override

public List getAllSales()

{

   return saleFacade. findAll();

}

 

//описатель развертывания GlassFish (если отсутствует – создать glassfish-ejb-jar. xml

//в ejb-модуле)

< enterprise-beans>

< ejb>

< ejb-name> RemoteService< /ejb-name>

< jndi-name> remote_service< /jndi-name>

< pass-by-reference> true< /pass-by-reference>

< /ejb>

< /enterprise-beans>

 

11. В проекте библиотеки классов:

 

//интерфейс для клиента в библиотеке классов

@Remote

public interface RemoteServiceRemote

{

List getAllSales();  

}

 

Этап 2

 

12. В web-разделе Enterprise-решения добавляем фейслеты. Сначала создаем в папке «Веб-страницы» два подкаталога - для шаблонов и чанков, и для страниц. Добавляем главный шаблон, внутри тега < h: body> в теги ui: insert делаем вставки ui: include – ссылаемся на будущие чанки:

 

< div id=" top" class=" top" >

< ui: insert name=" top" >

       < ui: include src=" header_chunk. xhtml" />

< /ui: insert>

< /div>

< div id=" left" class=" left" >

< ui: insert name=" left" >

       < ui: include src=" left_menu_chunk. xhtml" />

< /ui: insert>

< /div>

< div id=" content" class=" right_content" >

< ui: insert name=" content" >

       < ui: include src=" index_content_chunk. xhtml" />

< /ui: insert>

< /div>

 

13.  Создаем чанки с соответствующими именами. Создаем страницу на базе шаблона, при необходимости внутри тега ui: composition можно будет переопределять нужные чанки при помощи тега ui: define. В основном описателе развертывания должна быть информация о рендере фейслетов, например:

 

< context-param>

   < param-name> javax. faces. PROJECT_STAGE< /param-name>

   < param-value> Development< /param-value>

< /context-param>

< servlet>

   < servlet-name> Faces Servlet< /servlet-name>

   < servlet-class> javax. faces. webapp. FacesServlet< /servlet-class>

   < load-on-startup> 1< /load-on-startup>

< /servlet>

< servlet-mapping>

   < servlet-name> Faces Servlet< /servlet-name>

   < url-pattern> *. xhtml< /url-pattern>

< /servlet-mapping>

< welcome-file-list>

   < welcome-file> pages/index. xhtml< /welcome-file>

< /welcome-file-list>

 

 

14. Добавляем файлы для локализации. Создаем пакет nls, добавляем в него «прочее-файл свойств» (. properties), добавляем в файл ключи и значения на разных языках (в пакет перед этим нужно добавлять файлы локализации для необходимых языков). Создаем faces-config. xml, добавляем в него ключи для переводов:

 

< application>

   < resource-bundle>

       < base-name> org. tyaa. tradingactivity. nls. messages< /base-name>

       < var> msg< /var>

   < /resource-bundle>

< /application>

 

15. На страницах jsf (xhtml) через jsp-вызовы получаем локализованные значения строк (index_title – ключ одной из локализованных строк):

 

#{msg. index_title}

 

16. В веб-разделе создаем пакет bean, создаем в нем управляемый JSF-бин с областью видимости «приложение» (будет создан один экземпляр, общий для всех клиентов). Внедряем в него фасад доступа к данным о продажах и создаем метод получения заполненной данными коллекции (вызывать этот метод будет сам контейнер при попытке вывести данные на страницу):

 

@ManagedBean(name=" index_data" )

@ApplicationScoped

public class IndexJSFManagedBean {

 

 public IndexJSFManagedBean() {}

 

@EJB

SaleFacade saleFacade;

 

public List getAllSales() {

   return saleFacade. findAll();

}  

}

 

17. На странице (чанк для вывода основного контента) подключаем все необходимые библиотеки и выводим содержимое коллекции в виде ссылок с параметром id (в БД значение цены хранится в целочисленном формате, а при выводе на страницу делится на 100):

 

< h: dataTable value=" #{index_data. allSales}" var=" sale" >

       < h: column>

                   < f: facet name=" header" > id< /f: facet>

                   < h: outputText value=" #{sale. id}" />

       < /h: column>

       < h: column>

                   < f: facet name=" header" > security name< /f: facet>

                   < h: link value=" #{sale. securityName}" >

                              < f: param name=" sale_id" value=" #{sale. id}" />

                   < /h: link>

       < /h: column>

       < h: column>

                   < f: facet name=" header" > quantity< /f: facet>

                   < h: outputText value=" #{sale. quantity}" />

       < /h: column>

       < h: column>

                   < f: facet name=" header" > price< /f: facet>

                   < h: outputText value=" #{sale. price / 100}" />

       < /h: column>

< /h: dataTable>

 

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

 

//в проекте интерфейса

void addSale(Sale _sale, int _brokerId);

 

//в сессионном компоненте удаленного сервиса

@EJB

BrokerFacade brokerFacade;

@Override

public void addSale(Sale _sale, int _brokerId) {

   Broker broker = brokerFacade. find(_brokerId);

   if(broker! = null) {

       _sale. setBrokerId(broker);

       saleFacade. create(_sale);

   }

}

 

//в проекте клиента (идентификатор брокера пока установим жестко равным 1)

Sale newSale = new Sale();

   newSale. setPrice(4100);

   newSale. setQuantity(200);

   newSale. setSecurityName(" ORCL" );

   remoteServiceRemote. addSale(newSale, 1);

 

Этап 3

 

19. Дорабатываем структуру БД: добавляем таблицу «категории», в таблице «продажи» добавляем внешний ключ для связи с ней. Обновляем сущности на основе БД и фасады к ним (прежним способом – при помощи мастера EclipseLink).

20. Добавляем чанк левого меню, делаем в нем вывод списка категорий в виде ссылок с параметром – идентификатором категории:

 

< h: dataTable value=" #{index_data. allCategories}" var=" category" >

       < h: column>

                   < h: link value=" #{category. name}" >

                              < f: param name=" category_id" value=" #{category. id}" />

                   < /h: link>

       < /h: column>

< /h: dataTable>

 

21. Корректируем стили страниц: добавляем в каталог ресурсов файл custom. css, в нем определяем / переопределяем нужные стили. Например, в стилях по умолчанию указан красный цвет для текста заголовка первого уровня, заменим его более «спокойным» цветом:

 

h1{

color: #dddddd;

}

 

22. Соответствующим образом дополняем управляемый бин – поставщик данных для страниц:

 

@EJB

CategoryFacade categoryFacade;

 

public List getAllCategories(){

   return categoryFacade. findAll();

}

 

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

 

#{param. category_id}

 

24. Добавляем к аннотации «управляемый бин» параметр – загружать данные еще до первого обращения за ними: (eager = true)

25. В чанке левого меню можно было бы заменить тег Link на CommandLink с указанием атрибута action, тогда в управляемом бине-поставщике данных для страниц можно было бы написать отдельное действие. В данном примере используется ссылка без команды в паре с уже существующим действием, в которое добавлен условный оператор.

26. В управляемом бине добавить развилку в действие наполнения списка продаж, а в файле представления главной страницы убрать временное переопределение области основного контента:

 

public List getSales(){

       Map< String, String> params =

                              FacesContext. getCurrentInstance()

                                                                 . getExternalContext()

                                                                 . getRequestParameterMap();

       if(params. containsKey(" category_id" )){

                   Category category =

categoryFacade. find(Integer. valueOf(params. get(" category_id" )));

return (List)category. getSaleCollection();

       }

       return saleFacade. findAll();

}

 

27. Задание: а) добавить страницу покупки акций. Она должна открываться по клику по ID продажи и отображать наименование, количество, цену, кнопку «Заказать» (кнопку не программировать); б) добавить на главную страницу строку поиска продаж акций по набору символов в названии фирмы, запрограммировать ее функционал.

 

Этап 4

 

28. Сделаем загрузку области основного контента асинхронной, при этом перед загрузкой будет отображаться сообщение. В web-проекте решения в файле шаблона главной страницы в теге < h: head> указываем подключения будущего файла js-сценариев: < h: outputScript library=" js" name=" custom. js" />. После блока, выводящего чанк хедера, добавляем блок сообщения о загрузке контента:

 

< div id=" loading_wrapper" >

< p> Loading... < /p>

< /div>

 

, а в фале стилей задаем ему невидимость:

 

#loading_wrapper{

width: 100%;

display: block;

text-align: center;

position: fixed;

display: none;

}

 

#loading_wrapper p{

background-color: #fde88e;

width: 100px;

height: 20px;

margin: 0 auto;

font-weight: bold;

}

 

29. В каталоге resources создаем подкаталог js, помещаем в него файл сценариев для веб-клиента custom. js. Добавляем в него функцию отображения и скрытия сообщения «Загрузка …»:

 

function showProgress(data) {   

if (data. status === " begin" ) {

   document. getElementById('loading_wrapper'). style. display = " block";

} else if (data. status === " success" ) {

   document. getElementById('loading_wrapper'). style. display = " none";

}

}

 

30. В чанке области основного контента тегу dataTable зададим значение атрибута id, например, salesList. По этому идентификатору будет определяться область, которую нужно обновлять асинхронно.

31. В чанке левого меню содержимое тега < ui: composition> обернем в тег < h: form>. Тег формирования ссылок должен быть h: commandLink. В нем, после строки формирования параметра запроса, добавляем элемент асинхронного режима выполнения запроса (будет выполняться по событию «click», запускать функцию «showProgress» из файла js-сценариев, отправлять запрос методу «getSales()», находящемуся в управляемом бине, на основании ответа обновлять область разметки, помеченную атрибутом «salesList», где префикс «двоеточие» обозначает, что данный идентификатор следует искать вне формы, в которой расположен сам элемент «f: ajax»):

 

< f: ajax event=" click" onevent=" showProgress" render=": salesList" listener=" #{index_data. getSales()}" />

 

32. Если сообщение о загрузке исчезает слишком быстро, для отладки допускается приостанавливать поток, в котором выполняется управляемый бин (первой строкой в теле соответствующего метода прописать «Thread. sleep(1000); »);

33. Чтобы в ответ на ajax-запросы выводилась актуальная информация (например, веб-страница была открыта или перезагружена, затем в БД добавились новые записи о продажах, а после пользователь кликнул на названии категории), нужно в файл фасада добавить код принудительной очистки кэша. В качестве примера в файле AbstractFacade в шаблонный метод find первой строкой добавляем вызов очистки кэша:

 

getEntityManager()

      . getEntityManagerFactory()

      . getCache()

      . evict(entityClass, id);

 

34. Задание: а) сделать асинхронным обновление области основного контента при отображении результатов поиска продаж, функционал которого был осуществлен в предыдущем задании. б) Каким образом на производительность и надежность системы повлияет очистка кэша, добавленная нами в AbstractFacade? Если негативно, то как это исправить?

 

 

Этап 5

 

35. Настроим переключение локалей веб-клиента, заготовленных ранее. В каталог «resources» добавляем подкаталог «images», в него – файлы картинок для выбора локалей.

36. Создаем в пакете «bean» управляемый компонент переключения локалей:

 

@ManagedBean(name=" locale_changer", eager = true)

@SessionScoped

public class LocaleChangerManagedBean {

private Locale currentLocale = FacesContext. getCurrentInstance(). getViewRoot(). getLocale();

public LocaleChangerManagedBean() {}

public void changeLocale(String localeCode) {

   currentLocale = new Locale(localeCode);

}

public Locale getCurrentLocale() {

   return currentLocale;

}

}

 

37. Создаем чанк для вывода иконок выбора локали, содержащий:

 

< div class=" locales" >

< h: form>

                   < h: commandLink action=" #{locale_changer. changeLocale('en')}" >

                              < h: graphicImage library=" images" name=" en-us_round_icon_64.png" title=" #{msg. english}" />

                   < /h: commandLink>

                   < h: commandLink action = " #{locale_changer. changeLocale('ru')}" >

                              < h: graphicImage library=" images" name=" ru-ru_round_icon_64.png" title=" #{msg. russian}" />

                   < /h: commandLink>

       < /h: form>

< /div>

 

38. Подключаем этот чанк в чанк «шапки» сайта:

 

< ui: include src=" /templates/locales_chunk. xhtml" />

 

39. Для проверки работы переключения локалей, кроме тайтла страниц подставляем вывод локализованных строк в теле страниц, например, в заголовке колонки категорий вместо статического текста подставляем #{msg. categories_title} (предварительно добавив соответствующую строку в файлы локалей). Весь код в шаблоне главной страницы нужно обернуть в тег < f: view locale=" #{locale_changer. currentLocale}" > (внутри должны оказаться теги h: head и h: body).

40. Если локали не подключились автоматически, следует в описателе развертывания «faces-config» в тег < application> добавить указание локали по умолчанию и других поддерживаемых локалей:

 

< locale-config>

< default-locale> ru< /default-locale>

< /locale-config>

 < supported-locale> en< /supported-locale>

 

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

 

 

Этап 6

Форматируем вывод чисел на веб-страницу

Обрабатываем ошибки AJAX при помощи OmniFaces

Отправляем и обрабатываем сообщения JMS

Устанавливаем задачу на серверной стороне при помощи EJB Schedule

 

42. Корректируем формат вывода чисел в области основного контента веб-представления:

 

< h: outputText value=" #{sale. price/100}" >

       < f: convertNumber pattern=" #00. 00" />

< /h: outputText>

 

43. Для вывода таблицы, сформированной тегами div, можно использовать универсальный тег циклического вывода jsf (фрагмент кода):

 

< ui: repeat value=" #{main_data. allSales}" var=" sale" >

       < div class=" row" >

                   < div class=" col" >

                              < h: outputText value=" #{sale. id}" />

                   < /div>

 

44. Основа стиля для нового отображения таблицы:

 

. table{display: table; }

. row{display: table-row; }

. col{display: table-cell; padding-right: 5px; }

. col-header{display: table-cell; padding-right: 5px; font-weight: 600; }

 

45. Сейчас при возможных ошибках AJAX-запросов получаются непредвиденные результаты. Чтобы обрабатывать эти ошибки, добавляем в web-проект решения библиотеку OmniFaces. В файле faces-config. xml объявляем фабрику обработчиков исключений AJAX-запросов, и в ней – необходимый тип обработчика (внутри раздела < application> ):

 

< factory>

< exception-handler-factory> org. omnifaces. exceptionhandler. FullAjaxExceptionHandlerFactory< /exception-handler-factory>

< /factory>

 

46. В каталоге WEB-INF создаем файл фейслета, в контенте которого выводим информацию об исключении.

47. В файле web. xml внутри раздела < web-app> добавляем связку «тип исключения – страница ошибки» (для развертывания не подходит версия GlassFish4. 0):

 

< error-page>

   < exception-type> javax. faces. application. ViewExpiredException< /exception-type>

   < location> /WEB-INF/errorpages/expired. xhtml< /location>

< /error-page>

 

48. Для возможности асинхронного оповещения сервисов о добавлении информации в БД используем JMS. Создаем на сервере приложений в разделе JMS Resources фабрику соединений с именем JNDI (например, jms/TradingActivityPool) и точку назначения типа «топик» (jms/TradingActivityWebTopic).

49. В EJB-проекте решения в файле RemoteService внедряем оба ресурса:

 

@Resource(name=" jms/TradingActivityPool" )

private ConnectionFactory connectionFactory;

   

@Resource(name=" jms/TradingActivityWebTopic" )

private Destination destination;

 

50. Там же создаем метод отправки сообщений:

 

public void sendActionString(String _actionString) {

   try {

       Connection connection = connectionFactory. createConnection();

       Session session = connection. createSession(true, Session. AUTO_ACKNOWLEDGE);

       MessageProducer producer = session. createProducer(destination);

       TextMessage message = session. createTextMessage();

       message. setStringProperty(" message_type", " action" );

       message. setText(_actionString);

       producer. send(message);

       System. out. println(" message sent" );

       session. close();

       connection. close();

   } catch (JMSException ex) {

       System. err. println(" Sending message error" );

       ex. printStackTrace();

   }

}

 

51. Там же вызываем его в нужный момент и передаем в аргумент текст сообщения;

52. В WEB-проекте добавляем в каталог bean управляемый сообщениями бин:

 

@MessageDriven(

   mappedName=" jms/TradingActivityWebTopic",

   name = " IndexMDB" )

public class IndexMessageDrivenBean implements MessageListener{

       

@Override

public void onMessage(Message msg) {

   try {

       TextMessage message = (TextMessage)msg;

       //считываем свойство из соответствующего поля, заданное вручную в consumer

       System. out. println(" FROM MDB - message type IS " + message. getStringProperty(" message_type" ));

       //считываем само сообщение

       System. out. println(" FROM MDB - payload IS " + message. getText());

   } catch (JMSException ex) {

       ex. printStackTrace();

   }

}

   

}

 

53. Запускаем удаленного настольного клиента, в консоли сервера приложений смотрим вывод при отправке и получении сообщения. Вместо отладочного вывода в дальнейшем можно добавить логику.

54. В WEB-проекте в бин, управляемый фейслетом, добавляем планирование циклического выполнения задачи – обновления текущей веб-страницы каждую минуту:

 

@Schedule(minute=" */1" )

public void updateJsfPage() throws IOException {

   System. out. println(" Jsf page updating" );

   FacesContext. getCurrentInstance(). getExternalContext()

              . redirect(" #" );

}

 

55. Задание: добавить еще одного получателя сообщений из уже существующего «топика». Добавить еще одну цепочку передачи сообщений JMS, но вместо «топик» использовать тип точки назначения «очередь», и принимать сообщения одним получателем.

Этап 7

Переводим клиент Enterprise-приложения с консольного на JavaFX

 

56. В классе Main настольного клиента добавляем extends Application, переопределяем метод start, комментируем содержимое метода main(), вместо него добавляем метод запуска JavaFX-приложения: launch(args);

57. Добавляем пакет с двумя файлами ScreensFramework от Angela Caicedo (прилагаются).

58. Создаем пакет для представлений, создаем в нем два пустых текстовых файла с расширением fxml – главный и добавления продажи. Открываем каждый из них в приложении SceneBuilder2. 0, добавляем по корневому элементу (тип - AnchorPane). В NetBeans кликаем ПКМ по каждому из файлов представлений, выбираем пункт «установить контроллер». Контроллеры выносим в отдельный пакет. В файлах представлений в открывающей тег элемента AnchorPane добавляем атрибут fx: controller с указанием полностью квалифицированного имени соответствующего контроллера (имя_пакета. ИмяКласса).

59. В классах контроллеров устанавливаем поддержку интерфейса ControlledScreen из ScreensFarmework, реализовываем метод setScreenParent, в его тело добавляем установку родительского контейнера JavaFX, а также добавляем соответствующее поле класса:

 

ScreensController myController;

//…

@Override

public void setScreenParent(ScreensController screenParent) {

   myController = screenParent;

}

 

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

 

public static String mainID = " main";

public static String mainView = " /org/tyaa/taclient/view/Main. fxml";

public static String addSaleID = " add_sale";

public static String addSaleView = " /org/tyaa/taclient/view/AddSale. fxml";

 

61. Там же в теле метода start добавляем два экрана, устанавливаем экран по умолчанию, создаем пустой корневой элемент, добавляем в него контейнер сменных экранов. На базе корневого элемента создаем одну сцену, на ее базе – один стейдж, отображаем его:

 

//Создаем объект скринс-фреймворка (контейнер представлений)

ScreensController screensContainer = new ScreensController();

//Добавляем в него представления главного окна и окна добавления продажи

screensContainer. loadScreen(Main. mainID, Main. mainView);

screensContainer. loadScreen(Main. addSaleID, Main. addSaleView);

//Устанавливаем представление главного окна в качестве текуего

screensContainer. setScreen(Main. mainID);

//Создаем корневой контейнер, помещаем в него наш контейнер представлений,

//на его базе - сцену, которую подключаем в главный стейдж и отображаем стейдж

Group root = new Group();

root. getChildren(). addAll(screensContainer);

Scene scene = new Scene(root);

primaryStage. setScene(scene);

primaryStage. show();

 

62. Добавляем в контроллеры методы обработки событий для будущих контролов переключения между экранами, например:

 

@FXML

private void goToAddSaleScreen(ActionEvent event){

  myController. setScreen(Main. addSaleID);

}

 

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

 

<? import java. lang. *? >

<? import javafx. scene. layout. *? >

<? import java. util. *? >

<? import javafx. geometry. *? >

<? import javafx. scene. *? >

<? import javafx. scene. control. *? >

<? import javafx. scene. text. *? >

//…

< children>

   < Label text=" My sales" >

     < font>

       < Font size=" 18. 0" />

     < /font>

   < /Label>

   < Button mnemonicParsing=" false" onAction=" #goToAddSaleScreen" text=" Add sale" />

  < /children>

 

64. Эту же разметку открываем в SceneBuilder, в корневой элемент добавляем VBox, в него – три панели AnchorPane. В самую верхнюю из трех панелей добавляем заголовок и название технологии в виде элементов Label, которым в разделе настроек Layout (правая колонка в SceneBuilder) задаем привязки к сторонам родительского AnchorPane. В следующую панель добавляем название таблицы и переносим туда же кнопку перехода на экран добавления продажи. На третью панель добавляем элемент TableView.

65. Переходим в код представления и заменяем в разметке таблицы колонки, сгенерированные автоматически, на необходимые нам:

 

< columns>

       < TableColumn fx: id=" idTableColumn" sortable=" true" text=" id" />

       < TableColumn fx: id=" securityNameTableColumn" sortable=" true" text=" security name" />

       < TableColumn fx: id=" quantityTableColumn" sortable=" true" text=" quantity" />

       < TableColumn fx: id=" priceTableColumn" sortable=" true" text=" price" />

< /columns>

 

66.  Создаем пакет для ресурсов, а в нем – файл каскадной таблицы стилей. Добавляем в него свойства для стандартного класса панелей:

 

. pane{-fx-background-color: lightgrey; }

 

67. В SceneBuilder в разделе Properties для родительского AnchorPane устанавливаем stylesheet – выбираем созданный нами css, а в поле Style Class выбираем pane.

68. В коде стилей для метки-заголовка создаем пользовательский класс:

 

. caption{-fx-text-fill: lightslategrey; }

 

69. В SceneBuilder в поле Style Class выбираем caption (можно также в исходном коде добавлять нужным тегам атрибут styleClass=" имя_класса_стилей" ). Предпросмотр результат можно сделать в SceneBuilder: Preview -> Show preview in window.

70. Создаем пакет model, в нем – новый класс SaleModel. Он будет предоставлять модель данных продажи для отображения в таблице JavaFX TableView, в отличие от класса модели Sale, являющегося сущностью для обмена данными с реляционной БД. Строим его как класс модели со свойствами:

 

private IntegerProperty id;

private StringProperty securityName;

private IntegerProperty quantity;

private DoubleProperty price;

 

public SaleModel(int _id, String _securityName, int _quantity, double _price){

       this. id = new SimpleIntegerProperty(_id);

       this. securityName = new SimpleStringProperty(_securityName);

       this. quantity = new SimpleIntegerProperty(_quantity);

       this. price = new SimpleDoubleProperty(_price);

}

 

public int getId() {return id. getValue(); }

public IntegerProperty idProperty() {return id; }

//…

 

71. В контроллере главного экрана добавляем поля для внедрения графических элементов из разметки и поля для коллекции данных, поставляемых таблице:

 

@FXML

private TableView mySalesTableView;

@FXML

private TableColumn idTableColumn;

//…

ObservableList< SaleModel> sales;

 

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

 

sales = FXCollections. observableArrayList();

InitialContext initialContext = null;

RemoteServiceRemote remoteServiceRemote = null;

initialContext = new InitialContext();

remoteServiceRemote =

                   (RemoteServiceRemote) initialContext. lookup(" remote_service" );

List allMySales = remoteServiceRemote. getAllSales();

for (Object object: allMySales) {

       Sale sale = (Sale)object;

       sales. add(new SaleModel(

                              sale. getId()

                             , sale. getSecurityName()

                             , sale. getQuantity()

                             , sale. getPrice())

       );

}

idTableColumn. setCellValueFactory(

                   new PropertyValueFactory< SaleModel, String> (" id" )

);

securityNameTableColumn. setCellValueFactory(

                   new PropertyValueFactory< SaleModel, String> (" securityName" )

);

quantityTableColumn. setCellValueFactory(

                   new PropertyValueFactory< SaleModel, String> (" quantity" )

);

priceTableColumn. setCellValueFactory(

                   new PropertyValueFactory< SaleModel, String> (" price" )

);

mySalesTableView. setItems(sales);

 

73. Цены в колонке price выводятся как есть (целые числа из БД). Во время вывода в представление их значения нужно делить на 100 и форматировать в соответствии с локалью «en-US». Для этого сначала сделаем вывод значения цены в виде типа Double вместо String:

 

priceTableColumn. setCellValueFactory(

       new PropertyValueFactory< SaleModel, Double> (" price" )

);

 

74. Затем подготовим форматирующий объект, настроив его на нужную локаль, а также – на нужное число знаков до и после запятой:

 

NumberFormat doubleToCurrencyFormatter =

       NumberFormat. getCurrencyInstance(new Locale(" en", " US" ));

doubleToCurrencyFormatter. setMinimumIntegerDigits(1);

doubleToCurrencyFormatter. setMinimumFractionDigits(2);

 

75. Остается задать объекту колонки price измененную фабрику ячеек, внутри которой будем делить значение цены на 100, а затем применять метод format форматирующего объекта:

 

priceTableColumn. setCellFactory(column -> {  

       return new TableCell< SaleModel, Double> (){

                   @Override

                   protected void updateItem(Double item, boolean empty)

                   {

                              super. updateItem(item, empty);

                              if (item == null || empty) {

                                          setText(null);

                                          setStyle(" " );

                              } else {

                                          setText(doubleToCurrencyFormatter. format(item / 100));

                              }

                   }

                       

       };

});

 

76. Теперь нужно создать форму для ввода параметров продажи и ее отправки в БД. Открываем SceneBuilder, вверху в левой колонке жмем на кнопку настроек (маленькая шестеренка), выбираем импорт JAR. Нужно подключить файл библиотеки controlsfx, который скачивается с одноименного сайта. Этот же файл нужно добавить в набор библиотек в проекте десктопного клиента.

77. Контроллеру второго экрана нужно предоставить объект для выполнения удаленных методов при помощи RMI (можно скопировать код из контроллера первого экрана и вынести в отдельный файл, или временно – в файл второго контроллера).

78. Открываем в SceneBuilder разметку второго представления, добавляем VBox, в него – три AnchorPane. В первую панель добавляем Label – заголовок формы («Добавить продажу»), во вторую – VBox с вложенными HBox, в каждом из которых – пара  Label и CustomTextField. В третью панель добавляем HBox с двумя Button – «Добавить» и «Назад».

79. Всем элементам настраиваем расположение. Каждому полю ввода задаем fx: id, например, securityNameCTextField. Можно также задать Prompt Text. Кнопке «Добавить» устанавливаем обработчик события OnAction, например, actionAddSale, а также в разметке добавляем атрибут defaultButton=”true”. Кнопке отмены в разметке добавляем атрибут cancelButton.

80. В контроллере второго экрана внедряем поля ввода:

 

@FXML

CustomTextField securityNameCTextField;

 

81. Там же создаем переменную для валидатора:

 

ValidationSupport validationSupport;

 

82. Включаем экстрактор для нестандартного типа контрола, чтобы валидатор реагировал на события с этим типом контролов:

 

ValueExtractor. addObservableValueExtractor(

           c -> c instanceof CustomTextField

          , c -> ((CustomTextField) c). textProperty()

);

 

83. Инициализируем и настраиваем валидатор для всех полей:

 

validationSupport = new ValidationSupport();

validationSupport. setErrorDecorationEnabled(true);

validationSupport. registerValidator(

       securityNameCTextField

      , Validator. createEmptyValidator(" Security name is required" )

); //…

 

84. Создаем и заполняем метод (действие) обработки событий кнопки «Добавить»:

 

public void actionAddSale(ActionEvent actionEvent) {

       //securityNameCTextField. getStyleClass(). remove(" error-c-text-field" );

       //securityNameCTextField. getStyleClass(). add(" custom-text-field" );

       List< ValidationMessage> validationMessageList =

                              (List< ValidationMessage> ) validationSupport

                                                     . getValidationResult()

                                                     . getMessages();

       if (validationMessageList. isEmpty()) {

                   securityNameCTextField. promptTextProperty()

                                                                            . setValue(" security name" );

                   //...

                   securityNameCTextField. setStyle(" -fx-border-color: lightgray; " );

                   //...

                   Sale newSale = new Sale();

                   newSale. setPrice((int)(Double. parseDouble(priceCTextField. getText()) * 100));

                   newSale. setQuantity(Integer. parseInt(quantityCTextField. getText()));

                   newSale. setSecurityName(securityNameCTextField. getText());

                   remoteServiceRemote. addSale(newSale, 1, 1);

                   securityNameCTextField. textProperty(). setValue(" " );

                   //...

       } else {

                   String currentControlIdString;

                   for (ValidationMessage validationMessage: validationMessageList) {

                              currentControlIdString =

                                                      ((CustomTextField)validationMessage. getTarget()). getId();

                              switch(currentControlIdString){

                                          case " securityNameCTextField" : {

                                                      securityNameCTextField. promptTextProperty()

                                                                            . setValue(validationMessage. getText());

                                                      //securityNameCTextField. getStyleClass(). remove(" custom-text-field" );

                                                      //securityNameCTextField. getStyleClass(). add(" error-c-text-field" );

                                                      securityNameCTextField. setStyle(" -fx-border-color: red; " );

                                                      break;

                                          }

                                          //...

                              }

                   }

       }

}

 

85. Задание: а) во время запуска приложения показывать заставку («пожалуйста, подождите, идет загрузка данных с сервера…»); б) переработать удаленный метод добавления продажи так, чтобы он возвращал некоторое значение, получив которое клиентское приложение сообщало бы об успешном или неуспешном добавлении данных в БД, а во время добавления показывало соответствующее сообщение; в) добавить в первое представление кнопку «обновить» и запрограммировать ее, чтобы по клику обновлялись данные в таблице; г) найти необходимую документацию и советы, и с их помощью выяснить: как вместо RMI применять очередь JMS для связи серверной и клиентской сторон, как это сделать для обычного клиентского приложения, можно ли каким-то способом применять для такого клиента RMI.

 



  

© helpiks.su При использовании или копировании материалов прямая ссылка на сайт обязательна.