Reagent doesn't re-render on first transition of ratom - reactjs

I've been working on a linked text-box and list-component, similar to a combobox or typeahead component. I want to be able type free text into the text-box, unless an item from the list is clicked, in which case overwrite the text-box with the selected item's text.
These two components are contained in a single, parent div, which owns the ratom that maintains the selection state. The ratom is initally nil, but is updated when a list-item is selected. I'd like the text-box's value to be updated only when the ratom's contents are not nil, and premit free entry when the ratom's value is nil.
Unfortunately, this works only for the second distinct click of the list-component, and thereafter. The text-box does not update on the first click, even though console inspection shows that it's value attribute is updated. So, it looks like a question of triggering rendering.
Here's the code (uses dommy)...
(defn text-box [selection]
[:input.jdropbtn
(into
{:type :text
:placeholder "Dropdown"
:on-change #(.log js/console
(let [v (-> % .-target .-value)
opts (-> % .-target .-nextSibling dom/children array-seq)]
;(doseq [t opts] (dom/toggle! t (clojure.string/includes? (dom/text t) v)))
))}
(when #selection
{:value (:value #selection)} ) ; This fails on first selection!
; {:value (:value #selection)} ; This prevents free typing!
)
]
)
(defn list-component [selection data-key items]
[:div.jdropdown-content
(for [i items]
(let [{:keys [id value] :as item} i]
^{:key id} [:a {data-key id :on-click #(reset! selection {:id id :value value}) } value])
)
]
)
;;;;;;;;;;;;;; parent
(defn my-droplist [data-key items]
(let [selection (r/atom nil) ]
(fn []
[:div.jdropdown {:id "jack"}
[text-box selection]
[list-component selection data-key items]
])
))
; invoked as:
[my-droplist :data-appid
[{:id 11 :value "Hey there!"}
{:id 22 :value "Hi there!"}
{:id 33 :value "Ho there!"}]]
Styling is essentially the same as the dropdown menu css example from w3schools (http://www.w3schools.com/css/css_dropdowns.asp).
TIA.

Ok, it's all about control; Reagent doesn't like sharing. If I always set the textbox's value from the ratom, and update the ratom on-change, it works. Also, the ratom is initialized to empty string instead of nil.
Thing is, simply dereferencing the ratom every time (as I was originally doing) did not help. Seems I have to actually write the ratom's value to the textbox's value each time.

Related

Why is removing an item from a list with the key as index working as expected?

I have a component in which you can add or remove an option item. When I remove an option item, it is simply removed from an options list stored in state using the index value.
I would have thought that because I am using an index as the key, whenever I deleted an option item, the last element would incorrectly be removed however it seems to be working as expected.
displayedOptions = options.map((option, optionIndex) => (
<DropdownOption
option={option}
onRemoveOption={() => onRemoveOption(optionIndex)}
onChange={onChangeOption(optionIndex)}
key={optionIndex}
/>
));
const onRemoveOption = (taskIndex: number) => (optionIndex: number) => {
const newTaskFields = [...taskFields];
newTaskFields[taskIndex].options = newTaskFields[taskIndex].options.filter(
(_option, index) => {
return optionIndex !== index;
}
);
setTaskFields(newTaskFields);
};
Are there any risks to doing it this way?
Would anyone know why this is working as expected?
I thought the behaviour in my app would have been similar to what was reported here: React - Deleting from middle of list removes last element instead
That is, if I had an list that used indexes as keys containing the following values:
[a, b, c]
and I removed index 0 ('a'), I thought the diff would have been between:
Original:
[0:a, 1:b, 2:c]
and
Updated:
[0:b, 1:c]
In this case, React would see that keys 0 and 1 still exist and would continue to render a and b as it would assume these haven't changed. This would result in c disappearing (not a).
Thank you.
Your onRemoveOption is partially correct. You should shallowly copy all elements of the array/object you intend to update (mutate/remove/etc...).
Your onRemoveOption handler would then become something like:
const onRemoveOption = (taskIndex: number) => (optionIndex: number) => {
setTaskFields((tasks) =>
tasks.map((task, index) =>
index === taskIndex
? {
...task,
options: task.options.filter((_, index) => index !== optionIndex)
}
: task
)
);
};
Uses a functional state update
Maps the tasks to a new array object
If the task index matches the one you need to delete an option from, shallow copy to new object and update the options property, otherwise return the task object
If deleting an option, filter the options by index
Issue will raise while implementing sorting and Removing based on map index.
for example
let Options = [d,a,e,y];
right now Options and index looks like this way.
d 0
a 1
e 2
y 3
Removing item always apply changes from last item. Because you are not passing unique key to the item. Read diff algorithm.
But if you implement sorting and removing - it will failed in removing specific element
after sorting index will change
a 0
d 1
e 2
y 3
You can send option name instead of index.

Is there a way to use/get the value from a current entry window

I'm trying to get the variable that's entered in an entry widget on the Return key pressed event, but struggling a bit. What I have tried has always produced a blank result.
This code may look messy and hap-hazard, but it's only going to be a template that I'll be using on a current project!
I've tried that many things to get it to work, I can't remember what I have tried!
from collections import OrderedDict
try:
import tkinter as tk
except:
import Tkinter as tk
root = tk.Tk()
labelLIST = OrderedDict([
('Temp ID', 'tempID'),
('PO Number', "poNumber"),
('Reference', "reference"),
('Cut/Sample Date', "csDate"),
('Cut Number', "cut")
])
i = 0
e_loops = len(labelLIST)
print (e_loops)
def bval1(event=None):
for i in range(e_loops):
print (entries[i].get())
entries[0].delete(0, tk.END)
entries[0].insert(0, 'DISABLED')
entries[0].configure(state='disabled')
def bval2():
entries[0].configure(state='normal')
for i in range(e_loops):
entries[i].delete(0, tk.END)
entries[0].focus()
def onClick(event):
ent = event.widget # event.widget is the widget that called the event
print(ent.cget("text")) # Print the text for the selected button
event.widget.tk_focusNext().focus()
def enterEV(event):
# print(entries[].get())
event.widget.tk_focusNext().focus()
entries = []
for key, value in labelLIST.items():
label = tk.Label(root, text=key)
label.grid(row=i, column=0, sticky="ew", padx=1, pady=1)
entry = tk.Entry(root, width=10)
entry.grid(row=i, column=1, sticky="ew", padx=5, pady=5)
if value == "cut":
entry.bind('<Return>', bval1)
else:
# entry.bind('<Return>', enterEV)
entry.bind('<Return>', onClick)
entries.append(entry)
i = i+1
button = tk.Button(root, text="Submit", command=bval1)
button.grid(row=0, column=2, columnspan=9, sticky="ew")
button = tk.Button(root, text="Clear", command=bval2)
button.grid(row=1, column=2, columnspan=9, sticky="ew")
entries[0].focus()
tk.mainloop()
When enter/return is pressed, I want the value that is the entry box to be printed to terminal via the onClick event. But the output is always empty.
def onClick(event):
ent = event.widget # event.widget is the widget that called the event
print(ent.cget("text")) # Print the text for the selected button
event.widget.tk_focusNext().focus()
You don't use the text attribute to get the value in an Entry widget. Using cget("text") returns the value for the textvariable attribute. Since you haven't set that attribute, it will always be the empty string.
Instead, you need to call the get method:
print(ent.get())

SSJS remove item from an array

In ssjs I have an array that contains jsonobjects. this array is stored in a scope variable and displayed in a repeat control:
<xp:repeat id="rptAssessors" value="#{sessionScope.tmpAssessors}" var="obj" indexVar="idx">
From the repeat control I would like to add the option to remove individual items from the array e.g. via onclick event on a button for each row:
<xp:eventHandler event="onclick" submit="true" refreshMode="partial" immediate="true" refreshId="dlgContent"> <xp:this.action> <xp:executeScript
script="#{javascript:removeAssessor(idx);}"> </xp:executeScript> </xp:this.action></xp:eventHandler>
My removeAssessor function looks as followed:
function removeAssessor(idx){
var jsonObjArr = sessionScope.get("tmpAssessors");
print("before elements " + jsonObjArr.length)
print(idx + 1)
jsonObjArr.splice(idx,1);
print("after elements " + jsonObjArr.length)
sessionScope.put("tmpAssessors",jsonObjArr);
}
I notice the splice() method does not work. I read on an xsnippet that this method does not work in SSJS and an alternative is posted as an xsnippet but this is for arrays containing strings
https://openntf.org/XSnippets.nsf/snippet.xsp?id=remove-an-entry-from-an-array-of-strings
Anyone can tell me how to remove an item from the array that is not a string?
Array.splice does work in SSJS, but not in the "inline way" you might be used to from CSJS: In CSJS the modification is done inline (no new array is created) and the removed elements are returned. In SSJS, the original array is not modified and the return value is a copy of the array excluding the spliced elements.
If you replace jsonObjArr.splice(idx,1); with jsonObjArr=jsonObjArr.splice(idx,1); your code should work fine.

Error: No queries exists for component path?

I'm trying out om.next, trying to extend the example given in Components, Identity & Normalization.
The code in the example linked above contains of two lists that are backed by data in two different paths in the state. The example shows how the normalization of the data makes it possible to easily update normalized entities occurring in different views. In the code below I have removed the second list in the example below, since the behaviour shows up anyway.
Working example
The example below works and shows:
List A
John, points: 4 +
Mary, points: 0 +
Bob, points: 0 +
where the + is a button that increases the point for the person.
(ns ui.core
(:require [om.next :as om :refer-macros [defui]]
[om.dom :as dom]
[goog.dom :as gdom]))
(def init-data
{:list/one [{:name "John" :points 0}
{:name "Mary" :points 0}
{:name "Bob" :points 0}]})
;; -----------------------------------------------------------------------------
;; Parsing
(defmulti read om/dispatch)
(defn get-people [state key]
(let [st #state]
(into [] (map #(get-in st %)) (get st key))))
(defmethod read :list/one
[{:keys [state] :as env} key params]
{:value (get-people state key)})
(defmulti mutate om/dispatch)
(defmethod mutate 'points/increment
[{:keys [state]} _ {:keys [name]}]
{:action
(fn []
(swap! state update-in
[:person/by-name name :points]
inc))})
;; -----------------------------------------------------------------------------
;; Components
(defui Person
static om/Ident
(ident [this {:keys [name]}] [:person/by-name name])
static om/IQuery
(query [this] '[:name :points :age])
Object
(render [this]
(let [{:keys [points name] :as props} (om/props this)]
(dom/li nil
(dom/label nil (str name ", points: " points))
(dom/button
#js {:onClick
(fn [e]
(om/transact! this
`[(points/increment ~props)]))}
"+")))))
(def person (om/factory Person {:keyfn :name}))
(defui ListView
Object
(render [this]
(let [list (om/props this)]
(apply dom/ul nil
(map person list)))))
(def list-view (om/factory ListView {:key-fn ffirst}))
(defui RootView
static om/IQuery
(query [this]
(let [subquery (om/get-query Person)]
`[{:list/one ~subquery}]))
Object
(render [this]
(let [{:keys [list/one]} (om/props this)]
(apply dom/div nil
[
(dom/h2 nil "List A")
(list-view one)
]))))
(def rootview (om/factory RootView))
;; wrapping the Root in another root (this fails)
(defui AnotherRoot
static om/IQuery
(query [this] `[~#(om/get-query RootView)])
Object
(render
[this]
(dom/div nil
(rootview (om/props this)))))
(def reconciler
(om/reconciler
{:state init-data
:parser (om/parser {:read read :mutate mutate}) }))
(om/add-root! reconciler RootView (gdom/getElement "app"))
Problem: Using AnotherRoot as root component
However, when I change RootView in the last row to AnotherRoot, like:
(om/add-root! reconciler AnotherRoot (gdom/getElement "app"))
the ui still renders, but when pressing a button, the following error occurs:
Error: No queries exist for component path
(ui.core/AnotherRoot ui.core/RootView ui.core/Person)
The error comes from (next.cljc:1916)
I don't understand the error. The query from AnotherRoot returns the same query as RootView (however, you'll get a warning when returning the query straight away - which makes sense), but the reconciler seems to not be able to figure out how to re-render the component when the app-state changes.
The relevant dependencies should be:
[[org.clojure/clojure "1.9.0-alpha14"]
[org.clojure/clojurescript "1.9.473"]
[figwheel-sidecar "0.5.9"]
[org.omcljs/om "1.0.0-alpha47"]]
The question
What is the general way to create nested components in om.next?

IUP, button, tree, matrix

1)
I try to load ico image to IUPbutton but with no success.
After linking IM dll's and add proper header this is my approach:
Ihandle *btn1, *btn2;
Ihandle* img1;
btn1 = IupButton("", NULL);
IupSetAttribute(btn1, "MINSIZE", "24x24");
btn2 = IupButton("", NULL);
IupSetAttribute(btn2, "MINSIZE", "24x24");
img1 = IupImage(16, 16, IupLoadImage("icons\\home_16x16.ico"));
IupSetAttribute(btn1, "IMAGE", "img1");
frame = IupHbox(btn1, btn2, NULL);
dlg = IupDialog(IupHbox(mat, IupVbox(frame, tree, NULL), NULL));
IUP don't report any error but image don't appear on the button btn1.
How to load picture from file to a button in RGBA mode?
2)
I fill IupTree with data from sqlite database in following order: 'Name' (which is root) and then about 170 branches which have 1-10 leafs. VALUE is set to 0 and 'Name' is selected.
How can I get with code expanded tree to first branches like when I doubleclick to 'Name'?
I try EXPANDALL attribute but then all leaf's are expanded too what is not wanted.
3)
How can I get IUPtree item 'id' in k_any callback f. e. when press ENTER key?
4)
How can I get IUPtree item text from 'id' in executeleaf and branchopen callbacks?
5)
How can I loop through IUPtree to get id, text, sort of item (branch/leaf)?
6) Is here a way on IUPmatrix to catch keyUP or keyRELEASED event like we get keyPRESS in K_ANY?
1) Pay more attention to the data type of each function. Notice that IupLoadImage already returns an Ihandle. So instead of:
img1 = IupImage(16, 16, IupLoadImage("icons\\home_16x16.ico"));
you should do this:
img1 = IupLoadImage("icons\\home_16x16.ico");
Also if you do this:
IupSetAttribute(btn1, "IMAGE", "img1");
you are specifying a string, you must somehow associate the string "img1" to the Ihandle img1. They are two very different things. Check the IupImage documentation. Or you do:
IupSetHandle("img1", img1);
IupSetAttribute(btn1, "IMAGE", "img1");
Or a better way:
IupSetAttributeHandle(btn1, "IMAGE", img1);
2) Have you try to expand only the branch you want expanded? Check the STATEid attribute on the IupTree documentation.
3) What you want is to the the item that has the focus. So get the VALUE attribute of the IupTree. Notice that the Enter key will trigger the executeleaf callback which already has the item id.
4) Check the TITLEid attribute in the documentation.
5) A tip, when setting/getting attributes of a IupTree, IupMatrix or IupList, you can use:
IupSetAttribute(ih, "TITLE3", "My Title");
or
IupSetAttributeId(ih, "TITLE", 3, "My Title");
6) As I told you before, IupMatrix inherits from IupCanvas, so you must also check for the IupCanvas callbacks. Check the IupMatrix callbacks documentation, at the end there is an explanation about the IupCanvas callbacks.

Resources