So, your "data model" is a list of users but your "view model" is a boolean
state for each user? I'm working on thinking about how that would come up
but I guess the checkboxes could be for selection and the selection could
be view specific.

Anyway, here is my quick effort at breaking this out. I'm going to assume
that we can represent users with strings. If they need more data, then
there is presumably a way to convert a user into a user-id for internal use
and a string for display. So, our data model is:

type alias User = String

type alias DataModel = List User


-- In model:

dataModel : DataModel


The view model consists of the information for each checkbox which assuming
it's a checkbox really is just a boolean state. We maintain this as a
dictionary indexed by user:

type alias ViewModel = Dict User Bool


-- In model:

viewModel : ViewModel

The view function can now map over the data model (list of users) and
extract the extra view model information by consulting the dictionary.

Changes to the checkbox state, just write values back into the view model
dictionary.

Changes to the user list need to update the data model but then they also
need to update the view model to remove any users no longer on the list and
add entries for any users newly on the list. (Alternatively, one could use
the initial state for users as a default and use Maybe.withDefault to
handle new users. Then one just needs to remove the view model entries for
users no longer in the data model.)

The key thing in structuring this is that you can build all of the logic
that works on the data model independent of the checkbox states needed by
the view model. For example, if these checkboxes represent selected users,
you could have the changes to the data model be driven by a set of users.
Events would flow into the view model collecting up this selected set and
then turn into a message or operation for the data model. That change to
the data model would then result in an update to the view model.

Potential type signatures could be:

view : DataModel -> ViewModel -> Html ViewMsg

updateView : ViewMsg -> ViewModel -> (ViewModel, Cmd ViewMsg, Maybe DataMsg)

updateData: DataMsg -> DataModel -> (DataModel, Cmd DataMsg)

updateViewForData: DataModel -> ViewModel -> (ViewModel, Cmd ViewMsg)

Then we get something like the following at the model level:

type alias Model = { dataModel : DataModel, viewModel : ViewModel }

type Msg = ToData DataMsg | ToView ViewMsg

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =

case msg of

ToData dataMsg ->

let

(newData, dataCmd ) = updateData dataMsg model.dataModel

(newView, viewCmd ) = updateViewForData newData model.viewModel

in

( { model | dataModel = newData, viewModel = newView }

, Cmd.batch [ Cmd.map ToData dataCmd, Cmd.map ToView viewCmd ]

)

ToView viewMsg ->

updateView viewMsg model.viewModel

|> OutMessage.mapComponent (\newView -> { model | viewModel = newView })

|> OutMessage.mapCmd ToView

|> OutMessage.evaluateMaybe (\dataMsg m -> update (ToData dataMsg) m)


(You can find OutMessage here:
http://package.elm-lang.org/packages/folkertdev/outmessage/1.0.2/OutMessage)

This does have the potential for pinging back and forth between data model
updates and view updates but that's a hazard in any two component system.
The key thing is that the model can be built and in operation can evolve
independent of the view or views. The potentially more ugly problem comes
if it is difficult to factor the response to a view message into logic that
can just update the current view model and then generic logic that can
respond to a data model change.

Mark

On Mon, Oct 24, 2016 at 4:29 AM, Oliver Searle-Barnes <[email protected]>
wrote:

> @Mark this is a pattern I've been exploring recently. An area that I
> haven't found a solution to is keeping the view model in sync with the
> shared models. For instance, let's say I have a view with a list of
> checkboxes next to each user. The state for the checkboxes would be kept in
> the view model, but then I need to manage adding/removing checkboxes as the
> list of users changes. Currently I handle this by having a default state
> for the checkbox which is used until a user actually clicks on a checkbox
> at which point the state is added to the view model. This works but is
> semantically awkward and doesn't take care of removing the checkbox state
> if a user is removed from the shared list. https://github.com/elm-lang/
> html/issues/19 provides a potential avenue for cleanup, but even then the
> lifecycle seems a little awkward.
>
> I wonder if you have a different approach for this problem?
>
>
> On Sunday, 23 October 2016 21:29:23 UTC+2, Mark Hamburg wrote:
>>
>> We're just getting into having an app where this is likely to matter, but
>> if you have multiple views onto the same data, then the pattern that would
>> seem to apply is to have a single data model and multiple view models. The
>> data model handles the shared part. The view models handle things specific
>> to the particular view — e.g., is a spin down open, what's the scroll
>> position, etc. The view functions would take the data model and their
>> particular view model as parameters and would generate appropriately tagged
>> messages back. This avoids significant nesting while still providing some
>> structure to keep pieces that can be separated separate.
>>
>> There are then a couple ways to structure the multiple view models. One
>> is to have a view selector but keep all of the view models alive all the
>> time. The benefit of this is that when you switch back to a view, it will
>> remember where you were. The downside is that one is carrying around — and
>> potentially updating if the view model depends on the data model — an
>> increasing number of view models if you have an increasing number of views.
>> The alternative is to put the view models into a union type and have one
>> model entry that serves as both view selector and view model storage. This
>> keeps things lighter weight but means that you have nowhere to maintain
>> context when switching back and forth.
>>
>> Above all of this, the other key subdivision we are making in our SPA and
>> that I would recommend having seen where other programmers often first
>> head, is having a top layer that handles overall app state — e.g., logged
>> in v not logged in — as a type union. This can also often be where the
>> routing logic plugs in. People who haven't spent a lot of time thinking in
>> terms of type unions tend to load their model up with a lot of fields that
>> only have meaning in certain states. This then leads to more invariants
>> about the state that can't be enforced by the compiler. Breaking things up
>> into different type cases for different states allows one to make more bad
>> states simply impossible. For example, our logging in state has a
>> RemoteData.WebData User reflecting whether we've gotten valid user data
>> back from the server but our logged in state simply has a User. The top
>> level event dispatcher works with the following type signature on the log
>> in state update:
>>
>> update : Login.Msg -> Login.Model -> ( Login.Model, Cmd Login.Msg, Maybe
>> User )
>>
>>
>> After each call to update, it can check to see whether we now have a
>> valid user and if so switch to the logged in state, initializing it with
>> the supplied user.
>>
>> This isn't as flat as some people go, but it doesn't spread things out
>> into lots of components and the top layer(s) are pretty simple.
>>
>> Mark
>>
>>
>> On Sun, Oct 23, 2016 at 11:39 AM, Francois <[email protected]>
>> wrote:
>>
>>> Hi,
>>>
>>> I'm really new to Elm. I'm not good enough to propose a guideline about
>>> Elm app structure but as a novice a guideline can help lots of people.
>>>
>>> I worked / works on a large angular application and John Papa Styleguide
>>> has been really helpful at the beginning.
>>> So writing a community guideline can be a great resource. (there is
>>> another thread inter component that seems related)
>>>
>>> For example, on the server side, structuring domain core by features is
>>> good thing (for me). Help define some boundaries and help maintability.
>>> Grouping all Java classes by technical aspect at the project root : all
>>> services in one package, all models in one package can be tedious.
>>>
>>> On the front, if I correctly understood this thread :
>>> - a unique Msg / Model inside Main.elm can be a good starting point and
>>> easy to refactor later.
>>> - separating the view per feature (only view functions) : advice taken
>>> from Dave
>>> - break Model / Msg when too big (definition of "too big" is perhaps not
>>> easy, or someone can give some advices to detect "a too big Msg"). At this
>>> point, break Msg / Model / Update by feature. It is not all or nothing,
>>> just extract at first one feature and put them inside a file. Then the Main
>>> module wraps the feature (using map inside the Update).
>>>
>>> Can we imagine starting a community guideline somewhere ? adding
>>> examples later.
>>> Really easy for me to say that.
>>>
>>>
>>> --
>>> 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 [email protected].
>>> For more options, visit https://groups.google.com/d/optout.
>>>
>>
>> --
> 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 [email protected].
> For more options, visit https://groups.google.com/d/optout.
>

-- 
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 [email protected].
For more options, visit https://groups.google.com/d/optout.

Reply via email to