Fork me on GitHub
#hoplon
<
2016-07-24
>
chromalchemy00:07:00

Thanks in advance... and for the continued work.. it's definitely fun to play with.

chromalchemy00:07:06

One thing that's helping me: When I use hlisp in standard Hoplon, if I define an element with (defelem ...) in a namespace in a .cljs.hl page outside of the one with the Page declaration, boot-reload will not auto-refresh when I save the external namespace file. But this is working with hoplon/ui elems , so I am able to enjoy the boot-reload repl-y flow when working in sub files. And this is critical when doing a lot of visual changes to visually inspect things as I go...

chromalchemy00:07:27

Is there any way to overload the styling of the markdown elements? Or does that need to be baked in at the library level?

jumblerg02:07:09

here goes: 1. ids and classes. there isn’t support for these, and i’m opposed to adding it: ids and classes are for use with selectors, which we’re trying to avoid using. however, i’ve had others ask about this as well for debugging, and am instead working on a scheme where the same symbol passed to defelem will correspond to the name of the outer div: eg <hoplon.ui.mycomponent …> or something similar based on hoplon’s existing munging mechanisms.

jumblerg02:07:15

2. css transitions. yes, we’ve introduced a notion of states, and transactions are the next logical step to add on top of them after we work through a few more issues with the way elem states are being handled.

jumblerg02:07:53

3. hidden. this value can also be used to set the visibility attribute. it may also reappear as some part of the api to control scrolling/overflow, which has proven particularly tricky to get right.

jumblerg02:07:32

4. jQuery. no, if you need jQuery for something, then we need to add something to our own api.

jumblerg02:07:43

5. markdown. this is an api that is very much still in the works. the idea is that you will ultimately define your own function for each markdown element via a function (multimethod or map) that you can pass to an element’s :markdown attribute; this will be inherited by all the other elems below it in the display hierarchy.

jumblerg04:07:06

checkbox made from hoplon/ui toggle elem ctor and state fn:

(defelem checkbox [{:keys [label] :as attrs} _]
  (toggle :sh (r 1 1) :g 10 :av :mid :f 18 :fc grey (dissoc attrs :label)
    (elem :s 20 :a :mid :r 5 :b 1 :bc (c 0xCCCCCC) :f 14
      (s :over-on "X" :up-on "X" :down-on "X"))
    (elem label)))

flyboarder20:07:29

@jumblerg: looks good! So my understanding regarding ui is that everything is directly modifying the dom element correct? No external styling

jumblerg20:07:52

yes, ui functions construct and set properties on the dom elements themselves instead of going through selectors

dm321:07:15

how does (s :over-on "X" :up-on "X" :down-on "X") work?

dm321:07:32

is "X" a cell?

micha21:07:10

i don't see the definition for s

micha21:07:04

so (s ...) is a cell

micha21:07:40

which evaluates to "X" when the parent is in the :over-on, :up-on, or :down-on state, or nil otherwise

micha21:07:13

lisp really can do it

micha21:07:44

i don't understand how it works exactly though, i would think that *state* would be unbound when the cell evaluates the formula asynchronously

micha21:07:34

i'd have expected that you'd need to capture its value in the lexical scope with like (let [state *state*] (cell= ...))

jumblerg21:07:22

it depends on a macro that wraps the element in a binding

micha21:07:42

looks equivalent to (cell= (case *state* (:over-on :up-on :down-on) "X" nil))

jumblerg21:07:42

and consequently evaluates when the page is loaded so it will be accessible from the children

micha21:07:02

yeah but like when the cell evaluates the formula later

micha21:07:08

*state* won't be bound anymore

micha21:07:24

it's only bound when you create the cell

micha21:07:31

but there is nothing capturing the value

jumblerg21:07:50

state is also a cell

micha21:07:05

the value of *state* is

micha21:07:16

but when cljs performs the lookup

micha21:07:21

the second time

micha21:07:32

the *state* "var" won't point to a cell anymore

micha21:07:03

*state* is unbound as soon as the binding expression is competeld

micha21:07:42

(def ^:dynamic foo nil)
(defc bar 200)

(binding [*foo* (cell 100)]
  (cell= (pr [*foo* bar])))

(swap! bar inc)

micha21:07:52

you would see [100 200] printed first

micha21:07:00

then [nil 201]

micha21:07:16

because binding macro is basically this:

micha21:07:41

(let [old-foo *foo*]
   (set! *foo* 200)
   (cell= (pr [*foo* bar]))
   (set! *foo* old-foo))

micha21:07:14

that's more or less what the thing above would expand to

micha21:07:49

because it's global mutation you'd need to make that thread-local in a multi-threaded environment

micha21:07:05

but in js there is only one thread so it's just banging on a global js var

jumblerg21:07:52

otg atm, i know that it works

micha21:07:55

hm interesting

micha21:07:15

not sure how that could work

jumblerg21:07:10

i think the formula cell in the s state fn returns closes over the cell value in the dynamic var when the bindings are set up?

micha21:07:23

the dynamic var is just a global var though

micha21:07:29

like a global js variable

micha21:07:37

it can only have one value at a time

micha21:07:59

binding just sets the value, evaluates the body expressions, then resets the value back to what it was before

micha21:07:22

so if the formula is evaluated after the binding expression is done, it will see the current global value

micha21:07:31

which is pretty much random, usually nil

jumblerg21:07:40

changing as the stack grows and shrinks below its threshold as the page is evaluated

micha21:07:47

like imagin 5 such elements with *state*

micha21:07:05

the value is stored in the same physical memory location for all of them

micha21:07:16

because it's the same physical variable

micha21:07:03

if you make a lexical binding to close over it then you have a local variable that won't be reset when the binding expression completes

jumblerg21:07:34

but the cells are binding at the time to set up the dataflow

jumblerg21:07:48

not the values in the cells themselves

micha21:07:09

yeah this is interesting

micha21:07:16

it definitely does work

jumblerg21:07:17

which change when the callbacka fire in response to user inputs

micha21:07:35

i guess because javelin lifts the *state* cell out

micha21:07:43

in a lexical binding

micha21:07:07

very interesting

micha21:07:40

TIL dynamic vars are captured in javelin formulas

micha21:07:43

very handy

jumblerg21:07:12

i remember poking around in the cell implementations themselves and giving myself a headache trying to figure out how to do this

micha21:07:48

i guess the events that update *state* will need lexical bindings

micha21:07:53

for the event callbacks

jumblerg22:07:21

and basically concluding the cell could close over a var

jumblerg22:07:48

see selectable in ui.clsj

micha22:07:02

yeah and interactable

micha22:07:35

it could be possible to make something like clojure's bound-fn in cljs

micha22:07:08

that's what that does

micha22:07:23

but in clojure dynamic vars are special and first-class

micha22:07:31

which they aren't really in cljs

jumblerg22:07:46

right, they’re really a dumb variable with some metadata

jumblerg22:07:53

not a reference type

micha22:07:19

yeah it's a global variable

micha22:07:52

there isn't a way to enumerate them as far as i can tell

jumblerg22:07:01

with a ns prefix for disambiguation

micha22:07:33

i could imagine having a dynamic var that's used internally in the binding macro to keep track of dynamic bindings

jumblerg22:07:43

i think interactible and selectable conflate two ideas, actually, one being the mouse state relative to the component, and the other being the state of the component itself

jumblerg22:07:02

the latter being dependent on the former

jumblerg22:07:15

and should be generalizeable to all components

jumblerg22:07:42

my best idea so far is to have elems, which are stateless, and comps, which have *state*.

jumblerg22:07:30

so anything like the s state fn uses the *state* of the nearest parent component on the display hierarchy.

micha22:07:44

*state* probably should be a map

jumblerg22:07:09

yep, makes sense, will likely need more than a single value

jumblerg22:07:51

but then again, maybe not, *state* is a unique identifier of the component’s state

micha22:07:06

yeah that's true

jumblerg22:07:07

a component can only have one *state*

micha22:07:15

makig a map you end up with a coconut namespace

jumblerg22:07:23

a name for its position on the state machine

micha22:07:33

you'd have different dynavars for different pieces of state

jumblerg22:07:42

and the thing that the state fn responds to

micha22:07:50

rather than lookup magical keys that everyone needs to agree on

jumblerg22:07:58

so you can declaratively specify any attribute or child based on a component’s state

micha22:07:22

there will be many different types of state of course

micha22:07:38

with pieces of program that manipulate and respond to them

micha22:07:52

that don't necessarily know anything about each other

jumblerg22:07:57

so while the state identifiers might be component specific (albeit general in many cases), the state fn is not, and works everywhere

micha22:07:01

if you throw it all in a map you lose that

jumblerg22:07:34

look at the states in the skins here

jumblerg22:07:53

search s:states

jumblerg22:07:07

nice and declarative

jumblerg22:07:23

you can do this for any component

jumblerg22:07:53

so this is the approach i’m trying to generalize my way to

micha22:07:54

that kind of state machine specification only works for very simple machines though

micha22:07:27

you end up with just a protocol

micha22:07:30

essentially

micha22:07:59

a map of state_name --> callback_fn

micha22:07:57

i guess that's all you need for making skins?

jumblerg22:07:03

it covers a lot of cases. many components are binary, toggleable, ie on or off, toggle buttons, radio buttons, check boxes, dropdowns, othermenus that open and close

micha22:07:06

it never gets more complicated than that?

jumblerg22:07:23

i haven’t thought of any cases that can’t be enumerated that way

jumblerg22:07:30

i mean, take the path of a webapp

jumblerg22:07:05

i represent every possible state of my hoplon app with a single keyword already that appears in the path

jumblerg22:07:18

that gets mapped to a view

micha22:07:37

every entry point, you mean

jumblerg22:07:57

where i can see this maybe becoming less convenient is in the case of nested components

micha22:07:00

there are many states that are anonymous

jumblerg22:07:16

where a child components with its own state wants the state of the parent

jumblerg22:07:30

but maybe this violates some kind of encapsulation anyway

jumblerg22:07:44

probably bad practice

jumblerg22:07:07

violates demeter’s law on the stack as applied to dynamic vars

micha22:07:07

statecharts model that kind of thing, the hierarchical state machine stuff

jumblerg22:07:47

anyway, i think it would be cool to get this working more generally as a start

jumblerg22:07:09

then make it more complicated later on if there are practical cases for it

micha22:07:39

yeah that sounds pretty sane

jumblerg22:07:56

i know it worked nicely with adobe’s stuff back in the day

jumblerg22:07:36

adobe’s views and components were more featureful hlisp, basically 🙂

jumblerg22:07:16

once states are properly generalized

jumblerg22:07:20

next come transitions

jumblerg22:07:07

which are functions serving as the edges between the states

jumblerg22:07:50

(and not-so-good shittons of boilerplate)

jumblerg22:07:12

i find it interesting to think about the way we’re constructing document object models; which are, in effect, using the same techniques often used to create immutable models in oop: you get to construct the object once by passing values to the constructor, and after that, everything is read-only.

jumblerg22:07:26

from the developer’s perspective, we’re constructing immutable elements

micha22:07:56

they change over time

jumblerg22:07:39

right, but in two ways that are “implementation details”, much like the internal implementation details of stm.

jumblerg22:07:02

a. the browser modifies them internally (but this doesn’t matter since we never read from them)

jumblerg22:07:14

b. via a cell binding

jumblerg22:07:34

which creates new “versions” of the element

jumblerg22:07:59

maybe it’s like cracking open a piece of stm and sprinkling it around the app

micha22:07:53

i think what you're talking about is related to allocation

micha22:07:00

not really immutability

jumblerg22:07:03

much like you can have an encapsulated mvc triplet in the super-small that manages its own local state for something like a checkbox, or your entire client-side app, or even an entire system in the large where the model is the db uses mvc or cqrs

micha22:07:05

they're just statically allocated

micha22:07:51

you allocate them statically which makes it more declarative

micha23:07:08

but for example the order in which you construct them is important

micha23:07:15

if they were immutable that would not be the case

jumblerg23:07:01

can you give me a concrete example or the order making a difference?

micha23:07:05

like if you need to do things that can't be done unless the element is in the dom already

micha23:07:08

things like that