Fork me on GitHub
#hoplon
<
2016-03-23
>
levitanong02:03:02

@micha "the cond-tpl macro that's going to be merged seems like it would be ideal for that use case” Whoa, someone’s working on cond-tpl? I was just about to work on it, but will give way if someone else is on it. simple_smile

thedavidmeister08:03:08

Sooo, something that I faced, was a third party script that was heavy

thedavidmeister08:03:17

so I only wanted to load it into the page after an event had triggered

thedavidmeister08:03:26

so I did event-triggered? in a cell

thedavidmeister08:03:44

then (cell= (if event-triggered? (script …)))

thedavidmeister08:03:15

which seemed to work, but my problem is the performance issues i see haven’t lined up with my understanding of “building a complex tree"

thedavidmeister08:03:06

as in, I’d build the DOM, the DOM looked like it had finished building (which I would have expected to be all the computation required) but then all my events and functions from that point onwards run waaaay slower

thedavidmeister08:03:21

so it would be a permanent slowdown of the whole site

thedavidmeister08:03:43

it’s also sporadic and hard to reproduce

thedavidmeister08:03:06

I think it may have gotten better when i moved from Hoplon alpha10 to alpha13, but I also might be imagining that >.<

thedavidmeister08:03:24

I’ll just keep everyone posted as I have info/examples...

dm308:03:15

is there anything noticeable in the profiler?

dm308:03:39

I somehow doubt that the same functions will take slower after the DOM loaded than before

thedavidmeister08:03:55

that’s what was happening

thedavidmeister08:03:00

like, so obvious...

dm308:03:01

unless your database grows by a lot

thedavidmeister08:03:04

like the app nearly crashed

thedavidmeister08:03:15

i had one tab open running master

thedavidmeister08:03:18

and one running my branch

thedavidmeister08:03:28

master had more data in it and was silky fast

thedavidmeister08:03:44

the other branch had very little data and was sluggish

dm308:03:45

and what's in your branch?

thedavidmeister08:03:11

the only thing i changed was creating a (cell= (if foo bar baz))

thedavidmeister08:03:19

at first i thought it was my routing implementation

thedavidmeister08:03:27

but i set it back to default hoplon route-cell

thedavidmeister08:03:07

i have an event triggering on :input on form inputs

thedavidmeister08:03:15

if i mashed the keys, i’d see multiple second delays

thedavidmeister08:03:27

buuuut i threw it all away and went with :toggle

thedavidmeister08:03:37

so i’ll just keep an eye on things, and if i see it again, i’ll flag it simple_smile

thedavidmeister08:03:11

for attributes on elements

thedavidmeister08:03:19

are on! and do! the only options?

dm308:03:18

so it's not the (cell= (if event? (script...))) that slowed things down? I'm confused

dm308:03:29

what other options do you need? simple_smile

thedavidmeister08:03:33

i’m trying to improve the docs

thedavidmeister08:03:43

so i want to know i’ve covered the right stuff

thedavidmeister08:03:42

@dm3: when i started swapping elements in the DOM based on an if in a cell=, i got weird performance

thedavidmeister08:03:48

that’s the best way i can describe it

thedavidmeister08:03:35

i did a tiny bit of profiling and jquery adding events was really high in time

thedavidmeister08:03:20

@dm3: so yes, on! and do! are the only options for attributes?

dm308:03:47

regarding the performance - please read the conversation we had with Micha yesterday

dm308:03:06

also if you have the time to put some of the results to Wiki - would be awesome!

dm308:03:47

I think on and do are the only multimethods available

thedavidmeister08:03:49

but i got some of the details about attributes wrong

thedavidmeister08:03:54

so i want to update that

thedavidmeister08:03:40

and any function values will go to on! and all other values go to do! yeah?

dm308:03:11

so do! is when you want to perform some action on the element

dm308:03:19

like :toggle

dm308:03:36

and on! is when you want something to happen when a certain event is triggered

thedavidmeister08:03:37

or just create an attribute

thedavidmeister08:03:53

i think the bit that i was missing was that do! also handles adding HTML attributes

dm308:03:10

yes, by default

dm308:03:18

and on! attaches an event listener by default

thedavidmeister08:03:28

i’m going to actually put the things implemented by do! that are not the default attribute creation in the wiki

thedavidmeister08:03:48

because, as cool as hoplon is, it’s not fun poking around in core to find this stuff

dm308:03:03

not sure how useful on! being a multimethod is

thedavidmeister08:03:44

custom events probably

thedavidmeister08:03:47

something jquery supports

thedavidmeister08:03:01

but it’s probably not useful for simple apps or hoplon core

dm308:03:03

it will do that by default

dm308:03:20

(.on elem event-key callback)

thedavidmeister08:03:29

as in, you might want a custom event that does something to all elements

thedavidmeister08:03:41

without you needing to stick your own handler on everything

thedavidmeister08:03:06

i don’t see any harm in it, and there would be some edge cases where it could help

thedavidmeister08:03:09

even if just for debugging

dm308:03:13

(defmethod on! :my-event [elem event callback]
     ;; register your callback in some specific way?)

thedavidmeister08:03:06

(defmethod on! :my-event [elem event callback]
     (prn elem))

thedavidmeister08:03:37

the event handler version of (cell= (prn my-cell))?

dm308:03:50

but this is not the handler

dm308:03:12

you call on! when you register a callback

dm308:03:33

so like wrapping a callback into something else before attaching

thedavidmeister08:03:07

if I do your code above, for a :click event

thedavidmeister08:03:26

does it run when the thing is clicked, or when the click event is bound to the element?

dm308:03:56

when bound

dm308:03:09

but you can still do

(defmethod on! :my-event [e ev cb]
   (.on (js/jQuery e) ev (fn [e] (prn e) (cb e))))

thedavidmeister08:03:32

that doesn’t seem that useful

dm309:03:04

but then again - you can do that in a different place

dm309:03:15

so I don't see how on! being a multimethod is very useful

thedavidmeister09:03:31

but i didn’t write it

thedavidmeister09:03:38

i won’t pretend to know anything about it 😛

dm309:03:10

well, Hoplon is designed from the point of providing small and general abstractions for creating a DSL for writing your application

levitanong09:03:15

on! is useful if you don’t want to use the default jquery stuff.

dm309:03:43

yeah, it allows to plug in into the (input :click #(...)) mechanism

dm309:03:49

if you want to bail on jQuery

levitanong09:03:14

it also helps you define some custom stuff. For example, for my own app, i define :drag

dm309:03:43

hm, haven't thought of that simple_smile I also have draggable stuff

levitanong09:03:47

clojure
(defmethod on! :drag
  [elem event callback]
  (let [mousedown? (atom false)
        down-ev (atom nil)]

    (when-dom elem
      (fn []
        (.mousedown (js/jQuery elem)
                    (fn [ev]
                      (reset! mousedown? true)))
        (.mouseup (js/jQuery elem)
                  (fn [ev]
                    (reset! mousedown? false)))
        #_(.mouseout (js/jQuery elem)
                     (fn [ev]
                       (reset! mousedown? false)
                       (reset! down-ev nil)))
        (.mousemove (js/jQuery elem)
                    (fn [ev]
                      (if @mousedown?
                        (callback ev))))))))

thedavidmeister09:03:50

oh yeah, i need to do drag and drop re-ordering of form inputs at some point

thedavidmeister09:03:54

i’ll need a hand with that >.<

levitanong09:03:21

well there you have it 😄

thedavidmeister09:03:54

i will definitely forget that

levitanong09:03:06

I’ll make a post in the cookbook for you simple_smile

thedavidmeister09:03:57

just writing up ^^

thedavidmeister09:03:33

Any **attributes** that have function values will be handled by the `on!` multimethod. Hoplon core only implements the default case for the multimethod, which will simply bind the passed function to the jQuery event with the same name as the attribute key.

When the event triggers the bound event, the first argument will be the jQuery event. Hoplon implements `IDeref` for the jQuery event to call jQuery's own `.val` on the event's original target element.

As an example, we can use `on!` to respond to the user clicking (triggering the `:click` event) on an `input` by alerting the current text value of the input:

`(input :type "text" :click #(js/alert @%))`

Even though `on!` is provided only with a default case, the multimethod exists so that custom function binding outside of what jQuery provides can be handled in a standard way.

thedavidmeister09:03:38

does that look right?

levitanong09:03:12

As far as i know, input is a special case for the IDeref. Can anyone confirm this?

levitanong09:03:59

Or is it that it’s always IDeref, and only a few elements like input actually have anything useful to put in there?

levitanong09:03:33

In which case, your writeup looks good, @thedavidmeister!

thedavidmeister09:03:59

(extend-type js/jQuery.Event
  cljs.core/IDeref
  (-deref [this] (-> this .-target js/jQuery .val)))

thedavidmeister09:03:05

could someone walk me through this one?

thedavidmeister09:03:08

(defmethod do! :class
  [elem _ kvs]
  (let [elem  (js/jQuery elem)
        ->map #(zipmap % (repeat true))
        clmap (if (map? kvs)
                kvs
                (->map (if (string? kvs) (.split kvs #"\s+") (seq kvs))))]
    (doseq [[c p?] clmap] (.toggleClass elem (name c) (boolean p?)))))

thedavidmeister09:03:36

does it just take a map of classes and converts to a string?

thedavidmeister09:03:40

or is it more than that?

thedavidmeister09:03:48

well, not converts to a string

thedavidmeister09:03:55

ensures that those classes are on the element

thedavidmeister09:03:06

and if a string is passed, also handles that?

thedavidmeister09:03:28

so you need to do

thedavidmeister09:03:05

:class {:my-class true :another-class true}?

dm309:03:54

it doesn't convert to a string

dm309:03:15

but rather converts a string to a map

thedavidmeister09:03:35

so I can do :class “my-class another-class”

thedavidmeister09:03:39

and have both of those appear

thedavidmeister09:03:03

:class {:my-class true :another-class true :not-this-class false}

thedavidmeister09:03:11

to make sure that third class is removed if present?

dm309:03:25

but, if you have (input :class (cell= "a b") and then cell value changes to (input :class (cell= "a")), the "b" class won't disappear

dm309:03:49

as the code doesn't go through all classes present on the element

thedavidmeister09:03:02

unless i specifically set it to false

dm309:03:13

yes, which is impossible in the string representation

dm309:03:44

so I guess the rule is - only use strings for static classes

thedavidmeister09:03:47

:class {:add-me :and-me}

dm309:03:04

I mean it does

thedavidmeister09:03:09

have we considered a generic :trigger for do!?

dm309:03:09

but won't do what you would expect

dm309:03:41

what is a generic trigger?

thedavidmeister09:03:34

(div :trigger :click)

dm309:03:28

what would that do?

thedavidmeister09:03:42

trigger a click event on div

thedavidmeister09:03:05

(div :trigger :select) = (div :select true)

dm309:03:49

I think this is useless 😄

thedavidmeister09:03:05

which allows (div :trigger :select :select #(js/alert “selected!”))

thedavidmeister09:03:25

otherwise how do you facilitate triggering and handling events on one element?

dm309:03:25

so this would happen when the element is created?

dm309:03:35

or when it's attached to the DOM?

thedavidmeister09:03:45

no, with a (cell= ) controlling the timing/conditions, of course

dm309:03:46

I still don't understand - how would you do this without :trigger?

thedavidmeister09:03:12

i worked around this like

thedavidmeister09:03:15

:select (cell= (and (= list-id select-list-id) (= id select-id)))
          :focusin #(in/clear-select! in/conn)

thedavidmeister09:03:25

you can probably guess what those functions do

thedavidmeister09:03:37

but not everything has a counterpart with a different name

dm309:03:15

and how would that look with trigger?

dm309:03:25

sorry 😞 I still don't understand

thedavidmeister10:03:42

:trigger {:select (cell= (and (= list-id select-list-id) (= id select-id)))} :select #(in/clear-select! in/conn)

thedavidmeister10:03:47

it’s ugly, but I don’t know a better way to trigger and also bind to an event?

dm310:03:53

should not :select be :selected in this case?

thedavidmeister10:03:07

OK, but what about :click?

dm310:03:49

I guess you have different types of attributes: 1. properties, like selected or class where you might want a dynamic value 2. actions, like click, which you want to trigger at certain times using do! 3. event handlers, like click (really on-click), which you want to attach to an element during constructions

thedavidmeister10:03:12

it’s just weird because you can have name conflicts

dm310:03:43

I understand you propose attaching triggers for 2. through :trigger

dm310:03:08

I think it's better served by doing something like (cell= (do! elem :click ...))

dm310:03:44

that's because it does something

dm310:03:51

not just changes value of a property

dm310:03:04

so you want to be explicit

thedavidmeister10:03:16

where do I put that?

dm310:03:46

(with-let [elem (input ...)]
    (cell= (...)))

dm310:03:48

from my experience triggering usually happens in other places in code though

thedavidmeister10:03:30

i don’t think i’ve used with-let yet

dm310:03:21

it's a shortcut for

(let [a x] ... a)

thedavidmeister10:03:54

@dm3: would you mind reading over the attributes section here? https://github.com/hoplon/hoplon/wiki/API-Documentation

dm310:03:05

ok, a bit later

raywillig16:03:57

is it possible to redefine the behavior of (a :href ....) with a do method to automatically perform some action like say, posting an event to GA before performing the default behavior?

levitanong16:03:00

@raywillig: I do believe that is the case.

raywillig16:03:52

my question was a cleverly disguised passive aggressive plea for help in doing it simple_smile

levitanong16:03:10

(defmethod do! :href
  [elem event callback]
  (.preventDefault event)
  ;; post to GA
  ;; execute default behavior)

levitanong16:03:31

haha i was already typing the above after my first message 😛

raywillig16:03:33

very interesting. now suppose i wanted :target "_blank" even if the stupid coder (me) left that out of the function

levitanong16:03:56

(defmethod do! :href
  [elem key val]
  (.preventDefault event)
  ;; post to GA
  (.open js/window val))

levitanong16:03:51

my first code block is a confused one 😛 it’s conflating on! and do!

levitanong16:03:55

and it’s leaking into my second one

levitanong16:03:05

there should be no “event” necessary

levitanong16:03:12

so really, all you’d need to do is:

raywillig16:03:17

i was wondering about that

levitanong16:03:20

(defmethod do! :href
  [elem key val]
  ;; post to GA
  (.open js/window val))

raywillig16:03:25

should this be defined in index.cljs.hl or can be defined anywhere?

levitanong16:03:48

it just has to be called before you render it

levitanong16:03:58

so i guess you can just put it in a util

levitanong16:03:04

and then require it in your index

levitanong16:03:14

but i’d advise you to put it in your index first just to see

raywillig16:03:19

sweet. this is a game changer for me

levitanong16:03:20

i could be forgetting some stuff

raywillig16:03:46

is it possible for the do method to access other attributes defined on the same element?

levitanong16:03:18

afaik, no. what usage do you have in mind?

levitanong16:03:22

maybe there’s another way

raywillig16:03:35

i think i may have thought of another way. since i'm overriding the behavior of href, I could pass a more complex structure with whatever addition data i need right?

levitanong16:03:42

maybe you’d be able to use the usual js attr accessing

levitanong16:03:47

because you have elem

levitanong16:03:06

and yes, you could expect that href takes a hashmap

raywillig16:03:18

so i could have something like (a :href (cell= {:url url :extra data}))

levitanong16:03:42

(defmethod do! :href
  [elem key val]
  ;; post to GA
  (do-something {:extra val})
  (.open js/window {:url val}))

levitanong16:03:57

you might have to deref val

raywillig16:03:21

i just realized tho that this may clobber other things that use href like css stuff

raywillig16:03:43

is there a way to determine if it's an a and otherwise just proceed as usual

levitanong16:03:02

i think you should be able to do:

(defmethod do! :href
  [elem key val]
  ;; post to GA
  (let [attrval (.attr (js/jQuery elem) “extra”)]
    (do-something-to attrval))
  (.open js/window val))

levitanong16:03:19

this tries to get the value of :extra if it’s defined

raywillig16:03:07

will this code open the window as soon as the thing is rendered or wait until the link is clicked?

levitanong16:03:38

as for determining the tag, you can do this: (.prop (js/jQuery elem) “tagName”)

levitanong16:03:44

and test that against “a"

levitanong16:03:05

rather, against “A"

levitanong16:03:20

it’ll wait until it is clicked

levitanong16:03:35

i’m not sure 😛

raywillig16:03:55

href seems weird cuz it's sort of an attribute and an event handler

levitanong16:03:55

something you can do is defined its :click

levitanong16:03:04

and inside the :click, (.preventDefault ev)

levitanong16:03:13

and then do your GA and new window stuff

levitanong16:03:18

and leave href alone

raywillig16:03:44

but then i have to make sure to include a click handler in every a in the app?

levitanong16:03:16

you can extend click 😛

levitanong16:03:20

(defmethod on! :click [elem event callback]
  …
  (let [tagname (.prop (js/jQuery elem) "tagName”)]
    (when (= tagname “A”)
      (.preventDefault event)
      (.open js/window (.attr (js/jQuery elem) “href”))))
  

levitanong16:03:25

and then copy paste the rest of the original :click into a when-not

raywillig16:03:12

hmm this seems viable, so even if there's no :click attribute there's an implicit click handler. for most things i guess it does nothing but a's are different obv

levitanong16:03:50

You have such an interesting problem. Hahaha

levitanong16:03:07

I want to explore extending :href for some reason

levitanong16:03:27

I think extending :click is crazy

levitanong16:03:45

You just went the old fashioned way?

raywillig16:03:50

what did u mean when you wrote copy/paste rest of original click?

levitanong16:03:56

Whatever it would have done if it wasn't an A

levitanong16:03:50

Anyway, a sure fire way would be if you did: (.click (js/jQuery "a") (fn [] ...))

levitanong16:03:11

In other words, you can skip the fancy extension stuff

levitanong16:03:42

And simply target all the a's, add a click handler, and add your preventDefault stuff

levitanong16:03:11

Another thing you can do is create a custom "a" element that already has the :click predefined

levitanong16:03:14

This is even cleaner

levitanong16:03:33

So it wraps around the default "a"

raywillig16:03:55

I do some stuff that adds stuff to the dom on the fly. those wouldn't get handled by the jquery thing if they're added to the dom after the call right?

levitanong16:03:14

Yeah, those wouldn't.

levitanong16:03:26

I think your best bet is to create a custom anchor element.

levitanong16:03:39

(defelem a-ga [{:keys [href] :as attrs} kids]
...)

raywillig16:03:50

oh i think you hit it on the head

raywillig16:03:39

yeah, that actually solves a ton of problems

levitanong17:03:19

Hurrah! Happy to help, @raywillig :D

flyboarder18:03:31

does javelin have a way to force propagation for when values dont change?

dm318:03:39

modify propagate* to skip the diff step

dm318:03:47

and -notify-watches unconditionally

roti20:03:12

I noticed hlisp re-renders the UI only when there is a cell bound to an html attributes or content

roti20:03:01

So I need to write (div :style (cell= (if menu-visible "" "display:none"))) instead of (div :style (if @menu-visible "" "display:none"))

roti20:03:31

but how can I manage cases where I want an entire dom subtree to depend on a value?

micha20:03:02

hoplon doesn't "rerender" ever

micha20:03:13

there is no concept of rendering, at all

roti20:03:39

I meant updating the dom

roti20:03:59

I realize there's no top-down re-rendering of the entire page

micha20:03:24

it's very rare to want an entire subtree to be completely arbitrary

micha20:03:46

in general you'll linearize it somehow, and use loop-tpl

roti20:03:21

well, I have a (case my-cell ...) with each branch rendering a different div

roti20:03:07

which does not work, of course

roti20:03:36

(case @my-cell ...) works, but does not get updated when my-cell is changed

micha20:03:50

what is the use case?

micha20:03:01

i could tell you how i'd do it

roti20:03:56

page is composed of a header, menu and content. content depends on menu selection

roti20:03:14

and it's all in a page, so no navigating to another URL

roti20:03:14

I use a big case to render the content simple_smile

micha20:03:22

ok yeah, this is the classic "tabs" pattern

micha20:03:53

i would create the DOM the content for all menu selections, and just don't show the ones that aren't active

micha20:03:02

using the :toggle attribute

micha20:03:21

there are lots of ways to make that nicer than a case statment

roti20:03:39

what's the toggle attibute?

roti20:03:48

part of hlisp?

micha20:03:55

(div :toggle cell-a "hello")

micha20:03:12

that div will be hidden if cell-a doesn't contain a true value

micha20:03:39

you can implement your own attributes using the do! multimethod

roti20:03:04

but, isn't this a little bit heavy? I mean loading everything at the beginning and then just showing and hiding stuff. I already am loading at the beginning some dialogs which I show only later (or may not show at all). didn't you have issues with this?

micha20:03:27

not yet, really

roti20:03:29

if you don't mind me asking

micha20:03:43

i mean you can do some lazy type loading things too

micha20:03:50

it's just overkill usually

micha20:03:11

i mean creating a few dom elements up front usually isn't a problem

micha20:03:45

the reason why you want to statically allocate dom elements like this is because then you are freed from the manual memory management you'd need otherwise

micha20:03:10

like if you're creating and destroying whole subtrees you will need to have a lot of code to manage that

micha20:03:20

like what you see with react apps, with the lifecycle protocols

micha20:03:37

the hoplon idioms leverage static allocation

micha20:03:42

to eliminate all of that

micha20:03:02

but of course you can optimize where you need to

micha20:03:15

you could lazily populate the containers

micha20:03:29

like don't generate the dom until the predicate is true or whatever

micha20:03:56

you could use cljs.core/delay perhaps

micha20:03:10

there is also a if-tpl and cond-tpl macros in a PR that will be merged soon

micha20:03:19

which will do exactly what you want

micha20:03:37

it will handle the laziness and static allocation for you

roti20:03:44

simple_smile I was just about to say I would find something like that usefull

micha20:03:53

but exposing the familiar if or cond API

micha20:03:19

one sec i'll find it

roti20:03:42

now that I got you online, I would also ask how you handle animations simple_smile

roti20:03:11

e.g. menu becomes visible -> slide in effect

micha20:03:38

you can use the jquery animations if you need something simple

micha20:03:57

there are actually some of them built in for common cases like sliding or fading

micha20:03:16

(div :slide-toggle show? "hello")

micha20:03:21

where show? is a cell

micha20:03:13

you can make your own attributes for that pretty easily:

micha20:03:35

that's the built-in :slide-toggle attribute

micha20:03:48

you can totally make your own though that does more sophisticated animations

micha20:03:09

i was very interested in exploring the standalone animation libraries, like velocity.js for example

micha20:03:20

but i have not yet had a chance to really try it

roti20:03:10

thanks, I'll have a look

roti20:03:41

jquery should do fine

micha23:03:06

thanks to everyone for the awesome pull requests, documentation, and general cool things going on!

micha23:03:43

getting ready to do some hoploning this evening