My program allows users to select files to import into a database. Before now, it only allowed them to import one file at a time. Letting them import more than one file at a time is easy. But the problem I have is that if the database already contains a page with the same title, I want to display a warning and get confirmation before overwriting the version in the database with the one being imported.
Here's what I have so far. It largely follows what I was doing to import single files.
;; Handler for the POST "/import" route.
(defn- post-import-page
"Import the file(s) specified in the upload dialog. Checks the page
title of the import file against existing pages in the database. If
a page of the same name already exists, asks for confirmation before
importing."
[{{file-info "file-info"
referer "referer"} :multipart-params :as req}]
(let [file-vec (if (map? file-info)
(conj [] file-info)
file-info)]
(doseq [fm file-vec]
(let [file-name (:filename fm)
import-map (files/load-markdown-from-file (:tempfile fm))
page-title (get-in import-map [:meta :title])
id-exists? (db/title->page-id page-title)]
(if id-exists?
(build-response
(layout/compose-import-existing-page-warning
import-map file-name referer) req)
(do-the-import import-map file-name req))))))
This function imports any files that don't already exist in the database, but doesn't import anything that would overwrite an existing database entry with the same title. It never shows the warning page asking for confirmation either.
The warning page is constructed like this:
(defn compose-import-existing-page-warning
"Return a page stating that a page with the same title already exists
in the wiki. Ask the user to proceed or cancel the import."
[import-map file-name referer]
(short-form-template
[:div {:class "cwiki-form"}
(form-to {:enctype "multipart/form-data"
:autocomplete "off"}
[:post "proceed-with-import"]
(hidden-field "import-map" import-map)
(hidden-field "file-name" file-name)
(hidden-field "referer" referer)
[:p {:class "form-title"} "Page Already Exists"]
[:div {:class "form-group"}
[:p (str "A page with the title \"" (get-in import-map [:meta :title])
"\" already exists in the wiki.")]
[:p (str "Click \"Proceed\" to delete the existing page and "
"replace it with the contents of the imported file.")]
[:div {:class "button-bar-container"}
(submit-button {:id "proceed-with-import-button"
:class "form-button button-bar-item"}
"Proceed")
[:input {:type "button" :name "cancel-button"
:value "Cancel"
:class "form-button button-bar-item"
:autofocus "autofocus"
:onclick "window.history.back();"}]]])]))
How would the program be paused in the middle of the doseq (or other looping function) to display the confirmation page and wait for the user to make a selection?
Just use read-line in the middle of your loop, and then an if to choose the branching you want. Here is a list of other documentation you may find useful, especially the Clojure CheatSheet.
There is an attribute :organisation/ord. This is how I'm getting the data structure to pass to d/transact:
(assoc (d/pull db [:db/id] (:db/id organisation)) :organisation/ord new-org-ord)
;; => {:db/id 17592186045432, :organisation/ord 4198}
Here organisation is of type datomic.query.EntityMap and new-org-ord is an integer. This works fine but seems unwieldy. Is there simpler code that does the same job?
Thinking all I need do was turn EntityMap into a real map I tried this:
(assoc (into {} organisation) :organisation/last-invoice-ordinal new-org-ord)
But got:
:db.error/not-an-entity Unable to resolve entity: #:db{:id 17592186045433} in datom [-9223301668109597772 :organisation/timespan #:db{:id 17592186045433}]
This is simpler:
{:db/id (:db/id organisation), :organisation/ord new-org-ord}
And here's another alternative that also works:
(assoc (select-keys organisation [:db/id]) :organisation/ord new-org-ord)
It doesn't really make sense to be transacting with a map that has anything in it apart from a map-entry to identify the entity id you want to assert some new facts against, together with map-entries that represent those facts.
I would like to use Google Closure UI components from ClojureScript with the Rum/React library.
I started with goog.ui.DatePicker, but still cannot wrap it correctly. The code underneath renders the DatePicker once on component mount and at the right place, the event listener works and all is fine, except it needs the statically set id ("here") at the dom node to work which would be acceptable for one time hack, but not when I need to have the component wrapped and use several times on the same page/app.
(ns redux.components
(:require [rum.core :as r]
[cljs-time.core :as time]
[cljs-time.format :as tf]
[goog.dom :as dom]
[goog.ui.DatePicker :as goog-picker]
[goog.events :as goog-events]]))
(r/defcs +published-at < { :did-mount (fn [state]
(let [target-node (:r/ref state "here")
dp (goog.ui.DatePicker. nil goog.i18n.DateTimeSymbols_cs)]
(.listen dp (.. goog.ui.DatePicker -Events -CHANGE) #(println "new date is: " (tf/unparse (tf/formatter "YYYY-MM-dd")(time/to-default-time-zone (.. % -target getDate)))))
(.render dp (goog.dom/getElement "here")))
state) }
[]
[:div#here])
(r/defc app
[]
[:div
[:h1 "title"
(+published-at)]])
My further unsuccessful research
React documentation suggests that for integration with third-party DOM libraries one might need refs. Rum documentation describes how to do react refs from Rum. But the trouble is that React doc states that string refs are legacy and might be removed in future releases while Rum documentation does not cover callback based refs. I tried to guess how to combine both frameworks with string refs as well as callback refs, but neither one seems to work:
String based legacy approach
(r/defcs +published-at < { :did-mount (fn [state]
(let [target-node (:r/ref state "here")
dp (goog.ui.DatePicker. nil goog.i18n.DateTimeSymbols_cs)]
(.listen dp (.. goog.ui.DatePicker -Events -CHANGE) #(println (tf/unparse (tf/formatter "YYYY-MM-dd")(time/to-default-time-zone (.. % -target getDate)))))
(.render dp target-node))
state) }
[]
[:div
[:div { :ref "here" } ]])
This fails with error and the DatePicker is not even displayed:
Uncaught TypeError: opt_parentElement.insertBefore is not a function
at goog.ui.DatePicker.goog.ui.Component.render_ (component.js:705)
at goog.ui.DatePicker.goog.ui.Component.render (component.js:659)
at Function.<anonymous> (components.cljs?rel=1493075625598:123)
at Function.cljs.core.apply.cljs$core$IFn$_invoke$arity$3 (core.cljs:3694)
at cljs$core$apply (core.cljs:3676)
at util.cljc?rel=1492772300984:17
at core.cljs:2314
at Function.cljs.core.seq_reduce.cljs$core$IFn$_invoke$arity$3 (core.cljs:2314)
at cljs.core.LazySeq.cljs$core$IReduce$_reduce$arity$3 (core.cljs:3287)
at Function.cljs.core.reduce.cljs$core$IFn$_invoke$arity$3 (core.cljs:2358)
Callback base approach
(r/defcs +published-at < { :did-mount (fn [state]
(let [dp (goog.ui.DatePicker. nil goog.i18n.DateTimeSymbols_cs)]
(.listen dp (.. goog.ui.DatePicker -Events -CHANGE) #(println (tf/unparse (tf/formatter "YYYY-MM-dd")(time/to-default-time-zone (.. % -target getDate)))))
(.render dp (::put-date-here state)))
state) }
[state]
[:div
[:div { :ref #(assoc state ::put-date-here %) }]])
This renders functional DatePicker, but out of the component, at the end of the page.
You don't need :did-mount here, just render it right in the callback. This works fine for me:
(defn show-datepicker
[d]
(let [dp (goog.ui.DatePicker. nil goog.i18n.DateTimeSymbols_cs)]
(.listen dp (.. goog.ui.DatePicker -Events -CHANGE)
#(js/console.info
(.. % -target getDate)))
(.render dp d)))
[:div {:ref show-datepicker}]
If you need to tear down the datepicker then just check for (nil? d) in your show-datepicker. React will pass nil when the component unmounts.
PS: I wound't use cljs-time unless you really like big js output files.
I am trying to use a React component from React Bootstrap in my rum app.
In my macros namespace I have some code that I found on the rum gitter :
(defn ->kebab [s]
(str/join "-" (map str/lower-case (re-seq #"\w[a-z]+" s))))
(defn gen-wrapper [component]
`(defmacro ~(symbol (->kebab (str component))) [& args#]
(let [[opts# & [children#]] (if (-> args# first map?)
[(first args#) (rest args#)]
[nil args#])]
`(js/React.createElement
~(symbol "js" (str "window.ReactBootstrap." (name '~component)))
(cljs.core/clj->js ~opts#)
~#children#))))
(def components '[Button
])
(defmacro gen-wrappers []
`(do
~#(clojure.core/map gen-wrapper components)))
Then in my devcard namespace I have:
(pm/gen-wrappers)
(rum/defc foo []
[:div (button nil "bggg")])
(defcard foo "" (foo))
The error is:
react.inc.js:18342 Uncaught Error: Invariant Violation: Objects are
not valid as a React child (found: js/React.createElement). If you
meant to render a collection of children, use an array instead or wrap
the object using createFragment(object) from the React add-ons. Check
the render method of foo.
This turned out to be an issue with clojurescript macros. Note that the macro gen-wrappers itself expands to a series of macros -- in this case the button macro. But in clojurescript, you can't call a macro that was defined in the same compilation stage, which I unfortunately tried to do by calling button right away.
The solution was to move the gen-wrappers call into the macros file along with the other macros:
(defmacro gen-wrappers []
`(do
~#(clojure.core/map gen-wrapper components)))
(gen-wrappers)
And then call the fully qualified pm/button from my devcard namespace.
(rum/defc foo []
[:div (pm/button nil "bggg")])
"But wait!" you say. "Aren't you making the same mistake here of calling one macro (gen-wrappers) from the same compilation stage it was defined in? Actually no, because in clojurescript, macros are defined in clojure files, in which that restriction does not apply.
Java 8.0 x64, Win7 x64, Clojure, Emacs.
I'm doing some stuff in Clojure with TableView wherein I'm proxying TableCell so I can render and edit arbitrary things in it. The values are the fields of a map which is inside an atom. Code is below. It makes use of plenty of utility functions and macros to make this simpler, but you get the gist. The main thing is the management of the cell's graphic and text properties.
There is a keyboard handler which is attached to the ComboBox so it knows when the user presses ENTER, etc. This handler is removed on defocus from the cell, so we don't end up with multiple handlers in the object.
In this example I have three columns, one for the name of a field (A simple cell factory which only shows text and is not editable), one for the value (fancy cell factory), and one for the type (simple cell factory). The output, using some sample data, looks like this:
When I sort the table based on Value things seem to work fine, as follows:
Normally, when the keyboard handler triggers, it calls the cell's commitEdit function, which calls its TableCell superclass commitEdit. The TableView magic behind the scenes then calls the column's onEditCommit handler, which actually commits the edit to the database. After the superclass commitEdit returns, there is nothing left to do in the cell's commitEdit. The cell's updateItem is then automatically called by TableView which replaces the ComboBox with the normal contents of the cell.
PROBLEM
When I sort the table based on the Field column one or more times, or the Type column two or more times and try to edit something with a ComboBox (in this case the color selector), it takes an extra click to get the ComboBox to drop down, and the ENTER key doesn't work, specifically as follows:
CAUSE
In the broken case, the TableCell's superclass appears to return immediately and does not call column's onCommitEdit handler, nor does the cell's updateItem get called, so the cell is not rendered back to its normal non-editing state, ie, without the ComboBox.
Normal and broken cases look like this:
The debug text output in the normal case and broken case are shown here.
The weird thing is this problem sometimes appears with the non-color ComboBox as well (the sides field is has a ComboBox editor with numbers, for example).
So is this a bug in JavaFX TableView? Or am I doing something wrong?
(defn add-handlers!
"Adds common keyboard handler and focus listener to temporary editing graphic.
graphic is typically textfield or combo-box. cell is tablecell which
is being edited. getterfn is function to get value from graphic so
it can be commited to database."
[graphic cell getterfn]
(let [focus-listener (make-focus-change-listener cell getterfn)]
(println "adding focus and keyboard listener")
(add-listener! graphic :focused focus-listener)
(.setOnKeyPressed graphic (eventhandler [e] ;; here "cell" still refers to the tablecell
(condp = (.getCode e)
KeyCode/ENTER (do (println "ENTER pressed. Removing focus listener")
(remove-listener! graphic :focused focus-listener) ;; Prevent double-commit on defocus
(.commitEdit cell (getterfn)))
KeyCode/ESCAPE (do (println "ESC pressed. Removing focus listener")
(remove-listener! graphic :focused focus-listener) ;; Prevent double-commit on defocus
(.cancelEdit cell)) ;; Removes textfield
KeyCode/TAB (let [index (.. cell getTableRow getIndex)
next-column (get-next-column cell (not (.isShiftDown e)))]
(println "TAB pressed. Removing focus listener")
(remove-listener! graphic :focused focus-listener) ;; Prevent double-commit on defocus
(.commitEdit cell (getterfn))
(.edit (.getTableView cell) index next-column))
nil))))) ;; do nothing
(defn make-combobox
"Implements dropdown combobox. 'cell' is fancy table cell in
question. 'items' is list of things for dropdown, which can be
anything that the dropdown can render and choose as the final item"
[cell initvalue & [items]]
(let [cmb (jfxnode ComboBox (observable items))
cell-factory FANCY-LISTCELL-FACTORY
blank-cell (.call cell-factory nil)]
(doto cmb
(add-handlers! cell #(.getValue cmb))
(.setValue initvalue)
(.setButtonCell blank-cell)
(.setCellFactory cell-factory))))
(defn render-cell-with-item!
"Puts correct item in cell graphic and/or text property based on item
type. Additional arguments for editing such as drop-down, are
handled in the startEdit function; this function just renders the
cell when called by updateItem or cancelEdit."
[cell item]
(cond
(instance? Node item) (set-graphic-text! cell item nil) ;; for a graphic/Node item
(instance? Boolean item) (let [[var full-accesspath] (calc-full-accesspath cell)
cb (jfxnode CheckBox
:text (str item)
:selected item
:disable (not (mutable? var)))]
(.setEditable cell false)
(set-graphic-text! cell cb nil)
(when (mutable? var)
(uni-bind! (.selectedProperty cb) var full-accesspath)))
(instance? clojure.lang.PersistentVector item) (set-graphic-text! cell (Label. "Put vector editor here") nil)
(instance? Color item) (set-graphic-text! cell (make-color-box item) (color-map-inverse item))
;; All other types go here, presumably text types, so assume editable
:else (set-graphic-text! cell nil (si/to-normstr item)))) ;; else set underlying text
(def FANCY-TABLECELL-FACTORY
"The main callback interface which constructs the actual each cell
for arbitrary types. Assumes an editable cell for text representations."
(callback [column]
(proxy [TableCell] []
(updateItem [item empty]
(proxy-super updateItem item empty)
(when (not empty)
(render-cell-with-item! this item)))
(startEdit []
(proxy-super startEdit)
;; Change to appropriate graphic when editing
(println "in proxy's startEdit. Column commitHandler is" (.getOnEditCommit column))
(let [item (apply access-db (calc-full-accesspath this))
options (get-field-options this)] ;; could be nil ...
(if-let [combo-items (:combo-items options)] ;; ... so put as argument to :combo-items
(let [cmb (make-combobox this item combo-items)]
(set-graphic-text! this cmb nil)
(.requestFocus cmb)
(.show cmb)) ;; This makes drop-down appear without clicking twice.
(when (textish? item)
(let [tf (make-textfield-editor this)]
(set-graphic-text! this tf nil) ;; just set tf as graphic; leave existing text alone
(.requestFocus tf)
(.selectAll tf))))))
(cancelEdit []
;; CancelEdit gets called either by defocus or by ESC.
;; In any case, use the item currently in the database
;; for this cell and just render as in updateItem
(proxy-super cancelEdit)
(let [item (apply access-db (calc-full-accesspath this))]
(render-cell-with-item! this item)))
(commitEdit [value]
;; Nothing to do here. All commits happen either in the textField callback or in the column edit callback
(println "in cell's commitEdit, before super")
(proxy-super commitEdit value)
(println "in cell's commitEdit, after super")))))
(defn inner-table-view*
"Make inner table view for use by inspector-view and table-view"
[var accesspath columns]
(let [obslist (observable (var-snapshot var accesspath))]
(jfxnode TableView
:user-data {:var var ;; the actual var...
:accesspath accesspath } ;; ... and how to get to the displayed data
:items obslist
:columns columns
:editable (mutable? var))))
(defn inspector-view
"Takes plain map or atom/var/ref/agent of map and displays fields
and values in JFX TableView. Compound values (ie maps, vectors,
etc., for now are just displayed as their string value. If access
is supplied, assumes m is var/ref/atom and assigns appropriate
linkage between m and view contents. The topmost available var or
map is assigned to the TableView, and the accessor for each field is
assigned to each column."
[var & {:keys [accesspath field-options]}]
(let [ismutable (mutable? var)
field-col (jfxnode TableColumn "Field"
:cell-value-factory CELL-VALUE-FACTORY
:cell-factory SIMPLE-TABLECELL-FACTORY
:user-data {:accessfn key } ;; label-only option not relevant yet
:editable false
:sortable true)
value-col (jfxnode TableColumn "Value"
:cell-value-factory CELL-VALUE-FACTORY
:cell-factory FANCY-TABLECELL-FACTORY
:user-data {:accessfn val} ;; val is fn for accessing cell values from data item
:on-edit-start (eventhandler [e] (println "editing column " (.getOldValue e) (.getNewValue e)))
:on-edit-cancel (eventhandler [e] (println "canceling column with event" e))
:on-edit-commit (eventhandler [e] (do (println "column's on-edit-commit handler calling column-commit") (column-commit e)))
:editable ismutable
:comparator columnComparator)
type-col (jfxnode TableColumn "Type"
:cell-value-factory CELL-VALUE-FACTORY
:cell-factory SIMPLE-TABLECELL-FACTORY
:user-data {:accessfn #(type (val %))}
:editable false
:sortable true)
cols [field-col value-col type-col]
tv (inner-table-view* var accesspath cols)]
;; Add options to table's userData. This is for inspector-view
;; not table-view, so we don't put this in inner-table-view
;; function
(let [userdata (.getUserData tv)
newuserdata (conj userdata {:field-options field-options})]
(.setUserData tv newuserdata))
;; Add watches, use tv instance as key so we can remove it later
;; This gets called each time db is changed.
(if (mutable? var)
(add-watch var tv (fn [k r o n] ;; Key Ref Old New
(println "Inside KRON with new var" n)
;; Capture the existing sort order and type
;; Taken from http://stackoverflow.com/questions/11096353/javafx-re-sorting-a-column-in-a-tableview
(let [sort-order (vec (.getSortOrder tv)) ;; need to remember ObservableList<TableColumn> and vectorize or it gets reset from underneath us
sort-types (map #(.getSortType %) sort-order)
sortables (map #(.isSortable %) sort-order)]
;; Here we actually put the items into the tableview after the change
(.setItems tv (observable (var-snapshot var accesspath)))
;; Sort order is now empty up so we put back what was in it
(let [new-sort-order (.getSortOrder tv)] ;; get ObservableList<TableColumn>
(.setAll new-sort-order (into-array sort-order)) ;; reset the sort order based on what was there before
;; Assign sorting to each column
(doseq [col sort-order, sort-type sort-types, sortable sortables]
(.setSortType col sort-type)
(.setSortable col sortable)))))))
tv))
I found the problem, which was, of course, in my code.
Because JFX reuses cells, the editable property of the cell persists even when there are different contents rendered in the cell. In my case I had a boolean member of my databased which I rendered as a checkbox. The checkbox itself was clickable, but the cell in which it was rendered was not editable. When this cell got re-rendered after sort with a different item, the non-editing state persisted and screwed up the editing of the new item, which somehow led to the drop-down box not going away properly. Actually the bug showed up in non-combobox items too, such as for text edits, etc.
So the solution was to explicitly set the editable property of the cell for each item type that is rendered.