Разработка веб-приложения
При изучении технологий Ext JS и Java, написал web-приложение «Каталог автомобилей». Хочу поделиться с Вами этим опытом.
Вид и функциональность приложения
- Добавление;
- Удаление;
- Редактирование;
- Поиск;
- Валидация данных;
Инструменты
- IntelliJ IDEA 13 скачать
- Ext JS 5.0.1 скачать
- Apache Tomcat 8.0.12 скачать
- MySQL 5.6.20 скачать
- Apache Maven 3.0.5 скачать
- Java 1.8.0_20 скачать
- Java doc читать
- Ext JS Guides читать
- Hibernate читать
- JPA читать
- Spring читать
- SQL читать
- MVC читать
- DAO читать
- Layer Service читать
- Дескриптор развертывания читать
Укажите путь к Java в Project SDK:
Укажите путь к своей Maven home directory:
«Maven projects need to be imported» кликаем Enable Auto-Import
Добавим Tomcat Server:
В Application server укажите путь до Tomcat сервера:
Ok -> Apply -> Ok
Проверим, что всё работает:
Клиент (ExtJS)
Добавим файлы фрэймворка Ext JS:
Модель Ext JS MVC:
Создадим файл app.js:
Ext.application(< name: 'CarCatalog', launch: function () < Ext.create('Ext.container.Viewport', < layout: 'fit', items: < xtype: 'panel', html: 'Каталог автомобилей
' > >); > >);
- Метод Ext.application инициализирует приложение Ext JS;
- name: ‘CarCatalog’ указывает имя приложения, которое будет затем использоваться для создания полных имен классов приложения;
- launch: function()<> тут происходит создание приложения;
Проверим, что всё работает:
View
Понадобится четыре вида — это вид поиска SearchCarView.js, вид таблицы CarGridView.js, вид формы добавления данных AddCarFormView.js и вид каркаса CarCatalogView.js, куда поместим все виды.
Ext.define('CarCatalog.view.SearchCarView', < extend: 'Ext.form.Panel', alias: 'widget.searchCarView', bodyPadding: 10, items: [ < xtype: 'textfield', name: 'search', fieldLabel: 'Введите название модели', maxLength: 200 >] >);
Ext.define('CarCatalog.view.CarGridView', < extend: 'Ext.grid.Panel', alias: 'widget.carGridView', width: 400, height: 300, frame: true, iconCls: 'icon-user', viewConfig:< markDirty:false >, columns: [ < text: 'Модель', flex: 1, sortable: true, dataIndex: 'name', editor: < xtype:'textfield', allowBlank: false, blankText: 'Это поле должно быть заполнено' >>, < flex: 2, text: 'Цена', sortable: true, dataIndex: 'price', editor: < xtype:'textfield', regex: /^(4)*$/, regexText: 'Цена должна состоять из цифр', allowBlank: false, blankText: 'Это поле должно быть заполнено' > > ], plugins: [ Ext.create('Ext.grid.plugin.RowEditing', < clicksToEdit: 2, saveBtnText: 'Сохранить', cancelBtnText: 'Отменить' >) ], selType: 'rowmodel', dockedItems: [ < xtype: 'toolbar', items: [ < text: 'Добавить', action: 'add', iconCls: 'icon-add' >, '-', < action: 'delete', text: 'Удалить', iconCls: 'icon-delete', disabled: true >] > ] >);
Ext.define('CarCatalog.view.AddCarFormView', < extend: 'Ext.window.Window', alias: 'widget.addCarFormView', autoShow: true, layout: 'fit', modal: true, items: [ < bodyPadding: 10, xtype: 'form', items: [ < xtype: 'textfield', name: 'name', fieldLabel: 'Название модели', allowBlank: false, blankText: 'Это поле должно быть заполнено' >, < xtype: 'textfield', name: 'price', fieldLabel: 'Цена', regex: /^(5)*$/, regexText: 'Цена должна состоять из цифр', allowBlank: false, blankText: 'Это поле должно быть заполнено' > ] > ], buttons: [ < text: 'Сохранить', action: 'save', disabled: true >, < text: 'Отменить', handler: function () < this.up('window').close(); >> ] >);
Ext.define('CarCatalog.view.CarCatalogView', < extend: 'Ext.panel.Panel', width: 500, height: 360, padding: 10, alias: 'widget.carCatalogView', layout: 'border', items: [ < xtype: 'carGridView', region: 'center' >, < xtype: 'panel', html: 'Каталог автомобилей ', region: 'north', height: 80 >, < xtype: 'searchCarView', title: 'Поиск', region: 'west', width: 300, collapsible: true, collapsed: false >], renderTo: Ext.getBody() >);
- Метод Ext.define(‘Имя’, ) создает класс-компонент, который может быть унаследован от какого-нибудь компонента. Например в CarGridView.js указали extend: ‘Ext.grid.Panel’, что будет представлять собой таблицу;
- ‘carCatalogView’ алиас, который указали для вида CarCatalogView.js
Проверим, что всё работает:
Controller
Ext.define('CarCatalog.controller.CarCatalogController', < extend: 'Ext.app.Controller', init: function () < this.control(< >); > >);
- С помощью параметра init инициализируются обработчики для компонентов (кнопки, поля и т.д). Связать конпонент с обработчиком помогает функция control;
Модель и хранилище
Ext.define('CarCatalog.model.CarCatalogModel', < extend: 'Ext.data.Model', fields: ['name', 'price'], proxy: < type: 'rest', api: < create: 'car', read: 'car', destroy: 'car', update: 'car' >, reader: < type: 'json', root: 'data', successProperty: 'success' >, writer: < type: 'json', writeAllFields: true >> >);
Ext.define('CarCatalog.store.CarCatalogStore', < extend: 'Ext.data.Store', requires : [ 'CarCatalog.model.CarCatalogModel' ], model: 'CarCatalog.model.CarCatalogModel', autoLoad: true, autoSync: true, proxy: < type: 'rest', api: < create: 'car', read: 'car', destroy: 'car', update: 'car' >, reader: < type: 'json', root: 'data', successProperty: 'success' >, writer: < type: 'json', writeAllFields: true >> >);
- ‘car’ имя, на которое будет замапен java-класс (контролер), который будет обрабатывать GET, POST, PUT, DELETE запросы с клиента;
Добавим в CarGridView.js параметр store: ‘CarCatalogStore’, для отображения данных в таблице:
Укажим контролер CarCatalogController.js и хранилище CarCatalogStore.js в app.js:
Проверим, что всё работает. 404 (Not Found) — это нормально, так как по адресу localhost:8080/car еще ничего нет:
Сервер (Java)
UTF-8 1.7 3.2.2.RELEASE 3.1.4.RELEASE 1.5.6 1.2.17 4.2.2.Final 1.9.12 0.11.8 3.2.0 0.0.23-SNAPSHOT mysql mysql-connector-java 5.1.6 org.springframework spring-orm $ org.springframework spring-tx $ org.hibernate hibernate-entitymanager $ org.springframework spring-test $ test cglib cglib 2.2.2 commons-lang commons-lang 2.4 test org.hibernate hibernate-validator 4.3.1.Final org.hibernate hibernate-jpamodelgen 1.2.0.Final provided com.mysema.querydsl querydsl-jpa $ com.mysema.querydsl querydsl-apt $ provided org.codehaus.jackson jackson-core-asl $ org.codehaus.jackson jackson-mapper-asl $ org.springframework spring-webmvc $ org.springframework spring-aop $ commons-fileupload commons-fileupload 1.3 javax.servlet javax.servlet-api 3.0.1 provided commons-logging commons-logging 1.1.3 joda-time joda-time 2.2 org.springframework.security spring-security-config $ org.springframework.security spring-security-ldap $ org.springframework.security spring-security-web $ org.springframework spring-aspects $ org.springframework.security spring-security-taglibs $ opensymphony quartz 1.6.3 org.aspectj aspectjrt 1.7.2 provided org.aspectj aspectjweaver 1.7.2 provided junit junit 4.11 test commons-dbcp commons-dbcp 1.4 commons-pool commons-pool org.slf4j slf4j-api $ org.slf4j slf4j-log4j12 $ log4j log4j $ com.jolbox bonecp 0.7.1.RELEASE
Создадим папку java:
Создадим модель данных и слой доступа к данным (DAO):
@Entity @Table(name = "cars") public class Car implements Serializable < @Id @Column(name = "ID") @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "name") private String name; @Column(name = "price") private Long price; public Car() < >public Long getId() < return id; >public void setId(Long id) < this.id = id; >public String getName() < return name; >public void setName(String name) < this.name = name; >public Long getPrice() < return price; >public void setPrice(Long price) < this.price = price; >>
public interface CarDao < void add(Car car); void update(Car car); void delete(Car car); CollectiongetCars(String search); public List findByCar(String name, Long price); >
public class CarDaoImpl implements CarDao < @PersistenceContext private EntityManager emf; @Override public void add(Car car) < emf.persist(car); >@Override public void update(Car car) < emf.merge(car); >@Override public void delete(Car car) < emf.remove(emf.getReference(Car.class, car.getId())); >@Override public Collection getCars(String search) < if (null == search || search.trim().isEmpty()) < return emf.createQuery( "select c from Car c") .getResultList(); >return emf.createQuery( "select c from Car c where c.name like :search") .setParameter("search", search.trim() + "%") .getResultList(); > public List findByCar(String name, Long price) < return emf.createQuery( "select c from Car c where c.name = :name and c.price = :price") .setParameter("name", name) .setParameter("price", price) .getResultList(); >>
- Метод getCars(String search) принимает значение поля поиска и если пусто — вернет все данные;
- Метод findByCar(String name, Long price) используется для поиска дубликата при добавлении данных;
Создадим слой сервиса:
public interface CarService < Boolean add(Car car); void update(Car car); CollectiongetCars(String search); void delete(Car car); >
public class CarServiceImpl implements CarService < private CarDao carDao; public CarDao getCarDao() < return carDao; >public void setCarDao(CarDao carDao) < this.carDao = carDao; >@Transactional @Override public Boolean add(Car car) < Listduplicate = carDao.findByCar(car.getName(), car.getPrice()); if (duplicate.isEmpty()) < carDao.add(car); return true; >return false; > @Transactional @Override public void update(Car car) < carDao.update(car); >@Transactional @Override public Collection getCars(String search) < return carDao.getCars(search); >@Transactional @Override public void delete(Car car) < carDao.delete(car); >>
Создадим контролер, который будет замапен на адрес /car для обработки запросов с клиента:
@Controller @RequestMapping("/car") public class CarController < @Autowired private CarService carService; @RequestMapping(method = RequestMethod.GET) @ResponseBody public CollectiongetCars(String search) < return carService.getCars(search); >@RequestMapping(method = RequestMethod.POST) @ResponseBody public ExtResult setCar(@RequestBody Car car) < return new ExtResult(carService.add(car), car); >@RequestMapping(value = "", method = RequestMethod.DELETE) @ResponseBody public String deleteCar(@RequestBody Car car) < carService.delete(car); return "delete"; >@RequestMapping(value = "", method = RequestMethod.PUT) @ResponseBody public String updateCar(@RequestBody Car car) < carService.update(car); return "update"; >>
- Каждый метод замапен на соответствующий запрос с клиента. Внедряем зависимость с помощью spring аннотации Autowired и вызываем соответствующие методы у сервиса;
- ExtResult — вспомогательный класс. Используется для ответа клиенту, что сущность, которую пытаемся записать в БД , дубликат или не дубликат;
public class ExtResult < private boolean success; private Car data; public ExtResult(boolean success, Car data) < this.success = success; this.data = data; >public ExtResult() < >public boolean isSuccess() < return success; >public void setSuccess(boolean success) < this.success = success; >public Car getData() < return data; >public void setData(Car data) < this.data = data; >>
Проверьте, что всё работает. Соберите проект с помощью maven install и запустите приложение.
Создадим spring контекст my-context.xml c:
- настройками подключения к БД;
- бином EntityManager — объект, через который происходит взаимодействие с БД. Инжектится в CarDaoImpl.java;
- инжектом объекта класса CarDaoImpl.java в объект класса CarServiceImpl.java;
org.hibernate.dialect.MySQLDialect 1 thread true create org.hibernate.cfg.ImprovedNamingStrategy
Создайте БД с названием CarCatalog и кодировкой utf_general_ci или измените название в контексте my-context.xml на своё. Также измените p:username и p:password, если не совпадает.
Создадим настройки для spring DispatcherServlet, который будет обрабатывать запросы с клиента:
- context:component-scan поиск и регистрация компонентов в контейнере спринга;
- mvc:view-controller домашняя страница;
- mvc:resources автоматически обрабатывать запросы на получение статических данных;
mvc-dispatcher org.springframework.web.servlet.DispatcherServlet 1 mvc-dispatcher / CharacterEncodingFilter org.springframework.web.filter.CharacterEncodingFilter encoding UTF-8 forceEncoding true CharacterEncodingFilter /* contextConfigLocation classpath:/my-context.xml org.springframework.web.context.ContextLoaderListener
Добавим в контролер CarCatalogController.js параметр refs и обработчики для компонентов:
Ext.define('CarCatalog.controller.CarCatalogController', < extend: 'Ext.app.Controller', refs: [ , , , , , , , , ], init: function () < this.control(< 'carGridView button[action=add]': < click: this.onAddCar >, 'carGridView button[action=delete]': < click: this.onDelCar >, 'searchCarView textfield[name="search"]': < change: this.onChangeText >, 'carGridView': < cellclick: this.onLineGrid >, 'addCarFormView button[action=save]': < click: this.onSaveCar >, 'addCarFormView textfield[name=name]': < change: this.onValidation >, 'addCarFormView textfield[name=price]': < change: this.onValidation >>); >, onSaveCar: function (button) < var me = this; var carModel = Ext.create('CarCatalog.model.CarCatalogModel'); carModel.set(this.getAddCarFormView().down('form').getValues()); carModel.save(< success: function (operation, response) < var objAjax = operation.data; Ext.getStore('CarCatalogStore').add(objAjax); me.getAddCarFormView().close(); >, failure: function (dummy, result) < Ext.MessageBox.show(< title: 'Дубликат!', msg: 'Такая модель и цена уже существуют', buttons: Ext.Msg.OK, icon: Ext.Msg.ERROR >); > >); >, onAddCar: function () < Ext.widget('addCarFormView'); >, onDelCar: function () < var sm = this.getCarGridView().getSelectionModel(); var rs = sm.getSelection(); this.getCarGridView().store.remove(rs[0]); this.getCarGridView().store.commitChanges(); this.getCarGridDelete().disable(); >, onChangeText: function () < Ext.getStore('CarCatalogStore').load(< params: < search: this.getCarCatalogView().down('searchCarView').getValues() >>); >, onLineGrid: function () < this.getCarGridDelete().enable(); >, onValidation: function () < if (this.getAddCarFormName().validate() && this.getAddCarFormPrice().validate()) < this.getAddCarFormSave().enable(); >else < this.getAddCarFormSave().disable(); >> >);
- ref ссылка на что-то в selector’e;
- selector указывает на компоненты, для быстро обращения к ним через ref;
- onSaveCar создается модель данных и сохраняется;
- onAddCar создает виджет формы добавления;
- onDelCar удаляет запись;
- onChangeText загружает данные в соответствии со значением в поле поиска;
- onLineGrid при выделении строки кнопка «Удалить» становится активной;
- onValidation валидация полей формы добавления;
И последнее — добавим иконки к кнопкам «Добавить» и «Удалить».