Fulcro includes a set of CLJC wrappers for a good portion of Bootstrap 3. These should be considered of alpha quality, but they are used in many demos and can be useful in getting started quickly. The primary improvements that are needed would be easy contributions, and if you find them useful you can send a pull request to fix issues or extend them.
ℹ️
|
You must include version 3 of Bootstrap’s CSS (and optionally theme), but not Javascript, in your HTML file. |
The CSS affects many DOM elements, which means you’ll see examples that use DOM functions, which
in turn require a JavaScript object as the first argument (for performance). The helper functions from the
bootstrap
namespace need to modify the incoming arguments, so the first argument (if it takes DOM props) is
a cljs map instead.
; render a regular DOM element:
(dom/div #js { :className "a" })
; render a bootstrap element via one of these functions
(b/button { :className "b"} "Button label")
The javascript object verion with regular DOM elements avoid data conversion overhead at runtime. The bootstrap functions in this library need to augment and manipulate the passed parameters, so they use cljs maps instead.
This documentation covers the passive elements that largely add in proper structure and classnames for bootstrap rendering. The other sections on bootstrap cover active elements.
Fulcro also includes fulcro.ui.html-entities
(which we alias to ent
here). These just give symbol names
to HTML entities (e.g. ×
is ent/times
) so they are easier to use in React.
The examples in this section all render within iframes to prevent the bootstrap CSS from interfering with the book itself. Some of them intentionally go beyond their normal borders to show how they work on larger screens (even though your screen might not be that large).
The four iframes below represent the widths of a large, medium, small, and xsmall screen. The content being rendered is:
(b/container-fluid {}
(b/row {}
(b/col {:xs 12 :md 8} "xs 12 md 8") (b/col {:xs 6 :md 4} "xs 6 md 4"))
(b/row {}
(b/col {:xs 6 :md 4} "xs 6 md 4")
(b/col {:xs 6 :md 4} "xs 6 md 4")
(b/col {:xs 6 :md 4} "xs 6 md 4"))
(b/row {} (b/col {:xs 6 } "xs 6") (b/col {:xs 6 } "xs 6")))
See the Bootstrap documetation for more details.
link:src/book/book/bootstrap/grid.cljs[role=include]
Various elements support modified typography.
link:src/book/book/bootstrap/typography.cljs[role=include]
An inline badge that can be placed on other elements.
link:src/book/book/bootstrap/badges.cljs[role=include]
link:src/book/book/bootstrap/breadcrumbs.cljs[role=include]
link:src/book/book/bootstrap/buttons.cljs[role=include]
link:src/book/book/bootstrap/button_groups.cljs[role=include]
Glyphicon support through keyword names and the b/glyphicon
function.
E.g. (b/glyphicon {} :arrow-left)
.
link:src/book/book/bootstrap/icons.cljs[role=include]
A container that offsets the content. Note, this will not work if it isn’t in the grid:
link:src/book/book/bootstrap/jumbotron.cljs[role=include]
Panels use sub-elements to identify portions of the panel:
(b/panel nil
(b/panel-heading nil "Heading without title")
(b/panel-body nil "This is the body of the panel")
(b/panel-footer nil "The footer"))
(b/panel {:kind :danger}
(b/panel-heading nil
(b/panel-title nil "Panel Title of :danger panel"))
(b/panel-body nil "This is the body of the panel"))))
ℹ️
|
Tables can replace or follow the panel-body . They need not be placed within it.
|
(b/panel nil
(b/panel-heading nil
(b/panel-title nil "Panel with a table and no panel-body"))
(b/table nil
(dom/tbody nil
(dom/tr nil
(dom/th nil "Name")
(dom/th nil "Address")
(dom/th nil "Phone"))
(dom/tr nil
(dom/td nil "Sally")
(dom/td nil "555 N Nowhere")
(dom/td nil "555-1212")))))
link:src/book/book/bootstrap/panels.cljs[role=include]
Popovers are real React components (instead of just wrapper functions), thus the factory has a ui prefix. They do not have state, so they are fed their orientation and current state via props. You can include a single child (tree), and the popover will use that as its boundary for locating itself.
(Only the Toggle All is hooked up in this example)
link:src/book/book/bootstrap/popover.cljs[role=include]
This chapter covers Fulcro implementations of some of the Bootstrap components. These have been re-implemented in CLJC to support server-side rendering and easy inclusion in your Fulcro applications without having to depend on external Javascript.
These components therefore define mutations that manipulate them, and must be composed in with queries and such.
The collapse item is a stateful component that takes children. The children will be hidden/shown based on the state of the collapse wrapper.
The built-in mutation (b/toggle-collapse {:id ID-OF-COLLAPSE})
can be used to toggle a specific item, and
the mutation (b/set-collapse {:id ID :open BOOLEAN})
can be used to set the specific state.
ℹ️
|
Both of these mutations automatically do nothing if an animation is in progress. |
An example with source is below:
link:src/book/book/bootstrap/components/collapse.cljs[role=include]
There is no default coordination among collapse items, but they have a well-known table in app state. Therefore it is easy to provide a way to group them together into an accordian. The behavior is implemented with a single additional mutation:
(b/toggle-collapse-group-item {:item-id ID :all-item-ids IDs)
This mutation toggles the specific item (by ID), and it understands the grouping because you also pass it the IDs of all of the other items in the group. There’s no real need for them to be any more tightly coupled.
For example, say we choose to use a panel-group
to lay out each item. Each item can be a
panel, so we define an extra accordian-section
function (not part of the library) and
create the instances in app state and map over them with our helper.
Note that you can use the mutations defined for single Collapse elements (of course), but that will cause
strangeness in your accordian (e.g. two sections open at once). The toggle-collapse-group-item
does
this correctly by closing all open sections except the one being opened.
link:src/book/book/bootstrap/components/accordian.cljs[role=include]
Active dropdowns are components with state. They must be initialized in app state, and have simple data constructor functions you can use for this purpose:
-
dropdown
: a function that creates a dropdown’s state -
dropdown-item
: a function that creates an items with a label -
ui-dropdown
: renders the dropdown. It requires the dropdown’s properties, and allows optional named arguments-
:onSelect
: The callback for selection which receives an element’s ID. -
:kind
: Identical to thebutton
:kind
attribute.
-
All labels are run through tr-unsafe
, so if a translation for the current locale exists it will be used.
The following mutations are are available:
Close (or open) a specific dropdown by ID:
(prim/transact! component `[(b/set-dropdown-open {:id :dropdown :open? false})]`)
Close dropdowns (globally). Useful for capturing clicks a the root to close dropdowns when the user clicks outside of an open dropdown.
(prim/transact! component `[(b/close-all-dropdowns {})])
You can set highlighting on an item in the menu (to mark it as active) with:
(prim/transact! component `[(b/set-dropdown-item-active {:id :item-id :active? true})])
link:src/book/book/bootstrap/components/dropdowns.cljs[role=include]
Modals are stateful Fulcro components with app state and queries. They can, of course, be mixed with other app state.
The basic usage is to define your modal in the root, with the various things it should render. If you need more than one kind of modal, then you can use a UI router and embed them all in it, then embed the router at the root.
The following demonstrates all of these techniques:
link:src/book/book/bootstrap/components/modals.cljs[role=include]
This section just demonstrates some non-active modals so you can see some of the different layout sizes and options.
ℹ️
|
The iframe for some examples are forced larger than the container because large modals adapt down in size based on available space. |
link:src/book/book/bootstrap/components/modal_variations.cljs[role=include]
These are stateful components that act as tabs. A nav contains one or more nav-link
or dropdown
. It reports selections
through an onSelect
callback (which will send the ID of the item selected).
The typical way to embed a nav is using the nav
constructor in your initial state, adding Nav’s query to the query at
the same key, and rendering it with `ui-nav
.
In the demo below we define this mutation to call from the onSelect callback to display where the nav is currently pointing:
(m/defmutation nav-to [{:keys [page]}]
(action [{:keys [state]}] (swap! state assoc :current-page page)))
Then the main component can display nav and also show the current 'page'.
link:src/book/book/bootstrap/components/nav.cljs[role=include]
The Nav component does nothing more than manage the UI of the tabs. You are still responsible for rendering the content of the application. You will likely wish to use the Fulcro routing system so that your UI performance stays high, and HTML5 routing becomes simpler. To this end, you will likely want to combine Nav with routing.
First, we’ll need some screens to show. We’ll just make some placeholders. They’ll need to know (and query) their screen type:
Then we’ll need to define an ident that follows the [type id]
semantic. Since there isn’t really an ID, we’ll just
use the keyword :singleton
for the ID.
(ident [this props] [(:screen-type props) :singleton])
but this ident function goes with the UI router, which looks like this:
(defrouter MainRouter :main-router
(ident [this props] [(:screen-type props) :singleton])
:home HomeScreen
:other OtherScreen)
Composing the UI requires that you query for the correct router and nav subelements, compose in their initial state, and render them both. Something like this will work:
This is now a component that can display whichever screen is currently active. The fulcro.client.routing
namespace
includes a helper functions called set-route*
that can be composed into a mutation to change the current screen of
a router:
(m/defmutation select-tab
"Select the given tab"
[{:keys [tab]}]
(action [{:keys [state]}]
(swap! state routing/set-route* :main-router [tab :singleton])))
Of course, we could also be using the routing tree mechanisms, which would be similar (though you’d use the
update-routing-links
helper then).
If you’re using something like HTML5 routing, then you’ll not only need to make sure the current screen is showing,
but you’ll need to make sure the Nav
knows which tab to show as active. The bootstrap namespace includes
an mutation for setting the current nav link set-active-nav-link
, and a helper function set-active-nav-link*
that can be composed into mutations.
If you’re careful to make the IDs of the tabs and screens the same, then you’ll be able to ensure lock-step updates with that same mutation:
(m/defmutation select-tab
"Select the given tab"
[{:keys [tab]}]
(action [{:keys [state]}]
(swap! state (fn [s]
(-> s
(routing/set-route* :main-router [tab :singleton])
(b/set-active-nav-link* :main-nav tab))))))
The overall technique works the same with routing trees, but in that case you’ll need to know what nav links need
to be updated for a given page and compose those instructions with the routing/update-routing-links
helper.
link:src/book/book/bootstrap/components/nav_routing.cljs[role=include]