Adding storage to an elm-pages app
Last updated
Last updated
In this guide, we will show how to use Fission webnative storage to save user data. Our goal will be to add annotations to blog posts in the elm-pages-starter
.
All of the code in this guide is available in the fission-elm-pages-starter repository on the storage
branch.
When a user has signed in with Fission, an annotation button is displayed at the bottom right corner of the page.
Clicking the button opens a text input box where the user can write their notes.
If a user has not signed in, they do not see the annotation button or text input box.
Annotations are saved to the user's webnative filesystem and will be there when they return or use the app on another device.
We will start off by writing an annotation module. Annotation.elm
is built around an Annotation
type and has a way to create, encode, and view annotations.
An Annotation
can be editable or not. If an annotation is editable, it has a title
and notes
.
We have two ways of creating an annotation. A NotEditing
annotation can be created directly. An Editable
annotation must be created from a Value
that will come over a port.
Our code that interacts with the webnative filesystem will use blog post titles as keys to lookup and save annotations.
Add encode
for storing annotations and encodeTitle
for loading them.
Lastly, we need a way to view annotations. When a user is editing an annotation, we show their notes or the blog post title as a placeholder. When an annotation is not being edited, we show a button with a pencil icon.
Add a view
function with a case for Editable
and NotEditing
annotations.
Now that our annotation module is ready, we can add annotations to the elm-pages-starter
.
We want the user to see the annotation button when they navigate to a page with a blog post. When they click on the annotation button, we load the annotation from their webnative filesystem or an empty annotation if they are starting a new annotation. We replace the button with the notes in a text input box.
We save the annotation to webnative with each keystroke the user makes in the text input box.
Import the annotation module in Main.elm
.
Add an annotation
to the Model
.
We only display one annotation at a time, which means we can replace annotation
when we navigate to a new page or load an annotation.
The annotation starts off as NotEditing
in init
.
Our update
will handle four messages that initialize, load, or store annotations. OnPageChange
sets the annotation to NotEditing
when a user navigates to a new page. LoadAnnotation
calls over a port to load an annotation. UpdateAnnotation
updates the displayed annotation in the input box and stores the annotation. GotAnnotatation
subscribes to annotations that come in over a port after a load.
Our main
function needs a small change to configure the OnPageChange
message. Set the onPageChange
field in the record passed to Pages.Platform.init
to Just OnPageChange
.
Add loadAnnotation
, onFissionAnnotation
, and storeAnnotation
ports.
Add a subscription to the onFissionAnnotation
port.
We create an annotation from the Value
received over onFissionAnnotation
and use GotAnnotation
to update our model with it.
Pass the annotation and two messages to tell it how to load and update annotations to Layout.view
.
In Layout.elm
, the view
displays the annotation at the bottom right corner and in front of the rest of the page. We check if the user is logged in and viewing a blog post. If both are true, we show them the annotation.
Our app is ready for annotations and the last step is adding webnative storage.
Each Fission user has a filesystem that is stored locally and distributed across the web. An app that uses Fission storage asks the user for permission to use their filesystem -- similar to how a native mobile app asks for permission. After the app has been granted permission, it can store user data to their local filesystem and publish it to the distributed filesystem.
Let's start by installing the webnative
package from npm.
Import it in index.js
.
We initialize webnative
with a list of permissions stating what our app would like to use. In our case, we only need to request permission to use the storage associated with our app.
Shared storage: The webnative filesystem also has public and private shared storage that can be accessed across apps. See the webnative paths documentation for more details.
Declare an fs
variable that will be used to access the user's filesystem. Add fissionInit
with a request for permissions
to use app storage by app name and your Fission username.
Replace <username>
with your Fission username.
Next, we initialize webnative
. In the AuthSucceeded
and Continuation
cases, the user has authenticated and granted permission to use the filesystem. We can now set up the filesystem and a way to load and store annotations.
Annotations are stored in app storage in an annotations
subdirectory as JSON. Each annotation is stored as a file using the blog post title and a .json
suffix. For example, an annotation on the blog post "Hello Galaxy 🌠" would be stored on the path annotations/Hello Galaxy 🌠.json
. Paths are case sensitive, spaces are fine, and emoji are welcomed!
Paths are built using fs.appPath
which takes an array of strings that are parts of a path separated by forward slashes.
The numbered references in the code go as follows:
Alias the filesystem from state. We alias state.fs
as fs
to put it onto the global scope.
Create the app directory if it does not exist. A new user will not have a directory for our app. Check if the directory exists and make one if not with fs.mkdir
. Each time we make a change to the local filesystem, we fs.publish
to synchronize with the distributed filesystem.
Load an annotation or send an empty one. When the Elm app sends a message over the loadAnnotation
port, we make check if a file for the annotation exists. If it does, we fs.read
and send it over the onFissionAuth
port into the Elm app. If not, we send an empty annotation value.
Store and publish an annotation to the filesystem. When the Elm app sends a message over the storeAnnotation
port, webnative
saves the annotation to the filesystem with fs.write
. The current implementation uses a transaction queue to handle writes that come in quick succession. See the transaction queue implementation to examine how this works now. An upcoming version of webnative
will handle writes in parallel, and the transaction queue will not be needed.
Everything is in place and users can write their notes about blog posts! Annotations persist across visits to the blog and are available on any device where the user has logged in with Fission.
We haven't covered shared storage in this guide, but you can do much more when user data is not restricted to a single app. With shared private storage, you could write an app that shows users their annotations from multiple apps across the web. With shared public storage, you could convert annotations into a publicly visible comments!
For more information on how Fission storage works, take a look at: