Fork me on GitHub
#hoplon
<
2016-12-05
>
jumblerg00:12:11

@alandipert: too bad you didn't get the nsf grant for it.

mhr00:12:03

@micha: yeah, I'd definitely appreciate that. the issue is that I'm afraid to make design decisions because I'm not sure if these decisions will scale, and I'll need to rewrite everything later after a mess of spaghetti code. I don't need some special library, it's about how to conceptualize design and separate concerns.

mhr00:12:59

it's easy to make an incomprehensible mess in a real spreadsheet, and it's the same with Javelin.

micha00:12:37

@mhr one thing that javelin helps with vs spreadsheets is names

micha00:12:07

i guess the one rule of thumb that is pretty much always applicable is to make small formulas

micha00:12:43

if a formula gets large you can always factor it into regular functions and a small formula that composes those

micha00:12:01

this way it's very easy to refactor the application

micha00:12:39

for me it's kind of important to be able to refactor things as the application grows

micha00:12:47

small formulas makes a debugger almost unnecessary, too

micha00:12:16

like when i look at my namespace will all the little formulas it's pretty obvious where some unexpected result came from

micha00:12:51

with excel for example it's harder to factor formulas

micha00:12:42

and the A1 B7 business makes it difficult to inspect and debug

micha00:12:41

also the thing i think about from the very beginning is the "ui kit"

micha00:12:54

like a kit of components that will be used throughout the application

micha00:12:00

and how they will compose with each other

micha00:12:57

that's where i start to diverge from any prefab architecture really

micha00:12:35

because there are competing concerns there that need to be balanced against each other

micha00:12:44

like if you have caching in the client, things like that

mhr00:12:45

that's true, names help a lot. and keeping formulas small helps too.

micha00:12:04

i guess i've ended up with a 3 layer architecture for the UI

mhr00:12:11

what are the layers?

micha00:12:33

the lowest layer is the layout/low-level component layer

micha00:12:55

hoplon/ui provides one implementation

micha00:12:12

my own applications predate h/ui so i had to roll my own ad hoc ones

micha00:12:21

usually using twitter bootstrap and whatnot

micha00:12:54

bootstrap is not ideal because it precludes a clean separation of the low level from the upper layers

micha00:12:08

like there are structural dom things that leak to higher layers

micha00:12:46

like if you compose this bootstrap list item in this bootstrap list structure you need to add a special wrapping div

micha00:12:49

or things like that

micha00:12:04

it's a bummer but doable

micha00:12:32

the lowest layer components would be for example a text input

micha00:12:09

it would be like (text-input :state some-cell :placeholder "first name") for example

micha00:12:35

that's where i establish the javelin binding protocol i'll use in the upper layers

micha00:12:02

the next layer is similar to the lowest one, but it's specific to my application

micha00:12:19

like i'll extend the base elements with ones that are specialized for the ui in this application

micha00:12:47

because a specific application will have its own look and feel that impose constraints

micha00:12:59

and those constraints let you make things easier to use

micha00:12:24

like the second layer components provide default settings to the lower layer

micha00:12:30

things like that

micha00:12:46

the third layer is the "assembly" layer

micha00:12:03

that's where the second layer components are composed to make the actual application

micha00:12:20

this imposes even more constraints, which allows another layer of default settings and so on

micha00:12:37

the goal here is to have lisp generate most of the code, and all of the boilerplate

micha00:12:08

i use strategic macros to generate boilerplate

micha00:12:28

using macros makes it easy to override the boilerplate default settings when you want to

mhr00:12:55

makes sense

micha00:12:31

an example of the highest layer:

micha00:12:50

that's the entry point for https://app.adzerk.com

micha00:12:34

note how this namespace uses composition almost exclusively

micha00:12:55

there are a couple of conditionals, but they are declarative

alandipert00:12:58

vs. "registering handlers" for routes and/or events

alandipert00:12:13

this seems to be what people want when they say they want an architecture

alandipert00:12:57

i think, because most web type frameworks tend to orient around these

alandipert00:12:09

it's insane to imagine organizing a web app as a set of function calls lol

alandipert00:12:16

and yet, we do it

micha00:12:29

that's definitely an easy way to make a framework

alandipert00:12:43

yeah the straw man framework

micha00:12:53

takinh control of things then letting the user register with the framework

micha00:12:39

that's what you'll see if you go to http://app.adzerk.com

micha00:12:55

that form/with-field stuff in that file

micha00:12:02

we use that throughout the entire application

micha00:12:12

that's the layer 3 code

micha00:12:24

it's generating a bunch of boilerplate

micha00:12:43

but because lisp is awesome it's all composable

micha00:12:30

like line 100 for example

micha00:12:39

that's a unique item for a login form

micha00:12:47

that no other form in the application really has

micha00:12:05

the toggling from forgot-my-password to login-with-email-and-password mode

micha00:12:42

but it's ok, because the boilerplate stuff and the special unique stuff can coexist and compose with each other without issues

micha00:12:53

not as elegant as the layers above, heh

micha01:12:28

but you don't use those elements directly in your application

micha01:12:45

hoplon/ui should be much better than what i have there with the twitter bootstrap and whatnot

micha01:12:54

looks cleaner than layer 1 but still a little hairy

micha01:12:47

notice we have some macros in use there, things to make it easier to see what's going on

micha01:12:19

i guess that's a lot of stuff

mhr01:12:12

should you be posting your company code online...?

micha01:12:37

there is nothing confidential there

micha01:12:05

our UI actually uses our api for all of its functionality

micha01:12:30

anyway all of that code is sent to the browser

micha01:12:48

so if you load the application you have downloaded it

adamw01:12:11

Wouldn't syms1 & syms2 be identical and hence the intersection effectively be a no-op? Is something magical happening to bindings that means the destructure somehow alters it (dynamic var?)

adamw01:12:30

s/sumb/dumb

micha01:12:02

yeah that looks right

micha01:12:56

i mean it looks like a noop like you say

micha01:12:23

actually now i don't know what clojure.core/destructure is suppsoed to even do

alandipert01:12:14

boot.user=> (destructure '[[x & xs] [1 2 3 4]])
[vec__1369 [1 2 3 4] x (clojure.core/nth vec__1369 0 nil) xs (clojure.core/nthnext vec__1369 1)]

alandipert01:12:27

it takes a binding vector and produces a binding vector suitable for let*

micha01:12:49

i just did this

micha01:12:01

boot.user=> (->> '[a {:keys [foop]} [x y & z]] destructure)
[a {:keys [foop]} [x y & z]]

micha01:12:53

did i do sometihng wrong there?

micha01:12:34

the second thing is the values

alandipert01:12:24

yeah it wants the values also

alandipert01:12:40

and if you don't give it exactly what it wants, it's not especially vocal lol

adamw01:12:57

(->> '[{:keys [foop]} {:foop 123}] destructure)
=>
[map__35368
 {:foop 123}
 map__35368
 (if (clojure.core/seq? map__35368) (clojure.lang.PersistentHashMap/create (clojure.core/seq map__35368)) map__35368)
 foop
 (clojure.core/get map__35368 :foop)]

adamw01:12:05

(onetom here)

alandipert01:12:38

yeah micha's thing had an odd number of items, probably threw it off

micha01:12:40

yeah i think one of those sets should have been the map__243532 ones

micha01:12:55

the intersection would be just the ones the user specified

micha01:12:07

at laest that seems to be the intent with that code

micha01:12:32

it is a tricky side effect situation

micha01:12:48

because gensym names are side effects

micha02:12:00

haha what a sweet hack

alandipert02:12:07

lol that's great

micha02:12:08

perhaps a comment is in order there

micha02:12:15

just for shiggles

micha02:12:06

"i removed this redundant line of code and now the program is broken"

alandipert04:12:29

has anyone experimented with custom error types in cljs land?

thedavidmeister10:12:04

@micha i’m well on the way to organising my app in a similar way

thedavidmeister10:12:43

no “strategic macros” yet, but the 3 levels are more or less there

thedavidmeister10:12:13

actually, making DOM i’m now finding pretty easy

thedavidmeister10:12:36

it’s third party libs and snippets now that i’m not always sure the best way to bring into hoplon

mynomoto11:12:07

@micha Thanks for that, looks amazing!

alandipert15:12:41

hi @leobm, welcome 👋

alandipert18:12:12

@micha is ther a way to get back the original removeChild etc. methods from js/Element?

alandipert18:12:58

not sure if there is some JS backdoor i am missing

micha19:12:38

would have been better to rename them on the prototype maybe

alandipert19:12:58

maybe they should just be public?

alandipert20:12:32

workaround:

(defn removeChildNative [obj child]
  (.call @#'hoplon.core/removeChild obj child))

(defn appendChildNative [obj child]
  (.call @#'hoplon.core/appendChild obj child))