    (fn [{:keys [this]}]
      (.removeEventListener js/window "mousemove" (.-onMouseMoveHandler this))
      (.removeEventListener js/window "mouseup" (.-onMouseUpHandler this)))})
+
+
+
+(defn- initial-table-state [props]
+  (let [processed-columns (if-let [defaults (:column-defaults props)]
+                            (let [by-header (utils/index-by :header (:columns props))
+                                  default-showing (->> defaults
+                                                       (replace by-header)
+                                                       (map #(assoc % :show-initial? true)))
+                                  default-hiding (as-> by-header $
+                                                       (apply dissoc $ defaults)
+                                                       (vals $)
+                                                       (map #(assoc % :show-initial? false) $))]
+                              (concat default-showing default-hiding))
+                            (:columns props))
+        column-meta (vec (map (fn [{:keys [header header-key starting-width] :as col}]
+                                {:header (or header-key header)
+                                 :width (or starting-width 100)
+                                 :visible? (get col :show-initial? true)})
+                              processed-columns))
+        initial-sort-column (or (some->> (:columns props)
+                                         (filter #(contains? % :sort-initial))
+                                         first)
+                                (when (:always-sort? props)
+                                  (first (:columns props))))]
+    {:column-meta column-meta
+     :filter-group-index (get props :initial-filter-group-index 0)
+     :query-params (merge
+                    {:current-page 1 :rows-per-page initial-rows-per-page
+                     :filter-text ""}
+                    (when initial-sort-column
+                      {:sort-column (:header initial-sort-column)
+                       ; default needed when forcing sort
+                       :sort-order (or (:sort-initial initial-sort-column) :asc)}))}))
+
+(defn- render-table [{:keys [this state props refs after-update]}]
+  (assert (vector? (:column-meta @state)) "column-meta got un-vec'd")
+  (let [{:keys [filterable? reorderable-columns? toolbar retain-header-on-empty?]} props
+        {:keys [no-data? error]} @state
+        any-width=remaining? (->> (:column-meta @state)
+                                  (map :width)
+                                  (some (partial = :remaining)))]
+    [:div {}
+     (when (or filterable? reorderable-columns? toolbar)
+       (let [built-in
+             [:div {:style {:paddingBottom "1em"}}
+              (when reorderable-columns?
+                [:div {:style {:float "left"}}
+                 [comps/Button {:icon :settings :title-text "Select Columns..."
+                                :ref "col-edit-button"
+                                :onClick #(swap! state assoc :reordering-columns? true)}]
+                 (when (:reordering-columns? @state)
+                   [overlay/Overlay
+                    {:get-anchor-dom-node #(react/find-dom-node (@refs "col-edit-button"))
+                     :dismiss-self #(swap! state assoc :reordering-columns? false)
+                     :content
+                     (react/create-element
+                      table-utils/ColumnEditor
+                      {:columns (react/call :get-ordered-columns this)
+                       :on-reorder
+                       (fn [source-index target-index]
+                         (swap! state update :column-meta utils/move source-index target-index))
+                       :on-visibility-change
+                       (fn [column-index visible?]
+                         (if (= :all column-index)
+                           (swap! state update :column-meta #(vec (map merge % (repeat {:visible? visible?}))))
+                           (swap! state assoc-in [:column-meta column-index :visible?] visible?)))})}])])
+              (when filterable?
+                [:div {:style {:float "left" :marginLeft "1em"}}
+                 [table-utils/TextFilter {:initial-text (get-in @state [:query-params :filter-text])
+                                          :on-filter #(swap! state update-in [:query-params]
+                                                             assoc :filter-text % :current-page 1)}]])
+              (when (:filter-groups props)
+                [:div {:style {:float "left" :marginLeft "1em" :marginTop -3}}
+                 [table-utils/FilterGroupBar
+                  (merge (select-keys props [:filter-groups :data])
+                         {:selected-index (:filter-group-index @state)
+                          :on-change (fn [new-index]
+                                       (swap! state assoc :filter-group-index new-index)
+                                       (after-update #(react/call :refresh-rows this))
+                                       (when-let [f (:on-filter-change props)]
+                                         (f new-index)))})]])
+              (common/clear-both)]]
+         ((or toolbar identity) built-in)))
+     [:div {:style {:position "relative"}}
+      [comps/DelayedBlocker {:ref "blocker" :banner "Loading..."}]
+      ;; When using an auto-width column the table ends up ~1px wider than its parent
+      [:div {:style {:overflowX (if any-width=remaining? "hidden" "auto")}}
+       [:div {:style {:position "relative"
+                      :paddingBottom 10
+                      :minWidth (when-not (or no-data? any-width=remaining?)
+                                  (->> (react/call :get-ordered-columns this)
+                                       (filter :visible?)
+                                       (map :width)
+                                       (apply +)))
+                      :cursor (when (:dragging? @state) "col-resize")}}
+        (when (or (not no-data?) retain-header-on-empty?)
+          (table-utils/render-header state props this))
+        (when error
+          [:div {:style {:padding "0.5em"}}
+           (style/create-server-error-message error)])
+        (when (and (not error) no-data?)
+          (style/create-message-well (or (:empty-message props) "There are no rows to display.")))
+        [table-utils/Body
+         (assoc props
+           :columns (filter :visible? (react/call :get-ordered-columns this))
+           :rows (:display-rows @state))]]]]
+     (when-not (= (:pagination props) :none)
+       [:div {:style {:paddingTop (:paginator-space props)}}
+        [table-utils/Paginator
+         {:width (:width props)
+          :pagination-params (select-keys (:query-params @state) [:current-page :rows-per-page])
+          :num-visible-rows (:filtered-count @state)
+          :num-total-rows (or (:num-total-rows props) (:grouped-count @state))
+          :on-change #(swap! state update-in [:query-params] merge %)
+          :initial-rows-per-page (get-in @state [:query-params :rows-per-page])}]])]))
+
+(defn- refresh-table-rows [{:keys [props state refs]}]
+  (react/call :show (@refs "blocker"))
+  (let [{:keys [pagination data ->row]} props]
+    (if (fn? pagination)
+      (pagination (merge (select-keys @state [:filter-group-index]) (:query-params @state))
+                  (fn [{:keys [group-count filtered-count rows error]}]
+                    (react/call :hide (@refs "blocker"))
+                    (swap! state assoc
+                           :grouped-count group-count
+                           :filtered-count filtered-count
+                           :display-rows (map ->row rows)
+                           :no-data? (empty? rows)
+                           :error error)))
+      (let [{:keys [current-page rows-per-page sort-column sort-order filter-text]} (:query-params @state)
+            grouped-data (if-not (:filter-groups props)
+                           data
+                           (filter (:pred (get-in props [:filter-groups (:filter-group-index @state)]))
+                                   data))
+            filtered-data (if-let [txt (not-empty filter-text)]
+                            (table-utils/filter-data grouped-data ->row (:columns props) txt)
+                            grouped-data)
+            rows (map ->row filtered-data)
+            sorted-rows (if sort-column
+                          (let [column (get (:given-columns-by-header @state) sort-column)
+                                column-index (utils/first-matching-index (fn [{:keys [header]}] (= header sort-column)) (:column-meta @state))
+                                key-fn (or (:sort-by column) identity)
+                                key-fn (if (= key-fn :text) (:as-text column) key-fn)]
+                            (sort-by (fn [row] (key-fn (nth row column-index))) rows))
+                          rows)
+            ordered-rows (if (= :desc sort-order) (reverse sorted-rows) sorted-rows)
+            ;; realize this sequence so errors can be caught early:
+            clipped-rows (if (= pagination :none)
+                           ordered-rows
+                           (doall (take rows-per-page (drop (* (dec current-page) rows-per-page) ordered-rows))))]
+        (react/call :hide (@refs "blocker"))
+        (swap! state assoc
+               :grouped-count (count grouped-data)
+               :filtered-count (count filtered-data)
+               :display-rows clipped-rows
+               :no-data? (empty? clipped-rows))))))

   :starting-width (or (:starting-width props) 200)
   :as-text #(common/format-date % (:format props))})


;; Table component with specifiable style and column behaviors.
;;

              (persistence/try-restore
               {:key (:state-key props)
                :validator (fn [stored-value] (= (set (keys stored-value)) persistence-keys))
                :initial #(initial-table-state props)}))))
   :render render-table
   :get-ordered-columns
   (fn [{:keys [state]}]
     (->> (:column-meta @state)
          (map (fn [{:keys [header] :as column-meta}]
                 (merge column-meta (get (:given-columns-by-header @state) header))))
          vec))
   :refresh-rows refresh-table-rows
   :component-did-mount
   (fn [{:keys [this state locals]}]
     (swap! locals assoc :initial-state (select-keys @state persistence-keys))
