I don't know what I was thinking when I wrote that. The code I just provided can't actually cycle. We can go from updateView to updateData to updateViewForData, but then we're done.
Mark On Mon, Oct 24, 2016 at 12:36 PM, Mark Hamburg <[email protected]> wrote: > 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/ht >> ml/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.
