blob: 9b208b21fbe08293070a83b655baaf660fdec1f6 [file] [log] [blame]
<!doctype html>
<html class="no-js" lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>NetBeans Platform Feed Reader Tutorial</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="NetBeans Platform Feed Reader Tutorial - Apache NetBeans">
<meta name="author" content="Apache NetBeans">
<meta name="description" content="NetBeans Platform Feed Reader Tutorial - Apache NetBeans">
<meta name="keywords" content="Apache NetBeans Platform, Platform Tutorials, NetBeans Platform Feed Reader Tutorial">
<meta name="generator" content="Apache NetBeans">
<link rel="stylesheet" href="../../../../_/css/font-awesome.min.css">
<link rel="alternate" type="application/atom+xml" title="Apache NetBeans Blog" href="https://netbeans.apache.org/blogs/atom" />
<link rel="stylesheet" href="../../../../_/css/highlightjs/default.min.css">
<link rel="stylesheet" href="../../../../_/css/netbeans.css">
<link rel="apple-touch-icon" sizes="180x180" href="../../../../_/images/fav/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="../../../../_/images/fav/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="../../../../_/images/fav/favicon-16x16.png">
<link rel="manifest" href="../../../../_/images/fav/site.webmanifest">
<link rel="mask-icon" href="../../../../_/images/fav/safari-pinned-tab.svg" color="#5bbad5">
<meta name="msapplication-TileColor" content="#ffc40d">
<meta name="theme-color" content="#ffffff">
<link href="../../../../_/css/font-open-sans.css" rel="stylesheet">
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
</head>
<body>
<div class="title-bar" data-responsive-toggle="responsive-menu" data-hide-for="medium">
<button type="button" data-toggle="responsive-menu"><i style='font-size: 32px; color: #fff; padding: 8px' class='fa fa-bars'></i></button>
<div class="title-bar-title">Apache NetBeans</div>
</div>
<div class="top-bar" id="responsive-menu">
<div class='top-bar-left'>
<a class='title' href="../../../../index.html"><img src='../../../../_/images/apache-netbeans.svg' style='padding: 8px; height: 48px;'> Apache NetBeans</a>
</div>
<div class="top-bar-right">
<ul class="vertical medium-horizontal menu" data-responsive-menu="drilldown medium-dropdown">
<li> <input id="search-input" type="text" placeholder="Search the docs"> </li>
<li> <a href="../../../../front/main/community">Community</a> </li>
<li> <a href="../../../../front/main/participate">Participate</a> </li>
<li> <a href="../../../../front/main/blogs">Blog</a></li>
<li> <a href="../../../../front/main/help">Get Help</a> </li>
<li> <a href="https://plugins.netbeans.apache.org/">Plugins</a> </li>
<li> <a href="../../../../front/main/download">Download</a> </li>
</ul>
</div>
</div>
<!-- src/templates/news -->
<section class="hero news alternate">
<div class='grid-container'>
<div class='cell'>
<div class="annotation">Latest release</div>
<h1>Apache NetBeans 27</h1>
<p><a class="button success" href="../../../../front/main/download/nb27">Download</a></p>
</div>
</div>
</section>
<div class='grid-container main-content tutorial'>
<h1 class="sect0">NetBeans Platform Feed Reader Tutorial</h1>
<div class="sectionbody">
<div class="admonitionblock note">
<table>
<tbody><tr>
<td class="icon"><i class="fa icon-note" title="Note"></i></td>
<td class="content">This tutorial needs a review.
You can <a href="https://github.com/apache/netbeans-antora-tutorials/edit/main/modules/ROOT/pages/tutorials/nbm-feedreader.adoc" title="Edit this tutorial in github">edit it in GitHub </a>
following these <a href="../../../../tutorial/main/kb/docs/contributing">contribution guidelines.</a></td>
</tr></tbody>
</table>
</div>
</div>
<div id="toc" class="toc">
<div id="toctitle"></div>
<ul class="sectlevel1">
<li><a href="#_setting_up_the_application">Setting Up the Application</a>
<ul class="sectlevel2">
<li><a href="#_creating_the_application_skeleton">Creating the Application Skeleton</a></li>
<li><a href="#_wrapping_the_libraries">Wrapping the Libraries</a></li>
<li><a href="#_creating_the_module_project">Creating the Module Project</a></li>
<li><a href="#_specifying_the_applications_dependencies">Specifying the Application&#8217;s Dependencies</a></li>
<li><a href="#_setting_dependencies_between_library_wrapper_modules">Setting Dependencies Between Library Wrapper Modules</a></li>
</ul>
</li>
<li><a href="#_creating_the_feed_reader_window">Creating the Feed Reader Window</a></li>
<li><a href="#_creating_the_model">Creating the Model</a></li>
<li><a href="#_creating_the_node_hierarchy">Creating the Node Hierarchy</a>
<ul class="sectlevel2">
<li><a href="#_creating_the_first_level_node_the_root_node">Creating the First Level Node: The Root Node</a></li>
<li><a href="#_creating_the_children_of_the_root_node">Creating the Children of the Root Node</a></li>
<li><a href="#_creating_the_second_level_node_the_feed_node">Creating the Second Level Node: The Feed Node</a></li>
<li><a href="#_creating_the_children_of_the_feed_node">Creating the Children of the Feed Node</a></li>
<li><a href="#_creating_the_third_level_node_the_entry_node">Creating the Third Level Node: The Entry Node</a></li>
</ul>
</li>
<li><a href="#_displaying_the_node_hierarchy_in_the_feed_window">Displaying the Node Hierarchy in the Feed Window</a></li>
<li><a href="#_creating_the_actions">Creating the Actions</a>
<ul class="sectlevel2">
<li><a href="#_creating_the_add_feed_action">Creating the Add Feed Action</a></li>
<li><a href="#_creating_the_add_folder_action">Creating the Add Folder Action</a></li>
<li><a href="#_using_the_actions">Using the Actions</a></li>
</ul>
</li>
</ul>
</div>
<div id="preamble">
<div class="sectionbody">
<div class="paragraph">
<p>Welcome to the NetBeans Platform Feed Reader tutorial. The Feed Reader that you create in this tutorial is a simple RSS/Atom feed browser, modeled after the Sage plug-in for Mozilla Firefox. It presents a tree of feeds with subnodes representing individual feed entries that you can open in a browser.</p>
</div>
<div class="paragraph">
<p>To illustrate the end result, here you see the Feed Reader that you will create in this tutorial, displaying a feed entry from the <a href="http://planetnetbeans.org/rss20.xml">Planet NetBeans feed</a>:</p>
</div>
<div class="imageblock">
<div class="content">
<img src="../../_images/tutorials/feedreader_73_result-6.png" alt="feedreader 73 result 6">
</div>
</div>
<div class="paragraph">
<p>This is <span class="underline">not</span> a beginner&#8217;s tutorial. The assumption is that you are familiar with, and have used, NetBeans Platform idioms such as " <a href="../../../../wiki/main/wiki/#_nodes_and_explorer" class="xref page">Node</a>" and " <a href="../../../../wiki/main/wiki/#_lookup" class="xref page">Lookup</a>". The <a href="../../kb/docs/platform/" class="xref page">NetBeans Platform Learning Trail</a> provides several quick starts and other beginner materials that cover these topics in detail.</p>
</div>
<div class="paragraph">
<p>For troubleshooting purposes, you are welcome to download the <a href="http://web.archive.org/web/20170409072842/http://java.net/projects/nb-api-samples/show/versions/8.0/tutorials/FeedReader">completed tutorial source code</a>.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_setting_up_the_application"><a class="anchor" href="#_setting_up_the_application"></a>Setting Up the Application</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Let&#8217;s start with a quick revision of some basic concepts. Firstly, building an application on top of the NetBeans Platform starts with generating a number of folders and files which serve as the foundation of your application. For example, NetBeans IDE provides several project wizards that set up all the basic files needed by modules and applications built on the NetBeans Platform.</p>
</div>
<div class="ulist">
<ul>
<li>
<p><strong>NetBeans Platform Application.</strong> A project that groups a set of module projects and library wrapper module projects that have dependencies on each other, and lets you deploy them together as a unit. Automatically included are a subset of the modules that make up the NetBeans Platform.</p>
</li>
<li>
<p><strong>Module Suite.</strong> Same as above, except that the pre-included modules are more than only those relating to the NetBeans Platform—in this case, all the modules that make up NetBeans IDE are included as well.</p>
</li>
<li>
<p><strong>Library Wrapper Module.</strong> A project that puts a library JAR file on its classpath and exports some or all of the JAR file&#8217;s packages from the module as public packages.</p>
</li>
<li>
<p><strong>Module.</strong> A project for implementing the functionality, business logic, and user interface of a module or application built on the NetBeans Platform.</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>Now we&#8217;ll begin creating our own application, the Feed Reader.</p>
</div>
<div class="sect2">
<h3 id="_creating_the_application_skeleton"><a class="anchor" href="#_creating_the_application_skeleton"></a>Creating the Application Skeleton</h3>
<div class="olist arabic">
<ol class="arabic" start="1">
<li>
<p>Choose File &gt; New Project (Ctrl-Shift-N). Under Categories, select NetBeans Modules. Under Projects, select NetBeans Platform Application. You should see the following:</p>
</li>
</ol>
</div>
<div class="imageblock">
<div class="content">
<img src="../../_images/tutorials/feedreader_73_paintapp-proj-wiz-1.png" alt="feedreader 73 paintapp proj wiz 1">
</div>
</div>
<div class="paragraph">
<p>Depending on the modules installed in your IDE, different categories may be shown in the Categories list above.</p>
</div>
<div class="paragraph">
<p>Click Next.</p>
</div>
<div class="olist arabic">
<ol class="arabic" start="2">
<li>
<p>In the Name and Location panel, type <code>FeedReader</code> in Project Name. Change the Project Location to any directory on your computer:</p>
</li>
</ol>
</div>
<div class="imageblock">
<div class="content">
<img src="../../_images/tutorials/feedreader_73_paintapp-proj-wiz-2.png" alt="feedreader 73 paintapp proj wiz 2">
</div>
</div>
<div class="paragraph">
<p>Click Finish.</p>
</div>
<div class="paragraph">
<p>The IDE creates the <code>FeedReader</code> project, which looks as follows in the Projects window:</p>
</div>
<div class="imageblock">
<div class="content">
<img src="../../_images/tutorials/feedreader_73_paintapp-proj-wiz-3.png" alt="feedreader 73 paintapp proj wiz 3">
</div>
</div>
<div class="paragraph">
<p>The project will contain the custom module projects that you will create in the following sections.</p>
</div>
</div>
<div class="sect2">
<h3 id="_wrapping_the_libraries"><a class="anchor" href="#_wrapping_the_libraries"></a>Wrapping the Libraries</h3>
<div class="paragraph">
<p>You could bundle the entire Feed Reader application into a single module. However, the application needs the Rome, Rome Fetcher, and JDom libraries:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><strong>Rome.</strong> Reads RSS and Atom feeds, using a very simple API.</p>
</li>
<li>
<p><strong>Rome Fetcher.</strong> Allows the retrieval of feeds via HTTP.</p>
</li>
<li>
<p><strong>JDom.</strong> Is an XML parsing API. The only reason the Feed Reader will need it is because the Rome library uses it.</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>Later, if you want to extend the Feed Reader application with more modules that may use these libraries, it would be better for them to depend on just the library modules, rather than the entire Feed Reader. Also, library modules can be "autoloading", which means that NetBeans will only load them when needed. Until that happens, it won&#8217;t take up any memory at runtime.</p>
</div>
<div class="olist arabic">
<ol class="arabic" start="1">
<li>
<p>Right-click the Modules node in the project in the Projects window, as shown below, and click Add New Library:</p>
</li>
</ol>
</div>
<div class="imageblock">
<div class="content">
<img src="../../_images/tutorials/feedreader_73_paintapp-proj-wiz-4.png" alt="feedreader 73 paintapp proj wiz 4">
</div>
</div>
<div class="olist arabic">
<ol class="arabic" start="2">
<li>
<p>In the Select Library panel, browse to the folder where you downloaded JDom, and then select <code>jdom.jar</code> and <code>LICENSE.txt.</code> Click Next.</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>1.
In the Name and Location panel, accept all the defaults. The library wrapper module project will be stored within the application project. You could also store it somewhere else, but for versioning purposes it is a good idea to put it within the application project. Therefore, the <code>FeedReader</code> application project is selected in the Add to Module Suite drop-down. Click Next.</p>
</div>
<div class="olist arabic">
<ol class="arabic" start="4">
<li>
<p>In the Basic Module Configuration panel, type <code>org.jdom</code> as the code name base and leave all the other defaults unchanged. Click Finish. The new library wrapper module project opens in the IDE and displays in the Projects window. You should now see the following in the Projects window:</p>
</li>
</ol>
</div>
<div class="imageblock">
<div class="content">
<img src="../../_images/tutorials/feedreader_73_paintapp-proj-wiz-5.png" alt="feedreader 73 paintapp proj wiz 5">
</div>
</div>
<div class="paragraph">
<p>A frequent point of confusion is that you see two different "jdom" nodes above, each accompanied by a purple icon. The first of these, above, shows the relationship of the "jdom" module to the whole application. The second is the "jdom" project itself, containing its sources and libraries. This pattern is used throughout the application, with each module being shown in two different ways, as above.</p>
</div>
<div class="olist arabic">
<ol class="arabic" start="5">
<li>
<p>Return to step 1 of this section and create a library wrapper module project for Rome. Use code name base "org.rome" and accept all the other defaults.</p>
</li>
</ol>
</div>
<div class="olist arabic">
<ol class="arabic" start="6">
<li>
<p>Return to step 1 of this section and create a library wrapper module project for Rome Fetcher. Use code name base "org.fetcher" and accept all the other defaults.</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>You now have an application skeleton, with three library wrapper module projects, providing many useful Java classes that you will be able to make use of throughout this tutorial:</p>
</div>
<div class="imageblock">
<div class="content">
<img src="../../_images/tutorials/feedreader_73_paintapp-proj-wiz-6.png" alt="feedreader 73 paintapp proj wiz 6">
</div>
</div>
</div>
<div class="sect2">
<h3 id="_creating_the_module_project"><a class="anchor" href="#_creating_the_module_project"></a>Creating the Module Project</h3>
<div class="paragraph">
<p>In this section, we create a project for the functionality that our application will provide. The project will make use of the classes made available by the library wrapper modules that we created in the previous section.</p>
</div>
<div class="olist arabic">
<ol class="arabic" start="1">
<li>
<p>Right-click the Modules node in the application project in the Projects window, as shown below, and click Add New:</p>
</li>
</ol>
</div>
<div class="imageblock">
<div class="content">
<img src="../../_images/tutorials/feedreader_73_new-mod-1.png" alt="feedreader 73 new mod 1">
</div>
</div>
<div class="olist arabic">
<ol class="arabic" start="2">
<li>
<p>In the Name and Location panel, type <code>FeedReader</code> in Project Name. Accept all the other defaults. Click Next.</p>
</li>
</ol>
</div>
<div class="olist arabic">
<ol class="arabic" start="3">
<li>
<p>In the Basic Module Configuration panel, type <code>org.myorg.feedreader</code> in Code Name Base.</p>
</li>
</ol>
</div>
<div class="olist arabic">
<ol class="arabic" start="4">
<li>
<p>Do not select "Generate OSGi Bundle". Click Finish.</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>The IDE creates the FeedReader project. The project contains all of the module&#8217;s sources and project metadata, such as the project&#8217;s Ant build script. The project opens in the IDE. You can view its logical structure in the Projects window (Ctrl-1) and its file structure in the Files window (Ctrl-2). The Projects window should now show the following:</p>
</div>
<div class="imageblock">
<div class="content">
<img src="../../_images/tutorials/feedreader_73_new-mod-2.png" alt="feedreader 73 new mod 2">
</div>
</div>
</div>
<div class="sect2">
<h3 id="_specifying_the_applications_dependencies"><a class="anchor" href="#_specifying_the_applications_dependencies"></a>Specifying the Application&#8217;s Dependencies</h3>
<div class="paragraph">
<p>You need to subclass several classes that belong to the NetBeans APIs. The classes belong to modules that need to be declared as dependencies of your Feed Reader application. Use the Project Properties dialog box for this purpose, as explained in the steps below.</p>
</div>
<div class="olist arabic">
<ol class="arabic" start="1">
<li>
<p>In the Projects window, right-click the <code>FeedReader</code> module project and choose Properties. In the Project Properties dialog box, click Libraries.</p>
</li>
</ol>
</div>
<div class="olist arabic">
<ol class="arabic" start="2">
<li>
<p>Click Add Dependency. You will need the following APIs. Click the links below for further information on each of the APIs you will be using.</p>
<div class="ulist">
<ul>
<li>
<p><a href="https://bits.netbeans.org/dev/javadoc/org-openide-actions/org/openide/actions/doc-files/api.html">Actions APIs</a></p>
</li>
<li>
<p><a href="https://bits.netbeans.org/dev/javadoc/org-openide-loaders/org/openide/loaders/doc-files/api.html">Datasystems API</a></p>
</li>
<li>
<p><a href="https://bits.netbeans.org/dev/javadoc/org-openide-dialogs/org/openide/package-summary.html">Dialogs API</a></p>
</li>
<li>
<p><a href="https://bits.netbeans.org/dev/javadoc/org-openide-explorer/org/openide/explorer/doc-files/api.html">Explorer &amp; Property Sheet API</a></p>
</li>
<li>
<p><a href="https://bits.netbeans.org/dev/javadoc/org-openide-filesystems/org/openide/filesystems/doc-files/api.html">File System API</a></p>
</li>
<li>
<p><a href="https://bits.netbeans.org/dev/javadoc/org-openide-util-lookup/org/openide/util/lookup/doc-files/lookup-api.html">Lookup API</a></p>
</li>
<li>
<p><a href="https://bits.netbeans.org/dev/javadoc/org-openide-nodes/org/openide/nodes/doc-files/api.html">Nodes API</a></p>
</li>
<li>
<p>rome</p>
</li>
<li>
<p>rome-fetcher</p>
</li>
<li>
<p><a href="https://bits.netbeans.org/dev/javadoc/org-netbeans-modules-settings/overview-summary.html">Settings API</a></p>
</li>
<li>
<p><a href="https://bits.netbeans.org/dev/javadoc/org-openide-awt/overview-summary.html">UI Utilities API</a></p>
</li>
<li>
<p><a href="https://bits.netbeans.org/dev/javadoc/org-openide-util/overview-summary.html">Utilities API</a></p>
</li>
<li>
<p><a href="https://bits.netbeans.org/dev/javadoc/org-openide-windows/org/openide/windows/package-summary.html">Window System API</a></p>
</li>
</ul>
</div>
</li>
</ol>
</div>
<div class="paragraph">
<p>Click OK to exit the Project Properties dialog box.</p>
</div>
<div class="olist arabic">
<ol class="arabic" start="3">
<li>
<p>Expand the <code>FeedReader</code> project&#8217;s Libraries node and notice the list of modules that are now available to this project:</p>
</li>
</ol>
</div>
<div class="imageblock">
<div class="content">
<img src="../../_images/tutorials/feedreader_73_new-dep-1.png" alt="feedreader 73 new dep 1">
</div>
</div>
</div>
<div class="sect2">
<h3 id="_setting_dependencies_between_library_wrapper_modules"><a class="anchor" href="#_setting_dependencies_between_library_wrapper_modules"></a>Setting Dependencies Between Library Wrapper Modules</h3>
<div class="paragraph">
<p>Now that we have set dependencies on the NetBeans API modules that we will use, let&#8217;s also set dependencies between our library wrapper modules. For example, the Rome JAR makes use of classes from the JDom JAR. Now that these are wrapped in separate library wrapper modules, we need to specify the relationship between the JARs via the library wrapper module&#8217;s Project Properties dialog box.</p>
</div>
<div class="olist arabic">
<ol class="arabic" start="1">
<li>
<p>First, lets make Rome dependent on JDom. Right-click the Rome library wrapper module project in the Projects window and choose Properties. In the Project Properties dialog box, click Libraries and then click Add Dependency. Add <code>jdom</code> . Click OK to exit the Project Properties dialog box. When you expand the Libraries node in the Rome project, you should now see the following:</p>
</li>
</ol>
</div>
<div class="imageblock">
<div class="content">
<img src="../../_images/tutorials/feedreader_73_rome-props.png" alt="feedreader 73 rome props">
</div>
</div>
<div class="olist arabic">
<ol class="arabic" start="2">
<li>
<p>Finally, since Rome Fetcher depends on both Rome and JDom, you need to make Rome Fetcher dependent on Rome. Do so following the same instructions as the above, so that Rome Fetcher depends on Rome, as shown below:</p>
</li>
</ol>
</div>
<div class="imageblock">
<div class="content">
<img src="../../_images/tutorials/feedreader_73_fetcher-props.png" alt="feedreader 73 fetcher props">
</div>
</div>
<div class="paragraph">
<p>Because Rome already depends on JDom, you do not need to make Rome Fetcher dependent on JDom.</p>
</div>
<div class="paragraph">
<p>You have now created the source structure of your new application. In the next section, we will begin adding some code.</p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_creating_the_feed_reader_window"><a class="anchor" href="#_creating_the_feed_reader_window"></a>Creating the Feed Reader Window</h2>
<div class="sectionbody">
<div class="paragraph">
<p>In this section, you use the Java Class wizard as a starting point in creating a new window. A different way to achieve the same end is to use the New Window wizard, in the Module Development category in the New File dialog. The New Window wizard is useful in that it integrates with the Matisse GUI Builder, where you can design the layout of your window. No layouting will need to be done in the case of the Feed Reader window, therefore we will not use the New Window wizard in this case.</p>
</div>
<div class="olist arabic">
<ol class="arabic" start="1">
<li>
<p>Right-click the <code>org.myorg.feedreader</code> package node. Choose New &gt; Java Class. Enter <code>FeedTopComponent</code> as the Class Name. Ensure that <code>org.myorg.feedreader</code> is listed as the Package. Click Finish. <code>FeedTopComponent.java</code> opens in the Source editor.</p>
</li>
</ol>
</div>
<div class="olist arabic">
<ol class="arabic" start="2">
<li>
<p>Near the top of the file, change the class declaration to the following:</p>
</li>
</ol>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">public class FeedTopComponent extends TopComponent {</code></pre>
</div>
</div>
<div class="paragraph">
<p>Press Ctrl-Shift-I to let the IDE generate the required import statement.</p>
</div>
<div class="olist arabic">
<ol class="arabic" start="3">
<li>
<p>Register the <code>FeedTopComponent</code> in the window system by adding annotations to the top of the class, as shown here, and then press Ctrl-Shift-I to let the IDE generate the appropriate import statements:</p>
</li>
</ol>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java"><a href="https://bits.netbeans.org/dev/javadoc/org-openide-windows/org/openide/windows/TopComponent.Description.html">@TopComponent.Description</a>
(
preferredID = "FeedTopComponent",
persistenceType = TopComponent.PERSISTENCE_ALWAYS)
<a href="https://bits.netbeans.org/dev/javadoc/org-openide-windows/org/openide/windows/TopComponent.Registration.html">@TopComponent.Registration</a>(
mode = "explorer",
openAtStartup = true)
<a href="https://bits.netbeans.org/dev/javadoc/org-openide-awt/org/openide/awt/ActionID.html">@ActionID</a>(
category = "Window",
id = "org.myorg.feedreader.FeedTopComponent")
<a href="https://bits.netbeans.org/dev/javadoc/org-openide-awt/org/openide/awt/ActionReferences.html">@ActionReferences</a>({
<a href="https://bits.netbeans.org/dev/javadoc/org-openide-awt/org/openide/awt/ActionReference.html">@ActionReference</a>(
path = "Menu/Window",
position = 0)
})
<a href="https://bits.netbeans.org/dev/javadoc/org-openide-windows/org/openide/windows/TopComponent.OpenActionRegistration.html">@TopComponent.OpenActionRegistration</a>(
displayName = "#CTL_FeedAction")
<a href="https://bits.netbeans.org/dev/javadoc/org-openide-util/org/openide/util/NbBundle.Messages.html">@Messages</a>({
"CTL_FeedAction=Open Feed Window"})
public class FeedTopComponent extends TopComponent {</code></pre>
</div>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
While the module is being compiled, the annotations above will be processed. XML entries will be created in the module&#8217;s <code>generated-layer.xml</code> file, for each of the @TopComponent* and @Action* annotations. The <code>generated-layer.xml</code> file will be contributed by the module to the System Filesystem of the application. Read more <a href="../../../../wiki/main/netbeansdevelopperfaq/DevFaqSystemFilesystem/" class="xref page">about the System Filesystem here</a>. For example, the <code>FeedTopComponent</code> will be displayed in the main area of the application, defined by the "editor" position, as specified by the <code>@TopComponent.Registration</code> annotation above. For each item defined in the <code>@Messages</code> annotation, a new key/value string is generated into a <code>Bundle.java</code> class.
</td>
</tr>
</table>
</div>
<div class="olist arabic">
<ol class="arabic" start="4">
<li>
<p>Add a constructor to the <code>FeedTopComponent</code> , while again using the <code>@Messages</code> annotation referred to above:</p>
</li>
</ol>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">@Messages({
"CTL_FeedTopComponent=Feed Window",
"HINT_FeedTopComponent=This is a Feed Window"})
private FeedTopComponent() {
setName(Bundle.CTL_FeedTopComponent());
setToolTipText(Bundle.HINT_FeedTopComponent());
}</code></pre>
</div>
</div>
<div class="olist arabic">
<ol class="arabic" start="5">
<li>
<p>In the Projects window, right-click the <code>FeedReader</code> project and choose Run, as shown below:</p>
</li>
</ol>
</div>
<div class="imageblock">
<div class="content">
<img src="../../_images/tutorials/feedreader_73_result-1.png" alt="feedreader 73 result 1">
</div>
</div>
<div class="paragraph">
<p>The application starts up, the default splash screen is shown, and once the application has started, you should see your application, including the new window provided by your module:</p>
</div>
<div class="imageblock">
<div class="content">
<img src="../../_images/tutorials/feedreader_73_result-2.png" alt="feedreader 73 result 2">
</div>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
What you now have is an application consisting of the following modules:
</td>
</tr>
</table>
</div>
<div class="ulist">
<ul>
<li>
<p>The modules provided by the NetBeans Platform, for bootstrapping the application, lifecycle management, window system, menu bars, toolbars, and other infrastructural concerns.</p>
</li>
<li>
<p>The three library wrapper modules that you created in this tutorial.</p>
</li>
<li>
<p>The FeedReader functionality module that you created in this tutorial, for providing the Feed window.</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>In the application&#8217;s Window menu, you should see the new menu item, which you can use for opening the Feed window, if it is closed.</p>
</div>
<div class="paragraph">
<p>As you can see, without having done any coding, we have a complete application. It doesn&#8217;t do much yet, but the entire infrastructure exists and works as one would expect. Next, we begin using some of the NetBeans APIs, to add code to our application.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_creating_the_model"><a class="anchor" href="#_creating_the_model"></a>Creating the Model</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Now that you have laid the basis for your application, it&#8217;s time to begin adding your own code. We start by creating a plain Java class that encapsulates a URL and its associated Rome feed. In an MVC paradigm, this class the <em>model</em> of the application.</p>
</div>
<div class="olist arabic">
<ol class="arabic" start="1">
<li>
<p>Right-click the <code>FeedReader</code> module project node, choose New &gt; Java Class. Name the class <code>Feed</code> and select <code>org.myorg.feedreader</code> in the Package drop-down. Click Finish.</p>
</li>
</ol>
</div>
<div class="olist arabic">
<ol class="arabic" start="2">
<li>
<p>In the Source Editor, replace the default <code>Feed</code> class with the following:</p>
</li>
</ol>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">package org.myorg.feedreader;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.fetcher.FeedFetcher;
import com.sun.syndication.fetcher.impl.HashMapFeedInfoCache;
import com.sun.syndication.fetcher.impl.HttpURLFeedFetcher;
import java.io.IOException;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;
public class Feed implements Serializable {
private static final long serialVersionUID = 1L;
private static final FeedFetcher FEED_FETCHER =
new HttpURLFeedFetcher(HashMapFeedInfoCache.getInstance());
private transient SyndFeed syndFeed;
private final URL url;
private String name;
public Feed(URL url) {
this.url = url;
name = url.toExternalForm();
}
public URL getURL() {
return url;
}
public SyndFeed getSyndFeed() throws IOException {
if (syndFeed == null) {
try {
syndFeed = FEED_FETCHER.retrieveFeed(url);
String title = syndFeed.getTitle();
if (title != null) {
name = title;
}
} catch (Exception ex) {
throw new IOException(ex);
}
}
return syndFeed;
}
@Override
public String toString() {
return name;
}
public static Feed getSample() {
try {
return new Feed(new URL("http://planetnetbeans.org/rss20.xml"));
} catch (MalformedURLException x) {
throw new AssertionError(x);
}
}
}</code></pre>
</div>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
In the next three steps, you&#8217;re going to create a new <code>layer.xml</code> file. Each module in your application can have at most one of these files. The <code>layer.xml</code> file is registered in the manifest file of the module, by the wizard that you use to create it. The <code>layer.xml</code> file provides the module&#8217;s contributions to the application&#8217;s virtual filesystem, known as the System Filesystem. In this case, you&#8217;re going to contribute a new folder, named "RssFeeds" to the virtual filesystem. Later in this tutorial, when you work on the view, the node hierarchy will be created on top of this virtual folder. Within the virtual folder, multiple feeds will be found. Right now, a sample feed is registered there, via the <code>getSample</code> method above. That method will be registered in the "RssFeeds" folder, in the third of the three steps that follow, so that when the "RssFeeds" folder is referred to in the view, later in this tutorial, the sample feed will automatically be created because the <code>getSample</code> method will be instantiated.
</td>
</tr>
</table>
</div>
<div class="olist arabic">
<ol class="arabic" start="3">
<li>
<p>Right-click the <code>org.myorg.feedreader</code> package node and choose New | Other. In the Module Development category, choose XML Layer, as shown below:</p>
</li>
</ol>
</div>
<div class="imageblock">
<div class="content">
<img src="../../_images/tutorials/feedreader_73_new-layer-1.png" alt="feedreader 73 new layer 1">
</div>
</div>
<div class="paragraph">
<p>Click Next.</p>
</div>
<div class="olist arabic">
<ol class="arabic" start="4">
<li>
<p>The layer file will be created in the location shown below. It will also be registered in the manifest file of the module.</p>
</li>
</ol>
</div>
<div class="imageblock">
<div class="content">
<img src="../../_images/tutorials/feedreader_73_new-layer-2.png" alt="feedreader 73 new layer 2">
</div>
</div>
<div class="paragraph">
<p>Click Finish. The <code>layer.xml</code> file is added to the module and registered in the module&#8217;s <code>manifest.mf</code> file.</p>
</div>
<div class="olist arabic">
<ol class="arabic" start="5">
<li>
<p>Change the content of the <code>layer.xml</code> to the following, to create your RssFeeds folder in the virtual filesystem:</p>
</li>
</ol>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-xml hljs" data-lang="xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;!DOCTYPE filesystem PUBLIC "-//NetBeans//DTD Filesystem 1.2//EN" "https://netbeans.org/dtds/filesystem-1_2.dtd"&gt;
&lt;filesystem&gt;
&lt;folder name="RssFeeds"&gt;
&lt;file name="sample.instance"&gt;
&lt;attr name="instanceCreate" methodvalue="org.myorg.feedreader.Feed.getSample"/&gt;
&lt;/file&gt;
&lt;/folder&gt;
&lt;/filesystem&gt;</code></pre>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_creating_the_node_hierarchy"><a class="anchor" href="#_creating_the_node_hierarchy"></a>Creating the Node Hierarchy</h2>
<div class="sectionbody">
<div class="paragraph">
<p>The application will have a node hierarchy consisting of three levels. Below, each node is defined, together with a factory class for instantiating the node. The root node will be instantiated within the <code>TopComponent</code> , later in the tutorial, in the section "Displaying the Node Hierarchy in the Feed Window".</p>
</div>
<div class="sect2">
<h3 id="_creating_the_first_level_node_the_root_node"><a class="anchor" href="#_creating_the_first_level_node_the_root_node"></a>Creating the First Level Node: The Root Node</h3>
<div class="paragraph">
<p>The top level node of our Feed Reader is provided by the RootNode class. It will create its child nodes by wrapping a virtual folder, named "RssFeeds", which you created in the previous section. The virtual folder will be received, later in the tutorial, in the form of a node, which is what the root node will wrap. The root node will have the display name of the node that it wraps, while providing the top level node in the node hierarchy, as shown below:</p>
</div>
<div class="imageblock">
<div class="content">
<img src="../../_images/tutorials/feedreader_73_result-3.png" alt="feedreader 73 result 3">
</div>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
In addition to child nodes, when the user right-clicks the root node, the root node will provide a pop-up menu containing a menu item for any action registered in the "Actions/RootActions" folder in the application&#8217;s virtual filesystem, that is, the System Filesystem. Right at the end of this tutorial, two actions will be registered in that folder.
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>Take the following steps:</p>
</div>
<div class="olist arabic">
<ol class="arabic" start="1">
<li>
<p>Create a new class named <code>RootNode.java</code> in the <code>org.myorg.feedreader</code> package.</p>
</li>
</ol>
</div>
<div class="olist arabic">
<ol class="arabic" start="2">
<li>
<p>Replace the default class with the following:</p>
</li>
</ol>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">package org.myorg.feedreader;
import java.util.List;
import javax.swing.Action;
import org.openide.loaders.DataObjectNotFoundException;
import org.openide.nodes.FilterNode;
import org.openide.nodes.Node;
import org.openide.util.Utilities;
public class RootNode extends <a href="https://bits.netbeans.org/dev/javadoc/org-openide-nodes/org/openide/nodes/FilterNode.html">FilterNode</a> {
public RootNode(Node filterNode) throws DataObjectNotFoundException {
super(filterNode, new RssFolderChildren(filterNode));
}
@Override
public Action[] getActions(boolean bln) {
List&lt;? extends Action&gt; rootActions = Utilities.actionsForPath("Actions/RootActions");
return rootActions.toArray(new Action[rootActions.size()]);
}
}</code></pre>
</div>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
A red underline marking remains in the class, because you have not yet created the RssFolderChildren class.
</td>
</tr>
</table>
</div>
</div>
<div class="sect2">
<h3 id="_creating_the_children_of_the_root_node"><a class="anchor" href="#_creating_the_children_of_the_root_node"></a>Creating the Children of the Root Node</h3>
<div class="paragraph">
<p>In this section, we create the children of the root node. Each child is a folder, containing RSS feeds. The RSS feeds, in turn, contain the entries representing the feed articles, which the user will read in a browser.</p>
</div>
<div class="paragraph">
<p>To create this class, take the following steps:</p>
</div>
<div class="olist arabic">
<ol class="arabic" start="1">
<li>
<p>Create <code>RssFolderChildren.java</code> in the <code>org.myorg.feedreader</code> package.</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>1.
Replace the default class with the following:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">package org.myorg.feedreader;
import java.io.IOException;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.loaders.DataObjectNotFoundException;
import org.openide.nodes.FilterNode;
import org.openide.nodes.Node;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
public class RssFolderChildren extends <a href="https://bits.netbeans.org/dev/javadoc/org-openide-nodes/org/openide/nodes/FilterNode.Children.html">FilterNode.Children</a> {
RssFolderChildren(Node rssFolderNode) {
super(rssFolderNode);
}
@Override
protected Node[] createNodes(Node n) {
FileObject fo = n.getLookup().lookup(FileObject.class);
if (fo != null &amp;&amp; fo.isFolder()) {
try {
return new Node[]{new RootNode(n)};
} catch (DataObjectNotFoundException ex) {
Exceptions.printStackTrace(ex);
}
} else {
Feed feed = getFeed(fo.getLookup());
if (feed != null) {
try {
return new Node[]{new OneFeedNode(n, feed.getSyndFeed())};
} catch (IOException ioe) {
Exceptions.printStackTrace(ioe);
}
}
}
// best effort
return new Node[]{new FilterNode(n)};
}
/**
* Looking up a feed
*/
private static Feed getFeed(Lookup lookup) {
Feed f = FileUtil.getConfigObject("RssFeeds/sample.instance", Feed.class);
if (f == null) {
throw new IllegalStateException("Bogus file in feeds folder: "
+ lookup.lookup(FileObject.class));
}
return f;
}
}</code></pre>
</div>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
If you&#8217;re using NetBeans Platform 7.2, replace <code>getFeed(fo.getLookup())</code> with <code>getFeed(DataObject.find(fo).getLookup())</code> .
</td>
</tr>
</table>
</div>
</div>
<div class="sect2">
<h3 id="_creating_the_second_level_node_the_feed_node"><a class="anchor" href="#_creating_the_second_level_node_the_feed_node"></a>Creating the Second Level Node: The Feed Node</h3>
<div class="paragraph">
<p>Here we are concerned with feeds, that is, the containers for the entry nodes, as shown below for the "Planet NetBeans" feed:</p>
</div>
<div class="imageblock">
<div class="content">
<img src="../../_images/tutorials/feedreader_73_result-4.png" alt="feedreader 73 result 4">
</div>
</div>
<div class="paragraph">
<p>As can be seen, each of these nodes has a list of entries, a display name, retrieved from the feed, and an icon. The icon is provided in the table at the start of this tutorial. Unzip it from there and add it to the main source package of the module. Each Feed node also has a Delete menu item.</p>
</div>
<div class="paragraph">
<p>Take the following steps to create this class:</p>
</div>
<div class="olist arabic">
<ol class="arabic" start="1">
<li>
<p>Create <code>OneFeedNode.java</code> in the <code>org.myorg.feedreader</code> package.</p>
</li>
</ol>
</div>
<div class="olist arabic">
<ol class="arabic" start="2">
<li>
<p>Replace the default class with the following:</p>
</li>
</ol>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">package org.myorg.feedreader;
import com.sun.syndication.feed.synd.SyndFeed;
import java.awt.Image;
import javax.swing.Action;
import org.openide.actions.DeleteAction;
import org.openide.nodes.FilterNode;
import org.openide.nodes.Node;
import org.openide.util.ImageUtilities;
import org.openide.util.actions.SystemAction;
import org.openide.util.lookup.Lookups;
/**
* Getting the feed node and wrapping it in a FilterNode
*/
public class OneFeedNode extends <a href="https://bits.netbeans.org/dev/javadoc/org-openide-nodes/org/openide/nodes/FilterNode.html">FilterNode</a> {
OneFeedNode(Node feedFileNode, SyndFeed feed) {
super(feedFileNode, Children.create(new FeedChildFactory(feed), false), Lookups.fixed(feed));
}
@Override
public String getDisplayName() {
return getLookup().lookup(SyndFeed.class).getTitle();
}
@Override
public Image getIcon(int type) {
return ImageUtilities.loadImage("org/myorg/feedreader/rss16.gif");
}
@Override
public Image getOpenedIcon(int type) {
return getIcon(type);
}
@Override
public Action[] getActions(boolean context) {
return new Action[]{SystemAction.get(DeleteAction.class)};
}
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>Several red underline markings remain in the class, because we have not created our <code>FeedChildFactory</code> class yet.</p>
</div>
</div>
<div class="sect2">
<h3 id="_creating_the_children_of_the_feed_node"><a class="anchor" href="#_creating_the_children_of_the_feed_node"></a>Creating the Children of the Feed Node</h3>
<div class="paragraph">
<p>The children of the Feed node are Entry nodes, which in turn are created by a <code>ChildFactory</code> class, as defined below.</p>
</div>
<div class="paragraph">
<p>To create this class, take the following steps:</p>
</div>
<div class="olist arabic">
<ol class="arabic" start="1">
<li>
<p>Create <code>FeedChildFactory.java</code> in the <code>org.myorg.feedreader</code> package.</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>1.
Replace the default class with the following:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">package org.myorg.feedreader;
import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndFeed;
import java.beans.IntrospectionException;
import java.util.List;
import org.openide.nodes.ChildFactory;
import org.openide.nodes.Node;
import org.openide.util.Exceptions;
import org.openide.util.NbCollections;
public class FeedChildFactory extends <a href="https://bits.netbeans.org/dev/javadoc/org-openide-nodes/org/openide/nodes/ChildFactory.html">ChildFactory&lt;SyndEntry&gt;</a> {
private final SyndFeed feed;
public FeedChildFactory(SyndFeed feed) {
this.feed = feed;
}
@Override
protected boolean createKeys(List&lt;SyndEntry&gt; list) {
list.addAll(<a href="https://bits.netbeans.org/dev/javadoc/org-openide-util/org/openide/util/NbCollections.html">NbCollections</a>.checkedListByCopy(feed.getEntries(), SyndEntry.class, true));
return true;
}
@Override
protected Node createNodeForKey(SyndEntry entry) {
OneEntryNode node = null;
try {
node = new OneEntryNode(entry);
} catch (IntrospectionException ex) {
Exceptions.printStackTrace(ex);
}
return node;
}
}</code></pre>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_creating_the_third_level_node_the_entry_node"><a class="anchor" href="#_creating_the_third_level_node_the_entry_node"></a>Creating the Third Level Node: The Entry Node</h3>
<div class="paragraph">
<p>Finally, we deal with the lowest level nodes, those that represent entries provided by the feed, such as the highlighted entry below:</p>
</div>
<div class="imageblock">
<div class="content">
<img src="../../_images/tutorials/feedreader_73_result-5.png" alt="feedreader 73 result 5">
</div>
</div>
<div class="paragraph">
<p>To create this class, take the following steps:</p>
</div>
<div class="olist arabic">
<ol class="arabic" start="1">
<li>
<p>Create <code>OneEntryNode.java</code> in the <code>org.myorg.feedreader</code> package.</p>
</li>
</ol>
</div>
<div class="olist arabic">
<ol class="arabic" start="2">
<li>
<p>Replace the default class with the following:</p>
</li>
</ol>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">package org.myorg.feedreader;
import com.sun.syndication.feed.synd.SyndContent;
import com.sun.syndication.feed.synd.SyndEntry;
import java.awt.BorderLayout;
import java.beans.IntrospectionException;
import javax.swing.Action;
import javax.swing.JEditorPane;
import javax.swing.JScrollPane;
import org.openide.actions.OpenAction;
import org.openide.cookies.OpenCookie;
import org.openide.nodes.BeanNode;
import org.openide.nodes.FilterNode;
import org.openide.util.actions.SystemAction;
import org.openide.util.lookup.Lookups;
import org.openide.windows.TopComponent;
class OneEntryNode extends <a href="https://bits.netbeans.org/dev/javadoc/org-openide-nodes/org/openide/nodes/FilterNode.html">FilterNode</a> {
private final SyndEntry entry;
public OneEntryNode(SyndEntry entry) throws IntrospectionException {
super( new BeanNode&lt;SyndEntry&gt;(entry),
Children.LEAF,
Lookups.fixed(new EntryOpenCookie(entry)));
this.entry = entry;
}
/**
* Using HtmlDisplayName ensures any HTML in RSS entry titles are properly
* handled, escaped, entities resolved, etc.
*/
@Override
public String getHtmlDisplayName() {
return entry.getTitle();
}
/**
* Making a tooltip out of the entry's description
*/
@Override
public String getShortDescription() {
StringBuilder sb = new StringBuilder();
sb.append("Author: ").append(entry.getAuthor()).append("; ");
if (entry.getPublishedDate() != null) {
sb.append("Published: ").append(entry.getPublishedDate().toString());
}
return sb.toString();
}
/**
* Providing the Open action on a feed entry
*/
@Override
public Action[] getActions(boolean popup) {
return new Action[]{SystemAction.get(OpenAction.class)};
}
@Override
public Action getPreferredAction() {
return getActions(false)[0];
}
/**
* Specifying what should happen when the user invokes the Open action
*/
private static class EntryOpenCookie implements OpenCookie {
private final SyndEntry entry;
EntryOpenCookie(SyndEntry entry) {
this.entry = entry;
}
@Override
public void open() {
BrowserTopComponent btc = new BrowserTopComponent(entry);
btc.open();
btc.requestActive();
}
}
public static final class BrowserTopComponent extends TopComponent {
public BrowserTopComponent(SyndEntry entry) {
setName(entry.getTitle());
setLayout(new BorderLayout());
JEditorPane editorPane = new JEditorPane();
editorPane.setEditable(false);
SyndContent description = entry.getDescription();
if (description != null) {
editorPane.setContentType("text/html");
editorPane.setText(description.getValue());
}
add(new JScrollPane(editorPane), BorderLayout.CENTER);
putClientProperty(/*PrintManager.PRINT_PRINTABLE*/"print.printable", true);
}
}
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>Instead of a <code>JEditorPane</code> , you can use the JavaFX WebView component. A NetBeans module providing the JavaFX WebView component embedded in a <code>TopComponent</code> , together with all its dependencies, is found here: <a href="http://web.archive.org/web/20150927002527/https://java.net/projects/javafxbrowser">http://java.net/projects/javafxbrowser</a>.</p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_displaying_the_node_hierarchy_in_the_feed_window"><a class="anchor" href="#_displaying_the_node_hierarchy_in_the_feed_window"></a>Displaying the Node Hierarchy in the Feed Window</h2>
<div class="sectionbody">
<div class="paragraph">
<p>In this section, we use a NetBeans Platform component called <code> <a href="https://bits.netbeans.org/dev/javadoc/org-openide-explorer/org/openide/explorer/view/BeanTreeView.html">BeanTreeView</a></code> to display a hierarchy of feeds in our <code>TopComponent</code> .</p>
</div>
<div class="olist arabic">
<ol class="arabic" start="1">
<li>
<p>Open <code>FeedTopComponent.java</code> and type <code>implements <a href="https://bits.netbeans.org/dev/javadoc/org-openide-explorer/org/openide/explorer/ExplorerManager.Provider.html">ExplorerManager.Provider</a></code> at the end of the class declaration.</p>
</li>
</ol>
</div>
<div class="olist arabic">
<ol class="arabic" start="2">
<li>
<p>Press Alt-Enter in the line and click on the suggestion. The IDE adds an import statement for the required package <code> <a href="https://bits.netbeans.org/dev/javadoc/org-openide-explorer/org/openide/explorer/ExplorerManager.html">org.openide.explorer.ExplorerManager</a></code> . Press Alt-Enter again and click on the suggestion. The IDE implements the abstract method <code>getExplorerManager()</code> .</p>
</li>
</ol>
</div>
<div class="olist arabic">
<ol class="arabic" start="3">
<li>
<p>Type <code>return manager;</code> in the body of the new <code>getExplorerManager()</code> method. Press Alt-Enter in the line and let the IDE create a field called <code>manager</code> for you. Replace the default definition with this one:</p>
</li>
</ol>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">private final ExplorerManager manager = new ExplorerManager();</code></pre>
</div>
</div>
<div class="olist arabic">
<ol class="arabic" start="4">
<li>
<p>Finally, add the following code to the end of the constructor:</p>
</li>
</ol>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">setLayout(new BorderLayout());
add(new BeanTreeView(), BorderLayout.CENTER);
try {
FileObject rssFeedsFolder = FileUtil.getConfigFile("RssFeeds");
Node rssFeedsNode = DataObject.find(rssFeedsFolder).getNodeDelegate();
manager.setRootContext(new RootNode(rssFeedsNode));
} catch (DataObjectNotFoundException ex) {
Exceptions.printStackTrace(ex);
}
ActionMap map = getActionMap();
map.put("delete", ExplorerUtils.actionDelete(manager, true));
associateLookup(ExplorerUtils.createLookup(manager, map));</code></pre>
</div>
</div>
<div class="olist arabic">
<ol class="arabic" start="5">
<li>
<p>Now a lot of code is underlined, because you have not declared their associated packages. Press Ctrl-Shift-I and the IDE adds several import statements below the package statement. The complete list of import statements should be as follows:</p>
</li>
</ol>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">import java.awt.BorderLayout;
import java.util.logging.Logger;
import javax.swing.ActionMap;
import org.openide.util.NbBundle;
import org.openide.windows.TopComponent;
import org.openide.windows.WindowManager;
import org.openide.util.ImageUtilities;
import org.netbeans.api.settings.ConvertAsProperties;
import org.openide.explorer.ExplorerManager;
import org.openide.explorer.ExplorerUtils;
import org.openide.explorer.view.BeanTreeView;</code></pre>
</div>
</div>
<div class="olist arabic">
<ol class="arabic" start="6">
<li>
<p>Right-click the application and choose Run. You should see the application shown at the start of this tutorial:</p>
</li>
</ol>
</div>
<div class="imageblock">
<div class="content">
<img src="../../_images/tutorials/feedreader_73_result-6.png" alt="feedreader 73 result 6">
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_creating_the_actions"><a class="anchor" href="#_creating_the_actions"></a>Creating the Actions</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Two <code>Action</code> classes are defined below. Via annotations, they are registered in the "Actions/RootActions" folder, which is where the <code>RootNode</code> , defined earlier in this tutorial, will find them.</p>
</div>
<div class="sect2">
<h3 id="_creating_the_add_feed_action"><a class="anchor" href="#_creating_the_add_feed_action"></a>Creating the Add Feed Action</h3>
<div class="paragraph">
<p>In this section, we create the menu item that adds new feeds. As you can see in the previous section, the Add Feed Action is bound to the context-menu of the Root Node.</p>
</div>
<div class="paragraph">
<p>To create this class, take the following steps:</p>
</div>
<div class="olist arabic">
<ol class="arabic" start="1">
<li>
<p>Create <code>AddRssAction.java</code> in the <code>org.myorg.feedreader</code> package.</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>1.
Replace the default class with the following:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">package org.myorg.feedreader;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import javax.swing.AbstractAction;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.awt.ActionID;
import org.openide.awt.ActionRegistration;
import org.openide.filesystems.FileLock;
import org.openide.filesystems.FileObject;
import org.openide.loaders.DataFolder;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle.Messages;
@ActionID(category = "RootActions", id = "org.myorg.feedreader.AddRssAction")
@ActionRegistration(displayName = "#FN_addbutton")
@Messages("FN_addbutton=Add Feed")
public class AddRssAction extends AbstractAction {
private final DataFolder folder;
public AddRssAction(DataFolder df) {
folder = df;
}
@Messages({
"FN_askurl_msg=Enter the URL of an RSS/Atom Feed",
"FN_askurl_title=New Feed",
"FN_askurl_err=Invalid URL: {0}|",
"FN_cannotConnect_err=Cannot Connect!"
})
@Override
public void actionPerformed(ActionEvent ae) {
<a href="https://bits.netbeans.org/dev/javadoc/org-openide-dialogs/org/openide/NotifyDescriptor.html">NotifyDescriptor</a>.InputLine nd = new NotifyDescriptor.InputLine(
Bundle.FN_askurl_msg(),
Bundle.FN_askurl_title(),
NotifyDescriptor.OK_CANCEL_OPTION,
NotifyDescriptor.PLAIN_MESSAGE);
Object result = <a href="https://bits.netbeans.org/dev/javadoc/org-openide-dialogs/org/openide/DialogDisplayer.html">DialogDisplayer</a>.getDefault().notify(nd);
if (result.equals(NotifyDescriptor.OK_OPTION)) {
String urlString = nd.getInputText();
URL url;
try {
url = new URL(urlString);
} catch (MalformedURLException e) {
Exceptions.attachLocalizedMessage(e, Bundle.FN_askurl_err(result));
Exceptions.printStackTrace(e);
return;
}
try {
checkConnection(url);
} catch (IOException e) {
Exceptions.attachLocalizedMessage(e, Bundle.FN_cannotConnect_err());
Exceptions.printStackTrace(e);
return;
}
Feed f = new Feed(url);
FileObject fld = folder.getPrimaryFile();
String baseName = "RssFeed";
int ix = 1;
while (fld.getFileObject(baseName + ix, "ser") != null) {
ix++;
}
try {
FileObject writeTo = fld.createData(baseName + ix, "ser");
FileLock lock = writeTo.lock();
try {
ObjectOutputStream str = new ObjectOutputStream(writeTo.getOutputStream(lock));
try {
str.writeObject(f);
} finally {
str.close();
}
} finally {
lock.releaseLock();
}
} catch (IOException ioe) {
Exceptions.printStackTrace(ioe);
}
}
}
private static void checkConnection(final URL url) throws IOException {
InputStream is = url.openStream();
is.close();
}
}</code></pre>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_creating_the_add_folder_action"><a class="anchor" href="#_creating_the_add_folder_action"></a>Creating the Add Folder Action</h3>
<div class="paragraph">
<p>In this section, we create the menu item that adds new folders, in which new feeds can be created. As you can see in an earlier section, the Add Folder Action is bound to the context-menu of the Root Node.</p>
</div>
<div class="paragraph">
<p>To create this class, take the following steps:</p>
</div>
<div class="olist arabic">
<ol class="arabic" start="1">
<li>
<p>Create <code>AddFolderAction.java</code> in the <code>org.myorg.feedreader</code> package.</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>1.
Replace the default class with the following:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">package org.myorg.feedreader;
import java.awt.event.ActionEvent;
import java.io.IOException;
import javax.swing.AbstractAction;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.awt.ActionID;
import org.openide.awt.ActionRegistration;
import org.openide.loaders.DataFolder;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle.Messages;
@ActionID(id = "org.myorg.feedreader.AddFolderAction", category = "RootActions")
@ActionRegistration(displayName = "#FN_addfolderbutton")
@Messages("FN_addfolderbutton=Add Folder")
public class AddFolderAction extends AbstractAction {
private final DataFolder folder;
public AddFolderAction(DataFolder df) {
folder = df;
}
@Messages({
"FN_askfolder_msg=Enter the folder name",
"FN_askfolder_title=New Folder"
})
@Override
public void actionPerformed(ActionEvent ae) {
<a href="https://bits.netbeans.org/dev/javadoc/org-openide-dialogs/org/openide/NotifyDescriptor.html">NotifyDescriptor</a>.InputLine nd = new NotifyDescriptor.InputLine(
Bundle.FN_askfolder_msg(),
Bundle.FN_askfolder_title(),
NotifyDescriptor.OK_CANCEL_OPTION,
NotifyDescriptor.PLAIN_MESSAGE);
Object result = <a href="https://bits.netbeans.org/dev/javadoc/org-openide-dialogs/org/openide/DialogDisplayer.html">DialogDisplayer</a>.getDefault().notify(nd);
if (result.equals(NotifyDescriptor.OK_OPTION)) {
final String folderString = nd.getInputText();
try {
DataFolder.create(folder, folderString);
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
}
}
}</code></pre>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_using_the_actions"><a class="anchor" href="#_using_the_actions"></a>Using the Actions</h3>
<div class="paragraph">
<p>Run the application again and notice that the root node now provides access to two actions, when you right-click on the root node:</p>
</div>
<div class="imageblock">
<div class="content">
<img src="../../_images/tutorials/feedreader_73_result-7.png" alt="feedreader 73 result 7">
</div>
</div>
<div class="paragraph">
<p>Use the actions to create new folders and register new feeds in the application.</p>
</div>
<div class="paragraph">
<p>For example, use this NASA feed to try out the "Add Feed" action:</p>
</div>
<div class="paragraph">
<p><a href="http://www.nasa.gov/rss/breaking_news.rss">http://www.nasa.gov/rss/breaking_news.rss</a></p>
</div>
<div class="paragraph">
<p>You should see a new feed is added and the result should be something like this:</p>
</div>
<div class="imageblock">
<div class="content">
<img src="../../_images/tutorials/feedreader_73_new-feed.png" alt="feedreader 73 new feed">
</div>
</div>
<div class="paragraph">
<p><a href="../../../../front/main/community/mailing-lists/" class="xref page">Send Us Your Feedback</a></p>
</div>
</div>
</div>
</div>
<section class='tools'>
<ul class="menu align-center">
<li><a title="Facebook" href="https://www.facebook.com/NetBeans"><i class="fa fa-md fa-facebook"></i></a></li>
<li><a title="Twitter" href="https://twitter.com/netbeans"><i class="fa fa-md fa-twitter"></i></a></li>
<li><a title="Github" href="https://github.com/apache/netbeans"><i class="fa fa-md fa-github"></i></a></li>
<li><a title="YouTube" href="https://www.youtube.com/user/netbeansvideos"><i class="fa fa-md fa-youtube"></i></a></li>
<li><a title="Atom Feed" href="https://netbeans.apache.org/blogs/atom"><i class="fa fa-mf fa-rss"></i></a></li>
<li><a title="Slack" href="https://tinyurl.com/netbeans-slack-signup/"><i class="fa fa-md fa-slack"></i></a></li>
<li><a title="Issues" href="https://github.com/apache/netbeans/issues"><i class="fa fa-mf fa-bug"></i></a></li>
</ul>
<ul class="menu align-center">
<li><a href="https://github.com/apache/netbeans-antora-tutorials/edit/main/modules/ROOT/pages/tutorials/nbm-feedreader.adoc" title="See this page in github"><i class="fa fa-md fa-edit"></i> See this page in GitHub.</a></li>
</ul>
</section>
</div>
<div class='grid-container incubator-area' style='margin-top: 64px'>
<div class='grid-x grid-padding-x'>
<div class='large-auto cell text-center'>
<a href="https://www.apache.org/">
<img style="height: 60px" title="Apache Software Foundation" src="../../../../_/images/asf_logo_wide.svg" />
</a>
</div>
<div class='large-auto cell text-center'>
<a href="https://www.apache.org/events/current-event.html">
<img style="width:234px; height: 60px;" title="Apache Software Foundation current event" src="https://www.apache.org/events/current-event-234x60.png"/>
</a>
</div>
</div>
</div>
<footer>
<div class="grid-container">
<div class="grid-x grid-padding-x">
<div class="large-auto cell">
<h1><a href="../../../../front/main/about">About</a></h1>
<ul>
<li><a href="../../../../front/main/community/who">Who's Who</a></li>
<li><a href="https://www.apache.org/foundation/thanks.html">Thanks</a></li>
<li><a href="https://www.apache.org/foundation/sponsorship.html">Sponsorship</a></li>
<li><a href="https://www.apache.org/security/">Security</a></li>
</ul>
</div>
<div class="large-auto cell">
<h1><a href="../../../../front/main/community">Community</a></h1>
<ul>
<li><a href="../../../../front/main/community/mailing-lists">Mailing lists</a></li>
<li><a href="../../../../front/main/community/committer">Becoming a committer</a></li>
<li><a href="../../../../front/main/community/events">NetBeans Events</a></li>
<li><a href="https://www.apache.org/events/current-event.html">Apache Events</a></li>
</ul>
</div>
<div class="large-auto cell">
<h1><a href="../../../../front/main/participate">Participate</a></h1>
<ul>
<li><a href="../../../../front/main/participate/submit-pr">Submitting Pull Requests</a></li>
<li><a href="../../../../front/main/participate/report-issue">Reporting Issues</a></li>
<li><a href="../../../../front/main/participate/#documentation">Improving the documentation</a></li>
</ul>
</div>
<div class="large-auto cell">
<h1><a href="../../../../front/main/help">Get Help</a></h1>
<ul>
<li><a href="../../../../front/main/help/#documentation">Documentation</a></li>
<li><a href="../../../../wiki/main/wiki">Wiki</a></li>
<li><a href="../../../../front/main/help/#support">Community Support</a></li>
<li><a href="../../../../front/main/help/commercial-support">Commercial Support</a></li>
</ul>
</div>
<div class="large-auto cell">
<h1><a href="../../../../front/main/download">Download</a></h1>
<ul>
<li><a href="../../../../front/main/download">Releases</a></li>
<li><a href="https://plugins.netbeans.apache.org/">Plugins</a></li>
<li><a href="../../../../front/main/download/#_daily_builds_and_building_from_source">Building from source</a></li>
<li><a href="../../../../front/main/download/#_older_releases">Previous releases</a></li>
</ul>
</div>
</div>
</div>
</footer>
<div class='footer-disclaimer'>
<div class="footer-disclaimer-content">
<p>Copyright &copy; 2017-2025 <a href="https://www.apache.org">The Apache Software Foundation</a>.</p>
<p>Licensed under the Apache <a href="https://www.apache.org/licenses/">license</a>, version 2.0</p>
<div style='max-width: 40em; margin: 0 auto'>
<p>Apache, Apache NetBeans, NetBeans, the Apache feather logo and the Apache NetBeans logo are trademarks of <a href="https://www.apache.org">The Apache Software Foundation</a>.</p>
<p>Oracle and Java are registered trademarks of Oracle and/or its affiliates.</p>
<p>The Apache NetBeans website conforms to the <a href="https://privacy.apache.org/policies/privacy-policy-public.html">Apache Software Foundation Privacy Policy</a></p>
</div>
</div>
</div>
<script src="../../../../_/js/vendor/lunr.js"></script>
<script src="../../../../_/js/search-ui.js" id="search-ui-script" data-site-root-path="../../../.." data-snippet-length="100" data-stylesheet="../../../../_/css/search.css"></script>
<script async src="../../../../search-index.js"></script>
<script src="../../../../_/js/vendor/jquery.min.js"></script>
<script src="../../../../_/js/vendor/what-input.min.js"></script>
<script src="../../../../_/js/vendor/foundation.min.js"></script>
<script src="../../../../_/js/vendor/jquery.colorbox-min.js"></script>
<script src="../../../../_/js/netbeans.js"></script>
<script>
$(function(){ $(document).foundation(); });
</script>
<script src="../../../../_/js/vendor/highlight.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', (event) => {
document.querySelectorAll('pre code').forEach((el) => {
hljs.highlightElement(el);
});
});
</script>
</body>
</html>