A simple nodeJS REST API

The Background

For one of my home projects, I require an API service for my Angular SPA to interact with. I could’ve opted for C++ or Java but the turnaround on writing code in nodeJS is so much quicker, not to mention its efficiency.

The project I’m working on is a project task manager. There are dozens out there already but I need to brush up on my Angular and nodeJS knowledge anyway and figured this would be a good opportunity to do that. Plus I only want it locally, on my local network server, along with the SPA. Then my finished project will provide me with exactly what I need. Nothing less, nothing more.

I wanted to be able to store a bunch of data and access it when and where I need to. So my first port of call was to figure out what kind of data I wanted to store.

Well, it’s a project task manager, with emphasis on estimated task durations, deadlines and order of importance and with that in mind, here’s what I decided I need:

  • Project name
  • Project deadline
  • Project description
  • Project color*
  • Task name
  • Task deadline
  • Task description
  • Task estimated duration
  • Task color*
  • Task priority, normal | low | high

*The tasks and projects will have an assigned color because I intend on showing them in a bar graph, one of those horizontal ones with the cute colors.

Next, I wanted to use that information to design a database to store all of the values in. I will call the database TaskMan, because, why not. I could’ve drawn up an entity-relationship diagram but truth be told, when it’s a project for myself, I just open up a text editor, figure out what I need and then go for a loose design from there. It’ll only be myself using it so I’m not too bothered about having the perfect database, I’m not a database administrator, after all. I do, however, write all of my database creation code in a text file and save it as .sql just because thats wise. I used to use phpMyAdmin for managing MySQL but times have changed, my friends, times have changed.

 

RESTful

I won’t go into a great deal of detail with this because there’s a wealth of information about it online already. But REST refers to REpresentational State Transfer. As in, the state is transferred with each request, as opposed to being stored by the server. So the server remains stateless, it does not keep sessions for each request etc.

There’s four main components to using a RESTful API:

  • Create – POST
  • Read – GET
  • Update – PUT
  • Delete – DELETE

Commonly referred to as CRUD, these refer to the types of HTTP request that one can make. I make use of these here.

The Database

Alright, I’m just going to dump some tables here which details the database tables I decided to create, and then I’ll include the creation SQL for them also.

 

This is the Projects table:

Field Name Field Type Length Description
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
name VARCHAR 56 NOT NULL UNIQUE
description TEXT
start_date DATETIME
deadline DATETIME
completed BOOLEAN / TINYINT
color VARCHAR 7
priority_id INT 3 FOREIGN KEY

 

And this is the almost identical Tasks table:

Field Name Field Type Length Description
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
project_id INT NOT NULL FOREIGN KEY
name VARCHAR 56 NOT NULL UNIQUE
description TEXT
start_date DATETIME
deadline DATETIME
estimated_duration INT 3
completed BOOLEAN / TINYINT
priority_id INT 3 FOREIGN KEY

Finally, this table is the Priority table:

Field Name Field Type Length Description
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
name VARCHAR 20
color VARCHAR 7

 

I could definitely get some normalisation in the works here but like I said, not a DBA and its for a home project only, so, I’m happy with that. I decided to add a color record to the Priority table too but I’m not sure if I will use it or not, yet.

 

Following is the MySQL code to create the above tables and database:

CREATE DATABASE TaskMan; CREATE TABLE priority( id INT NOT NULL AUTO_INCREMENT, name VARCHAR(20) NOT NULL, color VARCHAR(7), CONSTRAINT priority_pk PRIMARY KEY(id) ); CREATE TABLE projects( id INT NOT NULL AUTO_INCREMENT, name VARCHAR(56) NOT NULL UNIQUE, description TEXT, start_date DATETIME, deadline DATETIME, completed BOOLEAN, color VARCHAR(7), priority_id INT(3), CONSTRAINT projects_pk PRIMARY KEY (id), FOREIGN KEY (priority_id) REFERENCES priority(id) ON DELETE SET NULL ); CREATE TABLE tasks( id INT NOT NULL AUTO_INCREMENT, project_id INT NOT NULL, name VARCHAR(56) NOT NULL, description TEXT, start_date DATETIME, deadline DATETIME, estimated_duration INT(3), completed BOOLEAN, priority_id INT(3), CONSTRAINT tasks_pk PRIMARY KEY(id), FOREIGN KEY(priority_id) REFERENCES priority(id) ON DELETE SET NULL, FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE );

Now, you can either log in to MySQL via terminal or, if you have phpMyAdmin set up, you can log in there and paste it straight into the SQL code executor that they have. I use MySQL via the terminal, although I definitely use phpMyAdmin for quickly inserting and examining data. The order of execution is important because of the FOREIGN KEY references.

As a side note, ON DELETE SET NULL means set the tasks.priority_id to NULL if a priority that is referenced is deleted. ON DELETE CASCADE means delete this record if the projects.id reference is deleted. It just keeps the database tidy.

 

The Project

That’s the database set up, at this point I began my nodeJS project in WebStorm. I just titled it TaskMan and created an empty project. From there, I opened the terminal within the WebStorm IDE and ran a couple of commands. The first being npm init. I keep all values default except the entry point, I change that to app.js instead because I have a whole lot of index.js files in my project, so I prefer the entry point to be descriptive and different.

Next I ran some more npm commands to install some node modules to the project:

npm install express
npm install mysql
npm install moment
npm install morgan -D
npm install http-errors

Express will let me get a server up and running really easily, mysql is, well, for mysql database interaction and moment is a fantastic library which, in their own words, allows you to “Parse, validate, manipulate, and display dates and times in JavaScript”. Morgan is a HTTP error logger I am using for development; the -D parameter is the same as –save-dev, just shorter. So the library won’t be included in production. Finally, http-errors just makes life easier when dealing with http errors, I use it to create a 404 Not Found error.

Alright, with all those dependencies installed, time to churn out some code. I created an app.js file in the root directory of my project. In this file, I included morgan, express and http-errors. At first, I just set up express to listen on port 3000 and return a typical “Hello, world” to ensure everything is working so far:

let createError = require('http-errors'); let express = require('express'); let logger = require('morgan'); let app = express(); app.use(function(req, res, next) { res.send('Hello, world!'); }); app.listen(3000, "0.0.0.0");

Running this code, I directed my browser to http://localhost:3000 and saw “Hello, world!” as expected. So now express is up and running, my journey continued!

I wanted to ensure I could handle urlencoded and json data, and as such I implemented the built-in middleware provided by the Express library to do so. The following middleware are based on the body-parser library. I also implemented the morgan logger and http-errors at this point.

So below let app = express();, I added some more app.use() calls to do this. See the full code below, everything new is bold:

let createError = require('http-errors'); let express = require('express'); let logger = require('morgan'); let app = express(); app.use(logger('dev')); app.use(express.json()); app.use(express.urlencoded({ extended: false })); // catch 404 and forward to error handler app.use(function(req, res, next) { next(createError(404, 'rip')); }); // error handler app.use(function(err, req, res, next) { // set locals, only providing error in development res.locals.message = err.message; res.locals.error = req.app.get('env') === 'development' ? err : {}; // render the error page res.status(err.status || 500).send(err.message); }); app.listen(3000, "0.0.0.0");

The http-errors library in action:
app.use(function(req, res, next) { next(createError(404, 'rip')); });

This piece of code takes all incoming requests that reach it and creates a 404 error, with the help of the http-errors library, and passes that to the next() function which will pass the request down the line to the next handler in the line. The next down the line happens to the be final handler, which sets some locals values and then returns the error code and message.

 

app.use(function(err, req, res, next) { // set locals, only providing error in development res.locals.message = err.message; res.locals.error = req.app.get('env') === 'development' ? err : {}; // render the error page res.status(err.status || 500).send(err.message); });

At this point, all I will ever get from this code, no matter what calls I make to what endpoints, is 404 errors. This is because of the call to createError(404, ‘rip’));. It’s the first request handler that is encountered and with that said, order is important here. Even if I fully implemented my whole API by now, if that block of code remains first in order, nothing but 404s will ever be returned. That’s not entirely true, you could throw a different error with a different handler further down the line, but order is important. Requests take a path through your code and you should ensure that it’s the right path.

So the final piece of code to add to app.js is something to handle my requests. I created a new directory in the root of my project called controllers, and in this directory I created an index.js file to handle some controlling. It doesn’t do anything just yet but let’s implement that in app.js for now so we can close off that file and not return to it for a while.

let createError = require('http-errors'); let express = require('express'); let logger = require('morgan'); let controllers = require('./controllers'); let app = express(); app.use(logger('dev')); app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(controllers); // catch 404 and forward to error handler app.use(function(req, res, next) { next(createError(404, 'rip')); }); // error handler app.use(function(err, req, res, next) { // set locals, only providing error in development res.locals.message = err.message; res.locals.error = req.app.get('env') === 'development' ? err : {}; // render the error page res.status(err.status || 500).send(err.message); }); app.listen(3000, "0.0.0.0");

Here, I created a controllers constant and told the express app to use whatever code is in that directory to process incoming requests. If that code cannot process requests, an error is thrown. I am yet to update the rest of the code to use http-errors, though, so instead, it simply sets an appropriate status and returns an appropriate message, as opposed to creating a new http error with the library and passing it along the handler chain.

Controllers

The controllers directory provides a controller for each endpoint in the API. So if I make a call to http://domain:3000/octopus, I will have a controller called octopus_controller.js to handle that request.

I had two endpoints in mind, one for the projects and one for the tasks, and so I created a controller for each of those endpoints. Now lets take a look at index.js inside the controllers directory:

let express = require('express'), router = express.Router(), projectsController = require('./projects_controller'), tasksController = require('./tasks_controller'); router.use('/projects', projectsController); router.use('/tasks', tasksController); router.get('/', function (req, res) { res.status(403).send("403 Access Forbidden"); }); module.exports = router;

The code here is relatively simple. I define a handle to the express module so I can access the router component, I define my controllers and then I tell the router to use the appropriate controller for the relative endpoints. I also add some code to return a 403 Access Forbidden error in the event that the directory is accessed directly. module.exports = router; is then called to return the code in this file as an object to app.js when it requires the controller directory.

I won’t go through both controllers because they are somewhat similar, so let’s just take a look at projects_controller.js:

let express = require('express'), router = express.Router(), projectsModel = require('../models/projects_model'), general = require('../helpers/general'); router.post('/', createNewProject); router.get('/', getAllProjects); router.get('/:projectId', getProjectById); router.delete('/:projectId', deleteProject); router.delete('/', deleteProject); router.put('/:projectId', updateProject); function getProjectById(req, res) { let required = ['projectId'], params = req.params; if (!general.checkIfObjectContains(params, required)) { res.status(400).send("Missing Parameter"); } else { projectsModel.getProjectById(params) .then(data => { if (data.toString() !== '') res.status(200).send({data: data}); else res.status(404).send('404 Not Found'); }) .catch( // Log the rejection reason (err) => { console.log(err); }); } } function getAllProjects(req, res) { let required = [], params = req.params; if (!general.checkIfObjectContains(params, required)) { res.status(400).send("Missing Parameter"); } else { projectsModel.getAllProjects(params) .then(data => { if (data !== null) res.status(200).send({data: data}); else res.status(404).send('404 Not Found'); }) .catch( // Log the rejection reason (err) => { console.log(err); }); } } function createNewProject(req, res) { let required = ['name', 'description', 'start_date', 'deadline', 'color', 'priority_id'], params = req.body; if (!general.checkIfObjectContains(params, required)) { res.status(400).send({data:"Missing Parameter"}); } else { projectsModel.newProject(params) .then(data => { if (data !== null && data.affectedRows > 0) { res.setHeader('Location', '/projects/' + data.insertId); res.status(201).send(null); } else { res.status(200).send({data:'unable to add record'}); } }) .catch( // Log the rejection reason (err) => { console.log(err.toString()); }); } } function updateProject(req, res) { let required = ['name', 'description', 'start_date', 'deadline', 'color', 'priority_id', 'project_id'], params = req.body; if (!general.checkIfObjectContains(params, required)) { res.status(400).send({data:"Missing Parameter"}); } else { projectsModel.updateProject(params) .then(data => { if (data !== null && data.affectedRows > 0) { res.setHeader('Location', '/projects/' + data.insertId); res.status(201).send(null); } else { res.status(200).send({data:'unable to add record'}); } }) .catch( // Log the rejection reason (err) => { console.log(err); }); } } function deleteProject(req, res) { let required = ['projectId'], params = req.params; if (!general.checkIfObjectContains(params, required)) { res.status(404).send("404 Not Found/Missing Parameter"); } else { projectsModel.deleteProject(params) .then(data => { if (data !== null && data.affectedRows > 0) res.status(200).send(null); else res.status(404).send(null); }) .catch( // Log the rejection reason (err) => { console.log(err); }); } } module.exports = router;

This follows very much the same format as the controllers index.js. I require express so I can access the router component and then tell it which endpoints to use for which type of request.

router.post('/', createNewProject); router.get('/', getAllProjects); router.get('/:projectId', getProjectById); router.delete('/:projectId', deleteProject); router.delete('/', deleteProject); router.put('/:projectId', updateProject);

This tells the router what to do with each endpoint. Notice that a few of the endpoints are just defined as a forward slash, that’s because this code is called within the index.js controller code which already defines the /projects endpoint. So when you see a forward slash as an endpoint here, it actually means /projects/. Each of these router function calls refer to the type of HTTP request it will handle, POST, GET, PUT and DELETE. The endpoint is defined and then a function provided so it knows what to do with that request.

Let’s take a closer look at this line: router.get(‘/:projectId’, getProjectById);. The :projectId means we are expecting a parameter in the URL after the slash, so the endpoint would be, for example, /projects/1 of a type GET. The parameter name used here is referenced in the function provided to handle the request.

function getProjectById(req, res) { let required = ['projectId'], params = req.params; if (!general.checkIfObjectContains(params, required)) { res.status(400).send("Missing Parameter"); } else { projectsModel.getProjectById(params) .then(data => { if (data.toString() !== '') res.status(200).send({data: data}); else res.status(404).send('404 Not Found'); }) .catch( // Log the rejection reason (err) => { console.log(err); }); } }

So this is how all of the request functions appear. They define some constants, required and params, the required contains a list of parameter names that are required, if any, and the params constant contains the request parameters, if any.

A custom written function contained within a helper class is used to determine whether or not any of the required parameters are missing from the request and if so, a HTTP 400 Bad Request error code is returned along with an indication as to why.

If no parameters are missing, the code goes on to call a function of the projectsModel class, which handles interaction with the MySQL database. Using promises, the code will either return a status of 200 with the requested data or a 404 Not Found, once the MySQL interaction is completed. Finally, any errors are caught and printed to the console, for now. Errors will be properly logged in the future. And finally, at the end of the code, module.exports = router; is called.

Models

Let’s take a look at the models directory now, which contains all the code to interact with MySQL. This directory doesn’t contain an index.js file because it’s not required. It does, however, contain a mysql_model.js file that is used within each of the models, which are, naturally, projects_model.js and tasks_model.js. mysql_model.js is simply a wrapper to make interacting with the mysql library more simple. I picked up the concept of this code from a previous job and have used it in all my nodeJS projects since, so shout out to ‘Ash’ for originally writing it and giving me the inspiration, knowledge and understanding to reproduce it.

let mysql = require('mysql'), config = require('../config'); module.exports = function() { this.query = function(sql, params) { if(!params){params = []; } return new Promise(function(resolve, reject) { con = mysql.createConnection(config.mysql); con.connect(function(err) { if (err) throw err; }); con.query(sql, params, function(err, result) { if(err) { return reject(err); } else { return resolve(result); } }); }); }, this.Select = function(sql, params) { return this.query(sql, params); }, this.Update = function(sql, params) { return this.query(sql, params); }, this.Insert = function(sql, params) { return this.query(sql, params); }, this.Delete = function(sql, params) { return this.query(sql, params); } };

First, the mysql library is included, or, required, as is a config file that defines the database connection information. I’ll show that code in just a minute so you can see how it looks. Then we attempt to connect to the database, if an error is thrown it is returned. This is wrapped in a Promise to allow easy query chaining. Finally, some specific functions are defined which simply provide a user-friendly means of running queries so instead of calling query(sql, params); I can call Select(sql, params); or Insert(sql, params); for better readability.

The config file looks like this, with sensitive values removed:

module.exports = { mysql: { host: 'host_here', user: 'user_here', password: 'password_here', database: 'TaskMan' } };

So that’s the mysql wrapper detailed, lets take a look at projects_model.js to see what that model is doing. I’ll focus on the getProjectById(); function, since that’s the one I singled out above.

let mysql = require('./mysql_model'), db = new mysql(); module.exports = { getProjectById({projectId}) { let query = "SELECT * FROM projects WHERE id=?", params = [projectId]; return db.Select(query, params); }, getAllProjects() { let query = "SELECT * FROM projects"; return db.Select(query); }, newProject({name, description, start_date, deadline, color, priority_id}) { let query = "INSERT INTO projects (name, description, start_date, deadline, color, priority_id) VALUES (?, ?, ?, ?, ?, ?)", params = [name, description, start_date, deadline, color, priority_id]; return db.Insert(query, params); }, deleteProject({projectId}) { let query = "DELETE FROM projects WHERE id=?"; params = [projectId]; return db.Delete(query, params); }, updateProject({name, description, start_date, deadline, color, priority_id, project_id}) { let query = "UPDATE projects SET name=?, desription=?, start_date?, deadline=?, color=?, priority_id=? WHERE id=?"; params = name, description, start_date, deadline, color, priority_id, project_id; return db.Update(query, params); } };

So I include the mysql wrapper that I just talked about, and instantiate a new object of that type, called db. Then the rest of the code is exported as a module. It simply declares all the functions I need to interact with the database. Each function takes an object literal as a paremeter, contains the required SQL and returns the result of the query that is executed by making a call to the appropriate method on the db object.

getProjectById({projectId}) { let query = "SELECT * FROM projects WHERE id=?", params = [projectId]; return db.Select(query, params); }

So here, the getProjectById({projectId}) function is defined. It then defines the query to run, and the params that are required. Then it returns a call to db.Select(query, params);. It’s that simple.

Summary

Alright, let’s wrap it up. I’ve talked about creating a RESTful API that interacts with a MySQL database, using nodeJS. It follows an MVC architecture, without the V, of course. I’ve discussed creating a database, an express nodeJS server, a mysql wrapper, endpoint controllers and models for those controllers. I’ve shown you how I create a RESTful API using this technology, and I hope it helps somebody out there that’s interested in doing the same thing.

The project structure I used is:

Project Root --app.js --controllers ----index.js ----projects_controller.js ----tasks_controller.js --models ----mysql_model.js ----projects_model.js ----tasks_model.js --config ----index.js

A final note, if you ever decide to use this in a production environment, adding authentication middleware is a relatively easy step also. You’d just have to use an existing node library like Passport or Auth0 and implement a registration and authentication endpoint too.

Thanks for stopping by!

Custom Shortcode and Quicktags for blog posts

I use inline code styling for a lot of my posts and I have been asked on several occasions how so. Well, I added some custom shortcode to my wordpress HTML editor. I’m a developer, and I very much dislike the visual editor, and so I use the HTML editor. So, custom shortcode is very convenient for me. Now, I’m going to explain how I achieved this, so y’all can get the same functionality too!

First of all, I created a child theme of the theme I am using. This means getting all up in your website files and making a new directory, so if you’re not comfortable with that. Abort now! I’m joking, follow along. Be Brave. You’ll never learn anything new if you don’t try.

Alright, child themes. These let you edit your current theme without losing the modifications when your theme is updated. WordPress have a brilliant tutorial on how to do this here, but I’ll explain it briefly anyway.

You’re going to need a new directory, the name being currentTheme-child. So if you’re using Chocolate Cookie then you will have a chocolate-cookie directory. To child this theme, create a chocolate-cookie-child directory.

The path is generally /wp-content/themes/

That’s step one, done!

Now, we gotta create a style.css file, this proclaims its love for the parent theme, so WordPress knows where to find the good stuff. The actual theme. The very first thing in this file should be a block comment to declare several attributes.

/* Theme Name: Chocolate Cookie Child Description: Chocolate Cookie Child Theme Author: Tim Talbot Author URL: http://timtalbot.co.uk Template: chocolate-cookie Version: 1.0.0 */

The only required attributes here are Theme Name and Template. These tell WordPress the name of your child theme and the template (parent theme) it should use within your child theme.

Alright, so far we have:

  • A new directory called chocolate-cookie-child
  • a style.css file within that directory
  • a block comment at the top of style.css to define some required attributes, Theme Name and Template

All we need to do now is create a functions.php file to enqueue the style within WordPress. This ensures that WordPress loads and presents the style.css style, to implement any of our custom CSS, after it has loaded the parent theme styling.

<?php add_action( 'wp_enqueue_scripts', 'my_theme_enqueue_styles' );

This is the beginning of our functions.php script. We’re calling add_action() and passing it two parameters, the first is a string to denote which action the second parameter, a function, should be hooked to. The second param, the name of a function within our functions.php script. I left mine defined as-is, per the WordPress tutorial.

Next, we’re going to implement that function and so this code follows the add_action() call:

function my_theme_enqueue_styles() { $parent_style = 'chocolate-cookie-style'; wp_enqueue_style( $parent_style, get_template_directory_uri() . '/style.css' ); wp_enqueue_style( 'child-style', get_stylesheet_directory_uri() . '/style.css', array( $parent_style ), wp_get_theme()->get('Version') ); } ?>

Here we provide a $parent_style handle, the easiest way to find this is look at the source of your blog with your parent theme activated. In my experience, it’s always been theme-name-style, though. So, scroll down your blog source code or CTRL+F to search for .css, you’re looking for a css include that looks like this:

<link rel='stylesheet' id='chocolate-cookie-style-css' href='http://timtalbot.co.uk/wp-content/themes/chocolate-cookie/style.css?ver=5.3.2' type='text/css' media='all' />

Here within the ID attribute, we can see chocolate-cookie-style-css, and so we remove the -style suffix to be left with the handle to our parent style, chocolate-cookie-style.

We then feed this $parent_style handle into wp_enqueue_style() to enqueue the parent style. We then call it again to enqueue our child style. Finally, we close up our PHP script with ?> and we’re good to go. Now we can equip our Chocolate Cookie Child theme within our WordPress dashboard, unless something wen’t wrong, somewhere.

Well, that’s our child theme created! Now we can move on to what we came here for, the shortcode implementation!

First of all, I added some custom CSS to the child style.css file, this dictates how my in-line code will be presented:

mycode { /*border-radius: 5px; -moz-border-radius: 5px; -webkit-border-radius: 5px;*/ border: 1px dashed #969696; background: #f5f5f5; color: #BF4D28; padding-left: 5px; padding-right: 5px; font: Monaco,Consolas,"Andale Mono","DejaVu Sans Mono",monospace; white-space: nowrap; }

Using a custom tag, mycode, allows me to just wrap text in that tag to apply the style. I don’t want to type all of those tags all of the time though, so let’s add a shortcode button above the text editor. For that, back to functions.php.

First of all, we call add_action(‘admin_print_footer_scripts’, ‘add_my_code_tag’);. We put this right after the previous call to add_action(), the one that enqueued our style. It follows the same format, the first parameter is the hook to which the function identified by the second parameter should be hooked to. So we want to inject our custom code on admin pages, and so we use the admin_print_footer_scripts hook. Our function, add_my_code_tag is a function that comes after my_theme_enqueue_styles() function in the functions.php file.

Here is that function:

function add_my_code_tag() { if(wp_script_is("quicktags")) { ?> <script type="text/javascript"> //this function is used to retrieve the selected text from the text editor function getSel() { var txtarea = document.getElementById("content"); var start = txtarea.selectionStart; var finish = txtarea.selectionEnd; return txtarea.value.substring(start, finish); } QTags.addButton( "code_shortcode", "Inline Code", callback ); function callback() { var selected_text = getSel(); QTags.insertContent("<mycode>" + selected_text + "</mycode>"); } </script> <?php } }

Sidenote: There’s nothing worse than badly formatted code, and while writing this post I just noticed that the <code> tag didn’t retain code formatting, so I just added code {white-space: pre; line-height: 1em;} to my child theme style.css to fix that. I also spent half an hour adjusting the <mycode> CSS to tweak the style to match the theme of the blog, since it looked very cool for me until I reloaded the CSS and it picked up some styling that it hadn’t previously, oops!

Back to the matter at hand!

The very first thing we do is check if the quicktags wordpress script is being loaded, since we don’t want to try and access that if it hasn’t loaded yet. If the condition is true, we write a function, in JavaScript to grab the selected text. We use this in the next function we write, callback(), which is used when we create our new quick tags button.

To create a new Quick Tags button, we call QTags.addButton(), part of the WordPress Quicktags API, which requires at least 3 arguments. An Identifier, a display name for the button and a callback or opening tag. Here, I use a callback… called… callback, wow. RIP naming conventions.

Finally, the callback() function which executes when the new Quicktags button is clicked declares a variable which will contain the return value of the first function, getSel(), which is any currently selected text. It’ll then use QTags.insertContent() to insert content at the cursor location. In a typical use-case, this will be at the location of the selected text. The inserted data is the selected text surrounded by the <mycode> opening and closing tags. If no text is selected, it’ll just insert the tags. That’s it, we’re done! If you’ve followed along correctly, we should be good to go!

I suspect we can add some elegance to this, though, so if that no text is selected, it’ll just insert the opening or closing tag. So let’s modify the add_my_code_tag() function to do that. I’m going to add a boolean variable to keep track of whether we want an opening or closing tag, and I am going to add some conditional statements to determine which course of action we seek. The possibilities are:

  • Insert an opening <mycode> tag
  • Insert a closing </mycode> tag
  • Wrap selected text in <mycode> tags

I may be over-complicating things here, but here’s where I’m at:

function add_my_code_tag() { if(wp_script_is("quicktags")) { ?> <script type="text/javascript"> var close = false; //this function is used to retrieve the selected text from the text editor function getSel() { var txtarea = document.getElementById("content"); var start = txtarea.selectionStart; var finish = txtarea.selectionEnd; return txtarea.value.substring(start, finish); } QTags.addButton( "code_shortcode", "Inline Code", callback ); function callback() { var selected_text = getSel(); if(selected_text == '') { if(!close) { QTags.insertContent("<mycode>"); close = true; } else { close = false; QTags.insertContent("</mycode>"); } } else { QTags.insertContent("<mycode>" + selected_text + "</mycode>"); } } </script> <?php } }

and now, all of that over-complicated, unnecessary, extra-work nonsense is out of the way – We can reduce the function to the following code for exactly the same functionality:

function add_my_code_tag() { if(wp_script_is("quicktags")) { ?> <script type="text/javascript"> QTags.addButton( "code_shortcode", "Inline Code", "<mycode>", "</mycode>" ); </script> <?php } }

Because that very same functionality is provided to us by WordPress’s Quicktips API. And there you have it, that’s how to add a quicktags, shortcode button to your WordPress HTML editor, to make life simpler!

Sidenote: just another side note, in case you’re wondering how I type all of these < and > opening and closing tags without them disappearing, I’m using HTML Special Entities. Without spaces: & lt; and & gt; for opening and closing, respectively.

A day in the life of php

So, I had today off (saaaame as every Tuesday!)  What better way to spend it than doing 10 weeks worth of lab tasks for Databases, Networks and the web. That’s that out the way.

Despite the fact that it was a whole term’s worth of exercises, they were quite simple for someone who’s been using PHP for the best part of 15 years. Reallly simple. Posting form data and spamming superglobals, loops in HTML forms (.php files, obviously), creating and eating (deleting) cookies, cookie based CSS, arrays, MySQL communication etc etc.

My next task at hand is to do the coursework for that course which, judging by the requirements, should only take a day or 2 at ze most. That will involve creating a database and web based CD sales site.

The only downside to this is all of our lecturer’s example code seems hella outdated. Using “print” instead of “echo”, not that there’s anything wrong with that, print is just weird and always returns 1 and can only print one string which makes no sense for something that isn’t a method but kind of looks like a method if you want it to. Using mysql_ functions when the PHP.net docs clearly, by clearly i mean big ass pink box at top of documentation screaming READ ME, states the method is deprecated and will be removed in future revisions of PHP.  It’s not the end of the world, just less fortunate for anyone who is unaware that the relative content is outdated 🙁

 

Anyway, plan for the term -> do everything in the first few weeks and waste my time looking for a job! Money bitches, that’s what we needin’

 

1) Dynamic web coursework x2 (60/40)

2) Internet and distributed Programming labs and coursework

3) PAP coursework

4) Social Computing coursework.