ClojureScript Mini Model View Presenter framework
JavaScript CSS Clojure Other
Switch branches/tags
Nothing to show
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.



Mini-MVP (Model-View-Presenter) library provides basics to create models that are regular Clojure objects. Any changes to these objects are automatically intercepted and communicated as updates to presenters that update views. This allows for clean isolation between data (that are regular Clojure collections and objects) and presenters (that update views based on updates).

Any model that supports ILookup protocol can be used as a composite model, which comprises other composite and atomic models.
Models that don't support ILookup protocol are atomic models as far as Mini-MVP is concerned and any inner changes in the state of that model is not tracked.

Mini-MVP strives for keeping all relationships among M-V-P components and composite MVP triads explicitly defined as the path tree into the model.

Let's say you have some data model that you'd like to use.
(def my-model (mvp/create { :type Text :hint "Enter your age" :value "1234" :error "Value too big to be true" }))

Let's bind this model onto a view that renders the text and the error from the model. I'm using jQuery here, the demo also uses Closure.

;; Defines a subscriber that updates a control's value if the model changes.
(mvp/add-reader text-model [:text] #(.val (jq "#text2") (:new-value %)))

;; In a similar way, we also render the error message, which is generated by a validator
(mvp/add-reader my-model [:error] #(.html (jq "#error-message") (:new-value %)))

;; now assume we have a generic age validation function
(defn- age-validator
    "Answers a text error message if the specified age is out of valid range.
     Yields an empty string in case if the specified age appears to be correct."
    (if (> age 120) "Value too big to be true" ""))

;; and use the age validation function in our model
   my-model  ;; we subscribe to changes in my-model
   [:value]  ;; at path ROOT -> :value
   ;; and when a value under ROOT->:value changes, we update the value at ROOT->:error
   #(mvp/assoc-value my-model [:error] (age-validator (:new-value %))))

;; finally, update the model when the age is changed
(.change (jq "#input-control") #(mvp/assoc-value my-model [:value] (. (jq "#input-control") (val))))

Now, if you enter any age less than 120, the error message is cleared. Enter more than 120 and it'll show up again.

API is documented in mpv.cljs that implements the core functionality. The rest of the files are not necessary and just serve as samples on how to use this librar.

create <some clojure data model> <optional initial version> <optional function to suggest the next version given the current>
       Creates a new MVP model.

create-ref <existing MVP mode> <path in the model>
       Creates a model that redirects to a subpath in the parent model.

get-version <MVP model>
       Answers the current version of the model. Each change causes version change.

get-value <MVP model> [path] <optional default value>
       Similar to clojure.core/get-in, gets the value in the model under the specified path.
       Empty path [] fetches the root of the model.

update-value <MVP model> [path] <function to apply> <additional arguments to the function ...>
       Similar to clojure.core/update-in, updates the model's value under the specified path. The specified function is invoked to get the new value.
       Producing equal value is considered a no-op.

assoc-value <MVP model> [path] <new-value>
       Similar to clojure.core/assoc-in, associates a new value with the sub-model at the specified path.
       Assigning equal value is considered a no-op.

add-writer <MVP model> [path] <subscriber function>
       Adds a 1st phase subscriber function that is invoked when the value under the specified path changes.
       The function is not invoked if the change(s) didn't cause actual value change.
       I.e. given a model { :first "CA" :second "NY" } which is being updated to { :first "WA" :second "NY" },
       the subscriber for [:second] isn't invoked. The subscribers for [] and [:first] are to be called.

       A subscriber is a function that receives a single parameter { :new-value <new-value> :old-value <old-value> :new-version <new-model-version> }.

add-reader <MVP model> [path] <subscriber function>
       Exactly the same as add-writer, but these folks are invoked after all writers.
       The idea is to avoid unnecessary UI rendering/flickering while validators/statistics update the model.

       A subscriber is a function that receives a single parameter { :new-value <new-value> :old-value <old-value> :new-version <new-model-version> }.

clear <MVP model> [path]
       Eliminates all subscribers under the specified path.
       (clear my-model []) ;; eliminates all existing subscribers

delay-events <MVP model> <function>
       All subscriber calls are delayed until after the function completion.
       Only subscribers which guarded value has actually changed are called.
       I.e. given a subscriber for path=[1] in a model ["a" "b" "c"], three subsequent changes within (delay-events ...)
       ["a" "b" "c"] -> ["b" "c" "d"] -> ["e" "f" "g"] -> ["h" "b" "e"] won't trigger the subscriber (which is subscribed for "b").

version <MVP model>
       Returns the current version of the model. By default, a new unique identifier is generated after each actual change.
       You can define the initial version and a function to generate new versions:
       (mvp/create { data...} 12 inc)  ;; will start with version=12, which is incremented upon each change


Same as Clojure/ClojureScript.