Skip to content

Instantly share code, notes, and snippets.

@ckirkendall
Last active January 3, 2016 22:59
Show Gist options
  • Save ckirkendall/8532519 to your computer and use it in GitHub Desktop.
Save ckirkendall/8532519 to your computer and use it in GitHub Desktop.
Enliven Component Model

##Sketch for Enliven Component Model

This design page is a sketch of how I see enliven being used for dynamic templates in the browser.

Design Principles

  1. All state is in an application state object.
  2. State is scoped through lenses.
  3. Users should not have to manage lenses manually.
  4. There is no tree walking; paths/lenses, for dom manipulation, are determined at compile time.
  5. Dom nodes are pre-rendered where possible and only areas where change occurs get re-rendered.
  6. App state is changed through events.

Transforms, Components, and Lenses

dynamic-templates take html from either a resource or string and a set of transforms.
Transforms are similar to enlive with selector+transform pairs.

Note: we may need some way to pass in a set of lenses/paths that point to the relevant application scope and relevant transient scope.

;; html - "<h1>test</h1>" or Resource
;; transformations pairs 
(dynamic-template [html lens-paths & transforms] ...)

return value

;;the base app-data needs to be an atom if not accessed trough a lens
;;might need to pass in a set of optional lenses here
(fn [root data] ...) 

I see templates/components as a series of nested structures that update the application state by propagating changes through a set of nested lenses. We need to provide helper functions that make it easy for them to create and nest the lenses. In this gist you use scope as a way of specifying the lens for an event. We might need to use something similar for sub-templates/components.

(def my-button 
  (dynamic-template "<div><button id="my-btn"></button><div>" 
    :button (scope :done
              (on-click [e done] (not done)))))

(def parent-comp 
  (dynamic-template "<div><h1>button below</h1><span id="tmp></span></div>"
    :#tmp (scope :button-data
            (attach-template my-button)))))

Rendering

I would suggest we use the same model as om when it comes to rendering. Rendering should be async and only done at requestAnimationFrame. With the scope functionality and a list of paths changed at each iteration, we can ask the components to update. Each component/template will contain logic that checks paths used and updated only those transforms affected.

###Anatomy of a Component

The design of the component is meant to allow eliven to manage the scope/lenses into the application state and abstract away the handling of internal component state.

(defprotocol Component
  (update [this dirty-path-set app-lens component-lens] )
  (initialize [this app-lens component-lens])
  (register-child [cstate scope child]))
  
;;this is returned by dynamic-template
(fn [root app-state]
 (reify 
   Component 
   (update [this dirty-path-set app-lens component-lens]
     (let [cstate (fetch component-lens)
	       astate (fetch app-lens)
       (do-seq [path (keys (:transforms (component-lens app-state)))]
         (when (contains? dirty-path-set path)
	       (modify-ref this cstate astate app-state path)))
		(update-children dirty-path cstate astate)))
		
   (initialize [this app-lens component-lens]
	 ;this is generated code with an accumulator for transforms	 
     (let [transforms (render-intial-dom ...)] 
	    (->> transforms
	     (push component-lens :transforms) 
         (update this transforms app-lens))))
		 
   (register-child [cstate scope child]
     (let [child-id (gen-id)]
	   ;;register child creates the comp lens, app lens
	   ;;and calls initialize on the child comp
       (register-child child-id cstate scope child)))))



  
;; bind-root is where it all begins in well formed app this would be at the body  
;;this adds the watcher on the app-state atom
;;this also initialized the first component and app lenses 
(bind-root state component root)


;;helper functions
(modify-ref [comp {:keys [node rules]} app-sate]
  (execute-plan rules node (find-in-path data path)))
@cgrand
Copy link

cgrand commented Jan 22, 2014

The general idea is correct. However I think components will be transforms. No need to attach-template them.

See https://gist.github.com/cgrand/8553906

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment