hatti
A cljs dataview from Ona.
Overview
This library is a work-in-progress to package up much of the dataview used in Ona's new product. It provides a set of Om components that can be used to visualize a dataset that has a schema attached to it.
Installation
Install via clojars with:
Terminology used below:
-
data
is a vector ofrecords
-
flat-form
is a vector offields
- Each
field
is a map, containing keysname
,type
,full-name
,label
at the very least. -
fields
with type repeats are special; we will not go into detail about them for now. - Each
record
is also a map. Each key in arecord
should correspond to thefull-name
of somefield
.
General overview of state, shared-state, etc.
app-state
has the following structure:
{:data []
:map-page {:submission-clicked {:data nil}
:geofield {}}
:table-page {:submission-clicked {:data nil}}
:chart-page {:visible-charts []
:chart-data {}}
:dataset-info {}
:languages {:current nil :all []}}
The meaning of the state is as follows:
-
:data
should be all of the data for this dataset. A list of records, each record being a submission. -
:dataset-info
is what is available via the/forms/
endpoint. Inside here, there should be things like formmetadata
,num_of_submissions
andinstances_with_geopoints
,enketo_url
,enketo_preview_url
and such. -
languages
stores the set of languages labels in this form are written in inside of:all
, the currently selected language (default English) is store in :current. This part of the app-state is used by the reference cursor language-cursor. -
:map-page
,:table-page
, and:chart-page
contain the state necessary for each of those pages.-
:submission-clicked
stores data about the record which is clicked. -
visible-charts
is the list of all charts that the user has called up on this page.chart-data
is a map that stores the raw chart API data for all of the fields that the user has requested charts on.
-
User has the responsibility of updating :data
, :dataset-info
and :flat-form
by fetching them; the utility flatten-form
is available to convert what you get from Ona
into a flat form..
Note: for speed of decoding, it is assumed for now that the keys for data
are plain strings, not keywords. data
is exceptional in this way; other maps are expected to contain plain clojure(script) keywords.
shared-state
needs:
{:flat-form []
:view-type :ona-default}
exposed cursor
s:
shared/language-cursor
To render the dataview into your application, you'll need something like the following. The app-state can be updated once constructed, and generally the right thing will happen. Shared state cannot be updated.
(om/root tabbed-dataview
app-state-atom
{:target ...
:shared {:flat-form _your-form_
:view-type _your-view-type_}})
(Future: flat-form
will be moved to app-state, since it does need to be updated sometimes).
Component Hierarchy
-
tabbed-dataview
dataview-infobar
-
map-page
(cursor: map-page, dataset-info)-
map-and-markers
(cursor: map-page) -
geofield-chooser
(cursor: map-page geo-field) -
view-by-legend
(cursor: map-page view-by, dataset-info)-
view-by-menu
(cursor: dataset-info)
-
-
view-by-answer-legend
(cursor: view-by) -
submission-legend
(cursor: map-page geo-field, map-page submission-clicked)single-submission/submission-view
-
-
table-page
-
label-changer
(cursor: nil, works on shared cursorlanguage-cursor
single-submission/submission-view
-
-
chart-page
-
chart-chooser
(cursor: nil) -
list-of-charts
(cursor: chart-page)-
single-chart
(cursor: chart-page chart-data >element)
-
-
-
settings-page
(cursor: dataset-info)- (settings-page will be super basic; it will just display the name / description / active-inactive status, and not allow for any editing of data)
Hatti structure (proposal)
Hatti will primarily provide a set of Om components that are meant to be used in a dataview such as Zebra. All of Hatti's Om components should be over-rideable, you should be able to over-ride any of the Internal Hatti components easily and have a new dataview that incorporates such over-riding. As such, here is the basic proposal for how Hatti's components should be written:
Components in Hatti should be multi-methods. By default, the dispatch value will be :view-type
[1] stored in the application shared state.
(defmulti map-page
(fn [_ owner & _]
(om/get-shared owner :view-type)))
(defn map-page :ona-default
[cursor owner]
; actual map-page definition
...)
This will allow any user to include the map-page
in their view.
Now say that they want to change the way the map view-by menu is rendered. They would do this by providing a value to correspond to ::view-type
in the om/root
call, and then overriding whatever component or subcomponent they want implemented differently.
(derive :my-view :ona-default)
(om/root map-page atom {:target ...
:shared {:view-type :my-view}})
(defn view-by-menu :my-view
[cursor owner]
; the view-by-menu definition specific to :my-view
...)
There may be some components which also need some special treatment within other views, eg. a submission-view which is different for the map and the table page. These should be implemented as multi-methods with a different dispatch function that still incorporates the shared ::view-type
, An example could be:
(defmulti single-submission-view
(fn [cursor owner & _]
[(om/get-shared owner :view-type) (-> cursor :selected-view)]))
(defmethod single-submission-view [:ona-default :map]
(fn [cursor owner opts]
...))
(defmethod single-submission-view [:ona-default :table]
(fn [cursor owner opts]
...))
[1] - It will actually be the namespace-qualified ::view-type
, but I need to do a bit more research to figure out how to use it properly.
License
Hatti is released under the Apache 2.0 License.