Create a WebView Based EPUB Reader With NativeScript and Epub.js
The whole source code contained in this post is available on GitHub.
One of the things making NativeScript different from other technologies allowing cross-platform mobile development, is that it doesn’t rely on WebViews to host your applications, but rather truly uses the native capabilities of the devices.
But that doesn’t prevent your from using WebViews to implement features that would normally require a browser to run. This is exactly what we’ll doing in this tutorial, by creating a basic EPUB reader using the epub.js library and NativeScript.
Note: For this tutorial, I’ll be focusing on Android. I’ll make an update if I ever come to the iOS compatibility.
Let’s start by doing some setup. I will use the Angular2 version of NativeScript, so we’ll create a new application using the –ng flag:
Let’s prepare some files we’re going to need for the reader. We’re going to use the epub.js library, and host it into a native WebView. For the communication between the NativeScript app and the epub.js based reader, we will use a plugin called nativescript-webview-interface, created by Shripal Soni.
Go ahead and download the epub.min.js source code. Then, copy both build/epub.min.js and build/libs/zip.min.js into a new directory app/www/lib. The JSZip library is needed to be able to open a compressed epub (with the .epub extension).
Next, install the nativescript-webview-interface plugin by running:
and copy the node_modules/nativescript-webview-interface/www/nativescript-webview-interface.js file into the newly created app/www/lib directory. Let’s also download the Moby Dick epub file that we’re going to use for our example. Copy it into app/www/books.
Let’s now start writing some code.
Prepare the WebView Code for the Reader
The next step is to prepare the code allowing us to load the epub and display it into some DOM element. We’re going to keep the whole thing simple because we don’t want to write too much of code ‘sitting outside’ of our NativeScript application. Create a epub.html file into app/www, and put the following content in it:
The only thing we’re doing here is declaring the placeholder for the EPUB to be displayed, and declare our libraries. Next to the libraries we’ve dowloaded previously, we also declared a script that is going to be responsible for the interface with epub.js and our NativeScript application. We also make sure that the WebView covers the whole screen height by setting the html, body and #book height style to 100%.
Let’s create the epub-reader.js script:
This script is using the nsWebViewInterface global object to react to events coming down to our application. When the loadBook event is received, we’re going to call the ePub() function and save the result into a book variable. Note that we can also use the interface to pass some parameters. This is what we’re doing here by passing the file name of the book we want to open. The book will then be rendered into the ‘book’ element (our
). We also add two listeners on nextPage and previousPage.
We now have the very least to create a basic epub reader.
Create the EpubComponent
Our main Component is going to be the EpubComponent, responsible for the display of the EPUB WebView, and its manipulation through the WebView interface and triggered by user gestures. Go ahead and create the EpubComponent with the following content:
As you can see, our template contains only the EPUB WebView. We’re setting a few lines of configuration to allow the WebView to access the file system (in order to be able to load the EPUB), and to hide the zoom controls which are not relevant in our case.
Before being able to see the result, we need to declare our Component inside the AppModule. Open up app.module.ts and modify it like this:
Then, edit the app.routing.ts file to declare the one and only route of the application (and delete the ones that aren’t needed):
After running the app on your device or an emulator, you should see the cover of the EPUB, meaning that it’s been correctly loaded:
Before moving to the interaction using gestures, let’s extract the Android specific stuff out of the EpubComponent. Even if we’re focusing on Android for now, it’s always a good practice to isolate the platform specific code. Let’s create a new class WebViewEpubifier, in a web-view-epubifier subfolder of epub. We’ll want it to be called on Android devices only, so we add the .android.ts suffix:
We just have one method taking a WebView as parameter and applying the settings we declared previously directly in the EpubComponent. We’ll now add a type definition to be able to import the class without caring about which platform implementation it’s going to be called at runtime (the platform polymorphism of the NativeScript world):
Back to our Component, we can now do this:
Which makes our Component completely platform agnostic.
Notice that you can still scroll the WebView vertically a bit, we’ll correct this later on.
Turn the Pages Through Gestures
The next step is to make the epub react to user gestures in order to turn the pages. We’re going to need the platform declarations plugin, so start by running:
Then, edit the tsconfig.json file and add the following property under “compilerOptions”:
And finally, edit the references.d.ts like this:
We have to do some manual declarations because of the core modules declarations colliding with some DOM elements typings, which is something the TypeScript compiler doesn’t like much.
Next, we’re going to create a class responsible for detecting the user gestures on the WebView. We want to be able to turn the pages when a swipe is detected. Let’s start by the type declaration, that we’ll name epub-gesture-detector:
We’re declaring a constructor waiting for a WebView in input, as well as a method accepting a callback, taking a SwipeDirection as parameter, and returning nothing. This is where we’ll put our logic triggered when a swipe is actually detected. To achieve this on the Android platform, we’ll rely on a class inheriting from SimpleOnGestureListener. According to the documentation, this is possible by implementing the onFling() method. Let’s go ahead and do this. Create a new class called OnGestureListener, inheriting from android.view.GestureDetector.SimpleOnGestureListener, and place it to a sub-directory of epub-gesture-detector called android (since it’s going to be used only by the Android platform):
Let’s explain what we’re doing here. First, we need to call this strange
Then, we declare two threshold variables, one to check if a given detected swipe is long enough (in term of distance), and the other, to check if it’s fast enough.
The onFling() method of SimpleOnGestureListener accepts two events (in respective order), and two velocity values, one for X, one for Y.
We first calculate the distance on the X axis (we only care about swiping on the horizontal axis). Then we check if the swipe reaches both the distance and the velocity threshold. If it’s the case, then we have a swipe and we emit it through our swipe Subject. The direction is calculated according to the distance (negative means a Left swipe, positive a Right one). We then return true to indicate that we always want to consume the onFling() gesture event.
Notice also that we implement the onDown() method and always return true, we’ll see why a bit later.
Finally, the onSwipe() method subscribes to the swipe Subject by passing the given callback to it. A subscriber can call it in order to be notified when a swipe is detected, and execute the callback.
We can now implement the Android variant of the EpubGestureDetector like this:
Let’s try to understand what’s going on here. We’re calling the setOnTouchListener() method from the android WebView, and pass a new instance of android.view.View.OnTouchListener to it. its content is an anonymous implementation, where we set the onTouch event to call our local onTouch() method whenever it’s triggered. We delegate the event to an instance of android.view.GestureDetector that we previously declared as an instance variable. The latter is using the android context, as well as an instance of GestureListener, here we’re passing an instance of our custom implementation (OnGestureListener). The onTouch method of OnTouchListener returns a boolean, indicating wether the event has been consumed or not. Within our implementation of OnGestureListener, we indicated that onDown() should always return true. This is a way of saying that as soon as the WebView is touched, we consume the event and don’t propagate it further. This will prevent the WebView from being scrolled up and down, and fixes the problem we mentioned earlier.
Let’s wire this up to the Component, and write its final implementation:
Which should enable you to turn the pages by swiping left and right:
That’s it ! I hope this article has been useful to you. If you encounter any trouble or have any suggestion, please leave a comment.NativeScript and tagged android, Epub, Native, nativescript, WebView. Bookmark the permalink.