A data-driven scaffolding library.
This library aims to allow you to build a frontend that both show data and promps mutations basead on your Graph API.
Inspirations:
- Heavily inpirated on Direct2Web from WebObjects
- scaffolding from CakePHP
-
Basead on EQL
-
Extensible via Pathom connect
-
No naming conventions
-
Separation between task's (how to get data) and ui's (how to show data)
-
Flow semanticweb standards
- TIP: it's based in
src/sample/counter/app.clj
. You can run withclj -A:dev -m counter.app
In the end, we want to have:
- A simple and easy frontend to interact with the app
- A minimal REST API
Let's build a simple "counter" app. Starting with a basic pathom connect API
;; [com.wsscode.pathom.connect :as pc]
(pc/defresolver count-resolver [{:my-app/keys [state]} input]
{::pc/output [:my-app/count]}
{:my-app/count @state})
(pc/defmutation inc-mutation [{:my-app/keys [state]} args]
{::pc/sym `inc-mutation
::pc/output [:my-app/count]}
{:my-app/count (swap! state inc)})
The inga/export
function will generate a hash-set
of pedestal routes.
;; [app.inga :as inga]
;; [io.pedestal.http :as http]
(defn register
[]
[inc-mutation count-resolver])
(defonce state (atom 0))
(def routes
(inga/export {:my-app/state state
::inga/register register}))
(def service
{:env :dev
::http/port 8081
::http/type :jetty
::http/routes #(route/expand-routes routes)
::http/join? false})
You can start this pedestal service from REPL with something like (-> service http/default-interceptors http/dev-interceptors http/create-server http/start)
.
At this stage, our rest API is already working.
$ curl http://localhost:8081/my-app/count
0
$ curl -X POST http://localhost:8081/my-app/inc-mutation
## will return 202 Accepted by default
$ curl http://localhost:8081/my-app/count
1
Now let's define a page which shows some arbitrary data
;; [app.inga :as inga]
;; [app.inga.ui :as inga.ui]
{::inga/path "/hello"
::inga/contents [{::inga/render ::inga.ui/query
::inga/display-properties [:my-app/count]}
{::inga/render ::inga.ui/mutation
::inga/mutation `my-app/inc-mutation}]}
attribute | value | description |
---|---|---|
::inga/path |
"/hello" |
An arbitrary "route" that will show up in browser |
::inga/contents |
[...] |
The list of components which we will use |
::inga/render |
::inga.ui/query |
The current UI implementation of the component. |
Any other key, like ::inga/display-properties [:my-app/count]
will be used by the render
implementation.
In this case, it's saying to query
task with keys you would like to show
Now we will put it inside a resolver and conj
into the app registry
;; [io.pedestal.http.route :as route]
(pc/defresolver router [app args]
{::pc/output [::inga/title
::inga/icon
::inga/router]}
{::inga/title "Ingá App"
::inga/icon "data:image/x-icon;,"
::inga/router {:my-app.page/hello {::inga/path "/hello"
::inga/contents [{::inga/render ::inga.ui/query
::inga/display-properties [:my-app/count]}
{::inga/render ::inga.ui/mutation
::inga/mutation `my-app/inc-mutation}]}}})
(defn register
[]
[router inc-mutation count-resolver])
As you can see,::inga/router
is returned by a resolver, so you can dynamically generate it based on anything.
At this point, you can connect to hello and you will see a table with :counter.app/count 27
and a form with counter.app/inc-mutation