Создание страниц и форм сайта на Struts с помощью Webix

Эта статья является продолжением туториала, который рассказывает о том, как разрабатывать веб-сайт с WEBIX и Struts 2.

В этой части вы узнаете о том, как создать страницы «Предстоящие события» и «Контакты», а также о том, как добавить информацию о событиях и затем редактировать ее.

Страница с предстоящими событиями будет выглядеть следующим образом:

страница предстоящих событий

Страница “Предстоящие события”

Начнем с создания файла codebase/tempdata.js с тестовыми событиями:

var events = [{
    id: "1",
    name: "Front-end #1",
    description: "Часть 1: -  Описание задачи. (Coздание сервиса для фотографов Обработка и проявка проприетарных RAW форматов NEF, CR2, DNG, создание редактора фотографий в браузере, Collaboration service на базе WebRTC)",
    date: "2014-06-06",
    location: "Минск, ул.Центральная, д.1, Синий конференц-зал",
    photo: "frontend1.png"
}, {
    id: "2",
    name: "Front-end #2",
    description: "Технический мастер-класс по разработке приложений на JavaScript",
    date: "2014-06-20",
    location: "Минск, ул.Центральная, д.1, Синий конференц-зал",
    photo: "frontend2.png"
}, {
    id: "3",
    name: "Front-end #3",
    description: "Технический мастер-класс по разработке приложений на JavaScript",
    date: "2014-07-04",
    location: "Минск, ул.Центральная, д.1, Синий конференц-зал",
    photo: "frontend3.png"
}, {
    id: "4",
    name: "Front-end #4",
    description: "Технический мастер-класс по разработке приложений на JavaScript",
    date: "2014-07-18",
    location: "Минск, ул.Центральная, д.1, Синий конференц-зал",
    photo: "frontend4.png"
}, {
    id: "5",
    name: "Front-end #5",
    description: "Технический мастер-класс по разработке приложений на JavaScript",
    date: "2014-08-01",
    location: "Минск, ул.Центральная, д.1, Синий конференц-зал",
    photo: "frontend5.png"
}, {
    id: "6",
    name: "JS full-stack - все, что можно делать на JS",
    description: "Технический мастер-класс по разработке приложений на JavaScript",
    date: "2014-08-15",
    location: "Минск, ул.Центральная, д.1, Синий конференц-зал",
    photo: "frontend6.png"
}];

Напоминание! Не забудьте изменить кодировку файла на UTF-8!

Теперь необходимо подключить файл tempdata.js в index.jsp:

<script src="./codebase/tempdata.js" type="text/javascript" charset="utf-8"></script>

Отредактируем конфигурацию контента:

{ cols: [
    { body: {
        view:"list",
        template:"<img src='./photos/#photo#' class='eventpic' /> <h3>#name# (#date#)</h3>#description# <a href='event?eventId=#id#' class='details'>Подробнее</a>",
        css: "eventsList",
        type:{
            height:152
        },
        select:false,
        autoheight: true,
        data: events
    }},
    { header: "Последние доклады", body: {
        template: "Список последних докладов"
    }, width: 409 }
]},

Здесь мы разбили ячейку контента на 2 части: правая часть будет содержать список последних докладов, а центральная часть список предстоящих событий, который мы реализуем с помощью компонента list. Для каждого элемента списка нам нужно будет высоту в 152 пикселя и шаблон, используя свойство template. В нашем примере опция select: false отключает выделение элементов списка. Также важно знать, что в папку photos вам нужно будет добавить изображения для каждого доклада размером 150*150 пикселей.

Теперь в файл myapp.css мы добавим немного правил для оформления списка предстоящих событий:

.eventpic {
    width: 120px;
    height: 120px;
    display: block;
    float: left;
    margin: 7px;
    border: 1px solid #dddddd;
}
.details {
    position: absolute;
    right: 10px;
    bottom: 10px;
}
.eventsList .webix_list_item {
    position: relative;
    padding: 7px;
}
.eventsList .webix_list_item h3 {
    margin: 4px 0px 0px 0px;
}

Затем конфигурацию для списка последних докладов вам нужно будет вложить в файл myapp.js в виде функции getLastSpeakersList:

function getLastSpeakersList() {
    return {
        id:"tweets",
        view:"list",
        template:"<img src='./photos/#photo#' class='eventpic' /><span class='speakerHeader'>#author# - #topic#</span><br><span class='speakerDetails'>#description#</span>",
        type:{
            height:190
        },
        select:false,
        data: lastSpeakers
    };
}

В файл tempdata.js внесем временные данные для списка последних докладов:

var lastSpeakers = [{
    "id":6,
    "event_id":1,
    "author":"Капитан Америка",
    "photo":"captainamerica.jpg",
    "description":"HTML-импорт - это способ включать одни HTML документы в другие. Вы не ограничиваетесь только разметкой, вы можете также включать CSS, JavaScript или что угодно, что может содержаться в .html файле.",
    "topic":"HTML-импорт"
},{
    "id":5,
    "event_id":1,
    "author":"Бэтмэн",
    "photo":"batman.jpg",
    "description":"Введение в antialiasing, объяснение, как отображать векторные фигуры и текст красиво.",
    "topic":"AntiAliasing. Начало."
},{
    "id":4,
    "event_id":2,
    "author":"Тор",
    "photo":"thor.jpg",
    "description":"Глубокое погружение в быструю анимацию в ваших проектах. Мы узнаем, почему современные браузеры могут легко анимировать следующие характеристики: позиция, масштаб, поворот и прозрачность.",
    "topic":"Высокопроизводительная анимация"
}];

В папке photos в этом примере мы разместим изображения 120*120 пикселей для докладов: batman.jpg, captainamerica.jpg, halk.jpg, ironman.jpg, spiderman.jpg, thor.jpg.

Теперь пришло время добавить в myapp.css стили для списка докладов:

.speakerHeader {
    line-height: 20px;
    font-weight: bold;
}
.speakerDetails {
    line-height: 20px;
    text-overflow: ellipsis;
}

В конце нам остается поправить конфигурацию в файле index.jsp:

{ header: "Последние доклады", body: getLastSpeakersList(), width: 409 }

Запускаем сервер, и открываем в браузере страницу http://localhost:8080/MyApp/index.action. Страница предстоящих событий уже готова.

страница с предстоящими событиями

Эту же страницу будем отображать по адресу upcoming для удобства именования пунктов меню. Для этого добавьте соответствующие настройки в файл struts.xml:

<package name="defaultpages" namespace="/" extends="struts-default">
    <action name="index">
        <result>/index.jsp</result>
    </action>
    <action name="upcoming">
        <result>/index.jsp</result>
    </action>
    …

Перезапускаем сервер. Теперь по адресу http://localhost:8080/MyApp/upcoming у вас будет доступна главная страница сайта.

Страница “Контакты”

Вначале нам нужно добавить маппинг в файл struts.xml:

<package name="defaultpages" namespace="/" extends="struts-default">
    ...
    <action name="contacts">
        <result>/contacts.jsp</result>
    </action>
</package>

Далее мы создадим файл src/main/webapp/contacts.jsp, который отвечает за отображение страницы контактов:

<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
    <title>Webix - Front-End события</title>
    <link rel="shortcut icon" href="favicon.ico" />
    <link rel="stylesheet" href="./codebase/webix.css" type="text/css" media="screen" charset="utf-8">
    <link rel="stylesheet" href="./codebase/myapp.css" type="text/css" media="screen" charset="utf-8">
    <script src="./codebase/webix.js" type="text/javascript" charset="utf-8"></script>
    <script src="./codebase/myapp.js" type="text/javascript" charset="utf-8"></script>
    <script src="./codebase/tempdata.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
    <jsp:include page="header.jsp" />
    <jsp:include page="footer.jsp" />
    <div id="contacts" style="display: none;">
        <span class='webix_icon fa-envelope-o'></span>
        <a href="mailto:dmitry.radyno@gmail.com">dmitry.radyno@gmail.com</a><br>
        <span class='webix_icon fa-phone-square'></span> +375 29 123-45-67<br>
        <span class='webix_icon fa-twitter'></span>
        <a href="https://twitter.com/radyno">@radyno</a><br>
        <span class='webix_icon fa-facebook'></span>
        <a href="https://www.facebook.com/dmitry.radyno">Dmitry Radyno</a><br>
    </div>
    <div id='myapp'></div>

    <script type="text/javascript" charset="utf-8">
        webix.ui({
            container:"myapp",
            cols: [{}, {
                width: 1280,
                rows: [
                    {
                        height: 250,
                        borderless:true,
                        cols: [{
                            rows: [
                                { view: "template", template: "html->header", css: "header", borderless: true},
                                getTopMenu("contacts")
                            ]},
                            getPhotos()
                        ]},
                        { height: 400, cols: [
                            { template: "html->contacts", borderless: true, css: "contacts" },
                            { header: "Последние доклады", body: getLastSpeakersList(), width: 409 }
                        ]},
                        getFooter()
                ]
            }, {}]
        });
        </script>
</body>
</html>

Теперь в файл src/main/weapp/codebase/myapp.css вставим стили для страницы контактов:

contacts {
    padding: 40px 40px 40px 72px;
    box-sizing: border-box;
    font-size: 20px;
    line-height: 30px;
}
.contacts .webix_icon {
    font-size: 23px;
    color: #580B1C;
    text-align: center;
}
.contacts a {
    color: #580B1C;
}

Открываем в браузере страницу http://localhost:8080/MyApp/contacts:

страница с контактами

Информация о предстоящем событии

Для отображения информации о предстоящем событии будем использовать страницу event. Идентификатор события передается как параметр eventId в URL страницы: http://localhost:8080/MyApp/event?eventId=1

Настроим маппинг в файле struts.xml. Не забывайте учитывать то, что контент данной страницы будет зависеть от параметра eventId — а значит просто указать результирующее представление будет недостаточно. Необходимо поискать подходящее событие с указанным идентификатором в базе данных. Этим будет заниматься класс EventAction:

<action name="event" class="com.myapp.action.EventAction" method="getEventById">
    <result>/event.jsp</result>
</action>

Здесь мы указали, что для генерирования динамического содержимого страницы будет использоваться класс com.myapp.action.EventAction и метод getEventById, при этом файл event.jsp будет заниматься отображением данных.

Создаем пакет com.myapp.model и в нем класс Event, который представляет собой предстоящее событие:

package com.myapp.model;
import java.util.Date;
import org.apache.struts2.json.annotations.JSON;

public class Event {
    private Long id;
    private String name;
    private String description;
    private Date date;
    private String location;
    private String photo;
   
    public Event() {}
    public Event(Long id, String name, String description, Date date, String location, String photo) {
        this.id = id;
        this.name = name;
        this.description = description;
        this.date = date;
        this.location = location;
        this.photo = photo;
    }

    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 getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }

    @JSON(format = "yyyy-MM-dd")
    public Date getDate() {
        return date;
    }
    public void setDate(Date date) {
        this.date = date;
    }

    public String getLocation() {
        return location;
    }
    public void setLocation(String location) {
        this.location = location;
    }

    public String getPhoto() {
        return photo;
    }
    public void setPhoto(String photo) {
        this.photo = photo;
    }
}

Теперь нужно создать пакет com.myapp.action и в нем класс EventAction, унаследованный от класса ActionSupport:

пакет com.myapp.action

Класс EventAction отвечает за выполнение действий над данными: сюда приходят параметры запроса, здесь осуществляется поиск необходимых записей, здесь принимается решение о том, какой файл представления использовать.

package com.myapp.action;
import java.util.Date;
import com.myapp.model.Event;
import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ActionSupport;

public class EventAction extends ActionSupport {
    private String eventId;
    private Event event = null;

    public String getEventById() {
        if (eventId != null) {
            event = new Event(new Long(1), "Front-end #1", "Часть 1: -  Описание задачи. (Coздание сервиса для фотографов. Обработка и проявка проприетарных RAW форматов NEF, CR2, DNG, созданиe редактора фотографий в браузере, Collaboration service на базе WebRTC)", new Date(), "Минск, ул.Центральная, д.1, Синий конференц-зал", "frontend1.png");
            return Action.SUCCESS;
        } else {
            return Action.ERROR;
        }
    }
    public Event getEvent() {
        return event;
    }
    public void setEvent(Event event) {
        this.event = event;
    }
}

На данный момент у нас нет интеграции с базой данных, поэтому вне зависимости от запрошенного события, будет возвращаться всегда один и тот же статический объект. В дальнейшем мы изменим метод getEventById таким образом, чтобы он возвращал из базы данных запись с идентификатором eventId. Пока же создадим файл event.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
    <title>Webix - Front-End события</title>
    <link rel="shortcut icon" href="favicon.ico" />
    <link rel="stylesheet" href="./codebase/webix.css" type="text/css" media="screen" charset="utf-8">
    <link rel="stylesheet" href="./codebase/myapp.css" type="text/css" media="screen" charset="utf-8">
    <script src="./codebase/webix.js" type="text/javascript" charset="utf-8"></script>
    <script src="./codebase/myapp.js" type="text/javascript" charset="utf-8"></script>
    <script src="./codebase/tempdata.js" type="text/javascript" charset="utf-8"></script>s
</head>
<body>
    <div id="header" style="display: none;">
        <div class="confname"><s:property value="event.name" /></div>
        <div class="conftime"><s:date name="event.date" format="dd/MM/yyyy" /> - <s:property value="event.location" /></div>
        <div class="confdsc"><s:property value="event.description" /></div>
    </div>
    <jsp:include page="footer.jsp" />
    <div id='myapp'></div>

    <script type="text/javascript" charset="utf-8">
        webix.ui({
            container:"myapp",
            cols: [{}, {
                width: 1280,
                rows: [
                    {
                        height: 250,
                        borderless:true,
                        cols: [{
                            rows: [
                                { view: "template", template: "html->header", css: "header", borderless: true },
                                getTopMenu()
                            ]
                        },
                        getPhotos()
                    ]},
                    { cols: [
                        { body: {
                            view:"list",
                            template:"<img src='./photos/#photo#' class='userpic' /> <h3>#author#: #topic#</h3>#description#",
                            css: "speakersList",
                            type:{
                                height:170
                            },
                            select:false,
                            minHeight: 400,
                            autoheight: true,
                            data: speakers
                        } },
                        { header: "Последние доклады", body: getLastSpeakersList(), width: 409 }
                    ]},
                    getFooter()
                ]
            }, {}]
        });
    </script>
</body>
</html>

В файл tempdata.js вложим тестовые данные для списка докладов:

var speakers = [{
    "id":1,
    "event_id":1,
    "author":"Железный человек",
    "topic":"JavaScript Promises - Туда и обратно",
    "description":"Одна из новых особенностей которые нам готовят разработчики браузеров совместно с группами разработчиков пишущих спецификации — JavaScript Promises — полюбившийся многим шаблон написания асинхронного кода обзаводится нативной поддержкой. Что же такое обещания и с чем их едят?",
    "photo":"ironman.jpg"
},{
    "id":3,
    "event_id":1,
    "author":"Человек-паук",
    "topic":"Использование вашего терминала в DevTools",
    "description":"DevTools Terminal - это новое расширение для браузера Chrome, которое организует работу командной строки прямо в вашем браузере.",
    "photo":"spiderman.jpg"
},{
    "id":5,
    "event_id":1,
    "author":"Бэтмэн",
    "topic":"AntiAliasing. Начало.",
    "description":"Введение в antialiasing, объяснение, как отображать отображать векторные фигуры и текст красиво.",
    "photo":"batman.jpg"
},{
    "id":6,
    "event_id":1,
    "author":"Капитан Америка",
    "topic":"HTML-импорт",
    "description":"HTML-импорт - это способ включать одни HTML документы в другие. Вы не ограничиваетесь только разметкой, вы можете также включать CSS, JavaScript или что угодно, что может содержаться в .html файле.",
    "photo":"captainamerica.jpg"
}];

Также добавим правила в myapp.css:

.conftime {
    font-size: 26px;
    float: right;
    text-align: right;
    width: 420px;
}

.userpic {
    width: 150px;
    height: 150px;
    display: block;
    float: left;
    margin: 7px;
}

Запускаем сервер, открываем страницу http://localhost:8080/MyApp/event?eventId=1. Страница конференции готова!
страница конференции с Webix и  Struts

Управление событиями и докладами

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

Для начала добавим соответствующую настройку в файл struts.xml с привязкой адреса “add” к представлению “add.jsp”:

<action name="add">
    <result>/add.jsp</result>
</action>

Создадим файл add.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Webix - Front-End события</title>
    <link rel="shortcut icon" href="favicon.ico" />
    <link rel="stylesheet" href="./codebase/webix.css" type="text/css" media="screen" charset="utf-8">
    <link rel="stylesheet" href="./codebase/myapp.css" type="text/css" media="screen" charset="utf-8">
    <script src="./codebase/webix.js" type="text/javascript" charset="utf-8"></script>
    <script src="./codebase/myapp.js" type="text/javascript" charset="utf-8"></script>
    <script src="./codebase/tempdata.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
    <jsp:include page="header.jsp" />
    <jsp:include page="footer.jsp" />
    <div id='myapp'></div>

    <script type="text/javascript" charset="utf-8">
    webix.ui({
            container:"myapp",
            cols: [{}, {
                width: 1280,
                rows: [
                    {
                        height: 250,
                        borderless:true,
                        cols: [{
                            rows: [
                                { view: "template", template: "html->header", css: "header", borderless: true},
                                getTopMenu()
                            ]
                        },
                        getPhotos()
                    ]},
                    { cols: [
                        {
                            id: "events",
                            view:"datatable",
                            columns:[
                                { id:"date", header:"Дата", width:80 },
                                { id:"name", header:"Название", fillspace: true },
                                { id:"location",header:"Место проведения", width:400 },
                                { id:"edit",header:"", width: 34, template: "<span class='webix_icon fa-edit editEvent control'></span>"},
                                { id:"remove", header:"<span class='webix_icon fa-plus bigControl'></span>", width: 34, template: "<span class='webix_icon fa-trash-o removeEvent control'></span>"}
                            ],
                            autoheight:true,
                            select:"row",
                            data: events
                        },
                        { width: 10 },
                        {
                            id: "speakers",
                            view:"datatable",
                            columns:[
                                { id:"author", header:"Автор", width:150 },
                                { id:"topic", header:"Тема", width:300 },
                                { id:"edit",header:"", width: 34, template: "<span class='webix_icon fa-edit editSpeaker control'></span>"},
                                { id:"remove", header:"<span class='webix_icon fa-plus bigControl'></span>", width: 34, template: "<span class='webix_icon fa-trash-o removeSpeaker control'></span>"}
                            ],
                            select: "row",
                            autoheight:true,
                            autowidth:true,
                            datatype: "json"
                        }
                    ]},
                    getFooter()
                ]
            }, {}]
        });

        $$("speakers").attachEvent("onAfterLoad", function(){
            var firstId = $$("events").getFirstId();
            if (firstId) {
                $$("events").select(firstId);
            }
        });

        $$("speakers").bind($$("events"), function(slave, master){
            if (!master) return false;
            return master.id == slave.event_id;
        });

        $$("speakers").parse(speakers);
</script>

</body>
</html>

десь мы создали два элемента с типом datatable. Этот элемент представляет собой таблицу, для которой можно задать список полей. Эти поля нужно будет отобразить. Таблицы в Webix поддерживают операцию байндинг — отображение данных в подчиненной таблице в зависимости от выделенной записи в мастер-таблице. В нашем случае мастер таблицей является список событий, а подчиненной таблицей — список докладов.
Для связывания таблиц используется следующий код:

  $$("speakers").bind($$("events"), function(slave, master){
            if (!master) return false;
            return master.id == slave.event_id;
        });

Такая конструкция указывает, что таблицу speakers надо привязать к таблице events и отображать только те записи, для которых callback-функция вернет true. В нашем случае это будут записи у которых speaker.event_id равно event.id.

В файл myapp.css добавим стили для оформления иконок добавления, редактирования и удаления:

.control {
    padding-top: 6px;
    cursor: pointer;
}
.bigControl {
    cursor: pointer;
    color: #ffffff;
    font-size: 20px;
}

Давайте сделаем эту страницу более интерактивной. Для начала сделаем возможным удаление уже существующих событий. Для этого добавим событие onClick для иконки удаления с CSS-классом removeEvent:

{
    id: "events",
    view:"datatable",
    columns:[
        { id:"date", header:"Дата" , width:80 },
        { id:"name",header:"Название", fillspace: true },
        { id:"location",header:"Место проведения",   width:400 },
        { id:"edit",header:"", width: 34, template: "<span class='webix_icon fa-edit editEvent control'></span>"},
        { id:"remove", header:"<span class='webix_icon fa-plus bigControl'></span>", width: 34, template: "<span class='webix_icon fa-trash-o removeEvent control'></span>"}
    ],
    onClick: {
        removeEvent: removeEventClick
    },
    autoheight:true,
    select:"row",
    data: events
},

Ниже добавим функцию removeEventClick, которая будет удалять текущий выделенный элемент из списка. Делается это очень просто, потому что Webix DataTable предоставляет очень удобное API для обработки данных:

function removeEventClick(ev, id, html){
    var event = this.getItem(id.row),
        self = this;

    webix.confirm({
        title:"Удаление события",
        ok:"Да, удалить",
        cancel:"Нет, оставить",
        text:"Вы уверены, что хотите удалить событие " + event.name + "?",
        callback: function(result) {
            if (result) {
                self.remove(id);
            }
        }
    });
}

Чтобы удаление не было случайным, мы используем диалог подтверждения webix.confirm. После выбора любого варианта вызывается функция callback, аргументом которой является выбор пользователя.
В нашем случае если пользователь подтверждает, что надо удалить запись, то вызывается функция remove, которая удаляет запись из таблицы.

предупреждающее окно

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

Для добавления/редактирования записи будем показывать пользователю отдельную форму в модальном окне. По нажатию кнопки “Сохранить” изменения добавятся в таблицу событий.
Форму редактирования оформим как отдельную функцию. Первый параметр — это объект события (изменение существующего события) либо null (создание нового события). Второй параметр — это функция, которая будет вызвана после того, как пользователь заполнил поля и нажал кнопку “Сохранить”. Создадим файл codebase/eventForm.js:

function editEvent(value, callback) {

    var saveEvent = function() {
        var newValue = $$("eventForm").getValues();
        $$("eventForm").clear();
        $$("eventWindow").close();
        callback(newValue);
    };
    var discardChanges = function() {
        $$("eventForm").clear();
        $$("eventWindow").close();
    };

    webix.ui({
        id: "eventWindow",
        view:"window",
        height:420,
        width:400,
        position:"center",
        modal: true,
        head: "Редактирование события",
        body:{
            rows: [{
                id: "eventForm",
                view:"form",
                elements:[
                    { view:"text", name:"name", label:"Название"},
                    { view:"datepicker", name:"date", label:"Дата", stringResult: true },
                    { view:"text", name:"location", label:"Адрес "},
                    { view:"text", name:"photo", label:"Изображение"},
                    { view:"textarea", name:"description",height:170, label:"Описание", labelPosition:"top" }
                ],
                rules:{
                    name: webix.rules.isNotEmpty,
                    description: webix.rules.isNotEmpty
                }
            }, { view:"toolbar", cols:[
                {},
                { view:"button", label:"Сохранить", type:"form", click:saveEvent, width: 200 },
                { view:"button", label:"Отмена", click:discardChanges, width: 200 },
                {}
            ]}]
        }
    }).show();

    if (value) {
        $$("eventForm").setValues(value);
    }
}

Теперь подключим этот файл на странице add.jsp:

<script src="./codebase/eventForm.js" type="text/javascript" charset="utf-8"></script>

Добавим onclick-обработчики на иконки добавления и редактирования события:

{
    id: "events",
    view:"datatable",
    columns:[
        { id:"date",    header:"Дата" , width:80 },
        { id:"name",    header:"Название", fillspace: true },
        { id:"location",header:"Место проведения",   width:400 },
        { id:"edit",header:"", width: 34, template: "<span class='webix_icon fa-edit editEvent control'></span>"},
        { id:"remove", header:"<span class='webix_icon fa-plus bigControl' onclick='addEventClick();'></span>", width: 34, template: "<span class='webix_icon fa-trash-o removeEvent control'></span>"}
    ],
    onClick: {
        removeEvent: removeEventClick,
        editEvent: editEventClick
    },
    autoheight:true,
    select:"row",
    data: events
},

Определим функции, которые будут вызывать форму для редактирования или добавления события:

function addEventClick() {
    editEvent(null, function(newValue) {
        $$("events").add(newValue);
    });
}
function editEventClick(ev, id, html){
    editEvent(this.getItem(id.row), webix.bind(function(newValue) {
        this.updateItem(newValue.id, newValue);
    }, this));
}

Открываем в браузере http://localhost:8080/MyApp/add и пробуем добавить новое событие или отредактировать существующее:
возможность добавить новое событие или отредактировать существующее

Аналогично добавляем форму для редактирования/добавления нового доклада. Однако при создании доклада нам нужно передать содержащий идентификатор события объект, чтобы привязать новый доклад к выбранному в данный момент событию.

speakerForm.js:

function editSpeaker(value, callback) {

    var saveSpeaker = function() {
        var newValue = $$("speakerForm").getValues();
        $$("speakerForm").clear();
        $$("speakerWindow").close();
        callback(newValue);
    };
    var discardChanges = function() {
        $$("speakerForm").clear();
        $$("speakerWindow").close();
    };

    webix.ui({
        id: "speakerWindow",
        view:"window",
        height:420,
        width:400,
        position:"center",
        modal: true,
        head: "Редактирование доклада",
        body:{
            rows: [{
                id: "speakerForm",
                view:"form",
                elements:[
                    { view:"text", name:"author", label:"Автор"},
                    { view:"text", name:"topic", label:"Название"},
                    { view:"text", name:"photo", label:"Фотография"},
                    { view:"textarea", name:"description",height:200, label:"Описание", labelPosition:"top" }
                ],
                rules:{
                    name: webix.rules.isNotEmpty,
                    description: webix.rules.isNotEmpty
                }
            }, { view:"toolbar", cols:[
                {},
                { view:"button", label:"Сохранить", type:"form", click:saveSpeaker, width: 200 },
                { view:"button", label:"Отмена", click:discardChanges, width: 200 },
                {}
            ]}]
        }
    }).show();

    if (value) {
        $$("speakerForm").setValues(value);
    }
}

add.jsp:

...
<script src="./codebase/speakerForm.js" type="text/javascript" charset="utf-8"></script>

{
    id: "speakers",
    view:"datatable",
    columns:[
        { id:"author", header:"Автор", width:150 },
        { id:"topic", header:"Тема", width:300 },
        { id:"edit",header:"", width: 34, template: "<span class='webix_icon fa-edit editSpeaker control'></span>"},
        { id:"remove", header:"<span class='webix_icon fa-plus bigControl' onclick='addSpeakerClick();'></span>", width: 34, template: "<span class='webix_icon fa-trash-o removeSpeaker control'></span>"}
    ],
    onClick: {
        removeSpeaker: removeSpeakerClick,
        editSpeaker: editSpeakerClick
    },
    select: "row",
    autoheight:true,
    autowidth:true
}



function removeSpeakerClick(ev, id, html){
    var topic = this.getItem(id.row),
        self = this;

    webix.confirm({
        title:"Удаление доклада",
        ok:"Да, удалить",
        cancel:"Нет, оставить",
        text:"Вы уверены, что хотите удалить доклад '" + topic.topic + "'?",
        callback: function(result) {
            if (result) {
                self.remove(id);
            }
        }
    });
}
function editSpeakerClick(ev, id, html){
    editSpeaker(this.getItem(id.row), webix.bind(function(newValue) {
        this.updateItem(newValue.id, newValue);
    }, this));
}
function addSpeakerClick() {
    editSpeaker({
        event_id: $$('events').getSelectedId()
    }, function(newValue) {
        $$("speakers").add(newValue);
    });
}

редактирование события на сайте webix и  struts

Выводы

Во второй части туториала мы добавили в наш сайт страницы “Контакты” и “Предстоящие события”, а также сделали возможным добавление новых событий, и их редактирование.
Все это было реализовано благодаря использованию JavaScript UI библиотеки UI-компонетов Webix и java-фреймворку Struts.
Вы можете скачать получившийся проект по этой ссылке.

Оставшиеся операции с сайтом будут описаны в следующей статье в блоге.