Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Halogen 5 #26

Merged
merged 20 commits into from
Jul 30, 2019
Merged

Halogen 5 #26

merged 20 commits into from
Jul 30, 2019

Conversation

thomashoneyman
Copy link
Owner

@thomashoneyman thomashoneyman commented Apr 23, 2019

Closes #25 by updating the project for Halogen 5. This update avoids updating any functionality in favor of a simple, direct translation from Halogen 4 to Halogen 5. There was also a major update in the form library, Formless, which required updating most page components more extensively than might be usual for a migration like this.

For more information on what's changed in Halogen 5, please see the Changes in Halogen 5 documentation.

This was a fairly quick & pleasant transition, completed in about 2-3 hours in total.

@thomashoneyman thomashoneyman self-assigned this Apr 23, 2019
render { route } = case route of
Home ->
HH.slot' CP.cp1 unit Home.component unit absurd
HH.slot (SProxy :: _ "home") unit Home.component unit absurd
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Halogen 4 we used a child path and slot type to identify a particular component among many. In Halogen 5 we use a symbol proxy and a slot type instead.

A symbol proxy is a type-level string. You've seen them before: they're used as labels for row types (and records and variants, which are built on top of rows). In this case we're identifying the component with the label home in our ChildSlots row defined at the top of this module.

I've written SProxy :: _ "home" as a shorthand here, but the full type is SProxy :: SProxy "home". That means that at the value level we have just SProxy, but this value has the type SProxy "home", which is how we're able to use the label "home" at the type level.

Unit
Unit
Unit
type ChildSlots =
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Halogen 5 we use a row type to describe child components, instead of the old nested either / coproduct pairing. It's quite a bit cleaner and allows you to easily give descriptive names to each component.

A child path used to be a pairing of a component's query type (what a parent can use to trigger behaviors in or request information from one of its child components) and slot type (a unique address when there are multiple components of the same type).

In Halogen 5, we instead have a dedicated Slot type which uses the child component's query type, message type (what outputs are possible to handle from the component), and slot address.

For example:

-- this parent component has one child component type, labeled `component`, with the query
-- type `ComponentQuery`, outputs of type `ComponentMessage`, and we'll distinguish among
-- multiple copies of the component using an `Int` address.
type MyChildSlots =
  ( component :: H.Slot ComponentQuery ComponentMessage Int )

Our router deals with page components which raise no output messages and have no public query interface you can use to request information or manipulate them. They simply aren't necessary. So for each of our page components the slot type is just:

type ChildSlots =
  ( home :: H.Slot (Const Void) Void Unit )

In other words, H.Slot expects a query type of kind Type -> Type, a message type of kind Type, and a slot address of kind Type. When you need to provide a type but have no possible values that could satisfy the type, you can use Void -- a type with no accompanying values. And when you need to provide something of kind Type -> Type but have no possible values, you can use Const Void. So when we have a component with no queries and no messages we can use Const Void and Void to represent that.

This is fairly common in our application, so I've defined a helper type in Component.Utils for components with no queries or messages:

type OpaqueSlot = H.Slot (Const Void) Void

@@ -48,32 +48,31 @@ type State =
type Input =
{ slug :: Maybe Slug }

type ChildQuery m = F.Query Query TagInput.Query Unit EditorFields m
type ChildSlots =
( formless :: F.Slot EditorFields (Const Void) FormChildSlots Article Unit )
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Formless is the component that has undergone the greatest number of changes as part of the Halogen 5 transition. For the full details, see the Formless library. If you have written a form and you want to extend the form's state, query algebra, actions, messages, child components, or anything else, that information no longer leaks out to the parent component but instead is handled entirely within Formless. Accordingly, any extensions you want to make to the component will show up in its slot type.

The main thing that affects us here is that the Formless slot type now takes:

  • Your form type being run in Formless (here, EditorFields)
  • Any queries you wish to extend the component with (here, none, represented with Const Void
  • Any child components the form will have (here, FormChildSlots, defined at the end of the module)
  • Any messages the component will output (here, we'll get an Article on successful submission)
  • The form slot address

( tagInput :: H.Slot (Const Void) TagInput.Message Unit )

data FormAction
= HandleTagInput TagInput.Message
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Formless can now be extended with more actions as you see fit. Here, we'll extend the component with the ability to handle messages being output by the TagInput component. Notice how the parent component, the Editor page, doesn't have to care about the tag input anymore because it's encapsulated within the form component.

src/Page/Editor.purs Outdated Show resolved Hide resolved
src/Page/Editor.purs Outdated Show resolved Hide resolved
th-awake and others added 13 commits April 23, 2019 23:41
add bus subscriptions for global state
convert to spago
* Update Formless dependency to Jordan's "exposeInput" branch

* Remove unneeded whitespace characters

* Migrate Editor page to new Formless

* Migrate Login page to new Formless

* Migrate Register page to new Formless

* Migrate Settings page to new Formless

* Convert back to using Thomas' Halogen 5 PR as dependency
@thomashoneyman
Copy link
Owner Author

Due to the long delay on the Halogen 5 release, I'm leaning towards merging this into master this week. However, if any watchers of this repository have a reason why I shouldn't do this, please feel free to comment!

@JordanMartinez
Copy link

I'd say just go ahead and merge it. I don't know when Gary will make the next release, even though it's quite useable as is.

* Update code for purs 0.13, update halogen to rc5

* Remove override for remotedata as its in the snapshot now

* Gitignore VSCode workspace files

* Use hash symbol instead of its code
@willisplummer
Copy link

It makes sense to me to merge -- I wanted to use Halogen 5 and having this as an example was useful for understanding what was different. Maybe add a link to this PR in the README or even tag the repo before merging so that it's easy for ppl to see what it looked like at 4 vs. 5?

@thomashoneyman
Copy link
Owner Author

That's a good idea -- I think a tag + a link in the readme would help make sure people on Halogen 4 and Halogen 5 are both able to get value from the repo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Update for Halogen 5
5 participants