Created using lein new chestnut app +edge +re-frame +less +http-kit
.
Quick run instructions:
lein repl
(go)
(cljs-repl)
(for the clojurescript repl; exit to clj repl with:cljs/quit
)- You should add the Giphy API key to
breakpoint-app.config/giphy-api-key
with the repl or in the source code. - Auto-reload works for most things, but you need to explicitly
(reset)
in the clj repl after changing routes.clj.
We will demonstrate how to add a simple button to update the application state.
This should give you an idea of how the unidirectional data flow in re-frame works.
- Start off by creating a new button. You can simply copy-paste the
animation-toggle
function to some other name, likecolor-change
(you'll also want to change the text). To include the component in the page, you will also need to include it in themain-panel
just like the existing[animation-toggle]
, maybe right after it. - Now, we need to use the button to dispatch an event that sets the application state's background color property to a desired value. To do this, we will use the
re-frame.core/dispatch
function in the button's on-click handler. The dispatch function will take a vector parameter which will contain a keyword as the event's name, and possibly some parameters after that. A example function that performs a dispatch:#(dispatch [:color-event :black])
. - Next, we should handle the event that we dispatch when the button is clicked. This is done in events.cljs, where we will create the event handler using
re-frame.core/reg-event-db
. This function is used to register a pure function that will update our application state. The parameters for thereg-event-db
function are the event's name (a keyword) and the handler function. The function will receive as parameters the current app state,db
, and a vector that holds the event name, and any possible parameters passed - in other words, the parameter given to the dispatch function. The function should return the updated application state. The event registration will look like:(reg-event-db :color-event (fn [db [_ color]] (assoc db :background-color color))))
. This will set thedb
map's entry with the key:background-color
to the value given. - In order to refer to the app-db, and the color value therein, we must create a subscription. Subscriptions are used to access the app-db and their values will change whenever the app-db does. We will add a subscription in subs.cljs using the
re-frame.core/reg-sub
function. The usage looks much likereg-event-db
: the parameters are the subscription's name (a keyword) and a function that receives the app-db map and should return data that is derived from the app-db. In our case, we just need to access the map value we just added:(reg-sub :background-color (fn [db] (:background-color db)))
. - Now we have access to the color, and just need to use it in our view. We have the necessary styles in place, so we can just set the
.black-background
style to the.main-wrapper
div. To subscribe to the subscription created above, use there-frame.core/subscribe
function, providing the subscription name as a parameter. It will return the subscription, but you will need to dereference the subscription to get the contained value:@(subscribe [:background-color])
. To put it all together, add a properties map to the.main-wrapper
div (as its first element), and use the subscription:{:class (when (= @(subscribe [:background-color]) :black) "black-background")}
. - This will only allow you to set the background to black! How about making the button a toggle, or adding another button for the sky blue background?
This involves side-effectful event handlers — the backend API has been provided for you.
- Again, the "Load random" button needs to dispatch an event.
- We also need to create a handler for the event. This time we will need to do an AJAX call to the REST API, so it's a bit different from before. To help you along, we have provided a handler in events.cljs, which has been commented out. Un-commenting it should be enough for now (just note that you have matching event names in the dispatch and handler.)
- The handler has an
on-success
property, which is another dispatch, so we need to create a handler for that as well. Implement it usingreg-event-db
. The handler function will receive the JSON response as a parameter, so you can access it just like thecolor
parameter in Ex. 1. The response contains a vector of images that should be appended to the:images
entry in the app-db. You can do this in a few ways usingupdate
, the shortest probably being the functioninto
, or a combination ofconj
andflatten
. - Next, create a subscription for the images. A stub is included and commented out in subs.cljs.
- To complete the final step of rendering the images, see the function
results-box
in views.cljs. In order to render an image, you can use the provided functionresults-item
which takes one image as a parameter. Put the list of images inside[:div.results-box]
, where you can use themap
function to turn the list of image data (accessed with@(subscribe [:the-subscription-you-just-created])
) into images. - Once you get everything working, you will see some React warnings to the effect of
Every element in a seq should have a unique :key
. You can do this by adding the:key
property to the outer div that the functionresults-item
returns, with something unique as the value. - If you have time to spare, how adding about YET ANOTHER button, which clears the list of images?
Here, you get to implement the entire end-to-end functionality.
- The text field currently console logs whatever is typed to it. Make it a - you guessed it - dispatch instead.
- Create a subscription using
reg-event-fx
. For now, only implement the app-db update with something like:
(reg-event-fx
:update-search
(fn [{:keys [db]} [_ query-text]]
{:db (assoc db :search-input query-text)}))
.. which could be achieved by using reg-event-db
, for now.
- Also change the
value
entry for the text field in views.cljs — you probably noticed it has been hard-coded to be the empty string. Instead, subscribe to the actual value that is now in the app-db. - To create a HTTP request, add a
:http
key to the:update-search
handler function's return value. The format can be copied from the:load-random
handler, but the URI should be constructed to be something like:/api/search?q=searchterm
(see thestr
function for help). - To create a corresponding backend endpoint, see routes.clj in
/src/clj/breakpoint_app
. Use the existing/api/random
endpoint definition to create a new one, with the following changes:- The path should be
/api/search
- Add a query parameter definition:
:query-params [q :- String]
after the:return
definition. - In the body, call the search function:
(ok (giphy/search q))
. - NB: you have to manually issue the
(reset)
command in the clj REPL after changing the route definitions!
- The path should be
- Now see giphy.clj and implement the function
search
. Again, you can copy-paste therandom
definition, but you will have to change the parameters to the functionquery-url
. The first one should besearch
and the second a map contains query parameters to the Giphy API request. The text string to search for should be namedq
. You can try thequery-url
function in your REPL to ensure you've got it right.
Since we are using the existing :add-images
handler to deal with the API response, the text search should now be working! However, performing the search after every keypress is rather dirty. We can fix this by only firing the search after there has been a short time of inactivity in the text field:
- We have a commented-out
dispatch-debounced
effects hander registration in events.cljs. Check it out to see how to add new types of handlers and uncomment it! - In the
:update-search
handler, remove (but don't throw away!) the:http-xhrio
definition in the returned map. - Replace it with a
:dispatch-debounced
entry. The value should be a map, which is passed to the function in thedispatch-debounced
fx handler. In its implementation, you can see that the keys:id
(any keyword):dispatch
(a dispatch name like[:do-search]
) and:timeout
(in milliseconds) should be included, so do so. - Add the handler for the dispatch you just created. It should be of the
reg-event-fx
type and contain the:http-xhrio
definition you removed a few steps earlier. Note that you also have to dig out the query text from the app-db.
After this you should experience a much smoother text search!
Clicking the remove button on image cards should remove the image from the app-db and view.
Add a "saved images" element, to which you can images with the 'plus' button.
Add a button that pushes liked images to the backend, and re-populates the saved image element on page load. A simple atom
can serve as your database in the backend.
Open a terminal and type lein repl
to start a Clojure REPL
(interactive prompt).
In the REPL, type
(go)
(cljs-repl)
The call to (go)
starts the Figwheel server at port 3449, which takes care of
live reloading ClojureScript code and CSS, and the app server at port 10555
which forwards requests to the http-handler you define.
Running (cljs-repl)
starts the Figwheel ClojureScript REPL. Evaluating
expressions here will only work once you've loaded the page, so the browser can
connect to Figwheel.
When you see the line Successfully compiled "resources/public/app.js" in 21.36 seconds.
, you're ready to go. Browse to http://localhost:10555
and enjoy.
Attention: It is not needed to run lein figwheel
separately. Instead (go)
launches Figwheel directly from the REPL
If all is well you now have a browser window saying 'Hello Chestnut',
and a REPL prompt that looks like cljs.user=>
.
Open resources/public/css/style.css
and change some styling of the
H1 element. Notice how it's updated instantly in the browser.
Open src/cljs/breakpoint-app/core.cljs
, and change dom/h1
to
dom/h2
. As soon as you save the file, your browser is updated.
In the REPL, type
(ns breakpoint-app.core)
(swap! app-state assoc :text "Interactivity FTW")
Notice again how the browser updates.
Lighttable provides a tighter integration for live coding with an inline browser-tab. Rather than evaluating cljs on the command line with the Figwheel REPL, you can evaluate code and preview pages inside Lighttable.
Steps: After running (go)
, open a browser tab in Lighttable. Open a cljs file
from within a project, go to the end of an s-expression and hit Cmd-ENT.
Lighttable will ask you which client to connect. Click 'Connect a client' and
select 'Browser'. Browse to http://localhost:10555
View LT's console to see a Chrome js console.
Hereafter, you can save a file and see changes or evaluate cljs code (without saving a file).
CIDER is able to start both a Clojure and a ClojureScript REPL simultaneously,
so you can interact both with the browser, and with the server. The command to
do this is M-x cider-jack-in-clojurescript
.
We need to tell CIDER how to start a browser-connected Figwheel REPL though, otherwise it will use a JavaScript engine provided by the JVM, and you won't be able to interact with your running app.
Put this in your Emacs configuration (~/.emacs.d/init.el
or ~/.emacs
)
(setq cider-cljs-lein-repl
"(do (user/go)
(user/cljs-repl))")
Now M-x cider-jack-in-clojurescript
(shortcut: C-c M-J
, that's a capital
"J", so Meta-Shift-j
), point your browser at http://localhost:10555
, and
you're good to go.
To run the Clojure tests, use
lein test
To run the Clojurescript you use doo. This can run your tests against a variety of JavaScript implementations, but in the browser and "headless". For example, to test with PhantomJS, use
lein doo phantom
This assumes you have a
Heroku account, have installed the
Heroku toolbelt, and have done a
heroku login
before.
git init
git add -A
git commit
heroku create
git push heroku master:master
heroku open
Heroku uses Foreman to run your
app, which uses the Procfile
in your repository to figure out which
server command to run. Heroku also compiles and runs your code with a
Leiningen "production" profile, instead of "dev". To locally simulate
what Heroku does you can do:
lein with-profile -dev,+production uberjar && foreman start
Now your app is running at http://localhost:5000 in production mode.
Copyright © 2016 FIXME
Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.
Created with Chestnut 0.16.0 (67651e9d).