Code Monkey home page Code Monkey logo

Comments (20)

megamaddu avatar megamaddu commented on June 8, 2024 1

@stratospark That's pretty close to what I ended up doing, though I don't know of any other examples: https://github.com/spicydonuts/pux-rock-paper-scissors/blob/master/src/Layout.purs#L29

from purescript-pux.

aspidites avatar aspidites commented on June 8, 2024 1

@freezeboy actually, I just spent the last couple of hours doing what you want. The code is a bit messy, so I plan to submit a cleaned up version tomorrow evening. The gist, however, is that (as with the above examples) you would simply have a parent state/action type which indexed into child states/actions. For the case of having a tab bar and tab content, I ended up using a single child action and state type, a single update function, but multiple view functions. viewTabContent was responsible for rendering the contents of a tab, while viewTab was responsible for rendering the tab bar elements (and interpretting the click event). Type wise, I have the following so far, though I expect it to change/improve later on:

type AppState a = { el :: string, tabs :: Array (TabState TabAction)}
data AppAction = Tab Index (TabAction)

updateApp :: forall a. AppAction -> AppState a- > AppState a
viewApp :: AppState ApAction -> Html AppAction

type TabState a = { label :: String, content :: Html a, active :: Boolean }
data TabAction = Activate

updateTab :: forall a. TabAction -> TabState a -> TabState a
viewTab :: TabState TabAction -> Html TabAction
viewTabContent :: TabState TabAction -> Html TabAction

from purescript-pux.

alexmingoia avatar alexmingoia commented on June 8, 2024 1

Yeah, there's nothing wrong with the approach of using a global action type. This might be easier for certain apps.

from purescript-pux.

alexmingoia avatar alexmingoia commented on June 8, 2024

Parent-child communication is briefly described in the multiple components section of the guide. Sorry if that didn't help you, writing better documentation is my number one priority. If you look at the example update function for the parent component, you can see it responds to Increment and Decrement actions from its child counters.

Pux has no concept of addresses, or the need to pass addresses around everywhere. Views themselves send actions of a certain type, represented by the Html action type. Let's say you want to use a child view of type Html Child.Action inside a parent view of Html Parent.Action. You map over the child view with your parent action:

module Parent where

import SomeChild as Child

data Action = Foo | Bar Child.Action

update :: Action -> State -> State
-- | (Bar action) here is the child action available to the parent
update (Bar action) state = Child.update action state
update Foo state = state

view :: State -> Html Action
view state =
  div [] [ map Bar Child.view ]

Update functions are nested the same way. Essentially, a pux application is just one update and view function, which is composed from many smaller update and view functions strung together.

Does that help? I'll put together an example of a list of counters for you.

from purescript-pux.

stratospark avatar stratospark commented on June 8, 2024

@alexmingoia Your documentation is great so far, I've been able to follow along. I was able to port over the Elm example 3, which is adding to and removing from a list of counters. However, I got stuck porting over example 4, which is where there is a remove button per child component. Pressing this button should trigger an action on the parent that removes the counter from the list.

Based on your comment, I was able to resolve this by adding a Remove action to the Counter component. Here is my attempt:
stratospark/pux-elm-architecture@6e677d4

Is there a more elegant way to accomplish this?

from purescript-pux.

alexmingoia avatar alexmingoia commented on June 8, 2024

Elm example 4 is unnecessarily complicated. There is no need to pass a channel for remove to the child when it's sufficient to compose the child's Remove action with the parent's action. In stratospark/pux-elm-architecture@6e677d4 there's no need for the parent to have a separate Remove action from the child. See my refactored version of that here: stratospark/pux-elm-architecture/pull/1

If you truly do have a use-case where a child needs to send an action that is totally arbitrary and defined by the parent (I can't think of one), you would need to parametize the types properly:

data Action a = Increment | Decrement | Parent a

view :: a -> State -> Html (Action a)
view parentAction state =  button [ onClick (Parent parentAction) ] []

There's no need for this though.

from purescript-pux.

stratospark avatar stratospark commented on June 8, 2024

@alexmingoia Thanks for the clarification! I merged in your code and ported over a few more of the elm-architecture examples.

Somewhat related, is there a suggested way of composing updates with effects? I took a shot at it when porting over example 7, but maybe there is something more elegant? https://github.com/stratospark/pux-elm-architecture/blob/master/ex7/src/Layout.purs#L46.

from purescript-pux.

freezeboy avatar freezeboy commented on June 8, 2024

I've another case in mind for which I don't really see a solution. Imagine the following situation:

A module declaring an Action, State, init, view types and functions to create a Tab component.

A tab is a { title :: String, content :: Html ???), and the component generates some tabbar + content of the active tab (or all tabs and plays with css classes to only have one visible)

The problem is that the tabs are really defined by the Main module, something like:

mainView = div [] $ Tab.view [ ... ... here I define my tabs ]

so the content field should in fact be some Html a

I'm not sure I'm clear, but I can't write a compiling code doing exactly what I need

from purescript-pux.

aspidites avatar aspidites commented on June 8, 2024

@freezeboy do you have at least partial code we could mess with to help you work through it? I'm not sure I completely understand your example, but I think for me I would have a tab component, then a tab container component which used an array of tabs as it's state.

from purescript-pux.

aspidites avatar aspidites commented on June 8, 2024

Never mind. Decided to go ahead and publish what I currently have. I do intend to clean it up still, but this way you aren't kept waiting.

from purescript-pux.

alexmingoia avatar alexmingoia commented on June 8, 2024

A tab is a { title :: String, content :: Html ???

Views are a function of state, so you shouldn't be putting Html in your state. Instead, put the state you want to view as @aspidites has done (thanks for sharing your code 👍 ) Try to think of an application as a single recursive function Action -> State -> Html -> Action -> ...

from purescript-pux.

aspidites avatar aspidites commented on June 8, 2024

@alexmingoia , I just realized what you said about HTML myself and agree. To know what actions are emitted should be enough to render the component. That said, it was kinda cool to know it's easy enough to arbitrarily include HTML in the state like that. I'll update my code to not do bad things some time today.

from purescript-pux.

aspidites avatar aspidites commented on June 8, 2024

@freezeboy, I updated my example. The PureScript should be fairly idiomatic. If you take a look at src/Components/Tab.purs, you'll see that the content of the tab is simply, as @alexmingoia points out, a representation of the current state, rather than embedded in the state itself. If you have tabs that need to house completely different content, I think I'd use a record for App's tabs instead of an Array and have each tab be its own component, since completely different tabs will have different actions that can be done on them, as well as a different state representation.

from purescript-pux.

freezeboy avatar freezeboy commented on June 8, 2024

@aspidites Thx for the code, I understand better how it should work, but I have two questions then:

  • Why are you putting the views inside a Tab in your renderTab* functions (here)
  • How do you insert an active tabcontent (which contains its own component) from the App ?

What I am looking for is

  1. App component which does more or less what you version does
  2. Tab component which nows how to display a Tabbar + an active component
  3. An auxiliary component to put inside a tabContent

What I expect is that the App component is aware of the Auxiliary component's state, but not tab (because it should not care)
Although, depending on the tab, it may be pure Html, Auxiliary component, or maybe another one I don't know yet

In the mean time I tried to implement it with Halogen too, It is more or less working, but I'm still fighting with some State/Query encapsulation.

from purescript-pux.

aspidites avatar aspidites commented on June 8, 2024

Hi @freezeboy,

Disclaimer: I didn't initially wite the example because I knew the answer to your original question. I wrote it as a means of exploring the answer to your original query. Also, I'm seeing things that could be cleaned up still (Such as viewApp's type, which is unnecessarily polymorphic, something left over from an earlier revision).

Why are you putting the views inside a Tab in your renderTab* functions (here)

Let's look at the types:

viewApp :: AppState AppAction -> Html AppAction
viewTab :: TabState -> Html TabAction

From these alone, I can't very well call a child component's view function from a parent component, as the types don't match. That's the purpose of the AppAction's Tab constructor, which takes a TabAction as an argument

someTab :: TabState
Tab :: Int -> TabAction -> Application
Tab i <$> viewTab tabState :: Html AppAction

So renderTab takes a tuple of (Int, TabState) and renders that state in a way that can be used inside the larger view.

How do you insert an active tabcontent (which contains its own component) from the App ?

If I understand the question correctly, the answer is in AppState's current attribute. If you look at updateApp, you'll see that I map over each tab and set their active attribute to false unless their index matches current, in which case I set it to true. Move to the tabs themselves, and you'll see that the two view functions (viewTab and viewTabContent know how to change element classes and contents based on this active attribute.

Although, depending on the tab, it may be pure Html, Auxiliary component, or maybe another one I don't know yet

Separate revisions of my examples show both of these approaches. Speaking from (limited) experience, I can say you're better off making each different thing you want to display a new component. If the actions and state used by these components is similar, it may be enough to simply have different view functions to interpret said actions and states. When embedding HTML directly, the types quickly got messy, and I found myself having to thread through actions and states in places where it didn't make sense, which reduced the flexibility of the components overall.

What I am looking for is ...

That sounds totally doable and not much different than what I have in the example. I think the biggest difference would be that instead of TabState's content type being a string, it would be a proxy into some other component's state. If you need multiple different ones, you'd just have different keys in the object. If they will be permutations of the same component type (eg. with different states), then you could use a list or array)

You could go halogen or thermites route and use a sum type for actions and a product type for state. For example (Either ThisAction ThatAction, Tuple ThisState ThatState).

from purescript-pux.

sloosch avatar sloosch commented on June 8, 2024

Nice challange! ;)
It basically boils down to the problem how to put child components with arbitrary actions, states and effects into some parent component.
My solution abstracts away the translation between child and parent state action. So there is no need to write the delegation code e.g. to update states update (ChildAction a) state -> state{child=updateChild a state.child} and to view child components ChildAction <$> viewChild state.child. Instead you can transform your child component to accept parent state and action childComponent # adaptState _{childState=_} _.childState >>> gAdaptAction ParentActionDelegation
For the Tab example we can now write:

myTabComponent ::  e. C.PuxComponent MyTabAction MyTabState (AppEff e)
myTabComponent = Tab.component componentForTab [
        Counter1,
        Static "foo" "lorem ipsum",
        Static "HAL" "I'm sorry, Dave. I'm afraid I can't do that.",
        Counter2,
        Interactive
    ]
    where
    componentForTab Counter1 =
        TabContent  "A counter" $
                    Counter.component #
                    C.adaptState _{counter1=_} _.counter1 >>> C.gAdaptTaggedAction Counter Counter1
    componentForTab Counter2 =
        TabContent  "Another counter" $
                    Counter.component #
                    C.adaptState _{counter2=_} _.counter2 >>> C.gAdaptTaggedAction Counter Counter2
    componentForTab (Static t txt) =
        TabContent t $
            C.Stateless $ H.div [] [H.text txt]
    componentForTab Interactive =
        TabContent  "Interactive" $
                    ConsoleAndCounter.component #
                    C.adaptState _{consoleAndCounter=_} _.consoleAndCounter >>> C.gAdaptAction ConsoleAndCounter

TL;DR;
Adapt state and actions: https://gist.github.com/sloosch/ea98c0c58f9440903c98b952265b556e
Here is a component with different child components: https://gist.github.com/sloosch/ea98c0c58f9440903c98b952265b556e#file-consoleandcounter-purs
Here is the TabComponent which can display arbitrary tabs: https://gist.github.com/sloosch/ea98c0c58f9440903c98b952265b556e#file-tab-purs
Here is the PuxComponent abstraction: https://gist.github.com/sloosch/ea98c0c58f9440903c98b952265b556e#file-component-purs

from purescript-pux.

menelaos avatar menelaos commented on June 8, 2024

@alexmingoia What do you think about this approach?
Basically, update functions return a GlobalAction alongside the new state if needed which is then fed back to all components that "listen" to it.
This avoids pattern matching on child actions but requires an additional updateGlobal function.

from purescript-pux.

freezeboy avatar freezeboy commented on June 8, 2024

@sloosch Wooh thank you for your code it is exactly what I had in might I was expecting that your "PuxComponent" was already included in some way in pux.

I have a question (coming from my haskell background), I'm not really sure if the purescript compiler's capabilities. You call twice tab componentForTab in the Tab.view function (once for the titles and once for the Tab content) Is the purescript compiler smart enough to optimize this into only I call ?

The idea behind this question is about perfomance: componentForTab is building the tab's content for each inactive tab (even though it is not rendered, maybe we could add some more tuning to help

Any way, thank you very much for sharing this cool PxuComponent :)

from purescript-pux.

sloosch avatar sloosch commented on June 8, 2024

@freezeboy yes that is possible by using a Lazy - TabContent e.g.:

data TabContent a s e = TabContent String (Lazy (C.PuxComponent a s e))

viewTab ::  a s e. TabContent a s e -> s -> H.Html a
viewTab (TabContent _ c) = C.viewComponent (force c)

updateTab ::  a s e. TabContent a s e -> a -> s -> Pux.EffModel s a e
updateTab (TabContent _ c) = C.updateComponent (force c)

and then you have to defer the construction of your content e.g.:

    counter = TabContent  "A counter" $ defer \_ ->
                Counter.component #
                C.adaptState _{counter1=_} _.counter1 >>> C.gAdaptTaggedAction Counter Counter1

    anotherCounter = TabContent  "Another counter" $ defer \_ ->
                Counter.component #
                C.adaptState _{counter2=_} _.counter2 >>> C.gAdaptTaggedAction Counter Counter2

    interactive = TabContent  "Interactive" $ defer \_ ->
                ConsoleAndCounter.component #
                C.adaptState _{consoleAndCounter=_} _.consoleAndCounter >>> C.gAdaptAction ConsoleAndCounter

by using Lazy the Component gets cached and will be evaluated only once when needed via force. Not as nice as the implicit laziness of haskell but it does the trick.
The Lazy-Module: https://github.com/purescript/purescript-lazy

Have fun!

from purescript-pux.

freezeboy avatar freezeboy commented on June 8, 2024

@sloosch Splendid, I think I'm loving purescript a bit more each day

from purescript-pux.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.