A ClojureScript, re-frame application designed to
- serve as a showcase of some of the ClojureScript / re-frame concepts I've learned
- invite collaboration as to ClojureScript / re-frame usage & best practices
- serve as an example of a real world ClojureScript / re-frame application
- be potentially useful as a portfolio management tool for a buy-to-let property investor (The Buy2Let Portfolio Manager online service & mobile app is built from this repository)
View the Online Demo
- The demo stores all data locally in your browser. New data entered will be lost when reloading the page
- Invoices / attachments will not be uploaded / stored on the demo server & therefore won't download when clicking the paperclip
npm install
clj -M:client watch app
shadow-cljs will automatically push cljs changes to the browser.
Wait a bit, then browse to http://localhost:8280
Material-UI React components wrapped with reagent-material-ui (for ease of use within ClojureScript)
This repository specifically contains client-side only code. Any data captured when running this application locally in development mode, or when playing with the online demo, is saved in the local re-frame app db, which will be lost once you refresh the browser.
You are responsible for providing your own backend (server, database & messaging protocol), & here you are free to choose your favourite stack.
Hooking up your backend to this re-frame app is done by implementing various clojure multimethods which is defined in the namespace: wkok.buy2let.backend.multimethods
The dispatching function assumes a global variable declared in the host html page. See the example in resources/public/index.hrml
Change this global variable to whatever your defmethod
functions expect for example:
let impl = "my-cool-app"
Lastly, the namespace defining your defmethod
's should be listed in the :entries
section of the module in shadow-cljs.edn. Replace wkok.buy2let.backend.demo
with your backend's namespace.
Interaction with the backend is accomplished using re-frame effects
Basically, the multimethods you implement, are called by the application at the right times where interaction with the backend is needed. The map of backend effect(s) you return, will be merged with other application effects (like updating the local re-frame app-db)
For example, to implement the persistence of a CRUD item (eg. Property, Charge, etc.) you'll implement the multimethod save-crud-fx
and return an effect like
(defmethod save-crud-fx :my-cool-app
[{:keys [account-id crud-type id item on-error]}]
{:my-backend/save {:account-id account-id
:crud-type crud-type
:id id
:item item
:on-error on-error}})
Then, register the effect (unless your backend library does this for you)
(re-frame/reg-fx
:my-backend/save
(fn [data _]
;; Add code here to send the data to the server / database
))
It is worth noting that the app assumes an Optimistic Update strategy when persisting something to the backend.
This means the local app-db will be opportunistically updated, giving instant affirmative feedback to the user on the screen, while the backend action will continue in the background.
This is based on the assumption that 90% of server updates will succeed, so it is mostly unnecessary for the user to have to wait for the server to respond before updating the screen. But this is explained in more detail on purelyfunctional.tv
Copyright (c) 2020 Werner Kok
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.