on Writings, Tutorials, Keystone, Node, CMS

Customising KeystoneJS: Adding Post Types [Part 1]

Introduction

This tutorial will show you how to add a custom post type in KeystoneJS. What do I mean by custom post types? I got this term from my knowledge of WordPress development. Wordpress can hold and display lots of different types of content, one of which is generally called a post. This is all well and good if you just want to add blog posts, but what if you want to add something else like a portfolio item?

You could just add it as a blog post, but wouldn't it be better if you had a 'portfolio' page in addition to a blog page, displaying all of your 'portfolio items' instead of blog posts? That's the general concept, and this tutorial will show you how to achieve this in KeystoneJS. I'll be showing you how to do it with the example of 'Portfolio' post types, since that's what I've just done (with this very website).

Overall, the tutorial will be separated into 2 parts:

  • Part 1 (now) will get a new Post Model set up for our new Portfolio post type. 
  • Part 2 will look at the Portfolio middleware and creating view templates

Step 1: Create the Portfolio.js file

Post types in KeystoneJS aren't the same as WordPress's implentation. Keystone goes about it slightly differently, using Post Models controlled by Lists. No need to feel overwhelmed either, because this is easy and you you can learn just by doing. Have a look at the example in Keystone's documentation - it shows us everything we need to know. Just above the example code, the documentation says 'A simple Post model for a blog might look like this'. And that's exactly what we're doing - creating a post model.

If you aren't aware, we've already got some post models in our Keystone project by default in the /models directory, and the example code in the documentation just happens to be the default model for Posts: /models/Post.js

Model directory structure

Image above: An open models directory showing Enquiry, Gallery, Post, PostCategory, and User models

Open it up and have a look. So we basically need to add one of these models, but for 'Portfolio'. Go ahead and create a new javascript file int he same folder called Portfolio.js. For clarifaction, your directory structure should now look like this:

Post.js directory

Image above: Added Porfolio.js model to the models directory

Next, simply copy and paste the entire contents of Posts.js into the new Portfolio.js file.

Just by glancing at the code, it's clear that the Posts.js model does more or less what we want the Portfoio model to do. Now jump right in and have a look at the Post.add() function (or just look below) - you can easily read that there’s a title, state, author, created date etc:

Post.add({  
    title: { type: String, required: true },
    state: { type: Types.Select, options: 'draft, published, archived', default: 'draft' },
    author: { type: Types.Relationship, ref: 'User' },
    createdAt: { type: Date, default: Date.now },
    publishedAt: Date,
    image: { type: Types.CloudinaryImage },
    content: {
        brief: { type: Types.Html, wysiwyg: true, height: 150 },
        extended: { type: Types.Html, wysiwyg: true, height: 400 }
    }
});

With a couple minor tweaks, that’s just what we need.


Step 2: Create the Portfolio Model

Right now, the Porfolio.js should look exactly the same as Posts.js. We'll step through each bit of the code that needs changeing now. The first bit we'll change is where you see the creation of a new Keystone List. According to the KeystoneJS documentation, Lists control our data schema and models, and Items are actually documents. So by creating a new List, we create our new model as seen below:

var Portfolio = new keystone.List('Portfolio', {  
    autokey: { path: 'slug', from: 'title', unique: true },
    map: { name: 'title' },
    defaultSort: '-createdAt'
});

The major changes from the Posts.js is that we're now passing 'Portfolio' as the first parameter to the List() function for the name of our model instead of 'Post', and also our variable is also renamed to 'Portfolio'. We've kept the same list of options in the second parameter for now. 

Once we’ve done that, we add fields to our List, using the add() function:

Portfolio.add({  
    title: { type: String, required: true },
    state: { type: Types.Select, options: 'draft, published, archived', default: 'draft' },
    author: { type: Types.Relationship, ref: 'User' },
    createdAt: { type: Date, default: Date.now },
    publishedAt: Date,
    image: { type: Types.CloudinaryImage },
    content: {
        brief: { type: Types.Html, wysiwyg: true, height: 150 },
        extended: { type: Types.Html, wysiwyg: true, height: 400 }
    }
});

Now that’s taken care of, how does that little bit of code actually save stuff to the database? This part of the documentation will shed some light:

“Behind the scenes, a Keystone List will create a mongoose schema, and add the appropriate paths to it for the fields you define.” 

Sounds good enough to me. So your final Porfolio.js code at this stage should look like this:

var keystone = require('keystone'),  
    Types = keystone.Field.Types;

/**
 * Portfolio Model
 * ==========
 */

var Portfolio = new keystone.List('Portfolio', {  
    map: { name: 'title' },
    autokey: { path: 'slug', from: 'title', unique: true }
});

Portfolio.add({  
    title: { type: String, required: true },
    state: { type: Types.Select, options: 'draft, published, archived', default: 'draft', index: true },
    author: { type: Types.Relationship, ref: 'Users', index: true },
    publishedDate: { type: Types.Date, index: true, dependsOn: { state: 'published' } },
    image: { type: Types.CloudinaryImage },
    content: {
        brief: { type: Types.Html, wysiwyg: true, height: 150 },
        extended: { type: Types.Html, wysiwyg: true, height: 400 }
    },
    categories: { type: Types.Relationship, ref: 'PostCategory', many: true }
});

Portfolio.schema.virtual('content.full').get(function() {  
    return this.content.extended || this.content.brief;
});

Portfolio.defaultColumns = 'title, state|20%, author|20%, publishedDate|20%';  
Portfolio.register();  

After adding that new file, we can restart our Node server to see what has changed. If you go to the Keystone admin dashboard, you’ll now see that Portfolios appearrs in the Manage list:

KeystoneJS admin dashboard


Step 3: Add the model to the admin navigation

However, it doesn't appear in our admin navigation bar. You can control what appears in the admin bar from within /keystone.js, in the nav array. We just need to add 'portfolio':'portfolios' to the options as shown below.

//Configure the navigation bar in Keystone's Admin UI

keystone.set('nav', {  
    'posts': ['posts', 'post-categories'],
    'portfolio':'portfolios'
    'galleries': 'galleries',
    'enquiries': 'enquiries',
    'users': 'users'
});

Doing this also assigns our portfolios model with the name portfolio. Now it won't have the word 'other' above it in the Manage list.


Step 4: Set the model relationships

Also, if you’re sharp you’ll have noticed a slight problem with our code. Our model won't work properly as there's a couple of relationship statements left over from the post model that need fixing. These are the two I'm referring to (in models/Portfolio.js):

author: { type: Types.Relationship, ref: 'Users', index: true },

categories: { type: Types.Relationship, ref: 'PostCategory', many: true }

So what’s going on here? If you look at Posts.js (and the couple lines above), there are relationships to both ‘Users’, and ‘PostCategory’. Now look in the models folder, and you’ll see a couple of matching files: Users.js and PostCategory.js. Becoming clearer? It will.

 Users.js and PostCategory.js are also models, created the same way as Post.js, except they store users and post categories (as opposed to Post.js storing posts). The important part is to know that in order to link Users to Posts, a relationship needs to exist. Open up Users.js, and you’ll see this relationship does in fact exist:

/**  
 * Relationships
 */

Users.relationship({ ref: 'Post', path: 'posts', refPath: 'author' });

We need to replicate this for our new Portfolio.js as follows:

Users.relationship({ ref: 'Portfolio', path: 'portfolios', refPath: 'author' });  

Just add that underneath the first relationship.

Next, I’m just going to remove the categories data from Portfolio.js for simplicity. You could create a new Model called PortfolioCategories (just a copy of PostCategories), and then link it to Portfolios via a relationship if you wanted.


Well done! 

That's all there is for part one. We're now at the stage where we can actually publish a Portfolio piece. However it’s not that much use because we can’t actually see it. Part 2 of this series will solve this part.

Go to Part 2