ClojureScript, Om, and Om-Bootstrap: Click Button, Make Inputs Editable - clojurescript

I am creating a page that displays, and then edits, renter info using om-boostrap. (I know Clojure, but am new to CLJS/Om/React/modern web development in general.) The UI and functionality between displaying and editing tenant info is similar -- both can use input fields; editing just needs the fields to be "text" instead of "static" and needs "Submit" and "Cancel" buttons.
The problem I face is that I can't figure out the React/Om way to change a component like that. The view is defined as follows:
(defcomponent view [{:keys [id] :as app} owner]
(render
[_]
(let [tenant (get #tenants id)]
(dom/div
(om/build header/header-view app)
(dom/div {:class "h3"} "View Tenant\t"
(utils/button {:on-click (fn []
(om/update-state! owner
#(assoc app :edit? true))
(om/refresh! owner))}
"Edit"))
(om/build tenant-info {:edit? (:edit? app)
:tenant {:id id
:name "Funny name"
:unit-addr "Funny addr"
:rent "Lots of rent"}})))))
I won't paste the entire tenant-view function here, but it builds Bootstrap inputs for each tenant data field using om-bootstrap:
. . .
(let [input-type (if edit? "text" "static")]
(i/input {:ref "name-display"
:type input-type
:label "Tenant Name"
:label-classname "col-xs-2"
:wrapper-classname "col-xs-4"
:value (str name)})
. . .)
I've tried multiple approaches. I've posted my most recent, using the button's :on-click function to modify the app state, setting the edit? property to true and prompting a re-render to make the inputs editable.
This doesn't work and I am not finding guidance in the React or Om documentation.
What is the right way to render the same component differently? (In my case, the view function's input fields.)
What React or Om documentation is relevant?
UPDATE: I can get the inputs to be editable when I hard-code the edit? flag to true, so making the inputs editable is not the issue: the problem is toggling from static to text (and presumably vice versa).

Yes the problem has to do with understanding the difference between the app state and component state. In this case we wish to affect the app state,I don't understand your scenario quite well but I think it would be better to use component local state for this. You can use init-state or :state. and you can use om/update-state! as you wish. However just adding onto the previous answer it would be easier to just use om/update! to affect the app state in your scenario.
(utils/button {:on-click #(om/update! app [:edit?] true)}
"Edit")
is another option where you can have the vector of keywords go as deep into the map as you like.
This is more terse but might cause a JS console warning because we ignore the event from on-click.

I think the problem is you are using om/update-state! which is for transitioning component local state instead of om/transact! which is used for updating the props passed in to your component. So try changing your button component to this:
(utils/button {:on-click (fn []
(om/transact! owner #(assoc app :edit true)))}
"Edit")

Related

How should I access generated children of a custom HTML component in an idiomatic React way?

I am attempting to create a search bar using a custom HTML component for predictive text input. The way this component is built, it generates several plain HTML children that I need to act on to get full features. Specifically, I need to execute a blur action on one of the generated elements when the user presses escape or enter.
I got it to work using a ref on the custom component and calling getElementsByClassName on the ref, but using getElementsByClassName does not seem like the best solution. It pierces through the virtual and has odd side effects when testing.
This is a snippet of the component being rendered:
<predictive-input id='header-search-bar-input' type='search'
value={this.state.keywords}
ref={(ref: any) => this.predictiveInput = ref}
onKeyDown={(e: React.KeyboardEvent<any>) => this.handleKeyDown(e)}>
</predictive-input>
and the keyDown handler:
private handleKeyDown(e: React.KeyboardEvent<any>) {
// must access the underlying input element of the kat-predictive-input
let input: HTMLElement = this.predictiveInput.getElementsByClassName('header-row-text value')[0] as HTMLElement;
if (e.key === 'Escape') {
// blur the predictive input when the user presses escape
input.blur();
} else if (e.key === 'Enter') {
// commit the search when user presses enter
input.blur();
// handles action of making actual search, using search bar contents
this.commitSearch();
}
}
The element renders two children, one for the bar itself and one for the predictive dropdown. The classes of the underlying in the first are 'header-row-text' and 'value', so the element is correctly selected, but I am worried that this is violating proper React style.
I am using React 16.2, so only callback refs are available. I would rather avoid upgrading, but if a 16.3+ solution is compelling enough, I could consider it.
If you don't have any control over the input then this is the best approach in my opinion.
It's not ideal, but as you're stuck with a 3rd party component you can only choose from the methods that are available to you. In this case, your only real options are to find the element based on its class, or its position in the hierarchy. Both might change if the package is updated, but if I had to choose which would be more stable, I'd go for className.

How to do modal dialogs with Om or Reagent (and Bootstrap)

I wonder how showing and hiding of a modal dialog should be implemented with Om or Reagent. Since my UI is a function on the state, the show/hide should be triggered by a property in this state.
But frameworks like Bootstrap require to call some JavaScript to show/hide dialogs.
Is there any common solution for this problem?
Don't use javascript or jquery effects to show/hide dialogs in Om or Reagent. Instead, make the value of an atom determine whether the modal dialog should be shown. Here is an example for Reagent:
[(fn [shown]
(if shown
[:div.jumbotron.modal-background
{:on-click #(reset! popup-shown false)}
[:div.alert.alert-info
[:div "Hello!"]]]
[:div]))
#popup-shown]
Have a look at re-com. It certainly shows one way of doing it.
https://github.com/Day8/re-com
For Bootstrap specifically, the JavaScript adds "show" to the modal's class and style="display: block;" to the modal. By adding these to the modal we can force it to display all the time, we can then use conditional rendering to hide it:
(defn my-modal []
(let [show #(app-state/query :modal-showing)]
(when show
[:div.modal.show {:tabIndex -1
:style {:display "block"}}
[:div.modal-dialog
[:div.modal-content
"etc"]]])))
Where you get your show boolean from and how it's updated will be application specific, but hopefully this is sufficient to demonstrate the idea.

reagent-forms bind-fields ignores ':field', missing dependencies or incorrect usage?

I am not able to get reagent-forms to bind to an atom. I have data binding working in reagent elsewhere in the same file. And I can set and display the atom in question as expected.
I have
form-doc that returns a [:div] vector with inputs I'd like bound
form-test that creates an atom and calls bind-forms
secretary route defined for /#/test
It seems like the :field key within the form-doc return value is ignored or not parsed by bind-fields.
In the test example below, the date picker is never displayed and the inputs look no different than [:input ] would.
Am I using reagent-forms incorrect? Missing a js dependency?
Browser rendered HTML of localhost.localdomain:3000/#/test
<div data-reactid=".5.0.0">
<input id="foobar" data-reactid=".5.0.0.0">
<input id="test" data-reactid=".5.0.0.1">
<input id="nofieldtest" data-reactid=".5.0.0.2">
<div id="picker" data-reactid=".5.0.0.3"></div>
</div>
in core.cljs
(ns ...
( :require
...
[reagent.core :as reagent :refer [atom]]
[reagent.session :as session]
[secretary.core :as secretary :include-macros true]
[reagent-forms.core :as rf ]
[json-html.core :refer [edn->hiccup]]
))
(defn form-doc []
[:div
[:input {:field :text :id :foobar}]
[:input {:field :text :id :test}]
[:div {:field :datepicker
:id :picker
:date-format "yyyy/mm/dd"
:inline true}]
]
)
(defn form-test []
(let [doc (atom {:test "test"} ) ]
(fn []
[:div.new-visit-form
[rf/bind-fields form-doc doc ]
[:div (edn->hiccup #doc) ]
]))
)
(secretary/defroute "/test" []
(session/put! :current-page #'form-test))
in ring/compojure handler I have
(include-js "//ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js")
(include-js "//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js")
(include-css "//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css")
(include-css "//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css")
[:style (-> "reagent-forms.css" clojure.java.io/resource slurp) ]
as far as I can tell, all the necessary js and css is loaded by the browser
in project.clj's :dependencies
[reagent "0.5.1"]
[reagent-utils "0.1.5"]
[reagent-forms "0.5.13"]
bind-fields appears to want an object not a function.
(def form-doc ... instead of defn
OR
[rf/bind-fields (form-doc) doc ]
The problem is that you have defined form-doc as a function when it should just be a def. This is an easy mistake to make. Have a look at the examples on the reagent-forms github page again and you will see how to do this.
I'm not sure your definition for a datepicker component is quite right either. I've not used the reagent-forms datepicker, but it doesn't look quite right, so perhapse look at the demo examples of that as well.
something you might find useful would be to start by using one of the existing template frameworks. this will allow you to focus on what you want to learn/experiment with rather than getting caught up on all the incidental bits. My recommendation would be to look at luminus. You can setup a basic template with
lein new luminus +cljs
This will take care of setting up a basic ring/compojure backend, a clojurescript, reagent, reagent-forms and secretary scaffolding for the front end and some other useful bits, such as logging and figwheel, which can make the learning process a little easier. Once you have done that, you can run
lein run
to start the web server and your application and then
lein figwheel
to compile your coljurescript and start a figwheel repl. This is really useful as figwheel provides a wonderful envirionment for developing clojurescript. Once you have done that, just go to
http://localhost:3000
to see your app. There is also some good documentation on the luminus site.

How do I implement specific interface methods using closure conversion in jruby?

I would like to take different actions when an swt menu is shown or hidden, so I am adding a MenuListener to a Menu
If I create the listener using a class and add an instance of that class via add_menu_listener I can separately detect showing events and hiding events. For example using the following Listener class:
class MyListener
include MenuListener
def menu_shown e
puts "#{e} was a show event"
end
def menu_hidden e
puts "#{e} was a Hide event"
end
end
and then add the listener to the menu via
my_menu.add_menu_listener MyListener.new
will print different messages when the menu is shown vs hidden.
I can also add a listener using "closure conversion" for example this will produce a message whenever the menu is shown or hidden.
my_menu.add_menu_listener { |e| puts "#{e} was a menu event" }
These two sections of the jruby wiki seem to cover implementing interfaces in jruby.
https://github.com/jruby/jruby/wiki/CallingJavaFromJRuby#implementing-java-interfaces-in-jruby
https://github.com/jruby/jruby/wiki/CallingJavaFromJRuby#closure-conversion
The second section seems to indicate that this "closure conversion" method should work for any interface, but I can't figure out out to get it to separate out the two different methods.
Does anyone know how to use this "closure conversion" scheme to implement each of the specific interface methods separately?
Looking more closely at https://github.com/jruby/jruby/wiki/CallingJavaFromJRuby#closure-conversion
I see this statement:
The block is converted to a Proc object, which is then decorated with
a java interface proxy that invokes the block for any method
called on the interface.
I think this means there is no way to tell what method called the block.
What I decided to (unless someone has a better solution) is this
show = "Show"
hide = "Hide"
my_listener = MenuListener.new
my_listener.define_singleton_method(:menu_shown) { |e| puts "#{e} was a #{show} event" }
my_listener.define_singleton_method(:menu_hidden) { |e| puts "#{e} was a #{hide} event" }
my_menu.add_menu_listener my_listener
Note:
I chose this over
my_listener = MenuListener.new
class << my_listener
def menu_shown e
...
end
def menu_hidden e
...
end
end
my_menu.add_menu_listener my_listener
since I need to reference some free variables as shown above

Using RxUI can you subscribe to the KeyDown event and prevent input based on a criteria?

I am having a go with RxUI within a WinRt project and just seeing if i can get something to work, i suspect that what i am doing is not a valid use case but thought i would ask to be sure.
I have a textbox that is bound to a property, i want to subscribe to the keydown event and prevent the user from inputting inappropriate characters (in this case, anything that is not a number). Due to using MVVM i do not have access to the textbox itself, only the binding values.
Can this still be done? - it seems a little odd to subscribe to property changed and then undoing their input afterwards, if it is undesirable.
This seems to be a UI based concern, so a more loosely-coupled approach would be to write a behavior class (basically an attached property which binds events), and attach it to the textbox in concern.
This way you can actively filter input and not have code-behind specific to that text-box.
Possible representation:
<TextBox Text="{Binding...}">
<Interaction:Interaction.Behaviors>
<NumericTextBoxBehavior />
</Interaction:Interaction.Behaviors>
</TextBox>
Have a look at:
The Behavior pattern
Code for a textbox behavior which filters based on Regex
Just because you're using MVVM, it doesn't mean that you can't put code in the View Code-Behind. In this case, I'd just do the simplest thing:
theTextBox.PreviewKeyUp += (o,e) => {
if (!IsValidKey(e.Key)) e.Handled = true;
};
If you don't care for that, you can also filter it in the ViewModel after the fact, which may cause caret problems but is more testable:
this.WhenAny(x => x.SomeProp, x => x.Value)
.Select(x => new { Filtered = FilterAllInvalidChars(x), Original = x} )
.Where(x => x.Filtered != x.Original)
.Subscribe(x => SomeProp = x.Filtered);