Composing Programs in the Elm Architecture - elm

Suppose I want to create a webpage with two components, say a Navbar and a Body. These two components do not interact with each other and can be developed independently. So, I have two elm files which have the following components in each of them:
type Model = ...
type Msg = ...
init : (Model, Cmd Msg)
update : Msg -> Model -> (Model, Cmd Msg)
view : Model -> Html Msg
Assumming that they both work fine, how do we compose them to make a program which has both these components?
I tried writing something like this:
type Model = {body : Body.Model , navbar : Navbar.Model}
type Msg = BodyMsg Body.Msg | NavbarMsg Navbar.Msg
view : Model -> Html Msg
view model = div [] [Body.view model.body, Navbar.view model.navbar]
update : Msg -> Model -> (Model, Cmd Msg)
update = ...
The above gets ugly quickly when I try to write this update function. In particular, once I extract the Msg's out of the Cmd's update functions from Navbar.update or Body.update, how do I extract them and feed them back to these functions again? Also, the view function above does not particularly look idiomatic.
What is the elm-architecture recommended way to solve this problem? Is this pattern idiomatic in elm-architecture?

I think #dwaynecrooks covered the technical aspects of the question. But I believe your problem implies a design aspect too.
How to grow Elm code?
As others pointed out: thinking in terms of components will almost certainly take you down a not so appealing path with Elm. (Many people start there. I and my team started there over 2 years ago, and it took us 3 apps/major redesigns to get to a point where I think we can be happy at least about the fundamentals.)
Instead of components, I suggest you should think of your Elm application as a tree. Each node of your tree represents a level of abstraction, and describes the behavior of your application on that level. When you have the feeling that there is too much detail for a given level, you can start thinking about how new, lower levels of abstraction could be introduced as child nodes.
In practice each node is implemented in its own Elm module: parents import their children. You may also consider that you don't have to stick to the usual model/update/view signatures, rather you should focus on the particularities of your app's domain. This is what – in my read – Richard Feldman is doing in his Real World SPA example app. And Evan's Life of a file talk is related to this question too.
The case of navbar + body
Regarding your particular case – it is not a rare case – here is my experience. If we say that our webapp has a navbar and then some body, this is a pretty static description of the app. This kind of description may fit a component based thinking, but it is less helpful if you want to end up with an elegant Elm app.
Instead, it is worth trying to describe the behavior of your app at this level of abstraction, which may sound something like this: The user can select x,y,z items in a navbar. Clicking on those items will affect the item in q way and also affect the body in either a, or b way. He can also click on v in the navbar, which would show a popup or do w, which logs him out of the app.
If you take this description and apply the logic that I described above, you should probably end up with some sort of a design where most of your navbar is described in your highest level of abstraction. This includes items x, y, z, v and behaviors a, b, w. Now, behavior a may mean that a specific, rich page must be displayed, which has its own detailed behavior that is described on a lower level of abstraction, whereas behavior b may mean that based on the selection some content must be loaded, and again the details of this loading process is worked out on a lower level of abstraction. And so on.
When we started approaching the problem this way, it became much more straight forward to find out how to split up logic, and how to deal with special cases.
We realized, for instance, that when someone said that a page wants to display some stuff "in the navbar", what she really meant was that the navbar should collapse (or transform) for a particular page so that page can display it's own header in that area.
Focusing on the app's behavior instead of static content areas helped with this.

Yes, you're on the right path.
In the view you need to use Html.map.
view : Model -> Html Msg
view model =
div []
[ Html.map BodyMsg (Body.view model.body)
, Html.map NavbarMsg (Navbar.view model.navbar)
]
Body.view model.body has type Html Body.Msg which requires us to use Html.map to get the correct type of Html Msg. Similarly for Navbar.view model.navbar.
And, for the update function you'd write it like so:
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
BodyMsg bodyMsg ->
let
(newBody, newCmd) = Body.update bodyMsg model.body
in
{ model | body = newBody } ! [ Cmd.map BodyMsg newCmd ]
NavbarMsg navbarMsg ->
let
(newNavbar, newCmd) = Navbar.update navbarMsg model.navbar
in
{ model | navbar = newNavbar } ! [ Cmd.map NavbarMsg newCmd ]
In the BodyMsg case, newBody has type Body.Model and so we can set the body field in model to it. However, newCmd has type Cmd Body.Msg and so before we can return we need to use Cmd.map to get the correct return type of Cmd Msg.
Similar reasoning can be used for the NavbarMsg case.
Also, the view function above does not particularly look idiomatic.
What bothers you about the view code?
N.B. This answer assumes you're using Elm 0.18.

That's basically the way to go, yes. There is a popular example for a larger SPA in Elm on GitHub. Here can you see the Main.elm that takes care of mapping the messages from each page: https://github.com/rtfeldman/elm-spa-example/blob/master/src/Main.elm
One thing that is missing from your example is the mapping of the message type that is absolutly required. I guess you left that out to have a smaller post, but from my experience that is the actual part where the boilerplate comes in.
However, you should try not to emulate a component approach like React. Just use functions. Separate pages in an SPA are one example where it makes sense to have a dedicatated message type and corresponding functions like you would with a program.
This article explains the general approach of scaling a larger Elm app and also mentions the point about not having dedicated messages for each component.

Related

Is there any example or sample code for the find and filter feature in Cytoscape JS

I saw in cytoscape application we have features like find and filter by keywords and degree. I tried a workaround following the original docs. Here you can see the demo webdemo.intolap.com/cytoscape (view-source for the source code or snippet). The filter works well partially. Example, "apple" will display apple and it's connected nodes (1st level) just what I am looking for.
But the problem I am facing is about resetting the graph and filter again with a
different keyword. It seems the filter function does not work after the text box is cleared and then keyed in a different keyword.
I mean when I clear the text box, it resets the graph to original which is correct. I did that using an init() function which reinstates the graph. But then if I search for "Ball" filter does not work. Any help please. Thanks!
actually there is a reasonably good explanation in the official docs here, but to be honest, I too struggled with this feature at first:
Basically, you can filter the specific collection you want to search by just inserting a filter query. So if you want to filter all nodes, you can use this:
cy.nodes(filterQuery);
If you want to filter all elements, just call this:
cy.elements(filterQuery);
If you want to make it easy, you can use this short version (short for cy.filter(...)):
cy.$(filterQuery);
The filter query itself is not that hard, you can do this (assuming that you have a node with the id "first" or an attribute like nodeColor "#2763c4"):
cy.$('[id != "first"]');
cy.$('[id = "first"]');
cy.$('[nodeColor = "#2763c4"]');
cy.$('[weight > 50]');
Additionally, you can specify the target collection within your filter query like this:
cy.$('node[id != "first"]');
Lastly, if you need complex filtering, you can use a function to apply that logic to the filter, for that just do this:
cy.$(function(element, i){
return element.isNode() && element.data('weight') > 50;
});
Sounds like you are trying to cy.filter on a cytoscape instance that no longer exists at that point. That's why it works the first time, but not the second time (after you reinstate the graph, which probably means destroy & create).
You need to make sure you point your filter handlers to the active cytoscape instance.

Iterating store in relay optimisticUpdater

Apologies in advance, I'm new to relay and not sure I've got all the terminology here right...
I have a (simplified) graph that looks like:
customer {
summary(id: "ABC123") {
records { // This is an array of Record
tag
}
}
}
Customer, Summary and Record are all objects with global IDs - they show up as records in the Relay DevTools inspector.
I have a mutation that removes a tag by name (from elsewhere in the graph - not shown), from which I need to update the customer summary object to remove the record with associated tag. I have tried two approaches and not gotten very far with either:
Re-request customer.summary as part of the mutation. The problem is I don't know what the ID is at that point. (Maybe I can thread it through some how, but that would be messy.) Also doesn't really solve the problem, since I'd like to do this optimistically.
In an optimistic updater, remove any tag record that matches. This seems like it should work, but the RecordProxy doesn't appear to have a rich enough API to enable me to do this.
First approach, I can't seem to get access to the summary record via the root:
const customer = store.getRoot().getLinkedRecord('customer') // works!
customer.getLinkedRecord("summary") // undefined
customer.getLinkedRecord("summary", {id: "ABC123"}) // undefined
Second approach, if I could ask the store for "all records of type" or even "all records" I could iterate through and find the one I need to edit, but this doesn't seem to be a method that's exposed (even though Relay DevTools must be doing it somehow).

Can I fill a websharper template's hole without knowing its name at compile time

I am new using WebSharper and I am stuck in the current situation with WebSharper 4:
I have the following html template (some-template.html) :
<div class="container">
<div ws-replace="Content">
</div>
</div>
It defines a content hole with the name Content. Usually, one could fill it using the following in code (F#):
type SomeTemplate = WebSharper.UI.Templating.Template<"some-template.html">
let doc = SomeTemplate().Content(someElements)
In my scenario, I do not know the name of the template and the hole at compile time. Suppose I have a function:
let buildDom(tempalte : string, holeName : string, content : Doc list) : Doc =
let template = WebSharper.UI.Template<tempalte> // (1)
// template.FillHole(holeName, content) (2)
I do not know how to best deal with (1) -- creating the template, and (2) - locating and filling the hole. Reflection comes to mind, but I would like to know if there is a more elegant and performant approach.
A more general question -- is there a good way to have dynamic composition of html-templated sitelets? This is what I am trying to achieve, but if it is alredy done there could be no need to reinvent the wheel. I'd appreciate if you point me to such resources if available as well.
You can pass a dynamic template by passing a string to the constructor:
type SomeTemplate = WebSharper.UI.Templating.Template<"some-template.html">
let doc = SomeTemplate("<div>...</div>").SomeHole("some content").Doc()
but the holes are still typed statically based on the type provider. There is currently no API to implement dynamic holes.
This could be a nice and useful addition though; you should post a suggestion on github.

Elm project scaling: separating Messages/Updates

I am trying to separate files in an Elm project, as keeping everything in global Model, Messages, etc. would be just a mess.
Here is how I tried it so far:
So, there are some global files, and then Header has its own files. However I keep getting error, when importing Header.View into my global View:
The 1st and 2nd entries in this list are different types of values.
Which kind of makes sense:
The 1st entry has this type:
Html Header.Messages.Msg
But the 2nd is:
Html Msg
So, my question is whether all the messages (from all my modules, like Header) needs to be combined somehow in global Messages.elm? Or there is a better way of doing this?
My advice would be to keep messages and update in 1 file until that feels uncomfortable (for you to decide how many lines of code that means - see Evan's Elm Europe talk for more on the modules flow). When you want to break something out, define a new message in Main
type Msg
= HeaderMsg Header.Msg
| ....
Then use Cmd.map HeaderMsg in your update function and Html.map HeaderMsg in your view function to connect up your sub-components

Rally print stories with parent feature name in the card generated

I've used Joel Krooswyk's Print All Backlog Story Cards solution for printing all stories in a backlog.
What I'd like to do is to extend this to have each card print the name of the parent feature that the card belongs to so I can print them all up and lay them on a table for a collaborative estimation session.
The issue is, I'm having trouble finding how to do it.
A snippet of his code in question:
queryArray[0] = {
key: CARD_TYPE,
type: 'hierarchicalrequirement',
query: '((Iteration.Name = "") AND (Release.Name = ""))',
fetch: 'Name,Iteration,Owner,FormattedID,PlanEstimate,ObjectID,Description,UserName',
order: 'Rank'
};
I can't seem to find the element to fetch!
Parent was listed on an example queries page(intended for use in the browser query functionality), with Parent.Name containing the actual text but so that hasn't worked - trying to find a reference that is clear about it seems to be eluding me.
I've looked at the type definition located at:
https://rally1.rallydev.com/slm/webservice/v2.0/typedefinition/?fetch=ObjectID&pagesize=100&pretty=true
Going to the hierarchical requirement's type definition from that page indicates it has a Parent field in one form or another.
I'm not even sure that that one will solve what I'm looking at.
A bit stuck, and I'm not sure what I'm trying to do is even possible with the hierarchical requirement object type.
Note: I assume even if I do find it I'll need to add some code to deal parentless stories- not worried about that though, that's easy enough to deal with once I find the actual value.
Many thanks to anyone who can help :)
I modified Joel's app to include PI/Feature's FormattedID to the cards when a story has a parent PI/Feature.
You may see the code in this github repo.
Parent field of a user story references another user story.
If you want to read a parent portfolio item of a user story, which is a Feature object, use Feature attribute or PortfolioItem attribute. Both will work:
if (data[i].PortfolioItem) {
//feature = data[i].PortfolioItem.FormattedID; //also works
//feature = data[i].Feature.Name; //also works
feature = data[i].Feature.FormattedID;
} else {
feature = "";
}
as long as the version of API is set in the code to 1.37 or above (up to 1.43).
PrintStoryCards app is AppSDK1 app.
1.33 is the latest version of AppSDK1.x
1.29, which the app is using is not aware of PortfoilioItems.
PortfolioItem was introduced in Rally in WS API version 1.37.
See API versioning section in the WS API documentation .
If you want to access Portfolio Items, or other features introduced in later versions of WS API up to 1.43 this syntax will allow it.
<script type="text/javascript" src="/apps/1.33/sdk.js?apiVersion=1.43"></script>
This has to be used with caution. One thing that definitely will break is around calculations of timebox start and end dates. That's why many legacy Rally App Catalog apps are still at 1.29.
This is due to changes in API Version 1.30.
Note that this method of setting a more advanced version of WS API for AppSDK1 does not work with v2.0 of WS API.
You should be able to add PortfolioItem to your fetch. Parent is the field used if the parent is a story. PortfolioItem is the field used if the parent is a Feature (or whatever your lowest level PI is).
Then in the results you can just get it like this:
var featureName = (story.PortfolioItem && story.PortfolioItem.Name) || 'None';