
Hiking, permaculture, software engineering, shogi, TTRPG, and writing take up my time. Hope you enjoy the site! (more…)
Hiking, permaculture, software engineering, shogi, TTRPG, and writing take up my time. Hope you enjoy the site! (more…)
Most of the Clojure tutorials and Clojure examples on this site require at the minimum Clojure to be installed. Usually, you want Leiningen installed too.
Easy peasy.
Seriously, do it once and ignore it for the rest of your learning experience. Here are the steps for getting Lein and Clojure up and running.
Clojure is a modern programming language. Depending on your skill level, you can create any type of software with Clojure. This includes games, web apps, mobile apps, and desktop applications.
The first step in programming Clojure is to install it and other things needed for creating Clojure programs.
(See my full list of Clojure Swing examples and tutorials at Clojure Swing Interop)
JFrame s are the root windows of all modern Clojure Swing applications. In the old days, there were other options, but polite society does not speak of them.
The visible components that you use in your JFrame are added to the content pane of the JFrame . You request the content pane and then add your components to that content frame.
Before adding components to a content pane, you need to decide one which of several layouts you will use for arranging your components. BorderLayout is the default, so we’ll stick with that one for this simple example.
Border Layouts are divided into NORTH , SOUTH , EAST , WEST , and CENTER . We are going to use one label for each of the five locations in the content pane’s BorderLayout . We center the text and change the colors as needed so it is easy to see the locations of each label in the BorderLayout .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
(ns swingproject.core (:gen-class) (:import [javax.swing SwingUtilities JFrame JLabel SwingConstants] [java.awt BorderLayout Color])) (defn create-and-show-gui [] (let [my-frame (doto (JFrame. “My Frame”) (.setDefaultCloseOperation JFrame/EXIT_ON_CLOSE)) my-label-north (doto (JLabel. “Top Label” SwingConstants/CENTER) (.setOpaque true) (.setForeground Color/WHITE) (.setBackground Color/BLACK)) my-label-south (doto (JLabel. “Bottom Label” SwingConstants/CENTER) (.setOpaque true) (.setForeground Color/WHITE) (.setBackground Color/BLACK)) my-label-east (JLabel. “East Label”) my-label-west (JLabel. “West Label”) my-label-center (doto (JLabel. “Center Label” SwingConstants/CENTER) (.setOpaque true) (.setForeground Color/WHITE) (.setBackground Color/DARK_GRAY)) content-pane (.getContentPane my-frame)] (.add content-pane my-label-north, BorderLayout/NORTH) (.add content-pane my-label-south, BorderLayout/SOUTH) (.add content-pane my-label-east, BorderLayout/EAST) (.add content-pane my-label-west, BorderLayout/WEST) (.add content-pane my-label-center, BorderLayout/CENTER) (.pack my-frame) (.setSize my-frame 600 400) (.setVisible my-frame true))) (defn -main “entry point for app” [& args] (SwingUtilities/invokeLater create-and-show-gui)) |
We set the size of the JFrame to 600 pixels wide by 400 pixels tall, then set the JFrame to visible equals true .
Note that we do all our layout in the event dispatch thread by using invokeLater .
(See my full list of Clojure Swing examples and tutorials at Clojure Swing Interop)
When working with UI elements in Clojure Swing, your “go to” event handler is the ActionListener found in the java.awt.event package. This is a one-size-fits-all handler that works for many general situations where you just need to know **something** happened.
The fist hurdle to overcome is what Clojure technique should you use to implement the ActionListener interface? The correct answer is reify .
Reify gives you an anonymous implementation of an interface. It is perfect for implementing Java event interfaces that are rarely named once created. Reify takes the interface name followed by method implementations as it’s arguments. The one trick that may throw off seasoned Java programmers implementing methods in Clojure’s reify, is that the first argument to functions used to implement methods is always an explicit this . this is usually hidden in Java implementations of methods. It’s there, just not visible.
For example, here is reify implementing an ActionListener for a typical JButton called my-button .
1 2 3 |
(.addActionListener my-button (reify ActionListener (actionPerformed [this e] (println “Clicked”)))) |
Here’s an example of a JFrame containing a JButton that prints “Clicked” to the console when clicked.
Here is the full source code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
(ns swingproject.core (:gen-class) (:import [javax.swing SwingUtilities JFrame JButton] [java.awt FlowLayout] [java.awt.event ActionListener])) (defn create-and-show-gui [] (let [my-frame (doto (JFrame. “My Frame”) (.setDefaultCloseOperation JFrame/EXIT_ON_CLOSE) (.setLocationRelativeTo nil)) my-button (JButton. “Click ME!”) content-pane (doto (.getContentPane my-frame) (.setLayout (FlowLayout.)))] (.addActionListener my-button (reify ActionListener (actionPerformed [this e] (println “Clicked”)))) (.add content-pane my-button) (.pack my-frame) (.setSize my-frame 300 200) (.setVisible my-frame true))) (defn -main “entry point for app” [& args] (SwingUtilities/invokeLater create-and-show-gui)) |
(See my full list of Clojure Swing examples and tutorials at Clojure Swing Interop)
Here is a full app demonstrating use of Swing from Clojure. Toy apps usually gloss over import code. For example, real Swing requires proper use of the event thread, and combining multiple layout types to get more complex behavior.
Here’s the full source.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
(ns swingproject.core (:gen-class) (:import [javax.swing SwingUtilities JFrame JTextField JPanel JLabel] [java.awt FlowLayout] [java.awt.event ActionListener])) (defn convert-temp [celsius fahrenheit to-celsius] (if to-celsius (let [f (.getText fahrenheit) f-as-int (Integer/parseInt f) c (* 5/9 (– f-as-int 32))] (SwingUtilities/invokeLater #(.setText celsius (str (float c))))) (let [c (.getText celsius) c-as-int (Integer/parseInt c) f (+ 32 (* 9/5 c-as-int))] (SwingUtilities/invokeLater #(.setText fahrenheit (str (float f))))))) (defn create-and-show-gui [] (let [my-frame (doto (JFrame. “My Frame”) (.setDefaultCloseOperation JFrame/EXIT_ON_CLOSE) (.setLocationRelativeTo nil)) celsius-panel (JPanel. (FlowLayout.)) fahrenheit-panel (JPanel. (FlowLayout.)) celsius (JTextField. “0” 10) fahrenheit (JTextField. “32” 10) content-pane (doto (.getContentPane my-frame) (.setLayout (FlowLayout.)))] (.addActionListener celsius (reify ActionListener (actionPerformed [this e] (convert-temp celsius fahrenheit false)))) (.addActionListener fahrenheit (reify ActionListener (actionPerformed [this e] (convert-temp celsius fahrenheit true)))) (.add celsius-panel (JLabel. “Celsius”)) (.add celsius-panel celsius) (.add fahrenheit-panel (JLabel. “Fahrenheit”)) (.add fahrenheit-panel fahrenheit) (.add content-pane celsius-panel) (.add content-pane fahrenheit-panel) (.pack my-frame) (.setSize my-frame 300 200) (.setVisible my-frame true))) (defn -main “entry point for app” [& args] (SwingUtilities/invokeLater create-and-show-gui)) |
Note that #(<some UI code>) is used to pass in a function to the event dispatch thread, otherwise you have null pointer exceptions due to the code being executed BEFORE it is passed to the event dispatch thread.
(See my full list of Clojure Swing examples and tutorials at Clojure Swing Interop)
Every complex GUI uses message dialogs. When using Clojure Swing interop, you will use the JOptionPane for your message dialogs. Two keys to remember when using JOptionPane s, is that they should always be called from within the Event Dispatch Thread (EDT). Sometimes the JOptionPane is shown by an ActionListener that is already on the EDT, and sometimes you need to call invokeAndWait , because your current thread is not the EDT.
The other key point to remember with JOptionPane s is that you can associate the message dialog with a frame, or set the frame to nil . If a JOptionPane ‘s frame is set to nil , then the message dialog will center itself on the screen when it displays. If a JFrame is provided to the JOptionPane , then the message dialog will center itself on the JFrame it is associated with.
Here is a basic example that provides a button to click, and that displays a plain JOptionPane with a message. The ActionListener is called from the EDT, so the JOptionPane is being called from the EDT.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
(ns swingproject.core (:gen-class) (:import [javax.swing SwingUtilities JFrame JButton JOptionPane] [java.awt FlowLayout] [java.awt.event ActionListener])) (defn create-and-show-gui [] (let [my-frame (doto (JFrame. “JOptionPane Example”) (.setDefaultCloseOperation JFrame/EXIT_ON_CLOSE) (.setLocationRelativeTo nil)) my-button (JButton. “Click ME!”) content-pane (doto (.getContentPane my-frame) (.setLayout (FlowLayout.)))] (.addActionListener my-button (reify ActionListener (actionPerformed [this e] (JOptionPane/showMessageDialog my-frame “Betcha always wanted a message in a JOptionPane!”)))) (.add content-pane my-button) (.pack my-frame) (.setSize my-frame 300 200) (.setVisible my-frame true))) (defn -main “entry point for app” [& args] (SwingUtilities/invokeLater create-and-show-gui)) |
Here is an example that shows a JOptionPane that displays from a normal thread. The EDT must be explicitly called with invokeAndWait . The four most common types of message dialogs are shown.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
(ns swingproject.core (:gen-class) (:import [javax.swing SwingUtilities JOptionPane])) (defn example-message-dialogs [] ;; Default message dialog (SwingUtilities/invokeAndWait #(JOptionPane/showMessageDialog nil “This is the default style”)) ;; Plain message dialog (SwingUtilities/invokeAndWait #(JOptionPane/showMessageDialog nil “This is the ‘plain’ style message dialog” “My Plain Title” JOptionPane/PLAIN_MESSAGE)) ;; Warning message dialog (SwingUtilities/invokeAndWait #(JOptionPane/showMessageDialog nil “I’m a ‘warning’ message dialog” “My Warning” JOptionPane/WARNING_MESSAGE)) ;; Error message dialog (SwingUtilities/invokeAndWait #(JOptionPane/showMessageDialog nil “This is the ‘error’ message dialog” “My Error” JOptionPane/ERROR_MESSAGE)) ) (defn -main “entry point for app” [& args] (example-message-dialogs)) |
(See my full list of Clojure Swing examples and tutorials at Clojure Swing Interop)
When dealing with files, its very handy to use file dialogs. Here’s a quick example of using a JFileChooser to select a file to slurp and print.
Note the use of (into-array [“txt”]) as an argument to the FileNameExtensionFilter constructor. This is because the file extensions are provided as a String... meaning a java.lang.String array.
Also note, the .showOpenDialog call uses nil instead of a JFrame . We do that because we don’t have a JFrame showing that we want to associate the file dialog with.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
(ns swingproject.core (:gen-class) (:import [javax.swing SwingUtilities JFileChooser] [ javax.swing.filechooser FileNameExtensionFilter])) (defn slurp–file [java-file-object] (let [contents (slurp java-file-object)] (println contents))) (defn slurp–a-thon [] ;; opening and using a file dialog (SwingUtilities/invokeLater #(let [my-filter (FileNameExtensionFilter. “Text file to slurp” (into–array [“txt”])) my-file-chooser (doto (JFileChooser.) (.setFileFilter my-filter)) selection-option (.showOpenDialog my-file-chooser nil) selection-file (.getSelectedFile my-file-chooser ) ] (if (= selection-option JFileChooser/APPROVE_OPTION) (future (slurp–file selection-file))))) ;;giving the program some time to print before exiting (Thread/sleep 60000) (System/exit 0)) (defn -main “entry point for app” [& args] (slurp–a-thon)) |
Good practice. Use each of the following functions and macros to create or manipulate hash-maps in Clojure.
{}
hash-map
get
get-in (one keyword)
get-in (<map> [<key1> <key2>])
(<map> <key>)
(:<keyword> <map>)
(:<kw1> (:<kw2> <map>))
(-> <map> :<k1>)
(-> <map> :<k1> :<k2>)
assoc (change value)
assoc-in
assoc (add new key-value pair)
assoc-in
update (function only)
update (function with args)
update-in (function only)
update-in (function with args)
If you need to understand basics of Clojure quickly …
Clojure for Java Programmers part 1
https://youtu.be/P76Vbsk_3J0?si=oWDHQwjHWt8qvbpT
Clojure for Java Programmers part 2
https://youtu.be/hb3rurFxrZ8?si=sUsf_67h3EqC_e-I
Simple Made Easy
https://youtu.be/LKtk3HCgTa8?si=9K4sZvThdJKJQhad
Clojure Concurrency
https://youtu.be/dGVqrGmwOAw?si=Twr5pfeJtgMH-FM4
Official Clojure Home Page
https://clojure.org/
ClojureDocs
https://clojuredocs.org/
Leiningen
https://leiningen.org/
Clojure Web Development
https://luminusweb.com/
Project Euler
https://projecteuler.net/
Code Golf
https://codegolf.stackexchange.com/
Advent of Code
https://adventofcode.com/2023/events
Eventually I’ll finish this, … maybe.
Want to make advanced web pages? Try Selmer. Selmer is a powerful templating system that saves time and money during complex website development.
Selmer is a Clojure based templating system that not too surprisingly works great with Luminus framework. We’ll use Luminus to host our Selmer examples, because it is easy to get running fast.
Prerequisite: Leiningen
Begin with the command …
1 |
lein new luminus mycoolapp |
Now you have a fully functional server with Selmer templates preconfigured. (Told you it was easy.)
cd into your project root
1 |
cd mycoolapp |
Run your new server.
1 |
lein run |
In a web browser visit http://localhost:3000/ You should see something similar to the following.
The web page you are looking at is a Selmer template.
Selmer is a templating language for creating web pages. Selmer integrates with Clojure. Clojure is one of the most concise and powerful general-purpose computer programming languages available. How concise is Clojure? There are many examples of Clojure rewriting common Java and Python functions of 10-20 lines as one line of Clojure code. Selmer templates get to take advantage of Clojure when creating web pages.
For those new to templated web pages, …
The very simplest web pages are written in HTML. If all you need to do is present a restaurant menu, a short list of services, or a description of some research, simple HTML may be enough for you. More advanced and complex web pages often have information that changes based off external factors. For example, if a user of your website needs to log in to see information, a Selmer template can change the information on the page to match the information that user should see. Or if users visiting the page from a different country should see a custom price list, Selmer templates can help with displaying custom information based off regions of the visitors. Selmer templates can also leverage session information to help users of your website navigate more easily to find the information they need.
From a technical view, Selmer templates create enough of a Separation Of Concerns (SoC) that you can hire less expensive junior developers to layout your website and have more expensive senior developers handle the complex server and database code. Using Selmer templates saves your business money, because senior developers are not entangled in tasks that junior developers can handle.
To be fair, there are tons of templating systems associated with various languages and servers. All have similar benefits, except very few exploit the power of Clojure.
Above, we created a web server in a Luminus Clojure project with Selmer templates. Let’s have a look around the Selmer related bits-and-pieces of our project.
I suggest opening the mycoolapp project in Visual Code or your favorite code editor.
Let’s add a Selmer template just to get started.