Build an App With WordPress – The compulsory todo list

Matt Mullenweg’s State Of The Word was very insightful into what was to come of WordPress in 2012. One thing he mentioned would be big, is be WordPress powering apps.

With that in mind, there aren’t many posts around that teach you how to make an app. I thought I’d start with the compulsory “How to make a to-do list app using WordPress!” It even works across all platforms- that’s right. Mobile, Tablet and Desktop!

There is a Demo page (username: user, password: notes) where you can see and play with what we’ll be creating. You can download the source theme too, and install it yourself. Or you can follow this tutorial.

A bit of Theory

Many web app frameworks these days use MVC or MVVM structure. WordPress has its own unique structure to handle data, but there’s no reason we can’t take some concepts and apply them ourselves. I’m talking about separating logic, event driven functions and views.

WordPress is a good starting point because it provides a plethora of APIs for us to leverage. We’ve got Authentication, User Roles, Sanitisation, and Content Types out-of-the-box, to name a few. We don’t have to worry about admin screens. If you’re creating a complex app, you don’t even have to worry about meta boxes, taxonomies, or dealing with image upload.

The approach we’re going to take is simple. I’ve built a starter theme (with CSS, and basic WP/JS functionality) for you to begin with. The code you’ll write in this tutorial will be separated into mainly Javascript (main.js), and WordPress logic (ajax-actions.php). This separation of code allows us to create a flexible yet well structured app.

To bring you up to speed

The focus of this tutorial isn’t to show you how to design an app, but to build it with AJAX. I’ll be showing you how to identify and deal with individual events, how to capture the data from them, post them to the WordPress database, and handle the response. All without ever going into wp-admin!

The starter theme I’ve put together for you is just a skeleton without any functionality. The entire UI is done for you (buttons, style, drop down menus, modal boxes), all that’s left to do is the heavy lifting, that is dealing with and saving data, and updating the UI based upon the response from WordPress. I’ve included Mustache.js and the templates we’ll be using (in footer.php), along with enough WordPress code to display a list of all posts and users currently in the database upon first load.

Data flow in a single page web app

Let’s examine the diagram from just above.

Here you can see our data is passed from the UI when a user submits data. Javascript interprets the UI input, and sends it to PHP as data. The PHP does the work of inserting the data into the WordPress database. When WordPress is happy, PHP tells Javascript the data is saved. Javascript then updates the UI for the user to recognise it all went smoothly.

In terms of Javascript, there are 4 important functions missing from the starter theme that we’re going to build. They are:

  1. UI interaction (user actions)
  2. UI requests (when a user submits data)
  3. AJAX requests (sending the data to WordPress code)
  4. AJAX response (handling the response we get back from WordPress)

In terms of WordPress and PHP code, we’re missing all the actions that handle the AJAX requests from the client. I’ll identify them for you in a second, but first get a new WordPress install up and running, and install the starter theme.

If you don’t know how to set up a WordPress blog, the Codex has a handy tutorial. Let’s hope you do though! This is quite an advanced tutorial.

The base theme

Your WordPress site should now look like this.

User Interactions

With the UI installed, four actions jump out to me immediately. I’ve circled them below.

  1. Clear All Posts
  2. Add User (therefore delete user too)
  3. Delete Post
  4. Add Post

Other actions are visible by interacting with the UI, “update post”.

We’re going to use jQuery’s powerful event engine to pick up on each of these interactions, and capture the data related for use in the database. If you quickly open footer.php and look at line 26, you’ll notice each link (button) has 3 sets of data: data-modal, data-action, and data-id.

<a ... data-modal="" data-action="update-post", data-id="{{id}}">update</a>

With jQuery, we can grab this very easily so we know (in this case) which modal box to open (if any), which action is being called, and what the ID of the item being manipulated is. Let’s quickly look at the data structure and type of users and notes.

The data structure of Users and Notes

To keep it simple, a user will simply be included by their email address. That means a User exists of:

  • An ID (integer)
  • An Email Address (string)

Posts are simply titles of WordPress posts, broken down to:

  • An ID (integer)
  • A note (string)

As you can see, the data object is basically identical, which simplifies things for us a little bit. Here’s what our JSON data that gets passed to the server will look like:

{
  action: "action-to-be-called",
  id: 1,
  text: "Email Address or Note"
}

Getting stuck into the AJAX Requests

Now we know a little bit about the data structure we’ll be working with, we need some Javascript functions to process these requests. Open up functions.php, and jump down the file to line 63, and you’ll the comment

//WP LOCALIZE SCRIPT

Just briefly, WordPress has a function called wp_localize_script() that is a helper for multilingual sites. Why does that matter to us? It’s primary purpose is to pass on a language identifier to scripts through the form of a variable attached to the window.

We can use this variable to make the wp-ajax file location available to our scripts, so we have the correct URL to post AJAX requests to. Go ahead and replace the comment with this.

wp_localize_script(
  'main',
  'WP_AJAX',
  array( 'ajaxurl' => admin_url( 'admin-ajax.php' ) )
);

Save and close functions.php. If you open the WordPress site we’re working on and view source, you’ll see this somewhere in the mix (near the closing </body> element).

/* <![CDATA[ */
var WP_AJAX = {"ajaxurl":"http://articl.es/notes-app/wp-admin/admin-ajax.php"};
/* ]]> */

In Javascript, this is avaialble to us in the variable

WP_AJAX.ajaxurl

We now have an AJAX URL to post to!

Processing AJAX Requests

Very quickly, we’re going to create our first Javascript function: Processing AJAX requests. Since I’ve packaged jQuery into the starter theme, we’ll be using jQuery’s $.post function to send requests to the server. Once we start detecting actions from the user (next section), we’ll use this function to send the data we collect from the user.

In js/main.js on line 64, replace the

//create processRequest function

with the following.

processRequest: function(data){
  console.log(data);
  $.post(
    WP_AJAX.ajaxurl,
    data,
    Notes.succcessfulRequest
  );
},

We’re extending the Notes object in main.js to include another function “processRequest”. If you’re not a whiz with Javascript, just follow along and copy code examples exactly.

Like PHP, Javascript functions take arguments too, and in our case “processRequest” is taking a variable we’re calling “data”.

console.log(data) logs the data recieved into your browser’s console. We’ll look at this later.

The $.post sends a POST request, and takes 3 arguments. In order they are

  1. AJAX URL to post to send data too
  2. The data to send to the URL (In object format)
  3. A success function to handle the response of the AJAX request

If you’re on the ball, you’ll deduce that we now need a successfulRequest function, since it doesn’t exist yet!

We’ll keep it simple for now, still in main.js replace the

//create successfulRequest function

with

succcessfulRequest: function(jsonResponse){
  alert('Request sent!');

  //change UI based on response from WordPress
},

successfulRequest() takes a variable ‘jsonResponse’, which is what we recieve back from the server. When we get to the PHP, we simply echo what we want to come back in this variable, and this function picks it up.

For now, it will just make a browser alert that tells you the AJAX Request has been sent.

But what is all this useful for?!

Aha. These functions don’t do anything yet, but now they’re in place we’re going to use them to handle UI Interactions. The next function we’re going to create is actually already half there.

Handling UI Actions

On line 55 of main.js, you’ll see the function ‘actions’. At present, this isn’t active, and currently only hides the modal boxes at the bottom of the page in the theme. Go up to line 7 where the comment

//actions call

resides, and replace it with

Notes.actions();

Since this is in the init function (which gets called at the very bottom of main.js once the page has loaded), if you refresh the page the modals will be hidden.

Detecting User Interactions

The “actions” function we’re going to write is to detect when the user clicks an action button. It will collect all the neccessary data, then check if a modal window is required. If it’s not, it will fire off an AJAX request. If it does, it will open up the modal window that relates to the action (dictated by the data-modal attribute we saw earlier).

On line 58, go ahead and replace the comment with the following code.

$('.action').on('click', function(e){

  e.preventDefault();

  var action = 'notes-' + $(this).data('action');
  var modal  = $(this).data('modal');
  var id     = $(this).data('id');
  var text   = $(this).parent('li').find('input').val();

  var data = {
    action: action,
    id: id,
    text: text
  };

  if( modal === "" ){
    Notes.processRequest(data)
  } else { //if a modal is required }
});

If you’re familiar with jQuery, you’ll know what this does. The “.on()” function tells jQuery to call the following function every time the ‘.action’ class (our action button) is clicked.

e.preventDefault() stop the URL changing.

The following four variable declarations pick up all the data we need to process the request.

It specifies the “action” that will be executed on the server as “notes-action-called”. The Modal will either be empty (in the case where no extra input is required), “yn” when a yes/no confirmation is needed, or “invite” in the case of adding a new user to the app. It then builds the data into a JSON object.

The conditional first checks if a modal is required. If not, it then passes the previously built data off to the function we created earlier that processes requests.

Go ahead and refresh your WordPress site, and then click the delete or add icon. You’ll be alerted that your request has been sent! Sweet!

Now we need to handle the else statement, when a modal is required. replace the

} else { //if a modal is required }

conditional with this one.

} else if ( modal != "" ){
  Notes.openNotice(modal, data);
}

This checks if a data-modal attribute is present, and if it is then it passes the modal type and data object to the “openNotice” function.

Your whole actions function should now look like this.

actions: function(){
  $('.notice').addClass('hidden');

  $('.action').on('click', function(e){

    e.preventDefault();

    var action = 'notes-' + $(this).data('action');
    var modal  = $(this).data('modal');
    var id     = $(this).data('id');
    var text   = $(this).parent('li').find('input').val();

    var data = {
      action: action,
      id: id,
      text: text
    };

    if( modal === "" ){
      Notes.processRequest(data)
    } else if ( modal != "" ){
      Notes.openNotice(modal, data);
    }
  });
},

The openNotice function is above the actions function already built for you! Find it on line 35.

Since we’re passing arguments to the function, we have to modify the function to take arguments as well. Change the openNotice() function to accept them.

openNotice: function(noticeClass, data){
  $('.notice:visible').toggleClass('hidden').data('info', '');
  Notes.centreNotice();
  $('.'+noticeClass).toggleClass('hidden').data('info', data);
},

This firstly hides any modals that are visible, and destroys any data attached to it.

It then centres the modals, another function pre-built for you. Finally it finds the modal with the class (specified by the action button’s data-modal attribute) then attaches our data to it. This last step is important as there’s no other way to pass on data to the next event (confirmation or entering an email address to invite or cancel) to then send to the server.

Save your main.js, and refresh the site. Then click an action that requires a modal, i.e. “add user” or “clear notes”. You’ll get a nice little modal window popping up pre-coded courtesy of yours truly.

The yes/no modal window.

Sending Requests Based On User Interaction

The last piece of the puzzle is to pass forward requests to processRequest() based on user input into the modals, i.e. a user enters an email address into the invite modal or presses yes on the confirmation one.

That comes in the form of UISendRequest(), a function that listens to the user response on a modal, grabs the data we previously attached to the modal, and then shoot it off to processRequest().

On line 8 of main.js, you’ll see another call commented out.

//UISendRequest call

Modify this similar to line 7, making a call to the UISendRequest function inside init().

Notes.UISendRequest();

Now that it’s being called, we need to actually write the function! Line 80 is another comment, of which we need to replace with a function.

UISendRequest: function(){
  $('.go').click(function(){
    var data = $(this).parents('.notice').data('info');

    Notes.processRequest(data);
    $('.notice:visible').toggleClass('hidden').data('info', '');
  });

  $('.cancel').click(function(){
    $(this).parents('.notice').toggleClass('hidden').data('info', '');
  });
},

This function binds to the click event of the “.go” button and the “.cancel” button. These reside in our modals in footer.php, on line 5, 6 and 12. It looks for the data attached to the modal that the action was called from, processes the request, and then closes the modal and destroys attached data. Cancel closes the modal and destroys the data attached.

Save, and go back to the site and refresh it. Remember how we put console.log() into the processRequest() function? Here is where it comes in handy. If you’re not sure how to bring up your browser console, stackoverflow has a good page to teach you.

The console is basically a log for JavaScript, and any other errors that get spat out by your website’s requests. The function console.log() puts whatever is in between the brackets into the console. Since we put the data variable in there, any time processRequest() gets called it’s going to log the data passed to it.

Here, in the console you can track the actions I’ve taken in the UI.

The first thing I did was confirm I wanted to clear all notes. We only need the action for this, as it will act as a blanket delete.

The second thing I did was attempt to update the post “Hello World!”. You can see the data-action, data-id and the text to update has been passed to processRequest().

The last thing I did was attempt to invite a user to the app. You can see in the console though, there is no email address. This is vital to pass to processRequest, as in the WordPress code we use it to register a new user!

That can be fixed with two lines of code. Before sending the request, it should checks if the modal is an invitation modal and picks out the email address, then adds it to the data variable. Put this right before the Notes.processRequest() line in the UISendRequest() function.

if($(this).parents('.notice').hasClass('invite'))
  data.text = $('.invite-email').val();

If you attempt to invite a person, you’ll now see their email address show up in the console data!

Finally, we want to be able to see what is coming back from the server. In the successfulRequest() function, change the alert to log the jsonResponse variable instead of alerting “Request sent!”

succcessfulRequest: function(jsonResponse){
  console.log(jsonResponse);

  //change UI based on response from WordPress
},

Great. That’s all the Javascript we need for now. We’ll look at it again later when dealing with responses from the server, but right now it’s time to accept the AJAX Requests.

At Long Last, Interacting With WordPress

Right now, the script pings WordPress with the data we’ve collected, but WordPress doesn’t know what to do with it.

Remember how we pass the data-action through the AJAX request? Now is where it comes in handy. As it turns out, an action is the only parameter that WordPress requires to process an AJAX request. We use the action as a hook, to attach our PHP functions too. So the hook

add_action( 'wp_ajax_notes-add-user', 'notes_add_user' );

Will respond to the “notes-add-user” AJAX request. Cool huh? That of course means, a PHP function called notes_add_user() needs to exist.

Before we dive into that though, we need pre-built and consistent response to send back to the Javascript when the PHP code has run. $fail will simply respond with a failed action (i.e. something went wrong in the WordPress code). generate_response(), however, accepts 3 arguments that get passed back to the javascript.

Open up ajax-actions.php, and replace lines 7-9 with the following.

$fail = json_encode(array('message' => 'fail'), JSON_FORCE_OBJECT);

function generate_response($message, $id = null, $text = null){
  $response = array(
    'message' => $message,
    'id'      => $id,
    'text'    => $text
  );

  echo json_encode($response, JSON_FORCE_OBJECT);
}

These are two utility functions, and they serve a very important purpose. Notice the json_encode() function. By passing an array as the first argument, and JSON_FORCE_OBJECT as the second, our PHP response gets translated back to JSON for our javascript to interpret.

Secondly, does anything here look familiar? The structure of our response is identical to the structure of our request: action becomes message, id and text remain the same. Whilst it is not 100% necessary to mimic the structure, it keeps things clean, simple, and understandable. Especially when dealing with the response from WordPress and translating it to UI changes (so the user can see what has happened).

Authentication and Failed Responses

For our actions, we want to make sure the current user is actually allowed to do the things we’re asking. For example, if the user is trying to add user, they must have the capability to use wp_insert_user(). The helper function current_user_can() allows us to check this. I’m going to go ahead and flesh out all our actions, along with their AJAX hooks. Add all this below the generate_response() function we just built.

function notes_add_user(){

  if(current_user_can( 'add_users' )){

    extract($_POST);

    //create user code here

  } else { echo $fail; }
  exit;
}

function notes_delete_user(){
  if(current_user_can( 'remove_users' )){

    extract($_POST);

    //delete user code here

  } else { echo $fail; }
  exit;
}

function notes_clear_all(){
  if(current_user_can( 'delete_posts' )){

    //do delete all posts here

  } else { echo $fail; }
  exit;
}

function notes_new_post(){
  if(current_user_can( 'publish_posts' )){
    extract($_POST);

    //do create new post code here

  } else { echo $fail; }
  exit;
}

function notes_update_post(){
  if(current_user_can( 'edit_posts' )){
    extract($_POST);

    //update post code here

  } else { echo $fail; }
  exit;
}

function notes_delete_post(){
  if(current_user_can( 'delete_posts' )){
    extract($_POST);

      //delete post code here

  } else { echo $fail; }
  exit;
}

add_action( 'wp_ajax_notes-add-user', 'notes_add_user' );
add_action( 'wp_ajax_notes-delete-user', 'notes_delete_user' );
add_action( 'wp_ajax_notes-clear-all', 'notes_clear_all' );
add_action( 'wp_ajax_notes-new-post', 'notes_new_post' );
add_action( 'wp_ajax_notes-update-post', 'notes_update_post' );
add_action( 'wp_ajax_notes-delete-post', 'notes_delete_post' );

That’s a lot of code to digest, and at present it actually does nothing for us. As you can see though, we have a PHP function and a hook to match every action our app allows.

Note: the extract($_POST) function is absolutely vital. It extracts our data package into variables for us, available as $action, $id and $text.

Another thing that is important to know, is that these hooks are only called if you’re logged in. You should be logged in, but we’ll force login later on to make sure anybody trying to post is logged in and has permissions to post.

Finally, the easiest mistake to make with this is actually including these functions in your theme! Open up functions.php, and go to the bottom. Replace the //INCLUDE AJAX ACTIONS comment with an include_once() function. Don’t forget this! Otherwise you’ll get empty responses forever and you’ll never know why.

include_once(ajax-actions.php);

Adding a user

Funnily enough, our add-user action is the most complex of them all. The reason being we have to perform a number of actions:

  1. Generate a user name based on their email
  2. Check if the user exists already
  3. Generate a random password
  4. Create the new user
  5. Set their role to ‘Editor’ so they have permissions to add, edit and delete posts
  6. Notify the user they’ve been added
  7. Respond back to the javascript with the result

The code is as follows.

function notes_add_user(){

  if(current_user_can( 'add_users' )){

    extract($_POST);
    $user_name = substr($text, 0, strpos($text, "@"));

    $user_id = username_exists( $user_name );

    if ( !$user_id and email_exists($text) == false ) {

      $random_password = wp_generate_password( $length=12, $include_standard_special_chars=false );
      $user_id = wp_create_user( $user_name, $random_password, $text );

      $user = new WP_User($user_id);
      $user->set_role( 'editor' );

      wp_new_user_notification( $user_id, $random_password );

      generate_response('user-added', $user_id, $text);

    } else { echo $fail; }

  } else { echo $fail; }
  exit;
}

That’s quite a lot to digest, but if you follow the points above you can see what happens.

The important thing to note is the generate_response() function. Here we pass the success message ‘user-added’, the new user’s ID, and finally their email address to be added to the UI on the fly. It is also what will show up in your console upon recieving a response from WordPress.

Save that, and go ahead and invite a user. In the screenshot below, you can see me requesting the user to be added, and then getting a response back saying that the user has been added, along with their email address and newly assigned ID. Cool!

This sort of request and response has one last puzzle piece to make the full loop in the diagram from the start of this post. Translating the response from a JSON object recieved from WordPress into a change in the UI that a human can recognise.

If you refresh the page, your newly added user will show up in the “users” menu, which is cool. But we want this to happen without having to refresh!

Handling the response

By now, you can tell that each action has 3 parts. Firstly, it sends the request. This is generic for all actions. Once the action is recieved, it has a WordPress function to do the heavy lifting. Upon return, it needs a function that translates the response.

Since the data is returned to the Javascript function successfulRequest(), that’s where we’ll handle the response.

If you’re adept at PHP, you’ll understand the switch/case loop well. Fortunately Javascript has a switch function as well, and we can switch on the message receieved back from the server to perform different tasks.

Head back to main.js, and go to line 103 where successfulRequest() is. Modify like so:

succcessfulRequest: function(jsonResponse){
  //action's effect on UI

  response = jQuery.parseJSON(jsonResponse);

  console.log(response.message);

  switch (response.message){

    case "fail" : alert('Something went wrong.'); break;

    case "user-added" :

      console.log('user with email address ' + response.text + ' has been added to WordNotes');

      var user_tmpl = $('#tmpl-user').html();
      var user_data = {
        id: response.id,
        email: response.text
      }

      if($('.all-users .empty').length > 0) $('.all-users .empty').remove();

      $('.all-users').append(Mustache.render(user_tmpl, user_data));

    break;

    //OTHER CASES GO IN HERE

  }

},

Firstly, we translate the returned JSON response into jQuery readable JSON using parseJSON().

We then console.log() the message coming back, for our convenience.

Now for the meaty part. We use switch to switch through this message, and apply javascript accordingly. Obviously, we test for the case of ‘user-added’, and then console.log() that the action was successful.

The next 5 lines are setting up a Mustache template, ready to be inserted into the menu (remember I set up these templates so we can focus on the JS and PHP. They are in footer.php if you’re curious).

The next line checks if the list placeholder ‘No users! Add some!’ is present, and if it is, delete it.

Finally, we append the new user to the user list using a Mustache function, render, which spits out the template along with the data we’ve passed to it.

Save that, head to your site, and refresh. Click the Users list, and then click add new user. Input an email address, and select ‘invite’. You’ll magically see the new user get added to the list!

If you visit the wp-admin section of your site, and select “users” you’ll also see all the users you’ve added. All without touching wp-admin!

This shows the power of AJAX requests combined with WordPress’s wp-ajax.php file.

But what about the rest of the actions?

You’re right. There are still 5 actions to be catered to.

Instead of holding your hand through them all, I’m going to show you the code for each action split into the WordPress code, and the main.js code. Simply add them in as you go. There will be short explanations.

If something goes wrong with your code, make sure your switch/case function in main.js is formatted properly. Otherwise, see you on the other side!

Deleting users

ajax-actions.php

function notes_delete_user(){
  if(current_user_can( 'remove_users' )){

    extract($_POST);

    if(wp_delete_user($id))
      generate_response('user-deleted', $id);

  } else { echo $fail; }
  exit;
}

Simply grab the ID, and use wp_delete_user() to delete the user. Respond with the ID of deleted user so it can be removed from the UI.

main.js

case "user-deleted" :
  $('#user-' + response.id).remove();
  if($('.all-users li').length == 0) $('.all-users').append('<li class="empty">No users yet! add some.</li>');
break;

Remove user with returned ID from the UI. Check the list isn’t empty, if it is add the placeholder.

Clear All Notes

ajax-actions.php

function notes_clear_all(){
  if(current_user_can( 'delete_posts' )){
    $posts = get_posts(array('posts_per_page' => 9999));
    foreach($posts as $post) wp_delete_post( $post->ID );

    generate_response('all-deleted');

  } else { echo $fail; }
  exit;
}

Uses get_posts() to get 9999 most recent posts, loops through them and uses wp_delete_post() to delete them all. Responds with ‘all-deleted’ so UI can be cleared of notes.

main.js

case "all-deleted" :
  $('.note').remove();
  alert('All notes have been deleted.');
break;

Very simple, just removes all list items with the class ‘note’, and alerts the user they have been deleted.

Add Notes

ajax-actions.php

function notes_new_post(){
  if(current_user_can( 'publish_posts' )){
    extract($_POST);

    $p = array(
      'post_title'    => $text,
      'post_author'   => get_current_user_id(),
      'post_content'  => '&nbsp;',
      'post_status'   => 'publish'
    );

    $post_id = wp_insert_post($p);

    if($post_id != 0){

      generate_response( 'post-added', $post_id, $text );

    } else { echo $fail; }

  } else { echo $fail; }
  exit;
}

Builds post array, uses wp_insert_post() to create new post. Responds with ‘post-added’ message, ID of new post and post title for insertion into the UI.

main.js

case "post-added" :

  console.log('Note added!');

  var post_tmpl = $('#tmpl-post').html();
  var post_data = {
    id: response.id,
    post_title: response.text
  };

  $(Mustache.render(post_tmpl, post_data))
    .insertBefore('.notes ul .new-post');

  $('.new-post input').blur().val('New note...');

break;

Logs that a note has been added, builds Mustache template to be inserted into page. Inserts it just before the ‘new-post’ input, and then blurs the input and returns the value to default.

Here’s what you’ve been waiting for. You can now add notes to your notepad on the fly! Below is a screenshot of my request, response and reaction. Clicking the ‘+’ sends the request.

And the new post in the database, without even touching wp-admin.

Editing notes

ajax-actions.png

function notes_update_post(){
  if(current_user_can( 'edit_posts' )){
    extract($_POST);

    $p = array(
      'ID'            => $id,
      'post_title'    => $text,
      'post_author'   => get_current_user_id(),
      'post_content'  => '&nbsp;',
      'post_status'   => 'publish'
    );

    $post_id = wp_update_post($p);

    if($post_id) generate_response('post-updated', $id, $text);

  } else { echo $fail; }
  exit;
}

Upon clicking the tick next to an existing post, this function gets called. Instead of using wp_insert_post(), it uses wp_update_post().

main.js

case "post-updated" :

  $('#post-' + response.id + ' input').blur();

break;

Since the user has already updated the UI for us by typing the new post, simply blur the field so no more editing can be done without clicking it.

Deleting Notes

ajax-actions.php

function notes_delete_post(){
  if(current_user_can( 'delete_posts' )){
    extract($_POST);

    $result = wp_delete_post( $id );

    if(!false) generate_response('post-deleted', $id);

  } else { echo $fail; }
  exit;
}

Simply grabs the ID, uses wp_delete_post() to delete it, and if it was successful, send a response back with the ID of the post to destroy.

main.js

case "post-deleted" :

  $('#post-' + response.id).remove();

break;

Gets the response, and removes the assosciated element based on the ID.

And the rest is history!

If you’ve gotten this far, well done. You now have a fully functioning notes app where you can take, update, delete, or delete all notes, as well as invite and remove users.

Although this is a simple application, you can imagine the power you’d have with more complex data structures, custom post types, and so on. If you have any questions, don’t hesitate to ask now.

Force Authentication

One thing is missing from our app. Forced Authentication. Sure, people would be able to publically view the app, which if you’re ok with stop now. But, if you want to force people to log in to be able to see and edit it, we’ve got a couple of extra lines of code to throw into functions.php.

It consists of 3 parts really. Firstly, force login. Secondly, redirect to home instead of wp-admin to bypass it completely. And thirdly, change the style of the login page to resemble anything but WordPress.

Force login

Here’s a really handy snippet for you. It goes at the bottom of functions.php in your theme.

// Require login for site
get_currentuserinfo();
global $user_ID;
if ($user_ID == '' && $_SERVER['PHP_SELF'] != '/wp-login.php') {
  header('Location: '.get_bloginfo('url').'/wp-login.php'); exit();
}

What this does, is get your current session info. If no user ID is returned (i.e. you’re not logged in), and you’re not on the login page already, send you to the login page. Go ahead and test this, if you log out and try to access any page you’ll get booted out.

Login redirect

Once the user logs in, we want to drop them on the home page of the site, not wp-admin. The following snippet does exactly that.

//change login redirect to home url
function redirect_to_home(){
  return home_url();}
add_filter('login_redirect', 'redirect_to_home');

Make sure it looks nothing like WordPress!

Finally, I’ve built some styles into the starter theme that change the login page to look nothing like WordPress! To activate them, the next snippet will suffice.

function custom_login_style() {
  wp_enqueue_style( 'custom-login', TEMPL_PATH.'/css/custom-login.css' ); }
add_action( 'login_enqueue_scripts', 'custom_login_style' );

Extra credit

There are a number of things we could do to further augment our notes app, but I don’t have time to cover them.

  • Using WP Nonces for security
  • Auto refresh of notes
  • Know when other users are online
  • Validation
  • Using the return button to fire actions

The possibilities are endless! Go ahead and try to do them.

WordPress to Power an App

As you can see, WordPress is perfectly capable of powering apps. Despite this being a small-scale app, you can imagine the endless possibilities with custom post types, custom taxonomies, custom fields, and basically anything related to content management.

I hope I’ve given you the building blocks here to create something more. If you do build something, please post it below! I’d love to see what you come up with.

Resources

Comments (30)

  1. Great tutorial Harley!

    Very impressive what you’ve managed to accomplish here. How would you go about adding the ability to create nested notes or categories within the App?

    Also would it be possible to add the last modified time/date and user who modified each note? I can see some simple project management uses here, with a much friendlier interface than what you typically see.

    This opens up whole knew possibilities. It’s very exciting stuff.

    • Thanks mate, thanks for the questions.

      I believe using revisions, it would be possible to deal with the last modified issue you bring up. You’d have to look into whether wp_update_post() records revisions, and if not how to do so.

      Nested notes, I believe you’d have to make a hierarchical custom post type. You could, however, add a custom field to posts that holds the post parent and post child IDs, and use them to spit out the visual hierarchy on the front-end.

      Hope that pushes you in the right direction :)

  2. Isn’t it an old-school route: javascript-php-db-php-javascript?
    The drawback of WordPress is PHP.
    How nice it would be if WordPress would be on Node.js – one language for all, including the CMS.

    imho, leave WordPress for Blogs and Web Sites.
    For web apps it’s better to look at the emerging technologies like the Meteor, the javascript-localdb server-serverdb route.
    It’s fast, instant, reliable, convenient, scalable etc.,etc.

    • I agree, it would be great if there was a plugin that dealt with all the db syncing issues so you could use backbone properly on the website.

      Unfortunately, it doesn’t exist. The js-php-db route is a good leg up though, and is good for teaching the basic idea that JS should handle live events.

      Sure. But a lot of people would be afraid of / don’t have the time to learn an entirely new framework. It’s no lie WordPress is easy to use, and it can indeed be used for apps. So why not? With the JSON API, there’s even more reason it can be the back end to a web app.

    • Hm, good question.

      In index.php, you’d have to add code within the loop along the lines of

      if($post->post_author == get_current_user_id()){
      //display note here
      }

      That should ensure that only posts of the logged in author get shown to them.

      • I would much rather override the default query with one that only fetches posts from a specific user. Doing it the way you list above would have issues with pagination. Consider:

        * page-size 20 posts.
        * all twenty posts are after 1/1/1970
        * author is really old and last posted in 1969

        with this scenario the loop will happily loop through all twenty posts and not display each one in turn because they’re not the author’s who’s viewing the page. This leaves the author with a blank page, and a “go to page 2″ paginated link.

  3. Nice post!
    I just recently created a webapp using wordpress. Actually I created a site with an alternative theme for mobile users which made the site into an app. I think that it was a great solution for a company seeking both since it allows them to have a single administration for both versions.. a strong point for developing apps using wordpress.

    Anyway, I think it’s noteworthy that you can create a nonce for the ajax call to make it more secure. wp_create_nonce() and use it for the security parameter of the ajax-call. I saw that you posted about it under “extra credit” but really it’s such a simple thing to do that it should be an obvious thing in all wordpress ajax calls.

    Another thing which I think could be an “extra credit” thing would be to not let post_content go to waste but utilize it as a “description” for the note which could appear on mouseover or perhaps mouseclick.

    • Excellent suggestions. It is simple, but the post was already 5k words long and I didn’t want it to drag on longer. Totally agree with wp_content, it could be utilized much the same way as it is in the workflowy.com app.

      Totally agree with the single administration purposes — nearly all my client websites have a jQuery Mobile counterpart that uses the same content as the desktop app. They love it!

  4. My Base Theme doesn’t look like yours (I’ve tried it in Chrome and IE).

    1. When I try to add a new note, it does not have the Check mark next to your circle “5″; I think you refer to this as the update post UI.

    2. When I enter my first note, it doesn’t give me a button (UI) to save the note, and the next line for a note doesn’t appear.

    Have I done something wrong?

    Thanks!

  5. This is a great tutorial and web app development advise.

    As a matter of fact I already built a small web application using WordPress which was initially built using a PHP MVC framework (Yii).
    It was a complete headache to follow up and I consider it is complicated to maintain an application with such frameworks (I also tasted CakePHP, CodeIgniter and Symphony), but using WordPress as a development platform is a complete piece of cake to build applications from scratch and maintain them.

    Cheers!

  6. Good demonstration for building an app in WordPress.

    For those looking for theme links, the links at the bottom of the post (Excluding demo) work fine.

    Also, there’s a slight error in the tutorial code for;

    include_once(ajax-actions.php);

    needs to have single quotes like so;

    include_once(‘ajax-actions.php’);

    otherwise, the requests will be returned as undefined.

Participate