Closed hellonico closed 9 years ago
You should be able to do something like the following:
(def fields (reagent/atom {:props {:foo :prop1 :bar :prop2}}))
(defn form []
[:div
(for [[k v] (@fields :props)]
[:input.form-control
{:field :text
:id v}])])
(defn home-page []
(let [doc (atom {})]
(fn []
[:div
[bind-fields (form) doc]
[:p (str @doc)]])))
The fields
could be populated using an Ajax call, and then you would bind them to the form and use the form with a separate document atom.
In the example above, how do you display the initial values, :foo and :bar, in the input field ? if I do:
(defn form []
[:div
(for [[k v] (@fields :props)]
[:input.form-control
{:field :text
:value k
:id v}])])
Then I cannot edit the value by typing in the input field.
The values have to come from the doc
, so simply populate that with the data.
Thanks for all the help. Along the way, I realize some of the keys had spaces and things were not updating.
The below gives the summary:
(def fields (reagent/atom
{:props (clojure.walk/keywordize-keys {"prop 1" :foo :prop2 :bar})}))
(defn form []
[:div
(for [[k v] (@fields :props)]
[:input.form-control
{:field :text
:id (str k)}])])
(defn edit-page []
(let [doc (atom (@fields :props))]
(fn []
[:div
[bind-fields (form) doc]
[:p (str @doc)]])))
That looks correct to me, sounds like we can close it. :)
I believe I have basically the same problem: I have a form
that is defined through an atom that is filled after loading the document, by clicking the "Reload" button. However the <select>
element stays empty, despite the @applist
clearly containing items.
Could you please give me a hint on what is going wrong here? And maybe add an brief example for a use-case such as this?
(ns mwe.core
(:require [reagent.core :as r]
[reagent-forms.core :refer [bind-fields]]))
(enable-console-print!)
(defn appdata2option
[{id :id name :name}]
[:option {:key (keyword id) :value id} name])
(def applist (r/atom []))
(defn reload-applist []
(reset! applist [{:name "Test #1" :id :test-1}]))
(defn form []
[:div
(println "Reloading with applist:" @applist)
(println "And generated options:" (map appdata2option @applist))
(into
[:select {:field :list
:id :appid}]
(map appdata2option @applist))
[:input {:id :reload
:type :button
:value "Reload"
:on-click #(reload-applist)}]])
(defn reagent-app []
(let [doc (r/atom {})]
(fn []
[:div
[bind-fields (form) doc]
[:label (str @doc)]
[:label (str @applist)]])))
(r/render-component [reagent-app]
(.getElementById js/document "app"))
The associated HTML file looks like this:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" type="text/css" href="css/reagent-forms.css">
<link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css">
<title>MWE</title>
</head>
<body>
<div id="app"></div>
<script type="application/javascript" src="http://code.jquery.com/jquery-2.2.1.min.js"></script>
<script type="application/javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<script type="text/javascript" src="cljs/mwe.js"></script>
</body>
</html>
I'm omitting project.clj
for brevity.
Seems like the problem is that you're binding the form to doc
, while you're reloading the applist
.
However, another problem is that the from
is evaluated once and its output is passed to bind-fields
. So, the list inside the select
can't be modified the way you're trying to do.
Seems like the problem is that you're binding the form to
doc
, while you're reloading theapplist
.
Unless I misunderstood you: That is intentional. I load applist
from a server and need to populate a [:select]
field with [:options]
based on that list.
Loading the applist
might take a while and I want to start to show the form as quickly as possible. Thus I do not want to delay r/render-component
until loading has finished.
However, another problem is that the
from
is evaluated once and its output is passed tobind-fields
. So, the list inside theselect
can't be modified the way you're trying to do.
As I understand Reagent, the anonymous (fn)
in reagent-app
should be called whenever a referenced r/atom
changes. Thus also when applist
changes. Since the second parameter to bind-fields
changed, Reagent should also re-evaluate that component, shouldn't it?
How would I do this correctly? Is there a way that allows me to "rebind-fields"? I.e. re-evaluate (form)
and bind the result to doc
?
The code I wrote IMO looks very similar to what you proposed earlier: https://github.com/reagent-project/reagent-forms/issues/49#issuecomment-88474457 I don't understand why your code works and mine does not. (I never actually tested yours, though.)
The anoymous fn would change, but I don't think anything triggers [bind-fields (form) doc]
to update. The doc would have to change for that.
I did as you suggested and store the applist
inside the doc
now, so that it will update and hopefully trigger rebuilding the [bind-fields]
component:
(ns mwe.core
(:require [reagent.core :as r]
[reagent-forms.core :refer [bind-fields]]))
(enable-console-print!)
(defn appdata2option
[{id :id name :name}]
[:option {:key (keyword id) :value id} name])
(def doc (r/atom {}))
(defn reload-applist []
(swap! doc assoc :applist [{:name "Test #1" :id :test-1}]))
(defn form [doc]
[:div
(println "Reloading with applist:" (:applist @doc))
(println "And generated options:" (map appdata2option (:applist @doc)))
[:select {:field :list
:id :appid}
(map appdata2option (:applist @doc))]
[:input {:id :reload
:type :button
:value "Reload"
:on-click #(reload-applist)}]])
(defn reagent-app []
[:div
[bind-fields (form doc) doc]
[:label (str @doc)]])
(r/render-component [reagent-app]
(.getElementById js/document "app"))
However, the issue stays: (form doc)
is being called, it sees the correct (:applist @doc)
, but the HTML is not updated.
I assume I am severely misunderstanding something?
Ah right, I think the problem is here. Since bind-fields
returns a fn
, that's what gets called on subsequent runs. So, the new template never gets passed in. So I don't think dynamically recreating the form would be possible.
Would it be possible to change Reagent Forms to support this use-case?
Unfortunately, there isn't an easy fix for this. If I think of something, I'll let your know.
a4f316fdfe8967fc3ef1eef5ebad3b5cb7586320 contains the easy fix for this. It simply changes bind-fields
to return form
instead of (fn [] form)
, which, as a side-effect, also causes the form to be examined by reagent-forms every time the atom changes. It was submitted as PR #98.
If you want a more complex fix, which retains the behaviour of Reagent Forms to only examine the form once, but still allow it to change (in a non-structural way, e.g. by adding [:option]
s) later, please tell me, so I can revise this patch.
As I recall returning the form
instead of fn
would also break some of the fields as well. It's definitely problematic from performance perspective I think, so a more comprehensive solution would be needed. If you come up with anything, I'd be happy to merge that in and help with the testing.
As I recall returning the
form
instead offn
would also break some of the fields as well.
Could you please elaborate on that? Could you tell me, which fields I should specifically test?
It's definitely problematic from performance perspective I think, so a more comprehensive solution would be needed.
Could you please point me to some reputable documents on how to optimise code for Reagent, which goes more in-depth on this matter? Maybe you also know a way to properly benchmark this? Before I start with random optimisations, I would like to know which changes would likely yield a reasonable gain and which are just premature.
Please excuse me asking for so much hand-holding, but I am quite new to both ClojureScript and Reagent, as well as web-application programming in general.
It's been a little while, I'll have to take a look at what specifically broke last time I tried this.
I'm not aware of any Reagent specific documentation on optimization. A lot of the standard React documentation applies however. I've also used the Chrome profiler to see what ends up being called and how often.
In general, you would want to ensure that components aren't repainted unless the data they're bound to has changed. So, as a rule of thumb is to minimize recalculation to the specific nodes that need it. Rerunning the whole bind-forms
function each time a piece of data changes in the document would scale very poorly.
So, I have the atom defined as:
and fields are set by an ajax call. Then in a template, I have the following.
I can't figure out how to get the pieces together. How to bind the value of key,value pair from a map in the fields atom ?