Fork me on GitHub
#membrane
<
2022-09-10
>
Ben Sless15:09:54

Is there anything I need to do to make a scroll view scrollable? I'm scrolling with a mouse and nothing happens

Ben Sless15:09:58

(defn view
  [& _args]
  (basic/scrollview
   {:scroll-bounds [150 150]
    :offset [0 0]
    :body (ui/vertical-layout
           (basic/textarea {:text "HELLO WORLD" :border? false})
           (basic/textarea {:text "Hello world" :border? false})
           (basic/textarea {:text "Hello world" :border? false})
           (basic/textarea {:text "Hello world" :border? false})
           (basic/textarea {:text "Hello world" :border? false})
           (basic/textarea {:text "Hello world" :border? false})
           (basic/textarea {:text "Hello world" :border? false})
           (basic/textarea {:text "Hello world" :border? false})
           (basic/textarea {:text "Hello world" :border? false})
           (basic/textarea {:text "Hello world" :border? false})
           (basic/textarea {:text "Hello world" :border? false})
           (basic/textarea {:text "Hello world" :border? false})
           (basic/textarea {:text "Hello world" :border? false})
           (basic/textarea {:text "Hello world" :border? false})
           (basic/textarea {:text "Hello world" :border? false})
           (basic/textarea {:text "goodbye world" :border? false}))}))

(java2d/run (component/make-app #'view))

phronmophobic15:09:03

All of the defui components are pure functions. The defui macro helps wire up all the incidental state.

phronmophobic15:09:41

I think view would need to also be a defui component for it to work for that example.

phronmophobic15:09:15

You can also wire up the state manually, which you might want to do if you're using your own state management

phronmophobic15:09:46

This is probably closer to what you want:

(defui view
  [{:keys []}]
  (basic/scrollview
   {:scroll-bounds [150 150]
    ;; :offset [0 0]
    :body (apply
           ui/vertical-layout
           (for [i (range 10)]
             (basic/textarea {:text (get extra [::text i] "initial text") :border? false})
             ))}))

(java2d/run (component/make-app #'view))

phronmophobic15:09:47

I have an example/experiment of using datascript that I can clean up as an example

phronmophobic15:09:49

Not sure if you've read https://blog.phronemophobic.com/reusable-ui-components.html, but it talks about the design for creating reusable, "stateful" components with only pure functions.

Ben Sless15:09:21

I read it months ago, will refresh

Ben Sless16:09:24

One thought I had when skimming it again, the problem with the syntax introduced by references is I can't control as a user how they get resolved in the state. Wonder if it could be possible to do something similar to hyperfiddle and let them be queries on the state

phronmophobic16:09:06

> I can't control as a user how they get resolved in the state. There is a way to do it

phronmophobic16:09:31

> Wonder if it could be possible to do something similar to hyperfiddle and let them be queries on the state I'm not sure I know what you mean. Do you have a short code example?

Ben Sless16:09:01

I can sketch something when I get to the computer in a few min

phronmophobic16:09:00

If you think it would be easier, we could setup a time to either pair-program or discuss your ideas at some point

Ben Sless16:09:08

That would be very helpful. Then I could also figure if my ideas are off base

Ben Sless16:09:25

roughly:

[{:keys [db chat]}]
  (basic/scrollview
   {:scroll-bounds [150 150]
    :offset [0 0]
    :body (ui/vertical-layout
           (d/q '[:find (pull ?e)
                  :in $ ?chat
                  :where
                  [?e :chat/name ?chat]]
                $db
                $chat))}))

👀 1
phronmophobic16:09:46

I'm not sure I've got the interpretation of that syntax right. Something like a scrollview of chat UIs? Are the chats editable?

Ben Sless16:09:09

the chats aren't editable, but you can send messages and get updates from server

phronmophobic16:09:08

In general, my position is that the updates from the server would happen in application code that is more or less separated from the UI code.

phronmophobic16:09:20

I'm sure your busy, but I'm not really doing anything if you wanted to do a quick video call.

Ben Sless17:09:40

Oh man, I appreciate it, but like you guessed, sort of busy. Just about dinner time. Let's try to schedule something for the near future?

phronmophobic17:09:49

Sounds good. I'll be out of town from the 11-18th, so my schedule is a bit uncertain this week, but I could probably make something work.

Ben Sless17:09:38

I was thinking instead of application code handling server events, they'll first be pushed to DB, then the tx diff will be sent to the UI Whole new meaning to materializes view

phronmophobic17:09:38

Anyway, you might find the datascript experiment interesting.

(skia/run-sync
 (make-app #'my-todo-app conn
           ;; query
           [{:todo-lists [:db/id
                          {:todos [:complete? :description :db/id]}]}
            '*]
           ;; root-id
           1))
Which looks at least sorta similar to your example syntax

Ben Sless17:09:26

I'll make sure to study it and come with prepared questions!

Ben Sless17:09:20

A specific screen could be a query filter + find, updates just take the diff and apply the find

👍 1
phronmophobic17:09:57

Generally, I have some ideas on the backburner for integrating spec-like descriptions with components so I would be interested to see if your approach might be similar. I'm not super familiar hyperfiddle, but my main hesitation is that instead of simplifying things and taking them apart, it looks like they're making it easy to smash everything together

Ben Sless17:09:26

forget HF then, just think of screen -> filter -> db -> filtered-db -> view Where the updates emit tx updates which pass through the same filter and get added to the filtered DB and then the redrawn view just pulls from it

👍 1
phronmophobic17:09:48

there are still some things to work out, but I think the datascript I linked example comes pretty close

phronmophobic17:09:12

or it's potentially a good starting point

phronmophobic18:09:52

I also wanted to leave you with one additional design challenge when integrating queries with components. Most designs start with assuming the following: 1. each component will specify a query for only the state it requires 2. the view is constructed by generating the full query for the UI which gets fed to the view function to create the UI. The challenge is that you can't do both 1 and 2 while still supporting views like tabbed views. For a tabbed view, you only want to fetch the data for the currently selected tab, but you don't know which tab is visible without first fetching the state for the tab view container. Essentially, to satisfy 1 and 2 while supporting components like tabs requires that generating the query and fetching the data need to be interleaved. Having an arbitrarily nested tabbed view of tabbed views would make a bad UI, but there are still realistic situations that require multiple interleavings of generating queries and fetching data. One workaround is to drop the requirement to only fetch the required data and instead fetch anything that could potentially be required, but that's a potentially big concession. It's not an insurmountable challenge, but it's an area that seems to have a weak story when looking at query+component frameworks like fulcro. Anyways, just some food for thought.

Ben Sless19:09:33

This is a weakness of the design My naive answer is that should be solved by composition. You want the state for tabs to be updated (maybe? I would assume yes), but not rendered. To render or not to render is different from fetching the data itself and actually building the UI. The only question is if this approach fits membrane

phronmophobic19:09:22

> You want the state for tabs to be updated (maybe? I would assume yes), but not rendered. To render or not to render is different from fetching the data itself and actually building the UI That sounds like the workaround I mentioned. Essentially, you fetch all the data that could potentially be required regardless of whether or not it gets drawn. That's pretty easy to do with membrane. That's plausible for the tab example, but let's say you have a search bar that allows you to type in an address which will show up in a map view. The data that is potentially required 1) depends on the state of the address input and 2) could potentially require the map data for every location on the planet.