Updating the image controller

Let's create the controller and view model for the image page. The controller for the image will be a little more complex, as we'll write the logic to handle uploading and saving of the image files via the form on the homepage.

Displaying an image

The index function in the image controller will look almost identical to the index function from the home controller. The only difference is that instead of generating an array of images, we will build a view model for a single image. However, the view model for this image will have a little more information than the one from the homepage, since we are building a page that renders a more detailed view of an image (versus the thumbnail collection on the homepage). The most noteworthy inclusion is that of a comments array for the image.

Taking another look at the original index function in our controllers/image.js file, we can see the simple existing res.render line of code:

res.render('image'),

We want to replace this line with a view model and an updated res.render statement using the following code:

var viewModel = {
    image: {
        uniqueId:       1,
        title:          'Sample Image 1',
        description:    'This is a sample.',
        filename:       'sample1.jpg',
        views:          0,
        likes:          0,
        timestamp:      Date.now
    },
    comments: [
        {
            image_id:   1,
            email:      '[email protected]',
            name:       'Test Tester',
            gravatar:   'http://lorempixel.com/75/75/animals/1',
            comment:    'This is a test comment...',
            timestamp:  Date.now()
        },{
            image_id:   1,
            email:      '[email protected]',
            name:       'Test Tester',
            gravatar:   'http://lorempixel.com/75/75/animals/2',
            comment:    'Another followup comment!',
            timestamp:  Date.now()
        }
    ]
};

res.render('image', viewModel);

Here we are declaring a new viewModel variable again, this time with an image property that contains the properties for the single image. In addition to the image property, there is also a comments property, which is an array of comment objects. You can see that each comment has various properties specific to a comment for each image. This JavaScript object is actually a pretty good preview of what our real data will wind up looking like once we include logic to connect our app to MongoDB!

After we build our sample image object and its collection of comments, we pass that along to our res.render call, thus sending this new viewModel directly to our image's Handlebars template. Again, if you review the HTML code in the image.handlebars file, you can see where each property of the viewModel is being displayed.

Again, let's run the application and make sure our image page is appearing properly:

$ node server.js

Once the app is running and you've launched it in your browser, click on any of the images that are listed in the Newest Images section of the homepage. This should take you to an individual image page where you will see something like the page shown in the following screenshot:

Displaying an image

Notice that the title, description, the likes and views count, and timestamp are all now appearing on the page. In addition, you can see a few comments listed below the image as well!

Uploading an image

The next feature we need to implement in our image controller is the logic to handle when a user submits an image via the Image Upload form on the homepage. Even though the form is on the homepage of our app, we decided to house the logic to handle uploading within our image controller because logically, this makes the most sense (since this feature has primarily to do with images and not the homepage per se). This was purely a personal decision, and you can house the logic wherever you please.

You should note that the HTML for the form on the homepage has its action set to /images and its method is post. This matches perfectly with the route we set up previously, where we are listening for a post to the /images route and calling the image controller's create function.

The create function in our image controller will have a few key responsibilities:

  • It should generate a unique filename for the image, which will also act as an identifier
  • It should save the uploaded file to the filesystem and ensure that it is an image file
  • It should redirect to the image/image_id route once its task is complete to display the actual image

As we are going to be working with the filesystem in this function, we are going to need to include a few modules from the Node.js core set of modules, specifically the File System (fs) and the Path (path) modules.

Let's begin by first editing the controllers/image.js file and inserting the two new require statements at the very top of the file:

var fs = require('fs'),
    path = require('path'),

Next, take the create function's original code:

res.send('The image:create POST controller'),
res.redirect('/images/1'),

Replace this original code with the following code:

var saveImage = function() {
    // to do...

};

saveImage();

Here, we created a function called saveImage, and we executed it immediately after we declared it. This might look a little odd, but the reason for this will become clearer when we implement database calls in the following chapter. The main reason is that we are going to call saveImage repeatedly to ensure that the unique identifier we generated is in fact unique and doesn't already exist in the database (as a previously saved image's identifier).

Let's review a breakdown of the code that will be inserted inside the saveImage function (replacing the // to do... comment). I will cover each line of code for this function and then give you the entire block of code at the end:

var possible = 'abcdefghijklmnopqrstuvwxyz0123456789',
    imgUrl = '';

We need to generate a random six-digit alphanumeric string to represent a unique identifier for an image. This identifier will work similar to other websites that provide tiny URLs for unique links (that is, bit.ly). To do this, we first provide a string of possible characters that can be used while generating the random string:

for(var i=0; i < 6; i+=1) {
    imgUrl += possible.charAt(Math.floor(Math.random() * possible.length));
}

Then, loop six times and randomly pull out a single character from our string of possible characters, appending it in each cycle. By the end of this for loop, we should have a string that consists of six random letters and/or numbers, for example 'a8bd73':

var tempPath = req.files.file.path,
    ext = path.extname(req.files.file.name).toLowerCase(),
    targetPath = path.resolve('./public/upload/' + imgUrl + ext);

Here we declare three variables; where our uploaded files will be stored temporarily, the file extension of the file that was uploaded (that is '.png', '.jpg', and so on), and a destination where the uploaded image should ultimately reside. For both the latter variables, we use the Path node module, which works great while dealing with file names and paths and getting information from a file (such as a file extension). Next we move the image from its temporary upload path to its final destination:

if (ext === '.png' || ext === '.jpg' || ext === '.jpeg' || ext === '.gif') {
    fs.rename(tempPath, targetPath, function(err) {
        if (err) throw err;

        res.redirect('/images/99'),
    });
} else {
    fs.unlink(tempPath, function () {
        if (err) throw err;

        res.json(500, {error: 'Only image files are allowed.'});
    });
}

This code performs some validation. Specifically, it conducts checks to make sure that the uploaded file extension matches a list of allowable extensions—namely, known image file types. If a valid image file was uploaded, it is moved from the temp folder via the filesystem's rename function. Notice how the filesystem (fs) rename function takes three parameters: the original file, the new file, and a callback function. The callback function is executed once the rename is complete. If node didn't work this way (always relying on callback functions), it's quite likely your code will execute immediately following the execution of the rename function and try to work against a file that doesn't exist yet (that is, the rename function didn't even finish doing its work). By using a callback function, we are effectively telling node that "once the rename of the file is finished and the file is ready and where it should be, then execute the following code."

The else condition that follows handles the situation when the uploaded file was invalid (that is, not an image), so we call the unlink function of the filesystem module, which will delete the original file (from the temp directory it was uploaded to) and then send a simple JSON 500 with an error message.

Here is the complete saveImage function (again, the following code will replace // to do... from earlier):

var possible = 'abcdefghijklmnopqrstuvwxyz0123456789',
    imgUrl = '';

for(var i=0; i < 6; i+=1) {
    imgUrl += possible.charAt(Math.floor(Math.random() * possible.length));
}

var tempPath = req.files.file.path,
    ext = path.extname(req.files.file.name).toLowerCase(),
    targetPath = path.resolve('./public/upload/' + imgUrl + ext);

if (ext === '.png' || ext === '.jpg' || ext === '.jpeg' || ext === '.gif') {
    fs.rename(tempPath, targetPath, function(err) {
        if (err) throw err;

        res.redirect('/images/' + imgUrl);
    });
} else {
    fs.unlink(tempPath, function () {
        if (err) throw err;

        res.json(500, {error: 'Only image files are allowed.'});
    });
}

With this code in place, we can now successfully upload an image file via the form on the homepage. Give it a try by launching the app and opening it in a browser. Once there, click on the Browse button in the main form, and select an image file from your computer. If successful, the image file should exist within the public/upload folder of your project with a new random filename.

Note

Be sure that you have the public/upload/temp folders created in your project, or you will get runtime errors when you attempt to write files to a location that doesn't exist. Write permissions may need to be set on the folder depending on your OS and security access.

After the upload form completes and the create controller function does its work, it will redirect to the individual image page for the image that was uploaded.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.189.171.52