Building a WordPress “product box” plugin, part 4 – adding the image

Part 1 – Initial idea, design, and first-pass implementation
Part 2 – Building the product box that gets displayed in posts
Part 3 – Refactoring the codebase into classes, views, and separate files
Part 4 – Adding image uploading, shared plugin options, and uninstallation

Day 9: Image upload and display

Next up: adding the Image Upload / Select feature to the plugin. When the user creates a new Product Box, they should be able to pick an image for it (not just paste in a URL, that’s too janky for this fancy plugin). Furthermore, the user also needs to be able to edit that image choice when editing an existing Product Box.

WordPress already has a media management page/popup.

That’s this thing:

Adding WordPress’s built-in Media Uploader to my plugin was quick and painless.

I just needed to hook into this feature from my plugin’s page. I used this tutorial as a starting point.

I didn’t use quite the same structure as this tutorial recommends. I wanted all of my form HTML in the same view files, not echoed in by a php function elsewhere in the codebase, but I did need to add an admin.js file and enqueue it as shown here, in my ‘init’ action. The tutorial left this step out so I’ve included it here:

add_action( 'init', function() {
    include dirname( __FILE__ ) . '/includes/class-amazin-product-box-admin-menu.php';
    include dirname( __FILE__ ) . '/includes/class-amazin-product-box-list-table.php';
    include dirname( __FILE__ ) . '/includes/class-form-handler.php';
    include dirname( __FILE__ ) . '/includes/amazin-product-box-functions.php';

    // WordPress image upload library
    wp_enqueue_media();
    $jsurl = plugin_dir_url(__FILE__) . 'admin.js';
    wp_enqueue_script('admin', $jsurl, array( 'jquery' ), 1.1, true);
   
    ...

The images are stored in my post content object by their ID (look at the very end – “215” is the image’s ID).

{"productName":"The Rainbow","productTagline":"It\'s got all the colors of the spectrum","productDescription":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce pulvinar, leo at cursus finibus, lectus massa tincidunt nulla, vitae lobortis lectus orci vitae nunc. Aenean a elit mollis, iaculis felis sed, consectetur velit. ","productUrl":"http://wow.com","productButtonText":"See the rainbow on Amazon.com","productImage":"215"}

I used wp_get_attachment_url() to turn the ID into the full path to the image. This is how it looks in the html that renders the actual product box in the post:

<img src="<?php echo wp_get_attachment_url( $content['productImage'] ) ?>"/>

Here’s my New Product Box page as the user first sees it:

And here’s my Edit Product Box page displaying an image the user already chose for a product box:

And here it is in a post!

Smaller images are centered; larger ones fill the frame as shown in this screenshot. Huge ones are stopped from overflowing the box and get scaled to fit. For pleasing results, square images are recommended.

This merged pull request shows the complete image uploading code (plus some bonus CSS styling and tagging to make it all look a bit better).

Day 10: Making the plugin’s settings page

I thought it’d be cool if all the product boxes shared a “headline” phrase, such as “We recommend” or “Our choice”. The user should edit in one place instead of on a per-box basis (though maybe a per-box override could be added, too, in the future).

WordPress calls this sort of thing an “option” and makes it very easy to set/get them. (This tutorial was helpful for identifying which hooks to use.)

Unlike everything else so far, the settings are not saved as posts. They’re saved as options in the _options table. Since all plugins (and WordPress itself) saves their plugins here, it’s important that the option have a unique name. Here’s my new option along with the string I gave it:

I wanted my plugin options to be adjustable from the plugin page (rather than a separate settings/options page elsewhere in the dashboard like some plugins do) so I simply added another form below the list table:

Here’s the form HTML (you can find this in view/product-box-list.php)

<form method="post" action="options.php">
  <?php settings_fields( 'amazin_product_box_options_group' ); ?>
    <h3>Product box settings</h3>
    <p>These settings are shared by all product boxes on your site.</p>
    <table>
      <tr valign="top">
        <th scope="row">
          <label for="amazin_product_box_option_headline">Product Box Headline</label>
         </th>
         <td>
           <input type="text" id="amazin_product_box_option_headline" name="amazin_product_box_option_headline" value="<?php echo get_option('amazin_product_box_option_headline'); ?>" />
            <br/>
             <span class="description"><?php _e('Examples: "We recommend", "Our pick", "A Sitename Favorite", etc.', 'apb' ); ?></span>
          </td>
        </tr>
    </table>
  <?php  submit_button(); ?>
</form>

I also had to add it as an option and register the settings in amazin-product-box-plugin.php:

add_action( 'init', function() {
  //other init code here
  add_option( 'amazin_product_box_option_headline', 'We recommend');
  register_setting( 'amazin_product_box_options_group', 'amazin_product_box_option_headline', 'amazin_product_box_callback' );

And finally, I modified the HTML that displays the actual in-post product box to echo out the setting value instead of a hard-coded string (also in amazin-product-box-plugin.php):

...
<p class="amazin-product-box-recommend-text"><?php echo get_option('amazin_product_box_option_headline'); ?></p>
...

Here’s the product box displaying the text “Our choice” (it used to say “We recommend”) after I made the change in the plugin’s settings.

The complete code that adds plugin-specific options can be found in this merged pull request.

[Bug fixes and improvements] Adding a welcome banner, hiding search, completing the shortcode display, and fixing “sort by name”

Before moving onto a new feature I took a moment to make a few more improvements. There is now a “Welcome” banner at the top of the plugin page, the shortcodes now display in full for each table row, I hid the search bar, and I fixed sorting by name (it used to not work at all because it was trying to run the query on ‘name’ instead of ‘post_title’).

You can see all of that code here in this commit.

Day 11: Adding the uninstall script

First, I read the official guide on deactivating vs. uninstalling plugins. I figured if there was one place I really didn’t want to just fly blind, this was it.

My uninstall script needed to do the following:

  • Delete the custom posts of type “amazin_product_box” from the _posts table
  • Delete the plugin’s setting (“amazin_product_box_option_headline”) from _options

I went the route of making a standalone .php script. The example from WordPress’s developer guide shows dropping a table but that’s a bit extreme for my use, I just needed to delete the posts of type “amazin_product_box”.

Here’s what I did (it’s basically the example from WP’s own site with my own plugin’s names for things instead):

uninstall.php

<?php
// if uninstall.php is not called by WordPress, die
if (!defined('WP_UNINSTALL_PLUGIN')) {
    die;
}

$option_name = 'amazin_product_box_option_headline';

delete_option($option_name);

// for site options in Multisite
delete_site_option($option_name);

// drop a custom database table
global $wpdb;
$wpdb->query("DELETE FROM {$wpdb->prefix}posts WHERE post_type='amazin_product_box'");
?>

With the uninstall script in place, I was able to deactivate and uninstall my plugin. I verified that the custom posts were gone from the _posts database and the headline option was gone from _options and from WordPress’s plugins directory on my server. Everything looked good.

The shortcode gets left behind in the posts, but I don’t think there’s anything I can do about that:

Finally, I reinstalled my plugin. None of the previously-created product boxes were present, which I expected since they were gone from the db, and I was able to create a brand new product box and stick it in a post.

Sweet – back in business.

Phew. I was dreading this part but the uninstall feature turned out to be the easiest, most “got it right on the first try” step of the whole project.

Here’s the commit that adds the uninstall script.

Day 12: Testing, bug fixes, and trying it out on a real website

The last thing I did was install the plugin on one of my actual sites and try it out as if I were an actual user.

I found a few bugs (including one that made the button not actually go anywhere, yikes) and had a few ideas for improving it, so I spent this last day on fixing those things.

  • [Bug fix] – Editing a product box with an existing image now retains the existing saved image
  • [Improvement] – Image upload help text added to Edit and New forms
  • [Improvement] – Added a “plugin action link” that goes directly to Amazin’ Product Box management page via the plugin’s entry in the plugins page
  • [Bug fix] – Button actually goes to the user’s link now
  • [Improvement] – Added a setting for whether the button should open the link in a new tab or stay in the same tab
  • [Improvement] – Product Box is now narrower than the post (I think it looks nicer that way)

You can see these fixes and improvements in this merge. I also did a small “security” pass in which I added code to prevent direct script access and removed an unused file. You can see that commit here.

Final thoughts

Here it is: my first WordPress plugin, just over 2 weeks after I started the project – looking exactly how I’d hoped.

I started blogging about product photography (and later smart home technology) about 5 years ago. I wanted to make my own custom plugins but I was just a baby programmer at the time and I got overwhelmed by terminology and just generally having no clue how to put something like it together.

Even now, after having worked professionally as a full-stack web dev for a few years and having completed most of a computer science degree, I was still a little intimidated by this project. Working in something new is always a bit uncomfortable at first.

Fortunately, this project wasn’t nearly as hard as I’d feared, and while I’m sure there’s something I got wrong with my first attempt at a plugin, I was only a little bit scared to deploy it on one of my live sites. :D

If you read this entire dev journal, thanks for following along – and if you see anything I could’ve done better, don’t hesitate to leave a comment or create a pull request on the project repo.

Resources

For future reference, here are some guides I found helpful for WP plugin development:

Building a WordPress “product box” plugin, part 3 – refactoring

Part 1 – Initial idea, design, and first-pass implementation
Part 2 – Building the product box that gets displayed in posts
Part 3 – Refactoring the codebase into classes, views, and separate files
Part 4 – Adding image uploading, shared plugin options, and uninstallation

Day 8: Refactoring the plugin’s management page with WP_List_Table

I did some soul-searching and realized that while I was happy with what I’d built so far in Part 1 and Part 2, it wasn’t very ‘standardized’. In other words, I had finally learned enough to want to scrap it all and start over.

I’m glad I did – and while it took some effort, it was well worth it in the end.

In which I keep what I’ve learned so far but throw out the code and start over.

Using WP List Table for a standard-looking admin table

I noticed many plugins (such as Contact Form 7 and TablePress ) seemed to be using a standardized table design that looks like WP’s own default for managing posts, with Edit and Delete links on hover, a checkbox for each row, and bulk actions.

I dug around a bit and found the WP_List_Table class. It’s not an official developer API, but it’s stable and widely used for the creation of admin tables in plugins.

Reading about WP List Table led me to a few example codebases (this one is my favorite), a detailed guide, and even a generator.

(I recommend reading all of the aforementioned links before attempting to work with WP List Table, even if through a generator or boilerplate code.)

I used the tareq.co generators to get started. (Definitely watch the video to see how all the parts fit together and how to name the files, using the generators alone is not enough to piece it together).

The generator did not include delete so I implemented that myself.

Implementing Delete (both single and bulk delete) in the WP List Table

I got stuck on implementing “Delete” (both the singular variety and the bulk kind) for a little while, mostly because I was trying to refresh the page myself after calling my delete method. Naively, I tried solutions like wp_redirect() right after calling my delete function, but that didn’t work – the page would reload but the stale, already-deleted row(s) were still in the table until the page was refreshed a second time or I would get a “headers already sent” error.

Ultimately, the solution was not to call any kind of redirect or refresh action but to simply call process_bulk_actions() before getting $items (in my code, that’s apb_get_all_product_boxes()) in the prepare_items() method in list-table.php.

The prepare_items() method (starting at about line 190) is called any time the list page is rendered, and the list page is automatically reloaded and rendered when clicking the Delete link or submitting the bulk action on the form. With that in mind, I didn’t need to reload the page manually – I just needed to make sure the deleted items were processed and removed from the db before retrieving the latest list of items.

For more on how I implemented Delete after using Tareq’s generated files as a starting point, see my comment on the generator’s repo here.

Here’s the merged pull request for this refactor.

And here’s how it all looks now:

Final thoughts on this refactor

It took a few days and it felt like backwards progress for a while, but I’m glad I did it – now my plugin looks more like “professional” plugins, both in-page and in the codebase, and I learned a lot in the process. I’d rather do it right than hold onto prototype-grade code.

Building it myself from scratch was still an important learning step for me. At this point, I feel like I might actually finish this thing because all that’s left is… implementing the image box! (And uninstalling, and finding and fixing bugs, and trying it out on a live site…)

Continue on to part 4!

Building a WordPress “product box” plugin, part 2 – shortcodes and frontend

Part 1 – Initial idea, design, and first-pass implementation
Part 2 – Building the product box that gets displayed in posts
Part 3 – Refactoring the codebase into classes, views, and separate files
Part 4 – Adding image uploading, shared plugin options, and uninstallation

Now it’s time to render the Product Box in a post and give it some attractive styling.

Day 6: Embedding the Product Box in a post using a Shortcode

With the management page built (or at least built enough), the next thing I wanted to do was see a product box in a post. I knew I wanted to do this with a WordPress shortcode, so I started by reading the Shortcode API and a Shortcode tutorial and used it to whip up the most basic Shortcode-driven bit of code in the history of WP plugins:

function amazin_product_box_shortcode( $atts ) {
    $a = shortcode_atts( array(
        'id' => 'id'
        ), $atts );
    return "Hello from Amazin Product Box for post ID " . $a['id'] . '!';
}
add_shortcode( 'amazin-product-box', 'amazin_product_box_shortcode' );

I have the new Gutenberg (blocks) version of WP so I used the “Shortcode” block to put my plugin’s Shortcode into the post:

And here it is in the published post! (It’s the last line, the one that begins with “Hello”.)

Sweet – it’s in the post!

The next step was to make it HTML, not just a string. I encountered a few problems along the way, so I documented them and their fixes here to help explain the code a bit better.

I now have a product box in a post displaying real data from the database.

The code for this step can be found here.

Tying up a few loose ends: classes on the HTML tags, Shortcodes in the management table

In preparation for styling the box, I added classes to all the HTML elements. The plugin will have some default styles, but will also expose the CSS for the user to edit to their liking (and save it somewhere).

Here are the classes in their own commit (yeah, it’s a tiny commit :) )

I also went back to the admin page and made it so Shortcodes display here without rendering as product boxes inside the table. That commit is here.

Day 7: Styling the Product Box

The next thing I did was add a stylesheet to the project, enqueue it, and add a bunch of styles to make the default Product Box look better. Here it is in my post, now styled:

Here is the commit that adds the box’s styling.

I also added the “We Recommend” text at the top, which is hard-coded for now but will ultimately be something the user can edit in the plugin’s settings (so that the user only has to change it in one place if they want it to say something different, like “Our Pick”, or hide it entirely) and fixed a problem with loading the posts for editing (it was the same “NULL fix” I used for displaying them in the post).

I considered letting the user modify the Amazin’ Product Box’s CSS via the plugin, but realized that 1. this would be a ton of work to develop, debug, and support, likely involving sanitizing the user’s CSS input and saving it to a file in the plugin directory, retrieving it, using it (or parts of it) based on the user’s settings and 2. this functionality is basically already present in WordPress’s own Appearance > Customize tab.

As a test, I restyled the button using WP’s custom CSS area. Anyone who wants to change the look of an Amazin’ Product Box should be able to safely do so via their theme or child theme or WP’s Customize using this same technique.

The photo area is just a placeholder for now, I’ll come back to that in a bit. For now, move on to Part 3 where I refactor everything I’ve built so far to be more “standardized”.

Building a WordPress “product box” plugin, part 1 – making my own plugin management page

Part 1 – Initial idea, design, and first-pass implementation
Part 2 – Building the product box that gets displayed in posts
Part 3 – Refactoring the codebase into classes, views, and separate files
Part 4 – Adding image uploading, shared plugin options, and uninstallation

In this series: watch me code a WordPress plugin from scratch. I’ve never made a WordPress plugin before but I did a bootcamp half a decade ago and I’m good at Googling and trying things until it works, so I’m sure I’ll figure it out.

This began innocently enough. I wanted a thing, so I did what any self-respecting developer would do and spent 5x as long making it myself as it would’ve cost me to simply buy someone else’s solution. (On the bright side, I got exactly what I wanted.)

Here’s my finished product. It’s a “product box” that the user creates, manages, edits from a backend and displays in a post using a shortcode. The idea is to call attention to recommended products and capture affiliate revenue from the button (link) click.

The next 4 posts (and 6000+ words) are my “dev journal” that I added to every time I worked on it. You can follow along and watch me go from plugin newbie to finishing my first plugin and deploying it on a live site.

PS: This first post is by far the longest of the series.

Day 1: Making a plan, setting up a GitHub repo, and getting started

First, I captured the requirements for this plugin. It seemed like a small project but I still like to bang out a page of requirements first so I have something to refer back to and can (hopefully) catch things I didn’t think about when I was all excited about the fun parts of the new idea.

Behold, my simple mockup:

I made this in wireframe.cc. Paper or a drawing in some sand would’ve also been fine.

This simple mockup revealed a lot of requirements:

  • a way for the user to input a title
  • a tagline
  • a short blurb
  • an affiliate URL for the button to use
  • an image
  • maybe I want to let them customize the colors of the button and the text, too
  • a way to insert it into the post with a shortcode (might look something like [amazin-wp-box id=1])
  • a way to manage and edit existing Amazin’ product boxes

I collected all of this in a Google Doc that will serve as this project’s documentation and idea repository. No matter how small the project seems at first, it always seems bigger once I start writing down every little thing it needs.

Make a github repo for this project

I usually start projects off with a new GitHub repo. It’s just a good practice and it takes like 2 seconds to make a repo and I sleep better at night knowing my work is backed up offsite.

New github repo. I made it public so everyone can see my process (for better or worse).

And then I cloned it locally and cd’d into it.

Boilerplate plugin coding: getting started by adding a button to the admin menu

I’ve never made a WP plugin before but I’m know a ton of other people have and, if I’m lucky, a few of them have documented the process.

This tutorial from wpbeaverbuilder was the first thing I followed. It got me as far as making a .php file like so:

amazin-product-box.php

<?php
/**
 * Plugin Name: Amazin' Product Box
 * Plugin URI: http://majoh.dev
 * Description: Customizable product box for Amazon products with an affiliate link
 * Version: 1.0
 * Author: Mandi Grant
 * Author URI: http://majoh.dev
 */

..and uploading it to my site’s plugins directory like so:

I took a domain I wasn’t using for anything, hooked it up to my shared hosting account, and spun up a new WordPress installation on it.

Hey, look, here it is in my site’s plugins! I AM NOW A PLUGIN DEVELOPER

(I’m kidding about the being a plugin developer thing, this is literally all it does so far.)

The next thing I wanted to do was make the plugin have a “page” of its own where the user can add product boxes, delete them, edit them, etc.

That meant:

  • Adding a “button” (or maybe “link” is a better term for it) to the plugin’s page on the admin_menu (that’s the toolbar down the left side of the WordPress interface)
  • Make the plugin admin page load and display something so I know it loaded

Mostly this was a lot of trial and error. I didn’t know what WordPress calls that menu down the left, I didn’t know what hook to use, and the Internet is bursting with outdated WordPress tutorials.

It took some Googling to figure out what this was called and how to “hook” into it. Ultimately, I found my answers in the WP Codex and ended up with this:

Amazin’ Product Box now has a button in the admin menu and some placeholder text I wrote in the page itself.

Here’s a link to the git commit that contains the code for this work.

Designing the management page

The next thing I wanted was a management page for my plugin. I went back to my Google Doc and mocked up a quick layout for the management page. Now I have a spec to code to. ;)

Quick text-based mockup of the management page. I’m sure it’ll change as I develop it, but this helps me envision it as I’m coding it.

Building the management page wireframe (just the HTML)

I knew I’d need a form and a table, so I built the HTML portion first. This part was easy, just an HTML form with some inputs.

Simple management page wireframe (just the HTML).

Here’s a link to the git commit that adds the HTML you see in the screenshot above.

Easy part is over. Next step is going to be making this form take the user’s input and stick it in the blog’s database.

Day 2: Saving the user’s data to the database as a post

This part felt like the first challenging step of this project, but like most things WordPress, I found it well-documented.

Writing the user’s product box data to the user’s WordPress db

The first thing I had to figure out was where to put my product box data in the WP database.

From the Codex:

Use the existing database tables instead of creating new custom tables if possible. Most use-cases can be accomplished with custom post types and metadata, custom taxonomy and/or one of the other standard tables and using the standard tables provides a lot of UI and other functionality “for free.” Think very carefully before adding a table because it adds complexity to your plugin that many users and site builders prefer to avoid.”

Okay – sounds like I shouldn’t make a new table just for these things. I looked at how TablePress saves its tables, since that plugin works (in some ways) like how I want mine to work.

As it turns out, TablePress saves its tables as posts. Neat.

I can see how I might do the same: product title could be saved as post title, but what about tagline and the affiliate link? I then did some reading on custom post types and considered a custom post type that had all the fields I needed, but I also realized I probably didn’t need a custom post type and I could potentially just serialize the product box data into an array and pull it out for display, thus making the normal post type fine.

Or, I could use custom fields on a normal post. For now, I decided to go with the normal post type and see if I can get a bare-minimum implementation going with just the normal post type. So that’s what I decided on: I’ll ahve it save the form data to the db as a normal post.

That was easy enough: I made the form call a function like so:

<form action="<?php echo esc_url( post_new_product_box() ); ?>" method="post">
   ...
</form>

And stuck a bunch of placeholder values in the function itself:

function post_new_product_box() {
    $my_post = array(
        'post_title'    => 'Test product box',
        'post_content'  => 'Test description',
        'post_status'   => 'publish',
        'post_author'   => 1,
        'post_category' => array( 8,39 )
    );

    // Insert the post into the database.
    wp_insert_post( $my_post );
}

This works – sort of. The new post is in the db, but it also shows up with my regular posts in the Posts section of my blog’s dashboard. (It also shows up twice. Maybe I double clicked the submit button.)

Here’s the commit – I wouldn’t get too excited about it, though. It’s buggy and doesn’t use the form data yet.

Getting the form data from the HTML form, into the php method, and into WordPress’s MySQL database

The next thing I did was start reading on how to get data from the form. This was also about the time I realized that there was a non-zero chance I was doing this all wrong and probably introducing a huge security hole.

But for now, I just want to see form data make it to the database (I can secure it later).

(I’m going to spare you my dozen or so trial and error steps here, such as the “step” where I realized I had a typo in “name” on all of the form inputs and the “steps” where I experimented with different ways to serialize the data.)

I ultimately settled on this way of capturing the form data and encoding it for the database.

function post_new_product_box() {
    if ( isset( $_POST['submit'] ) ) {
        // retrieve the form data by using the element's name attributes
        // value as key $firstname = $_GET['firstname']; $lastname = $_GET['lastname'];
        // display the results echo '<h3>Form GET Method</h3>'; echo 'Your name is ' . $lastname . ' ' . $firstname; exit;
        $content = array(
            "amazin-product-name" => $_POST['amazin-product-name'],
            "amazin-product-tagline" => $_POST['amazin-product-tagline'],
            "amazin-product-description" => $_POST['amazin-product-description'],
            "amazin-product-url" => $_POST['amazin-product-url'],
            "amazin-product-button-text" => $_POST['amazin-product-button-text']
        );

        $product_box = array(
            'post_title'    => $_REQUEST['amazin-product-box-name'],
            'post_content'  => wp_json_encode($content), //broke when switched this from 'none' to the content array
            'post_status'   => 'publish',
            'post_author'   => 1,
            'post_category' => array( 8,39 )
        );

        // Insert the post into the database.
        wp_insert_post( $product_box );
    }
}

(Aside: I swear, I use JSON in everything I build. IT’S JUST SO USEFUL. Turns out WordPress even has a method for encoding stuff into json, so at least this feels legit.)

Anyway, this posts to the db. Hooray! Here is my form data, turned into a post and inserted into the db.

I included my earlier failures so that 1. I don’t look like one of those people who magically gets it right on the first try and 2. so you can see my earlier attempts at formatting the data correctly (the weird a:5 {… formatting is an artifact of using serialize() instead of wp_json_encode()).

Now I have this for my “post content”.

{"amazin-product-name":"TILCODE\'s New Book","amazin-product-tagline":"Tagline goes here","amazin-product-description":"Description goes here","amazin-product-url":"http://buy-my-book.com","amazin-product-button-text":"Buy my book!"}

(I’d have called this an “object”, since it’s wrapped in curlies and uses keys, but php likes to call this an array. I am new in php land so I will abide by its terminology and also call this an array.)

And here it is as a commit, if you’re into that sort of thing.

Day 3: Adding nonce to the form

I suspected my form was insecure based on the fact that I had done absolutely nothing so far to secure it or sanitize the data sent through it.

So, before I went any further, I read a bit on WordPress plugin and form security. A lot of guides say that using something called a “nonce” (a number used once) is a good, easy way to secure WordPress forms. This page from the Codex explained it nicely and gave an example I could adapt to my own plugin’s code. Getting nonce working was easy though I’m not yet sure how to test that it’s actually doing what it’s supposed to. I’ll come back to this.

This commit adds nonce to the plugin code.

Day 4: Hiding the Product Box “posts” from the blog’s main Posts page and displaying them on the plugin page using Custom Post Types

I had a suspicion that my Product Box posts were showing up as normal Posts in WordPress, and sure enough, here they are:

These are product boxes, not posts, and I don’t want to see them when I view my blog’s Posts.

I did some reading and decided that this might be a job for custom post types. This tutorial even says, “One advantage of using custom post types is that it keeps your custom content types away from your regular posts.”

Good enough for me, let’s try it.

The only thing I wasn’t sure about was the part about “create post type” needing to be called “on init”. I wasn’t sure where that was in my plugin, but I guessed it belonged in the same block of code (at the top) that adds the plugin menu to the admin menu.

if ( is_admin() ){ // admin actions
    add_action( 'admin_menu', 'amazin_plugin_menu' );
    add_action( 'init', 'create_post_type' );
} else {
  // non-admin enqueues, actions, and filters
}

The code for actually creating the custom post type looks like this. I found this Codex page on registering post types helpful for identifying what parameters to include. (I can always come back and add more later, too, if I need to.)

function create_post_type() {
    register_post_type('amazin_product_box',
        //custom post type options
        array(
            'labels' => array(
                'name' => __( 'Amazin Product Boxes' ),
                'singular_name' => __( ' Amazin Product Box ')
            ),
            'public'            => false,
            'show_ui'           => false,
            'query_var'         => false,
            'rewrite'           => false,
            'capability_type'   => 'amazin_product_box',
            'has_archive'       => true,
            'can_export'        => true,
        )
    );
}

To bring it all together, the last step was to modify the array of parameters that are passed when creating a product box post. There’s a param for “post_type”, so I added my “amazin_product_box” as the post type here.

$product_box = array(
                'post_title'    => $_REQUEST['amazin-product-box-name'],
                'post_type'     => 'amazin_product_box',
                'post_content'  => wp_json_encode($content)
                'post_status'   => 'publish',
                'post_author'   => 1,
                'post_category' => array( 8,39 )
            );

Make a new product box, go back to php myadmin and reload the posts in the db and.. success! My newest product box is now of post_type “amazin_product_box”, and it is nowhere to be seen in the WP dashboard page for Posts.

I deleted all the “post” type product boxes to clean up the Posts screen. We’re gonna be amazin_product_boxes from here on out!

Here’s the commit that changes the posts to a new custom post type.

Displaying the product boxes on the Amazin Product Box admin page

So, now that they’re not in the Posts page anymore, maybe now’s a good time to get them to show up on the admin page as their creator intended:

Since they are now their own post type I thought it might be easy to select them by post type. The tricky part will be stuffing their data into my table (I mean, I don’t want the full posts displayed like they’re blog posts…).

I started by Googling “WordPress select posts by post type” and read this page on get_posts() and this page on the args available for use with get_posts(). I also found this (5 year old) tutorial helpful when it came to picking out parts of the post data.

The general idea was:

  1. Get each post of type ‘amazin_product_box’
  2. Pick out its product name field, author name, last modified
  3. Display each one as a table row using a loop

It wasn’t super difficult but it took some trial and error to get the date right and figure out how, exactly, to pick out things like author meta from a post ID.

Here’s what I ended up with:

If you’re thinking, “Wow, that’s kinda ugly!” you’re not alone! But I want to get all the hookups working before I venture off into styling land, so it’s going to stay ugly for a little while longer.

Here’s the commit that grabs all the Amazin’ Product Box post data and renders it into a table.

(The code’s a bit ugly with the php and HTML mixed together, but I’m not sure yet what the proper conventions are and there’ll be time to tidy it up later.)

Day 5: Hooking up the Delete buttons

Each existing product box should be able to be edited or deleted independently of the others. The Edit and Delete buttons are client-side (HTML) and the php methods they need to call are server-side. AJAX and JQuery to the rescue.

First, I read this guide on using JavaScript in a WordPress plugin. Then, I got to work on hooking up a console log to each button that logs the post ID of the product box associated with the table row.

When faced with a novel problem (or just one that I know is going to take some trial and error), I like to do a bare minimum “wiring” as the first pass. For this particular bit of work, that meant 1. associating the post ID with each edit/delete button, 2. passing that ID into the JS methods, 3. logging that ID from the JS method to verify the hookup was successful.

Clicking Edit and Delete logs the post ID. From here, I’ll build up an actual edit and delete functionality.

Looks good – now I know the post IDs can make it over to the JS file’s methods.

Note: I had to include a version number in my call to wp_enqueue_script and increment it every change to the .js file. This is called “cache busting” and without it, the plugin used a stale version of the .js code even though the latest was uploaded to the server.

Every time I change the JS code I increment the last parameter. Below, it’s at 1.06. Next time I update the JS code I’ll change it to be 1.07, and so on.

wp_enqueue_script('scripts', $jsurl, array('jquery'), 1.06); 

Here’s the commit that adds console logs and IDs to the Edit and Delete buttons.

Hooking up Delete functionality

I thought Delete might be easier to start with since it doesn’t involve putting data back into the form nor anything with canceling the edit state.

Unsurprisingly, WordPress has a method appropriately named wp_delete_post(). But I couldn’t just call it from the “on click” method in scripts.js – WordPress doesn’t know what it is unless it’s in the php (server-side) code.

Using wp_delete_post() from the JS file was a no-go.

Turns out I needed to use Ajax, but WordPress is already set up for this sort of thing. I found this tutorial very helpful for this step. I skipped anything relating to permissions, confirmations, or security on this first iteration, but if you check out this commit, the delete button now deletes the appropriate row (it fades out!) in the table and in the wp database.

Securing the Delete functionality with a nonce

After getting a bare-bones delete functionality working, I went back to the same tutorial that got me started and followed its examples for adding a nonce. My code was a bit different than theirs, but a few educated (lucky?) guesses later and I had it working.

I put the nonce directly on the Delete button input itself like so:

<input type="button" id="<?php echo $id; ?>" class="delete-button" nonce="<?php echo wp_create_nonce('amazin_delete_post_nonce') ?>" value="Delete"/></td>

Notice how the input tag accepts nonce=”…”. Surprisingly (to me, anyway) that’s valid syntax. Over in scripts.js I’m then able to pluck it out of the event (“e”) like so:

var nonce = e.target.nonce;

Here’s the commit that adds nonce to delete. I tested it by changing the nonce value on one of the delete buttons (to “12345”) and then clicking delete – and nothing happened. So the nonces do actually seem to do something. Hooray!

Building the “Edit” functionality

First, a few small UX changes: I removed the product name box field (I think I’ll just refer to boxes by their IDs, not by names) and exposed the ID in the table. This commit contains that work.

Populating the existing form

The first bit of work here was to make the Edit button populate the form with the existing post’s contents.

In scripts.js, an ajax call that calls an action (‘amazin_get_existing_post’) defined in the php part of the project and stuffs the data into the appropriate form fields upon success:

$ ( '#admin-table').on( 'click', '.edit-button', function(e) {
        console.log("Gonna edit a product box with ID:", e.target.id);
        var id = e.target.id;

        $.ajax({
            type: 'get',
            url: MyAjax.ajaxurl,
            data: {
                action: 'amazin_get_existing_post',
                id: id
            },
            success: function ( response ) {
                console.log( response );
                var data = JSON.parse(response.productBoxData);
                $ ("#product-name").val(data.productName);
                $ ("#product-tagline").val(data.productTagline);
                $ ("#product-description").val(data.productDescription);
                $ ("#product-url").val(data.productUrl);
                $ ("#product-button-text").val(data.productButtonText);
            }
        });
        return false;
    } );

In amazin-product-box.php, the new method:

function amazin_get_existing_post( ) {
    $post = get_post($_REQUEST['id']);
    $postDataToBePassed = array(
        'productBoxProductName' => $post->post_title,
        'productBoxData' => $post->post_content
    );

    wp_send_json($postDataToBePassed);
    die();
}

The tricky part here, for me, was figuring out how to “send” the post data from the php method back to the js. It took a bit of Googling and being stumped for a while, but it turns out WordPress has something made specifically for the job of sending a JSON response back to an AJAX request: wp_send_json(). The response could then be captured and picked apart back in the .js file.

(Hopefully that makes sense – the overall “journey” goes something like this: the clicking of the Edit button is handled in JS -> that JS method calls an action defined in the PHP file -> that PHP file action gets the post from the db and uses wp_send_json() to kick the data back to the JS method -> the JS method captures that data as the “response” parameter.)

Here’s the commit that populates the form fields – hitting “Submit” just creates a new one, though – it doesn’t yet update the existing one.

Saving the form contents as an “update” to an existing post

The last bit of work to do on “Edit” was to make the form recognize two states: making a new one and editing an existing one. I chose to do this with a hidden ID field in the form. The hidden field holds either “undefined” or a post ID (placed there by getting a post to edit). When the user submits the form, it uses that ID (or lack of ID) to figure out what to do – either submit a new post or update an existing.

Here’s the commit for the invisible ID field and the logic that either creates a new post or updates an existing one.

I tested creating, editing, creating after editing, going into edit and then canceling, and clearing the form.

Some test posts to verify the various user flows for the form are working.

Yay, the bare-bones backend is working! Continue on with Part 2 where I add the ability to display a Product Box in an actual post.

OSU eCampus CS475 Parallel Programming review & recap

This post is part of an ongoing series recapping my experience in Oregon State University’s eCampus (online) post-baccalaureate Computer Science degree program. You can learn more about the program here.

Six-word summary: A well-designed and satisfying course

I feel like I sometimes write these recap articles to complain and warn other students of landmines, but CS475 was what every OSU online CS course should be: organized, succinct, and educational. I learned so much and enjoyed the course from start to finish.

CS475 Review

It only runs in the Spring quarters, but it’s worth holding out for! You’ll learn a lot, the material is interesting, the course is well-paced, and the exams felt fair.

I picked this class because parallel programming was something I thought sounded intimidating and was unlikely to be something I would attempt on my own. I’m very glad I chose it! It was way less intimidating than I feared, and now I feel entry-level competent in a subject I formerly knew nothing about.

Class structure

  • 10 weeks
  • 8 programming assignments where you code, run the code, and then write an analysis of it
  • I spent about 7-10 hours on this class each week, making it one of the lighter workloads in the program
  • Proctored midterm, proctored final
  • Weekly quizzes in Canvas (usually 5 questions with a full hour to answer them)
  • About 1-1.5 hours of video lectures each week (sadly they are without captions, but they are very high quality otherwise)
  • Some code is provided for you to use, saving time for focusing on what the class is really about
  • Instructor attends office hours! He’s really approachable, you should go even if you don’t have a specific question!

The instructor is SUPER active! You can get on a video chat with him – he’s just sitting there at pre-announced times, waiting for people to show up to his video chat room! He’ll look at your code! He’ll talk to you 1:1 about topics! I’ve never had such a productive “office hours” experience as I did in this class, and I didn’t even show up with any questions.

Contrast this with how most classes in this program hand you a problem and set you loose on figuring it yourself (via Google, Stack Overflow, old projects posted to GitHub, etc). There’s a time and a place for that, sure, but for $2000 in tuition I’m happiest when the class makes me feel like I learned something from the person teaching it. I want an experience I can’t get from Google and an independent project. I want an intelligent, skilled person to say “here’s what I know, and here’s how I think about it” and that’s how Professor Bailey ran this class.

Prof. Bailey also sends out an anonymous survey at the end of the course asking for feedback on each and every assignment as well as time spent. He genuinely cares about the class.

CS475 Project Structure

Unlike most classes, CS475 hands you some “starter code” for every assignment. You still have to put it in the right place, call it at the right time with the right data, and format and analyze the output yourself, but being provided this “starter code” was great for a few reasons:

  • Less time spent on things that aren’t the point of the assignment
  • No super fancy math skills needed (the bezier volume calculation formula is given to you, for example)
  • Less likely that you’ll go off into the weeds and do something ridiculously wrong and miss the point of the assignment
  • Less time needed overall for each assignment

This class has you throw nearly 100% of your efforts towards parallel programming related coding and analysis. For those of us with a finite number of hours in a week it was so good to just focus on the subject of the class and skip some of the boilerplate project setup. This class would pair well with another less-time consuming class, if you need (or prefer) to double up.

Most of this class’s projects go something like this: start a new C project, bring in the starter code, refactor/add to it to make it do what you need, write a bash script (or whatever you prefer) to run a loop and pass different parameters to the program each run, get all of the output in your Terminal (some people write it to a file), dump the output into a spreadsheet (I used Google Sheets), create graphs and write your analysis (in response to prompts).

Typical project output – here’s my output from Project 2

Bring these into your favorite spreadsheet software and create a pivot table. Then, generate the needed graphs for your analysis.

Somehow I made it this far in life without ever needing a pivot table. In this class, pivot tables made graphs a cinch.

And that’s pretty much how the assignments go.

I was a bit intimidated by this class before I took it. People say it’s dry and the subject has a reputation for being challenging, so I wanted to show other prospective students what’s actually involved and how approachable it really is.

Tips for CS475

NVIDIA CUDA support is limited on some versions of Mac computers. (For example, Apple just doesn’t support it for OS 10.14 at the time of this writing). If you’re on a Mac, I suggest using the school-provided rabbit server for your CUDA assignments and not wasting hours like I did trying to get it all working on a Mac.

Go to the office hours! Seriously, I went on a whim near the end of the quarter and was surprised to find myself alone in a video chat with the professor. I was just going to lurk but it ended up being a great chance to discuss a bunch of class topics one on one. Another student showed up and he stepped through her previous assignment code with her. You can’t beat the value of having an expert review your code with you!

The exams are straightforward. Some classes in this program use exams as an opportunity to test your speediness and your ability to keep track of registers through multiple iterations of multiple loops, but this class kept it fair and simple: watch the lectures, take notes, study those notes and you’ll do fine! He even gives a generous amount of time for the exams.

Overall, it’s just a high quality course. The lectures were freshly recorded, well-paced, on-topic, and relevant to the assignments and tests. The weekly assignments always looked challenging at first but were ultimately quite doable and enjoyable.

I’ve got just 4 classes left to complete my degree. I’ll be back in the fall quarter for Intro to Networks! See you all then!

OSU eCampus CS344 Operating Systems review & recap

This post is part of an ongoing series recapping my experience in Oregon State University’s eCampus (online) post-baccalaureate Computer Science degree program. You can learn more about the program here.

Six-word summary: Four final projects in a row

CS344 is your chance to work on four large, back-to-back projects that cover a breadth of topics. They feel a bit contrived (ie: more like schoolwork than real-world problems) at times and their application to real problems might be hard to see at first, but there’s no denying it: doing four big projects in a row will level up your skills a whole bunch.

CS344 Review

This course picks up where other C courses left off and introduces some new and exciting features, such as bash scripting and mutual exclusion locks.

The projects are utterly massive – any one of them could’ve been CS162’s notoriously huge final project – and the course comes off as feeling more like an advanced C course than an operating systems course because of it. You do a lot of project setup and boilerplate in this class (but you do touch on a long list of OS topics as well). Sometimes the new thing (whatever the actual point of the assignment was) only takes minutes to add, like a little cherry on top of a 5-tier wedding cake of a C project.

Class structure

  • 10 weeks
  • 4 programming projects with about 2 weeks each
  • Bash and C programming language
  • No midterm, open-book (but timed) final
  • Due dates are kinda all over the place, best to triple-check them (they don’t follow a pattern)

Grading script FTW

CS344’s best feature is the clarity of its expectations. Unlike many of the courses that came before it in this program, this one is VERY CLEAR about what’s expected and how you’ll be graded on it. They hand you a grading script that runs your program code and spits out a list of results (pass/fail) or something for you to interpret. When it all passes, so do you. It’s that simple.

Overall, this class is fair and straightforward. It’s one of those classes you just gotta be disciplined and hardworking in, and it’ll reward you by introducing you (in a fairly friendly way) to topics such as sockets, locking threads, writing bash script, making your own shell, and more. I’m not sure I would have pushed myself through some of this stuff on my own – the project where we wrote our own shell was definitely intimidating at first – but I’m glad the program included this class and these opportunities. I think I learned a lot in CS344!

Tips for CS344

You don’t have to code on the school server. The class will tell you up and down and over and over to code in VIM on the school server. You don’t have to. I did the whole class on my MacBook in SublimeText and I transferred the files to the school server for testing using FileZilla. It’s just bash and C code, there’s no reason to isolate it the way the class tells you to.

I think this whole “only work on the school server” stuff is bad advice. The programs in this class are large enough to justify a text editor and working locally makes it easy to use version control (which you should absolutely be using – at least one person in my class lost their work by compiling over their .c files on accident :( ).

I kinda wish I’d taken it alone or with a less time-consuming class. I paired it with CS340 (Databases) and the two classes just took turns being hellishly time-consuming. It’s like right on the fence of being a “better off alone” class. By itself it might’ve felt light, but paired with anything I was constantly strapped for time. YMMV.

Tutorial: Making a variety of 2D particle effects in Godot 3.1

In this tutorial: a step-by-step guide to making a few different particle effects in Godot 3.1. This guide assumes some familiarity with the Godot game engine.

I recently started adding particle effects to my hobby game project but I found that while the Godot docs offer a pretty good introduction to particles, I was still a bit lost when it came to some of the more advanced features like color ramps, stacking particle effects, and controlling them through code.

Note: the illustrative gifs I made for this guide run at 8 fps. They look a lot smoother in-engine and on target.

Godot 2d particle tutorial #1: floaty sparkles from a cauldron

I have a giant pink cauldron and I want it to “emit” pink sparkles that fade to white and disappear.

A sparkling pot of sparkles (the jump is just the gif looping, it runs smoothly in-game)

All of these begin as a scene (their own scenes, for the sake of organization) with a Particles2D node as the root.

The little yellow warning triangle tells us we need to assign a material to this particle effect, so in the Inspector scroll down to Process Material, roll out the bar and click where it says [empty].

Choose New ParticlesMaterial.

You should now have a steady stream of white dots in your viewport. These little white dots are just the placeholders for sprites you’ll attach to the effect later.

Click on ParticlesMaterials to roll out all the tweakable properties. (There are many and this guide covers some of them in more detail.)

Flipping gravity

First thing I did was reverse gravity from 98 (heavy!) to -30 (floaty!) so that the little particle dots emit upwards.

A negative Y value makes particles appear to travel up instead of down.

Changing the emission shape

Next, I changed the Emission Shape to a box and gave it a height and a width. This area represents the space in which new particles can spawn.

A short, wide rectangular box

Hey, it’s starting to look like bubbles floating up from a cauldron!

Adding a sprite texture

The next step is to add a sprite texture. I have an 8×8 simple “plus” graphic to serve as my sparkle for now.

Grab that sprite graphic and drag it over to the Textures > Texture field in Inspector tab like so:

Voila – the white dots are now white plusses.

Adding randomized rotation to each sparkle

Back in the ProcessMaterials section of the Inspector, open up the Angle property. If you just type a rotation value for Angle, all particles will emit at that angle. Increase Angle Random to 1 to generate every particle at a random angle.

Setting Angle to 45 and Angle Random to 1

Now we have randomized sparkle rotations.

(This gif is a lot choppier than how it looks in-engine.)

Adding a color ramp

I want my particles to start one color and fade to a different color as they reach the end of their lifespans. That’s easy to do with a Color Ramp. In the Color section, find Color Ramp and click on [empty]. Add a New Gradient Texture.

Click on the new GradientTexture to add a Gradient Map.

Click on that new Gradient to access the actual color gradient.

Here’s where things got unintuitive for me. The biggest hurdle was realizing the box to the right of the gradient is a button! I also didn’t notice the vertical adjustment boxes, since they start out at the extents of the gradient and are easy to overlook until you know they are there.

This gif attempts to demonstrate the color gradient UI.

How to use Godot’s gradient editor.
  • Slide the vertical boxes left/right to adjust how much of the gradient is dedicated to a particular color
  • Click on the vertical boxes to open a color picker (it’s easy to accidentally dismiss the color picker, as I do in this gif at least once)
  • Click anywhere in the gradient to add another color
  • (Right click on a box to remove it – not shown in gif)
  • With the color picker open, slide the “A” slider to adjust opacity.

Here’s what I went with for my pink-to-white cauldron sparkles:

Now you can see the sparkles starting out pink and turning white as they reach the end of their lifespan.

I saved this particle effect as its own scene, then place it (via drag and drop) into the scene where I want to use these particles.

From here, I continued to fine-tune their properties: I made their spawn box wider to better fit the width of the cauldron and I changed their gravity to -10 so they would float slower. I think there’s always going to be a bit of try-and-see-then-tweak-again when it comes to making particle effects, or at least that’s the case for me.

Godot 2d particle tutorial #2: ghostly glow on defeated heroes

From here on out I’m going to skip the setup steps covered in tutorial #1 and just show you some of the effects I was able to make and what parameters I used to achieve them.

Here, I wanted a ghostly “cloud” to surround defeated heroes.

I wanted the “ghost glow” to follow them around as they walk, so I made it a child of the hero node.

However, I control its appearance through script like so:

func ghost_mode(ghostMode):
if (ghostMode):
print("ghost mode on")
$body.modulate = Color(0.8, 0.7, 1)
$particles_ghost.set_emitting(true)
$particles_ghost.show()
else:
$body.modulate = Color(1, 1, 1)
$particles_ghost.set_emitting(false)
$particles_ghost.hide()

Parameters used to achieve this effect:

  • Amount: 9
  • Lifetime: 4
  • Preprocess: 3 (this makes it behave like it’s already been running for 3 seconds we don’t see the effect “start up” when we enter a scene where this effect is playing)
  • Emission shape: box (5x5x5)
  • Velocity: 10
  • Scale: 0.55 (because the puff png is too big otherwise)
  • Color ramp starts transparent and ends transparent, with purple in between
  • Material: CanvasItemMaterial
    • Blend mode: Add (so it makes things underneath the effect look bright and more ghostly-er)
    • Light mode: normal

Godot 2d particle tutorial #3: Boom! spawn-in animation

For this last one I wanted to achieve a “spawn in” effect for my game’s enemies. I wanted it to be big enough to cover the enemies popping in and I wanted to try layering at least two effects so that it looked like both clouds and sparkles happening at the same time.

Here’s what I ended up with after a bit of playing around:

Incoming! :D

This is two separate particles but they spawn at the same time.

Effect #1: purple puff clouds

They puff cloud is just a chunky Photoshop scribble with some randomization applied. Here’s the puff cloud by itself:

This is the .png used to make it. It’s just a fat brush stroke from Photoshop.

smoke.png

Parameters used to make this animation:

  • Amount: 6
  • Lifetime: 5
  • Preprocess: 0.25
  • Speed scale: 3.8
  • Explosiveness: 1
  • Spread: 180
  • Gravity: -4 on Y
  • Angle: 160
  • Angle random: 1
  • Scale: 0.55
  • Scale random: 0.03
  • Scale curve: Curve texture
Curve Texture is useful for fine-tune control over the scale of the particles through their lifespan. Here they start small, get bigger, then shrink again as they reach the end of their lifespan.
  • Color ramp is a gradient that starts and ends transparent with a solid section of purple in the middle

Effect #2: bright sparkle ring

This ring effect plays over the puff clouds.

Parameters used to make this animation:

  • Amount: 20
  • Lifetime: 1.6
  • Preprocess: 0.25
  • Speed scale: 1.5
  • Explosiveness: 1
  • Spread: 180
  • Initial velocity: 40
  • Angular velocity: 28
  • Linear accel: 1
  • Damping: 22
  • Angle: 45
  • Angle random: 1
  • Color ramp:

Spawning the two effects simultaneously

For the sake of completion, here’s the entire method that spawns my game’s mobs. I bolded the parts that spawn the particles. I add them as a child to the mobScene and show the mob sprites themselves after a brief pause so that the spawn-in animation starts slightly before the mobs are actually visible to the player.

func populate_mobs(mobs):
for i in mobs.size():
var mobScene = preload("res://baseEntity.tscn").instance()
mobScene.hide()
mobScene.set_script(load("res://mob.gd"))
mobScene.set_instance_data(mobs[i])
var p_spawnCloud = load("res://particles/particles_spawnCloud.tscn").instance()
var p_boomRing = load("res://particles/particles_boomRing.tscn").instance()

p_spawnCloud.set_emitting(true)
p_boomRing.set_emitting(true)
mobScene.add_child(p_spawnCloud)
mobScene.add_child(p_boomRing)

mobScene.set_position(Vector2(mobPositions[str(i)]["x"], mobPositions[str(i)]["y"]))
mobScene.set_display_params(false, true) #no walking, show name
mobScene.add_to_group("mobs")
add_child(mobScene)

yield(get_tree().create_timer(0.5), "timeout")
get_tree().call_group("mobs", "show")
get_tree().call_group("mobs", "_draw_sprites")

Parting thoughts (and links to more readings)

OSU eCampus CS340 Intro to Databases review & recap

This post is part of an ongoing series recapping my experience in Oregon State University’s eCampus (online) post-baccalaureate Computer Science degree program. You can learn more about the program here.

Six-word summary: Surprise, it’s a web development class!

You’ll glance at some database topics in CS340, but you’ll put most of your effort into building a big full-stack website.

CS340 Review

Get ready to sharpen your web skills! You’ll work with a partner the entire quarter (try to pick a good one!) to design and implement a website that interfaces with a database. This class is very heavy on web development. You’ll make tables to display data that comes in from the db, forms that can create, update, and delete data, and you’ll need it all up and running on the school’s servers by end of quarter. Course pacing is uneven and the numbering of assignments, projects steps, and assignments is borderline illogical.

I HIGHLY recommend finishing 290 before you take 340 so that you can use your last 290 project as starter code for 340’s. It’s criminal that people say you should take 290 and 340 simultaneously. Don’t do it. One of the last projects of 290 is a node.js website that interfaces with a database, and if you have this project complete, you have a fantastic head start on 340’s website project.

Class structure

  • 10 weeks
  • No midterm, no final
  • Pick your partner in the first week
  • Each week you make progress towards a completed website project with your partner
  • Some weeks require “peer review” in which people from outside your group look at what you turned in and critique it (and you, in turn, review others’ work)
  • Hits hard in the last few weeks of the quarter when you have to put the entire site together. Start early.

The first week is slow: you’ll pick a partner and you’ll import a database dump into the database tool of your choosing. You’ll sit on your hands after this ~30 minutes of work is complete.

This is a good time to figure out what you want your site to be. My partner and I did a museum ticketing system and found it easy to hit all the requirements. Don’t reinvent the wheel here.

Each week you’ll work on an ever-growing document outlining your plans. No one will read it, but you’ll submit it every week nonetheless. There is absolutely no TA feedback at any point in this class. I finished this class without so much as a “Looks good!” from a TA.

About midway through the quarter they’ll give you an assignment to build the HTML portion of your site. If this is all you do this week you’ll probably fall behind, because each week after this has a much higher workload. You should immediately look at your projects from 290 to get started with the routing and db queries once you’re happy with the HTML.

There are no exams and no quizzes.

CS340: Too light on database-specific stuff

For a database class, this class is (sadly) rather light on hands-on query writing. It tries to teach database design, which is appreciated, but I think it spends too long belaboring minutia relating to the design diagrams (we must’ve done three iterations of ours before the precise requirements finally became clear on Piazza). It also spends weeks on the diagrams – one would’ve been enough.

The two places CS340 has you get your hands dirty is on Mimir for a few query-writing assignments and in your quarter-long website project. This is where the class shines: where it’s actually teaching (or forcing you to figure out on your own) how to interact with a database to do something useful.

CS340’s Mimir assignments

Rather than write SQL queries in any kind of industry-standard tool, CS340’s query writing is done in a browser-based tool called Mimir. Deadlines are generous and the work takes maybe 2-5 hours to get through (per assignment). Mimir is slow and the feedback it gives is not as robust as what you might get in a better tool. CS340 does give you the db dump, so you can go play around in a better environment (I used MySQL Workbench) but sometimes syntax that works locally does not work in Mimir.

Nonetheless, the Mimir-based parts of the course are some of the best parts. Experience writing queries is a skill you can take to an interview and job. I just wish there had been more of it.

CS340 group work

“What one engineer can do in one week, two engineers can do in two weeks.” The adage holds up in CS340. My partner was great, but I think I would’ve moved faster through the project without having to coordinate with someone else. There’s a lot of overhead in keeping someone else in the loop, not duplicating work, waiting for input before continuing, etc.

A few weeks into the quarter, everyone gets assigned a random group of 5. This is your “peer review” group. (Your partner will be in his/her own peer review group). Since 340 doesn’t seem to feature any TA feedback, this is what the class gives you instead: feedback from other students who probably know the same or less than you do about what you’re working on.

The real kicker? You have to bring their feedback into your document and either act on it or explain why you chose not to. The feedback we got was generally useless: at best it was people pointing out UI bugs we already knew about, at worst it was a hot take dashed out 2 minutes before the feedback was due that suggested little to no reading comprehension on the reviewer’s part. At least we didn’t have to all meet at the same time like we did in 361.

Our CS340 project

We made a museum ticketing system. This project was a bit large for the time given but I enjoyed working on it.

In our app, you can add museums, add exhibits to museums, “sell” tickets to guests with a variety of exhibit entitlements, create new guests, add orders to existing guests, search for tickets by ID, date or transaction, view transactions, refund exhibit entitlements from tickets, and refund tickets themselves. You can also rename exhibits and museums, and update guest info.

We used Bootstrap on a node.js/Express/MySQL stack. (I’d have preferred to use an actual front-end framework but my partner was much newer to all this so we went with a nice big bowl of JQuery spaghetti instead.)

Here’s a few screenshots from our completed web app:

Our app’s default page – you can change which museum to view tickets and exhibits for.
Here’s the “sell tickets” flow, where the user chooses which guest types to create tickets for as well as a visit date.
The user can manage which extra exhibits each guest ticket should have access to.
Review your order before proceeding…
Enter guest info and complete the purchase! The ticket info is added to the database.
The Transactions page shows all pages and provides links to individual tickets.
In the Museums page the user views all museums and can choose to rename them.

Here’s one of the queries from the project that I wrote:

app.get('/get-transactions', function(request, response) {
var context = {};
var queryStr = "SELECT tr.transaction_id, tr.trans_date, tr.trans_time, g.fname, g.lname, tr.pymt_type, GROUP_CONCAT(ti.ticket_id) AS ticket_ids, GROUP_CONCAT(ti.admission_type) AS guest_types "+
"FROM transactions tr "+
"JOIN guests g ON tr.guest_id = g.guest_id "+
"JOIN tickets ti ON tr.transaction_id = ti.transaction_id "+
"WHERE tr.museum_id = ? "+
"GROUP BY tr.transaction_id;";
pool.query(queryStr, [request.query.id], function(err, result, fields) {
if (err) {
console.log(err);
return;
}

context.transactions = result;
response.json(context);
});
});

GROUP_CONCAT is a neat trick we used to get a string of ticket IDs and admissions types back from the db (which you can then parse on the front-end).

My favorite parts of this class were when I got to do something new and exciting in SQL.

CS340: What’s missing?

I wish the class had covered any (or all) of these topics:

  • Non-relational databases
  • Input sanitization
  • Security
  • Stored procedures
  • Thread locks
  • Transactions
  • Advanced SQL (this class never goes further than a SELECT within a SELECT)
  • Best practices

And this is just “what I know I don’t know”.

If I were to redesign this course, I would give students a defined project (“Make a theme park ride ticketing system”) and provide a functioning front-end so the class can super deep dive into database-specific topics instead. I know databases don’t exist in isolation, but the sheer amount of front-end work it required to interact meaningfully with our data greatly overshadowed the database work.

A few final tips for CS340

The course is disorganized and the assignments never fall into a predictable “rhythm”, so double-check everything. Every week it’s something different: this week you turn in a PDF, the next week a .zip. Sometimes you turn it into Canvas, sometimes you post it to your “peer review” discussion group. Does it count for you and your partner or just you? It varies week to week. Is there a quiz that opened last week due this week? These aren’t difficult things to figure out, but they add a lot of “overhead” and I saw more than a few people going “OMG I thought that submission counted for both of us!!” in the Slack chat.

Take 290 first. Easily the best thing you can do for yourself to ensure success in CS340. The last 290 assignment will have you make routes for interacting with a database. You can use that work as boilerplate for your CS340 project and save yourself a ton of grief. Can you imagine taking 290 and 340 at the same time and being stuck on the same problem for both classes? Take 290 first! It should be a hard pre-req for 340.

Skip the lectures. They’re a mess. I mean, I won’t tell you how to live your life, but the lectures in this class are worthless. They won’t help you build your site, they’re thin on examples, and sometimes the topic they introduce was actually needed for the homework due last week so… yeah. I’m a diehard “watch the lectures no matter what” person and I gave up on them. It’s like they’re from a previous version of the course or something.

Don’t reinvent the wheel. Use Bootstrap (or similar) to make your front-end look nice, choose a project idea that lends itself to lots of pairings (customers to orders, people to tickets, etc).

Overall, I was disappointed by CS340. I’ve weathered other not-so-great courses and found the good in them, but this one was just a whole lotta making a bigger website than we made in 290 and writing a few SQL queries on the side. My database/SQL skills didn’t grow much in this course and I’m bummed that it didn’t live up to my expectations. My partner was awesome, though, so there was that. :)

Simple program to shuffle an array of strings in C

Here’s a quick and simple program for shuffling an array of strings in C. There are many ways to do this, but I needed something much simpler and more beginner-friendly (ie: complete) than the examples I was finding online.

Enjoy!

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define CITY_NAMES_COUNT 10

const char* cityNames[] = {
    "Seattle",
    "Chicago",
    "San Diego",
    "Orlando",
    "Anahiem",
    "Tampa",
    "Indianapolis",
    "Portland",
    "Chelan",
    "Sandusky"
};

void shuffleCities()
{
    int size = CITY_NAMES_COUNT;
    if (size > 1)
    {
        int i;
        //step through each index of the city name array
        for (i = 0; i < size - 1; i++)
        {
        //pick a random index (j) to swap it with
        //okay to pick same value as i
        int j = rand() % CITY_NAMES_COUNT; //random between 0 and 10
        const char* temp = cityNames[j];
        cityNames[j] = cityNames[i];
        cityNames[i] = temp;
        }
    }
}

int main(int argc, char* argv[])
{
    srand (time(NULL));
    shuffleCities();

    printf("SHUFFLED CITY NAME ARRAY\n");
    int p;
    for (p = 0; p < CITY_NAMES_COUNT; p++) {
        printf("%i: %s\n", p, cityNames[p]);
    }
}

To compile:

 gcc -o shuffleCities shuffleCities.c

To run:

./shuffleCities

Expected output:

SHUFFLED CITY NAME ARRAY
0: Portland
1: Tampa
2: Sandusky
3: Seattle
4: Chelan
5: San Diego
6: Anahiem
7: Chicago
8: Indianapolis
9: Orlando

Godot 3.0: Exporting game data from Google Sheets into a staticData .gd file using node.js

If you’re making a Godot game with a bunch of “static data” such as items with stats and effects, quests, characters with stats, enemies, loot tables, spawn tables, etc. – then you might be wondering where to put your game’s data (and how to format it) in a Godot project.

When I first set out to do this I couldn’t find a great guide so I decided to figure it out and make one. My way probably isn’t perfect, but it gets the job done. 

My goals (and what I ultimately built):

  • Organize my game’s data into separate tabs in a Google Sheets spreadsheet.  Advantages: I would get to use Sheets’s validation features, I can access/edit my data from any computer anywhere, working in Sheets is easy and familiar to me and anyone who has used Excel.
  • Export from Sheets into something I can process into Godot objects. I went with JSON. (More on this later)
  • Turn the exported JSON into a game data file, which would be “globally” available for my game’s code to use
  • Easily access items, mobs, etc. by ID from within the game’s code. I want to do something like this: var sword = allItems[“Rusty Sword”].duplicate()

Setting up the game data in Google Sheets

If you’re doing something similar feel free to build your data however you like. I liked rows, with the thing’s id in the first column. 

This first example is the items sheet. These are all the swords, robes, armor pieces, crafting items, quest items, etc. in the game. Every item has a name, a “slot” it goes into, class restrictions, and stat bonuses. Typical RPG stuff. This sheet is very large, but it’s easy to search and I color coded by slot to help make it easier to find things. 

The next example is my game’s enemy (mob) data. Enemies (mobs) in my game just have a few stats, but you can see how the data could be built out to support much more complex monsters.

You might have noticed that the mobs have loot tables. Loot tables are yet another tab in the data sheet.

Google Sheets lets you set up “validation” so that some cells can only contain data from another sheet. (You can type the loot table name or pick it out of the dropdown that Google Sheets generates when you use validation.)

Adding a simple validation here helps cut down on typos and errors that result from things getting renamed. 

There are more data sheets for my game, but this should be enough to give you an idea of how to put your game data in a spreadsheet. Next up, exporting.

Exporting the game data as .json

I picked .json for a few simple reasons:

  • It’s easy to find Google Sheets to JSON exporters
  • I’m already familiar with JSON and it’s fairly human readable
  • Godot has some built-in capabilities for parsing JSON

For the export itself, I used the free “Export Sheet Data” Google Sheets add-on (link). Just install it and find it in the Add-ons dropdown.

There are a bunch of settings in here to play with depending how you want your data formatted.

I wanted mine to be an array of objects, so the settings I checked are:

Checked settings: JSON format, current sheet only, Replace existing file(s), Export cell arrays, Export contents as array.

The exported data looks like this. (This is a super truncated version of what the items tab gets exported as.)

[
  {
    "name": "Novice's Blade",
    "icon": "sword1.png",
    "bodySprite": "sword1.png",
    "itemType": "sword",
    "consumable": false,
    "prestige": 0,
    "slot": "mainHand",
    "rarity": "common",
    "classRestriction1": "warrior",
    "classRestriction2": "",
    "classRestriction3": "",
    "classRestriction4": "",
    "classRestriction5": "",
    "hpRaw": 0,
    "manaRaw": 0,
    "dps": 2,
    "armor": 0,
    "strength": 0,
    "defense": 0,
    "intelligence": 0,
    "noDrop": false
  },
  {
    "name": "Rusty Mace",
    "icon": "mace1.png",
    "bodySprite": "mace1.png",
    "itemType": "mace",
    "consumable": false,
    "prestige": 0,
    "slot": "mainHand",
    "rarity": "common",
    "classRestriction1": "cleric",
    "classRestriction2": "paladin",
    "classRestriction3": "",
    "classRestriction4": "",
    "classRestriction5": "",
    "hpRaw": 0,
    "manaRaw": 0,
    "dps": 2,
    "armor": 0,
    "strength": 0,
    "defense": 0,
    "intelligence": 0,
    "noDrop": false
  }
]

Turning the exported .json into a .gd file for Godot 

Here’s what we need to do:

  • Open the .json file(s) (remember there’s one .json for every tab in the spreadsheet)
  • Parse it line by line and make any formatting changes necessary
  • Write the parsed and modified data to a .gd file that contains all of the game’s “static” data
  • Access the static data in the .gd file from the game’s code 

There are practically infinite number of ways to accomplish these steps (it’s just some file i/o and text parsing), but I went with what I already knew – Javascript. My quick-n-dirty parser (named “Parsely”) is far from beautiful code but it gets the job done.

You can see it in its entirety here: Parsely.js gist.

Parsely’s code is written specifically for my project, but one thing I want to point out is the way it transforms my objects. 

An object comes out of the Google Sheets exporter like this simplified example:

[
  {
    "name": "Novice's Blade",
    "icon": "sword1.png"
  },
  {
    "name": "Awesome Weapon",
    "icon": "sword2.png"
  }
]

But what I really need is this:

{
  "Novice's Blade": {
    "name": "Novice's Blade",
    "icon": "sword1.png"
  },
  "Awesome Weapon": {
    "name": "Awesome Weapon",
    "icon": "sword2.png"
  },
}

Notice the array has become one giant object, and each object is a property within that object. I want to grab my item by its name, ie: “Novice’s Blade”, and get the associated object data. I also want all this stuff to be in one giant object so I can grab objects directly without having to iterate through it (as I would have to if it were an array). 

So that’s what Parsely is doing when it does this:

for (var value of fromJSON) {
  var key = "";
  if (file == "items.json") {
    key = value["name"];
    formatted[key] = value;
  } ...
... 

It’s getting the name and making that the key, and it’s getting the whole object and making that the value. Now I have a giant object full of smaller objects, where each smaller object is a unique item in the game (like the second JSON example above).

Also, sometimes I wanted to add more parameters to an item at runtime, such as a unique ID or a boolean representing whether it was an enhanced item or not. Those things don’t exist in my spreadsheet, they’re just slapped on when this script is run. 

Here’s an example from parsely.js of three new params (“itemID”, “improved”, and “improvement”) being added to each item as it is processed.

For the sake of keeping everything related to my project in one place, I placed parsely.js and the .json files it processes in my Godot project folder like so:

When I export from Google Sheets, I download the .json file it generates and place the .json file in the appropriate folder (names, staticData, or timedNodeData). Parsely handles files from different source folders slightly differently, ie: files from names get built into arrays, files from timedNodeData get some “inProgress”, “readyToCollect” booleans attached, etc.

Finally, we can run it! I open a Terminal window and navigate to the Parsely folder. To run, I type:

node parsely.js

It then grabs those json files, processes them, and places them in res://gameData (which is where I want them to go).

Inside each of these .gd files is an object or an array formatted as one long line.

This is staticData.gd:

Here’s our data, organized into separate objects.

Using the data in my Godot project

Finally, make the staticData.gd file global by adding it in Project Settings > Auto Load. Now it’s accessible everywhere in the project. 

Now, throughout the project items, mobs, loot tables, etc. are all accessed from staticData like in these examples:

var newItem = staticData.items[itemNameStr].duplicate()

var loadout = staticData.loadouts[loadoutIDStr]

And that’s it! Now my game has all of its items, spawn tables, loot tables, quests, crafting recipes, and more pre-loaded as data objects and universally available anywhere in the game’s code.