Facebook-like Photo Gallery With Comments
Join the DZone community and get the full member experience.
Join For FreeFacebook like photo gallery with comments
Have you thought about own facebook-style photo gallry system with
comments? I have and today I prepared it for you.
Main idea – when we click on images – they popup (ajax) with bigger
image at the left and comments section at the right. All images are in
the database (mySQL). And, of course, we will use PHP to achieve our
result. Also, our comment system will prevent accepting more than 1
comment per 10 mins (to avoid spam).
Live Demo
download in package
Now – download the source files and lets start coding!
Step 1. SQL
For our gallery I prepared two SQL tables: first table keeps records of our images. It contains several fields: title, filename, description, time of adding and comments count. Another table keeps comments. So, execute next SQL instructions:
CREATE TABLE IF NOT EXISTS `s281_photos` ( `id` int(10) unsigned NOT NULL auto_increment, `title` varchar(255) default '', `filename` varchar(255) default '', `description` text NOT NULL, `when` int(11) NOT NULL default '0', `comments_count` int(11) NOT NULL default '0', PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; INSERT INTO `s281_photos` (`title`, `filename`, `description`, `when`) VALUES ('Item #1', 'photo1.jpg', 'Description of Item #1', UNIX_TIMESTAMP()), ('Item #2', 'photo2.jpg', 'Description of Item #2', UNIX_TIMESTAMP()+1), ('Item #3', 'photo3.jpg', 'Description of Item #3', UNIX_TIMESTAMP()+2), ('Item #4', 'photo4.jpg', 'Description of Item #4', UNIX_TIMESTAMP()+3), ('Item #5', 'photo5.jpg', 'Description of Item #5', UNIX_TIMESTAMP()+4), ('Item #6', 'photo6.jpg', 'Description of Item #6', UNIX_TIMESTAMP()+5), ('Item #7', 'photo7.jpg', 'Description of Item #7', UNIX_TIMESTAMP()+6), ('Item #8', 'photo8.jpg', 'Description of Item #8', UNIX_TIMESTAMP()+7), ('Item #9', 'photo9.jpg', 'Description of Item #9', UNIX_TIMESTAMP()+8), ('Item #10', 'photo10.jpg', 'Description of Item #10', UNIX_TIMESTAMP()+9); CREATE TABLE IF NOT EXISTS `s281_items_cmts` ( `c_id` int(11) NOT NULL AUTO_INCREMENT , `c_item_id` int(12) NOT NULL default '0', `c_ip` varchar(20) default NULL, `c_name` varchar(64) default '', `c_text` text NOT NULL , `c_when` int(11) NOT NULL default '0', PRIMARY KEY (`c_id`), KEY `c_item_id` (`c_item_id`) ) ENGINE=MYISAM DEFAULT CHARSET=utf8;
Step 2. PHP
Now, please create empty index.php file and put next code:
index.php
<?php // disable warnings if (version_compare(phpversion(), "5.3.0", ">=") == 1) error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED); else error_reporting(E_ALL & ~E_NOTICE); require_once('classes/CMySQL.php'); // include service classes to work with database and comments require_once('classes/CMyComments.php'); if ($_POST['action'] == 'accept_comment') { echo $GLOBALS['MyComments']->acceptComment(); exit; } // prepare a list with photos $sPhotos = ''; $aItems = $GLOBALS['MySQL']->getAll("SELECT * FROM `s281_photos` ORDER by `when` ASC"); // get photos info foreach ($aItems as $i => $aItemInfo) { $sPhotos .= '<div class="photo"><img src="images/thumb_'.$aItemInfo['filename'].'" id="'.$aItemInfo['id'].'" /><p>'.$aItemInfo['title'].' item</p><i>'.$aItemInfo['description'].'</i></div>'; } ?> <!DOCTYPE html> <html lang="en"><head> <meta charset="utf-8" /> <title>Facebook like photo gallery with comments | Script Tutorials</title> <!-- Link styles --> <link href="css/main.css" rel="stylesheet" type="text/css" /> <!-- Link scripts --> <script src="https://www.google.com/jsapi"></script> <script> google.load("jquery", "1.7.1"); </script> <script src="js/script.js"></script> </head> <body> <header> <h2>Facebook like photo gallery with comments</h2> <a href="http://www.script-tutorials.com/facebook-like-photo-gallery-with-comments/" class="stuts">Back to original tutorial on <span>Script Tutorials</span></a> </header> <!-- Container with last photos --> <div class="container"> <h1>Last photos:</h1> <?= $sPhotos ?> </div> <!-- Hidden preview block --> <div id="photo_preview" style="display:none"> <div class="photo_wrp"> <img class="close" src="images/close.gif" /> <div style="clear:both"></div> <div class="pleft">test1</div> <div class="pright">test2</div> <div style="clear:both"></div> </div> </div> </body></html>
We have just created a main index file of our gallery. By default – script generates a list of images (with title and description), and it also generates an empty hidden object which we are going to use in order to accept custom content by ajax requests. Also, when we post comments, we forward this request (to accept new comment) into comments class. Now, lets review next important php file:
photos_ajx.php
<?php // disable warnings if (version_compare(phpversion(), "5.3.0", ">=") == 1) error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED); else error_reporting(E_ALL & ~E_NOTICE); if ($_POST['action'] == 'get_info' && (int)$_POST['id'] > 0) { require_once('classes/CMySQL.php'); // include service classes to work with database and comments require_once('classes/CMyComments.php'); // get photo info $iPid = (int)$_POST['id']; $aImageInfo = $GLOBALS['MySQL']->getRow("SELECT * FROM `s281_photos` WHERE `id` = '{$iPid}'"); // prepare last 10 comments $sCommentsBlock = $GLOBALS['MyComments']->getComments($iPid); $aItems = $GLOBALS['MySQL']->getAll("SELECT * FROM `s281_photos` ORDER by `when` ASC"); // get photos info // Prev & Next navigation $sNext = $sPrev = ''; $iPrev = (int)$GLOBALS['MySQL']->getOne("SELECT `id` FROM `s281_photos` WHERE `id` < '{$iPid}' ORDER BY `id` DESC LIMIT 1"); $iNext = (int)$GLOBALS['MySQL']->getOne("SELECT `id` FROM `s281_photos` WHERE `id` > '{$iPid}' ORDER BY `id` ASC LIMIT 1"); $sPrevBtn = ($iPrev) ? '<div class="preview_prev" onclick="getPhotoPreviewAjx(\''.$iPrev.'\')"><img src="images/prev.png" alt="prev" /></div>' : ''; $sNextBtn = ($iNext) ? '<div class="preview_next" onclick="getPhotoPreviewAjx(\''.$iNext.'\')"><img src="images/next.png" alt="next" /></div>' : ''; require_once('classes/Services_JSON.php'); $oJson = new Services_JSON(); header('Content-Type:text/javascript'); echo $oJson->encode(array( 'data1' => '<img class="fileUnitSpacer" src="images/'. $aImageInfo['filename'] .'">' . $sPrevBtn . $sNextBtn, 'data2' => $sCommentsBlock, )); exit; }
This file sends back information about requested photo. This is an enlarged image, block with comments and navigation buttons (to open previous / next images ajaxy). As you can see – we use comments class, now, it’s time to look at it too:
classes/CMyComments.php
<?php class CMyComments { // constructor function CMyComments() { } // return comments block function getComments($i) { // draw last 10 comments $sComments = ''; $aComments = $GLOBALS['MySQL']->getAll("SELECT * FROM `s281_items_cmts` WHERE `c_item_id` = '{$i}' ORDER BY `c_when` DESC LIMIT 10"); foreach ($aComments as $i => $aCmtsInfo) { $sWhen = date('F j, Y H:i', $aCmtsInfo['c_when']); $sComments .= <<<EOF <div class="comment" id="{$aCmtsInfo['c_id']}"> <p>Comment from {$aCmtsInfo['c_name']} <span>({$sWhen})</span>:</p> <p>{$aCmtsInfo['c_text']}</p> </div> EOF; } return <<<EOF <div class="comments" id="comments"> <h2>Comments</h2> <div id="comments_warning1" style="display:none">Don`t forget to fill both fields (Name and Comment)</div> <div id="comments_warning2" style="display:none">You can't post more than one comment per 10 minutes (spam protection)</div> <form onsubmit="return false;"> <table> <tr><td class="label"><label>Your name: </label></td><td class="field"><input type="text" value="" title="Please enter your name" id="name" /></td></tr> <tr><td class="label"><label>Comment: </label></td><td class="field"><textarea name="text" id="text"></textarea></td></tr> <tr><td class="label"> </td><td class="field"><button onclick="submitComment({$i}); return false;">Post comment</button></td></tr> </table> </form> <div id="comments_list">{$sComments}</div> </div> EOF; } function acceptComment() { $iItemId = (int)$_POST['id']; // prepare necessary information $sIp = $this->getVisitorIP(); $sName = $GLOBALS['MySQL']->escape(strip_tags($_POST['name'])); $sText = $GLOBALS['MySQL']->escape(strip_tags($_POST['text'])); if ($sName && $sText) { // check - if there is any recent post from you or not $iOldId = $GLOBALS['MySQL']->getOne("SELECT `c_item_id` FROM `s281_items_cmts` WHERE `c_item_id` = '{$iItemId}' AND `c_ip` = '{$sIp}' AND `c_when` >= UNIX_TIMESTAMP() - 600 LIMIT 1"); if (! $iOldId) { // if everything is fine - allow to add comment $GLOBALS['MySQL']->res("INSERT INTO `s281_items_cmts` SET `c_item_id` = '{$iItemId}', `c_ip` = '{$sIp}', `c_when` = UNIX_TIMESTAMP(), `c_name` = '{$sName}', `c_text` = '{$sText}'"); $GLOBALS['MySQL']->res("UPDATE `s281_photos` SET `comments_count` = `comments_count` + 1 WHERE `id` = '{$iItemId}'"); // and print out last 10 comments $sOut = ''; $aComments = $GLOBALS['MySQL']->getAll("SELECT * FROM `s281_items_cmts` WHERE `c_item_id` = '{$iItemId}' ORDER BY `c_when` DESC LIMIT 10"); foreach ($aComments as $i => $aCmtsInfo) { $sWhen = date('F j, Y H:i', $aCmtsInfo['c_when']); $sOut .= <<<EOF <div class="comment" id="{$aCmtsInfo['c_id']}"> <p>Comment from {$aCmtsInfo['c_name']} <span>({$sWhen})</span>:</p> <p>{$aCmtsInfo['c_text']}</p> </div> EOF; } return $sOut; } } return 1; } // get visitor IP function getVisitorIP() { $ip = "0.0.0.0"; if( ( isset( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) && ( !empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) ) { $ip = $_SERVER['HTTP_X_FORWARDED_FOR']; } elseif( ( isset( $_SERVER['HTTP_CLIENT_IP'])) && (!empty($_SERVER['HTTP_CLIENT_IP'] ) ) ) { $ip = explode(".",$_SERVER['HTTP_CLIENT_IP']); $ip = $ip[3].".".$ip[2].".".$ip[1].".".$ip[0]; } elseif((!isset( $_SERVER['HTTP_X_FORWARDED_FOR'])) || (empty($_SERVER['HTTP_X_FORWARDED_FOR']))) { if ((!isset( $_SERVER['HTTP_CLIENT_IP'])) && (empty($_SERVER['HTTP_CLIENT_IP']))) { $ip = $_SERVER['REMOTE_ADDR']; } } return $ip; } } $GLOBALS['MyComments'] = new CMyComments(); ?>
This class performs two main functions – it can accept new comments and also it can give us a box with comments. There are two more service classes: CMySQL.php and Services_JSON.php. They are two known classes to work with database and json. You can adjust database settings in database class. Both classes available in our package.
Step 3. Javascript
Now we should prepare user interface behavior with using javascript, please prepare next file for the project:
js/script.js
// close photo preview block function closePhotoPreview() { $('#photo_preview').hide(); $('#photo_preview .pleft').html('empty'); $('#photo_preview .pright').html('empty'); }; // display photo preview block function getPhotoPreviewAjx(id) { $.post('photos_ajx.php', {action: 'get_info', id: id}, function(data){ $('#photo_preview .pleft').html(data.data1); $('#photo_preview .pright').html(data.data2); $('#photo_preview').show(); }, "json" ); }; // submit comment function submitComment(id) { var sName = $('#name').val(); var sText = $('#text').val(); if (sName && sText) { $.post('index.php', { action: 'accept_comment', name: sName, text: sText, id: id }, function(data){ if (data != '1') { $('#comments_list').fadeOut(1000, function () { $(this).html(data); $(this).fadeIn(1000); }); } else { $('#comments_warning2').fadeIn(1000, function () { $(this).fadeOut(1000); }); } } ); } else { $('#comments_warning1').fadeIn(1000, function () { $(this).fadeOut(1000); }); } }; // init $(function(){ // onclick event handlers $('#photo_preview .photo_wrp').click(function (event) { event.preventDefault(); return false; }); $('#photo_preview').click(function (event) { closePhotoPreview(); }); $('#photo_preview img.close').click(function (event) { closePhotoPreview(); }); // display photo preview ajaxy $('.container .photo img').click(function (event) { if (event.preventDefault) event.preventDefault(); getPhotoPreviewAjx($(this).attr('id')); }); })
Please note, we use jQuery instructions in our script (I hope that you haven’t forgot that we linked jQuery library in the header section through google service).
Step 4. CSS
In the long run, we should stylize our page elements (our container with photos, photo preview area with comments):
css/main.css
/* project styles */ .container { border: 1px solid #111111; color: #000000; margin: 20px auto; overflow: hidden; padding: 15px; position: relative; text-align: center; width: 1090px; -moz-border-radius: 5px; -ms-border-radius: 5px; -o-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; } .photo { border: 1px solid transparent; float: left; margin: 4px; overflow: hidden; padding: 4px; white-space: nowrap; /* CSS3 Box sizing property */ -moz-box-sizing: border-box; -webkit-box-sizing: border-box; -o-box-sizing: border-box; box-sizing: border-box; /* CSS3 transition */ -moz-transition: border 0.2s ease 0s; -ms-transition: border 0.2s ease 0s; -o-transition: border 0.2s ease 0s; -webkit-transition: border 0.2s ease 0s; transition: border 0.2s ease 0s; } .photo:hover { border-color: #444; } .photo img { cursor: pointer; width: 200px; } .photo p, .photo i { display: block; } .photo p { font-weight: bold; } /* preview styles */ #photo_preview { background-color: rgba(0, 0, 0, 0.7); bottom: 0; color: #000000; display: none; left: 0; overflow: hidden; position: fixed; right: 0; top: 0; z-index: 10; } .photo_wrp { background-color: #FAFAFA; height: auto; margin: 100px auto 0; overflow: hidden; padding: 15px; text-align: center; vertical-align: middle; width: 1000px; -moz-border-radius: 5px; -ms-border-radius: 5px; -o-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; } .close { cursor: pointer; float: right; } .pleft { float: left; overflow: hidden; position: relative; width: 600px; } .pright { float: right; position: relative; width: 360px; } .preview_prev, .preview_next { cursor: pointer; margin-top: -64px; opacity: 0.5; position: absolute; top: 50%; -moz-transition: opacity 0.2s ease 0s; -ms-transition: opacity 0.2s ease 0s; -o-transition: opacity 0.2s ease 0s; -webkit-transition: opacity 0.2s ease 0s; transition: opacity 0.2s ease 0s; } .preview_prev:hover, .preview_next:hover { opacity: 1; } .preview_prev { left: 20px; } .preview_next { right: 40px; } /* comments styles */ #comments form { margin: 10px 0; text-align: left; } #comments table td.label { color: #000; font-size: 13px; padding-right: 3px; text-align: right; width: 105px; } #comments table label { color: #000; font-size: 16px; font-weight: normal; vertical-align: middle; } #comments table td.field input, #comments table td.field textarea { border: 1px solid #96A6C5; font-family: Verdana,Arial,sans-serif; font-size: 16px; margin-top: 2px; padding: 6px; width: 250px; } #comments_list { margin: 10px 0; text-align: left; } #comments_list .comment { border-top: 1px solid #000; padding: 10px 0; } #comments_list .comment:first-child { border-top-width:0px; } #comments_list .comment span { font-size: 11px; }
And...You're Done
Published at DZone with permission of Andrey Prikaznov, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Trending
-
Low Code vs. Traditional Development: A Comprehensive Comparison
-
Implementing RBAC in Quarkus
-
JSON to PDF Magic: Harnessing LaTeX and JSON for Effortless Customization and Dynamic PDF Generation
-
Using DuckDB With CockroachDB
Comments