News

Discover fast app dev with Hoodie

Use Hoodie to create a basic forum in under an hour using nothing but front-end technologies

How to use overlay effects on background video

Screen shot 2014-11-13 at 16.59.53

Hoodie describes itself as “an Offline First and no Backend architecture for frontend-only web apps on the web and on iOS”. With the buzzword quota met we’re going to look to see if it delivers on its promise of “very fast app development”.

To do this we’ll create a traditional, minimal forum with users and allow thread creation and replying. Hoodie uses Node and CouchDB, which takes care of as much of the backend for you so you don’t have to touch any part of it, just concentrate on the front-end. What does Hoodie mean by ‘offline first’? Hoodie stores all of the user’s information on their machine and then syncs it up when a connection is available making it great for mobile apps with spotty connections.

Unlike Meteor, Hoodie concentrates on only providing back-end services, leaving you free to use whichever front-end tech you like. We’re going to use AngularJS for its easy routing and flexible views. Although Hoodie’s ultimate aim is to allow non-developers to create powerful apps, for now a working knowledge of JavaScript is recommended.

GET THE CODE

Install Hoodie

Head to hood.ie/#installation to find out the installation instructions for your OS. Once you’ve finished installation run the following in your terminal/command-line program. This will install the command line tools for Hoodie, scaffold a new Hoodie app called ‘forum’ and then start it – it’s that easy!

001    $ npm install -g hoodie-cli
002    $ hoodie new forum
003    $ cd forum
004    $ hoodie start
005    

Project structure

Hoodie generates many files but we only need to concentrate on the ones under the ‘www’ folder. This will contain our main index.html file, HTML files for our views, and our CSS and JS files. ‘vendor’ contains libraries already included with Hoodie and ‘bower_components’ will be automatically populated with files when we use Bower.

www/
├── assets
│   ├── bower_components
│   ├── css
│   ├── js
│   │   ├── controllers
│   │   │   ├── main.js
│   │   │   └── topic.js
│   │   └── main.js
│   └── vendor
├── index.html
└── views
├── create-topic.html
├── main.html
└── topic.html

Install extra packages

We’ll use Bower to add some other third-party libraries: jQuery, Bootstrap and Angular. angular-route will install Angular with it as a dependency meaning that you have less to type. Once these have downloaded include the new files in index.html (generated by Hoodie for you) before the closing <body> tag.

001    $ bower install bootstrap angular-route
002    --- index.html ---
003    <script src=”assets/bower_components/jquery/dist/
jquery.min.js”></script>
004    <script src=”assets/bower_components/bootstrap/dist/
js/bootstrap.min.js”></script>
005    <script src=”assets/bower_components/angular/
angular.min.js”></script>
006    <script src=”assets/bower_components/angular-route/
angular-route.js”></script>
007    <script src=”assets/js/main.js”></script>

Hoodie actions

You may have noticed that there’s a lot of markup in your Hoodie app. Here’s the sign-in drop-down that it created for us; notice that if you have the user plugin installed (which it automatically does) we can use these ‘data-hoodie-action` attributes to hook up complicated functionality. Hoodie uses Bootstrap for its UI (eg modal dialogs).

001    <ul class=”dropdown-menu pull-right”>
002        <li><a href=”#” data-hoodie-action=”signin”>
Sign In</a></li>
003        <li><a href=”#” data-hoodie-action=”resetpassword”
>Reset Password</a></li>
004        <li><a href=”#” data-hoodie-action=”destroy”>
Clear local data</a></li>
005    </ul>

Forum module

To initialise hoodie we just call ‘new Hoodie()’. This will give us access to the data and account information. We’ll configure o+ur forum to use routing. This means that if the user clicks a link going to ‘#/thread’ then the view will automatically update. Angular provides this functionality through the ngRoute module.

001    “use strict”;
002    var hoodie = new Hoodie();
003    angular.module(‘forum’, [‘ngRoute’]).config
(function ($routeProvider) {
004    });

Configure routes

Our routes are associated with an HTML template and a controller. A colon (eg ‘:id’) denotes that this is a placeholder for an actual value. We’ll then be able to access that value within the route’s controller. You can think of a controller as a big function that contains the functionality necessary for that area of the application.

001    $routeProvider
002        .when(‘/’, {
003            templateUrl: ‘views/main.html’,
004            controller: ‘MainCtrl’
005        })
006        .when(‘/topic/create’, {
007            templateUrl: ‘views/create-topic.html’,
008            controller: ‘TopicCtrl’
009        })
010        .when(‘/thread/:id’, {
011            templateUrl: ‘views/topic.html’,
012            controller: ‘TopicCtrl’
013        })
014        .otherwise({
015            redirectTo: ‘/’
016        });

Boot the app

To boot our app we need to tell Angular what the scope of our app is. We’ll also provide a placeholder <div> that will render the templates inside of it. You’ll have noticed that Hoodie provided you with an example app when it started. In index.html remove the content block and add the following.

001    <div ng-app=”forum”>
002        <div ng-view></div>
003    </div>

The main controller

When we configured our routes we told it that we’d have a controller called MainCtrl for the root (‘/‘) so we best create it! Make a new file in the controllers folder called main.js. To create a new controller in Angular you pass it the name and a function. The function will contain methods that the view can use as well as properties.

001    angular.module(‘forum’).controller(‘MainCtrl’, function ($scope) {
002        ‘use strict’;
003    });

Hoodie plug-ins

You can extend Hoodie with plug-ins. Hoodie plug-ins are just npm modules so you can go to https://www.npmjs.org and search for ‘Hoodie’ to find the latest available. By default all data in Hoodie is private and must be ‘published’ to be viewable by other users. If you get an error try using sudo.

001    $ hoodie install hoodie-plugin-global-share

Find all threads

Within MainCtrl we want to find all the threads that have been created and then display them. Usually you would use hoodie.store to access data as this uses an offline-first approach (it checks localStorage and then fetches it from the server). Every document saved in Hoodie has a ‘type’ – passing a string to ‘findAll’ will find all documents with that type.

001    hoodie.global.findAll(‘thread’).done(function(threads) {
002        /* next step */
003    });

Set threads

Once Hoodie has found the threads we want to expose them to our view. We do this by attaching it to ‘$scope’. Because this is an AsyncOperation that uses Hoodie, not Angular, we need to prompt Angular to update the scope when it’s completed with ‘$apply’. You can find out more about $apply at
http://bit.ly/1q3f1fx.

001    $scope.threads = threads;
002    $scope.$apply();

Main thread view

To list the threads, create a file called main.html in a folder called ‘views’. We’ll use Angular’s ng-repeat to provide a template for each item. The first section, ‘thread in threads’ is just a ‘for…in’ loop, the second part orders each
item by the date, which Hoodie automatically updates when a document is saved called ‘updatedAt’.

001    <h2>Threads</h2>
002    <ul class=”topic-list list-group”>
003        <li class=”list-group-item” ng-repeat=
”thread in threads | orderBy: ’updatedAt’ : true”>
004        <li>
005    </ul>
006    

Display thread information

Within the <li> of the previous step we’ll add the following. For each thread we’ll show the title and use Angular’s date formatting to display a human-readable version of it. Earlier we configured the router to take a placeholder ID for the thread route. Here we set each link to the thread’s ID.

001    <h4><a href=”#/thread/{{thread.id}}”>
{{thread.title}}</a></h4>
002    <p>Created at {{thread.createdAt | date:’short’}}</p>
003    <p>Last updated at {{thread.updatedAt | date:’short’}}
</p>

Create topic link

Now that we’ve got listing of threads working we need a way to create them! We only want to allow thread creation if they’re logged in so we use ‘ng-show’. The whole sign-up/log-in mechanism is handled automatically by Hoodie (through the drop-down we saw previously). You can access user specific information and events through ‘hoodie.account’.

001    <div class=”col-md-4” ng-show=
”hoodie.account.username”>
002        <h2><a href=”#/topic/create”>Create topic</a></h2>
003        <p>Create your own discussion on the topic of 
hoodies :D</p>
004    </div>

Create topic form

Create a new file under the view folder called ‘create-topic.html’. This will hold a simple form with a text input for the title and a <textarea> for the main body text. We’re using some HTML5 input attributes to ensure a value is entered and blank topics aren’t created.

001    <form id=”create-topic” role=”form”>
002        <div class=”form-group”>
003            <label for=”thread-title”>Thread title</label>
004            <input name=”thread-title” id=”
thread-title” class=”form-control” required>
005        </div>
006        <!-- next step -->
007    </form>

Create topic text

Here’s the <textarea> portion of the create topic form. When the user clicks the submit button we call the createTopic method in our controller through ‘ng-click’. We haven’t created the topic controller yet so we’ll do that
as our next step. In case you were wondering, the classes that we’re using are part of Bootstrap.

001    <div class=”form-group”>
002        <label for=”post”>What do you want to say?</label>
003        <textarea class=”form-control” name=”post” id=
”post” required></textarea>
004    </div>
005    <input type=”submit” class=”btn btn-default” value=
”Create topic” ng-click=”createTopic()”>

Topic controller

We create the topic controller in much the same way as we made MainCtrl but this time we’re passing $routeParams and $timeout. Angular is clever enough to include these based on their name but if you’re using code like this in production you should use a technique called Dependency Injection (https://docs.angularjs.org/guide/di).

001    angular.module(‘forum’)
002        .controller(‘TopicCtrl’, function ($scope, 
$routeParams, $timeout) {
003    });

Create topic function

Our ‘createTopic’ method will use the value of the inputs and the name of the user who created it. We split the creation of the topic and then the post into two parts. Note that we use hoodie.store to create the topic first, this means that it’s synced to our local machine first and then we publish it.

001    $scope.createTopic = function () {
002        hoodie.store.add(‘thread’, {
003            title: angular.element(‘#thread-title’).val(),
004            user: hoodie.account.username
005        }).then(function (thread) {
006            /* next step */
007        hoodie.store.find(‘thread’, thread.id).publish();
008        });

Create the post

Once the thread has been created we then add the post associated with it and once that’s completed we relocate to the thread in question. Hoodie uses promises for all of its asynchronous operations so we can use ‘then’ and ‘done’ once they’ve completed. It would be good to add error handling too with ‘fail’.

001    hoodie.store.add(‘post’, {
002        parent: thread.id,
003        user: hoodie.account.username,
004        text: angular.element(‘#post’).val()
005    }).done(function () {
006        hoodie.store.findAll(‘post’).publish().done(function() {
007            window.location.hash = ‘#/thread/’ + thread.id;
008        });
009    });

Find matching thread

The createTopic functionality is now complete so we’ll move on to what happens when we go to a thread page (by going to ‘/thread/threadID’). Outside of the createTopic function but still within our ‘TopicCtrl’ we want to find the thread that matches the ID passed in $routeParams. find() will return one result so needs the type that it’s looking for and its ID.

001    hoodie.global.find(‘thread’, $routeParams.id).
done(function (thread) {
002        $scope.thread = thread;
003        $scope.$apply();
004    });

Find and filter

We’ll create a findPosts method too, which will run straightaway. findPosts finds all posts and then filters them depending on if the post’s parent is the same as the thread that we’re looking at. With store you can pass this filtering function directly to the find so the client doesn’t have to filter everything. We’ll also call it when the database is updated with an added post.

Composing a reply

While we’re working on our TopicCtrl we’ll add the final bit of functionality to it – the ability to post a reply. We’ll cover adding the reply to the database in the next step. After the reply has been made we’ll empty the <textarea> and let the view know that we’re no longer composing a reply.

001    $scope.postReply = function () {
002        /* next step */
003        angular.element(‘#reply-text’).val(‘’);
004        $scope.composeReply = false;     };

Adding reply

To add a post we want to know which thread to attach it to, which user added it, and the text content of the reply. Once the post has been created we then publish it to make it public. Hoodie currently doesn’t have a way to find a user based on their ID, hence the need to store the username.

001    hoodie.store.add(‘post’, {
002        parent: $routeParams.id,
003        text: angular.element(‘#reply-text’).val(),
004        user: hoodie.account.username
005    }).done(function (post) {
006        hoodie.store.find(‘post’, post.id).publish();});

Display thread

Now that we have all of this functionality packed into our topic controller we need a way to interact with it all! Create a file called ‘topic.html’ within the ‘views’ folder. We’ll start in a similar way to main.html, by listing the thread’s title and when it was created.

Displaying posts

This chunk of HTML (see code download) shows each post (as found by the controller) and makes sure that they’re ordered by the date that they were created. For each post we’ll show who created the post in the left column and the post’s text on the right.

Conditional display

After our list of posts we’ll add a button allowing the user to reply to the thread if they’re logged in. All that happens when they click the button is set the `composeReply` flag to true. The `ng-hide` attribute on the button will then realise that `composeReply` is true so will hide this button.

001    <div class=”form-group” ng-show=
”hoodie.account.username”>
002        <button name=”button” class=”btn btn-default” 
ng-click=”composeReply = true;” ng-hide=
”composeReply”>Reply to thread</button>
003    </div>

Thread reply form

When `composeReply` is true we’ll show the form to create a reply to the thread. This is similar to the create topic form, just without the title. When we click the ‘Post reply’ button it’ll invoke the `postReply` method on our
controller which creates the post and hides this form. With that our very basic forum is complete!

001    <form role=”form” ng-show=”composeReply”>
002        <div class=”form-group”>
003            <label for=”reply-text”>What do you want to say?
</label>
004            <textarea class=”form-control” id=
”reply-text” required></textarea>
004        </div>
006        <button name=”button” class=”btn btn-default” id=
”post-reply” ng-click=”postReply($scope)”>Post reply
</button> </form>
×