Code Monkey home page Code Monkey logo

purescript-pux's Introduction

PUX

Build type-safe web applications with PureScript.

Documentation | Examples | Chat

Latest Release ComVer Build Status Gitter Chat

Pux is a PureScript library for building web applications. Interactive UI is modeled as a single state transition function, Event -> State -> (State, HTML) which is run for every event. Pux also provides tooling such as:

  • Isomorphic routing and rendering
  • Hot reloading
  • Render to React (or any virtual DOM library)
  • Time-travelling debug extension

Quick start

The starter app provides everything you need to get started:

git clone git://github.com/alexmingoia/pux-starter-app.git my-awesome-pux-app
cd my-awesome-pux-app
npm install
npm start

Example

The following chunk of code sets up a basic counter that can be incremented and decremented:

module Main where

import Prelude hiding (div)
import Control.Monad.Eff (Eff)
import Pux (CoreEffects, EffModel, start)
import Pux.DOM.Events (onClick)
import Pux.DOM.HTML (HTML)
import Pux.Renderer.React (renderToDOM)
import Text.Smolder.HTML (button, div, span)
import Text.Smolder.Markup (text, (#!))

data Event = Increment | Decrement

type State = Int

-- | Return a new state (and effects) from each event
foldp ::  fx. Event -> State -> EffModel State Event fx
foldp Increment n = { state: n + 1, effects: [] }
foldp Decrement n = { state: n - 1, effects: [] }

-- | Return markup from the state
view :: State -> HTML Event
view count =
  div do
    button #! onClick (const Increment) $ text "Increment"
    span $ text (show count)
    button #! onClick (const Decrement) $ text "Decrement"

-- | Start and render the app
main ::  fx. Eff (CoreEffects fx) Unit
main = do
  app <- start
    { initialState: 0
    , view
    , foldp
    , inputs: []
    }

  renderToDOM "#app" app.markup app.input

Benchmarks

Table of benchmarks comparing rendering speed of similar libraries

Why is Pux slow?

Pux has not focused on performance yet. The slow performance arises from translating Pux's (smolder) virtual DOM to React's virtual DOM. The goal is to write a purescript virtual DOM module for smolder, which would avoid that translation step and could be optimized for a monadic datastructure. I suspect this would achieve performance on par with Halogen.

Below are the render steps for the other libraries compared, which shows that Pux is the only one that has an intermediate virtual DOM representation (it has to render to React first then React has to render):

  • Elm = Virtual DOM -> DOM patch
  • React = Virtual DOM -> DOM patch
  • Thermite = Virtual DOM -> DOM patch
  • Halogen = Virtual DOM -> DOM patch
  • Pux = Smolder Markup -> React Virtual DOM -> DOM patch

purescript-pux's People

Contributors

alexmingoia avatar aztecrex avatar chaenni avatar charleso avatar chexxor avatar damncabbage avatar eskimor avatar jazmit avatar jgoux avatar jmatsushita avatar justinwoo avatar karls avatar kurtharriger avatar megamaddu avatar menelaos avatar michaelxavier avatar mjhoy avatar mostalive avatar mrmurphy avatar omerzach avatar parsonsmatt avatar passy avatar prikhi avatar ravloony avatar roryc89 avatar sa1 avatar simonyangme avatar thimoteus avatar ttoe avatar werehamster avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

purescript-pux's Issues

Is there a way of having "conditional elements" in pux-html?

I'm thinking of something like:

myHtml :: Maybe v -> Html Action
myHtml v = div [] [
           span [] [ text "my text" ] 
         , case v of 
             Just v -> span [] [ text v ]
             Nothing -> Nothing --<< how do I represent a "null" element -- similar to "when" in monadic code?
         ]

equivalent to:

if (myVar == true) {
 html += "<div>text</div>"
}

in a real application there is likely to numerous values / properties that might conditionally generate html so it isn't practical to duplicate the entire function for every element.

race condition for fast effects

I want an Action to do nothing to the State and kick off an effect (whose result does something to the state). The problem is that in update I don't see a way not to set the state, even if I want it not to change. And if the effect returns very quickly, its signal gets rendered before the do-nothing change to the state. Here is a really contrived minimal example (the actual way I found this was more complicated and involved random numbers):

data Action = Increment | EffIncrement | Receive Int

type State = Int

update :: Action -> State -> EffModel State Action (console :: CONSOLE)
update Increment count =
  trace ("Increment: " ++ show count) \_ -> { state: count + 1, effects: [] }
update (Receive c) count =
  trace ("Receive: " ++ show c) \_ -> { state: c, effects: [] }
update EffIncrement count =
  -- I don't actually want to change the state here, but what can I do?
  trace "EffIncrement" \_ -> { state: count, effects: [pure $ Receive (count + 1)]}

view :: State -> Html Action
view count =
  div # do
    button ! onClick (const Increment) # text "Increment"
    span # text (show count)
    button ! onClick (const EffIncrement) # text "EffIncrement"

If I click on EffIncrement, the Receive (count + 1) signal gets rendered first. So if I click EffIncrement, what happens is the Receive 1 redraw happens before the state: 0 redraw, so the state gets updated but the UI appears not to. Then if you (e.g.) click Increment it goes up to 3.

A hacky solution is to artificially add a small delay to my fast effects, is there a better way?

(gist with full code: https://gist.github.com/joelgrus/f65b4106cdf32f4bc1109f0e1a0e0497)

Documentation improvements

The documentation could always be better. I'd like this issue to be an open-ended discussion on the documentation.

  • How can existing documentation be improved?
  • What is missing from the docs?
  • What is confusing about the docs?

Todo

  • Add note about using EXCEPTION effect because Pux's start / CoreEffects is already parametized with it.

pattern match failure, crossed-wire?

Hi,

I'm experimenting with pux in a public project. Its just pretty much a pux app with a bunch of small demos of increasing complexity. One of them is an AJAX-backed list. I have a component for the AJAXList and a child component for the AJAXListItem, and the Action for AJAXList is partly composed of child actions for AJAXListItem, which seems pretty standard. Interestingly, in the AJAX list case, firing an item's Delete action should get wrapped in the parent's ItemAction constructor, but it actually results in a pattern match failure. It almost seems like the wrong signal is being provided to the wrong component.

Here's how you'd repro it:

git clone https://github.com/MichaelXavier/pux-scrapyard.git --branch rest
cd pux-scrapyard
npm install
bower install
npm run-script build
npm run-script server

Browse to http://localhost:8000 and click on AJAX List, then open your dev console and click Delete by the first item. You'll see the following error in the console:

index.js:25374 Uncaught Error: Failed pattern match at Components.AJAXList line 60, column 1 - line 61, column 45: DeleteItem,Object

The function in question is expecting to receive ItemAction Int AJAXListItem.Action but is instead receiving AJAXListItem.Action, despite the types check passing.

Interestingly, there's also a demo there for a non-effectful list/listitem component that works fine. This leads me to believe that something about the effects system may be causing a signal to get routed to the wrong component or perhaps its dropping the wrapped version of the signal it uses for the parent's update function.

Sorry for the somewhat convoluted repro steps. Please let me know if there's any further info I can provide!

a couple of v1.0.0 issues / quirks

hey, cool that 1.0.0 is out, I discovered it by accident today. a couple of challenges:

  1. with the new Html a type, the ! sugar doesn't work for childless elements. e.g. in previous versions I would have done something like
img ! src "me.jpg" ! alt "me"

but now the types don't work out. I ended up doing

img [src "me.jpg", alt "me"]

but is there a better way?

  1. the withChildren method feels like it should be called withChild? Since it takes a single Html a child element? I don't know if it's just me, but I always end up needing to use an array of child elements. I see in the examples you use ol [] $ map ... Is that the cleanest way?

I ended up defining

withChildren' :: forall a.
                 (Array (Attribute a) -> Array (Html a) -> Html a) ->
                 Array (Html a) ->
                 Html a
withChildren' f htmls = f [] htmls

infixl 4 withChildren' as ##

and then doing div ## map ... but wasn't sure if there's a simpler way.

3 I got some issues about react and react-dom not being defined, I ended up having to npm install them. I guess maybe that's expected? Might be nice in the docs.

Anyway, still love the framework. :)

Popping up modals asynchronously *inside* EffModel's effects.

I'm working with an API where every API call can potentially fail with a "two-factor-authentication-code-required" error and need to be retried with the exact same arguments (modulo the two factor code). There's also some additional stuff with using HMAC for authentication.

To make the API a bit easier to use, there's a purescript library I've already implemented. The library uses the Reader monad to take a function from the UI that runs the API call, and if the call fails it promps the user for a two-factor code and passes it to the API call:

type Runner a e =
 (Maybe TwoFactor -> Aff (AuthEffects e) (Either TwoFactorRequired (Either APIError (APIResponse a)))) 
  -> MaybeT (Aff a) (Either APIError (APIResponse a))

type MyAPI e a =  forall a b. (Respondable b) =>
 ReaderT { runner :: Runner a e} (MaybeT (Aff a)) (AffjaxResponse (APIResponse b))

 getProfile :: forall e . GetProfileReq -> MyAPI (now:: NOW | e)  GetProfileResp

Is there a good way to create this callback in pux, using signals and channels?

Avoid unnecessary vdom creation

This was discussed on the Gitter channel. Since the output of Pux is 1 React component there is no place to use React's shouldComponentUpdate and thus avoid constructing vdom elements. Alex has suggested thunkifying the views which would allow deferring running them similarly to how React avoids running render.

Some questions:

• What does React put in the vdom when shouldComponentUpdate returns false?
• How can views be thunkified without requiring the user to add something to their code?

Load React from CommonJS

React is loaded from a global variable. It would be nice if it would load using require if the global variable isn't available.

Recursive `forwardTo` doesn't work exactly how I'd hoped

I'm working on a toy app with a recursive structure. Here's my action:

data Action
    = Child Int Action
    | UpdateWeight Int Number
    | UpdateScore (Score Number)
    | AddGrade
    | Undo
    | Redo

And an example on how I'm using it:

renderScore :: Array (Score Number) -> Html Action
renderScore gs = H.div # do
     H.button ! E.onClick (const AddGrade) # H.text "Add Grade"
     H.ul ## 
        forEachIndexed gs \i g ->
            H.li # (H.forwardTo (Child i) (viewGrade g))

When the UI gets nested, forwardTo uses the most recent function given. It'd be nice if it composed the functions instead.

Subscriptions (Elm 0.17)

What are your thoughts on Elm 0.17-like subscriptions?

From what I can tell there are basically two advantages to signals:

  1. The ability to unsubscribe from subscriptions. This seems especially useful
    for e.g. MouseMove events.
  2. To encapsulate functionality, such as e.g. retrying AJAX requests.

It should also make the architecture simpler in some cases:
Suppose there's a deeply nested component in your app that needs to know about window dimensions. With the current architecture you would need to feed Signal.DOM.windowDimensions to the inputs array at the top-level and manually pass that information down your component tree either via props or via actions.

With the subscription-based approach, it should be possible to have a deeply nested component declare that it wants to listen to window dimensions and let the framework take care of the rest.

-- Deeply nested component
data Action = ResizeEvent WindowDimensions

updateSub :: Action -> Model -> Sub Action
updateSub _ _ = Subscription.DOM.windowDimensions ResizeEvent

-- Intermediate components would simply map over the child components
data Action = ChildAction Child.Action

updateSub :: Action -> Model -> Sub Action
updateSub (ChildAction act) state =
  possiblyLocalSubscriptions <> map ChildAction (Child.updateSub act state.childState)

What do you think about this?

Using JS React components

I had looked around for information about using Pux with existing React components but was not successful in finding anything. I have an existing React system and I'd like to start converting it over bit by bit into Purescript. Is there an existing way to have a view render existing components including passing down props? If not, where would be a good place to look to add such functionality?

How to share global state?

I'm writing a somewhat extensive app using Pux (currently around 20 components, but likely to be 200 or more). Basically, it's typical app but goes well beyond the relatively simple (but elegant) example apps that currently exist like #https://github.com/spicydonuts/pux-rock-paper-scissors.

To give you an idea, my app implements nested routes as discussed here ##59. It has a structure very similar to this:

├── Application
│ ├── Repository
│ │ ├── Issues
│ │ │ ├── IssueList.purs
│ │ │ └── NewIssue.purs
│ │ └── Issues.purs
│ └── Repository.purs
└── Application.purs

For various reasons there are certain values that would be useful to share throughout most of the app. For example a user id, name and authentication token. (But also, conceivably numerous run time configuration options). The only way I can see to do this is by explicitly passing these values through Actions which seems quite clumsy and verbose?

I'm aware of purescript-refs although I'm not quite sure how to use it. Might that be a reasonable solution?

Am I missing something else?

SVG "text" element

The <text> element is missing form Pux.Html.Elements. Probably because the function would be in name conflict with the existing text function (text :: forall a. String -> Html a).

In my project I created a svgText function, but I'm not happy with the naming because it is not consistent with the other functions.

What about removing the current text function in favour of fromString (which it really is, after all), and so we can use text for the HTML element?

Export `render` function

For those who need a custom start function, it would be useful to export the render function from Pux.

After webpack hot reload, update function effects for `PageView` action happen twice

As the title says, for some reason after the hot reload takes place, the effects returned by my update function happens twice. And it seems like it's only for the PageView action.

To reproduce...

  1. Clone the pux-starter-app repo
  2. Update to webpack 2
  3. Change the update function to use EffModel, add a couple routes, and add some link to the Counter view so we can change between pages.
  4. Add a console log statement to the pageview update function
  5. Navigate between pages. The console.log fires once per page view, great.
  6. Now modify some text in the view to get webpack to recompile and hot reload.
  7. Now click the links again to switch between pages, and the console.log messages fire twice each time! If you hot-reload a third time, now each page view triggers 3 console logs... and so on.

From a fresh project and an update function is as simple as this...

update (PageView route) state =
  { state: state { route = route }
  , effects: [ (liftEff $ log "pageview action" >>= (\_ -> pure NoOp)) ]
  }

It only doubles the effects of the PageView actions, so it's something related to the popstate handler / history API, but as far as I can tell the javascript handlers in Router.js are only being run once.

Am I doing something seriously wrong or did I find a bug? Could it be something to do with webpack 2?

(I can upload a GitHub repo that reproduces the issue)

Call `ev.preventDefault()` for drag-and-drop events

Right now it seems Pux will call preventDefault only for onSubmit and onClick event handlers. Trying to use the drag-and-drop API, for example to handle dropped files, requires calling this function for the drag and drop events (for example, it's required to prevent the browser from navigating away from the page when an image file is dropped, most browsers will navigate to the dropped file):

  • onDragStart
  • onDrag
  • onDragEnter
  • onDragOver
  • onDragEnd
  • onDragLeave
  • onDrop

Also, can this be made optional? Is it a good idea to force Pux users to call preventDefault, when there might be situations to run a function before allowing the default action to happen?

Investigate using purescript-react

@AppShipIt and @paf31 have asked why Pux isn't built ontop of purescript-react. The reason is that no functions from purescript-react are exposed in the Pux API. For example, Attribute a is used instead of Props, or Html a instead of ReactElement. When I wrote Pux, it was faster and less code to have Html a be a foreign data type corresponding directly to React.createElement.

Advantages

  • Share bug-fixes, elements, and attributes with the wider ecosystem.

Disadvantages

  • Additional types and function calls that aren't exposed in Pux's API.
  • Possible performance impact when rendering.

Performance

One concern is that if Html a translates to purescript-react's ReactElement, that adds an extra layer of function calls in the render method. The only place where performance is a real concern is the render method, because it blocks the application. Function calls have very little overhead but it's still a concern. Renders should be as lightweight as possible.

Thoughts?

is there a way to render children components into a layout component?

In React, you can render children components using props.children

const Layout = props => (<div>{props.children}</div>)

const Menu = () => (<Layout><ul><li><a>Link</a></li></ul></Layout>)

which would render <ul><li><a>Link</a></li></ul> inside the Layout div.

Is there any way to do this in Pux?

Thanks

Input signals of varying types

I'm trying to figure out how to translate some Javascript architecture into Pux / Purescript. This is for a desktop application using Electron. I have in several places UI events that fire off Async events that are currently observed by several different components via Flux store change emit events. I was thinking that the extra inputs array that can be provided to a component could possibly be used for this, but the action type for all the signals must be the same so that they can be merged. I could expose a global signal and then wire up Signal.runSignal somewhere via FFI but that feels pretty messy. Do you have any idea how I could provide a global events signal that can be used by my async background processes to signal progress/success/failure to all the components that would need it? Is there perhaps a better way to do what I'm going for that doesn't require components up the chain to be explicitly aware of the data needed by components down the chain?

Inter-component communication?

I'm trying to translate some Elm tutorials into Pux, but currently getting stuck with inter-component communication: https://github.com/evancz/elm-architecture-tutorial/#example-4-a-fancier-list-of-counters.

I'm struggling with how to get the child to communicate with the parent. In javascript, I could just pass a callback to the child. In Elm, they have the notion of addresses, for decoupled communication. I'm wondering how to do this in a typesafe way, without cyclical dependencies?

Thanks from an FP beginner :)

`Html` and component typing

Background: I'm kind of new to Purescript and FP in general

While adding new pages the pux start app, I faced a runtime talking about puxParentAction. I had no idea where I was wrong.

foreign.js:28 Uncaught TypeError: Cannot read property 'puxParentAction' of undefined

That's only after a long debugging session in the compiled JS that I could realize that my new view did not return a react component:

view :: forall state action. state -> Html action
view _ = text "Hello!"

This perfectly compiled however, and I was convinced that using text from Pux.Html was a fair use. This has been very confusing for a beginner like me, who has at least a good knowledge of react.

Html is defined as follows (same for Attributes):

foreign import data Html :: * -> *

In my opinion, the fact that Html can be anything is a flaw in pux as it can easily lead to mistakes causing runtime crashes.

Considering react's architecture, we'd need some smart way to manage two kinds: elements and child elements. Indeed, the render function must strictly return a react component (or "null" since react 15). Child elements however can be strings or arrays.

I hope this issue makes sense, please tell me if I misunderstood anything, and thanks for your commitment in this community!

Initial values of signals in `inputs` are discarded

Providing an array of external signals via the inputs field can be problematic if one uses signals that typically fire only once at the beginning.

Take e.g. the following example in which the app is supposed to read in the current window dimensions and the current route via Signal.DOM.windowDimensions and Pux.Router.sampleUrl:

module Main where

import Control.Alt ( (<|>) )
import Control.Apply ( (<*) )
import Control.Monad.Eff ( Eff )
import Data.Functor ( (<$) )
import Data.Generic ( class Generic, gShow )
import Data.Maybe ( fromMaybe )
import DOM ( DOM )
import Prelude ( class Show, ($), (<<<), (<>), bind, map, show )
import Pux ( fromSimple, renderToDOM, start )
import Pux.Html ( Html, div, text )
import Pux.Router ( end, lit, router, sampleUrl )
import Signal ( Signal )
import Signal.DOM ( DimensionPair, windowDimensions )
import Signal.Time ( Time, every )

-- ##############
-- ### Router ###
-- ##############

data Route = Home | Login | NotFound

derive instance genericRoute :: Generic Route

instance showRoute :: Show Route where
  show = gShow

match :: String -> Route
match url = fromMaybe NotFound $ router url $
  Home <$ end
  <|>
  Login <$ (lit "login") <* end


-- ###############
-- ### Signals ###
-- ###############

routeSignal' :: forall eff. Eff (dom :: DOM | eff) (Signal Action)
routeSignal' = ( map <<< map ) ( RouteAction <<< match ) sampleUrl

windowDimensionsSignal' :: forall eff. Eff (dom :: DOM | eff) (Signal Action)
windowDimensionsSignal' = ( map <<< map ) ResizeAction windowDimensions

timeSignal :: Signal Action
timeSignal = map TimeAction (every 2000.0)


-- #############
-- ### State ###
-- #############

type State =
  { width  :: Int
  , height :: Int
  , route  :: Route
  , time   :: Time
  }

init :: { width :: Int, height :: Int } -> String -> Time -> State
init { width, height } initialPath time =
  { width  : width
  , height : height
  , route  : match initialPath
  , time   : time
  }


-- ##############
-- ### Update ###
-- ##############

data Action
  = ResizeAction DimensionPair
  | RouteAction Route
  | TimeAction Time

update :: Action -> State -> State
update action state =
  case action of
    ResizeAction dimensions ->
      state { width = dimensions.w, height = dimensions.h }
    RouteAction newRoute ->
      state { route = newRoute }
    TimeAction newTime ->
      state { time = newTime }


-- ############
-- ### View ###
-- ############

view :: State -> Html Action
view state =
  div []
    [ div [] [ text ("Width: "  <> show state.width) ]
    , div [] [ text ("Height: " <> show state.height) ]
    , div [] [ text ("Route: "  <> show state.route) ]
    , div [] [ text ("Time: "   <> show state.time) ]
    ]


-- ############
-- ### Main ###
-- ############

main = do
  routeSignal            <- routeSignal'
  windowDimensionsSignal <- windowDimensionsSignal'

  app <- start
    { initialState : init { width : 0, height : 0 } "" 0.0
    , update       : fromSimple update
    , view         : view
    , inputs       : [ routeSignal, windowDimensionsSignal, timeSignal ]
    }

  renderToDOM "#app" app.html

If you run this example and visit "/login", you will notice that neither the window dimensions nor the route have been picked up.

Why is this? Looking at

input = fromJust $ mergeMany $
, the inputs array is reversed and the signals are merged with Signal.mergeMany which calls Signal.merge under the hood.

When two signals are merged, the initial value of the first signals wins. For the example above, this means that from the inputs array [ routeSignal, windowDimensionsSignal, timeSignal ] only the TimeAction is processed.

I don't know why Elm and subsequently purescript-signal settled on this behaviour but I think we should document some workarounds for this issue here and maybe in the documentation.

Target types of events in Pux.Html.Events have the wrong type

Currently all events with a target specify target :: Target and type Target = { value :: String } which is incorrect. Inspecting the value returned in the MouseEvent shows that target is a DOM element. This could be represented by a Pux type or probably better a DOM.Node.Node so that it could be passed in directly to DOM functions.

Updating state in effects does not trigger rerender

As I experiment with timers and animations, I find that I can't replicate a basic elm animation example.

https://github.com/stratospark/pux-elm-architecture/blob/master/ex8/src/SpinSquare.purs#L64

Essentially, I'm trying to use a timer to keep on dispatching Tick actions until an animation has completed. However, I'm only seeing renders at the end of each animation cycle, not a smooth animation based on the current angle in the State. If I check the Pux Devtool, I do see that the intermediate states were recorded, but none of them triggered a render.

Any ideas? Thanks!

Move examples to branches of pux-starter-app

It'd be easier if instead of keeping examples in the pux repository, they would instead be branches of the pux-starter-app repository. That way anyone could get an example up and running by cloning that branch and running npm start.

Components

Does Pux need them? What would the API look like? Most importantly, what problem(s) do components solve?

A component is commonly understood as an encapsulation of model-view-update. Essentially, a Pux application can be seen as a single component.

The reason I opened this issue is that multiple people have asked me about components in Pux. Unfortunately, there's no clear definition of what people mean by components, and it's not clear what problems they're actually looking to solve with such an abstraction.

Routing doesn't work in IE

https://github.com/alexmingoia/purescript-pux/blob/master/src/Pux/Router.js#L37 is failing in IE. To workaround I had to use https://github.com/krambuhl/custom-event-polyfill with window.Event = window.CustomEvent . There is a more detailed discussion at krambuhl/custom-event-polyfill#6 (comment) where maintainer of custom-event-polyfill claims this should be fixed in purescript-pux.

If you find this a valid point, we can use CustomEvent implementation if browser doesn't support Event but I am not sure this should be pux concern at all.

Config's inputs only initially runs the last input.

-- | App configuration
config :: forall eff. State -> Eff (dom :: DOM | eff) (Config State Action AppEffects)
config state = do
 -- | Create a signal of URL changes.
urlSignal <- sampleUrl

-- | Map a signal of URL changes to PageView actions.
let routeSignal = urlSignal ~> \r -> PageView (match r)

pure
  { initialState: state
  , update: update
  , view: view
  , inputs: [  routeSignal
            , constant DummyOne  -- Note the addtional constant inputs
            , constant DummyTwo
            ]
  }

-- | Entry point for the browser.
main :: State -> Eff (CoreEffects AppEffects) (App State Action)
main state = do
  app <- start =<< config state
  renderToDOM "#app" app.html
  -- | Used by hot-reloading code in support/index.js
  pure app

Given inputs: [ routeSignal, constant DummyOne, constant DummyTwo], the only action initially run when you load the page will be DummyTwo, and DummyOne and PageView are never emitted.

However, if you have inputs: [routeSignal, every second ~> const DummyOne, constant DummyTwo], then, while the only action to be fired initially is DummyTwo, DummyOne will be fired every second after the website is loaded.

Compose routes and deep nested components

Hello ✋,

I'd like to organize my application in a fractal manner.
To do so, I need to be able to declare routes locally with their corresponding component. I was able to do it in JS using react + react-router's route object.

Here is an example splitting the current page into components :

image

So we have this hierarchy of components for the current page https://github.com/alexmingoia/purescript-pux/issues/new :
Application → Repository → Issues → NewIssue

The idea here is to organize these components according to their relation Parent → Child :

├── Application
│   ├── Repository
│   │   ├── Issues
│   │   │   ├── IssueList.purs
│   │   │   └── NewIssue.purs
│   │   └── Issues.purs
│   └── Repository.purs
└── Application.purs

We can identify "routable" components which display different children based on the current URI. If we imagine that each "routable" component consume a part of the URI as we go deep in the hierarchy we can say that :

  • Application displays its child Repository when the URI is :username/:projectname
  • Repository displays its child Issues when the URI is issues
  • Issues displays its child NewIssue when the URI is new and its child IssueList when the URI is / or `` (this case can be considered as its index route)

In that way, a "routable" component just needs to care about its own part of the URI, and know nothing outside of its direct children.

For the routes definition, we could add a Route data type to each "routable" component definition in addition to the usual State, Action, init, update, view.

-- Application.purs
data Route = Repository

-- Repository.purs
data Route = Issues

-- Issues.purs
data Route = IssueList | NewIssue

This is where I stopped, now I'm looking for a way to declare local match functions, consume and pass the current URI to children so I can repeat the pattern down the hierarchy. 🆘

Make (!) operator work without needing (#)

I love the shorthand syntax for specifying HTML elements. However, some elements don't have children, for example:

img ! href "foo.jpg"

Unfortunately this doesn't work 😭

Thoughts about CSS handling

This is supposed to be a discussion thread about CSS in Pux.

Currently - due to its use of react - Pux's style function takes a record of camelCased versions of CSS properties.

myStyle = { backgroundColor : "red" }

As it stands, there are a few things that I think could be improved:

Type safety

Currently, errors like

myStyle = { backgroundColor : "re" }

or

myStyle = { backgroundColo : "red" }

are not caught at compile time. By using a solution like purescript-css, this could be remedied.

Merging CSS

In PureScript, it's not (yet) possible to combine two records into one.

As far as I can tell, things like

const myStyleWithRedBorder =
  { ...myStyle
  , border : "1px solid red"
  }

are not possible right now in Pux.

For this issue, one could adopt Elm's approach of style taking an Array of (key, value) pairs.

@alexmingoia I'm sure you have already thought about these issues and so I'd be curious to know your stance on this.

Impossible to get the element that was clicked?

I use onClick on a bunch of div elements and the Action handler needs to know which of those div elements was clicked.

It does not look like there is a way to do this?

There is event.target.value (a String) but the browser craps out when trying to access this field:

[Error] TypeError: undefined is not an object (evaluating 'val.constructor')
	(anonymous function) (Anonymous Script 16 (line 20))
	stringify
	stateToString (Anonymous Script 16 (line 19))
	(anonymous function) (Anonymous Script 17 (line 243))
	(anonymous function) (Anonymous Script 18 (line 75))
	(anonymous function) (Anonymous Script 19 (line 25))
	(anonymous function) (Anonymous Script 19 (line 14))
	forEach
	set (Anonymous Script 19 (line 14))
	(anonymous function) (Anonymous Script 19 (line 25))
	(anonymous function) (Anonymous Script 19 (line 14))
	forEach
	set (Anonymous Script 19 (line 14))
	(anonymous function) (Anonymous Script 19 (line 57))
	(anonymous function) (Anonymous Script 19 (line 14))
	forEach
	set (Anonymous Script 19 (line 14))
	(anonymous function) (Anonymous Script 19 (line 14))
	forEach
	set (Anonymous Script 19 (line 14))
	(anonymous function) (Anonymous Script 20 (line 16))
	(anonymous function) (Anonymous Script 21 (line 12))
	(anonymous function)
	dispatchEvent
	invokeGuardedCallback (Anonymous Script 22 (line 70))
	executeDispatch (Anonymous Script 23 (line 89))
	executeDispatchesInOrder (Anonymous Script 23 (line 112))
	executeDispatchesAndRelease (Anonymous Script 24 (line 44))
	forEach
	forEachAccumulated (Anonymous Script 25 (line 25))
	processEventQueue (Anonymous Script 24 (line 231))
	runEventQueueInBatch (Anonymous Script 26 (line 18))
	handleTopLevel (Anonymous Script 26 (line 29))
	handleTopLevelImpl (Anonymous Script 27 (line 73))
	perform (Anonymous Script 9 (line 138))
	batchedUpdates (Anonymous Script 10 (line 63))
	batchedUpdates (Anonymous Script 11 (line 98))
	dispatchEvent (Anonymous Script 27 (line 150))
	dispatchEvent

Is this related to #40?

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.