December 15, 2014

Imagining atomatizing the App

This morning, while reflecting on a late night of working through David Nolen's (swannodette) Intro to Clojurescript and Om Basic Tutorial, I had an epiphany, an insight into a solution to a problem I've been working on for about 15 years. The goal I've been seeking is an elegant app for a non-programmer user to create a custom, elegant scientific data collection and visualization app. It requires building up a data survey from simple and complex data types, possibly based on an existing denormalized database schema, and linking that with an easily customizable data entry screen. It's kind of like Access, but more complex while also easier to use. Yes, a paradox. XRX is a major step in the right direction, but the designed by commitee feel and actually writing and reading XML and XQuery leaves something to be desired. However, I've never really given it a chance and it could be great. I like the document store approach of eXistDB, similar to MongoDB for JSON data. ODK is doing amazing things with XRX, but only by reverting to HTML and javascript for the UI.

I've taken a few runs at the solution over the years, starting with a naive PHP app in the foggy beginning of my programming career, and a couple of paid database gigs for clients for similar needs, but I have yet to attain the degree of Nirvana I know is possible. When I became familiar with Clojure, I intuited that it would probably be much more useful for the job, due to its homoiconic nature, being represented in its own data format and amenable to meta-programming, and to its immutable data types - a severely useful toolbox. But the language has been opaque to me, and simultaneously both too complex and too simple to see how to use it to its fullest ... until this morning. Suddenly, while looking through my bay windows to the trees behind my monitor, I saw the architecture I needed to use. Kind of a mix between a spreadsheet tree, data schema UML, Pure Data wiring, a Swift playground, etc, but that changes based on position within the multidimensional data abstraction context tree.

One possible step toward making this app easier to contemplate is the definition of the app itself, which Om's component-based nature aids. However, I'm wondering if there's an additional step of abstraction that could be taken to a simpler declarative definition of the app, which would be easier for a user or a builder app to construct and edit. For example, here's a prototypical Om component definition:

    (defn comment [{:keys [author text]} owner]
        (dom/div #js {:className "comment"}
          (dom/h2 nil author)
          (dom/span nil text))))

It's a very nice declarative way to define a component, and reminds me of Enyo's nested declarative javascript component model:

{kind: "onyx.Button", name:"Image Button", ontap:"buttonTapped", components: [
  {tag: "img", attributes: {src: "assets/enyo-logo-small.png"}},
  {content: "There is an image here"}

Enyo is the closest tool I've found yet to enabling the workflow I need. However, it feels funky. I know, I'm picky.

Clojure's Lispiness is very amenable to representing the tag nature of XML and HTML, and is preferrable if the UI is being generated programmatically anyway. In that vein, Enyo's definition is more clear in some ways because it is simpler, relying on a convention or schema for being converted into running code. But what if the Om approach were to be similarly abstracted further? Can the following simpler version work?

{ :name :comment
  :class "comment"
  ;:kind :div ; :div is the default
  :content [
   { :kind :h2, :state :author}
   { :kind :span, :state :text}]}

It's easier for me to read and seems to contain all of the same essential info, without a lot of the repetitive code (DRY). This declaration is taking advantage of some conventions. One is that a component will pass to its children the same state that it received, unless otherwise stated (pun intended). It's unnecesary to declare its input variables, and it only defines more refined sub-cursors when handing state to its children. This declaration could be used to generate the CLJS code above. This component uses no state of its own; it's just a container DIV. This is another convention: a component is by default a DIV, unless otherwise noted. Here's another more complex component, first in Om code:

(defn comment-list [cs owner]
    (dom/div nil
      (dom/div #js {:className "commentList"}
        ;; Helper function build-all builds a vector of components
        (om/build-all comment cs))
      (dom/button #js
        {:onClick #(change-author cs "Fredrik")}
        "Change authors"))))


and here's a mapped abstraction:

{ :name :commentList
  :state [:comment] ; Declares that this component accepts a vector of :comment components.
  :content [
    { :class "commentList"
      :content [
        {:kind :comment, :build :all}]}
    { :kind :button
      :onClick [:changeAuthor :state "Frederik"]
      :content "Change authors"}]}

The mapping of the "build-all" macro seems a bit wonky, but I'm confident there's some reasonable way to represent it. Also, does this need a schema? A Schema might be nice, but really, it's all convention over configuration, a la Enyo, yes? Hmmm ...

Here's a full mapping with its original

My impression is that the mapped form would be much easier for me to reason around with the aim of building an app that could read and write this structure. These structures would be the blueprint from which to actually output the clojure code above. It's the equivalent of the relationship between the data in the atom and its runtime representation to the user.

Plus, as if that weren't enough, there's a huge bonus to this approach: undo and change branching (and saving, and sharing) of the app itself, FOR FREE! Non-programmers are overwhelmed enough trying to make a relatively sophisticated app, but this would make it much easier to save different versions, try something for a while, save it, go back a few steps, go in a different direction, save it, and then be able to browse around through the different versions. This might even make it possible to more easily diff different versions.

One caveat to this approach is that functions will need to be saved and versioned outside of the the app-def atom. Or they can be represented as strings and edited from within the builder app. I neglected to mention that I'm fairly new to clojure, only having worked through David Nolen's intro tutorial yesterday, so I have much to learn and discover of the Joys of Clojure.

Tags: om data-driven clojurescript