blob: 439165d2c0c774f68ce0104a77b51341fff69e93 [file] [log] [blame]
<!DOCTYPE html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]--><head>
<meta charset='utf-8'/><meta http-equiv='X-UA-Compatible' content='IE=edge'/><meta name='viewport' content='width=device-width, initial-scale=1'/><meta name='keywords' content='groovy, groovyfx, javafx, emoji, jackson databind'/><meta name='description' content='This blog looks at a GroovyFX TODO application.'/><title>The Apache Groovy programming language - Blogs - Adventures with GroovyFX</title><link href='../img/favicon.ico' type='image/x-ico' rel='icon'/><link rel='stylesheet' type='text/css' href='../css/bootstrap.css'/><link rel='stylesheet' type='text/css' href='../css/font-awesome.min.css'/><link rel='stylesheet' type='text/css' href='../css/style.css'/><link rel='stylesheet' type='text/css' href='https://cdnjs.cloudflare.com/ajax/libs/prettify/r298/prettify.min.css'/>
</head><body>
<div id='fork-me'>
<a href='https://github.com/apache/groovy'>
<img style='position: fixed; top: 20px; right: -58px; border: 0; z-index: 100; transform: rotate(45deg);' src='/img/horizontal-github-ribbon.png'/>
</a>
</div><div id='st-container' class='st-container st-effect-9'>
<nav class='st-menu st-effect-9' id='menu-12'>
<h2 class='icon icon-lab'>Socialize</h2><ul>
<li>
<a href='https://groovy-lang.org/mailing-lists.html' class='icon'><span class='fa fa-envelope'></span> Discuss on the mailing-list</a>
</li><li>
<a href='https://twitter.com/ApacheGroovy' class='icon'><span class='fa fa-twitter'></span> Groovy on Twitter</a>
</li><li>
<a href='https://groovy-lang.org/events.html' class='icon'><span class='fa fa-calendar'></span> Events and conferences</a>
</li><li>
<a href='https://github.com/apache/groovy' class='icon'><span class='fa fa-github'></span> Source code on GitHub</a>
</li><li>
<a href='https://groovy-lang.org/reporting-issues.html' class='icon'><span class='fa fa-bug'></span> Report issues in Jira</a>
</li><li>
<a href='http://stackoverflow.com/questions/tagged/groovy' class='icon'><span class='fa fa-stack-overflow'></span> Stack Overflow questions</a>
</li><li>
<a href='http://groovycommunity.com/' class='icon'><span class='fa fa-slack'></span> Slack Community</a>
</li>
</ul>
</nav><div class='st-pusher'>
<div class='st-content'>
<div class='st-content-inner'>
<!--[if lt IE 7]>
<p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
<![endif]--><div><div class='navbar navbar-default navbar-static-top' role='navigation'>
<div class='container'>
<div class='navbar-header'>
<button type='button' class='navbar-toggle' data-toggle='collapse' data-target='.navbar-collapse'>
<span class='sr-only'></span><span class='icon-bar'></span><span class='icon-bar'></span><span class='icon-bar'></span>
</button><a class='navbar-brand' href='../index.html'>
<i class='fa fa-star'></i> Apache Groovy
</a>
</div><div class='navbar-collapse collapse'>
<ul class='nav navbar-nav navbar-right'>
<li class=''><a href='https://groovy-lang.org/learn.html'>Learn</a></li><li class=''><a href='https://groovy-lang.org/documentation.html'>Documentation</a></li><li class=''><a href='/download.html'>Download</a></li><li class=''><a href='https://groovy-lang.org/support.html'>Support</a></li><li class=''><a href='/'>Contribute</a></li><li class=''><a href='https://groovy-lang.org/ecosystem.html'>Ecosystem</a></li><li class=''><a href='/blog'>Blog posts</a></li><li class=''><a href='https://groovy.apache.org/events.html'></a></li><li>
<a data-effect='st-effect-9' class='st-trigger' href='#'>Socialize</a>
</li><li class=''>
<a href='../search.html'>
<i class='fa fa-search'></i>
</a>
</li>
</ul>
</div>
</div>
</div><div id='content' class='page-1'><div class='row'><div class='row-fluid'><div class='col-lg-3'><ul class='nav-sidebar'><li><a href='./'>Blog index</a></li><li class='active'><a href='#doc'>Adventures with GroovyFX</a></li><li><a href='#_further_information' class='anchor-link'>Further information</a></li></ul><br/><ul class='nav-sidebar'><li style='padding: 0.35em 0.625em; background-color: #eee'><span>Related posts</span></li><li><a href='./deep-learning-and-eclipse-collections'>Deep Learning and Eclipse Collections</a></li><li><a href='./reading-and-writing-csv-files'>Reading and Writing CSV files with Groovy</a></li><li><a href='./helloworldemoji'>Hello World with Emojis</a></li><li><a href='./zipping-collections-with-groovy'>Zipping Collections with Groovy</a></li><li><a href='./fruity-eclipse-collections'>Fruity Eclipse Collections</a></li></ul></div><div class='col-lg-8 col-lg-pull-0'><a name='doc'></a><h1>Adventures with GroovyFX</h1><p><span>Author: <i>Paul King</i></span><br/><span>Published: 2022-12-12 02:22PM</span></p><hr/><div id="preamble">
<div class="sectionbody">
<div class="paragraph">
<p>This blog looks at a <a href="http://groovyfx.org/">GroovyFX</a> version of a <a href="https://donraab.medium.com/my-weird-and-wonderful-first-adventures-with-javafx-6efe3b1923c8">ToDo application originally written in JavaFX</a>.
First we start with a <code>ToDoCategory</code> enum of our ToDo categories:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code data-lang="groovy">enum ToDoCategory {
EXERCISE("🚴"),
WORK("📊"),
RELAX("🧘"),
TV("📺"),
READ("📚"),
EVENT("🎭"),
CODE("💻"),
COFFEE("☕️"),
EAT("🍽"),
SHOP("🛒"),
SLEEP("😴")
final String emoji
ToDoCategory(String emoji) {
this.emoji = emoji
}
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>We will have a <code>ToDoItem</code> class containing the todo task, the previously mentioned category and the due date.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code data-lang="groovy">@Canonical
@JsonIncludeProperties(['task', 'category', 'date'])
@FXBindable
class ToDoItem {
final String task
final ToDoCategory category
final LocalDate date
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>It&#8217;s annotated with <code>@JsonIncludeProperties</code> to allow easy serialization to/from JSON format, to provide easy persistence, and <code>@FXBindable</code> which eliminates the boilerplate required to define JavaFX properties.</p>
</div>
<div class="paragraph">
<p>Next, we&#8217;ll define some helper variables:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code data-lang="groovy">var file = 'todolist.json' as File
var mapper = new ObjectMapper().registerModule(new JavaTimeModule())
var open = { mapper.readValue(it, new TypeReference&lt;List&lt;ToDoItem&gt;&gt;() {}) }
var init = file.exists() ? open(file) : []
var items = FXCollections.observableList(init)
var close = { mapper.writeValue(file, items) }
var table, task, category, date, images = [:]
var urls = ToDoCategory.values().collectEntries {
[it, "emoji/${Integer.toHexString(it.emoji.codePointAt(0))}.png"]
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>Here, <code>mapper</code> serializes and deserializes our top-level domain object (the ToDo list) into JSON using the <a href="https://github.com/FasterXML/jackson">Jackson library</a>. The <code>open</code> and <code>close</code> Closures do the reading and writing respectively.</p>
</div>
<div class="paragraph">
<p>For a bit of fun and only slightly more complexity, we have included some slightly nicer images in our application. JavaFX&#8217;s default emoji font rendering is a little sketchy on some platforms, and it&#8217;s not much work to have nice multicolored images. This is achieved using the icons from <a href="https://github.com/pavlobu/emoji-text-flow-javafx" class="bare">https://github.com/pavlobu/emoji-text-flow-javafx</a>.
The application is perfectly functional without them (and the approximately 20 lines for the <code>cellFactory</code> and <code>cellValueFactory</code> definitions could be elided) but is prettier with the nicer images. We shrunk them to 1/3 their original size but we could certainly make them larger if we felt inclined.</p>
</div>
<div class="paragraph">
<p>Our application will have a combo box for selecting a ToDo item&#8217;s category. We&#8217;ll create a factory for the combo box so that each selection will be a label with both graphic and text components.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code data-lang="groovy">def graphicLabelFactory = {
new ListCell&lt;ToDoCategory&gt;() {
void updateItem(ToDoCategory cat, boolean empty) {
super.updateItem(cat, empty)
if (!empty) {
graphic = new Label(cat.name()).tap {
graphic = new ImageView(images[cat])
}
}
}
}
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>When displaying our ToDo list, we&#8217;ll use a table view. So, let&#8217;s create a factory for table cells that will use the pretty images as a centered graphic.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code data-lang="groovy">def graphicCellFactory = {
new TableCell&lt;ToDoItem, ToDoItem&gt;() {
void updateItem(ToDoItem item, boolean empty) {
graphic = empty ? null : new ImageView(images[item.category])
alignment = Pos.CENTER
}
}
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>Finally, with these definitions out of the way, we can define our GroovyFX application for manipulating our ToDo list:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code data-lang="groovy">start {
stage(title: 'GroovyFX ToDo Demo', show: true, onCloseRequest: close) {
urls.each { k, v -&gt; images[k] = image(url: v, width: 24, height: 24) }
scene {
gridPane(hgap: 10, vgap: 10, padding: 20) {
columnConstraints(minWidth: 80, halignment: 'right')
columnConstraints(prefWidth: 250)
label('Task:', row: 1, column: 0)
task = textField(row: 1, column: 1, hgrow: 'always')
label('Category:', row: 2, column: 0)
category = comboBox(items: ToDoCategory.values().toList(),
cellFactory: graphicLabelFactory, row: 2, column: 1)
label('Date:', row: 3, column: 0)
date = datePicker(row: 3, column: 1)
table = tableView(items: items, row: 4, columnSpan: REMAINING,
onMouseClicked: {
var item = items[table.selectionModel.selectedIndex.value]
task.text = item.task
category.value = item.category
date.value = item.date
}) {
tableColumn(property: 'task', text: 'Task', prefWidth: 200)
tableColumn(property: 'category', text: 'Category', prefWidth: 80,
cellValueFactory: { new ReadOnlyObjectWrapper(it.value) },
cellFactory: graphicCellFactory)
tableColumn(property: 'date', text: 'Date', prefWidth: 90, type: Date)
}
hbox(row: 5, columnSpan: REMAINING, alignment: CENTER, spacing: 10) {
button('Add', onAction: {
if (task.text &amp;&amp; category.value &amp;&amp; date.value) {
items &lt;&lt; new ToDoItem(task.text, category.value, date.value)
}
})
button('Update', onAction: {
if (task.text &amp;&amp; category.value &amp;&amp; date.value &amp;&amp;
!table.selectionModel.empty) {
items[table.selectionModel.selectedIndex.value] =
new ToDoItem(task.text, category.value, date.value)
}
})
button('Remove', onAction: {
if (!table.selectionModel.empty)
items.removeAt(table.selectionModel.selectedIndex.value)
})
}
}
}
}
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>We could have somewhat separated the concerns of application logic and display logic by placing the GUI part of this app in an <code>fxml</code> file. For our purposes however, we&#8217;ll keep the whole application in one source file and use Groovy&#8217;s declarative builder style.</p>
</div>
<div class="paragraph">
<p>Here is the application in use:
<span class="image"><img src="img/TodoScreenshot.png" alt="TodoScreenshot"></span></p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_further_information">Further information</h2>
<div class="sectionbody">
<div class="paragraph">
<p>The code for this application can be found here:</p>
</div>
<div class="paragraph">
<p><a href="https://github.com/paulk-asert/groovyfx-todo" class="bare">https://github.com/paulk-asert/groovyfx-todo</a></p>
</div>
<div class="paragraph">
<p>It&#8217;s a Groovy 3 and JDK 8 application but see this
<a href="https://groovy.apache.org/blog/reading-and-writing-csv-files">blog post</a>
if you want to see Jackson deserialization of classes and records
(and Groovy&#8217;s emulated records) from CSV files using recent
Groovy and JDK versions.</p>
</div>
</div>
</div></div></div></div></div><footer id='footer'>
<div class='row'>
<div class='colset-3-footer'>
<div class='col-1'>
<h1>Groovy</h1><ul>
<li><a href='https://groovy-lang.org/learn.html'>Learn</a></li><li><a href='https://groovy-lang.org/documentation.html'>Documentation</a></li><li><a href='/download.html'>Download</a></li><li><a href='https://groovy-lang.org/support.html'>Support</a></li><li><a href='/'>Contribute</a></li><li><a href='https://groovy-lang.org/ecosystem.html'>Ecosystem</a></li><li><a href='/blog'>Blog posts</a></li><li><a href='https://groovy.apache.org/events.html'></a></li>
</ul>
</div><div class='col-2'>
<h1>About</h1><ul>
<li><a href='https://github.com/apache/groovy'>Source code</a></li><li><a href='https://groovy-lang.org/security.html'>Security</a></li><li><a href='https://groovy-lang.org/learn.html#books'>Books</a></li><li><a href='https://groovy-lang.org/thanks.html'>Thanks</a></li><li><a href='http://www.apache.org/foundation/sponsorship.html'>Sponsorship</a></li><li><a href='https://groovy-lang.org/faq.html'>FAQ</a></li><li><a href='https://groovy-lang.org/search.html'>Search</a></li>
</ul>
</div><div class='col-3'>
<h1>Socialize</h1><ul>
<li><a href='https://groovy-lang.org/mailing-lists.html'>Discuss on the mailing-list</a></li><li><a href='https://twitter.com/ApacheGroovy'>Groovy on Twitter</a></li><li><a href='https://groovy-lang.org/events.html'>Events and conferences</a></li><li><a href='https://github.com/apache/groovy'>Source code on GitHub</a></li><li><a href='https://groovy-lang.org/reporting-issues.html'>Report issues in Jira</a></li><li><a href='http://stackoverflow.com/questions/tagged/groovy'>Stack Overflow questions</a></li><li><a href='http://groovycommunity.com/'>Slack Community</a></li>
</ul>
</div><div class='col-right'>
<p>
The Groovy programming language is supported by the <a href='http://www.apache.org'>Apache Software Foundation</a> and the Groovy community.
</p><div text-align='right'>
<img src='../img/asf_logo.png' title='The Apache Software Foundation' alt='The Apache Software Foundation' style='width:60%'/>
</div><p>Apache&reg; and the Apache feather logo are either registered trademarks or trademarks of The Apache Software Foundation.</p>
</div>
</div><div class='clearfix'>&copy; 2003-2023 the Apache Groovy project &mdash; Groovy is Open Source: <a href='http://www.apache.org/licenses/LICENSE-2.0.html' alt='Apache 2 License'>license</a>, <a href='https://privacy.apache.org/policies/privacy-policy-public.html'>privacy policy</a>.</div>
</div>
</footer></div>
</div>
</div>
</div>
</div><script src='../js/vendor/jquery-1.10.2.min.js' defer></script><script src='../js/vendor/classie.js' defer></script><script src='../js/vendor/bootstrap.js' defer></script><script src='../js/vendor/sidebarEffects.js' defer></script><script src='../js/vendor/modernizr-2.6.2.min.js' defer></script><script src='../js/plugins.js' defer></script><script src='https://cdnjs.cloudflare.com/ajax/libs/prettify/r298/prettify.min.js'></script><script>document.addEventListener('DOMContentLoaded',prettyPrint)</script><script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-257558-10', 'auto');
ga('send', 'pageview');
</script>
</body></html>