СоХабр закрыт.

С 13.05.2019 изменения постов больше не отслеживаются, и новые посты не сохраняются.

H Разработка веб-приложения в черновиках Из песочницы Tutorial

Привет, Хабр!

При изучении технологий Ext JS и Java, написал web-приложение «Каталог автомобилей». Хочу поделиться с вами этим «шедевром».

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


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









Инструменты


  1. IntelliJ IDEA 13 скачать
  2. Ext JS 5.0.1 скачать
  3. Apache Tomcat 8.0.12 скачать
  4. MySQL 5.6.20 скачать
  5. Apache Maven 3.0.5 скачать
  6. Java 1.8.0_20 скачать
  7. Java doc читать
  8. Ext JS Guides читать
  9. Hibernate читать
  10. JPA читать
  11. Spring читать
  12. SQL читать
  13. MVC читать
  14. DAO читать
  15. 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:


app.js
Ext.application({
    name: 'CarCatalog',
    launch: function () {
        Ext.create('Ext.container.Viewport', {
            layout: 'fit',
            items: {
                xtype: 'panel',
                html: '<h2>Каталог автомобилей</h2>'
            }
        });
    }
});

  • Метод Ext.application инициализирует приложение Ext JS;
  • name: 'CarCatalog' указывает имя приложения, которое будет затем использоваться для создания полных имен классов приложения;
  • launch: function(){} тут происходит создание приложения;

В файле index.jsp подключаем стили Ext JS, затем фреймворк Ext JS и только потом 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/packages/ext-theme-neptune/build/resources/ext-theme-neptune-all.css"/>
    <script type="text/javascript" src="resources/ext-all.js"></script>
    <script type="text/javascript" src="resources/packages/ext-theme-neptune/build/ext-theme-neptune.js"></script>
    <script type="text/javascript" src="app.js"></script>
</head>
<body>
</body>
</html>


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


View

Понадобится четыре вида — это вид поиска SearchCarView.js, вид таблицы CarGridView.js, вид формы добавления данных AddCarFormView.js и вид каркаса CarCatalogView.js, куда поместим все виды.


SerachCarView.js
Ext.define('CarCatalog.view.SearchCarView', {
    extend: 'Ext.form.Panel',
    alias: 'widget.searchCarView',
    bodyPadding: 10,
    items: [
        {
            xtype: 'textfield',
            name: 'search',
            fieldLabel: 'Введите название модели',
            maxLength: 200
        }
    ]
});


CarGridView.js
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: /^([0-9]{1,20})*$/,
                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
                }
            ]
        }
    ]
});


AddCarFormView.js
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: /^([0-9]{1,20})*$/,
                    regexText: 'Цена должна состоять из цифр',
                    allowBlank: false,
                    blankText: 'Это поле должно быть заполнено'
                }
            ]
        }
    ],

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

        }
    ]
});


CarCatalogView.js
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: '<div style="font: normal 18px cursive"><center><font size = "10">Каталог автомобилей</font></center></div>',
            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', что будет представлять собой таблицу;

Укажим виды в app.js:
Ext.application({
    name: 'CarCatalog',

    views: [
        'AddCarFormView',
        'CarCatalogView',
        'CarGridView',
        'SearchCarView'
    ],

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

  • 'carCatalogView' алиас, который указали для вида CarCatalogView.js

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


Controller




CarCatalogController.js
Ext.define('CarCatalog.controller.CarCatalogController', {
    extend: 'Ext.app.Controller',

    init: function () {
        this.control({

        });
    }

});

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


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



CarCatalogModel.js
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
        }

    }
});


CarCatalogStore.js
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:
Ext.application({
    name: 'CarCatalog',

    views: [
        'AddCarFormView',
        'CarCatalogView',
        'CarGridView',
        'SearchCarView'
    ],

    controllers : [
        'CarCatalogController'
    ],

    stores : [
        'CarCatalogStore'
    ],

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


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


Сервер (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):


Car.java
@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;
    }

}


CarDao.java
public interface CarDao {

    void add(Car car);

    void update(Car car);

    void delete(Car car);

    Collection<Car> getCars(String search);

    public List findByCar(String name, Long price);

}


CarDaoImpl.java
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<Car> 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<Car> 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) используется для поиска дубликата при добавлении данных;

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


CarService.java
public interface CarService {

    Boolean add(Car car);

    void update(Car car);

    Collection<Car> getCars(String search);

    void delete(Car car);

}


CarServiceImpl.java
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) {
        List<Car> duplicate = 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<Car> getCars(String search) {
        return carDao.getCars(search);
    }

    @Transactional
    @Override
    public void delete(Car car) {
        carDao.delete(car);
    }

}


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


CarController.java
@Controller
@RequestMapping("/car")
public class CarController {

    @Autowired
    private CarService carService;


    @RequestMapping(method = RequestMethod.GET)
    @ResponseBody
    public Collection<Car> getCars(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 = "{id}", method = RequestMethod.DELETE)
    @ResponseBody
    public String deleteCar(@RequestBody Car car) {
        carService.delete(car);
        return "delete";
    }

    @RequestMapping(value = "{id}", method = RequestMethod.PUT)
    @ResponseBody
    public String updateCar(@RequestBody Car car) {
        carService.update(car);
        return "update";
    }
}


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


ExtResult.java
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;



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/CarCatalog?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="carDao" class="model.dao.impl.CarDaoImpl">
    </bean>

    <bean name="CarService" class="service.impl.CarServiceImpl">
        <property name="carDao" ref="carDao">
        </property>
    </bean>

</beans>


Создайте БД с названием CarCatalog и кодировкой utf_general_ci или измените название в контексте my-context.xml на своё.

Создадим настройки для 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>


Добавим в контролер CarCatalogController.js параметр refs и обработчики для компонентов:
Ext.define('CarCatalog.controller.CarCatalogController', {
    extend: 'Ext.app.Controller',

    refs: [
        {selector: 'carGridView',
            ref: 'carGridView'},
        {selector: 'carGridView button[action="add"]',
            ref: 'carGridAdd'},
        {selector: 'carGridView button[action="delete"]',
            ref: 'carGridDelete'},
        {selector: 'searchCarView button[action="search"]',
            ref: 'searchCar'},
        {selector: 'addCarFormView',
            ref: 'addCarFormView'},
        {selector: 'carCatalogView',
            ref: 'carCatalogView'},
        {selector: 'addCarFormView textfield[name=name] ',
            ref: 'addCarFormName'},
        {selector: 'addCarFormView textfield[name=price]',
            ref: 'addCarFormPrice'},
        {selector: 'addCarFormView button[action=save]',
            ref: 'addCarFormSave'}
    ],

    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 валидация полей формы добавления;

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


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/packages/ext-theme-neptune/build/resources/ext-theme-neptune-all.css"/>
    <script type="text/javascript" src="resources/ext-all.js"></script>
    <script type="text/javascript" src="resources/packages/ext-theme-neptune/build/ext-theme-neptune.js"></script>
    <script type="text/javascript" src="app.js"></script>
    <style type="text/css">
        .icon-delete {
            background-image: url(resources/delete.png) !important;
        }
        .icon-add {
            background-image: url(resources/add.png) !important;
        }
    </style>
</head>
<body>
</body>
</html>


Готовый проект скачать


–5
3246

комментарии (9)

+2
+3 –1
fenst ,  
В следуюшей версии:
— делать пробелы между цифрами (89035050000 -> 8 903 505 0000)
— сделать шрифт и кнопки покрупнее
— в целях оптимизации использования места, выводить данные в 2 столбца (имя, телефон; имя, телефон)
— трансформировать панель поиска из столбца в строку, чтобы она не съедала 1\3 ширину экрана
— добавить категории (отделы, подразделения, города)
— добавить дополнительные поля (городское телефон, второй мобильный телефон)
— добавить подсказку по написнаию имени (Петров Василий или Василий Петров?)
— убрать гигантскую надпись «телефонный справочник», добавить версию программы, добавить контакты разработчика
— добавить статистику: сколько контактов в базе, какая дата её последнего изменения

Но не воспринимайте это как критику, пожалуста. Просто было самому интересно подумать, как можно улучшить ваше творенье.
+2
+3 –1
alterpub ,   * (был изменён)
А затем нужно открыть справочник на мобиле, планшете, нетбуке, ноутбуке с небольшим дисплеем ~13" и разшением 1280х800 и понять что это кровь, кишки, расчлененка =)
+5
script88 ,  
Как мне кажется, без Extjs было бы на много лучше.
+1
jonic ,  
Работал я как то в одной конторке, тоже в качестве клиентской библиотеки использовали Ext.JS. Ну и запарился я с ней, и со вторым новым программистом, который почему то считал своей обязанностью спросить все у меня. Хотя я на день раньше него только узнал что то из Ext.JS. К слову контора эта обанкротилась, не долго просуществовав после моего ухода.

Но из того что знаю, то Ext.JS используется в качестве интерфейса(сильно переработанная) на площадке медицинской по РТ. Но правда у них часто там что то отваливалось. Если тут есть ребята с «барс групп», то может напомнят :)
+5
+6 –1
igordata ,  
Дошкольники благодарны за книжку с картинками!
+1
romario13 ,  
Спасибо автору.

Хороший пост для первого раза. Творческих вам успехов!
+1
tagintsev ,  
Спасибо!
0
+1 –1
Loriowar ,  
Простите, но вид статьи вгоняет в уныние и вызывает панику.
0
getauft ,  
Напомнило лабараторные работы которые нам подсовывли в колледже… Не удивлюсь, если ваша пошаговая инструкция в скором будущем будет распечатана без каких либо изменений и роздана студентам младших курсов… Мне всегда хотелось к таким лабараторным первым пунктом подписать: «Перед началом хардкор копи-паст необходимо выключить мозг»…