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-keywith 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-togglefunction 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-paneljust 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/dispatchfunction 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-dbfunction 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 thedbmap's entry with the key:background-colorto 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-subfunction. 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-backgroundstyle to the.main-wrapperdiv. To subscribe to the subscription created above, use there-frame.core/subscribefunction, 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-wrapperdiv (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-successproperty, 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 thecolorparameter in Ex. 1. The response contains a vector of images that should be appended to the:imagesentry in the app-db. You can do this in a few ways usingupdate, the shortest probably being the functioninto, or a combination ofconjandflatten. - 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-boxin views.cljs. In order to render an image, you can use the provided functionresults-itemwhich takes one image as a parameter. Put the list of images inside[:div.results-box], where you can use themapfunction 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:keyproperty to the outer div that the functionresults-itemreturns, 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
valueentry 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
:httpkey to the:update-searchhandler function's return value. The format can be copied from the:load-randomhandler, but the URI should be constructed to be something like:/api/search?q=searchterm(see thestrfunction for help). - To create a corresponding backend endpoint, see routes.clj in
/src/clj/breakpoint_app. Use the existing/api/randomendpoint 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:returndefinition. - 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 therandomdefinition, but you will have to change the parameters to the functionquery-url. The first one should besearchand 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-urlfunction 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-debouncedeffects hander registration in events.cljs. Check it out to see how to add new types of handlers and uncomment it! - In the
:update-searchhandler, remove (but don't throw away!) the:http-xhriodefinition in the returned map. - Replace it with a
:dispatch-debouncedentry. The value should be a map, which is passed to the function in thedispatch-debouncedfx 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-fxtype and contain the:http-xhriodefinition 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 testTo 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 phantomThis 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 openHeroku 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 startNow 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).