Uploading files asynchronously can be a pain at the best of times, but when coupled with CodeIgniter, it can be a particularly frustrating experience. I finally found a way that not only works consistently, but keeps to the MVC pattern. Read on to find out how!
By the way, you can find some useful CodeIgniter plugins and code scripts on Envato Market, so have a browse to see what you can find for your next project.
Preface
In this tutorial, we'll be using the PHP framework CodeIgniter, the JavaScript framework jQuery, and the script AjaxFileUpload.
It's assumed you have a working knowledge of CodeIgniter and jQuery, but no prior knowledge of AjaxFileUpload is necessary. It is also assumed that you already have an install of CodeIgniter already set up.
For the sake of brevity, the loading in of certain libraries/models/helpers has been omitted. These can be found in the source code supplied, and is pretty standard stuff.
You'll also need a database, and a table, called files
. The SQL to create said table is:
CREATE TABLE `files` ( `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY, `filename` varchar(255) NOT NULL, `title` varchar(100) NOT NULL );
By the end of the tutorial, your file structure should look similar to this (omitting unchaged folders/files):
public_html/
-- application/
---- controllers/
------ upload.php
---- models/
------ files_model.php
---- views/
------ upload.php
------ files.php
-- css/
---- style.css
-- files/
-- js/
---- AjaxFileUpload.js
---- site.js
Step 1 - Creating the Form
Set up the Controller
First, we need to create our upload form. Create a new Controller, called upload, and in the index method, render the view upload.
Your controller should look like this:
class Upload extends CI_Controller { public function __construct() { parent::__construct(); $this->load->model('files_model'); $this->load->database(); $this->load->helper('url'); } public function index() { $this->load->view('upload'); } }
We are also loading in the files model, so we can use it in our methods. A better alternative may be to autoload it in your actual project.
Create the Form
Create your view, upload.php. This view will contain our upload form.
<!doctype html> <html> <head> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script> <script src="<?php echo base_url()?>js/site.js"></script> <script src="<?php echo base_url()?>js/ajaxfileupload.js"></script> <link href="<?php echo base_url()?>css/style.css" rel="stylesheet" /> </head> <body> <h1>Upload File</h1> <form method="post" action="" id="upload_file"> <label for="title">Title</label> <input type="text" name="title" id="title" value="" /> <label for="userfile">File</label> <input type="file" name="userfile" id="userfile" size="20" /> <input type="submit" name="submit" id="submit" /> </form> <h2>Files</h2> <div id="files"></div> </body> </html>
Don't forget to place ajaxfileupload.js in js/.
As you can see, we are loading in our scripts at the top; jQuery, AjaxFileUpload, and our own js file. This will house our custom JavaScript.
Then, we are simply creating a standard HTML form. The empty #files div is where our list of uploaded files will be.
Some Simple CSS
Just so it doesn't look quite so bad, lets add some basic CSS to our file style.css in css/.
h1, h2 { font-family: Arial, sans-serif; font-size: 25px; } h2 { font-size: 20px; } label { font-family: Verdana, sans-serif; font-size: 12px; display: block; } input { padding: 3px 5px; width: 250px; margin: 0 0 10px; } input[type="file"] { padding-left: 0; } input[type="submit"] { width: auto; } #files { font-family: Verdana, sans-serif; font-size: 11px; } #files strong { font-size: 13px; } #files a { float: right; margin: 0 0 5px 10px; } #files ul { list-style: none; padding-left: 0; } #files li { width: 280px; font-size: 12px; padding: 5px 0; border-bottom: 1px solid #CCC; }
Step 2 - The Javascript
Create and open site.js in js/. Place the following code:
$(function() { $('#upload_file').submit(function(e) { e.preventDefault(); $.ajaxFileUpload({ url :'./upload/upload_file/', secureuri :false, fileElementId :'userfile', dataType : 'json', data : { 'title' : $('#title').val() }, success : function (data, status) { if(data.status != 'error') { $('#files').html('<p>Reloading files...</p>'); refresh_files(); $('#title').val(''); } alert(data.msg); } }); return false; }); });
The JavaScript hijacks the form submit and AjaxFileUpload takes over. In the background, it creates an iframe and submits the data via that.
We're passing across the title value in the data parameter of the AJAX call. If you had any more fields in the form, you'd pass them here.
We then check our return (which will be in JSON). If no error occured, we refresh the file list (see below), clear the title field. Regardless, we alert the response message.
Step 3 - Uploading the File
The Controller
Now on to uploading the file. The URL we are uploading to is /upload/upload_file/, so create a new method in the upload controller, and place the following code in it.
public function upload_file() { $status = ""; $msg = ""; $file_element_name = 'userfile'; if (empty($_POST['title'])) { $status = "error"; $msg = "Please enter a title"; } if ($status != "error") { $config['upload_path'] = './files/'; $config['allowed_types'] = 'gif|jpg|png|doc|txt'; $config['max_size'] = 1024 * 8; $config['encrypt_name'] = TRUE; $this->load->library('upload', $config); if (!$this->upload->do_upload($file_element_name)) { $status = 'error'; $msg = $this->upload->display_errors('', ''); } else { $data = $this->upload->data(); $file_id = $this->files_model->insert_file($data['file_name'], $_POST['title']); if($file_id) { $status = "success"; $msg = "File successfully uploaded"; } else { unlink($data['full_path']); $status = "error"; $msg = "Something went wrong when saving the file, please try again."; } } @unlink($_FILES[$file_element_name]); } echo json_encode(array('status' => $status, 'msg' => $msg)); }
This code loads in the CodeIgniter upload library with a custom config. For a full reference of it, check out the CodeIgniter docs.
We do a simple check to determine if the title is empty or not. If it isn't, we load in the CodeIgniter upload library. This library handles a lot of our file validation for us.
Next, we attempt to upload the file. if successful, we save the title and the filename (passed in via the returned data array).
Remember to delete the temp file off the server, and echo out the JSON so we know what happened.
The Model
In keeping with the MVC pattern, our DB interaction will be handled by a model.
Create files_model.php, and add the following code:
class Files_Model extends CI_Model { public function insert_file($filename, $title) { $data = array( 'filename' => $filename, 'title' => $title ); $this->db->insert('files', $data); return $this->db->insert_id(); } }
Files Folder
We should also create the folder our files will be uploaded to. Create new file in your web root called files, making sure it is writable by the server.
Step 4 - The File List
Upon a successful upload, we need to refresh the files list to display the change.
The JavaScript
Open site.js and add the following code to the bottom of the file, below everything else.
function refresh_files() { $.get('./upload/files/') .success(function (data){ $('#files').html(data); }); }
This simply calls a url and inserts the returned data into a div
with an id of files.
We need to call this function on the page load to initially show the file list. Add this in the document ready function at the top of the file:
refresh_files();
The Controller
The URL we are calling to get the file list is /upload/files/, so create a new method called files, and place in the following code:
public function files() { $files = $this->files_model->get_files(); $this->load->view('files', array('files' => $files)); }
Quite a small method, we use our model to load in the currently saved files and pass it off to a view.
The Model
Our model handles the retrieval of the file list. Open up files_model.php, and add in the get_files()
function.
public function get_files() { return $this->db->select() ->from('files') ->get() ->result(); }
Quite simple really: select all the files stored in the database.
The View
We need to create a view to display the list of files. Create a new file, called files.php, and paste in the following code:
<?php if (isset($files) && count($files)) { ?> <ul> <?php foreach ($files as $file) { ?> <li class="image_wrap"> <a href="#" class="delete_file_link" data-file_id="<?php echo $file->id?>">Delete</a> <strong><?php echo $file->title?></strong> <br /> <?php echo $file->filename?> </li> <?php } ?> </ul> </form> <?php } else { ?> <p>No Files Uploaded</p> <?php } ?>
This loops through the files and displays the title and filename of each. We also display a delete link, which include a data attribute of the file ID.
Deleting the File
To round off the tutorial, we'll add in the functionality to delete the file, also using AJAX.
The JavaScript
Add the following in the document ready function:
$('.delete_file_link').live('click', function(e) { e.preventDefault(); if (confirm('Are you sure you want to delete this file?')) { var link = $(this); $.ajax({ url : './upload/delete_file/' + link.data('file_id'), dataType : 'json', success : function (data) { files = $(#files); if (data.status === "success") { link.parents('li').fadeOut('fast', function() { $(this).remove(); if (files.find('li').length == 0) { files.html('<p>No Files Uploaded</p>'); } }); } else { alert(data.msg); } } }); } });
It's always a good idea to get a user confirmation when deleting information.
When a delete link is clicked, we display a confirm box asking if the user is sure. If they are, we make a call to /upload/delete_file
, and if successful, we fade it from the list.
The Controller
Like above, the url we are calling is /upload/delete_file/
, so create the method delete_file
, and add the following code:
public function delete_file($file_id) { if ($this->files_model->delete_file($file_id)) { $status = 'success'; $msg = 'File successfully deleted'; } else { $status = 'error'; $msg = 'Something went wrong when deleteing the file, please try again'; } echo json_encode(array('status' => $status, 'msg' => $msg)); }
Again, we let the model do the heavy lifting, echoing out the output.
The Model
We're now at the final piece of the puzzle: our last two methods.
public function delete_file($file_id) { $file = $this->get_file($file_id); if (!$this->db->where('id', $file_id)->delete('files')) { return FALSE; } unlink('./files/' . $file->filename); return TRUE; } public function get_file($file_id) { return $this->db->select() ->from('files') ->where('id', $file_id) ->get() ->row(); }
Because we only pass the ID, we need to get the filename, so we create a new method to load the file. Once loaded, we delete the record and remove the file from the server.
That's it, tutorial complete! If you run it, you should be able to upload a file, see it appear, and then delete it; all without leaving the page.
Final Thoughts
Obviously, the views can do with some prettying up, but this tutorial should have given you enough to be able to integrate this into your site.
There are a few shortcomings to this method, however:
- You can only upload one file at a time, but this can rectified easily by using a service like Uploadify.
- There is no progress bar built into the script.
- We could reduce the SQL calls by updating the files
div
upon file upload, instead of fully replacing them.
Thanks for reading!
Comments