orus-io / elm-spa Goto Github PK
View Code? Open in Web Editor NEWPure Elm library to easily build Single Page Applications
Home Page: https://package.elm-lang.org/packages/orus-io/elm-spa/latest/
License: MIT License
Pure Elm library to easily build Single Page Applications
Home Page: https://package.elm-lang.org/packages/orus-io/elm-spa/latest/
License: MIT License
Hi. It seems like my whole application loses state each time i do some code changes, have you encountered the same issues? The time travel debugger resets and loses state and I get rerouted back to my start page.
Hi again. Have you given any thoughts about supporting elm-app-url ?
Just looking at the README, seems redundant to apply the same mappers
function to each invocation of addProtectedPage
, addPublicPage
.
Can it not be added to the configuration object?
I'd argue the same could be said for the match
functions... Those could just all be one big Route.toPage : Url -> Page
function passed to the config.
type Page
= NotFound
| Page1
| Page2
toPage : Url -> Page
toPage url =
case url.path of
["stuff"] ->
Page1
["things"] ->
Page2
_ ->
NotFound
Or if the Maybe
is used as a means of building the NotFound
use case into the framework
type Page
= Page1
| Page2 { id : Maybe Int }
toPage : Url -> Maybe Page
toPage url =
case url.path of
["stuff"] ->
Just Page1
["things"] ->
Just (Page2 (parseId url.query))
_ ->
Nothing
Is there a way to prevent calling the init
function when the route changes (to the same page)?
Something like sending a FlagsChanged
message when the route is same with different flags.
Use case would be a search field that reflects its state in the url. Currently it reloads the page on every keystroke.
PS: Thanks for the great package!
Hi. I'm having some issues trying to add subpages, do you have any suggestion how to solve it?
Hi, as always thanks for the great work on elm-spa!
I have an issue, it might just be that I've missed something but I hope maybe you could provide an example of how to do this.
So I have created an own module Confirm.elm
which is a dialog with a confirm msg. some use case could be "are you really sure you want to delete this whatver{id}. I want to make a general function in Effect.elm
which can be called on whatever page am I on. The problem here is that I struggle with the mapping of messages.
Lets say i have a Page.elm
where i have a Msg DeleteWhatever Int
, from here when i call the ConfirmDialog from Shared.elm
i need some kind of mapping from Page Msg
to Shared Msg
.
I've been stuck on this for a long time and I really hope that you guys could help me out. I'll try to provide as much as info as i can for you to understand my implementation.
this is the implementation of Confirm.elm
{-| Contains a general type and combinators for a confirmation dialog.
You should prefer to use `init` and builder functions
rather than constructing this manually.
-}
type alias Confirm msg =
{ title : String
, prompt : String
, cancelText : String
, confirmText : String
, attributes : List (H.Attribute msg)
, clickOutside : Bool
, onConfirm : msg
, onCancel : Maybe msg
}
{-| Maps a function over `ConfirmConfig`
-}
map : (a -> msg) -> Confirm a -> Confirm msg
map f confirm =
{ title = confirm.title
, prompt = confirm.prompt
, confirmText = confirm.confirmText
, cancelText = confirm.cancelText
, attributes = List.map (A.map f) confirm.attributes
, clickOutside = confirm.clickOutside
, onConfirm = f confirm.onConfirm
, onCancel = Maybe.map f confirm.onCancel
}
{-| Set the title of a confirm.
-}
setTitle : String -> Confirm msg -> Confirm msg
setTitle title confirm =
{ confirm | title = title }
{-| Set the prompt text of a confirm.
-}
setPrompt : String -> Confirm msg -> Confirm msg
setPrompt prompt confirm =
{ confirm | prompt = prompt }
{-| Set the cancel button text of a confirm.
-}
setCancelText : String -> Confirm msg -> Confirm msg
setCancelText cancelText confirm =
{ confirm | cancelText = cancelText }
{-| Set the confirm button text of a confirm.
-}
setConfirmText : String -> Confirm msg -> Confirm msg
setConfirmText confirmText confirm =
{ confirm | confirmText = confirmText }
{-| Set the container attributes of a confirm.
The given attribute list will be applied to the outermost
dialog container.
-}
setAttributes : List (H.Attribute msg) -> Confirm msg -> Confirm msg
setAttributes attributes confirm =
{ confirm | attributes = attributes }
{-| Close the confirm dialog when the user clicks on
the overlay outside the dialog.
-}
closeOnClickOutside : Confirm msg -> Confirm msg
closeOnClickOutside confirm =
{ confirm | clickOutside = True }
{-| Set the confirm action of a confirm.
-}
setOnConfirm : msg -> Confirm msg -> Confirm msg
setOnConfirm onConfirm confirm =
{ confirm | onConfirm = onConfirm }
{-| Set the cancel action of a confirm.
-}
setOnCancel : msg -> Confirm msg -> Confirm msg
setOnCancel onCancel confirm =
{ confirm | onCancel = Just onCancel }
{-| `Confirm` with default values.
Using this function and provided builder functions should
be your preferred approach when creating `Confirm` instances.
-}
init : msg -> Confirm msg
init onConfirm =
{ title = "Confirm"
, prompt = ""
, cancelText = "Cancel"
, confirmText = "Confirm"
, attributes = []
, clickOutside = False
, onConfirm = onConfirm
, onCancel = Nothing
}
This is the implementation of main.elm
mappers : ( (a -> b) -> PageView a -> PageView b, (c -> d) -> PageView c -> PageView d )
mappers =
( PageView.map, PageView.map )
toDocument : Shared -> PageView (Spa.Msg Shared.Msg pageMsg) -> Document (Spa.Msg Shared.Msg pageMsg)
toDocument shared view =
View.baseView shared view
main =
Spa.init
{ defaultView = PageView.defaultView
, extractIdentity = Shared.identity
}
|> Spa.addProtectedPage mappers (Route.matchAppRoot Route.Page1) Page1.page
|> Spa.addProtectedPage mappers (Route.matchAppRoot Route.Page2) Page2.page
|> Spa.beforeRouteChange Shared.beforeRouteChange
|> Spa.application PageView.map
{ init = Shared.init
, subscriptions = Shared.subscriptions
, update = Shared.update
, toRoute = Route.toRoute
, toDocument = toDocument
, protectPage = Route.toUrl >> Just >> Route.SignIn >> Route.toUrl
}
|> Spa.onUrlRequest Shared.clickedLink
|> Browser.application
this is PageView.elm
type alias PageView msg =
{ dialogs : List (Dialog msg)
, view : Html msg
}
init : Html msg -> PageView msg
init view =
{ dialogs = []
, view = view
}
setView : Html msg -> PageView msg -> PageView msg
setView view pageView =
{ pageView | view = view }
setDialogs : List (Dialog msg) -> PageView msg -> PageView msg
setDialogs dialogs pageView =
{ pageView | dialogs = dialogs }
map : (a -> msg) -> PageView a -> PageView msg
map f pageView =
{ dialogs = List.map (Dialog.map f) pageView.dialogs
, view = H.map f pageView.view
}
defaultView : PageView msg
defaultView =
init
(H.div []
[ H.text
"You should not see this page unless you forgot to add pages to your application"
]
)
and this is the baseView in View.elm
baseView : Shared -> PageView (Spa.Msg Shared.Msg pageMsg) -> Document (Spa.Msg Shared.Msg pageMsg)
baseView shared { view, dialogs } =
{ title = ""
, body =
viewHeader shared
:: navigationRow shared.environment shared.currentRoute
:: H.div [ A.class "page_content_container" ]
[ view
]
:: Dialog.viewList dialogs
:: (shared.toasts
|> Toast.viewList
|> H.map (\( id, msg ) -> Spa.mapSharedMsg (Shared.setToastMsg id msg))
)
:: List.map
(\error ->
error
|> Dialog.fromError shared.environment.language (Spa.mapSharedMsg Shared.closeErrorDialog)
|> Dialog.view
)
(List.take 1 shared.errors)
++ List.map
(\confirm ->
confirm
|> Confirm.setOnCancel (Spa.mapSharedMsg Shared.closeConfirmDialog)
|> Confirm.setOnConfirm (Spa.mapSharedMsg Shared.confirmConfirmDialog)
|> Dialog.fromConfirm
|> Dialog.view
)
(List.take 1 shared.confirms |> List.map (\x -> Confirm.map Spa.mapSharedMsg x))
}
so far i have this in Shared.elm
and this is where my problems start
type alias Shared =
{ key : Nav.Key
, identity : Maybe Identity
, currentRoute : Maybe Route
, preventReroute : Bool
, confirms : List (Confirm Msg)
}
type Msg
= ReplaceRoute Route
| BeforeRouteChange Route
| ShowConfirmDialog (Confirm Msg)
| CloseConfirmDialog
| ConfirmHeadConfirmDialog
| ClickedLink Browser.UrlRequest
update : Msg -> Shared -> ( Shared, Cmd Msg )
update msg shared =
case msg of
ShowConfirmDialog confirm ->
( { shared | confirms = confirm :: shared.confirms }, Cmd.none )
CloseConfirmDialog ->
case shared.confirms of
confirm :: rest ->
( { shared | confirms = rest }
, confirm.onCancel
|> Maybe.map TaskUtil.doTask
|> Maybe.withDefault Cmd.none
)
[] ->
( shared, Cmd.none )
ConfirmHeadConfirmDialog ->
case shared.confirms of
confirm :: rest ->
( { shared | confirms = rest }, TaskUtil.doTask confirm.onConfirm )
[] ->
( shared, Cmd.none )
--THIS IS WRONG
showConfirmDialog : Confirm Msg -> Msg
showConfirmDialog confirm =
ShowConfirmDialog confirm
and for a use case in Page.elm
ClickConfirmDialog ->
let
confirmDialog =
Confirm.init (DeleteWhatever 2000)
|> Confirm.setPrompt "Are you sure you want to do this?"
in
model |> Effect.withShared (Shared.showConfirmDialog confirmDialog)
With ryannhg's elm-spa you can go to a specific page with a url e.g. http://localhost:8000/time.
It would be nice to also have this functionality in this package.
Hi, thanks for developing this framework! ๐
I have a question about how routes handle flags.
Currently, the flags are defined in a Route.elm
module, in which are defined the functions
toUrl : Route -> String
route : Parser (Route -> a) a
that handle rendering and parsing the flags through URL parameters.
My question is: would it be possible to pass flags to each route without exposing them in the URL?
For example, protected pages can receive an Identity
and fall back to a specified page if not provided, could that behavior be extended to generic values passed as flags?
I need the Url
in the init
function to add the current route to the shared state on page load.
I added this in a local copy of the package and it is just a minor change and works fine.
If there is a better way to do this, let me know ๐
With ryannhg's elm-spa you can create a shared view that has access to the SharedMsg and to the current route, because the Model and the Msg is in the Main module. Because these are not accessible in this package, this is currently not possible. Official support would be nice to have.
For a better understanding here's what I currently use for the time being. (I'm sure there are better ways to do his):
application :
{ toDocument : shared -> view -> Document (Msg sharedMsg pageMsg)
, sharedView : Maybe ((sharedMsg -> Msg sharedMsg pageMsg) -> route -> shared -> view -> view)
}
-> Builder flags route identity shared sharedMsg view current previous pageMsg
->
{ init : flags -> Url -> Nav.Key -> ( Model route shared current previous, Cmd (Msg sharedMsg pageMsg) )
, view : Model route shared current previous -> Document (Msg sharedMsg pageMsg)
, update : Msg sharedMsg pageMsg -> Model route shared current previous -> ( Model route shared current previous, Cmd (Msg sharedMsg pageMsg) )
, subscriptions : Model route shared current previous -> Sub (Msg sharedMsg pageMsg)
, onUrlRequest : UrlRequest -> Msg sharedMsg pageMsg
, onUrlChange : Url -> Msg sharedMsg pageMsg
}
application { toDocument, sharedView } builder =
let
view_ =
case sharedView of
Just sharedView_ ->
\model -> builder.view model |> (\view -> toDocument (modelShared model) (sharedView_ SharedMsg (modelRoute model) (modelShared model) view))
Nothing ->
\model -> builder.view model |> toDocument (modelShared model)
in
{ init = builder.init
, view = view_
, update = builder.update
, subscriptions = builder.subscriptions
, onUrlRequest = UrlRequest
, onUrlChange = UrlChange
}
modelRoute : Model route shared current previous -> route
modelRoute ( core, _ ) =
core.currentRoute
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.