Ajaxify _s

Ajaxify Your WordPress _s (Underscores) Theme With This Code

I’ve created a modified version of the Browserstate (History.js) Ajaxify plugin that works with _s (Underscores). In order for this to work, you’ll need to use wp_enqueue_script() to include the History.js and scrollTo libraries, as well as my modified Ajaxify script. Do this in your existing enqueue function. If you don’t know what that means, check out this article.

wp_enqueue_script( 'scrollto', '//balupton.github.io/jquery-scrollto/lib/jquery-scrollto.js', array( 'jquery' ), '', true );
wp_enqueue_script( 'history-js', '//browserstate.github.io/history.js/scripts/bundled/html4+html5/jquery.history.js', array(), '', true ); 
wp_enqueue_script( 'ajaxify', '//raw.githubusercontent.com/AbacusPowers/ajaxify/master/ajaxify-html5.js', array( 'jquery' ), '', true ); 

Above, you’ll find the lines to include, if you want to load these libraries remotely. I would recommend, however, that you actually take the time to download them, and use a filepath specific to your theme. You can use something like

wp_enqueue_script( 'ajaxify', get_template_directory_uri() . '/js/vendor/ajaxify/ajaxify-html5.js', array( 'jquery' ), '', true );

instead of the direct link to the library on GitHub. To get you started, here’s a direct link to the Ajaxify file download.

Note: The menu update only works for the ‘current-menu-item’ class, at the moment. Ideally, this would be modified to also update any parent or children menu classes as well.

Add Post Types to WordPress

How to add WordPress custom post types without another plugin

When a post is not a post

Almost every WordPress website I build uses at least one custom post type. WordPress comes packaged with a couple of post types (posts, pages, attachments), but not everything fits neatly into one of these boxes. In fact, a problem a see all too often is new WordPress developers using categories to control the appearance and structure of different types of content. I also regularly hear non-WordPress developers complain that, “WordPress sux, ’cause not everything qualifies a post”. Well, that’s exactly right, but WordPress never said you had to have to use the “Post” post type.

Here’s the scenario. I’m building my website and I want to add my portfolio projects to show off my work. This doesn’t seem like something that fits into my normal blog feed, so I don’t want to fuss with using categories or tags to control my templates or using `pre_get_posts()` to filter those categories out of my blog index.  Instead of manually coding a page with all of my projects, I can take advantage of WordPress’ content management and add a new post type.

Let’s do it!

We must add our function to the ‘init’ phase in WordPress’ cycle, so first let’s drop in the add_action. It looks like this:

add_action( 'init', 'my_custom_post_types');

Next, we’ll create the function, which calls register_post_type() and that’s it.

function my_custom_post_types(){
    register_post_type('project', array( ARGUMENTS GO HERE ));
}

The only required parameter here is the first: the post type name. So, you can literally add a new post type, called ‘project’, by writing:

add_action( 'init', 'my_custom_post_types');
function my_custom_post_types(){
    register_post_type('project');
}

However, this method uses all of the WordPress defaults, which can be a little confusing from a UX standpoint, especially in the dashboard, because everything will be labeled with generic labels like “Edit post”, “All posts”, etc.

I have a fever and the only prescription is more options

Inside the array, we’re going to add any of the parameters we’d like to change. Since I have a tendency to forget all of my options, I find it helpful to first add ALL of the parameters, and then remove the ones I don’t need. The most important one, for me, is usually the ‘labels’ section. This section is an array, which controls how the text is printed across the site. So, here’s the post type registration with everything filled out.

add_action( 'init', 'my_custom_post_types');
function my_custom_post_types()
{
    //projects (portfolio items)
    register_post_type('project',
        // If you get stuck, need some more detail, or are looking to go deeper, read the documentation here: https://codex.wordpress.org/Function_Reference/register_post_type */
        array(
            'labels' => array(
                'name' => __('Projects', 'your-text-domain'), /* The group title. It shows in the main dashboard menu, alongside "Posts", "Media", "Pages", etc. */
                'singular_name' => __('Project', 'your-text-domain'), /* The individual label. Usually the singular form. Displayed in the admin bar, under "+ New" */
                'all_items' => __('All Projects', 'your-text-domain'), /* The 'all items' label. Displayed under the group name in the dashboard. */
                'add_new' => __('Add New', 'your-text-domain'), /* The 'add new' menu item. Displayed under the 'all items' label and at the top of the page in the 'all items' view. */
                'add_new_item' => __('Add New Project', 'your-text-domain'), /*  */
                'edit_item' => __('Edit project', 'your-text-domain'), /* The header of the page when editing a post. */
                'new_item' => __('New project', 'your-text-domain'), /* The header of the 'Add new' page. Displays when adding a new post. */
                'view_item' => __('View project', 'your-text-domain'), /* View display title */
                'search_items' => __('Search Projects', 'your-text-domain'), /* Search label for dashboard pages */
                'not_found' => __('Nothing found in the Database.', 'your-text-domain'), /* This displays if there are no entries yet */
                'not_found_in_trash' => __('Nothing found in Trash', 'your-text-domain'), /* This displays if there is nothing in the trash */
                'parent_item_colon' => 'Parent Projects:' /* Parent label for hierarchical post types */
            ), /* end of arrays */
            'description' => __('Projects for the portfolio', 'your-text-domain'), /* A description of your post type */
            'public' => true, /* If set to 'false', this will hide this post type from the public */
            'publicly_queryable' => true, /* Sets whether the post type can be queried. Automatically false, if 'public' is false. */
            'exclude_from_search' => false, /* Want to hide this post type from searches on your site? */
            'show_ui' => true, /* If set to 'false', the dashboard UI won't be created. All management (creating/editing/deleting) would need to be done through code. */
            'menu_position' => 5, /* This controls the order in the dashboard menu. 5 - below Posts, 10 - below Media, 15 - below Links, 20 - below Pages, 25 - below comments, 60 - below first separator, 65 - below Plugins, 70 - below Users, 75 - below Tools, 80 - below Settings, 100 - below second separator */
            'menu_icon' => 'dashicons-format-video', /* Which icon should we use? Checkout your options here: https://developer.wordpress.org/resource/dashicons/#editor-code */
            'rewrite' => array('slug' => 'project'), /* Will automatically inherit the post type label unless specified. */
            'has_archive' => 'projects', /* If 'false', no archive will exist. If a string is given, it will set the slug of the archive page.  */
            'capability_type' => 'post', /* This is related to user permissions and capabilities. Use 'post' or 'page' unless you are building custom capabilities. */
            'hierarchical' => false, /* Can a post have a parent post? If so, use 'true'. */
            'supports' => array('title', 'editor', 'thumbnail', 'page-attributes', 'excerpt', 'revisions', 'sticky') /* This sets which features are available to this post type. See https://codex.wordpress.org/Function_Reference/register_post_type#supports */
        ) /* end of options */
    ); /* end of register post type */
}

Note: I’ve used the function __() to fill in all text. This allows for us to use a “text domain” for translations. Hopefully, it’s obvious that ‘your-text-domain’ should be replaced with the text domain of your project. If you don’t have one yet, go ahead and make one up. This code will still work, and later, you can improve your project by implementing translations across the board. If you want a little preview of how that would go, the function works like this: __('Text to display goes here','text-domain-goes-here'). When you build your translation files, the ‘Text to display goes here’ will be the basis of your translations and will get used for all languages. So, if you had a translation file for English, you could write __(‘nothing’, ‘text-domain’), then later tell it to translate ‘nothing’ as ‘Text to display goes here’. Then, when this function is run, every instance in the code of the string ‘nothing’ would be replaced by the string ‘Text to display goes here’. Make sense? If not, you can read more here.

Using it in my project

If you need to add a second post type, just add another register_post_type() call. No need to duplicate the whole my_custom_post_types() function.

I like to include this code in a separate file, called ‘post-types.php’ or something really fun and creative like that. Then in my main plugin file (or functions.php, if building a theme), simply require that file.

For plugins:

/*For plugins*/
require_once __DIR__ . '/post-types.php';

For themes:

/*For themes*/
require get_template_directory() . '/post-types.php';

The final piece of the puzzle is getting the post type to be activated when the plugin or theme is activated. If using this code in a custom plugin, use this:

/*For plugins*/
function my_rewrite_flush() {
    // First, we "add" the custom post type via the above written function.
    // Note: "add" is written with quotes, as CPTs don't get added to the DB,
    // They are only referenced in the post_type column with a post entry, 
    // when you add a post of this CPT.
    my_custom_post_types();

    // ATTENTION: This is *only* done during plugin activation hook in this example!
    // You should *NEVER EVER* do this on every page load!!
    flush_rewrite_rules();
}
register_activation_hook( __FILE__, 'my_rewrite_flush' );

Otherwise, if including this in a theme, use this:

/*For themes*/
function my_rewrite_flush() {
    my_custom_post_types();
    flush_rewrite_rules();
}
add_action( 'after_switch_theme', 'my_rewrite_flush' );

Moving on

Now, most of the time, when we want something to behave differently or be presented differently than a post, we are also going to want some additional meta data (or custom fields) to be added to our new post type. I will cover custom fields in a future post, but if you’re itching to get to the good stuff, check out CMB2. Requiring their files in your project can be a pretty neat way to bake custom fields into your theme or plugin without requiring another plugin (no more ACF), and it’s pretty easy to learn, once you’ve grasped the major concepts here.

Good luck with your post types! Please leave a comment below if anything here is unclear, inaccurate, or turns out to be just what you needed.