Skip to content

A collection of Re-frame components that most applications need.

Notifications You must be signed in to change notification settings

MattiNieminen/re-fill

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

39 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Re-fill

A collection of Re-frame components that most applications need. Currently, Re-fill offers event handlers, subscriptions, effects and co-effects for:

  • Navigating and dispatching between routes and rendering their corresponding views.
  • Global notifications, which can be hidden after timeout (just bring your own views).
  • Debouncing events (making sure that only one event of certain type is scheduled to be ran at once.)
  • Generating UUIDs to your event handlers.

Usage

Re-fill contains multiple utilities, which all have their own namespaces. It's possible to use only one of the utilities, or any combination of them. As in Re-frame, event handlers, subscriptions etc. will be registered globally after requiring the specific namespace for the wanted utility.

Routing

Routing utility provides means to listen for URL changes and dispatch events based on them, making it possible to write bookmarkable single-page apps with ease.

Currently, Re-fill only supports routes defined using Bidi syntax. For listening the URL in your address bar, Re-fill uses Pushy.

To use Re-fill for routing, first require the correct namespace(s):

(ns example.core
  (:require [reagent.core :as r]
            [re-frame.core :as rf]
            [re-fill.routing :as routing]))

Then, define your routes using Bidi syntax (no need to add dependency for it though). For example:

(def routes ["/" {"" :routes/home
                  ["page/" :id] :routes/page}])

It's recommended to use namespaced keywords, as those keywords will be dispatched by Re-frame.

You also need to create mappings between routes and views:

(def views {:routes/home home-view
            :routes/page page-view
            ;; Value of :else will be used if there's no mapping for route
            :else loading-view})

In order to render the correct view, add routed-view in your root view / template, and give the mappings from previous step to it as argument. Here's an example:

(defn main-view
  [views]
  [:div.main
   [:h1 "This is shown in all views"]
   ;; views refers to the mappings created in the previous step
   [routing/routed-view views]])

Lastly, in order to initialize the routing, dispatch :re-fill/init-routing during the startup of the application:

(defn init!
  []
  ;; routes refer to your Bidi-routes
  (rf/dispatch [:re-fill/init-routing routes])
  ;; views refer to your route - view mappings
  (r/render [main-view views]
            (js/document.getElementById "app")))

(init!)

After doing this, the URL in the address bar will be listened by Re-fill and the corresponding view will be rendered by template. You can either use links normally with hrefs (no preventDefault or manual history manipulation is needed), or dispatch :re-fill/navigate event to navigate programmatically. Here's an example of both:

[:div
 ;; Normal navigation with link
 [:a.controls__a {:href "/page/1"} "Page 1 (by link)"]
 
 ;; Navigation by dispatching an event
 [:button.controls__button
  {:on-click (fn [_]
               (rf/dispatch [:re-fill/navigate
                             ;; The argument to re-fill/navigate is the actual
                             ;; route with path-params.
                             ;; See Bidi documentation for more information
                             [:routes/page :id 2]]))}
  "Page 2 (by dispatch)"]]

The event :re-fill/navigate is great for initiating navigation from views. There's also an effect handler for :re-fill/navigate, which makes it possible to navigate from event handlers without dispatching more events. This is useful for cases where the view dispatches an event, and the event handler may conditionally initiate navigation to some other view in addition to other side effects (through effects of course).

There are also event and effect handlers called :re-fill/navigate-raw, which navigates to a given raw url, and :re-fill/refresh-page, which reloads the current url. See example app for details.

If you need to refer to your current route from your views, you can use :re-fill/routing subscription for that:

(defn page-view
  []
  (let [routing @(rf/subscribe [:re-fill/routing])]
    [:h1 (pr-str routing)]))

After navigation happens, Re-fill dispatches an event using the route key as an identifier for it. It's recommended to register an event handler for each of the routes, otherwise you get a warning to console about a missing event handler. This dispatched event is great for setting up the state for the view that's gonna be rendered. Here's a simple example:

(rf/reg-event-fx
 :routes/home
 ;; The first argument is the co-effects (normal Re-frame stuff)
 ;; The second argument is the event itself. The route match
 ;; from bidi can be destructured from it
 (fn [_ [_ bidi-match]]
   ;; In real apps, this function would return effects map for
   ;; fetching data, setting state or something else.
   (js/console.log "Navigated to " bidi-match)))

See full example for more information.

Notifications

Notifications utility provides a generic notification interface for creating and deleting notifications, and a subscription for using them in your views. Notifications are often used to show information or warning dialogs to end-users.

In order to use notifications, the correct namespaces must be required:

(ns example.core
  (:require [re-frame.core :as rf]
            [re-fill.notifications :as notifications]))

After that, notifications can be created with :re-fill/notify event. For example:

[:button.controls__button
 {:on-click (fn [_] (rf/dispatch [:re-fill/notify
                                   ;; The first argument is the data
                                   ;; you want to use as notification.
                                   ;; It's your responsibility to add types
                                   ;; your notifications if you need them.
                                   {:type :success
                                    :content "Success!"}
                                   ;; The second (optional) argument is used
                                   ;; by re-fill to configure the notification.
                                   ;; Currently, the only supported key is
                                   ;; :hide-after, which can be used to delete
                                   ;; the notification after a given time (in
                                   ;; ms) has passed.
                                   {:hide-after 3000}]))}
 "Notify success!"]

If :hide-after is used, the notification will be removed automatically after the timeout has passed. In order to delete the notification manually, the :re-fill/delete-notification event can be used.

A view must be created for rendering notifications. Re-fill provides a subscription :re-fill/notifications for getting the notifications in view functions.

An example view with the subscription and deletion usage can be found in the full example.

Debounce

Debounce utility can be used for:

  • Scheduling a dispatch of some event to a later time
  • Canceling those scheduled events before they are dispatched
  • Identifying scheduled events with keys to make sure that an event of certain type has been only scheduled once.

For those who are new to debounce there's a good explanation of debounce at css-tricks.com

To use debounce utility, the correct namespaces must be required:

(ns example.core
  (:require [re-frame.core :as rf]
            [re-fill.debounce :as debounce]))

To schedule an event to the future, the event :re-fill/debounce can be used. Here's an example of how to schedule the :re-fill/notify with a one second timeout.

[:button.controls__button
 {:on-click (fn [_] (rf/dispatch [:re-fill/debounce
                                  ;; The :id is used to identify the scheduled
                                  ;; event. If :id is not supplied, the id of
                                  ;; the event will be used.
                                  {:id :test-notify
                                   ;; The actual event that will be dispatched
                                   ;; or debounced.
                                   :event [:re-fill/notify
                                           {:type :success
                                            :content "From debounce!"}
                                           {:hide-after 3000}]
                                   ;; Timeout in ms for scheduling the dispatch
                                   ;; to the future.
                                   :timeout 1000}]))}
      "Notify with debounce"]

Debounce is more than just :dispatch-later built into Re-frame: this scheduled event can be moved further into the future by dispatching :re-fill/debounce again with the same :id that was used before. This means that in the example above the :re-fill/notify event won't be dispatched ever if the user keeps clicking the button repeatedly with the less than one second intervals.

Debounce is great for dispatching events after the user has stopped doing something that results in multiple browser events, such as starting a search after the user has stopped typing.

There's also a subscription :re-fill/debounce for getting all the scheduled events which have not yet been dispatched. Debounce utility also allows canceling the dispatch of the event while the timeout is still active by using the :re-fill/stop-debounce event.

Here's an example of a cancel button which is only clickable if the event has scheduled

(defn cancel-button []
  (let [debounce @(rf/subscribe [:re-fill/debounce])]
    [:button.controls__button
     {:on-click (fn [_] (rf/dispatch [:re-fill/stop-debounce :test-notify]))
      :disabled (not (:test-notify debounce))}
     "Stop debounce"]))

See full example for more information.

UUIDs

Re-fill used to provide a co-effect for injecting UUIDS to event handlers. This feature has been removed in favor of manually calling random-uuid function from ClojureScript core.

Development

All the files for actual library are located under src directory. There's an example app for development and testing under example-src directory.

To get an interactive development environment run:

lein build-dev

and your browser will open automatically at localhost:9500. From there, you get the normal Figwheel development flow.

Releasing

lein deploy clojars

License

Copyright © 2017-2018 Metosin Copyright © 2017-2019 Matti Nieminen

Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.

About

A collection of Re-frame components that most applications need.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published