Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

Tools for building Om applications

License

NotificationsYou must be signed in to change notification settings

plumatic/om-tools

Repository files navigation

A ClojureScript library of general-purpose tools for building applications withOm andFacebook's React.

Leiningen dependency (Clojars):

Clojars Project

Build Status

This library does not currently have an active maintainer. If you are interested in becoming one, please post an issue.

Introduction

om-tools aims to provide higher-order abstractions and utilities frequentlyuseful when building components with Om's API.

Contents

DOM tools

om-tools.dom mirrors theom.dom namespace while using macros andminimal runtime overhead to make the following improvements:

  • Element attributes are not required to be JavaScript values and areoptional. You don't need to use the#js reader macro ornilfor no attributes.
  • More natural attribute names. We translate attributes like:class to:className and:on-click to:onClick to stayconsistent with Clojure naming conventions.
  • Children can be in collections. You don't need to useapply ifyou have a sequence of children or useconcat for combiningsequences of siblings.

Example by comparison. First withom.dom:

(nsexample  (:require [om.dom:as dom:include-macrostrue]))(dom/divnil  (apply dom/ul #js {:className"a-list"}         (for [i (range10)]           (dom/li #js {:style #js {:color"red"}}                   (str"Item" i)))))

And withom-tools.dom:

(nsexample  (:require [om-tools.dom:as dom:include-macrostrue]))(dom/div  (dom/ul {:class"a-list"}          (for [i (range10)]            (dom/li {:style {:color"red"}}                    (str"Item" i)))))

Component tools

defcomponent

Theom-tools.core/defcomponent macro defines Om componentconstructor functions.

Advantages over the ordinarydefn &reify approach:

  • Removes boilerplate code around usingreify to instantiateobjects with Om lifecycle methods. Component definitions becomemuch smaller and easier to read.

  • AddsSchema support to specify and validate the datawhen component is built.

    One of React's mostpowerful featuresisprop validation,which allows a component's author to document and validate whichproperties a component requires and their types.

    This functionality is not utilized in Om because we use normalClojureScript data structures as component inputs. However, withmore complex input structures, documentation and validation areeven more important.

    Schema annotations are optional and validation is disabled bydefault.

  • Automatically implementsIDisplayName for better debugging messages.

Example ofdefcomponent including schema annotation:

(nsexample  (:require    [om-tools.core:refer-macros [defcomponent]]    [om-tools.dom:include-macrostrue]))(defcomponentcounter [data:- {:init js/Number} owner]  (will-mount [_]    (om/set-state! owner:n (:init data)))  (render-state [_ {:keys [n]}]    (dom/div      (dom/span (str"Count:" n))      (dom/button        {:on-click #(om/set-state! owner:n (inc n))}"+")      (dom/button        {:on-click #(om/set-state! owner:n (dec n))}"-"))))(om/root counter {:init5}         {:target (. js/document -body)})

defcomponentk

Theom-tools.core/defcomponentk macro is similar todefcomponent,except that it usesPlumbing'sfnk destructuringsyntax for constructor arguments.This enables succinct and declaritive definition of the structure andrequirements of component input data.

It also provides additional useful utilities mentioned inComponent Inputs.

Fnk-style Arguments

The args vector ofdefcomponentk usesFnk syntaxthat's optimized for destructuring (nested) maps with keyword keys.It is the similar pattern used in ourFnhouse library toexpressively declare HTTP handlers.

If you are unfamiliar with this syntax, here are some quick comparisonsto default Clojure map destructuring.

{:keys [foo bar]}:: [foo bar]{:keys [foo bar]:as m}:: [foo bar:as m]{:keys [foo bar]:or {bar21}}:: [foo {bar21}]{{:keys [baz qux]}:foo:keys [bar]}:: [[:foo baz qux] bar]

However, an important distinction between Clojure's defaultdestructuring and Fnk-style is that specified keys are required bydefault.Rather than defaulting tonil, if a key that's destructured ismissing and no default value is specified, an error is thrown.

By being explicit about component inputs, we are less error-prone anddebugging is often easier because errors happen closer to the source.

Component Inputs

The map that's passed todefcomponentk arg vector has the followingkeys:

KeyDescription
:dataThe data (cursor) passed to component when built
:ownerThe backing React component
:optsThe optional map of options passed when built
:sharedThe map of globally shared data from om.core/get-shared
:stateAn atom-like object for convenience to om.core/get-state and om.core/set-state!

Example

(nsexample  (:require    [om.core:as om]    [om-tools.core:refer-macros [defcomponentk]]    [schema.core:refer-macros [defschema]]))(defschemaProgressBar  {:value js/Number   (s/optional-key:min) js/Number   (s/optional-key:max) js/Number})(defcomponentkprogress-bar"A simple progress bar"  [[:data value {min0} {max100}]:- ProgressBar owner]  (render [_]    (dom/div {:class"progress-bar"}      (dom/span        {:style {:width (-> (/ value (- max min))                            (*100)                            (int)                            (str"%"))}}))))
;; Valid(om/root progress-bar {:value42}  {:target (. js/document (getElementById"app"))});; Throws error: Key :value not found in (:wrong-data)(om/root progress-bar {:wrong-datatrue}  {:target (. js/document (getElementById"app"))});; Throws error: Value does not match schema(schema.core/with-fn-validation  (om/root progress-bar {:value"42"}    {:target (. js/document (getElementById"app"))})

State Proxy (experimental)

A component usingdefcomponentk can use the key,:state, to accessan atom-like object that conveniently wrapsom.core/get-state andom.core/set-state! so that we can read and write state idiomaticallywithderef,reset! andswap!.

(defcomponentkprogress-bar"A simple progress bar"  [[:data value {min0} {max100}] state]  (render [_]    (dom/div {:class"progress-bar"}      (dom/span        {:style {:width (-> (/ value (- max min))                            (*100)                            (int)                            (str"%"))}:on-mouse-enter #(swap! state assoc:show-value?true):on-mouse-leave #(swap! state assoc:show-value?false))}        (when (:show-value? @state)          (str value"/" total))))))

It's important to note that whilestate looks and behaves likeanatom, there is at least one minor difference: changes made byswap! andreset! are not immediately available if youderefin the same render phase.

defcomponentmethod

With Om,multimethods can be usedinstead of normal functions to create polymorphic components (requiresOm version 0.7.0+).Thedefcomponentmethod macro allows you to register components intoa multimethod (created fromcljs.core/defmulti), while usingthe normal om-tools syntax.

(defmultifruit-basket-item  (fn [fruit owner] (:type fruit)))(defcomponentmethodfruit-basket-item:orange  [orange owner]  (render [_]    (dom/label"Orange")))(defcomponentmethodfruit-basket-item:banana  [banana owner]  (render [_]    (dom/label     {:class (when (:peeled? banana)"peeled")}"Banana")))(defcomponentmethodfruit-basket-item:default  [fruit owner]  (render [_]    (dom/label (str"Unknown fruit:" (name (:type fruit))))))(om/build-all fruit-basket-item              [{:type:banana}               {:type:pineapple}               {:type:orange}])

Mixin tools

React providesmixin functionality to handlecross-cutting concerns and allow highly reusable component behaviors.Whilemixins are possible with Om, it does not providemuch functionality to support this React feature.One issue is that you must create a React constructor and specify iteach time the component is built.This puts the responsibility of using mixins on both the component(create a constructor) and its parent (specify the constructor).Another issue is having to drop down to raw JavaScript functions,breaking you out of Om's data and state abstractions.

om-tools provides adefmixin macro in theom-tools.mixin namespaceto define mixins. The syntax ofdefmixin follows same pattern as thecomponent macros.

One last thing: the factory functions created bydefcomponent/defcomponentk (ie(->component-name data))encapsulate any custom constructor automatically. So a parentcomponent no longer needs to be aware when a child uses mixins!

Here's how you could reimplementReact's mixin example:

(nsexample  (:require    [om-tools.core:refer-macros [defcomponentk]]    [om-tools.dom:as dom:include-macrostrue]    [om-tools.mixin:refer-macros [defmixin]]))(defmixinset-interval-mixin  (will-mount [owner]    (set! (. owner -intervals) #js []))  (will-unmount [owner]    (.. owner -intervals (map js/clearInterval)))  (set-interval [owner f t]    (.. owner -intervals (push (js/setInterval f t)))))(defcomponentktick-tock [owner state]  (:mixins set-interval-mixin)  (init-state [_]    {:seconds0})  (did-mount [_]    (.set-interval owner #(swap! state update-in [:seconds] inc)1000))  (render [_]    (dom/p      (str"React has been running for" (:seconds @state)" seconds."))))

Seeexample for full version.

Community

Please feel free to open anissue on GitHub

For announcements of new releases, you can also follow on@PrismaticEng on Twitter.

We welcome contributions in the form of bug reports and pull requests;please see CONTRIBUTING.md in the repo root for guidelines.

License

Copyright (C) 2014 Prismatic and Contributors. Distributed under the EclipsePublic License, the same as Clojure.

About

Tools for building Om applications

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages


[8]ページ先頭

©2009-2025 Movatter.jp