And now you are living in the fabulous world where you can, from your browser:

Edit existing drafts and posts

Create new drafts

Publish drafts into posts

Run some basic git commands.

Run middleman build

Run middleman deploy

Why do this

This is explained in more detail in the Slow Data and Fast Sites post, but basically what we’re trying to do it seperate out runtime tooling with development time tooling. Future work will be around differentiating when components are rendered and from which data source.

Static sites have a huge runtime benefit, but actually setting them up and running them can really suck unless you already know how to do it. This is a proof of concept of how to add a CMS to a static site so that it’s easier to manage. This doesn’t address the “pain in the ass to setup” part, but it helps with the usability.

Lets walk through what we’re doing if we were to implement this in your own app directly. The gem packages this up with a slightly different directory scheme, but the code below is the same but slightly easier to understand.

The basic idea

We’re going to add a static JavaScript application in the source/admin directory which will interact with the local filesystem and middleman install. This application will consume data from your middleman app on the local system, in the “development” environment, such as the lists of published and draft articles, and will interact with a API server that will be mounted as part of the middleman preview server. This will allow the Javascript app to change the filesystem, which will in turn be monitored and rendered by the middleman application.

Examples of the commands that can be triggered are:

create new draft

publish draft into article

git status

bundle exec middleman build

Let go through each of the components as if we were building them in our app directly. The gem does a lot of this for you, but it’s always nice to see how it works.

Static Middleman Site

We’re going to add our files in source/admin, and source/javascript/admin and source/stylesheets/admin.scss. All of these files will be rendered in development mode, so when the server is running locally and you load up http://localhost:4567/admin we can use the middleman build process to load up our UI. This, in turn, will use AJAX to pull in data from the running middleman instance.

Inside of config.rb, we tell middleman about our pages, and we tell the build process to ignore these files as part of the build process since we don’t want to release them into production:

If they were not ignored, then you would be deploying a read-only version of the admin tools, since the production build would include the javascript application but not the API which is needed to make changes.

This will allow us to create files like /source/admin/drafts.json.erb:

Note that when we do middleman build these files will not be generated, since we’ve specified them as ignore. These files only are in the :development environment.

The Sinatra App

That’s fine for static data, or data that can be exposed using normal middleman ways of doing things, but we’ll also want to be able to interact with the filesystem and run commands. It’s also going to be useful to interact with the middleman Sitemap as it understands it, so we can build a simple sinatra app that acts as a bridge. The current version of the code I’m using for this blog can be found on github.

First we need to require and mount our app on config.rb. This is done like so:

To reiterate right now the code is in more of a proof of concept, butso please feel free to make suggestions. Lets take a look at a few of these methods in lib/api_server.rb:

require'sinatra/base'require'sinatra/json'classApiServer<Sinatra::Baseget'/'do"This is the api server"endget'/post'doapp=load_applogger.info"Looking up #{params[:path]}"file=app.sitemap.find_resource_by_pathparams[:path]ifparams[:path]if!filestatus404jsonerror:"Unknown path #{params[:path]}"elseraw=File.readfile.source_filebody=raw.gsub(/^---\n.*?---\n*/m,"")# Remove the preyamljsonmeta:file.data,content:bodyendendprivatedefload_appopts={}app=::Middleman::Application.server.instdoset:environment,opts[:environment].to_symifopts[:environment]loggerendappenddeflogger::Middleman::Logger.singleton(1)endend

The Javascript App

I also wanted to see how to build this in React and Reflux to start exploring how we can start to add build-type rendering into the equation. While this doesn’t do any server side rendering, its still good to explore how a React app actually works.

The main html file simple calls React.render( React.createElement(App, null), document.getElementById('content') ); to setup the page. The App component works off of the pathStore component, which keeps track of what page the user is currently editing. Lets take a quick look at how the DashboardDraftList component works:

The first thing that this does is wire itself up to the draftStore store which manages the communication to the server and the state received from the server. When this changes, draftStore will trigger a state change and all components that have been connected will receive a setState message, which will then call the render method of the component keeping the UI up to date. As part of the React component life cycle componentDidMount, we call the updateDraftList() action when the component is first loaded on the page. This is defined in drafts store:

The first thing we do is to define and export a Reflux action called updateDraftList. This action is what the componentDidMount function triggeres. Inside of the draftStore we listen for that action, and when it get it, call onUpdateDrafts. This uses request.get to load up the json from /admin/drafts.json and then pushes it out to all of the components that have subscribed to it.

The other components are defined here and the different stores can be found here. This is my first real React app so I’m sure there’s cleaner/better ways of doing things…

Putting it all together

When I was working on this I did it out of the source directory of this blog, but I wanted to package it up into a gem so that other folks could use it. I pulled the relavent code out from willschenk.com/source into middleman-blog-ui/source and then created a middleman extension to put the various templates in the sitemap, tell sprockets where to find the javascript files, and setup coffee script.

Read next

See also

This article walks through the motivations driving and benefits of using a the Seed Architecture for building performant websites using Middleman, React, and a seperate API server such as Parse. The benefits are:
You get full SEO with a heavy client JavaScript site without having to do crazy things with PhantomJS or meta fragments. Hosting and operations become both cheap and doesn’t require a support team. Scaling out the system is mainly a bandwidth problem, and secondarily a API scaling problem for a subset of behavior.

We’re going to build a simple, niave search for middleman blogs. We’re going to build a search index at build time, and then use that index to perform the search itself on the client side.
Building the index When you typed in something in google, it doesn’t then go and hit every page on the internet to check to see if there’s a match. It doesn’t even look at every page that it has squirreled away somewhere in the googleplex.

Middleman extensions, like rails plugins, are packaged as gems. There are three main ways to extend middleman. You can add helpers, add middleman commands, or extend the sitemap generation in someway. Lets go through those in detail.
Creating the extension Create a gem using bundle gem _name_
$ bundle gem middleman-graphviz Add middleman-core to your gem dependancies in the .gemspec file:
spec.add_runtime_dependency 'middleman-core', ['>= 3.0.0'] Register your extension into middleman.