Пошаговая разработка веб-приложения

в 6:25, , рубрики: annotations, apache maven, client-server, css, dao, extjs, extjs mvc, framework, hibernate, intellij idea, java, java developing, javascript, jpa, JSP, mac os x, mvc, mysql, patterns, Servlet, spring framework, Spring MVC, sql, tomcat, validation, web-разработка, XML, Библиотека ExtJS/Sencha, Веб-разработка, Программирование

При устройстве на работу java программистом меня попросили написать тестовое web приложение «Телефонный справочник». Хочу поделиться с вами этим «шедевром».

Вид и функциональность приложения

  • Добавление;
  • Удаление;
  • Поиск;
  • Валидация данных.


Пошаговая разработка веб приложения
Пошаговая разработка веб приложения
Пошаговая разработка веб приложения
Пошаговая разработка веб приложения
Пошаговая разработка веб приложения
Пошаговая разработка веб приложения

Инструменты

  1. IntelliJ IDEA 13 скачать
  2. Ext JS 4.2 скачать
  3. Apache Tomcat 7.0.55 скачать
  4. MySQL 5.6.20 скачать
  5. Apache Maven 3.0.5 скачать
  6. Java 1.6.0_65 скачать
  7. Hibernate читать
  8. JPA читать
  9. Spring читать
  10. SQL читать
  11. MVC читать
  12. DAO читать
  13. 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)

Добавим файлы фрэймворка.

Пошаговая разработка веб приложения

Модель ExtJS MVC.

Пошаговая разработка веб приложения

Создадим файл app.js

Пошаговая разработка веб приложения

app.js

Ext.application({           
    name: 'PhonesDir',   
    appFolder: 'app',
    launch: function () {
        Ext.create('Ext.container.Viewport', {
            layout: 'fit',
            items: {
                xtype: 'panel',
                html: '<h2>Телефонный справочник</h2>'
            }
        });
    }
});
  • Метод Ext.application инициализирует приложение Ext JS;
  • name: 'PhonesDir' указывает имя приложения, которое будет затем использоваться для создания полных имен классов приложения;
  • appFolder: 'app' указывает на нашу папку с инфраструктурой приложения (controller, model, store, view);
  • launch: function(){} тут происходит создание приложения.

В файле index.jsp подключаем стили ExtJS, затем фреймворк ExtJS и только потом app.js:

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Справочник</title>
    <link rel="stylesheet" type="text/css" href="resources/css/ext-all.css"/>
    <script type="text/javascript" src="resources/ext-all.js"></script>
    <script type="text/javascript" src="app.js"></script>
</head>
<body>
</body>
</html>

Проверим, что всё работает.

Пошаговая разработка веб приложения

View

Нам понадобится четыре вида — это вид поиска SearchPhones.js, вид таблицы PhoneGrid.js, вид формы добавления данных AddWindowForm.js и вид каркаса PhonesDirectory.js, куда мы поместим все виды.

Пошаговая разработка веб приложения

SerachPhones.js

Ext.define('PhonesDir.view.SearchPhones', {
    extend: 'Ext.form.Panel',
    alias: 'widget.searchPhones',
    bodyPadding: 10,
    items: [
        {
            xtype: 'textfield',
            name: 'search',
            fieldLabel: 'Введите имя',
            maxLength: 200
        }
    ]
});

PhoneGrid.js

Ext.define('PhonesDir.view.PhonesGrid', {
    extend: 'Ext.grid.Panel',
    alias: 'widget.phonesGrid',
    width: 400,
    height: 300,
    frame: true,
    iconCls: 'icon-user',
    columns: [
        {
            text: 'Имя',
            flex: 1,
            sortable: true,
            dataIndex: 'name'

        },
        {
            flex: 2,
            text: 'Телефон',
            sortable: true,
            dataIndex: 'phone'
        }
    ],
    dockedItems: [
        {
            xtype: 'toolbar',
            items: [
                {
                    text: 'Добавить',
                    action: 'add',
                    iconCls: 'icon-add'
                },
                '-',
                {
                    action: 'delete',
                    text: 'Удалить',
                    iconCls: 'icon-delete',
                    disabled: true
                }
            ]
        }
    ]
});

AddWindowForm.js

Ext.define('PhonesDir.view.AddWindowForm', {
    extend: 'Ext.window.Window',
    alias: 'widget.addWindowForm',
    autoShow: true,
    layout: 'fit',
    modal: true,

    items: [
        {
            bodyPadding: 10,
            xtype: 'form',
            items: [
                {
                    xtype: 'textfield',
                    name: 'name',
                    fieldLabel: 'Имя',
                    regex: /^[А-Я-Ё][а-я-ё]{1,}$/,
                    regexText: 'Имя должно состоять из двух и более букв и начинаться с заглавной буквы',
                    allowBlank: false,
                    blankText: 'Это поле должно быть заполнено'
                },
                {
                    xtype: 'textfield',
                    name: 'phone',
                    fieldLabel: 'Телефон',
                    regex: /^([+][0-9]{11,12})*$/,
                    regexText: 'Телефон должно соответсвовать шаблону "+XXXXXXXXXXX" или "+XXXXXXXXXXXX", где X - цифры. ',
                    allowBlank: false,
                    blankText: 'Это поле должно быть заполнено'
                }
            ]
        }
    ],

    buttons: [
        {
            text: 'Сохранить',
            action: 'save',
            disabled: true
        },
        {
            text: 'Отменить',
            handler: function () {
                this.up('window').close();
            }

        }
    ]
});

PhonesDirectory.js

Ext.define('PhonesDir.view.PhonesDirectory', {
    extend: 'Ext.panel.Panel',
    width: 500,
    height: 360,
    padding: 10,
    alias: 'widget.phonesDirectory',
    layout: 'border',
    items: [
        {
            xtype: 'phonesGrid',
            region: 'center'
        },
        {
            xtype: 'panel',
            html: '<div style="font: normal 18px cursive"><center><font size = "10">Телефонный справочник</font></center></div>',
            region: 'north',
            height: 80
        },
        {
            xtype: 'searchPhones',
            title: 'Поиск',
            region: 'west',
            width: 300,
            collapsible: true,
            collapsed: false
        }
    ],
    renderTo: Ext.getBody()
});

Изменим параметр items в app.js.

     items: {
                xtype: 'phonesDirectory'
            }

  • phonesDirectory' алиас, который мы указали в параметре alias в PhoneDirectory.js.
  • Метод Ext.define('Имя', {параметры}) создает класс-компонент, который может быть унаследован от какого-нибудь компонента. Например в PhoneGrid.js указали extend: 'Ext.grid.Panel', что будет представлять собой таблицу.
Controller

Виды загружаются из контролера, поэтому создадим контролер PhonesDirectoryControoler.js и укажим его в app.js

Пошаговая разработка веб приложения

PhonesDirectoryController.js

Ext.define('PhonesDir.controller.PhonesDirectoryController', {
    extend: 'Ext.app.Controller',

    views: [
        'AddWindowForm',
        'PhonesDirectory',
        'PhonesGrid',
        'SearchPhones'
    ],

    init: function () {
        this.control({

        });
    }
  • С помощью параметра init инициализируются обработчики для компонентов (кнопки, поля и т.д). Связать конпонент с обработчиком помогает функция control.

Конечная структура app.js

Ext.application({
    name: 'PhonesDir',

    appFolder: 'app',

    controllers: [
        'PhonesDirectoryController'
    ],

    launch: function () {
        Ext.create('Ext.container.Viewport', {
            layout: 'fit',
            items: {
                xtype: 'phonesDirectory'
            }
        });
    }
});

Проверим, что всё работает.

Пошаговая разработка веб приложения

Модель и хранилище

Пошаговая разработка веб приложения

PhonesDirectoryModel.js

Ext.define('PhonesDir.model.PhonesDirectoryModel', {
    extend: 'Ext.data.Model',
    fields: ['name', 'phone'],
    proxy: {
        pageParam: 'search',
        type: 'rest',
        api: {
            create: 'phone',
            read: 'phone',
            destroy: 'phone'
        },
        reader: {
            type: 'json',
            root: 'data',
            successProperty: 'success'
        },
        writer: {
            type: 'json',
            writeAllFields: true
        }

    }
});
  • pageParam: 'search' нужен для реализации логики поиска на сервере. В нём будут находится данные из поля поиска и, если не пусто, сервер выполнит выборку данных из БД, иначе вернет все данные.

PhonesDirectoryStore.js

Ext.define('PhonesDir.store.PhonesDirectoryStore', {
    extend: 'Ext.data.Store',
    model: 'PhonesDir.model.PhonesDirectoryModel',
    autoLoad: true,
    autoSync: true,
    proxy: {
        type: 'rest',
        api: {
            create: 'phone',
            read: 'phone',
            destroy: 'phone'
        },
        reader: {
            type: 'json',
            root: 'data',
            successProperty: 'success'
        },
        writer: {
            type: 'json',
            writeAllFields: true
        }

    }
});
  • 'phone' имя, на которое будет замапен java-класс (контролер), который будет обрабатывать GET, POST, DELETE запросы с клиента.

Добавим модель PhonesDirectoryModel.js и хранилище PhonesDirectoryStore.js в контролер PhonesDirectoryController.js

Пошаговая разработка веб приложения

Добавим в PhonesGrid.js параметр store: 'PhonesDirectoryStore', для отображения данных в таблице.

Пошаговая разработка веб приложения

Проверим, что всё работает. 404 (Not Found) — это нормально, так как по адресу localhost:8080/phone еще ничего нет.

Пошаговая разработка веб приложения

Сервер (Java)

Добавьте зависимости в pom.xml

<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.jvm.version>1.7</project.jvm.version>
        <spring.version>3.2.2.RELEASE</spring.version>
        <spring.security>3.1.4.RELEASE</spring.security>
        <slf4j.version>1.5.6</slf4j.version>
        <log4j.version>1.2.17</log4j.version>
        <hibernate.version>4.2.2.Final</hibernate.version>
        <jackson.version>1.9.12</jackson.version>
        <lombok.version>0.11.8</lombok.version>
        <querydsl.version>3.2.0</querydsl.version>
        <springkex.version>0.0.23-SNAPSHOT</springkex.version>
    </properties>


    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>${hibernate.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>2.2.2</version>
        </dependency>
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.4</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>4.3.1.Final</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-jpamodelgen</artifactId>
            <version>1.2.0.Final</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.mysema.querydsl</groupId>
            <artifactId>querydsl-jpa</artifactId>
            <version>${querydsl.version}</version>
        </dependency>
        <dependency>
            <groupId>com.mysema.querydsl</groupId>
            <artifactId>querydsl-apt</artifactId>
            <version>${querydsl.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.codehaus.jackson</groupId>
            <artifactId>jackson-core-asl</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupId>org.codehaus.jackson</groupId>
            <artifactId>jackson-mapper-asl</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.3</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.0.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.3</version>
        </dependency>
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>2.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>${spring.security}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-ldap</artifactId>
            <version>${spring.security}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>${spring.security}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-taglibs</artifactId>
            <version>${spring.security}</version>
        </dependency>
        <dependency>
            <groupId>opensymphony</groupId>
            <artifactId>quartz</artifactId>
            <version>1.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.7.2</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.7.2</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>commons-dbcp</groupId>
            <artifactId>commons-dbcp</artifactId>
            <version>1.4</version>
            <exclusions>
                <exclusion>
                    <artifactId>commons-pool</artifactId>
                    <groupId>commons-pool</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>${log4j.version}</version>
        </dependency>
        <dependency>
            <groupId>com.jolbox</groupId>
            <artifactId>bonecp</artifactId>
            <version>0.7.1.RELEASE</version>
        </dependency>
    </dependencies>

Создадим папку java:

Пошаговая разработка веб приложения

Создадим модель данных и слой доступа к данным (DAO)

Пошаговая разработка веб приложения

Phone.java

@Entity
@Table(name = "phones")
public class Phone implements Serializable {

    @Id
    @Column(name = "ID")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Pattern(regexp = "^[А-Я-Ё][а-я-ё]{1,}$")
    @Column(name = "name")
    private String name;

    @Pattern(regexp = "^([+][0-9]{11,12})([,][+][0-9]{11,12})*$")
    @Column(name = "phone")
    private String phone;

    public Phone() {
    }

    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 String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

}

PhoneDao.java

public interface PhoneDao {

    void add(Phone entity);

    void delete(Phone entity);

    Collection<Phone> getPhones(String search);

    public List findByPhone(String name, String phone);

}

PhoneDaoImpl.java

public class PhoneDaoImpl implements PhoneDao {

    @PersistenceContext
    private EntityManager emf;

    @Override
    public void add(Phone phone) {
        emf.persist(phone);
    }

    @Override
    public void delete(Phone phone) {
        emf.remove(emf.getReference(Phone.class, phone.getId()));
    }

    @Override
    public Collection<Phone> getPhones(String search) {
        if (null == search || search.trim().isEmpty()) {
            return emf.createQuery(
                    "select c from Phone c")
                    .getResultList();
        }
        return emf.createQuery(
                "select c from Phone c where c.name like :search")
                .setParameter("search", search.trim() + "%")
                .getResultList();
    }

    public List<Phone> findByPhone(String name, String phone) {
        return emf.createQuery(
                "select c from Phone c where c.name = :name and c.phone = :phone")
                .setParameter("name", name)
                .setParameter("phone", phone)
                .getResultList();
    }
}
  • Метод getPhones(String search) принимает значение параметра, которого мы указали в PhonesDirectoryModel.js;
  • Метод findByPhone(String name, String phone) используется для поиска дубликата при добавлении данных.

Создадим слой сервиса:

Пошаговая разработка веб приложения

PhoneService.java

public interface PhoneService {

    Boolean add(Phone phone);

    Collection<Phone> getPhones(String search);

    void delete(Phone phone);

}

PhoneServiceImpl.java

public class PhoneServiceImpl implements PhoneService {

    private PhoneDao phoneDao;

    public PhoneDao getPhoneDao() {
        return phoneDao;
    }

    public void setPhoneDao(PhoneDao phonePhoneDao) {
        this.phoneDao = phonePhoneDao;
    }

    @Transactional
    @Override
    public Boolean add(Phone entity) {
        List<Phone> duplicate = phoneDao.findByPhone(entity.getName(), entity.getPhone());
        if (duplicate.isEmpty()) {
            phoneDao.add(entity);
            return true;
        }
        return false;
    }

    @Transactional
    @Override
    public Collection<Phone> getPhones(String search) {
        return phoneDao.getPhones(search);
    }

    @Transactional
    @Override
    public void delete(Phone entity) {
        phoneDao.delete(entity);
    }

}

Создадим контролер, который будет замапен на адрес /phone для обработки запросов с клиента.

Пошаговая разработка веб приложения

PhoneController.java

@Controller
@RequestMapping("/phone")
public class PhoneController {

    @Autowired
    private PhoneService phoneService;


    @RequestMapping(method = RequestMethod.GET)
    @ResponseBody
    public Collection<Phone> getPhone(String search) {
        return phoneService.getPhones(search);
    }

    @RequestMapping(method = RequestMethod.POST)
    @ResponseBody
    public ExtResult setPhone(@RequestBody Phone phone) {
        return new ExtResult(phoneService.add(phone), phone);
    }

    @RequestMapping(value = "{id}", method = RequestMethod.DELETE)
    @ResponseBody
    public String delPhone(@RequestBody Phone phone) {
        phoneService.delete(phone);
        return "del";

    }
}
  • Каждый метод замапен на соответствующий запрос с клиента. Внедряем зависимость с помощью spring аннотации @Autowired и вызывает соответствующие методы у сервиса;
  • ExtResult — вспомогательный класс. Используется для ответа клиенту, что сущность, которую пытаемся записать в БД, дубликат или не дубликат.

ExtResult.java

public class ExtResult {
    private boolean success;
    private Phone data;


    public ExtResult(boolean success, Phone data) {
        this.success = success;
        this.data = data;
    }

    public ExtResult() {
    }

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    public Phone getData() {
        return data;
    }

    public void setData(Phone data) {
        this.data = data;
    }
}

Проверьте, что всё работает. Соберите проект с помощью maven install и запустите приложение.

Создадим spring контекст my-context.xml c:

  • настройками подключения к БД;
  • бином EntityManager — объект, через который происходит взаимодействие с БД. Инжектится в PhoneDaoImpl.java;
  • инжектом объекта класса PhoneDaoImpl.java в объект класса PhoneServiceImpl.java.

Пошаговая разработка веб приложения

my-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jdbc="http://www.springframework.org/schema/jdbc"
       xmlns:aop="http://www.springframework.org/schema/aop" xmlns:p="http://www.springframework.org/schema/p"
       xmlns:tx="http://www.springframework.org/schema/tx"

       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
                           http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.2.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
          p:dataSource-ref="dataSource"
          p:packagesToScan="model"
          p:jpaProperties-ref="jpaProperties"
          p:persistenceProvider-ref="persistenceProvider"/>

    <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"
          depends-on="entityManagerFactory"/>

    <bean id="dataSource" class="com.jolbox.bonecp.BoneCPDataSource" destroy-method="close"
          p:driverClass="com.mysql.jdbc.Driver"
          p:jdbcUrl="jdbc:mysql://localhost:3306/phonedir?characterEncoding=UTF-8"
          p:username="root"
          p:password="1234"
          p:idleConnectionTestPeriodInMinutes="1"
          p:idleMaxAgeInSeconds="240"
          p:minConnectionsPerPartition="2"
          p:maxConnectionsPerPartition="5"
          p:partitionCount="2"
          p:acquireIncrement="1"
          p:statementsCacheSize="100"
          p:releaseHelperThreads="2"
          p:statisticsEnabled="false"/>

    <bean id="persistenceProvider" class="org.hibernate.ejb.HibernatePersistence"/>

    <bean id="jpaProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
        <property name="properties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
                <prop key="connection.pool_size">1</prop>
                <prop key="current_session_context_class">thread</prop>
                <prop key="show_sql">true</prop>
                <prop key="hibernate.hbm2ddl.auto">create</prop>
                <prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
            </props>
        </property>
    </bean>
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"
          p:entityManagerFactory-ref="entityManagerFactory"/>

    <tx:annotation-driven transaction-manager="transactionManager"/>

    <bean name="phoneDao" class="model.dao.impl.PhoneDaoImpl">
    </bean>

    <bean name="PhoneService" class="service.impl.PhoneServiceImpl">
        <property name="phoneDao" ref="phoneDao">
        </property>
    </bean>

</beans>

Создайте БД с названием phonedir или измените название в контексте на своё.

Создадим настройки для spring DispatcherServlet, который будет обрабатывать запросы с клиента.

Пошаговая разработка веб приложения

mvc-dispatcher-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="controllers"/>
    <mvc:view-controller path="/" view-name="/index.jsp"/>
    <mvc:resources mapping="/**" location="/"/>
    <mvc:annotation-driven/>

</beans>
  • context:component-scan поиск и регистрация компонентов в контейнере спринга;
  • mvc:view-controller домашняя страница;
  • mvc:resources автоматически обрабатывать запросы на получение статических данных.

Добавим spring контекст my-context.xml и настройки для spring DispatcherServlet в дескриптор развертывания web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
	http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    <servlet>
        <servlet-name>mvc-dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>mvc-dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath:/my-context.xml
        </param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

</web-app>

Добавим в контролер PhonesDirectoryController.js параметр refs и обработчики для компонентов.

Ext.define('PhonesDir.controller.PhonesDirectoryController', {
    extend: 'Ext.app.Controller',

    views: [
        'AddWindowForm',
        'PhonesDirectory',
        'PhonesGrid',
        'SearchPhones'
    ],

    stores: ['PhonesDirectoryStore'],
    models: ['PhonesDirectoryModel'],

    refs: [
        {selector: 'phonesGrid',
            ref: 'phonesGrid'},
        {selector: 'phonesGrid button[action="add"]',
            ref: 'phonesGridAdd'},
        {selector: 'phonesGrid button[action="delete"]',
            ref: 'phonesGridDelete'},
        {selector: 'searchPhones button[action="search"]',
            ref: 'searchPhones'},
        {selector: 'addWindowForm',
            ref: 'addWindowForm'},
        {selector: 'phonesDirectory',
            ref: 'phonesDirectory'},
        {selector: 'addWindowForm textfield[name=name] ',
            ref: 'addWindowFormName'},
        {selector: 'addWindowForm textfield[name=phone]',
            ref: 'addWindowFormPhone'},
        {selector: 'addWindowForm button[action=save]',
            ref: 'addWindowFormPhoneSave'}
    ],

    init: function () {
        this.control({
            'phonesGrid button[action=add]': {
                click: this.onAddPhone
            },
            'phonesGrid button[action=delete]': {
                click: this.onDelPhone
            },
            'searchPhones textfield[name="search"]': {
                change: this.onChangeText
            },
            'phonesGrid': {
                cellclick: this.onLineGrid
            },
            'addWindowForm button[action=save]': {
                click: this.onSavePhone
            },
            'addWindowForm textfield[name=name]': {
                change: this.onValidation
            },
            'addWindowForm textfield[name=phone]': {
                change: this.onValidation
            }
        });
    },

    onSavePhone: function (button) {
        var me = this;
        var phoneModel = Ext.create('PhonesDir.model.PhonesDirectoryModel');
        phoneModel.set(this.getAddWindowForm().down('form').getValues());
        phoneModel.save({
            success: function (operation, response) {
                var objAjax = operation.data;
                me.getPhonesDirectoryStoreStore().add(objAjax);
                me.getAddWindowForm().close();
            },
            failure: function (dummy, result) {
                Ext.MessageBox.show({
                    title: 'Дубликат!',
                    msg: 'Такое имя и телефон уже существуют',
                    buttons: Ext.Msg.OK,
                    icon: Ext.Msg.ERROR
                });
            }

        });
    },

    onAddPhone: function () {
        Ext.widget('addWindowForm');
    },

    onDelPhone: function () {
        var sm = this.getPhonesGrid().getSelectionModel();
        var rs = sm.getSelection();
        this.getPhonesGrid().store.remove(rs[0]);
        this.getPhonesGrid().store.commitChanges();
        this.getPhonesGridDelete().disable();
    },

    onChangeText: function () {
        this.getPhonesDirectoryStoreStore().load({
            params: {
                search: this.getPhonesDirectory().down('searchPhones').getValues()
            }
        });
    },

    onLineGrid: function () {
        this.getPhonesGridDelete().enable();
    },

    onValidation: function () {
        if (this.getAddWindowFormName().validate() && this.getAddWindowFormPhone().validate()) {
            this.getAddWindowFormPhoneSave().enable();
        } else {
            this.getAddWindowFormPhoneSave().disable();
        }
    }

});
  • ref ссылка на что-то в selector'e.
  • selector указывает на компоненты, для быстро обращения к ним через ref.
  • onSavePhone создается модель данных и сохраняется.
  • onAddPhone создает виджет формы добавления.
  • onDelPhone удаляет запись.
  • onChangeText загружает данные в соответствии со значением в поле поиска.
  • onLineGrid при выделении строки кнопка «Удалить» становится активной
  • onValidation валидация полей формы добавления.

И последнее — добавим иконки к кнопкам «Добавить» и «Удалить»:

Пошаговая разработка веб приложения

index.jsp

<%@page contentType="text/html" pageEncoding="utf-8"%>
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Справочник</title>
    <link rel="stylesheet" type="text/css" href="resources/resources/css/ext-all.css"/>
    <style type="text/css">
        .icon-delete {
            background-image: url(resources/resources/delete.png) !important;
        }

        .icon-add {
            background-image: url(resources/resources/add.png) !important;
        }
    </style>
    <script type="text/javascript" src="resources/ext-all.js"></script>
    <script type="text/javascript" src="app.js"></script>
</head>
<body>
</body>
</html>

Скачать готовый проект.

Автор: tagintsev

Источник

  1. Y24099534:

    Спасибо большое за твой проэкт,очень помог.
    Успехов тебе.

  2. Слава:

    Скажите пожалуйста, а в папке targer что лежит?

  3. Владимир:

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

  4. Вася:

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

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js