Introduction
This article is part 1 of a series of articles that introduces the PureOOP Rich Web UI library and browser.
The PureOOP library is used to develop rich GUI applications for the Web using Java or C#.NET. When I first came across this library and the website, I imagined
this was another library that allowed the developer to create an application using an object oriented language syntax, and ultimately generated HTML and CSS code that was rendered
in the browser. As it turned out, this software does nothing with HTML,
JavaScript, or CSS. The PureOOP browser renders desktop GUI from data received from the server. The server
is a PureOOP library that can be used in Java and C# applications. You write your application in standard Java or C#, using the PureOOP library as the UI library, and all the plumbing
as far as the communication between the client and the server, the protocols, and everything else is handled internally by the PureOOP components. I have used other products in the past
that claim to do similar stuff, but I have not found anything that works reliably, and is so easy to use. If you have been developing desktop applications, you will find yourself able to
develop web applications using the PureOOP technology with great ease. Following is the first part of a series of tutorials that I'm planning on publishing to the CodeProject web site.
Creating a project
To create a project, you will need either Visual Studio for .NET development, or a Java IDE such as Eclipse to create a java PureOOP application. Although there is hardly any difference in
the syntax whether you are developing in Java or C#, the project creation steps are obviously different
because you are using different IDE to develop in each of these languages.
To create a .NET project using Visual Studio, follow these steps:
- Select New->Project from the File menu.
- Expand the Visual C# node, click on Web, select the C# ASP.NET Website template, enter the name of the new project and click OK.
- When you install PureOOP, it installs everything under c:\PureOOP in
Windows. One file that you will need to link your application with is the SWServer.dll file, which is located under the PureOOP folder.
- Right-click on References in the project hierarchy inside the Solution Explorer panel in visual studio, and select Add=>Reference from the context menu. Browse to
c:\PureOOP, and select the file SWServer.dll to add a reference from your project.
- Modify the default.aspx file so that it only contains the first line that is generated by visual studio. You must remove all the other lines in order
for this to work. The first line that remains should look like the following:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="YourProjectName._Default" %>
- Modify the Web.config file to add the following settings inside the
system.web
element:
<system.web>
<customErrors mode="Off"/>
<caching>
<outputCache enableOutputCache="false"/>
</caching>
</system.web>
- Modify the default.aspx.cs file so that the
Page_Load
function contains the following code:
protected void Page_Load(object sender, EventArgs e))
{
try
{
Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
Response.Cache.SetMaxAge(TimeSpan.Zero);
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.Cache.SetNoStore();
Response.Cache.SetExpires(DateTime.Now);
Response.Cache.SetLastModified(DateTime.Now);
Response.Cache.SetAllowResponseInBrowserHistory(false);
}
catch (Exception ee) { }
SWServerMain.ProcessRequest(new ApplicationFactory(), Request, Response);
}
To create a Java project using Eclipse, follow these steps:
- Select New->Project from the File menu, choose the "Dynamic Web Project" template under Web and click Next.
- Enter a project name, enter a project location or accept the default location under the default workspace, and click Finish, the following project structure
will be created by eclipse.
- Now add the SWServer.jar to the project classpath, the file should be located under
c:\PureOOP. Before we add the jar file to the project classpath, copy it to the LIB folder under the project
WebContent/WEB-INF folder.
- Now right-click the project node and select Refresh from the context menu. Expand the WebContent node so you can see the
SWServer.jar node under the WEB-INF/lib node, right-click the jar file and select Build Path=>Add to Build Path, this will add the SWServer jar to your project classpath.
- Add the following elements to the web.xml file, which is located under
WebContent/WEB-INF. These elements must be added as children of the web-app root element:
<web-app...>
...
</welcome-file-list>
<servlet>
<servlet-name>MainServlet</servlet-name>
<servlet-class>com.agmvc.main.MainServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>MainServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app...>
At this point, your project is already setup to integrate with the PureOOP library. Next, we will develop the classes that will make up our application. From here on, the instructions for java and .NET should be almost identical. There are tutorials for both languages on the PureOOP.com website, but for this tutorial, I will use java to finish the rest of this tutorial.
First of all, every PureOOP must have one class that extends (inherits from)
SWApplication
, this will be the application entry point. The application class implements two functions:
getLicenseString
and getMainFrame
. The getLicenseString
function can return an empty string, or a valid license string that is issued by PureOOP to suppress ads. The
getMainFrame
must return an instance of the class that represents the first screen the user sees when your application starts up. This can be the login screen or the home screen. For this tutorial, I will be building a single screen application that implements a shopping cart. Here is my implementation of the application class which returns an instance of
ShoppingCart
as the Main Frame of my app.
Note that the Application class must be created under the package com.main.
package com.main;
import com.agmvc.baseobjects.SWApplication;
import com.agmvc.baseobjects.SWScreen;
import com.shoppingcart.ShoppingCart;
public class AppMain extends SWApplication {
@Override
public String getLicenseString() {
return "";
}
@Override
public SWScreen getMainFrame() {
return new ShoppingCart();
}
}
Creating the shopping cart screen
As you can see, the application class returns an instance of the ShoppingCart screen. We will create this class under the package com.shoppingcart. To do that:
- Right-click the src node under project=>Java Resources, and choose New=>Class from the menu.
- Set the package name to com.shoppingcart, the class name to ShoppingCart, and click Finish.
- Change the class so that it inherits from
SWScreen
, and implements the abstract functions of that class as shown below:
package com.shoppingcart;
import com.agmvc.baseobjects.SWScreen;
public class ShoppingCart extends SWScreen {
@Override
public String getQualifiedClassName() {
return "com.shoppingcart.ShoppingCart";
}
@Override
protected void getView() {
}
}
For some reason, the framework requires that you return the fully qualified class name for every screen or dialog class. To return that, you must implement the function
getQualifiedClassName
, and return the full name of the class including the package name.
The getView
function is where you create and layout the screen components. If you are familiar with GUI frameworks like swing, swt,
and Windows Forms, they all implement what is called a layout manager. A layout manager allows you to layout your components
inside a panel in a way which allows the layout to be fluid, that means the screen can be resized, and the layout automatically stretches and repositions the components within it to fit the new size of the screen. PureOOP implements a grid layout, which is extremely flexible,
and allows you to divide the screen into rows and columns, you can then configure each row and column to make them fixed or stretchable. Every row in the layout can be configured to a fixed height, or a
FILL
height. A FILL
height means the row will be stretched automatically
when the screen is resized, a fixed-height does not grow beyond the height that you set. The same applies to columns.
Every screen and dialog contain what is normally referred to as a root panel. A root panel is the panel that is embedded in every screen and every dialog, and is used to add components to the that screen and dialog. In the
getView
function, you obtain a reference to the root panel
by calling getContentPane()
, which returns an instance of SWPanel
. The instance of
SWPanel
includes the method setLayout
, which you call to set the layout of the panel. The
setLayout
function accepts 2 arrays as arguments, a column array, and a row array. Each of these
arrays is an array of integers. The number of elements in each array corresponds to the number of columns or rows that you want the panel to be divided into.
We will now modify the ShoppingCart
class so that it displays a button menu along the left side of the screen, this button menu will contain a button for each category of products. We will also have a button grid component to the right side of the category menu, which will be used to display
the items that belong to the selected category. To the right side of the item list, we will have a grid that will hold all the items that have been added to the cart. To do all this, modify the
ShoppingCart
class so that it has the following code:
package com.shoppingcart;
import com.agmvc.baseobjects.SWButtonGrid;
import com.agmvc.baseobjects.SWButtonMenu;
import com.agmvc.baseobjects.SWGrid;
import com.agmvc.baseobjects.SWPanel;
import com.agmvc.baseobjects.SWScreen;
public class ShoppingCart extends SWScreen {
SWButtonMenu _categories = new SWButtonMenu();
SWButtonGrid _categoryItems = new SWButtonGrid();
SWGridMemory _cart = new SWGridMemory();
@Override
public String getQualifiedClassName() {
return "com.shoppingcart.ShoppingCart";
}
@Override
protected void getView() {
SWPanel contentPane = getContentPane();
contentPane.setLayout(new int[]{200,300,SWPanel.FILL},
new int[]{SWPanel.FILL,25,25,25});
contentPane.addComponent(_categories, 0, 0, 1, 4);
contentPane.addComponent(_categoryItems, 1, 0, 1, 4);
contentPane.addComponent(_cart, 2, 0, 1, 1);
}
}
If you now run this application in eclipse by running it under Tomcat, eclipse will display the
URL of our running application, copy the URL, and paste it into
the PureOOP browser, and then click GO, you should see the following screen come up:
Now let us initialize the components by adding categories, to the categories menu, and then register listeners so that we can receive events when the user clicks a category,
item, and other actions. Append the following code to the end of the getView
function:
_categories.addItem("Candy", "Candy","");
_categories.addItem("Soda", "Soda","");
_categories.addItem("Juice", "Juice","");
_categories.addItem("Lottery", "Lottery","");
_cart.setTitle("Cart Items");
_cart.addColumn("Item Name", Alignment.LEFT, false, EditStyle.TEXT, "", 2);
_cart.addColumn("Quantity", Alignment.RIGHT, false, EditStyle.NUMERIC, "", 2);
_cart.addColumn("Unit Price", Alignment.RIGHT, false, EditStyle.NUMERIC, "", 2);
_categories.setActionListener(this);
_categoryItems.setActionListener(this);
When your registers itself as an action listener, it must implement the com.agmvc.listeners.ActionListener
interface. To implement this interface, you must implement
the actionPerformed
function to receive any action events triggered by the components that you registered an action listener on by calling
setActionListener
. We need to add
code in actionPerformed
to handle the case when a category is clicked, so that we can load the
_categoryItems
component with the items that belong under the clicked category.
To do that, add the following code in actionPerformed
:
@Override
public SWScreen actionPerformed(SWComponent source, String actionName, String customData) {
if(source == _categories) {
_categoryItems.clear();
if("Soda".equals(actionName)) {
_categoryItems.addItem("Code","Coke","images/coke.gif");
_categoryItems.addItem("7Up","7Up","images/7up.gif");
_categoryItems.addItem("Crush","Crush","images/crush.gif");
_categoryItems.addItem("RC","RC","images/rc.gif");
}
else if("Lottery".equals(actionName)) {
_categoryItems.addItem("SuperLotto","SuperLotto","images/SuperLotto.gif");
_categoryItems.addItem("Fantasy5","Fantasy5","images/Fantasy5.gif");
_categoryItems.addItem("PowerBall","PowerBall","images/PowerBall.gif");
}
}
return null;
}
Now we need to place the images that we set to the category items under the images folder. The images folder must be at the same level as the
WEB-INF folder under WebContent.
Once you have saved the image files under the images folder, right-click the project node in eclipse and select Refresh. Run the application in tomcat, and then enter the
URL in PureOOP,
click on the Soda category, this is what you should see:
The next step is to code for the event when the user clicks on one of the items in the
categoryItems
component, so that the item gets added to the grid (shopping cart). Add the following code in
actionPerformed
to handle take care of that logic:
@Override
public SWScreen actionPerformed(SWComponent source, String actionName, String customData) {
if(source == _categories) {
_categoryItems.clear();
if("Soda".equals(actionName)) {
_categoryItems.addItem("Code","Coke","images/coke.gif");
_categoryItems.addItem("7Up","7Up","images/7up.gif");
_categoryItems.addItem("Crush","Crush","images/crush.gif");
_categoryItems.addItem("RC","RC","images/rc.gif");
}
else if("Lottery".equals(actionName)) {
_categoryItems.addItem("SuperLotto","SuperLotto","images/SuperLotto.gif");
_categoryItems.addItem("Fantasy5","Fantasy5","images/Fantasy5.gif");
_categoryItems.addItem("PowerBall","PowerBall","images/PowerBall.gif");
}
}
else if(source == _categoryItems) {
if(_cart.addRow()) {
_cart.setValue("Item Name", actionName);
_cart.setValue("Quantity","1");
_cart.setValue("Unit Price","1.00");
}
}
return null;
}
When an item is clicked in the _categoryItems
component, the code that we added last adds a new item to the cart with the name of the item set to
actionName
, and the quantity and unit price both set to 1. If you run the code and click on an item in
_CategoryItems
, you should see each item that you click gets added to the cart as shown below:
Next, we will add a Save button below the cart grid, so that when the user clicks the save button, our code will capture all the items that have been added to the cart, and save them as an order in the database. To do that, add the following code as indicated in each section.
- Add the declaration of the button as an instance of the
ShoppingCart
class:
SWButton _saveBtn = new SWButton();
Initialize the button at the end of the getView
function:
protected void getView() {
...
contentPane.addComponent(_saveBtn,2,3,1,1);
...
_saveBtn.setText("Save");
_saveBtn.setActionName("Save");
_saveBtn.setActionListener(this);
_categories.setActionListener(this);
_categoryItems.setActionListener(this);
}
Add the following else to the end of the actionPerformed
function:
public SWScreen actionPerformed(SWComponent source, String actionName, String customData) {
...
else if(source == _saveBtn) {
Connection dbConnection = null;
PreparedStatement preparedStatement = null;
String insertTableSQL = "INSERT INTO orderdetail"
+ "(orderno,itemname, quantity, unitprice, price) VALUES"
+ "(?,?,?,?,?)";
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
dbConnection = DriverManager.getConnection(
"jdbc:oracle:thin:@localhost:1521:sales", "username","password");
preparedStatement = dbConnection.prepareStatement(insertTableSQL);
int newOrderNo = 1;
int rowCount = _cart.getRowCount();
for(int rowIndex = 0;rowIndex<rowCount;rowIndex++) {
String itemName = _cart.getValue(rowIndex,"Item Name");
String quantity = _cart.getValue(rowIndex,"Quantity");
String unitPrice = _cart.getValue(rowIndex,"Unit Price");
double itemPrice = NumberUtil.stringToDouble(quantity)*NumberUtil.stringToDouble(unitPrice);
preparedStatement.setInt(1, newOrderNo);
preparedStatement.setString(2, itemName);
preparedStatement.setString(3, quantity);
preparedStatement.setString(4, unitPrice);
preparedStatement.setString(5, Double.toString(itemPrice));
preparedStatement.executeUpdate();
}
} catch (ClassNotFoundException e) {
} catch (SQLException e) {
} finally {
if (preparedStatement != null) {
preparedStatement.close();
}
if (dbConnection != null) {
dbConnection.close();
}
}
}
}
If you run the code now, you should see the Save button below the cart grid, add few items to the cart and click Save, you will also need to change the connection string and create the table
orderdetail in a sales database in order for this code to successfully save the order to the database.
History
This is it for this part of the tutorial, stay tuned for part 2.