Initial Window.dimensions

688 views
Skip to first unread message

Rudolf Adamkovič

unread,
Jan 27, 2015, 6:49:54 PM1/27/15
to elm-d...@googlegroups.com
Every time my Elm program starts, I need to resize the browser to trigger Window.dimensions signal that causes my view to size properly.

Here's my action signal:

action : Signal Action

action =

    Signal.merge

        (Signal.map UpdateDimensions Window.dimensions)

        (Signal.map UpdateTime time)


Can I somehow trigger Window.dimensions signal when the program starts?

Probably not. So, what am I missing here? :)

Thanks!

Jeff Smits

unread,
Jan 28, 2015, 3:33:08 AM1/28/15
to elm-discuss
Window.dimensions start with the correct initial value. Merge propagates the left arguments initial value. So the problem is probably not in the code you shared. 
But you can try debugging with `main = Text.asText <~ action` to see what the initial value is. If time updates too quickly, use `main = Text.asText <~ Signal.sampleOn (constant ()) action` to only get the initial value of the signal. 

--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Rudolf Adamkovič

unread,
Jan 28, 2015, 12:58:28 PM1/28/15
to elm-d...@googlegroups.com
Thanks a lot, Jeff. That was very helpful. Actually, the very first action I get is UpdateDimensions. I will investigate further.

R+

Joseph Collard

unread,
Jan 28, 2015, 1:06:07 PM1/28/15
to elm-d...@googlegroups.com
I had the same issue back in 2013: https://groups.google.com/forum/#!searchin/elm-discuss/jcollard$20window/elm-discuss/pevppMaHyyA/EA5nGei5ltQJ

Basically, the issue is that Window.dimensions doesn't actually fire when the page loads.

Rudolf Adamkovič

unread,
Jan 28, 2015, 1:56:16 PM1/28/15
to elm-d...@googlegroups.com
It does, actually. Check my last message. It must be something else in my program.

R+

Sent from my Mac

Max Goldstein

unread,
Jan 28, 2015, 5:17:21 PM1/28/15
to elm-d...@googlegroups.com
Mouse.position, on the other hand, starts at (0,0). From what I understand there is no easy way to fix this.

Rudolf Adamkovič

unread,
Jan 28, 2015, 5:47:57 PM1/28/15
to elm-d...@googlegroups.com
All right guys, I need some serious help here. I’m stuck.

Here’s some more code:

main : Signal Element
main = 
    Signal.map view model

model : Signal Model
model =
    Signal.foldp update initialModel action

update : Action -> Model -> Model
update action model =
    case action of
        UpdateDimensions dimensions -> { model | dimensions <- dimensions }
        ...

action : Signal Action
action =
    Signal.merge
        (Signal.map UpdateDimensions Window.dimensions)
        ...

R+

Sent from my Mac

On Jan 28, 2015, at 23:17, Max Goldstein <maxgol...@gmail.com> wrote:

Mouse.position, on the other hand, starts at (0,0). From what I understand there is no easy way to fix this.

Janis Voigtländer

unread,
Jan 29, 2015, 12:19:33 AM1/29/15
to elm-d...@googlegroups.com

Your action : Signal Action has an initial value of UpdateDimensions .... That is not the same has having an event firing. Signal.foldp applies the update function only when an event fires on its input signal. In general, in foldp f s sig, the initial value of sig is completely ignored. The initial value of the result signal is simply s, not something made from the initial value of sig (via f or whatever means). If different behavior is desired, like in your case, consider using foldp' from http://package.elm-lang.org/packages/Apanatshka/elm-signal-extra/3.2.1/Signal-Extra instead.

Rudolf Adamkovič

unread,
Jan 31, 2015, 5:33:36 PM1/31/15
to elm-d...@googlegroups.com
All right, I’ve figured this one out. I’ve realized that I don’t need to store window dimensions in my model in order to do layout. My joy is back. :)

Thanks everybody for helping me out, I really appreciate it.

R+

Sent from my Mac

Elliott Jin

unread,
Feb 29, 2016, 12:22:02 AM2/29/16
to Elm Discuss
I came across this thread when I was trying to figure out how to keep the contents of an Elm app in the exact center of the browser window.  I originally tried adding "width" and "height" to the model and mapping Window.dimensions into a signal of Actions, but I ran into the same problem as the OP.

For posterity, here's the approach I ended up using:

main =

    let 

        centerInWindow (width, height) content =

            fromElement

                <| container width height middle

                <| toElement contentWidth contentHeight content


    in  

        Signal.map2

            centerInWindow

            Window.dimensions

            app.html

Mark Hamburg

unread,
Feb 29, 2016, 3:52:37 PM2/29/16
to Elm Discuss
Having worked with other reactive systems — e.g., Flapjax — this is a case where the complexity of having both "events" and "behaviors" may be warranted over the simplicity of just having "signals". Like behaviors, signals always have values but we don't always pay attention to the initial value. This hurts in cases where we would like to pay attention to the initial value (e.g., Window.dimensions) and in cases where we know we won't pay attention (e.g., the initial value for a mailbox that will receive actions to fold over).

The Flapjax model would lead to something like the following:

initialValue : Behavior a -> a
events : Behavior a -> Event a -- roughly "changes" but it might include duplicates
fold : (a -> b -> b) -> b -> Event a -> Behavior b
-- special case of fold:
withInitialValue: a -> Event a -> Behavior a

Further, it would provide appropriate filter functions for events and map functions for both events and behaviors.

Of note relative to Flapjax, this does not provide a way to get the current value of a Behavior. It only provides access to the initial value.

I'm pretty sure this could all be implemented as a veneer on top of signals though to build a behavior equivalent for Window.dimensions would take deeper surgery.

Would it be worth it? That's harder to say. On the one hand, it uses the type system to convey what turns out to be an important distinction (good). On the other hand, my experience introducing people to Flapjax suggests that this is a distinction that people have a hard time grasping why it matters.

Mark

Richard Osafo

unread,
Mar 1, 2016, 5:54:29 AM3/1/16
to Elm Discuss
Hi,
How did you fix it?
I'm having the same issue. In my case, it works in Chrome but not IE or Edge.

sizeChanged =
  Window.dimensions |> Signal.map SizeChanged

contextChanged =
  AppContext.signal |> Signal.map UpdateContext


app =
  App.start
    { init = init AppContext.defaultState
    , update = update
    , view = view
    , inputs =  [ sizeChanged, contextChanged]
    }



regards,
Richard.

trotha01

unread,
Mar 3, 2016, 10:25:25 PM3/3/16
to Elm Discuss
+1

I have this issue as well when using Window.dimensions in start-app

Mark Hamburg

unread,
Mar 18, 2016, 2:14:07 PM3/18/16
to Elm Discuss
Here is the code I just wrote to generalize solutions for this problem:

module SamplingStartApp (Config, App, start) where

import Html
import Effects
import StartApp
import Task

{-| SamplingStartApp is much like StartApp but allows sampling an `initParam`
signal when building the initial model. This functionality is represented in
the replacement of the pre-built model and effects `init` config option from
StartApp with a function to create a model and any initial effects based on
values sampled from a new `initParam` signal. Because we won't have a model
right away, we also won't have a view. Hence, the configuration options now
also include a initial placeholder view (`initialView`). Simplarly, lack of
an initial model changes the resulting `model` signal in the resulting app
to a `Maybe Model` valued signal. When we do construct the initial model,
we run any actions that have come in from inputs before the initialization.
This is useful for some inputs but for signals sampled via `initParam`, we
won't want to see stale values. Hence, we introduce a `postInitInputs` list
of action-valued signals that will be ignored until after the initialization
is processed.
-}

type alias Config model action param view =
  { init : param -> (model, Effects.Effects action)
  , initParam : Signal.Signal param
  , update : action -> model -> (model, Effects.Effects action)
  , view : (Signal.Address action) -> model -> view
  , initialView : view
  , inputs : List (Signal.Signal action)
  , postInitInputs : List (Signal.Signal action)
  }


type alias App model =
    { html : Signal Html.Html
    , tasks : Signal (Task.Task Effects.Never ())
    , model : Signal (Maybe model)
    }


start : Config model action param Html.Html -> App model
start config =
    let
        initSignal =
            Signal.sampleOn startupMailbox.signal config.initParam
            |> Signal.map Init
        sendStartup =
            Signal.send startupMailbox.address ()
            |> Task.toResult
            |> Task.map ( \_ -> Nop )
            |> Effects.task
        startApp = StartApp.start
            { init = ( Waiting [], sendStartup )
            , update = updateWrapped config.init config.update
            , view = viewWrapped config.view config.initialView
            , inputs =
                initSignal ::
                List.append
                    (List.map (Signal.map (Forward False)) config.inputs)
                    (List.map (Signal.map (Forward True)) config.postInitInputs)
            }
    in
        { html = startApp.html
        , tasks = startApp.tasks
        , model = startApp.model
            |> Signal.map (\wrappedModel ->
                case wrappedModel of
                    Waiting actions -> Nothing
                    Running model -> Just model)
        }

{- The model is either waiting and accumulating actions (in reverse order) or
is running.
-}
type WrappedModel model action
    = Waiting (List action)
    | Running model

{- We can receive actions to do nothing (used for the task which sends to the
startup mailbox), cause initialization (we should only see this once), or wrap
an action to be forwarded.
-}
type WrappedAction action params
    = Nop
    | Init params
    | Forward Bool action


updateWrapped :
  (startParams -> (model, Effects.Effects action))
  -> (action -> model -> (model, Effects.Effects action))
  -> WrappedAction action startParams
  -> WrappedModel model action
  -> (WrappedModel model action, Effects.Effects (WrappedAction action startParams))
updateWrapped init update wrappedAction wrappedModel =
  case wrappedAction of
    Nop -> ( wrappedModel, Effects.none )
    Init startParams ->
      case wrappedModel of
        Waiting actions ->
          let
            initialState = init startParams
            (finalModel, finalEffects) =
              actions |> List.foldr (applyUpdate update) initialState
                -- fold from right since we add values at the head
          in
            (Running finalModel, finalEffects |> Effects.map (Forward False))
        Running model ->
          -- This should never happen!
          (wrappedModel, Effects.none)
    Forward postInitOnly action ->
      case wrappedModel of
        Waiting actions ->
            if postInitOnly then
                (wrappedModel, Effects.none)
            else
                (Waiting (action :: actions), Effects.none)
        Running model ->
          let
            (newModel, effects) = update action model
          in
            (Running newModel, effects |> Effects.map (Forward False))


applyUpdate :
  (action -> model -> (model, Effects.Effects action))
  -> action
  -> (model, Effects.Effects action)
  -> (model, Effects.Effects action)
applyUpdate update action (incomingModel, incomingEffects) =
    let
      (newModel, newEffects) = update action incomingModel
    in
      (newModel, Effects.batch [ incomingEffects, newEffects ] )


viewWrapped :
    (Signal.Address action -> model -> view)
    -> view
    -> (Signal.Address (WrappedAction action params))
    -> WrappedModel model action
    -> view
viewWrapped view initialView wrappedAddress wrappedModel =
  case wrappedModel of
    Waiting actions -> initialView
    Running model -> view (Signal.forwardTo wrappedAddress (Forward False)) model


startupMailbox : Signal.Mailbox ()
startupMailbox =
    Signal.mailbox ()


This then allows code like the following:

app = SamplingStartApp.start
    { init = makeInitialModelAndEffectsForWindowWidth -- makeInitialModelAndEffectsForWindowWidth : Int -> (Model, Effects.Effects Action)
    , initParam = Window.width
    , update = update -- as usual
    , view = view -- as usual
    , initialView = text "Waiting to initialize..."
    , inputs = []
    , postInitInputs = [ Window.width |> Signal.map WindowWidth ]
    }

-- Now set main and tasks as usual.

Mark

Reply all
Reply to author
Forward
0 new messages