Table 12.1 Functional Requirements and Related REST Calls View available data sources /resources/ds View all schemas / tables by type for a data source /resources/ds/{ds} View database d
Trang 1Table 12.1 Functional Requirements and Related REST Calls
View available data sources /resources/ds
View all schemas / tables (by type) for a data
source
/resources/ds/{ds}
View database driver details /resources/dbInfo/{ds}
View columns and types for a table /resources/ds/{ds}/table/{table}
Perform free-form SQL queries and view results /resources/ds/{ds}/sql
Using DataSources in your Dojo applications provides a standardized way to read, write,
and search back-end resources without direct knowledge of how the data is structured For any
data that contains many rows, DataStores will greatly enhance your data access concerns
DBA—A Complete RIA Using WebSphere sMash and Dojo
At this point, we’re ready to take all the knowledge gained and build a complete and usable
Rich Internet Application (RIA) Let’s approach this application like a typical business
project, where we are provided with a list of requirements to meet The business has requested
the following:
A web-based, cross-vendor database administration utility
We have also heard that they want the application up and running by the end of the week!
We know that the only way we can accomplish this task is to use our freshly acquired knowledge
of WebSphere sMash combined with the Dojo Toolkit Lucky for us, a few days ago, you
hap-pened to be wearing your DBA hat, while reading through Chapter 8 of this book, and you have
already provided us with a set of RESTful resources that can be utilized to access the company’s
various relational databases Along with the project request, we received a list of functional
requirements to meet For each requirement, we just happen to have a corresponding REST
resource, shown in Table 12.1 It’s funny how these things fall together so cleanly!
We have enough information to sketch out a flow diagram of the application Figure 12.8
provides a basic flow, including the REST calls and user interaction Building up an application
flow diagram is always a good idea as it illustrates key criteria to address If you can’t easily draw
a flow diagram, you need to rethink how you plan to build your application
Because we’re using Dojo to build up the application’s front-end, let’s take a quick
inven-tory of some of the widgets that will come in handy These include the following:
Trang 2Figure 12.8 DBA flow diagram
• Layout: BorderContainers, ContentPanes, AccordionContainer, Themes
• Data Management: ItemFileReadStore
• Data Presentation: Tree, DataGrid, ProgressBar, Toaster
• User Input: Buttons, Checkbox, FilterSelect, Editor
Project Creation
Create a new WebSphere sMash project, and name it Book.Client.DBA Next, open the
/config/ivy.xml file, add a dependency on Dojo, and also add a dependency on the
book:Book.Database.DBA project This gives us direct access to the REST services we’ll be
using to actually access our databases This works out quite well because we have a clean
separa-tion of concerns between the two projects; as long as the REST contract—URLs, request
parame-ters, and response payloads—remain the same, the back-end can be maintained separately from
our front-end presentation layer Ensure that the project resolves properly and we’re ready to
begin construction of the sMash DBA application Remember, the clock is ticking on delivery,
and we want to impress the business
Trang 3Layout Mockup
The first thing that needs to be accomplished is defining and constructing the application’s
physical layout For complex layouts, this is generally managed by using Dojo’s
dijit.layout.BorderContainer widget, which for those familiar with general GUI design
is similar to a grid bag layout component A BorderContainer defines up to five general
regions: Top, Bottom, Leading (Left), Trailing (Right), and Center The Top and Bottom regions
require a fixed height value, the Leading and Trailing regions require a fixed width, and the
Cen-ter region fills in whatever is left over Not all regions need to be defined, but the CenCen-ter region is
mandatory Additionally, the BorderContainer is configured to use either a Headline design,
where the top and bottom regions consume the full width of the available area, or it can use a
Sidebar design, where the leading and trailing regions take up the full height of the available area
Finally, you can define regions to contain a splitter, which allows the user to dynamically alter the
height or width of a region by dragging a splitter bar
The other key layout widgets to know about for this application are the
dijit.layout.AccordionPane and dijit.layout.ContentPane The AccordionPane
provides a visually engaging stack container where only a single content area is visible at a time
The ContentPane is a general-purpose container where content can be placed directly within it,
or reference external HTML files to be retrieved and rendered within it Each BorderContainer
region is defined by a layout widget, which is typically a ContentPane Other layout widgets can
also be used as region containers, such as BorderContainers and Accordions So, it’s
pos-sible to have a BorderContainer within a BorderContainer By combining and nesting
BorderContainers, you can create a sophisticated application layout with very little effort
The DBA application’s layout follows a typical multipaned application layout similar to
that used in classic email and file manager clients A logical layout diagram for the DBA
applica-tion is shown in Figure 12.9 With Dojo’s declarative layout containers, it’s easy to mark this
up in HTML, as shown in Listing 12.8 You can view the basic layout design in the
Book.Client.DBA application source code in the /public/example_layout.html
Listing 12.8 Layout Markup
<div id="main" dojoType="dijit.layout.BorderContainer"
design="headline" livesizing="true">
<! ########## Header ########## >
<div dojoType="dijit.layout.ContentPane" region="top" id="header">
Header</div>
<! ########## Footer ########## >
<div dojoType="dijit.layout.ContentPane" region="bottom"
id="footer">Footer</div>
Trang 4Figure 12.9 DBA logical layout
<! ########## Left Column ########## >
<div dojoType="dijit.layout.AccordionContainer" region="leading"
id="left" splitter="true">
<div dojoType="dijit.layout.ContentPane"
title="Data Source Selection" selected="true"></div>
<div dojoType="dijit.layout.ContentPane"
title="Database Layout"></div>
<div dojoType="dijit.layout.ContentPane"
title="Table Details"></div>
<div dojoType="dijit.layout.ContentPane"
title="Driver Details"></div>
</div><! AC >
<! ########## Right Content Area ########## >
<div dojoType="dijit.layout.BorderContainer" region="center">
<div dojoType="dijit.layout.ContentPane" region="top"
id="RightTop" splitter="true"></div>
<div dojoType="dijit.layout.ContentPane" region="center"
id="RightBottom"></div>
</div>
</div>
Trang 5Figure 12.10 DBA example layout mockup
If you start the application and load this file in a browser, you can see that we have the
beginnings of a professional-looking application, as seen in Figure 12.10 There isn’t any content
yet, but the layout is well defined, with an active Accordion area on the left and three resizeable
content areas Dojo makes its incredibly easy to do complex layouts with little effort We won’t
actually use this file in the application because we’ll be transposing this layout into the main
index.html page and adding in more content
For this application, all styling details are located in the /public/style/dba.css file
Style-sheet references are linked by ID to make it easy to locate the appropriate rules in the CSS
file There are a lot of style rules applied to the various widgets in this application, so refer to the
CSS if you have any questions on how various widgets are controlled
Initial Page Loading
As with any Dojo application, we have the standard setup requirements of bringing in style
sheets, loading the core Dojo, defining our used modules with dojo.require() calls and
pars-ing the page for declarative widgets These can all be seen within the head element of the
index.html page, as shown in Listing 12.9
Trang 6Listing 12.9 DBA Page Initialization
<style type="text/css">
@import "/style/dba.css";
</style>
<script type="text/javascript" src="/dojo/dojo.js"
djConfig="parseOnLoad:true, isDebug:true" ></script>
<script type="text/javascript">
console.info(">>>>> sMash DBA starting up <<<<<");
console.debug("Loading required Dojo modules");
dojo.require("dojo.parser");
dojo.require("dijit.layout.BorderContainer");
// Other dojo.require()s not shown
console.debug("Loading custom dba module");
dojo.registerModulePath("dba"," /js/dba");
dojo.require("dba");
dojo.addOnLoad( dba.init );
</script>
Near the end of the script tag is a new feature we have not yet addressed We are telling Dojo
about a new module path that contains our custom code This essentially provides a namespaced
directory tree we can use just as you would any other defined Dojo object This is a best practice to
place all your custom code within an externally defined directory location It is important to note
that the module path’s location is relative to the dojo.js file and not the index.html file where
it is being called Because WebSphere sMash Dojo support puts the dojo tree directly under
/public, the /js/dba path is one directory above the /dojo/dojo.js file We can now
dojo.require() our custom code, and the Dojo loader will locate it and bring it into memory
Within the dba tree, there is a file called dba.js We inform Dojo of our intent to use this file, and
finally when the page is loaded and parsed, we tell Dojo to run the dba.init() function
Examine the rest of the index.html The majority of it should resemble the layout sample
provided earlier, but we have added a fair amount of extra content Although space does not
per-mit us to describe everything going on in this file, it should all be relatively decipherable If you
can’t figure out what a particular widget definition is providing, try looking up the widget name
in the Dojo documentation Several items are merely placeholders for later functionality injected
by the dba controller (dba.js)
Trang 7Application Initialization
Let’s move on to the /js/dba.js script This is where most of the action occurs for the this
application Listing 12.10 shows the beginning of the script The first line registers this module
with the Dojo loading system so that any future requires for this module will show as
avail-able and not cause a reloading of this file After that are several more require statements
Although we could have put all the require statements either in the index.html or in this
file, it’s a good practice to put the requires in the file that directly instantiates or uses a class
The next line of code immediately executes an anonymous function that encloses a rather large
object definition for the dba object The dba object contains many variables and functions In
this application, we made the dba object effectively a static global object We could just as
eas-ily have made this an instantiable class but opted for the more direct approach instead; the
rea-soning for this is that a single static object is easier to manage We don’t have to be concerned
about object scoping, and because we have a single instance for the entire application, there’s
not a need for a unique instance object
Before we move on to heavy stuff, let’s explain a few items At the beginning of the dba
object, we define a few variables The first is a map of the URLs that are used to retrieve data
Keeping all the URLs in a single location makes it easy to know what services will be called and
makes maintenance of these much easier Notice how a few have embedded tokens These are
replaced at runtime with the actual values using the dojo.string.substitute function Next
are a few more variables that simply hold the DataStore instances that will be used, a reference to
the currently selected data source, and finally a list object that holds the current activities that we
show in a progress bar at the bottom of the application See the dba.busy() function and the
footer section of the index.html to see how the progress bar is utilized for showing activity
Let’s walk through the initialization process of the DBA application As shown previously,
when the page is loaded and the declarative widgets are rendered, the OnLoad event calls the
dba.init() function In this function, all we’re effectively doing is calling another function to load
the available data sources Notice, though, that each function defines an F variable This is simply a
convention we’ve used so that when writing console output, we can start with the F variable, and we
always know what function the message came from This can also be used in error reporting, as seen
in several areas of the application where there are try/catch blocks that call dba.error(F,e) to
report on the caught “e” Error object As an interesting side note, when the page and dba objects are
loaded, you can use Firebug to call any function directly; to test out the dba.error function, and the
resulting toaster widget display of the error, type the following into the Firebug console’s input area:
dba.error("Blah(): ", new Error("Ker-Blamo") );
Listing 12.10 DBA Script Initialization
dojo.provide("dba");
dojo.require("dijit.Tree");
dojo.require("dojox.string.Builder");
Trang 8// Remote REST URL’s used by the application
// Parameters are replaced at runtime
(function(){
dba = {
urls : {
DS: "/resources/ds",
SCHEMA: "/resources/ds/${ds}?showSysTables=${showSysTables}",
DBINFO: "/resources/dbInfo/${ds}",
SQL: "/resources/ds/${ds}/sql",
TABLE: "/resources/ds/${ds}/tables/${table}"
},
// Data stores used within the application
stores : {
dataSources: null,
schema: null,
tableDetails: null
},
dataSource : null,
busyMessage : [],
// -init : function() {
// summary:
// Start DBA application
var F = "dba.init(): ";
console.debug(F, "Starting ");
dba.loadDataSources();
console.debug(F, "finished");
},
// -loadDataSources : function() {
// summary:
// Load initial Data Source list
var F = "dba.loadDataSources(): ";
console.debug(F, "Starting ");
Trang 9dba.busy("Data Sources", true);
dba.stores.dataSource = new dojo.data.ItemFileReadStore({
url: dba.urls.DS} );
dba.stores.dataSource.fetch({
sort: ,
onComplete: function(items, request) {
console.debug(F, "Data Sources loaded #:", items.length);
var key = dba.stores.dataSource.getValue(items[0], "uid");
console.debug( F, "Setting DS default to: ", key );
var dsInput = dijit.byId("dataSource");
dsInput.attr({
store : dba.stores.dataSource,
value : key,
disabled : false
});
var dsButton = dijit.byId("loadDataSourceButton");
dojo.connect(dsButton, "onClick", dba, "loadDatabaseInfo");
dojo.connect(dsButton, "onClick", dba, "loadDatabaseSchema");
dsButton.attr( "disabled", false );
dba.busy("Data Sources", false);
console.debug( F, "Finished" );
}
});
},
//
Other functions removed
};
}() );
It’s time to tackle the loadDataSources() function This function defines a new
Data-Store (ItemFileReadData-Store) passing in a target URL to the data source service on the host The
next task is to perform a query on the data This is the action that actually causes the DataStore to
go out and fetch the data from the server Because DataStore use asynchronous AJAX calls to
obtain the data by URL, we need to provide a callback function to run when the data has been
fully retrieved Within the onComplete callback, we obtain a reference to the data source dijit
This is the FilteringSelect drop-down widget Notice how we used the dijit.byId(),
Trang 10Figure 12.11 Data source selector and loading button
which returns an instance of the widget, rather than the dojo.byId(), which simply returns the
DOM node of the ID It is important to understand the difference, because we need the widget
instance to call the appropriate methods to populate the widget After we have the input’s widget
reference, a call is made to update several attributes at once The first is to assign the DataStore to
the input We also set the default value and enable to the widget for user interaction The final
task within this function obtains a reference to the load button, enables it, and sets up two event
handlers to call other functions when it is clicked The data source FilteringSelect and the
load button are shown in Figure 12.11
Driver Details and Schema Loading
As shown in the logic flow diagram, two simultaneous asynchronous events are initiated when a
data source is selected These event handlers make calls to the appropriately named
dba.loadDatabaseInfo and dba.loadDatabaseSchema functions Because we don’t want
to completely fill this book with source code samples, we talk generally about the activity in the
remaining functions, and you can review the actual logic in the provided source code
First, a call is made to retrieve the driver details used for this data source A normal xhrGet
call is used to fetch this data This is an AJAX call and, as such, needs a callback function named
load for when the results are received This data is returned as a normal JSON data object, and
the individual values are placed into a table within the Driver Details accordion pane, as seen in
Figure 12.12 There is also an associated error callback that can be used to process any failures in
an AJAX call For more information on xhrGet—and its sister functions, such as xhrPost—as
well as the more general topic of asynchronous processing and callbacks, read up on the topic of
dojo.Deferred