This article is the continuation of the previous blog post that refers to developing a basic website functionality with Webix and Struts 2.
In this part you’ll know how to create the pages “Forthcoming Events” and “Contacts” as well as how to add the information about events and then manage it.
The page with coming events will look this way:
Page “Forthcoming Events”
Firstly we will create the file codebase/tempdata.js that will contain test events.
id: "1",
name: "Front-end #1",
description: "Part 1: - Task description. (Creating a service for photographers. Processing and developing of proprietary RAW formats: NEF, CR2, DNG, creating a photo editor in a browser, Collaboration service based on WebRTC)",
date: "2014-06-06",
location: "Minsk, Centralnaya str., 1, Blue conference hall",
photo: "frontend1.png"
}, {
id: "2",
name: "Front-end #2",
description: "Technical master class: Developing JavaScript applications",
date: "2014-06-20",
location: "Minsk, Centralnaya str., 1, Blue conference hall",
photo: "frontend2.png"
}, {
id: "3",
name: "Front-end #3",
description: "Technical master class: Developing JavaScript applications",
date: "2014-07-04",
location: "Minsk, Centralnaya str., 1, Blue conference hall",
photo: "frontend3.png"
}, {
id: "4",
name: "Front-end #4",
description: "Technical master class: Developing JavaScript applications",
date: "2014-07-18",
location: "Minsk, Centralnaya str., 1, Blue conference hall",
photo: "frontend4.png"
}, {
id: "5",
name: "Front-end #5",
description: "Technical master class: Developing JavaScript applications",
date: "2014-08-01",
location: "Minsk, Centralnaya str., 1, Blue conference hall",
photo: "frontend5.png"
}, {
id: "6",
name: "JS full-stack - all the things you can create with JS",
description: "Technical master class: Developing JavaScript applications",
date: "2014-08-15",
location: "Minsk, Centralnaya str., 1, Blue conference hall",
photo: "frontend6.png"
}];
Pay attention! Don’t forget to change the file’s coding into UTF-8!
Now we should include the file tempdata.js into index.jsp:
Then we edit the content configuration:
{ body: {
view:"list",
template:"<img class="eventpic" src="./photos/#photo#" />
<h3>#name# (#date#)</h3>
#description# <a class="details" href="event?eventId=#id#">Details</a>",
css: "eventsList",
type:{
height:152
},
select:false,
autoheight: true,
data: events
}},
{ header: "Latest reports", body: {
template: "The list of latest records"
}, width: 409 }
]},
Thus, we’ve divided a cell into two parts. The right part will contain the list of latest reports, and the central part will present the list of forthcoming events. The latter list will be implemented with the help of the “list” component. Each item of the list is 152 pixels high and has a template set by the “template” property. The select: false option disables selection of the list’s items. We also need to add 150*150 pixels images for each of the reports.
We’ll add a few formatting rules for a list of forthcomning events into the file myapp.css:
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;
}
Let’s add the configuration of the list with latest reports into the file myapp.js by means of specifying the function getLastSpeakersList:
return {
id:"tweets",
view:"list",
template:"<img class="eventpic" src="./photos/#photo#" /><span class="speakerHeader">#author# - #topic#</span>
<span class="speakerDetails">#description#</span>",
type:{
height:190
},
select:false,
data: lastSpeakers
};
}
We also add temporary data for the list “Latest reports” into the file tempdata.js:
"id":6,
"event_id":1,
"author":"Captain America",
"photo":"captainamerica.jpg",
"description":"HTML-import - is a way of including some HTML documents into others. You're not limited to markup either. You can also include CSS, JavaScript or anything else an .html file can contain",
"topic":"HTML-import"
},{
"id":5,
"event_id":1,
"author":"Batman",
"photo":"batman.jpg",
"description":"An introduction to antialiasing, explaining how vector shapes and text are rendered smoothly.",
"topic":"AntiAliasing. Basics."
},{
"id":4,
"event_id":2,
"author":"Thor",
"photo":"thor.jpg",
"description":"Diving deep into getting faster animations in your projects. We'll discover why modern browsers can easily animate the following properties: position, scale, rotation, opacity.",
"topic":"High Performance Animations"
}];
Put 120*120 pixels images for reports into the “photos” folder: batman.jpg, captainamerica.jpg, halk.jpg, ironman.jpg, spiderman.jpg, thor.jpg.
Add styles for the list of reports into the file myapp.css.
line-height: 20px;
font-weight: bold;
}
.speakerDetails {
line-height: 20px;
text-overflow: ellipsis;
}
The only thing left to do is to correct the configuration defined in the file index.jsp:
Now start the server and open the page http://localhost:8080/MyApp/index.action in a browser. Well, the page of forthcoming events is ready.
The same page will be rendered by the address upcoming for convenient naming of menu items. For this purpose the necessary settings should be added to the file struts.xml:
/index.jsp
…
Restart the server. Now the main page of the site is available by the link http://localhost:8080/MyApp/upcoming.
Page “Contacts”
Let’s add mapping into the file struts.xml:
/contacts.jsp
Now we should create the file src/main/webapp/contacts.jsp which is responsible for the “Contacts” page rendering:
<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>
<div id="contacts" style="display: none;">
<a href="mailto:dmitry.radyno@gmail.com">dmitry.radyno@gmail.com</a>
+375 29 123-45-67
<a href="https://twitter.com/radyno">@radyno</a>
<a href="https://www.facebook.com/dmitry.radyno">Dmitry Radyno</a></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: "Latest reports", body: getLastSpeakersList(), width: 409 }
]},
getFooter()
]
}, {}]
});
</script>
Next we add styles for the “Contacts” page to the file src/main/weapp/codebase/myapp.css:
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;
}
Open the following page in a browser – http://localhost:8080/MyApp/contacts:
The page “Contacts” is completed!
Information about a Forthcoming Event
For displaying information about a coming event we’ll use the page event. An event identifier is passed as the parameter eventId in the URL of the page: http://localhost:8080/MyApp/event?eventId=1.
Let’s configure mapping in the file struts.xml. You should take into account that the content of this page will depend on the parameter eventId, which means that specifying the resulting view alone is not enough. It is necessary to look for a suitable event which has the above mentioned identifier in the database. The class EventAction will be responsible for this search:
Thus, we’ve defined that the class com.myapp.action.EventAction and the method getEventById will be used for generating the dynamic content of the page. The file event.jsp will be responsible for rendering data.
Next we create the package com.myapp.model and the class Event in it. Don’t forget to create folder main/src/java if it’s not created automatically. This class presents an upcoming event:
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;
}
}
After that we create the package com.myapp.action with the class EventAction inside. This class is inherited from the class ActionSupport:
The class EventAction is responsible for manipulating data. The request parameters come to this class, search for necessary records is also implemented here and the decision what view file to use is also made in this class.
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", "Part 1: - Task description. (Creating a service for photographers. Processing and developing of proprietary RAW formats: NEF, CR2, DNG, creating a photo editor in a browser, Collaboration service based on WebRTC)", new Date(), "Minsk, Centralnaya str., 1, Blue conference hall", "frontend1.png");
return Action.SUCCESS;
} else {
return Action.ERROR;
}
}
public Event getEvent() {
return event;
}
public void setEvent(Event event) {
this.event = event;
}
public String getEventId() {
return eventId;
}
public void setEventId(String newEventId) {
eventId = newEventId;
}
}
At the moment integration with database is missing, that’s why one and the same static object will be returned all the time regardless of the requested event. Later on we will change the method getEventById in such a way that it will return the record with the identifier “eventId”. For now we’ll create the file event.jsp:
<%@ taglib prefix="s" uri="/struts-tags"%>
<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
<div id="header" style="display: none;">
<div class="confname"></div>
<div class="conftime">-</div>
<div class="confdsc"></div>
</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()
]
},
getPhotos()
]},
{ cols: [
{ body: {
view:"list",
template:"<img src='./photos/#photo#' class='userpic' /> </p>
<h3>#author#: #topic#</h3>
<p>#description#",
css: "speakersList",
type:{
height:170
},
select:false,
minHeight: 400,
autoheight: true,
data: speakers
} },
{ header: "Latest reports", body: getLastSpeakersList(), width: 409 }
]},
getFooter()
]
}, {}]
});
</script>
Then we add test data for the list of reports into the file tempdata.js.
"id":1,
"event_id":1,
"author":"Iron Man",
"topic":"JavaScript Promises - There and Back Again",
"description":"One of the new features that browser developers are preparing for us together with the developers who write specifications of JavaScript Promises is that this template of writing asynchronous code popular with lots of users gets native support. What’s the point of Promises and how to deal with them?",
"photo":"ironman.jpg"
},{
"id":3,
"event_id":1,
"author":"Spider-Man",
"topic":"Using Your Terminal from the DevTools",
"description":"DevTools Terminal - is a new Chrome extension which provides the command-line functionality right in your browser.",
"photo":"spiderman.jpg"
},{
"id":5,
"event_id":1,
"author":"Batman",
"topic":"AntiAliasing. Basics.",
"description":"An introduction to antialiasing, explaining how vector shapes and text are rendered smoothly.",
"photo":"batman.jpg"
},{
"id":6,
"event_id":1,
"author":"Captain America",
"topic":"HTML-Import",
"description":"HTML-import - is a way of including some HTML documents into others. You’re not limited to markup either. You can also include CSS, JavaScript or anything else an .html file can contain.",
"photo":"captainamerica.jpg"
}];
We also add some rules to the myapp.css file:
font-size: 26px;
float: right;
text-align: right;
width: 420px;
}
.userpic {
width: 150px;
height: 150px;
display: block;
float: left;
margin: 7px;
}
Start the server and open the page http://localhost:8080/MyApp/event?eventId=1. The conference page is ready!
Manipulating Events and Reports
In order to edit and process reports we’ll create a page for rendering two tables: one – to show the list of events and the second – to display the list of reports on the chosen event. When you choose some other event, the list of reports will be updated.
First of all, let’s add the necessary setting into the struts.xml file, mapping the address “add” to the view “add.jsp”:
After that we create the file add.jsp:
<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>
<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:"Date", width:80 },
{ id:"name", header:"Name", fillspace: true },
{ id:"location",header:"Location", 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:"Author", width:150 },
{ id:"topic", header:"Topic", 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>
In the above code snippet we’ve created two items of the type “datatable”. This element presents a table, for which it’s possible to define the list of columns that should be rendered in this table.
Webix tables support the binding operation, that is rendering data in a slave table depending on the row which is selected in a master table. In our case the master table is the list of events, and the slave table is the list of reports.
To bind the tables the following code is used:
if (!master) return false;
return master.id == slave.event_id;
});
Such a construction shows that the table “speakers” should be bound to the table “events”. It also shows that only those rows for which the callback-function will return true should be rendered. In this particular case these are the rows where speaker.event_id is equal to event.id.
Let’s also add styles for formatting the icons used for adding, editing and deleting rows:
padding-top: 6px;
cursor: pointer;
}
.bigControl {
cursor: pointer;
color: #ffffff;
font-size: 20px;
}
Now we’ll bring interactivity to the page. Firstly, we implement the possibility to delete existing events. For this purpose we create the event onClick with CSS-class removeEvent for the icon used for deleting rows:
id: "events",
view:"datatable",
columns:[
{ id:"date", header:"Date" , width:80 },
{ id:"name",header:"Name", fillspace: true },
{ id:"location",header:"Location", width:400 },
{ id:"edit",header:"", width: 34, template: ""},
{ id:"remove", header:"", width: 34, template: ""}
],
onClick: {
removeEvent: removeEventClick
},
autoheight:true,
select:"row",
data: events
},
Then we add the function removeEventClick into the JavaScript section of add.jsp file. It will delete a currently selected item from the list. To implement such a behaviour is really easy as Webix DataTable provides a very handy API for data processing:
var event = this.getItem(id.row),
self = this;
webix.confirm({
title:"Deleting an event",
ok:"Yes, delete it",
cancel:"No, leave it",
text:"Are you sure you want to delete the event " + event.name + "?",
callback: function(result) {
if (result) {
self.remove(id);
}
}
});
}
To avoid accidental deleting we use the confirmation dialog webix.confirm. After selecting any variant in the confirm dialog box the callback function is called. The argument of this function is the result of the user’s choice.
In our case, if a user confirms the deletion of a record, the remove function is called. It deletes the record from the table.
Next we add the possibility to create and edit events. In general, these two operations are very similar, because one and the same form is used for both of them. However, there are two differences between these operations. Firstly, their end actions are different: adding a new row into the table and changing an existing row. Secondly, during the editing operation the table’s columns should be filled with existing values.
A separate form will appear in a modal window for adding/editing a row . By clicking the “Save” button the changes will be added into the table of events.
The editing form will be implemented as a separate function. Its first parameter is either an event object (in case of the change of an existing event) or null (in case of the creation of a new event). The second parameter is a function which will be called after the user has filled in the fields and clicked the “Save” button. So let’s create the file codebase/eventForm.js:
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: "Editing an event",
body:{
rows: [{
id: "eventForm",
view:"form",
elements:[
{ view:"text", name:"name", label:"Name"},
{ view:"datepicker", name:"date", label:"Date", stringResult: true },
{ view:"text", name:"location", label:"Address"},
{ view:"text", name:"photo", label:"Image"},
{ view:"textarea", name:"description",height:170, label:"Description", labelPosition:"top" }
],
rules:{
name: webix.rules.isNotEmpty,
description: webix.rules.isNotEmpty
}
}, { view:"toolbar", cols:[
{},
{ view:"button", label:"Save", type:"form", click:saveEvent, width: 200 },
{ view:"button", label:"Cancel", click:discardChanges, width: 200 },
{}
]}]
}
}).show();
if (value) {
$$("eventForm").setValues(value);
}
}
Now we include this file on the add.jsp page:
After that we add onclick-handlers for the icons of adding and editing events:
id: "events",
view:"datatable",
columns:[
{ id:"date", header:"Date" , width:80 },
{ id:"name", header:"Name", fillspace: true },
{ id:"location", header:"Location", width:400 },
{ id:"edit",header:"", width: 34, template: ""},
{ id:"remove", header:"", width: 34, template: ""}
],
onClick: {
removeEvent: removeEventClick,
editEvent: editEventClick
},
autoheight:true,
select:"row",
data: events
},
Let’s define the functions which will call the form of adding/editing events:
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));
}
Open the page http://localhost:8080/MyApp/add in a browser and try to add a new event or edit an existing one:
In the same way we create a form for adding/editing reports. But when creating a report we need to pass an object which contains the event’s identifier in order to attach a new report to the currently selected event.
speakerForm.js:
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: "Editing a report",
body:{
rows: [{
id: "speakerForm",
view:"form",
elements:[
{ view:"text", name:"author", label:"Author"},
{ view:"text", name:"topic", label:"Topic"},
{ view:"text", name:"photo", label:"Photo"},
{ view:"textarea", name:"description",height:200, label:"Description", labelPosition:"top" }
],
rules:{
name: webix.rules.isNotEmpty,
description: webix.rules.isNotEmpty
}
}, { view:"toolbar", cols:[
{},
{ view:"button", label:"Save", type:"form", click:saveSpeaker, width: 200 },
{ view:"button", label:"Cancel", 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:"Author", width:150 },
{ id:"topic", header:"Topic", width:300 },
{ id:"edit",header:"", width: 34, template: ""},
{ id:"remove", header:"", width: 34, template: ""}
],
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:"Deleting a report",
ok:"Yes, delete it",
cancel:"No, leave it",
text:"Are you sure you want to delete the report'" + 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);
});
}
Conclusions
In the second part of the tutorial we’ve added some useful functionality to the website by using JavaScript UI library Webix and Java framework Struts.
Thus, we’ve created two important pages “Contacts” and “Forthcoming Events” as well as made it possible to add and manage the conference events. So we have an almost ready website with lots of functions and attractive appearance.
You’ll get the final information about developing a website with Webix and Struts in the third part of our tutorial that will be soon published in the Webix blog.