blob: 077a606007494102f382325e7e7badf1177bfd33 [file] [log] [blame]
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<!-- -*- xhtml -*- -->
<title>NetBeans CRUD Application Tutorial for NetBeans Platform 6.5</title>
<meta name="AUDIENCE" content="NBUSER">
<meta name="TYPE" content="ARTICLE">
<meta name="EXPIRES" content="N">
<meta name="developer" content="gwielenga@netbeans.org">
<meta name="indexed" content="y">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="description"
content="A guide describing how to create a CRUD application on
NetBeans Platform 6.5.">
<link rel="stylesheet" type="text/css" href="https://netbeans.org/netbeans.css">
</head>
<body>
<h1><a name="top"></a>NetBeans Platform CRUD Application Tutorial</h1>
<p>This tutorial shows you how to integrate a MySQL database into a
NetBeans Platform application. We start by exploring a MySQL database,
for which we create an entity class. However, note that these instructions
are not applicable to MySQL only. Rather, they are relevant to any
relational database supported by NetBeans IDE. Next, we wrap the entity class
into a module, together with modules for the related JPA JARS.
<p>Once the above modules are part of our application, we create a new
module that provides the user interface for our application. The
new module gives the user a tree hierarchy showing data from the database.
We then create another module that lets the user edit the data displayed
by the first module. By separating the viewer from the editor in distinct modules, we will enable
the user to install a different editor for the same viewer, since different
editors could be created by external vendors, some commercially and some
for free. It is this flexibility that the modular architecture of the
NetBeans Platform makes possible.
<p>Once we have an editor, we begin adding CRUD functionality. First, the
"R", standing for "Read", is handled by the viewer described above. Next,
the "U" for "Update" is handled, followed by the "C" for "Create", and the
"D" for "Delete".
<p>At the end of the tutorial, you will have learned about a range of NetBeans
Platform features that help you in creating applications of this kind. For
example, you will have learned about the <tt><a href="http://bits.netbeans.org/dev/javadoc/org-openide-awt/org/openide/awt/UndoRedo.Manager.html">UndoRedo.Manager</a></tt> and the <tt><a href="http://bits.netbeans.org/dev/javadoc/org-openide-explorer/org/openide/explorer/ExplorerManager.html">ExplorerManager</a></tt>,
as well as NetBeans Platform Swing components, such as <tt><a href="http://bits.netbeans.org/dev/javadoc/org-openide-windows/org/openide/windows/TopComponent.html">TopComponent</a></tt> and <tt><a href="http://bits.netbeans.org/dev/javadoc/org-openide-explorer/org/openide/explorer/view/BeanTreeView.html">BeanTreeView</a></tt>.
<p><b>Contents</b></p>
<img src="../images/articles/65/netbeans-stamp65.gif" class="stamp" width="114" height="114" alt="Content on this page applies to NetBeans IDE 6.5" title="Content on this page applies to NetBeans IDE 6.5"> </p>
<ul class="toc">
<li><a href="#creating-app">Setting Up the Application</a></li>
<li><a href="#integrating-database">Integrating the Database</a>
<ul>
<li><a href="#creating-entity">Creating Entity Classes from a Database</a></li>
<li><a href="#wrapping-entity">Wrapping the Entity Class JAR in a Module</a></li>
<li><a href="#creating-other">Creating Other Related Modules</a></li>
<li><a href="#designing-ui">Designing the User Interface</a></li>
<li><a href="#setting-dependencies">Setting Dependencies</a></li>
<li><a href="#running-prototype">Running the Prototype</a></li>
</ul>
<li><a href="#integrating-crud">Integrating CRUD Functionality</a>
<ul>
<li><a href="#read">Read</a></li>
<li><a href="#update">Update</a></li>
<li><a href="#create">Create</a></li>
<li><a href="#delete">Delete</a></li>
</ul>
</li>
<li><a href="#related-screencasts">Related Screencasts</a></li>
</ul>
<p><b>To follow this tutorial, you need the software and resources listed in the following
table.</b></p>
<table>
<tbody>
<tr>
<th class="tblheader" scope="col">Software or Resource</th>
<th class="tblheader" scope="col">Version Required</th>
</tr>
<tr>
<td class="tbltd1"><a href="https://netbeans.org/downloads/index.html">NetBeans IDE</a></td>
<td class="tbltd1">version 6.5</td>
</tr>
<tr>
<td class="tbltd1"><a href="http://java.sun.com/javase/downloads/index.jsp">Java Developer Kit (JDK)</a></td>
<td class="tbltd1">Version 6 or<br/>version 5</td>
</tr>
</tbody>
</table>
<p>The application you create in this tutorial will
look as follows:</p>
<p><img src="http://blogs.oracle.com/geertjan/resource/states-manager-crud-application-gj.png"></p>
<p class="tips"> It is advisable to watch the screencast series
<a href="https://platform.netbeans.org/tutorials/nbm-10-top-apis.html">Top 10 NetBeans APIs</a>
before beginning to work on this tutorial. Many of the concepts addressed in this tutorial
are discussed in more detail within the screencast series.
<p>
<!-- ===================================================================================== -->
<br>
<h2 class="tutorial"><a name="creating-app"></a>Setting up the Application</h2>
<p>Let's start by creating a new NetBeans Platform application.
<ol>
<li>Choose File &gt; New Project (Ctrl+Shift+N). Under Categories, select NetBeans Modules.
Under Projects, select NetBeans Platform Application. Click Next.</li>
<li>In the Name and Location panel, type <tt>DBManager</tt> in the Project Name field.
Click Finish.</li>
</ol>
<p>The IDE creates the <tt>DBManager</tt> project. The project
is a container for all the other modules you will create.
<p><img src="../images/tutorials/crud/dbmanager-1.png"/> </p>
<br>
<!-- ===================================================================================== -->
<br>
<h2><a name="integrating-database"></a>Integrating the Database</h2>
<p>In order to integrate the database, you need to create entity classes
from your database and integrate those entity classes, together with
their related JARs, into modules that are part of your NetBeans Platform
application.</p>
<div class="indent">
<h3 class="tutorial"><a name="creating-entity"></a>Creating the Entity Classes</h3>
<p>In this section, you generate entity classes from a selected database.
<ol>
<li><p>For purposes of this example, choose or create a "states" database, listing
the US states, together with their abbreviations.</p>
<p><img src="../images/tutorials/crud/dbmanager-2.png"/> </p>
<p class="tips"> Alternatively, use
any database you like and adapt the steps that follow to your particular
use case. For help with this step, see <a href="https://netbeans.org/kb/docs/ide/mysql.html">Connecting to a MySQL Database</a>.</p>
<p><li>In the IDE, choose File | New Project, followed by
Java | Java Class Library to create a new library project
named <tt>StatesLibrary</tt>.
<p><li><p>In the Projects window, right-click the library project
and choose File | New File, followed by Persistence | Entity
Classes from Database. In the wizard, select EclipseLink in
the step where you use the wizard
to generate a persistence unit. Specify "demo" as the name of
the package where the entity classes will be generated.</p>
<p>Once you have completed this step, look at the generated code and notice
that, among other things, you now have a <tt>persistence.xml</tt> file in a folder
called META-INF, as well as entity classes for each of your tables:</p>
<p><img src="../images/tutorials/crud/dbmanager-3.png"/> </p>
<p><li><p>Build the Java Library and you will have a JAR file in the library
project's "dist" folder, which you can view in the Files window:</p>
<p><img src="../images/tutorials/crud/dbmanager-4.png"/> </p>
</li>
</ol>
<h3 class="tutorial"><a name="wrapping-entity"></a>Wrapping the Entity Class JAR in a Module</h3>
<p>In this section, you create a new NetBeans module that wraps the JAR file
you created in the previous section.
<ol>
<li>Right-click the <tt>DBManager</tt>'s Modules node in the Projects window
and choose Add New Library.
<p><li>Select the JAR you created in the previous subsection
and complete the wizard, specifying any values you like.
</ol>
<p>You now have your first custom module in the new application:</p>
<p><img src="../images/tutorials/crud/dbmanager-5.png"/> </p>
<h3 class="tutorial"><a name="creating-other"></a>Creating Other Related Modules</h3>
<p>In this section, you create two new modules, wrapping the EclipseLink JARs,
as well as the database connector JAR.
<ol>
<li><p>Do the same as you did when creating the library wrapper for the
entity class JAR, but this time for the EclipseLink JARs, which are in
your GlassFish distro, make sure to include the persistence JAR that
you find there too.</p>
<p>Use Ctrl-Click to select multiple JARs, as shown here:</p>
<p><img src="../images/tutorials/crud/dbmanager-6.png"/></p>
<p class="tips"> If you don't know which ones to include,
go back to the library project created earlier and
then expand the Libraries folder, which will show you which libraries you need.</p>
<p><li>Next, create yet another library wrapper module, this time for the MySQL connector JAR,
which is available in the NetBeans IDE installation folder, within the <tt>ide10/modules/ext</tt>
folder.</p>
</ol>
<h3 class="tutorial"><a name="designing-ui"></a>Designing the User Interface</h3>
<p>In this section, you create a simple prototype user interface, providing
a window that uses a <tt>JTextArea</tt> to display data retrieved
from the database.
<ol>
<li>Right-click the <tt>DBManager</tt>'s Modules node in the Projects window
and choose Add New. Create a new module named <tt>StatesViewer</tt>, with
the code name base <tt>org.demo.states.viewer</tt>.
<p><li>In the Projects window, right-click the new module and choose
New | Window Component. Specify that it should be created in the <tt>editor</tt>
position and that it should open when the application starts. Set <tt>States</tt>
as the window's class name prefix.
<p><li><p>Use the Palette (Ctrl-Shift-8) to drag
and drop a <tt>JTextArea</tt> on the new window:</p>
<p><img src="../images/tutorials/crud/dbmanager-7.png"/></p>
<p><li>Add this
to the end of the TopComponent constructor:
<pre class="examplecode">EntityManager entityManager = Persistence.createEntityManagerFactory("StatesLibraryPU").createEntityManager();
Query query = entityManager.createQuery("SELECT c FROM States c");
List&lt;States&gt; resultList = query.getResultList();
for (States c : resultList) {
jTextArea1.append(c.getName() + " (" + c.getAbbrev() + ")" + "\n");
}</pre>
<p class="tips"> Since you have not set dependencies on the modules that
provide the States object and the persistence JARs, the statements
above will be marked with red error underlines. These will be fixed
in the section that follows.</p>
<p>Above, you can see references to a persistence unit named "StatesLibraryPU",
which is the name set in the <tt>persistence.xml</tt> file. In addition,there is a reference
to one of the entity classes, called <tt>States</tt>, which is in the entity classes module.
Adapt these bits to your needs.
</ol>
<h3 class="tutorial"><a name="setting-dependencies"></a>Setting Dependencies</h3>
<p>In this section, you enable some of the modules to use code from
some of the other modules. You do this very explicitly by setting intentional
contracts between related modules, i.e., as opposed
to the accidental and chaotic reuse of code that tends to happen when
you do not have a strict modular architecture such as that provided by
the NetBeans Platform.
<ol>
<li>The entity classes module needs to have dependencies on the MySQL
module as well as on the EclipseLink module. Right-click the
<tt>StatesLibrary</tt> module, choose Properties, and use the
Libraries tab to set dependencies on the two modules that the
<tt>StatesLibrary</tt> module needs.
<p><li>The <tt>StatesViewer</tt> module needs a dependency
on the EclipseLink module as well as on the entity classes module.
Right-click the
<tt>StatesViewer</tt> module, choose Properties, and use the
Libraries tab to set dependencies on the two modules that the
<tt>StatesViewer</tt> module needs.
<p><li>Open the <tt>StatesTopComponent</tt> in the Source view, right-click
in the editor, and choose "Fix Imports". The IDE is now able to add
the required import statements, because the modules that provide
the required classes are now available to the <tt>StatesTopComponent</tt>.
</ol>
<p>You now have set contracts between the modules
in your application, giving you control
over the dependencies between distinct pieces
of code.
<h3 class="tutorial"><a name="running-prototype"></a>Running the Prototype</h3>
<p>In this section, you run the application so that you can see
that you're correctly accessing your database.
<ol>
<li>Start your database server.
<p><li>Run the application. You should see this:</p>
<p><img src="../images/tutorials/crud/dbmanager-8.png"/></p>
</ol>
<p>You now have a simple prototype, which you will extend in the next
section.
</div>
<br>
<h2><a name="integrating-crud"></a>Integrating CRUD Functionality</h2>
<p>In order to create CRUD functionality that integrates smoothly
with the NetBeans Platform, some very specific NetBeans Platform coding patterns need to
be implemented. The sections that follow describe these patterns
in detail.</p>
<div class="indent">
<h3 class="tutorial"><a name="read"></a>Read</h3>
<p>In this section, you change the <tt>JTextArea</tt>, introduced in the
previous section, for a NetBeans Platform explorer view. NetBeans Platform
explorer views are Swing components that integrate better with the
NetBeans Platform than standard Swing components do. Representing your data
you will have a generic hierarchical model provided by a NetBeans Platform
<tt>Node</tt> class, which can be displayed by any of the NetBeans Platform
explorer views. This section ends with an explanation of how to synchronize
your explorer view with the NetBeans Platform Properties window.
<ol>
<li>In your <tt>TopComponent</tt>, delete the <tt>JTextArea</tt>
in the Design view and comment out its
related code in the Source view:
<pre class="examplecode">EntityManager entityManager = Persistence.createEntityManagerFactory("StatesLibraryPU").createEntityManager();
Query query = entityManager.createQuery("SELECT c FROM States c");
List&lt;States&gt; resultList = query.getResultList();
//for (States c : resultList) {
// jTextArea1.append(c.getName() + " (" + c.getAbbrev() + ")" + "\n");
//}</pre>
<p><li>Right-click the <tt>StatesViewer</tt> module, choose Properties,
and use the Libraries tab to set dependencies on the Nodes API and
the Explorer & Property Sheet API.
<p><li>Next, change the class signature to implement <tt>ExplorerManager.Provider</tt>:
<pre class="examplecode">final class StatesTopComponent extends TopComponent implements ExplorerManager.Provider</pre>
<p>You will need to override <tt>getExplorerManager()</tt>
<pre class="examplecode">@Override
public ExplorerManager getExplorerManager() {
return em;
}</pre>
<p>At the top of the class, declare and initialize the <tt>ExplorerManager</tt>:
<pre class="examplecode">private static ExplorerManager em = new ExplorerManager();</pre>
<p class="tips"> Watch <a href="https://platform.netbeans.org/tutorials/nbm-10-top-apis.html">Top 10 NetBeans APIs</a>
for details on the above code, especially the screencast dealing with the Nodes API
and the Explorer & Property Sheet API.
<p><li>Switch to the <tt>TopComponent</tt> Design view,
right-click in the Palette, choose Palette Manager | Add from JAR. Then browse to
the <tt>org-openide-explorer.jar</tt>, which is in <tt>platform9/modules</tt> folder,
within the NetBeans IDE installation directory. Choose the BeanTreeView and complete
the wizard. You should now see <tt>BeanTreeView</tt> in the Palette. Drag it from
the Palette and drop it on the window.
<p><li>Create a <tt>Node</tt> that models your data:
<pre class="examplecode">import demo.States;
import java.util.List;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.ChildFactory;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
class StateChildFactory extends ChildFactory&lt;States&gt; {
private List&lt;States&gt; resultList;
public StateChildFactory(List&lt;States&gt; resultList) {
this.resultList = resultList;
}
@Override
protected boolean createKeys(List&lt;States&gt; list) {
for (States states : resultList) {
list.add(states);
}
return true;
}
@Override
protected Node createNodeForKey(States s) {
Node node = new AbstractNode(Children.LEAF);
node.setDisplayName(s.getName());
node.setShortDescription(s.getAbbrev());
return node;
}
}</pre>
<p><li>Back in the <tt>StatesTopComponent</tt>,
use the <tt>ExplorerManager</tt> to pass the result list
from the JPA query in to the <tt>Node</tt>:
<pre class="examplecode">EntityManager entityManager = Persistence.createEntityManagerFactory("StatesLibraryPU").createEntityManager();
Query query = entityManager.createQuery("SELECT c FROM States c");
List&lt;States&gt; resultList = query.getResultList();
<b>em.setRootContext(new AbstractNode(Children.create(new StateChildFactory(resultList), true)));</b>
//for (States c : resultList) {
// jTextArea1.append(c.getName() + " (" + c.getAbbrev() + ")" + "\n");
//}</pre>
<p><li><p>Run the application. Once the application is running,
open the Properties window. Notice that even though the data
is available, displayed in a <tt>BeanTreeView</tt>, the <tt>BeanTreeView</tt>
is not synchronized with the Properties window, which is available
via Window | Properties. In other words, nothing is displayed
in the Properties window when you move up and down the tree hierarchy:</p>
<p><img src="../images/tutorials/crud/dbmanager-9.png"/></p>
<p><li>Synchronize the Properties window with the <tt>BeanTreeView</tt>
by adding the following to the constructor in the <tt>TopComponent</tt>:
<pre class="examplecode">ActionMap map = getActionMap();
associateLookup(ExplorerUtils.createLookup(em, map));</pre>
<p>Here we add the <tt>TopComponent</tt>'s <tt>ActionMap</tt> and <tt>ExplorerManager</tt>
to the <tt>Lookup</tt> of the <tt>TopComponent</tt>. A side effect of this is
that the Properties window starts displaying the display name and tooltip text
of the selected <tt>Node</tt>.
<p><li><p>Run the application again and notice that the Properties window
is now synchronized with the explorer view:</p>
<p><img src="../images/tutorials/crud/dbmanager-10.png"/></p>
</ol>
<p>Now you are able to view your data in a tree hierarchy, as you would
be able to do with a <tt>JTree</tt>. However, you're also able to swap
in a different explorer view without needing to change the model at all
because the <tt>ExplorerManager</tt> mediates between the model and the
view. Finally, you are now also able to synchronize the view with the
Properties window.
<h3 class="tutorial"><a name="update"></a>Update</h3>
<p>In this section, you first create an editor. The editor will be provided
by a new NetBeans module. So, you will first create a new module. Then, within
that new module, you will create a new <tt>TopComponent</tt>, containing two <tt>JTextFields</tt>,
for each of the columns you want to let the user edit. You will need to
let the viewer module communicate with the editor module. Whenever a new
<tt>Node</tt> is selected in the viewer module, you will add the current <tt>States</tt>
object to the <tt>Lookup</tt>. In the editor module, you will listen to the
<tt>Lookup</tt> for the introduction of <tt>States</tt> objects. Whenever a
new <tt>States</tt> object is introduced into the <tt>Lookup</tt>, you will
update the <tt>JTextFields</tt> in the editor.
<p>Next, you will synchronize your <tt>JTextFields</tt>
with the NetBeans Platform's Undo, Redo, and Save functionality. In other words,
when the user makes changes to a <tt>JTextField</tt>, you want the
NetBeans Platform's existing functionality to become available so that,
instead of needing to create new functionality, you'll simply be able to
hook into the NetBeans Platform's support. To
this end, you will need to use the <tt>UndoRedoManager</tt>, together with the
<tt>SaveCookie</tt>.
<ol>
<li>Create a new module, named <tt>StatesEditor</tt>, with <tt>org.demo.states.editor</tt> as
its code name base.
<p><li>Right-click the <tt>StatesEditor</tt> module and choose New | Window Component.
Make sure to specify that the window should appear in the <tt>editor</tt> position and
that it should open when the application starts. In the final panel of the wizard,
set "Editor" as the class name prefix.
<p><li><p>Use the Palette (Ctrl-Shift-8) to add two <tt>JLabels</tt> and two <tt>JTextFields</tt>
to the new window. Set the texts of the labels to "State" and "Abbreviation" and
set the variable names of the two <tt>JTextFields</tt> to <tt>nameField</tt>
and <tt>abbrevField</tt>.</p>
<p>In the GUI Builder, the window should now look something like this:</p>
<p><img src="../images/tutorials/crud/dbmanager-11.png"/></p>
<p><li>Go back to the <tt>StatesViewer</tt> module and make sure that its <tt>layer.xml</tt>
file specifies that its window will appear in the <tt>explorer</tt> mode.
<p class="tips"> Right-click the application project and choose "Clean", after
changing the <tt>layer.xml</tt> file. Why? Because whenever you run the
application and close it down, the window positions are stored in the
user directory. Therefore, if the <tt>StatesViewer</tt> was initially
displayed in the <tt>editor</tt> mode, it will remain in the <tt>editor</tt> mode,
until you do a "Clean", thus resetting the user directory (i.e., thus
<i>deleting</i> the user directory) and enabling
the <tt>StatesViewer</tt> to be displayed in the position currently set in the
<tt>layer.xml</tt> file.</p>
<p>Also check
that the <tt>BeanTreeView</tt> in the <tt>StatesViewer</tt> will stretch horizontally
and vertically when the user resizes the application. Check this by opening the window,
selecting the <tt>BeanTreeView</tt>, and then clicking the arrow buttons in the
toolbar of the GUI Builder.
<li>Now we can start adding some code.
Firstly, we need to show the currently selected States object in the editor:
<ul>
<p><li>Start by tweaking the <tt>StatesViewer</tt> module so that the current <tt>States</tt> object
is added to the viewer window's <tt>Lookup</tt> whenever a new <tt>Node</tt>
is selected. Do this by changing the <tt>Node</tt> created by the <tt>StateChildFactory</tt>
such that the current <tt>States</tt> object is added to the <tt>Lookup</tt>
as follows (note the part in bold):
<pre class="examplecode">@Override
protected Node createNodeForKey(States s) {
Node node = new AbstractNode(Children.LEAF<b>, Lookups.singleton(s)</b>);
node.setDisplayName(s.getName());
node.setShortDescription(s.getAbbrev());
return node;
}</pre>
<p>Now, whenever a new <tt>Node</tt> is created, which
happens when the user selects a new state in the viewer, a new
<tt>States</tt> object is added to the <tt>Lookup</tt> of the <tt>Node</tt>.
<p><li>Let's
now change the editor module in such a way that its window
will end up listening for <tt>States</tt> objects being added to the <tt>Lookup</tt>. First,
set a dependency in the editor module on the module that provides
the entity class, as well as the module that provides the persistence
JARs.
<p><li>Next, change the <tt>EditorTopComponent</tt> class signature
to implement <tt>LookupListener</tt>:
<pre class="examplecode">public final class EditorTopComponent extends TopComponent implements LookupListener</pre>
<p><li>Override the
<tt>resultChanged</tt> so that the <tt>JTextFields</tt> are updated whenever
a new <tt>States</tt> object is introduced into the <tt>Lookup</tt>:
<pre class="examplecode">@Override
public void resultChanged(LookupEvent lookupEvent) {
Lookup.Result r = (Lookup.Result) lookupEvent.getSource();
Collection&lt;States&gt; c = r.allInstances();
if (!c.isEmpty()) {
for (States s : c) {
nameField.setText(s.getName());
abbrevField.setText(s.getAbbrev());
}
} else {
nameField.setText("[no state]");
abbrevField.setText("[no abbreviation]");
}
}</pre>
<p><li>Now that the <tt>LookupListener</tt> is defined,
we need to add it to something. Here, we add it to
the <tt>Lookup.Result</tt> obtained from the global context.
The global context proxies the context of the selected <tt>Node</tt>.
For example, if "Missouri" is selected in the tree hierarchy,
the <tt>States</tt> object for "Missouri" is added to the <tt>Lookup</tt>
of the <tt>Node</tt> which, because it is the currently selected <tt>Node</tt>,
means that the <tt>States</tt> object for "Missouri" is now available in
the global context. That is what is then passed to the <tt>resultChanged</tt>,
causing the text fields to be populated.</p>
<p>All of the above starts happening, i.e., the <tt>LookupListener</tt>
becomes active, whenever the editor window is opened, as you can see below:</p>
<pre class="examplecode">@Override
public void componentOpened() {
result = Utilities.actionsGlobalContext().lookupResult(States.class);
result.addLookupListener(this);
resultChanged(new LookupEvent(result));
}
@Override
public void componentClosed() {
result.removeLookupListener(this);
result = null;
}</pre>
<p>Since the editor window is opened when the application starts, the
<tt>LookupListener</tt> is available at the time that the application starts up.
<p><li>Finally, declare the result variable at the top of the class, like this:
<pre class="examplecode">private Lookup.Result result = null;</pre>
<p><li><p>Run the application again and notice that the editor window
is updated whenever you select a new <tt>Node</tt>:</p>
<p><img src="../images/tutorials/crud/dbmanager-12.png"/></p>
<p>However, notice what happens when you switch to the editor window:</p>
<p><img src="../images/tutorials/crud/dbmanager-13.png"/></p>
<p>Because the <tt>Node</tt> is no longer current, the <tt>States</tt> object
is no longer in the global context. This is the case because, as pointed out
above, the global context proxies the <tt>Lookup</tt> of the current <tt>Node</tt>.
Therefore, in this case, we cannot use the global context. Instead, we will
use the local <tt>Lookup</tt> provided by the States window.</p>
<p>Rewrite this line:
<pre class="examplecode">result = Utilities.actionsGlobalContext().lookupResult(States.class);</pre>
<p>To this:
<pre class="examplecode">result = WindowManager.getDefault().findTopComponent("StatesTopComponent").getLookup().lookupResult(States.class);</pre>
<p>The string "StatesTopComponent" is the ID of the <tt>StatesTopComponent</tt>, which
is a string constant that you can find in the source code of the <tt>StatesTopComponent</tt>. One
drawback of the approach above is that now our <tt>EditorTopComponent</tt> only works if it
can find a <tt>TopComponent</tt> with the ID "StatesTopComponent". Either this needs to
be explicitly documented, so that developers of alternative editors can know that they
need to identify the viewer <tt>TopComponent</tt> this way, or you need to rewrite the
selection model, <a href="http://weblogs.java.net/blog/timboudreau/archive/2007/01/how_to_replace.html">as described here</a> by Tim Boudreau.
<p>If you take one of the above approaches, you will find that the context is not lost
when you switch to the <tt>EditorTopComponent</tt>, as shown below:</p>
<p><img src="../images/tutorials/crud/dbmanager-14.png"/></p>
</ul>
<li>Secondly, let's work on the Undo/Redo functionality. What we'd
like to have happen is that whenever the user makes a change to one
of the <tt>JTextFields</tt>, the "Undo" button and the "Redo" button,
as well as the related menu items in the Edit menu, become enabled. To
that end, the NetBeans Platform makes the <a href="http://bits.netbeans.org/dev/javadoc/org-openide-awt/org/openide/awt/UndoRedo.Manager.html">UndoRedo.Manager</a> available.
<ul>
<p><li>Declare and instantiate a new UndoRedoManager at the top of the
<tt>EditorTopComponent</tt>:
<pre class="examplecode">private UndoRedo.Manager manager = new UndoRedo.Manager();</pre>
<p><li>Next, override the <tt>getUndoRedo()</tt> method in the <tt>EditorTopComponent</tt>:
<pre class="examplecode">@Override
public UndoRedo getUndoRedo() {
return manager;
}</pre>
<p><li>In the constructor of the <tt>EditorTopComponent</tt>, add
a <tt>KeyListener</tt> to the <tt>JTextFields</tt> and, within
the related methods that you need to implement, add the <tt>UndoRedoListeners</tt>:
<pre class="examplecode">nameField.addKeyListener(new KeyListener() {
public void keyTyped(KeyEvent e) {
nameField.getDocument().addUndoableEditListener(manager);
abbrevField.getDocument().addUndoableEditListener(manager);
}
public void keyPressed(KeyEvent e) {
nameField.getDocument().addUndoableEditListener(manager);
abbrevField.getDocument().addUndoableEditListener(manager);
}
public void keyReleased(KeyEvent e) {
nameField.getDocument().addUndoableEditListener(manager);
abbrevField.getDocument().addUndoableEditListener(manager);
}
});
abbrevField.addKeyListener(new KeyListener() {
public void keyTyped(KeyEvent e) {
nameField.getDocument().addUndoableEditListener(manager);
abbrevField.getDocument().addUndoableEditListener(manager);
}
public void keyPressed(KeyEvent e) {
nameField.getDocument().addUndoableEditListener(manager);
abbrevField.getDocument().addUndoableEditListener(manager);
}
public void keyReleased(KeyEvent e) {
nameField.getDocument().addUndoableEditListener(manager);
abbrevField.getDocument().addUndoableEditListener(manager);
}
});</pre>
<p><li>Run the application and show the Undo and Redo functionality in action,
the buttons as well as the menu items:</p>
<p><img src="../images/tutorials/crud/dbmanager-15.png"/></p>
<p>The functionality works exactly as you would expect. You might want to
change the <tt>KeyListener</tt> so that not ALL keys cause the undo/redo
functionality to be enabled. For example, when Enter is pressed, you probably
do not want the undo/redo functionality to become available. Therefore, tweak
the code above to suit your business requirements.</p>
</ul>
<li>Thirdly, we need to
integrate with the NetBeans Platform's Save functionality:
<ul>
<p><li>By default, the "Save All" button is available in the
NetBeans Platform toolbar. In our current scenario, we do not
want to save "all", because "all" refers to a number of different
documents. In our case, we only have one "document", which is the
editor that we are reusing for all the nodes in the tree hirerarchy.
Remove the "Save All" button and add the "Save" button instead, by adding
the following to the layer file of the <tt>StatesEditor</tt> module:
<pre class="examplecode">&lt;folder name="Toolbars"&gt;
&lt;folder name="File"&gt;
&lt;file name="org-openide-actions-SaveAllAction.instance_hidden"/&gt;
&lt;file name="org-openide-actions-SaveAction.instance"/&gt;
&lt;/folder&gt;
&lt;/folder&gt;</pre>
<p>When you now run the application, you will see a different icon
in the toolbar. Instead of the "Save All" button, you now have
the "Save" button available.
<p><li>Set dependencies on the Dialogs API and the Nodes API.
<p><li>Create a new <tt>Node</tt>. We will call it "DummyNode", since
it is only a <tt>Node</tt> in so far as that is how Save functionality
is added to a NetBeans Platform application, i.e., by creating a
new <tt>Node</tt>, one that adds new implementations of <tt>SaveCookie</tt>
to its set of capabilities, which is then set as the activated <tt>Node</tt>
of the <tt>TopComponent</tt>.
<pre class="examplecode">private class DummyNode extends AbstractNode {
SaveCookieImpl impl;
public DummyNode() {
super(Children.LEAF);
impl = new SaveCookieImpl();
}
public void fire(boolean modified) {
if (modified) {
<b>//If the text is modified,
//we implement SaveCookie,
//and add the implementation to the cookieset,
//which defines the capabilities of the Node,
//in this case, the capability of being saved:</b>
getCookieSet().assign(SaveCookie.class, impl);
} else {
<b>//Otherwise, we make no assignment
//and the SaveCookie is not set as
//one of the capabilities of the Node:</b>
getCookieSet().assign(SaveCookie.class);
}
}
private class SaveCookieImpl implements SaveCookie {
public void save() throws IOException {
Confirmation msg = new NotifyDescriptor.Confirmation("Do you want to save \"" +
nameField.getText() + " (" + abbrevField.getText() + ") " + "\"?",
NotifyDescriptor.OK_CANCEL_OPTION,
NotifyDescriptor.QUESTION_MESSAGE);
Object result = DialogDisplayer.getDefault().notify(msg);
<b>//When the user clicks "Yes", indicating they really want to save,
//we need to disable the Save button and Save menu item,
//so that it will only be usable when the next change is made
//to the text field:</b>
if (NotifyDescriptor.YES_OPTION.equals(result)) {
fire(false);
<b>//We will add the Save handling code here.</b>
}
}
}
}</pre>
<p><li>Declare the <tt>Node</tt> at the top of the <tt>TopComponent</tt> class:
<pre class="examplecode">private DummyNode dummyNode;</pre>
<p>Now, in the constructor of the <tt>TopComponent</tt>, add it
to the <tt>TopComponent</tt>'s activated nodes:
<pre class="examplecode">setActivatedNodes(new Node[]{dummyNode = new DummyNode()});</pre>
<p><li>Next, we need to fire a change in the <tt>DummyNode</tt> whenever
a change is made in the <tt>JTextFields</tt>, which in turn adds
an implementation of <tt>SaveCookie</tt> to the capabilities of
the activated <tt>Node</tt>, which is our "DummyNode":
<pre class="examplecode">nameField.addKeyListener(new KeyListener() {
public void keyTyped(KeyEvent e) {
nameField.getDocument().addUndoableEditListener(manager);
abbrevField.getDocument().addUndoableEditListener(manager);
<b>dummyNode.fire(true);</b>
}
public void keyPressed(KeyEvent e) {
nameField.getDocument().addUndoableEditListener(manager);
abbrevField.getDocument().addUndoableEditListener(manager);
<b>dummyNode.fire(true);</b>
}
public void keyReleased(KeyEvent e) {
nameField.getDocument().addUndoableEditListener(manager);
abbrevField.getDocument().addUndoableEditListener(manager);
<b>dummyNode.fire(true);</b>
}
});
abbrevField.addKeyListener(new KeyListener() {
public void keyTyped(KeyEvent e) {
nameField.getDocument().addUndoableEditListener(manager);
abbrevField.getDocument().addUndoableEditListener(manager);
<b>dummyNode.fire(true);</b>
}
public void keyPressed(KeyEvent e) {
nameField.getDocument().addUndoableEditListener(manager);
abbrevField.getDocument().addUndoableEditListener(manager);
<b>dummyNode.fire(true);</b>
}
public void keyReleased(KeyEvent e) {
nameField.getDocument().addUndoableEditListener(manager);
abbrevField.getDocument().addUndoableEditListener(manager);
<b>dummyNode.fire(true);</b>
}
});</pre>
<p><li><p>Run the application and notice the enablement/disablement of the
Save button:</p>
<p><img src="../images/tutorials/crud/dbmanager-16.png"/></p>
<p class="tips"> Right now, nothing happens when you click OK in the dialog
above. In the next step, we add some JPA code for handling persistence of
our changes.
<p><li>Next, we add JPA code for persisting our change.
Do so by replacing the comment "//We will add the Save handling code here."
The comment should be replaced by all of the following:
<pre class="examplecode">EntityManager entityManager = Persistence.createEntityManagerFactory("StatesLibraryPU").createEntityManager();
entityManager.getTransaction().begin();
States states = entityManager.find(States.class, s.getId());
states.setName(nameField.getText());
states.setAbbrev(abbrevField.getText());
entityManager.getTransaction().commit();</pre>
<p class="tips"> The "s" in <tt>s.getId()</tt> is currently undefined. Redefine
the <tt>resultChanged</tt> as follows, after declaring <tt>States s;</tt> at the top
of the class, so that the current <tt>States</tt> object sets the <tt>s</tt>,
which is then used in the persistence code above to obtain the ID of
the current <tt>States</tt> object.
<pre class="examplecode">@Override
public void resultChanged(LookupEvent lookupEvent) {
Lookup.Result r = (Lookup.Result) lookupEvent.getSource();
Collection&lt;States&gt; c = r.allInstances();
if (!c.isEmpty()) {
for (States states : c) {
s = states;
nameField.setText(states.getName());
abbrevField.setText(states.getAbbrev());
}
} else {
nameField.setText("[no state]");
abbrevField.setText("[no abbreviation]");
}
}</pre>
<p><li><p>Run the application and change some data. Currently, we have no "Refresh"
functionality so, to see the changed data, restart the application. Here, for
example, the tree hierarchy shows the persisted state name for "Missouri":</p>
<p><img src="../images/tutorials/crud/dbmanager-17.png"/></p>
</ul>
<li>Fourthly, we need to
add functionality for refreshing the States viewer. You might want to
add a <tt>Timer</tt> which periodically refreshes the viewer.
However, in this example, we will add
a "Refresh" menu item to the Root node so that the user will be able
to manually refresh the viewer.
<ul>
<p><li>In the main package of the <tt>StatesViewer</tt> module,
create a new <tt>Node</tt>, which will replace the <tt>AbstractNode</tt>
that we are currently using as the root of the children in the
viewer. Note that we also bind a "Refresh" action to our
new root node.
<pre class="examplecode">class StatesRootNode extends AbstractNode {
public StatesRootNode(Children kids) {
super(kids);
setDisplayName("Root");
}
@Override
public Action[] getActions(boolean context) {
Action[] result = new Action[]{
new RefreshAction()};
return result;
}
private final class RefreshAction extends AbstractAction {
public RefreshAction() {
putValue(Action.NAME, "Refresh");
}
public void actionPerformed(ActionEvent e) {
StatesTopComponent.refreshNode();
}
}
}</pre>
<p><li>Add this method to the <tt>StatesTopComponent</tt>, for refreshing
the view:
<pre class="examplecode">public static void refreshNode() {
EntityManager entityManager = Persistence.createEntityManagerFactory("StatesLibraryPU").createEntityManager();
Query query = entityManager.createQuery("SELECT c FROM States c");
List&lt;States&gt; resultList = query.getResultList();
em.setRootContext(new <b>StatesRootNode</b>(Children.create(new StateChildFactory(resultList), true)));
}</pre>
<p>Now replace the code above in the constructor
of the <tt>StatesTopComponent</tt> with a call to the above. As
you can see in the highlighted part above, we are now using our <tt>StatesRootNode</tt> instead
of the <tt>AbstractNode</tt>. The <tt>StatesRootNode</tt> includes
the "Refresh" action, which calls the code above.
<p><li><p>Run the application again and notice that you have a new root node,
with a "Refresh" action:</p>
<p><img src="../images/tutorials/crud/dbmanager-18.png"/></p>
<p><li>Make a change to some data, save it, invoke the Refresh action, and notice
that the viewer is updated.
</ul>
</ol>
<p>You have now learned how to let the NetBeans Platform handle changes
to the <tt>JTextFields</tt>. Whenever the text changes, the NetBeans
Platform Undo and Redo buttons are enabled or disabled. Also, the
Save button is enabled and disabled correctly, letting the user
save changed data back to the database.
<h3 class="tutorial"><a name="create"></a>Create</h3>
<p>In this section, you allow the user to create a new entry in the database.
<ol>
<li><p>Right-click the <tt>StatesEditor</tt>
module and choose "New Action".
Use the New Action wizard to create a new "Always Enabled" action. The new
action should be displayed in the toolbar and in
the menu bar.</p>
<p><img border="1" src="../images/tutorials/crud/dbmanager-19.png"/></p>
<p>In the next step of the wizard, call the action <tt>NewAction</tt>:</p>
<p><img border="1" src="../images/tutorials/crud/dbmanager-20.png"/></p>
<p class="tips">Make sure that
you have a 16x16 icon available, which the wizard forces
you to select if you indicate that you want the
action to be invoked from the toolbar.</p>
<P><li>In the New action, let the <tt>TopComponent</tt> be opened,
together with emptied <tt>JTextFields</tt>:
<pre class="examplecode">import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public final class NewAction implements ActionListener {
public void actionPerformed(ActionEvent e) {
EditorTopComponent tc = EditorTopComponent.getDefault();
tc.resetFields();
tc.open();
tc.requestActive();
}
}</pre>
<p class="tips">The action implements the <tt>ActionListener</tt> class, which
is bound to the application via entries in the layer file, put there
by the New Action wizard. Imagine how easy it will be when you
port your existing Swing application to the NetBeans Platform, since
you'll simply be able to use the same <tt>Action</tt> classes that you
used in your original application, without needing to rewrite them
to conform to <tt>Action</tt> classes provided by the NetBeans Platform!</p>
<p>In the <tt>EditorTopComponent</tt>, add the following method for resetting
the <tt>JTextFields</tt> and creating a new <tt>States</tt> object:
<pre class="examplecode">public void resetFields() {
s = new States();
nameField.setText("");
abbrevField.setText("");
}</pre>
<p><li>In the <tt>SaveCookie</tt>, ensure that a return of <tt>null</tt>
indicates that a new entry is saved, instead of an existing entry
being updated:
<pre>public void save() throws IOException {
Confirmation msg = new NotifyDescriptor.Confirmation("Do you want to save \"" +
nameField.getText() + " (" + abbrevField.getText() + ") " + "\"?", NotifyDescriptor.OK_CANCEL_OPTION,
NotifyDescriptor.QUESTION_MESSAGE);
Object result = DialogDisplayer.getDefault().notify(msg);
//When user clicks "Yes", indicating they really want to save,
//we need to disable the Save button and Save menu item,
//so that it will only be usable when the next change is made
//to the text field:
if (NotifyDescriptor.YES_OPTION.equals(result)) {
fire(false);
EntityManager entityManager = Persistence.createEntityManagerFactory("StatesLibraryPU").createEntityManager();
entityManager.getTransaction().begin();
<b>if (s.getId() != null)</b> {
States states = entityManager.find(States.class, s.getId());
states.setName(nameField.getText());
states.setAbbrev(abbrevField.getText());
entityManager.getTransaction().commit();
} else {
<b>Query query = entityManager.createQuery("SELECT c FROM States c");
List&lt;States&gt; resultList = query.getResultList();
s.setId(resultList.size()+1);
s.setName(nameField.getText());
s.setAbbrev(abbrevField.getText());
entityManager.persist(s);
entityManager.getTransaction().commit();</b>
}
}
}</pre>
<p><li><p>Run the application again and add a new state to the database:</p>
<p><img src="../images/tutorials/crud/dbmanager-21.png"/></p>
<p class="tips">When you refresh the data, you will find that
new entries are added to the bottom of the list, because they
are sorted by their ID number. So, "Disneyland" is added right
at the end, rather than in its alphabetical position.</p>
</ol>
<h3 class="tutorial"><a name="delete"></a>Delete</h3>
<p>In this section, let the user delete a selected entry in the database. Using
the concepts and code outlined above, implement the Delete action yourself.
<ol>
<li>Create a new action, <tt>DeleteAction</tt>. Decide whether you
want to bind it to a State node or whether you'd rather bind it
to the toolbar, the menu bar, or both. Depending on where you bind
it, you will need to use a different NetBeans Platform class. Read the
tutorial again for help, especially by looking at how the "New" action
was created, while comparing it to the "Refresh" action on the root
node.
<p><li>Get the current <tt>States</tt> object, return an 'Are you sure?' dialog,
and then delete the entry. For help on this point, read the tutorial
again, focusing on the part where the "Save" functionality is implemented.
Instead of saving, you now want to delete an entry from the database.
</ol>
</div>
<!-- ======================================================================================== -->
<h2><a name="related-screencasts"></a>Related Screencasts</h2>
<p>The first screencast explains how to create a view on the
NetBeans Platform on top of your database:
<div id="videoPlayer"></div>
<script type="text/javascript">
<!--
var so = new SWFObject("http://www.netbeans.tv/images/playerWidget.swf" , "pv", "410", "331", "8", "#666666");
so.addParam("quality", "high");
so.addParam("allowScriptAccess", "sameDomain");
so.addParam("allowFullScreen", "true");
so.addVariable("autoPlay", "false");
so.addVariable("urlFileIdList", "6807");
so.addVariable("show_title", "true");
so.addVariable("PLAY_CALLBACK","emphasize");
so.addVariable("resizeLimitMax","true");
so.addVariable("lang", "en");
so.addVariable("flash_play","true");
so.addVariable("author_name","NetBeans.tv");
so.addVariable("configUrl","http://sun.ora.ma/config.sun.ora.ma.xml");
so.write("videoPlayer");
// -->
</script>
<p><p>The second screencast shows how to create the related editor. (To come.)
<!-- ======================================================================================== -->
<h2><a name="nextsteps"></a>See Also</h2>
<p>This concludes the NetBeans Platform CRUD Tutorial. This document has described
how to create a new NetBeans Platform application with CRUD functionality for
a given database.
For more information about creating and developing applications, see the following resources:
<ul>
<p><li><a href="https://netbeans.org/kb/trails/platform.html">NetBeans Platform Learning Trail</a></li>
<p><li><a href="http://bits.netbeans.org/dev/javadoc/">NetBeans API Javadoc</a></li>
</ul>
<!-- ======================================================================================== -->
</body>
</html>