Using Angular to allow a search facility for a 404 page.

Jekyll has a “404” handler that allows for you to do what ever you want when someone lands on your site with a missing link. Of course it’s best not to mess with your URLs long term, or have redirects in place for when you do. There is a possibility you had to yank a blog entry because your current employer recognized themselves (or their client) and whispered in your ear. That, or you might have ham-fisted a URL when typing it into the URL/nav textfield in a browser. Either way a handler to allow some sort of search of articles is desirable.

I’ve made an Angular-enabled version of my blog’s 404 page (Jekyll, hosted on GitHub Pages). See here http://paulhammant.com/dsjhskdjfsdfsd for a live example. I’ll run though how to do the same for yours…

Making a static JSON resource of all your postings

This is ph_postings_meta.json in the root of my Jekyll project:


---
layout: null
---
{
"posts" : [
{% for post in site.posts %}
    {
        "title": "{{ post.title }}",
        "href": "{{ post.url }}",
        "words": "{% for tag in post.tags %} {{tag | downcase }}{% endfor %}{% for category in post.categories %} {{category | downcase | replace: ' ', '_'}}{% endfor %}"
 }
{% endfor %}
]
}

I’m putting tags and categories into a “words” property so that I can match on them in Angular as well as words within the title of the page. See here http://paulhammant.com/ph_postings_meta.json. There’s a risk that I could mess up during publication and make something that needs escaping in the JSON feed, which I’ve not addressed.

Angular in the 404 page to use that JSON

This is 404.html in the root of my Jekyll project:


---
layout: page
title: 404
hidden: hidden
permalink: /404.html
---
<h1>Sorry!</h1>

<p>The page you're after isn't hosted here</p>

<h1>Search for it ?</h1>

<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<script>

    var App = angular.module("four04App", []);

    App.config(function($interpolateProvider) {
        $interpolateProvider.startSymbol('⸨');
        $interpolateProvider.endSymbol('⸩');
    });

    App.filter('matchesQuery', function(){
        return function(items, query){
            var alternate = query.replace(/ /g,"_").toLowerCase();
            var lcQuery = query.toLowerCase();
            var arrayToReturn = [];
            for (var i=0; i<items.length; i++){
                if (items[i].title.toLowerCase().indexOf(lcQuery) !== -1 || items[i].words.indexOf(alternate) !== -1) {
                    arrayToReturn.push(items[i]);
                }
            }
            return arrayToReturn;
        };
    });

    App.controller('PostListCtrl', ['$scope', '$http', function ($scope, $http) {
        // draft articles in _posts are hidden from public view, and begin 1970-01-01-
        // this allows me to copy/paste the file name direct to the browser for sharing of previews.
        if (window.location.href.indexOf("1970-01-01") !== -1) {
            window.location.href = window.location.href.replace(/1970-01-01-/, "1970/01/01/").replace(/\.textile/, "").replace(/\.markdown/, "").replace(/_posts\//, "");
        }
        $scope.query = "";
        $scope.posts = [];
        $http.get('/ph_postings_meta.json').success(function (data) {
            $scope.posts = data.posts;
        });
    }]);

</script>

<div ng-app="four04App" ng-controller="PostListCtrl">

    <p>Subset to posts containing these words: <input ng-model="query"></p>
    <p>Posts matching above query:</p>
    <ul>
        <li ng-show="post.title != ''" ng-repeat="post in posts | matchesQuery:query">
          <a ng-href="⸨post.href⸩">⸨ post.title ⸩</a>
        </li>
    </ul>
</div>

It’s still run through a series of Jekyll decorators to stitch it into the site experience. The raw/endraw is to stop Jekyll processing things intended for Angular.

Limitations

Of course this stuff is not SEO safe (yet). But 404 pages do not have to be, so let’s not sweat that.

Jumping past the SEO issue.

It would be great if there were an ng-slurp feature of Angular, that could pull a model out of some relatively normal HTML, after the page’s initial presentation. Here’s what that could look like, if it existed:

<ul>
  <li >
    <a ng-slurp="@href, title = value()"
	   href="/2013/02/20/abc">A B C</a>
  </li>
  <li>
    <a ng-slurp="@href, title = value()"
	   href="/2013/02/16/d_e_f">D E F</a>
  </li>
  <li>
    <a ng-slurp="@href, title = value()"
	   href="/2013/02/15/someArticle">Some Article</a>
  </li>
  <li ng-slurp-init="posts = []" ng-repeat="post in posts | matchesQuery:query">
     <a ng-href="{{post.href}}">{{ post.title }}</a> 
  </li>
</ul>

The first three <li> rows are real HTML, the fourth is the angular template one. The Page would load and present itself as normal, then Angular and the slurper would kick in and recreate the model object ‘posts’ with content from those HTML rows. Here’s the tricky bit, three plain HTML <li> rows would be replaced with doppelgängers that are under Angular control. Would that flicker any more than normal, as Angular activates? I should read more on ng-cloak for the fourth row too. Right now that’s all a hypothetical, and I’m not about to do any work to make that happen.

Perhaps too ‘ng-slurp’ is bit silly, as JavaScript should be activated in all search engines within a couple of years. Meaning you’d stick with idiomatically correct Angular with data in JSON that’s separately cacheable to the HTML source.

Jekular ?

If anyone is going to start a blogging platform that’s correct for GitHub Pages, in that it is Jekyll on the back end, but is also lots of Angular in the front end - count me in. Angular has a fairly general suitability for a blog, or any content-rich site. The implicit ‘search’ facility (of this article) specifically could go much further. Also on desktop/mobile responsiveness (as previously noted) there’s some suitability for Angular.

Updates

May 14th, 2016 - Angular 1.5.5 upgrade



Published

February 24th, 2013
Reads:

Tags

Categories