Fork me on GitHub
#hoplon
<
2017-07-10
>
flyboarder00:07:15

@thedavidmeister if you have an app, I can fix it for you

thedavidmeister01:07:22

@flyboarder did you see the failing test?

thedavidmeister01:07:00

in your example, if you have a a b and change the first a to c, the UI shows c b c

thedavidmeister01:07:21

that's not "reording elements" from the user's perspective

thedavidmeister01:07:55

if it's "correct behaviour", what's the use case for that?

thedavidmeister01:07:06

what i'm asking for is an example for starting with a a b then changing the first a to c and then seeing a b c

flyboarder01:07:20

Yeah my demo app does that correctly

thedavidmeister01:07:26

doesn't in my tests

flyboarder01:07:27

With the example I posted before

thedavidmeister01:07:37

i put your example in the test

flyboarder01:07:48

I think that's the test failure since it doesn't actually track the cell state just the element

thedavidmeister01:07:08

if you can write a better test please do

thedavidmeister01:07:18

i thought i pretty faithfully represented your example

thedavidmeister01:07:38

did you try literally a a b in your demo app?

thedavidmeister01:07:43

it has to be a a b

thedavidmeister01:07:00

it can't be a b z d as you posted, the bug would not manifest in that

thedavidmeister01:07:10

or alternatively, is the demo app somewhere that i can play with @flyboarder ?

flyboarder01:07:55

I'm working on a test library for hoplon that mimics the HLisp structure

flyboarder01:07:08

The demo code I posted above should work in anything

flyboarder01:07:28

I'm testing it with snapshot builds of degree9/meta

flyboarder02:07:43

@thedavidmeister I tested with all "a" and it still seems to work without the bug you describe

thedavidmeister02:07:13

ok then, i'll try with the demo code in a browser

flyboarder02:07:54

@thedavidmeister do you have a test app?

flyboarder02:07:08

i’d like to make sure we are looking at the same thing

thedavidmeister02:07:36

(ns ^{:hoplon/page "index.html"} pages.index
 (:require
  hoplon.jquery
  [javelin.core :as j]
  [hoplon.core :as h]))

(j/defc data ["a" "a" "b"])

(j/defc= sdata (sort data))

(j/defc= idata (into {} (map-indexed vector sdata)))

(j/defc= rdata idata #(reset! data (vals %)))

(prn idata)
(h/defelem testelem [attr kids]
 (h/div
  (h/for-tpl [[k v] rdata]
   (h/input :change #(swap! rdata assoc @k @%) :value v))))

(h/html
 (h/body
  (testelem)))

flyboarder02:07:03

@thedavidmeister ok, so I see the issue, let me play with it

flyboarder02:07:55

@thedavidmeister so I found the issue, it’s the -tpl caching

flyboarder02:07:52

when you change the first input to c it actually created 2 new elements

flyboarder02:07:33

one for the reorder of b and one for the “new” c

mudphone02:07:40

Hi, is there a pattern for doing a fetch of data at the top of a defelem? I have a websocket connection that fetches data for parts of the page, and want to only fetch it when the element is being created. (I can use a click event on a button later, if it must be refreshed.)

mudphone02:07:16

Right now, I have a do at the top of my defelem (with the fetch code triggered in the do), but that feels gross.

mudphone02:07:31

2nd question: Is there a way to get an index (like in map-indexed) when using loop-tpl?

flyboarder02:07:38

@mudphone we are currently working out the index/sorting thing but you can just map-indexed the data cell

flyboarder02:07:53

for your data you can just use a let

mudphone02:07:55

@flyboarder oh man, thanks, yeah, ha

mudphone02:07:28

@flyboarder and the websocket data load call will only happen once? when the element is loaded?

flyboarder02:07:49

correct, a defelem constructs and returns an element

flyboarder02:07:07

so later you “call” the returned element

flyboarder02:07:27

hoplon extends the IFn protocol so elements can be used as functions

mudphone02:07:08

@flyboarder ok, thank you. So, my websocket call is made when I call the element fn that I’ve created.

mudphone03:07:19

One more question. I wasn’t able to get :load to work as an on! attribute on a div. I thought it would work, since there’s a .load() jQuery event handler. I tested it by having an anon fn console.log. Should that not work?

mudphone03:07:51

Like with (div :load #(.log js/console "testing") "etc")

flyboarder03:07:56

@mudphone no, the browser never loads any hoplon elements, it loads a script tag and everything happens from javascript

mudphone04:07:38

flyboarder: ah, okay, that makes sense

flyboarder04:07:01

yeah no problem 🙂

thedavidmeister04:07:59

@flyboarder so is there a solution for that? maybe the caching could be smarter somehow?

flyboarder04:07:16

@thedavidmeister i think the caching should be smarter, but I dont think it really can be, since it’s based on the cell

thedavidmeister04:07:33

so why did what i did help?

thedavidmeister04:07:39

did i just bypass the cache somehow?

flyboarder04:07:52

yep, your forcing a new element

flyboarder04:07:47

which although working, would result in a memory leak if you infinitely change the inputs

flyboarder04:07:05

thats just a theory tho, you can profile your app and see what happens to confirm

onetom04:07:48

@flyboarder hi! i saw u were talking about using lens. haven't read it in detail but do u have any solution for editing vectors?

onetom04:07:43

something like a for-tpl but where the (potentially destructured) loop vars are actually lens into the original sequence

onetom04:07:19

@flyboarder also $ git push --tags pls. the latest tag in git is 7.0.0

thedavidmeister04:07:56

@flyboarder but i'm not creating a new element? i'm just adding a new watch to an existing cell

onetom04:07:47

@thedavidmeister ah, i just saw u r trying to do the very same thing as i am. i remember i solved this in the past but can't find the code now. but it was not very self-contained / reusable anyway, so probably it worth reconstructing it again anyway

flyboarder04:07:27

@thedavidmeister right, which I believe is forcing the -tpl to detect a new value and create a new instance

thedavidmeister04:07:51

is there some way to measure/detect that?

flyboarder04:07:20

@thedavidmeister you can profile your page and watch the memory as elements are changed with the inputs

flyboarder04:07:59

I think this can be fixed by adjusting the approach, instead of tracking the values, we can just track the index

onetom05:07:05

@thedavidmeister you are losing the ordering because of the (into {} ...) transformation

thedavidmeister05:07:13

@onetom no, the order in the data structure is correct, it's the order in the DOM that is wrong

thedavidmeister05:07:52

well, maybe in the example above something weird might be going on, but there's another test i wrote

thedavidmeister05:07:08

@flyboarder if what you're saying is true, then why does :data-k have the right value in it in my example?

thedavidmeister05:07:34

if the problem is in for-tpl then i expect read only attributes to break in the same way as read/write attributes like value

thedavidmeister05:07:04

if i do (for-tpl [[k v] xs] ...) then i expect k and v to behave identically inside the for-tpl

flyboarder05:07:20

@thedavidmeister let me try it with my click version instead and see if this is specific to the input elements

onetom05:07:50

@thedavidmeister it will get mixed up eventually, eg:

(prn (vals (assoc (into {} (map-indexed vector [0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39])) 0 "X")))
("X" 32 1 33 2 34 3 35 4 36 5 37 6 38 7 39 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31)

thedavidmeister05:07:29

my example has a sort by with no into

thedavidmeister05:07:11

@flyboarder actually, scratch that, ks should behave because there are no duplicates

thedavidmeister05:07:37

what would be interesting is to see whether v on a data attribute gives the same results or something different

onetom05:07:11

@thedavidmeister ok, i was talking about the example u pasted above. in that the change handler is modifying the rdata

thedavidmeister05:07:34

i think that's a fair point for that example, but probably doesn't change much overall here 😕

thedavidmeister05:07:54

i gotta duck out and do some errands in a sec

thedavidmeister05:07:05

but i'll try to test v on other attributes later on

thedavidmeister05:07:37

then we can see whether the bug lives in the choice of attribute or the template itself 🙂

flyboarder06:07:07

things to note, this only happens when you modify the first value and it has a matching value later in the list,

flyboarder06:07:38

the cell value is not actually changing because the new location has been modified instead

flyboarder06:07:19

does not seem to be specific to the value attribute or input elements

flyboarder06:07:38

correction! does seem to be limited to input elements, but only when it’t the first element that matches another

flyboarder06:07:10

using my click version I can change the first element to “c” via a click and they order and update correctly

flyboarder06:07:13

@onetom what is your take on this?

dm307:07:19

I think this explanation by Micha still gives the best overview of the situation: https://clojurians-log.clojureverse.org/hoplon/2016-05-08.html#inst-2016-05-08T18:33:49.002601Z

flyboarder07:07:06

@dm3 Yeah it seems strange that it’s limited to the one case of input

dm307:07:13

it’s not just input

dm307:07:25

anywhere where the browser puts its own value into the attribute

dm307:07:37

which is also bound to a cell value

dm307:07:05

hence 2-way databinding

dm307:07:20

it just doesn’t work

dm307:07:06

that’s why David’s solution is closer to a generic one (memory leaks aside) - it makes 2-way binding into 1-way binding

flyboarder07:07:04

This also is only applicable when you want to use hoplon cell data as the element order

thedavidmeister07:07:54

we don't necessarily know that yet @flyboarder

thedavidmeister07:07:10

it's just the best reproducible scenario we have atm

thedavidmeister07:07:19

i just happened to stumble across this one 😛

onetom08:07:22

im expecting to be able to write code like this:

(j/defc users [{:user/name "joe"
                :user/email "[email protected]"}])

(defelem ordered-inputs [attr kids]
  (h/div
    (vec-tpl [[index {:keys [user/name
                             user/email]
                      :as   item}] users]
             (h/input :value name
                      :change #(reset! name @%))
             (h/input :value email
                      :change #(reset! email @%)))))

onetom08:07:53

i would think it's a fairly common use-case. i would describe it as: edit an ordered list of items

flyboarder08:07:13

@onetom how would you order by input value with your example?

onetom08:07:42

i supplying the values in a vector. that's the order i would expect to maintain

onetom08:07:28

in reality the data would be more like:

[{:entity/id #uuid "xxxx-yyy-...-1234"
  :entity/index 0
  :user/name "joe"
  :user/email "[email protected]"}

 {:entity/id #uuid "xxxx-yyy-...-5678"
  :entity/index 1
  :user/name "joe"
  :user/email "[email protected]"}]

onetom08:07:04

on the backend. but for the frontend i would just supply the order in a vector

flyboarder08:07:09

right so our edge case is when you change that order on the client

flyboarder08:07:21

via the value of the data

dm308:07:12

I think you can come up with more situations where there’s 2-way databinding and the expected behaviour breaks. Not just order

dm308:07:18

although I won’t try that now 🙂

flyboarder08:07:09

@dm3 I find this strange because it only happens to the first input element in the example, if you modify the second one instead the correct behaviour happens

onetom08:07:36

oh, im not talking about the bug you observed. i was just elaborating on my earlier question whether such a vec-tpl exists yet or not which provides me with lenses

flyboarder08:07:50

oh sorry my bad

flyboarder08:07:40

Im not sure what you mean in your lense question

flyboarder08:07:26

I want to say no, the values only exist as cells currently

onetom08:07:56

i would expect name and email to be lenses for convenience which know where to insert themselves back into the original vector

flyboarder08:07:27

yeah I dont think so, but you may be able to make up a -tpl macro that does that

flyboarder08:07:40

@dm3 @thedavidmeister so if you use a click event to change the cell value instead of the change event it works correctly

dm308:07:25

that’s because there’s no more 2-way binding, right? click -> reset value cell -> do! input

flyboarder08:07:05

right but the order changes, seems strange the browser only updates the first elements value if you typed into one of the elements

flyboarder08:07:21

not if the change happened from a click or other event

flyboarder08:07:43

so the browser is migrating state from one element to another, but only under certain cases

dm308:07:23

it’s not really migrating state

dm308:07:42

the only thing reordering DOM elements is the for-tpl

flyboarder08:07:20

with the example above any click event makes everything work correctly, you can also change alpha at index 1 to omega and it works, however if you do the same at index 0 the bug will show

flyboarder08:07:10

you can also see what the cell has as a value and what the input box shows

thedavidmeister10:07:10

@flyboarder yes, because one of the conditions for the bug is changing index 0

thedavidmeister10:07:21

it has to "jump" over an element with the same value as it

thedavidmeister10:07:43

because the element that it "jumps" shifts up one position to take its place, but because the value is the same the do-watch to push the value into the DOM doesn't fire

thedavidmeister10:07:25

have to be very careful about start state and the exact user interactions in order to test properly

thedavidmeister11:07:55

@flyboarder here's an update from me

thedavidmeister11:07:01

(deftest ??sorting-elements
 (async done
  (let [data (j/cell {:a 1 :b 1 :c 2})
        sorted-data (j/cell= (sort-by (fn [[k v]] [v k]) data))
        el (h/div
            (h/for-tpl [[k v] sorted-data]
             (h/input
              :data-k k
              :data-v v
              :value v
              :click #(swap! data assoc @k (int @%)))))
        read-vals (fn [el]
                   (map
                    #(-> % js/jQuery .val)
                    (-> el js/jQuery (.find "input") array-seq)))
        read-ks (fn [el k]
                 (map
                  #(-> % js/jQuery (.attr k))
                  (-> el js/jQuery (.find "input") array-seq)))]
   (-> js/document .-body (.appendChild el))

   (h/with-dom el
    (is (= ["1" "1" "2"] (read-vals el)))
    (is (= [":a" ":b" ":c"] (read-ks el "data-k")))

    (-> el js/jQuery (.find "input") .first (.val 3) (.trigger "click"))
    (is (= {:a 3 :b 1 :c 2} @data)) ; passes
    (is (= [":b" ":c" ":a"] (read-ks el "data-k"))) ; passes
    (is (= ["1" "2" "3"] (read-ks el "data-v"))) ; passes
    (is (= ["1" "2" "3"] (read-vals el))) ; fails with ["3" "2" "3"]!
    (done)))))

thedavidmeister11:07:50

putting v on data-v passes, so it's really only the :value attribute that has the bug, which supports the 2-way data idea

thedavidmeister11:07:17

i moved to the click event but it hasn't helped my test

flyboarder16:07:24

@thedavidmeister I wonder if using mutation observers would catch this

thedavidmeister16:07:02

@flyboarder potentially, if you can put together an example i can run the tests

Ruben W19:07:41

Hi, I’m trying to get started with Hoplon using a http-kit server + compojure, and I can’t figure out to which resource folder the generated html/js is compiled to, or how I can set to which folder it should compile.. Anyone who could give me a hint in the right direction?

jjttjj19:07:55

@freakinruben I believe you just have to use the target boot task, ie have (target :dir #{"resources/public"}) at some point in your task chain

Ruben W19:07:44

@jjttjj oh wow, that sounds almost to simple to be true.. @dm3 yeah, but none of the examples seem to explicitly state the resource path 😉

flyboarder19:07:43

@freakinruben target is the default folder

flyboarder19:07:59

for the target task

Ruben W19:07:53

alright 🙂 tnx! Trying it now with (target...) set

jjttjj19:07:09

@freakinruben yeah there might be a reason to keep the hoplon files separate from the rest of the public resources, and then serving that up with compojure, instead of just dumping them all in the same spot as your static image files etc

jjttjj19:07:52

but if you put that target line just before the serve task in your workflow it should work

Ruben W20:07:52

tnx, setting the target works 🙂

raywillig20:07:03

hi all, i've recently converted over my project from .hl files to .cljs which is pretty awesome, but now that the hoplon task is essentially unnecessary, I've lost the bust-cache functionality. How are other people handling this? do i need to provide something to cljs task in order to get this back?

Ruben W21:07:40

hm I’m still struggling with this output folder. I now have the following folders:

- assets/ ; static css files
- resources/ ; resource files for server app
in build.boot I have:
(set-env!
...
  :resource-paths #{"resources"}
  :asset-paths  #{"assets"}
...
)

(deftask run-clj-dev [] ....)

(deftask run-cljs-dev []
  (comp
    (hoplon)
    (reload)
    (cljs-repl :nrepl-opts {:port 9009})
    (cljs :optimizations :none :source-map true)
    (target :dir #{"resources/public"})))

(deftask run-dev []
  (comp
    (watch :verbose true :include #{#"\.(cljs|cljc|clj|hl|less)$"})
    (run-clj-dev)
    (run-cljs-dev)))
The problem now is that the files in resources/ are also copied to resources/public, making the non-public resources, public. If I don’t set :resource-paths that is fixed, but then my app can’t access the resources (of course). Second problem is that the script seems to get into an infinite loop copying the resources/ folder (after the generated resources/public have been added) to resources/public, causing watch to compile cljs again and copying the resources/ folder etc.. Changing the output-dir in (target ) will make my generated resources not available to serve with compojure.. 😯 So, what I would hope to accomplish is: - have resources available to clojure-app - make generated cljs output serveable through compojure - make static css files serveable through compojure

dm306:07:18

freakinruben: if you goal is to just run the application - it seems you have a misunderstanding about the way Boot works. You don’t have to actually write the assets into the resource dir. They will be on the classpath. Boot manages that for you via filesets (https://github.com/boot-clj/boot/wiki/Filesets). For most purposes in Boot you shouldn’t need the target task.

Ruben W07:07:31

@dm3, I was slowly figuring that out (coming from lein).. How would you recommend running an API (with reload), a systems map, and some cljs compilation and serving? Most examples are using (serve), but it seems like an overkill to run 2 http-servers when developing + I can’t test the entire stack

dm307:07:44

it should work the same way you do it in Lein. For example, I have a dev.clj on the classpath which has start/stop/reset functions and uses tools.namespace to reload the application.

dm307:07:25

be aware of a few gotchas with reloading due to the way Boot works: https://github.com/boot-clj/boot/wiki/Repl-reloading

dm307:07:21

the only difference is that you don’t see the cljs compilation output in the target dir

dm307:07:37

it’s in another dir in ~/.boot/cache managed for you by Boot

Ruben W07:07:01

sure, but the tmp-folder with clj-output isn’t a resource or anything.

Ruben W07:07:44

so I can’t serve it with my existing http-kit instance?

dm307:07:44

what do you mean by clj-output? Your clojure files will be on the classpath if you register them as sources/resources with Boot

dm307:07:55

cljs output will be on the classpath too

dm307:07:49

hmm, am I wrong though? 🙂

dm307:07:03

haven’t done web cljs in a while

dm307:07:51

I think this one is quite comprehensive: https://github.com/Deraen/saapas

dm307:07:57

I was wrong - the Cljs output will be in the fileset, but it seems people write it out via target to have an easier time serving from within the app

Ruben W08:07:49

clj-output should have been cljs-output 😉 the saapas example is quite nice! It looks like it uses (sift to prevent existing resources to be written to the (target folder

dm308:07:48

I’ve opened my last project and the dev task I have didn’t have a target

dm308:07:01

so it was all served from the fileset

dm308:07:51

I use Mount

dm308:07:14

and I didn’t use boot like that

Ruben W08:07:02

tnx for helping me

Ruben W08:07:25

I’ll post an update here when I figure it out

flyboarder22:07:37

@raywillig it is still useful and needed to generating hoplon html pages

flyboarder22:07:53

unless you are handling the html page another way

flyboarder22:07:18

I still use it for the :hoplon/page metadata

raywillig22:07:37

right I'm doing this (ns ^{:hoplon/page "index.html"} pages.index

raywillig22:07:02

but i'm wondering how to get the cache busting functionality to work now @flyboarder

flyboarder22:07:46

youll still need the hoplon task for using that metadata

raywillig22:07:24

right. I'm using the hoplon task, but i'm not getting the cache busting anymore

flyboarder22:07:49

oh sorry, im tracking

raywillig22:07:33

oh actually i do still get cache busting for my main app. it just seems to not work when i compile a regular html page with some hoplon mounted to it ala https://github.com/alandipert/embedded-hoplon-example

raywillig22:07:20

which i guess sorta makes sense since i'm not compiling an html page

flyboarder22:07:53

is the mountpoint required?

flyboarder23:07:16

⚠️ [degree9/material-hl "0.10.0"] ⚠️ New Release: this version implements almost all of the material components, although documentation is still lacking….

flyboarder23:07:49

and its a few versions behind MWC master