Fork me on GitHub
#fulcro
<
2018-01-09
>
LL01:01:06

The Fulcro's query syntax is a subset of the Datomic's, and Datomic's pull expression support attribute navigation which can do recursive pull, such as (d/pull db e [:value {:children/ref ...}]). Does the Fulcro's support this syntax like {:children/ref ...}?

LL02:01:37

And does the Fulcro's query syntax support the reverse pull, such as :children/_ref?

cjmurphy02:01:28

:children/ref is a join (or edge) and the ... will be (get-query ....). Almost every example has this. I'm pretty sure the answer is 'no' to your second question, but only because I've never seen reverse pull used in a query.

LL03:01:14

@cjmurphy Yes, I had just tried to use {:query [:db/id {:children/ref ...}], and got an error 'java.lang.NullPointerExcetption`.

cjmurphy03:01:39

Always best to not do it like that, but instead have the (get-query ...) for the subquery.

cjmurphy03:01:35

That way there's a connection between components that is kept (via metadata).

LL03:01:12

I need a tree structure for file system' folders. so if use get-query, there will be

(defsc Folder [this {:keys [db/id folder/name folder/parent]}]
  {:query [:db/id :folder/name {:folder/parent (get-query Folder)}]
   :ident [:folder/by-id :db/id]})

LL03:01:41

the component will get-query itself.

cjmurphy03:01:15

There is syntax esp for recursion. I've never used it myself but I remember there was a blog post about it.

LL03:01:20

Great. Can you gave the link of the blog post.

LL03:01:44

Thanks, I read now

cjmurphy03:01:54

Is it not in the book? (Reading it is on my TODO list).

LL03:01:10

There is really a section 'Recursive query'

LL03:01:27

@cjmurphy Sadly, he use ..., the code is

(defui Node
  static om/IQuery
  (query [this]
    '[:id :value {:children ...}]))

LL03:01:17

How can defsc' :query work with ...?

cjmurphy03:01:02

I don't know the ins and outs of defsc. Switch to defui is all I can say, or wait a while till you can get a definitive answer.

LL03:01:25

Ok, got, Thx.

macrobartfast04:01:58

fulcro is so damn hot.

tony.kay04:01:06

@tpliliang Recursion syntax is supported, just like Datomic. You have to quote the symbols, and for defsc you have to use lambda mode:

(defsc Person [this props]
  {:query (fn [] [:db/id :person/name {:person/children '...}])}  ;;anybody can have children (or grandchildren)
   :ident [:person/by-id :db/id]}
  ...)
is a legal query. IMPORTANT NOTE: the query will work. normalization will work. UI Refresh requires that have circular refs require more effort on your part. When you render such circular data structures be sure to pass a “depth” as a computed property to children so you can tell if you need to go deeper.

tony.kay04:01:23

numbers are also supported to limit depth (instead of '...)

tony.kay04:01:32

I think recursion still needs a chapter (or at least section) in the book

LL05:01:08

@tony.kay Thanks, the '... works, even in Template Mode. I try the reverse navigation :children/_ref now, and want to know if the reverse thing works too.😀

tony.kay06:01:09

reverse navigation is not supported @tpliliang

tony.kay06:01:50

the storage format cannot efficiently process such a thing. if you want it, you have to add that explicit prop to the source entity

tony.kay06:01:00

you could do that as a post-processing step on load

tony.kay06:01:49

On the plus side, I’ve written docs for recursion now…book update in progress

tony.kay06:01:05

the timestamps on the files should get you fresh stuff, there are 4 new demos.

tony.kay06:01:24

They work for me, but I had to reload with a cache refresh

tony.kay06:01:01

@thosmos The SSR got a little more refined today on that startups website project. I got the uberjar build working…had to add an additional build to get the js to work out well enough. It’s also a bit fiddly on the order of requires it seems. I had a hell of a time for about 2 hours just getting it to work…reordered some stuff and it started working again. Not my favorite dev experience 😕

eric.shao12:01:51

fulcro-inspect work in index.html,but do not work in cards.html. Please give me a hint.

eric.shao13:01:21

Never mind.Just add fulcro.inspect.preload to cards-build’s :preloads.

roklenarcic15:01:40

if you had a table that had sort order, would you put it in react component state using set-state!?

claudiu15:01:07

personally would like it to show up in the state, with filters and other properties of the table. Then it would also show un in inspect & support viewer 🙂

tony.kay18:01:02

2.1.0 seems pretty stable, so I’m taking it out of beta. Now on clojars. Has one additional fix from @mitchelkuijpers: removed a reference to a missing file that was causing problems when building with shadow-cljs

tony.kay19:01:47

Lein template also updated on clojars to use the new versions of things

tony.kay19:01:32

Speaking of shadow-cljs. I have not had time to try it out, but I’m really wanting to make sure Fulcro works well with it. I’ll probably play with it some in the coming weeks, but would love tips from anyone that is currently using Fulcro with it.

thheller20:01:43

@tony.kay as far as I'm aware all issues are resolved and it just works

tony.kay20:01:56

@thheller That’s great. I read in your blog that the cljsjs/react stuff and using js/React can cause problems.

thheller20:01:06

so if you use cljsjs.react it just works (although you still need to manually npm install react react-dom create-react-class)

tony.kay20:01:40

ok. I’ll see if I can get a version of the lein template working with it, and include that as a template generation option

thheller20:01:13

which template is that? I can take a quick look at what would need to be modified

tony.kay20:01:10

The 2.x side in particular…there is a lot to it, though. There are serveral builds, CI integration, and i18n stuff. Probably still need project.clj, so the two need to work together

tony.kay20:01:33

if you just do a lein new fulcro boo you’d see the generated result for 2.x

thheller20:01:01

one thing that is not supported since I consider this an anti-pattern is :source-paths per build

thheller20:01:09

it is totally safe to put everyone into one source path if desired. unlike CLJS we only include actually used sources in shadow-cljs builds. so if nothing requires a namespace it won’t be include in the build

tony.kay20:01:18

@thheller so the i18n extraction is the one thing that has some additional special needs. I use the generated js to find strings (using gettext’s xgettext)

tony.kay20:01:00

so it has to be a single js file with the original symbol names so the tool can find them

tony.kay20:01:57

Well, actually there are a number of concerns I have on that…

tony.kay20:01:53

well, it might be ok…so, you can still have multiple builds, just one set of source?

tony.kay20:01:25

e.g. an ssr build might need react.dom.server, but it would be important that the regular client mount not happen (the template does not have ssr…but I have projets that do)

tony.kay20:01:45

I assume from your comment that this means I’d still have multiple build, just with different entry point namespaces

thheller21:01:26

yes multiple builds, one entry per.

thheller21:01:07

the closure compiler has support for extracting i18n strings, maybe that could be easily hooked up?

tony.kay21:01:30

wasn’t aware of that…I’ll take a look

thheller21:01:48

I suppose you need to go through (js/goog.getMsg "the string") though

thheller21:01:43

didn’t play with any of this myself yet but might be useful

tony.kay21:01:44

as far as I can tell, this won’t leverage the GNU gettext infrastructure, which I consider to be a lot more important

tony.kay21:01:00

that is such a mature pipeline and tool set

thheller21:01:06

as for the xgettext support I can probably write an easy hook to do that automatically

tony.kay21:01:19

that would be nice…basically, calls to tr, trc, and such are currently mapped to low-level global javascript functions of those names. Then a whitespace optimized js file is generated, and xgettext can run against that without modifications.

thheller21:01:29

what do I need to do the in the generated lein template to use that?

tony.kay21:01:11

make i18n-extract

tony.kay21:01:13

it’s in the makefile

tony.kay21:01:36

sorry, I’m a bit old-school sometimes 🙂

tony.kay21:01:53

did a lot of C and C++ systems programming…

tony.kay21:01:47

that code supports doing both the extraction and cljs code generation

thheller21:01:22

hmm its always a tr call?

tony.kay21:01:50

There are 4, I think

tony.kay21:01:25

tr, trc, trf

thheller21:01:42

ah its already a macro, you could instead write the strings into the analyzer metadata

tony.kay21:01:53

trc means “with context”. Used for translating things like this (trc "The abbreviation for male" "M"). It gives context to tre translator

thheller21:01:23

so you don’t need to run xgettext over the generated JS

thheller21:01:36

instead feed it with a proper format

tony.kay21:01:50

so, I wrote that stuff when I was very new to clj/cljs…the reason it is a macro is I wanted compile-time error checking, and I needed the js output to be tr(“s”), not call(…)

thheller21:01:12

but you are only EXTRACTING strings right?

tony.kay21:01:15

but yes, it would be awesome to not need xgettext

thheller21:01:26

so in the macro you already have all the strings

thheller21:01:32

just need a way to get them “out”

thheller21:01:48

so what you can do is (swap! cljs.env/*compiler* assoc-in [:cljs.analyzer/namespaces (-> &env :ns :name) ::strings] the-string some-info-about-string)

thheller21:01:07

so for each namespace you would record the strings used in that namespace

thheller21:01:59

finally when the “build” is done collect all the namespaces that were used and merge the strings

thheller21:01:05

write them out in whatever format required

tony.kay21:01:18

that sounds very very nice

thheller21:01:19

reading them out could be done the same way

tony.kay21:01:12

and the file/line is also in &env, so that could make nice comments in the translator file 🙂

tony.kay21:01:20

or is that in &form?

tony.kay21:01:26

either way…

thheller21:01:58

should be in (meta &form) yes

thheller21:01:36

should also be in &env though

tony.kay21:01:39

so how do I hook into the compiler to know when it is done (e.g. if user wants to use lein cljsbuild)

thheller21:01:10

I think cljsbuild has a hook for that

tony.kay21:01:28

yeah, I was reading the docs…not seeing it. perhaps there is a compiler option…

tony.kay21:01:30

hm. not seeing anything. I like the idea, though. I guess if there was an entry point for i18n it could run a macro to emit, and require everything in the UI…that should get the order correct, right?

tony.kay21:01:39

@thheller are you trying something possibly reusable? I don’t want to duplicate effort.

roklenarcic21:01:46

I am still struggling with how to elegantly code that a modal is hidden if form is commited successfully

thheller21:01:41

creating a quick proof of concept now, just testing how it can work easily

thheller21:01:55

not sure I want to add full i18n support to shadow-cljs itself

tony.kay21:01:00

@thheller thanks. thought you might be doing something. I appreciate the help 🙂

tony.kay21:01:46

@roklenarcic So, that is a bit too large of a question to answer

roklenarcic21:01:51

So I've got commit-to-entity! and I've got my bootstrap/hide-modal! mutation.

thheller21:01:57

how does the gettext stuff insert the translated texT?

thheller21:01:05

where does that happen?

tony.kay21:01:29

the sequence is: extract -> po files -> translator -> returned po files -> generate cljs FROM po files

tony.kay21:01:55

@roklenarcic don’t use commit-to-entity!…write your own, and use ptransact!

tony.kay21:01:38

I could swear I had a demo somewhere of modal form interaction…

roklenarcic21:01:03

Alright. Yes that is a very valuable example to have in documentation, because a lot of people will be looking for that one.

tony.kay21:01:16

@thheller tr is just a map lookup against *loaded-translations*, and the generated cljs adds to that map

thheller21:01:45

thats horrible 😛

thheller21:01:18

so everyone always download the “source” language

thheller21:01:31

and then just the additional languages are loaded on demand?

tony.kay21:01:41

@thheller The source language are the lookup keys. Yes, you can put the additional in modules.

tony.kay21:01:50

dynamic loading in the change-locale mutation is automatic

tony.kay21:01:02

(assuming you’re using cljs.loader)

tony.kay21:01:48

I agree it needs some work. Again, it was very early code, and there has not been a lot of demand for improvements, so I’ve left it alone

thheller21:01:45

well I guess a key is needed always anyways so might as well use the source language

tony.kay21:01:58

right…as opposed to inventing some crap that you don’t remember what it is, and having to remember which file “those strings” are in…cross-referencing…ugh

tony.kay21:01:08

makes debugging so much easier to be able to see the real string in source

tony.kay21:01:24

I hate the way most i18n systems work. GNU gettext got it right IMO

thheller21:01:45

yeah i18n is a PITA

thheller21:01:33

had to do it once and wrote an entire webapp for the translators to use 😛

tony.kay22:01:00

yeah, I have distant plans for something like that…it would be pretty easy to instrument the UI automatically and let you translate against the running app

tony.kay22:01:20

like fulcro-inspect, but as an editor…a pop-out where you can click on UI elements

tony.kay22:01:14

@currentoor fun project if you’re looking ^^^^ 😉

tony.kay22:01:30

@thheller the other nice thing about using the source string: if a translation is missing, you get the default string on the screen…so the viewer has some hope of understanding what you’re saying.

tony.kay22:01:52

and it makes writing an on-screen translator app trivial

thheller22:01:54

true, thinking it through it would be pretty painful to output localized JS anyways

tony.kay22:01:00

the main thing I don’t like is global *loaded-translations* (makes using more than one app on a page a pain to i18n).

thheller22:01:08

how do you figure out which JS to load for which users, better to do that on load with a default language

tony.kay22:01:37

default language comes in. You can code whatever you like in started-callback to switch locale with a change-locale mutation

tony.kay22:01:05

for SSR you have all available on the server, so you just pre-render with locale set…detect from browser headers or user session info

tony.kay22:01:20

When I get time, I plan on making translations app-specific, with custom namespaces for the translations, and use something like bindings during render to put the current translations in context for tr.

thheller22:01:56

so I created a basic macro for tr

thheller22:01:01

in the analyzer data you get something like [{:msg "translate me plz", :line 6, :column 1}] for (tr "translate me plz")

thheller22:01:32

whats and easy output format that gettext can process?

thheller22:01:04

don’t know anything about gettext tools

thheller22:01:32

bad part about the tr call is that it emits a (get-text ...) call which accesses an atom so it will never be DCE’d probably

tony.kay22:01:30

So, gettext po files are an easy format…here’s an example

msgid "string"
msgstr "translation"

tony.kay22:01:46

there’s also a standard header at the top

tony.kay22:01:14

typically the source file and line are included above each as a comment

tony.kay22:01:28

one big file for the entire app

thheller22:01:28

yeah, looking at what you currently generate for the make cmd

tony.kay22:01:51

theres also msgctx I think it’s called

tony.kay22:01:54

for message context

thheller22:01:17

poedit doesn’t seem to show the comments or anything?

tony.kay22:01:45

the # comments?

tony.kay22:01:57

yeah, though are useful for developers when resolving future things

tony.kay22:01:15

poedit will show the msgctx

tony.kay22:01:23

which you send by using trc instead of tr

tony.kay22:01:54

Say you change a string from “Hi” to “Hello”. When you update translations msgmerge I think also uses those comments on line/file to figure out certain things. not sure.

tony.kay22:01:03

there is fuzzy logic around translation evolution

thheller22:01:25

makes sense

tony.kay22:01:58

it would improve things to have the clj[sc] context instead of the generated js context

tony.kay22:01:06

so I’m liking where we’re going 🙂

tony.kay22:01:30

On DCE: don’t care. The tr itself is very tiny, and if you don’t use i18n then you don’t call it..so there’s not much dead code to eliminate

tony.kay22:01:02

@thheller so am I right in assuming that a macro called at the end of an entry point ns (that in turn requires all UI) could execute a different macro to affect the string output?

tony.kay22:01:14

(ns i18n-entry
   (:require-macros i18n)
   (:require ui.things))

(i18n/dump-strings "i18n/messages.pot")

tony.kay22:01:58

so that ui-things and related nses call tr, gathering up the strings

tony.kay22:01:22

then dump-strings just looks in the compiler and dumps them

thheller22:01:35

you don’t want a macro to do that

thheller22:01:34

#: demo.i18n:6:1
msgid "translate me plz"
msgstr ""

thheller22:01:52

does that need a filename or would the ns be enough?

tony.kay22:01:17

that I don’t know

tony.kay22:01:07

I don’t think the tools should care, since they work on multiple programming languages with various filename extensions…I think they just need to match on successive executions

thheller22:01:02

I deduped the messages so there might be multiple #:. as far as I can tell thats ok

tony.kay22:01:24

so, before you spend too much time, could we discuss what you’re doing?

tony.kay22:01:56

e.g. is it reusable with regular cljsbuild, or is this a shadow-cljs solution only?

tony.kay22:01:16

not that it hurts to have support 🙂

tony.kay22:01:43

just thinking about the extra documentation, really…I’m having a bit of writers fatigue 🙂

thheller22:01:28

the sketch is shadow-cljs specific but once it works it could probably be made standalone

thheller22:01:41

about finished

tony.kay22:01:57

ok…thanks. On deduping…conj the namespaces onto the comment

tony.kay22:01:18

I think that’s how gettext does it…every mention of a particular string gets included in comment form

tony.kay22:01:56

don’t remember if they are separate comments, or one bit one….I think separate

tony.kay22:01:09

so I think what you did is right

thheller22:01:16

the idea is

thheller22:01:18

:test-i18n
  {:target :browser
   :output-dir "out/demo-i18n/js"
   :asset-path "/js"

   :i18n-options
   {:messages-file "messages.pot"}

   :modules
   {:main {:entries [demo.i18n]}}}

thheller22:01:33

basic build config, you add :i18n-options

thheller22:01:45

compiler generates that file on compile

tony.kay23:01:01

pretty darn nice

thheller23:01:17

no extra steps or anything, fully automatic

tony.kay23:01:47

is this some kind of plugin to shadow-cljs, or you thinking of just including it?

thheller23:01:34

ends up with a messages.pot like

#: demo.i18n:6:1
msgid "translate me plz"
msgstr ""

thheller23:01:15

might be neat if it just works when setting :i18n-options but yeah will probably be a plugin

thheller23:01:21

so it works with other tools as well

thheller23:01:34

kinda weird that I added the context thingo half-way but the file generator looks like https://github.com/thheller/shadow-cljs/blob/test-i18n/src/main/shadow/build/i18n.clj

tony.kay23:01:11

missing msgctxt (only emitted when trc is used), but other than that it looks great

thheller23:01:50

#: demo.i18n:6:1
msgid "translate me plz"
msgstr ""

#: demo.i18n:8:1
msgid "foo?"
msgctxt "foo"
msgstr ""

#: demo.i18n:9:1
msgid "foo?"
msgctxt "bar"
msgstr ""

tony.kay23:01:15

The file format suggests ctxt comes before msgid

thheller23:01:18

(ns demo.i18n
  (:require
    [shadow.i18n :refer (tr trc)]
    ))

(tr "translate me plz")

(trc "foo" "foo?")
(trc "bar" "foo?")

tony.kay23:01:19

add in trf (the first arg is the string of interest as msgid), and you’re done 🙂

thheller23:01:42

yeah the plural stuff would be useful as well I guess

tony.kay23:01:51

plurals are not handled by gettext

tony.kay23:01:06

in Fulcro, we have to use something that works in browsers….

tony.kay23:01:25

plural support is done via Yahoo’s FormatJS, and I just use msgid for the format string

thheller23:01:36

the docs contain this example

thheller23:01:38

#: src/msgcmp.c:338 src/po-lex.c:699
#, c-format
msgid "found %d fatal error"
msgid_plural "found %d fatal errors"
msgstr[0] "s'ha trobat %d error fatal"
msgstr[1] "s'han trobat %d errors fatals"

tony.kay23:01:38

SSR uses IBM’s JVM implementation of ICU

tony.kay23:01:44

yeah, we don’t use it

tony.kay23:01:46

into the message string itself

thheller23:01:50

ah thats also an example for multiple source occurences, all in one not multiple #:

tony.kay23:01:59

I have a feeling the tools do both (all in one and spread across multiple comments)

tony.kay23:01:08

I wouldn’t go to the trouble of changing what you have

thheller23:01:06

too late 😉

thheller23:01:01

I do like having this built into the compiler but I’m not actually sure I want it in shadow-cljs

tony.kay23:01:47

it doesn’t make much sense to build Fulcro-specific things into your tool

thheller23:01:19

might be a good candidate to finally figure out the plugin API I’ve been thinking about for a way too long time now

thheller23:01:52

well i18n is such a general thing that it can be useful to ALL cljs builds

thheller23:01:01

regardless of other frameworks used

thheller23:01:33

coupled to a build tool is not the best idea though

tony.kay23:01:58

and you don’t like the macro idea because? Is there an evaluation order problem?

tony.kay23:01:44

I mean, the compiler plugin is definitely superior

tony.kay23:01:48

your tool wraps the compiler, right? So, you’re basically just holding onto the compiler, running the build, then pulling this stuff out of the compiler before discarding it I assume

tony.kay23:01:22

or does shadow-cljs do deeper hacks…I assume it must

thheller23:01:56

yes, way way deeper

thheller23:01:33

the macro approach I hate because its a file every use would need to write in their project

thheller23:01:47

then invoke a build tool to build that file

thheller23:01:05

much rather directly tell the build tool what to do without the macro indirection

tony.kay23:01:30

oh I completely agree…was just trying to find something usable now

tony.kay23:01:18

it seems an improvement over having to have a custom build and two additional external steps to run, and a command-line tool to install

thheller23:01:36

hehe slight improvement yes

thheller23:01:03

so I could write a function that takes the compiler env and generates the info from that

thheller23:01:18

no idea how cljsbuild would invoke that though

thheller23:01:43

also not sure you really want to do this for every build

thheller23:01:00

probably better to do it “on demand”

thheller23:01:05

clj -m shadow.i18n/extract-strings my-build-id or something like that

thheller23:01:48

I’ll sleep on it. shouldn’t rush things like this 😉

tony.kay23:01:17

Yeah. Thanks a lot for looking into it.

thheller23:01:33

totally forgot that I wanted to look at the lein template. will do that tomorrow I guess 😉

tony.kay23:01:54

cool. have a good night

thheller23:01:15

the list of stuff I want to add keeps getting longer. at this pace no docs will ever be written 😛

thheller23:01:30

should get into the habit of writing the docs first

thheller23:01:36

but not today. good night.