{"id":13801118,"url":"https://github.com/plumatic/om-tools","last_synced_at":"2026-04-06T06:04:22.965Z","repository":{"id":16841884,"uuid":"19601606","full_name":"plumatic/om-tools","owner":"plumatic","description":"Tools for building Om applications","archived":false,"fork":false,"pushed_at":"2018-07-07T17:28:45.000Z","size":199,"stargazers_count":433,"open_issues_count":9,"forks_count":30,"subscribers_count":34,"default_branch":"master","last_synced_at":"2025-09-29T09:34:18.268Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/plumatic.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-05-09T06:35:30.000Z","updated_at":"2025-06-05T18:09:57.000Z","dependencies_parsed_at":"2022-08-04T11:45:12.404Z","dependency_job_id":null,"html_url":"https://github.com/plumatic/om-tools","commit_stats":null,"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/plumatic/om-tools","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/plumatic%2Fom-tools","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/plumatic%2Fom-tools/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/plumatic%2Fom-tools/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/plumatic%2Fom-tools/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/plumatic","download_url":"https://codeload.github.com/plumatic/om-tools/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/plumatic%2Fom-tools/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31461534,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-05T21:22:52.476Z","status":"online","status_checked_at":"2026-04-06T02:00:07.287Z","response_time":112,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-04T00:01:19.666Z","updated_at":"2026-04-06T06:04:22.943Z","avatar_url":"https://github.com/plumatic.png","language":"Clojure","funding_links":[],"categories":["Awesome ClojureScript"],"sub_categories":["Miscellaneous"],"readme":"# om-tools\n\nA ClojureScript library of general-purpose tools for building applications with\n[Om](https://github.com/omcljs/om) and\n[Facebook's React](http://facebook.github.io/react/).\n\nLeiningen dependency (Clojars):\n\n[![Clojars Project](https://img.shields.io/clojars/v/prismatic/om-tools.svg)](https://clojars.org/prismatic/om-tools)\n\n[![Build Status](https://travis-ci.org/plumatic/om-tools.svg?branch=fix-react-0-12-dom)](https://travis-ci.org/plumatic/om-tools)\n\n**This library does not currently have an active maintainer.  If you are interested in becoming one, please post an issue.**\n\n## Introduction\n\nom-tools aims to provide higher-order abstractions and utilities frequently\nuseful when building components with Om's API.\n\n## Contents\n\n*   [DOM tools](#dom-tools)\n*   [Components tools](#component-tools)\n    *   [`defcomponent`](#defcomponent)\n    *   [`defcomponentk`](#defcomponentk)\n    *   [`defcomponentmethod`](#defcomponentmethod)\n*   [Mixin tools](#mixin-tools)\n\n## DOM tools\n\n`om-tools.dom` mirrors the `om.dom` namespace while using macros and\nminimal runtime overhead to make the following improvements:\n\n\n*   Element attributes are not required to be JavaScript values and are\n    optional. You don't need to use the `#js` reader macro or `nil`\n    for no attributes.\n*   More natural attribute names. We translate attributes like\n    `:class` to `:className` and `:on-click` to `:onClick` to stay\n    consistent with Clojure naming conventions.\n*   Children can be in collections.  You don't need to use `apply` if\n    you have a sequence of children or use `concat` for combining\n    sequences of siblings.\n\nExample by comparison. First with `om.dom`:\n\n```clojure\n(ns example\n  (:require [om.dom :as dom :include-macros true]))\n\n(dom/div\n  nil\n  (apply dom/ul #js {:className \"a-list\"}\n         (for [i (range 10)]\n           (dom/li #js {:style #js {:color \"red\"}}\n                   (str \"Item \" i)))))\n```\n\nAnd with `om-tools.dom`:\n\n```clojure\n(ns example\n  (:require [om-tools.dom :as dom :include-macros true]))\n\n(dom/div\n  (dom/ul {:class \"a-list\"}\n          (for [i (range 10)]\n            (dom/li {:style {:color \"red\"}}\n                    (str \"Item \" i)))))\n```\n\n## Component tools\n\n### `defcomponent`\n\nThe `om-tools.core/defcomponent` macro defines Om component\nconstructor functions.\n\nAdvantages over the ordinary `defn` \u0026 `reify` approach:\n\n*   Removes boilerplate code around using `reify` to instantiate\n    objects with Om lifecycle methods. Component definitions become\n    much smaller and easier to read.\n*   Adds [Schema][schema] support to specify and validate the data\n    when component is built.\n\n    One of React's most\n    [powerful features](https://speakerdeck.com/vjeux/why-does-react-scale-jsconf-2014)\n    is\n    [prop validation](http://facebook.github.io/react/docs/reusable-components.html#prop-validation),\n    which allows a component's author to document and validate which\n    properties a component requires and their types.\n\n    This functionality is not utilized in Om because we use normal\n    ClojureScript data structures as component inputs. However, with\n    more complex input structures, documentation and validation are\n    even more important.\n\n    Schema annotations are optional and validation is disabled by\n    default.\n*   Automatically implements `IDisplayName` for better debugging messages.\n\nExample of `defcomponent` including schema annotation:\n\n```clojure\n(ns example\n  (:require\n    [om-tools.core :refer-macros [defcomponent]]\n    [om-tools.dom :include-macros true]))\n\n(defcomponent counter [data :- {:init js/Number} owner]\n  (will-mount [_]\n    (om/set-state! owner :n (:init data)))\n  (render-state [_ {:keys [n]}]\n    (dom/div\n      (dom/span (str \"Count: \" n))\n      (dom/button\n        {:on-click #(om/set-state! owner :n (inc n))}\n        \"+\")\n      (dom/button\n        {:on-click #(om/set-state! owner :n (dec n))}\n        \"-\"))))\n\n(om/root counter {:init 5}\n         {:target (. js/document -body)})\n```\n\n### `defcomponentk`\n\nThe `om-tools.core/defcomponentk` macro is similar to `defcomponent`,\nexcept that it uses [Plumbing][plumbing]'s `fnk` destructuring\nsyntax for constructor arguments.\nThis enables succinct and declaritive definition of the structure and\nrequirements of component input data.\n\nIt also provides additional useful utilities mentioned in\n[Component Inputs](#component-inputs).\n\n#### Fnk-style Arguments\n\nThe args vector of `defcomponentk` uses\n[Fnk syntax](https://github.com/plumatic/plumbing/tree/master/src/plumbing/fnk#fnk-syntax)\nthat's optimized for destructuring (nested) maps with keyword keys.\nIt is the similar pattern used in our\n[Fnhouse](https://github.com/plumatic/fnhouse) library to\nexpressively declare HTTP handlers.\n\nIf you are unfamiliar with this syntax, here are some quick comparisons\nto default Clojure map destructuring.\n\n```clojure\n{:keys [foo bar]}                    :: [foo bar]\n{:keys [foo bar] :as m}              :: [foo bar :as m]\n{:keys [foo bar] :or {bar 21}}       :: [foo {bar 21}]\n{{:keys [baz qux]} :foo :keys [bar]} :: [[:foo baz qux] bar]\n```\n\nHowever, an important distinction between Clojure's default\ndestructuring and Fnk-style is that specified keys are required by\ndefault.\nRather than defaulting to `nil`, if a key that's destructured is\nmissing and no default value is specified, an error is thrown.\n\nBy being explicit about component inputs, we are less error-prone and\ndebugging is often easier because errors happen closer to the source.\n\n#### Component Inputs\n\nThe map that's passed to `defcomponentk` arg vector has the following\nkeys:\n\n| Key       | Description\n| --------- |-------------------------------------------------------------------\n| `:data`   | The data (cursor) passed to component when built\n| `:owner`  | The backing React component\n| `:opts`   | The optional map of options passed when built\n| `:shared` | The map of globally shared data from om.core/get-shared\n| `:state`  | An atom-like object for convenience to om.core/get-state and om.core/set-state!\n\n#### Example\n\n```clojure\n(ns example\n  (:require\n    [om.core :as om]\n    [om-tools.core :refer-macros [defcomponentk]]\n    [schema.core :refer-macros [defschema]]))\n\n(defschema ProgressBar\n  {:value js/Number\n   (s/optional-key :min) js/Number\n   (s/optional-key :max) js/Number})\n\n(defcomponentk progress-bar\n  \"A simple progress bar\"\n  [[:data value {min 0} {max 100}] :- ProgressBar owner]\n  (render [_]\n    (dom/div {:class \"progress-bar\"}\n      (dom/span\n        {:style {:width (-\u003e (/ value (- max min))\n                            (* 100)\n                            (int)\n                            (str \"%\"))}}))))\n```\n\n```clojure\n;; Valid\n(om/root progress-bar {:value 42}\n  {:target (. js/document (getElementById \"app\"))})\n\n;; Throws error: Key :value not found in (:wrong-data)\n(om/root progress-bar {:wrong-data true}\n  {:target (. js/document (getElementById \"app\"))})\n\n;; Throws error: Value does not match schema\n(schema.core/with-fn-validation\n  (om/root progress-bar {:value \"42\"}\n    {:target (. js/document (getElementById \"app\"))})\n```\n\n#### State Proxy (experimental)\n\nA component using `defcomponentk` can use the key, `:state`, to access\nan atom-like object that conveniently wraps `om.core/get-state` and\n`om.core/set-state!` so that we can read and write state idiomatically\nwith `deref`, `reset!` and `swap!`.\n\n```clojure\n(defcomponentk progress-bar\n  \"A simple progress bar\"\n  [[:data value {min 0} {max 100}] state]\n  (render [_]\n    (dom/div {:class \"progress-bar\"}\n      (dom/span\n        {:style {:width (-\u003e (/ value (- max min))\n                            (* 100)\n                            (int)\n                            (str \"%\"))}\n         :on-mouse-enter #(swap! state assoc :show-value? true)\n         :on-mouse-leave #(swap! state assoc :show-value? false))}\n        (when (:show-value? @state)\n          (str value \"/\" total))))))\n```\n\nIt's important to note that while `state` looks and behaves like\nan `atom`, there is at least one minor difference: changes made by\n`swap!` and `reset!` are not immediately available if you `deref`\nin the same render phase.\n\n### `defcomponentmethod`\n\nWith Om, [multimethods](http://clojure.org/multimethods) can be used\ninstead of normal functions to create polymorphic components (requires\nOm version 0.7.0+).\nThe `defcomponentmethod` macro allows you to register components into\na multimethod (created from `cljs.core/defmulti`), while using\nthe normal om-tools syntax.\n\n```clojure\n(defmulti fruit-basket-item\n  (fn [fruit owner] (:type fruit)))\n\n(defcomponentmethod fruit-basket-item :orange\n  [orange owner]\n  (render [_]\n    (dom/label \"Orange\")))\n\n(defcomponentmethod fruit-basket-item :banana\n  [banana owner]\n  (render [_]\n    (dom/label\n     {:class (when (:peeled? banana) \"peeled\")}\n     \"Banana\")))\n\n(defcomponentmethod fruit-basket-item :default\n  [fruit owner]\n  (render [_]\n    (dom/label (str \"Unknown fruit: \" (name (:type fruit))))))\n\n(om/build-all fruit-basket-item\n              [{:type :banana}\n               {:type :pineapple}\n               {:type :orange}])\n```\n\n## Mixin tools\n\nReact provides [mixin functionality][react-mixin] to handle\ncross-cutting concerns and allow highly reusable component behaviors.\nWhile [mixins are possible with Om][om-mixin], it does not provide\nmuch functionality to support this React feature.\nOne issue is that you must create a React constructor and specify it\neach time the component is built.\nThis puts the responsibility of using mixins on both the component\n(create a constructor) and its parent (specify the constructor).\nAnother issue is having to drop down to raw JavaScript functions,\nbreaking you out of Om's data and state abstractions.\n\nom-tools provides a `defmixin` macro in the `om-tools.mixin` namespace\nto define mixins. The syntax of `defmixin` follows same pattern as the\ncomponent macros.\n\nOne last thing: the factory functions created by\n`defcomponent`/`defcomponentk` (ie `(-\u003ecomponent-name data)`)\nencapsulate any custom constructor automatically. So a parent\ncomponent no longer needs to be aware when a child uses mixins!\n\nHere's how you could reimplement [React's mixin example][react-mixin]:\n\n```clojure\n(ns example\n  (:require\n    [om-tools.core :refer-macros [defcomponentk]]\n    [om-tools.dom :as dom :include-macros true]\n    [om-tools.mixin :refer-macros [defmixin]]))\n\n(defmixin set-interval-mixin\n  (will-mount [owner]\n    (set! (. owner -intervals) #js []))\n  (will-unmount [owner]\n    (.. owner -intervals (map js/clearInterval)))\n  (set-interval [owner f t]\n    (.. owner -intervals (push (js/setInterval f t)))))\n\n(defcomponentk tick-tock [owner state]\n  (:mixins set-interval-mixin)\n  (init-state [_]\n    {:seconds 0})\n  (did-mount [_]\n    (.set-interval owner #(swap! state update-in [:seconds] inc) 1000))\n  (render [_]\n    (dom/p\n      (str \"React has been running for \" (:seconds @state) \" seconds.\"))))\n```\n\nSee [example](examples/mixin) for full version.\n\n## Community\n\nPlease feel free to open an\n[issue on GitHub](https://github.com/plumatic/om-tools/issues/new)\n\nFor announcements of new releases, you can also follow on\n[@PrismaticEng](http://twitter.com/prismaticeng) on Twitter.\n\nWe welcome contributions in the form of bug reports and pull requests;\nplease see CONTRIBUTING.md in the repo root for guidelines.\n\n## License\n\nCopyright (C) 2014 Prismatic and Contributors. Distributed under the Eclipse\nPublic License, the same as Clojure.\n\n[schema]: https://github.com/plumatic/schema\n[plumbing]: https://github.com/plumatic/plumbing\n[om]: https://github.com/swannodette/om\n[react-mixin]: http://facebook.github.io/react/docs/reusable-components.html#mixins\n[om-mixin]: https://github.com/swannodette/om/blob/master/examples/mixins/src/core.cljs\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fplumatic%2Fom-tools","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fplumatic%2Fom-tools","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fplumatic%2Fom-tools/lists"}