We've just released IHP v0.11.0, which includes:
- Server-Side-Components, similar to React or elm
- Joins, for more complex queries
- IHP.FileStorage, which makes it easy to store files on AWS S3 or any other compatible service
- ...and more!
Server-Side-Components aka. you don't need React
IHP's new Server-Side-Components allow you to build React-like components on the server (as the name implies). This allows you to build highly interactive pages without having to worry about server vs. client state, and no change of environment - as everything is still in Haskell.
Let's look at an example component, a basic counter:
-- everything starts with the normal module definition and imports
module Web.Component.Counter where
import IHP.ViewPrelude
import IHP.ServerSideComponent.Types
import IHP.ServerSideComponent.ControllerFunctions
-- Followed by a type definition for the state.
-- This means there will never be any surprise what the state contains.
-- In this case it's simply an Int, so a number.
data Counter = Counter { value :: !Int }
-- Then comes a list of possible actions that can be used to update the state.
-- If you've used redux or the elm architecture, this should be familiar.
-- Defining what actions are available means it's there won't be any surprises here either, and future developers can immediately see what behavior this component supports.
data CounterController
= IncrementCounterAction
deriving (Eq, Show, Data)
-- just one line of boilerplate to generate some code that we really don't want to write ourselves...
$(deriveSSC ''CounterController)
-- The heart of any component are
-- 1. the render function, taking care of turning the state into html
-- 2. action handlers, which update the state based on selected actions
instance Component Counter CounterController where
-- but first, we need to define the default state
initialState = Counter { value = 0 }
-- the render function takes the state and return HTML
render Counter { value } = [hsx|
Current: {value} <br />
<button onclick="callServerAction('IncrementCounterAction')">Plus One</button>
|]
-- this is the handler for the IncrementCounterAction
-- the function takes a state and the action, modifies the state (in this case incrementing the counter's value) and returns the new value
action state IncrementCounterAction = do
state
|> incrementField #value
|> pure
-- this allows us to use 'incrementField' to increment the counter
instance SetField "value" Counter Int where setField value' counter = counter { value = value' }
And the result is a beautiful counter, all controlled server-side:
Here's a little gif showcasing a more complex use-case of a filterable and sortable table:
To learn more check out the documentation.
Joins
If you've tried to do complex queries involving joins of multiple tables in IHP, this was previously a little bit of a hassle. With first-class support for joins, there's now a completely type-safe way to do anything you can imagine.
Here's an example of how you can select all posts that were written by a user with the name "Tom":
tomPosts <- query @Post
|> innerJoin @User (#authorId, #id)
|> filterWhereJoinedTable @User (#name, "Tom")
|> fetch
And here's a more complex example that selects all posts that were tagged by a tag named "haskell" in the case of a many-to-many relationship:
query @Posts
|> innerJoin @Tagging (#id, #postId)
|> innerJoinThirdTable @Tag @Tagging (#id, #tagId)
|> filterWhereJoinedTable @Tag (#tagText, "haskell")
|> fetch
IHP.FileStorage to store files in AWS S3-compatible services
Instead of having to manually use the APIs of AWS S3, you can now use our special support for these services to upload anything you'd like.
Check out this example for an action used to upload a logo of a company to S3:
action UpdateCompanyAction { companyId } = do
company <- fetch companyId
company
|> fill @'["name"]
|> uploadToStorage #logoUrl
>>= ifValid \case
Left company -> render EditView { .. }
Right company -> do
company <- company |> updateRecord
redirectTo EditCompanyAction { .. }
As you can see it's not much different from any other action used to update the company data. All the magic happens due to one line: |> uploadToStorage #logoUrl
.
You can do much more though, for example:
- use ImageMagick to do pre-processing of the uploaded image file (resizing, conversion,...)
- generate secure, signed download URLs for private files
- in development, files are stored on-disk in the
static/
folder for ease of debugging and offline-only development
Find out how to use it and configure it for your project in the documentation.
Case Insensitivity
We've added filterWhereCaseInsensitive
and validateIsUniqueCaseInsensitive
, which do exactly what you expect them to do.
If you're using IHP's login, be aware that these functions are now used for checking the emails. This increases UX for your users, but in case someone messed up and created two accounts - one with some letters of their email uppercase, one not - you will have to migrate that account.
Conclusion
If you haven't tried out IHP yet, now's a great time to do so. We'll be at ZuriHack (huge Haskell Hackathon), so sign up for free there if you haven't already and get an introduction to IHP from us directly!
Top comments (0)