-
-
Save jichon/1bf901b95a9d0562900e816ce4cd95f7 to your computer and use it in GitHub Desktop.
nested tabbed interface
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns app.core | |
(:require | |
app.mutations | |
[untangled.client.core :as uc] | |
[untangled.i18n :refer-macros [tr trf]] | |
[untangled.client.data-fetch :as df] | |
[om.next :as om])) | |
(def initial-state | |
(atom { | |
; These are database tables. We need this structure for the ident to work properly (it looks up something | |
; in a table when followed in the db). Of course, the tabs sub-structure can be completely different | |
; since the UI query can be different for each. That's the point. We *do* need data *inside* these objects | |
; that can be pulled by the ident function of the TabUnion so it can generate the proper ident on the fly. | |
; Thus, the :id and :which-tab fields. | |
:main {:tab {:id :tab | |
:which-tab :main | |
:main-content "Main tab" | |
:inner-tab1 {:tab {:id :tab | |
:which-tab :inner-tab1 | |
:inner-tab1-content "Main1 tab"}} | |
:inner-tab2 {:tab {:id :tab | |
:which-tab :inner-tab2 | |
:inner-tab2-content "Settings1 tab"}} | |
:current-inner-tab [:inner-tab1 :tab] | |
}} | |
:settings {:tab {:id :tab | |
:which-tab :settings | |
:settings-content "Settings tab" | |
}} | |
; The following is the top-level key that can be queried to find which tab to show. | |
; The first element of the ident is the "type" of tab, and will choose the query from | |
; the union in the UI (TabUnion) | |
:current-tab [:main :tab]})) | |
(defonce app (atom (uc/new-untangled-client :initial-state initial-state))) | |
(ns app.ui | |
(:require [om.dom :as dom] | |
[om.next :as om :refer-macros [defui]] | |
[untangled.i18n :refer-macros [tr trf]] | |
yahoo.intl-messageformat-with-locales | |
[untangled.client.data-fetch :as df])) | |
(defui ^:once InnerTab1 | |
static om/IQuery | |
(query [this] [:which-tab :inner-tab1-content {[:tab-data-query '_] [:text]}]) | |
Object | |
(render [this] | |
(let [{:keys [settings-content tab-data-query]} (om/props this)] | |
(dom/div nil | |
settings-content | |
(dom/p nil (:text tab-data-query)))))) | |
(def ui-inner-tab1 (om/factory InnerTab1)) | |
(defui ^:once InnerTab2 | |
static om/IQuery | |
(query [this] [:which-tab :inner-tab2-content]) | |
Object | |
(render [this] | |
(let [{:keys [main-content]} (om/props this)] | |
(dom/div nil main-content)))) | |
(def ui-inner-tab2 (om/factory InnerTab2)) | |
(defui ^:once InnerTabUnion | |
static om/IQuery | |
(query [this] {:inner-tab1 (om/get-query InnerTab1) :inner-tab2 (om/get-query InnerTab2)}) | |
static om/Ident | |
(ident [this props] [(:which-tab props) :tab]) | |
Object | |
(render [this] | |
(let [{:keys [which-tab] :as props} (om/props this)] | |
(dom/div nil | |
(case which-tab | |
:inner-tab1 (ui-inner-tab1 props) | |
:inner-tab2 (ui-inner-tab2 props) | |
(dom/p nil "Missing tab!")))))) | |
; This UI component uses a "link"...a special ident with '_ as the ID. This indicates the item is at the database | |
; root, not inside of the "settings" database object. This is not needed as a matter of course...it is only used | |
; for convenience (since it is trivial to load something into the root of the database) | |
(defui ^:once SettingsTab | |
static om/IQuery | |
(query [this] [:which-tab :settings-content {[:tab-data-query '_] [:text]}]) | |
Object | |
(render [this] | |
(let [{:keys [settings-content tab-data-query]} (om/props this)] | |
(dom/div nil | |
settings-content | |
(dom/p nil (:text tab-data-query)))))) | |
(def ui-settings-tab (om/factory SettingsTab)) | |
(defui ^:once MainTab | |
static om/IQuery | |
(query [this] [:which-tab :main-content {:current-inner-tab (om/get-query InnerTabUnion)}]) | |
Object | |
(render [this] | |
(let [{:keys [main-content]} (om/props this)] | |
(dom/div nil main-content)))) | |
(def ui-main-tab (om/factory MainTab)) | |
; This is the main trick: a component that serves to pick which UI/query to render based on which thing | |
; the ident of :current-tab points to in the database. The :current-tab name means nothing, of course, it | |
; was just a convenient name (see Root, which queried for it and passed the result here). | |
; IMPORTANT: | |
; 1. query must be a union, which is a map keyed by "object type" with the query to use for that object | |
; 2. The ident MUST derive the correct db ident for whatever you got in props | |
; 3. You are responsible for calling the correct UI from render to properly render the thing you got | |
(defui ^:once TabUnion | |
static om/IQuery | |
(query [this] {:main (om/get-query MainTab) :settings (om/get-query SettingsTab)}) | |
static om/Ident | |
(ident [this props] [(:which-tab props) :tab]) | |
Object | |
(render [this] | |
(let [{:keys [which-tab] :as props} (om/props this)] | |
(dom/div nil | |
(case which-tab | |
:main (ui-main-tab props) | |
:settings (ui-settings-tab props) | |
(dom/p nil "Missing tab!")))))) | |
(def ui-tabs (om/factory TabUnion)) | |
(defui ^:once Root | |
static om/IQuery | |
(query [this] [:ui/react-key {:current-tab (om/get-query TabUnion)}]) | |
Object | |
(render [this] | |
(let [{:keys [ui/react-key current-tab] :or {ui/react-key "ROOT"} :as props} (om/props this)] | |
(println "Root" props) | |
(dom/div #js {:key react-key} | |
; The selection of tabs can be rendered in a child, but the transact! must be done from the parent (to | |
; ensure proper re-render of the tab body). See om/computed for passing callbacks. | |
(dom/ul nil | |
(dom/li #js {:onClick #(om/transact! this '[(app/choose-tab {:tab :main})])} "Main") | |
(dom/li #js {:onClick #(om/transact! this '[(app/choose-tab {:tab :settings}) | |
; extra mutation: sample of what you would do to lazy load the tab content | |
(app/lazy-load-tab {:tab :settings})])} "Settings")) | |
(ui-tabs current-tab))))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment