Search code examples
twitter-bootstrapreactjsscrollspyom

ScrollSpy with Om


I'd like to get Om to work with ScrollSpy.

I'm currently using this code:

(defn main-component
  [data owner]
  (om/component
    (dom/div
      #js {:className "col-sm-10 col-sm-offset-2 col-md-10 col-md-offset-2 main"}
      (dom/div
        #js {:className "main-panel"
             :data-spy "scroll"
             :data-target ".nav-sidebar"}
        "..."))))
(defn sidebar-component
  [data owner]
  (om/component
    (dom/div
      #js {:className "container-fluid"}
      (dom/div
        #js {:className "row"}
        (dom/div
          #js {:className "col-sm-2 col-md-2 sidebar"}
          (dom/div
            #js {:className "nav-sidebar"}
            (dom/ul
              #js {:className "nav"}
              (dom/li nil (dom/a #js {:href "#thing-1"} "Thing 1"))
              (dom/li nil (dom/a #js {:href "#thing-2"} "Thing 2"))
              (dom/li nil (dom/a #js {:href "#thing-3"} "Thing 3"))
              )))))))
(defn app-component
  "The top-level Om component."
  [data owner]
  (om/component
    (om/build
      (common/app-template
        (om/build sidebar-component data)
        (om/build main-component data))
      data)))

Currently, it seems to work up until Om / React.js re-renders the DOM. After that, ScrollSpy stops working. I think I know why. According to Bootstrap ScrollSpy: "When using scrollspy in conjunction with adding or removing of elements from the DOM, you'll need to call the refresh method like so:"

$('[data-spy="scroll"]').each(function () {
  var $spy = $(this).scrollspy('refresh');
});

What should I do? I think I want to hook into Om and tell it to call ScrollSpy as shown above.


Solution

  • I made a few changes to get this to work.

    First, ScrollSpy needs to bind to the scrolling element, e.g. the body, not an element nested inside. So I removed the static attributes from the "main panel" shown above (e.g. main-component). Next, I show how I use scripting to bind to the body element.

    Second, I added handlers for IDidMount and IDidUpdate as follows:

    (defn app-component
      [data owner]
      (reify
        om/IDidMount
        (did-mount
          [this]
          (.log js/console "did-mount")
          (-> (js/$ "body")
              (.scrollspy #js {:target ".nav-sidebar"})))
    
        om/IDidUpdate
        (did-update
          [this prev-props prev-state]
          (.log js/console "did-update")
          (-> (js/$ "body")
              (.scrollspy "refresh")))
    
        om/IRender
        (render
          [this]
          ; ...
          )))