Input text reagent component value formatting - text

I'm trying to format the value of an input "text" reagent component without success. I expected that, after changing the value of the ratom variable, the value of the component will be formatted at re-render.
I used cl-format to format number with space thousand separator. I works well at first load, but not after change the price value.
Is it possible to format the price value of this example after saving the new value?
(ns shopping.app
(:require [reagent.core :as r]
[cljs.pprint :refer [cl-format]]))
(defonce shoppinglist (r/atom (sorted-map
1 {:id 1 :name "Bread" :price 20},
2 {:id 2 :name "Milk" :price 12})))
(defn update-item! [fn & args]
(apply swap! shoppinglist fn args))
(defn shopping-item [{:keys [id name price]} item]
(let [rreadonly (r/atom true)
rprice (r/atom price)]
(fn [{:keys [id name price]} item]
[:div
[:label id]
[:label (str " | " name)]
[:input { :type "text"
:readOnly #rreadonly
:value (cl-format nil "~,,' :d" #rprice)
:on-change #(reset! rprice (-> % .-target .-value))}]
[:button { :id "button-edit"
:hidden (not #rreadonly)
:on-click #(swap! rreadonly not)}
"Edit"]
[:button { :id "button-delete"
:hidden (not #rreadonly)
:on-click #(update-item! dissoc id)}
"Delete"]
[:button { :id "button-save"
:hidden #rreadonly
:on-click #(do
(update-item! assoc id (assoc {} :id id :name name :price (js/parseInt #rprice)))
(swap! rreadonly not))}
"Save"]
[:button { :id "button-reset"
:hidden #rreadonly
:on-click #(do (reset! rprice price)
(update-item! assoc id (assoc {} :id id :name name :price (js/parseInt #rprice)))
(swap! rreadonly not))}
"Reset"]])))
(defn shopping-list []
[:div.container
(doall (for [item (vals #shoppinglist)]
^{:key (:id item)} [:div
[shopping-item item]]))])
(defn init
"Initialize components."
[]
(let [container (.getElementById js/document "container")]
(r/render-component
[shopping-list]
container)))

This does not work because you are creating a cycle:
on-change sets the value of the rprice atom to a string, but in this expression (cl-format nil "~,,' :d" #rprice) #rprice should be a number
when you set value of the input to some formatted value, next time you type something the on-change will see this formatted value instead of the raw input you had before, so then the contents of rprice will be set to the formatted value, a string
So if you set rstring to the raw number or the formatted string, after a cycle it will always be the formatted string.
Unfortunately formatting the input as you type is a bit complex and you'll probably need a library. Maybe reagent-forms supports it: https://github.com/reagent-project/reagent-forms, if not, there are other React libraries that do.

Related

iterating through a list to look up data, and construct a string

Elisp newbie, looking for help with this.
I have this variable:
(setq bibtex-completion-additional-search-fields '(tags keywords))
I then have a function, which, if this variable is set, then needs to iterate through those field names, and look them up in a data record, concatenate the resulting values into a string, which it returns.
Here's what the data looks like:
("2009-03-01 Zukin, Sharon and Trujillo, Valerie and Frase, Peter and Jackson, Danielle and Recuber, Tim and Walker, Abraham gentrification New Retail Capital and Neighborhood Change: Boutiques and Gentrification in New York City article zukin_new_2009"
("date" . "2009-03-01")
("author" . "Zukin, Sharon and Trujillo, Valerie and Frase, Peter and Jackson, Danielle and Recuber, Tim and Walker, Abraham")
("tags" . "gentrification, retail")
("title" . "New {{Retail Capital}} and {{Neighborhood Change}}: {{Boutiques}} and {{Gentrification}} in {{New York City}}")
("=type=" . "article")
("=key=" . "zukin_new_2009"))
This is what I have for the function ATM, which I know is wrong. But I can't wrap my head around how to do this in elisp (I have more experience with Python and Ruby).
(defun bibtex-completion--get-extra-search-data (candidate)
"Return extended search metadata as string."
(if bibtex-completion-additional-search-fields
; if the data is present, pull its value(s), join into a single string
; TODO FIX ME, this is wrong
(format "%s" (cl-loop
for field in bibtex-completion-additional-search-fields
collect
(cdr (assoc field (cdr candidate)))
))))
So with the example data above, the function should return that string "gentrification, retail". And if that record were to have a keyword field with "foo", the return string would be "gentrification, retail, foo" (or could just be space-separated; not sure it matters).
First, the keys in your data structure are strings, not symbols. So, you could change your lookup fields,
(setq bibtex-completion-additional-search-fields '("tags" "keywords"))
but, using symbols as the cars in the candidate data structure is probably better (efficiency-wise I believe).
The canonical elisp for joining list into string is
(mapconcat #'identity ...),
(mapconcat
#'identity
(delq nil
(cl-loop for field in bibtex-completion-additional-search-fields
collect (cdr (assoc field (cdr candidate)))))
", ")

String comparison mistake

I tried to write a set of functions that check the expiration date for a domain name:
(ql:quickload 'inferior-shell)
(defun whois-lookup (site)
(let ((request (format nil "whois ~a" site)))
(inferior-shell:run/ss request)))
(defun match-expiration-string (input)
(let ((test-string "Registrar Registration Expiration Date:"))
(string> input test-string)))
(defun domain-expiration-date (site)
(with-input-from-string (input (whois-lookup site))
(loop for line = (read-line input nil nil)
while line do
(when (match-expiration-string line)
(format t "~A~%~t~A ~%" site line)))))
I'd call it like this: (domain-expiration-date "startpage.com").
Unfortunately, instead of just displaying the relevant line, it shows all of them.
match-expiration-string seems to be working correctly, so I have no idea what the problem is.
CL-USER> (match-expiration-string "Registrar Registration Expiration Date: 2016-05")
39 (6 bits, #x27, #o47, #b100111)
CL-USER> (match-expiration-string "Registrar Registration Expiration ")
NIL
As suggested by jkiiski, it works with a regular expression:
(defun match-expiration-string (input)
(let ((test-string "Registrar Registration Expiration Date:"))
(ppcre:scan test-string input)))
==>
CL-USER> (domain-expiration-date "startpage.com")
startpage.com
Registrar Registration Expiration Date: 2018-10-10T04:00:00Z
NIL
Like Joshua Taylor says, you don't need regex, only search. I also noticed that "Registrar Registration Expiration Date:" wasn't in every whois response, so I modified the search (and made place in case I needed other search strings for other types of domains):
(defun match-expiration-string (input)
(let ((inclusion-strings '("Expiration Date:"))
(exclusion-strings '("Registrar Registration Expiration Date:")))
(when (some #'(lambda (s) (search s input))
inclusion-strings)
(notany #'(lambda (s) (search s input))
exclusion-strings))))

Not sure how to parse this using Text.XML.Cursor

I'm trying to parse XML that looks like this:
<h1>Collection A</h2>
<table>
<tr>Property 1</tr>
<tr>Property 2</tr>
</table>
<h2>Collection 2</h2>
<table>
<tr>Property 1</tr>
<tr>Property 88</tr>
</table>
I would like to parse that info as such:
MyClass "Collection 1" "Property 1"
MyClass "Collection 1" "Property 2"
MyClass "Collection 2" "Property 1"
MyClass "Collection 2" "Property 88"
I'm not sure how to go about doing this. My first thought was doing something like element "h1" $| followingSibling &// element "tr" &/ content, but that doesn't work, since it will capture all of the tr's, even the ones that don't "belong" to the table that I'm trying to read from, and I won't be able to know what properties belong to which collection.
How do I go about solving this?
You have to define your own XML Axis of a "immediate sibling" as followingSibling returns every node after the context. It's possible since Axis in Text.XML.Cursor are type synonym of Cursor -> [Cursor]:
immediateSibling = take 1 . (anyElement <=< followingSibling)
And combining information from different level is just nested list comprehension:
selected = root $/ selector
selector = element "h2" >=> toTuple
-- replace tuple with your constructor
toTuple c = [ (coll, prop)
| coll <- c $/ content
, prop <- c $| (immediateSibling >=> element "table" &/ element "tr" &/ content) ]

Xml-conduit parsing for text inside links

I would like to extract the text contents from the below Html page. All the paragraphs from the <div>.
I use the xml-conduit package for html parsing and came up with the following code:
getWebPageContents :: Url -> IO [T.Text]
getWebPageContents u = do
cursor <- cursorFor u
return $ cursor $// filter &/ content
filter = element "div" >=> attributeIs "id" "article-body-blocks" &// element "p"
This will return most of the text but not the ones from the links("front page of today's Daily Mirror")
Could anyone help?
You need to filter to all the descendants of the p tags, not just the children. You probably just need to replace &/ content with &// content.

Tcl: default if variable is empty?

I've "inherited" some Tcl code, and while I worked through some tutorials and can make sense out of the language, my own Tcl constructs lack a certain... finesse.
For example, I have this code:
puts "Column 'name': [ $queryRs getString name ]"
$queryRs is the result set of a SQL query. The [ $queryRs getString name ] construct retrieves the content of the table column "name" from the current row in the result set. If the database field is NULL, the puts does not print anything.
I would like to print a "default" string instead, i.e. if [ $queryRs getString name ] results in nothing, I'd like to replace it with "--".
Now, I could do something like this:
set nameVar "[ $queryRs getString name ]"
if { [ string length $nameVar ] == 0 } {
set nameVar "--"
}
puts "Column 'name': $nameVar"
But there has to be a more compact solution, something that can be done inline instead of adding four lines and a temporary variable. Help, please?
Two things.
First, Tcl doesn't have a notion of NULL (nil, undefined or whatever) value, and when you want to simulate such a value you have to either use a variable and test for its existence or use an entry in an array of dictionary and test for its existence, too. If such a variable/entry exists, then the value is defined, otherwise it's not.
Unfortunately, the creator of your inherited code apparently did not care about the case where a variable can be NULL so NULLs are indistinguishable from variables having default values (an empty string).
Next, you can use a helper procedure to do what you need:
proc ValueOrDef {val {def --}} {
expr {$val ne "" ? $val : $def}
}
and then go like this:
puts [ValueOrDef [$queryRs getString name]]
puts [ValueOrDef [$queryRs getString name] "some other default"]
You could use the x?y:z construct of the expr command. x is the condition, y is the alternative if the condition is met and z is the alternative if x is not met.
E.g. (but still with a temporary variable):
set nameVar [ $queryRs getString name ]
puts "Column 'name': [expr {[string length $nameVar]>0 ? $nameVar : "--"}]"

Resources