Facebook Style Link Preview Using Jquery And Simple HTML Dom

Facebook Style Link Preview Using Jquery And Simple HTML Dom

Hi friends, Today I’ve brought a very interesting tutorial (specially for Jquery and Ajax beginners). In this post, I’m going to teach you, that how to create a facebook style link preview widget in your website. I don’t know which technologies does facebook use to grab the link or url data but in this tutorial, I’ve used PHP’s simple html dom parser and jquery to make this awesome project.  If you don’t have any idea about simple html dom then let me explain a brief about this.

Facebook Style Link Preview Using Jquery And Simple HTML Dom

Live Demo Download

Simple HTML Dom:

First of all, this is a  PHP’s dom parser class that I love very much always as it has helped me a lot in my past to make the crawling applications with php. Simple Html Dom is another “kind of Jquery ” that is written in PHP 5 to manipulate both valid and invalid HTML on server side.  It can find tags on an HTML page with selectors and extract contents from HTML in a single line just like jQuery. That’s why, It can be used better in developing the crawling applications and grabbing data from other websites.

The application contains following files:

1. simple_html_dom.php :

This is the dom parser class included for grabbing the meta data from provided url in the textarea.

2. get.php :

This file gets the url requested through ajax and validate it with curl either it has any html content or redirected to another link.  Sometimes the url is redirected to another  address for eg. If you type www.yahoo.com in the browser, then it is redirected to “http://in.yahoo.com/?p=us” and curl finds no html on yahoo.com url. So  I’ve used curl’s “CURLINFO_EFFECTIVE_URL” parameter  to get the last redirected url and pass it again in the curl to get the html content.

/**
* Grabs Meta Data For The Link
* @author Manish Jangir
* @copyright 2013 Manish Jangir Production
*/
ini_set('max_execution_time', 80);

function get_web_page( $url, $type="url" )
{
    $options = array(
    CURLOPT_RETURNTRANSFER => true,     // return web page
    CURLOPT_HEADER         => false,    // don't return headers
    CURLOPT_FOLLOWLOCATION => true,     // follow redirects
    CURLOPT_ENCODING       => "",       // handle all encodings
    CURLOPT_USERAGENT      => "spider", // who am i
    CURLOPT_AUTOREFERER    => true,     // set referer on redirect
    CURLOPT_CONNECTTIMEOUT => 120,      // timeout on connect
    CURLOPT_TIMEOUT        => 120,      // timeout on response
    CURLOPT_MAXREDIRS      => 10,       // stop after 10 redirects
);

    $ch = curl_init( $url );
    curl_setopt_array( $ch, $options );
    $content = curl_exec( $ch );
    $finalUrl = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
    curl_close( $ch );

    if($type == "url") {
         return $finalUrl;
    } else if($type="content") {
         return $content;
    }
}

//Check if the given url is a valid link or not
if (preg_match("/\b(?:(?:https?|ftp)://|www\.)[-a-z0-9+&@#/%?=~_|!:,.;]*[-a-z0-9+&@#/%=~_|]/i", $_GET['url'])) {
    require 'simple_html_dom.php';
    $url = get_web_page($_GET['url']); //Get last redirected url
    $html = str_get_html(get_web_page($url,'content')); //Get HTML Of That Link

    $data = array(); //Prepare data array to show
    $data['title'] = $html->find('title',0)->innertext; //Title Of Page
    $data['url'] = $url; //Url Of Page

    Set description if desc meta tag found else grab a little plain text of the page
    if($html->find('meta[name="description"]',0)) {
        $data['description'] = $html->find('meta[name="description"]',0)->content;
    } else {
        $data['description'] = $html->find('body',0)->plaintext;
    }

   //Get all images found on this page
   $jpgs = $html->find('img[src$=jpg],img[src$=png]');
   foreach ($jpgs as $jpg) {
        $data['images'][] = $jpg->getAttribute('src'); //Store them in an image key of the array
   }

   //Set Total Images for pagination in the widget
   $data['total_images'] = !empty($data['images']) ? count($data['images']) : 0;

   //Finally encode the data in JSON format and print it
   echo json_encode($data);
}
else {
   echo false; //Return false if provided url from textarea is not valid
}
?>

After getting the html content, We iterate the html tags through dom parser and get the meta title, meta description and all images found on the page. Encoded the whole grabbed data in JSON format and used in the widget with ajax response.

3. Script.js :

This is the javascript written to handle whole client side functionality such as sending request and getting ajax response, showing it to appropriate div and image pagination.

/**
 * Javascript Document For The Application
 * @author Manish Jangir
 * @copyright 2013 Manish Jangir Production
 */
$(document).on('keyup','#status',function(e){
	if(e.which == 32) {
		getUrlData();
	}
	e.stopImmediatePropagation();
})

function getUrlData() {
var url = $.trim($('#status').val());
if(url != '') {
	$('#fb_loader').show();
	resetEverything();
	$.ajax({
		url: "get.php",
		data:"url="+url,
		success:function(data) {
			if(!data) {
				$('#fb_loader').hide();
				alert("Invalid Url");
			} else {
				var data = $.parseJSON(data);
				$('span#preview_title_span').text(data.title);
				$('#preview_url').text(data.url);
				$('span#preview_desc_span').text(data.description);
				var imgcnt = 1;
				if(data.images){
					$.each(data.images, function(key,value) {
						$('</pre>
<img alt="" />
<pre>').attr({
							src:value,
							id:'linkimage_'+imgcnt,
							style:'width: 130px; height: auto; display: none;'
						}).appendTo($('').attr({
							href:data.url,
							target:'_blank'
						}).appendTo($('#preview_images_div')));
						imgcnt++;
					});
				}
				$('#linkimage_1').css('display','block');
				$('#currimg').val(1);
				$('#totalimg').val(data.total_images);
				if(data.total_images > 0) {
					$('.image_count').text('1 of '+data.total_images);
				} else {
					$('.image_count').text('No images found');
				}
				$('#preview_div').fadeIn('slow');
				$('#fb_loader').hide();
			}
		}
	});
}
}

function resetEverything() {
	$('#preview_images_div').html('');
	$('#next_image').removeClass().addClass('next_button');
	$('#prev_image').removeClass('prev_button').addClass('prev_button_disabled');
	$('#preview_div').hide('fast');
}

$(document).on('click','#closebtn',function(){
	resetEverything();
})

$(document).on('click','#next_image,#prev_image',function() {
	if($(this).hasClass('next_button')) {
		var current = parseInt($('#currimg').val())+1;
		curimg = current > $('#totalimg').val() ? $('#totalimg').val() : current;
	} else if($(this).hasClass('prev_button')) {
		var current = parseInt($('#currimg').val())-1;
		curimg = current < 1 ? 1 : current;
	}
	$('#currimg').val(curimg);
	$('#preview_images_div img').hide();
	$('#preview_images_div #linkimage_'+curimg).show();
	$('.image_count').text(curimg+' of '+$('#totalimg').val());

	$('#next_image').removeClass().addClass('next_button');
	$('#prev_image').removeClass().addClass('prev_button');
	if(curimg == $('#totalimg').val()) {
		$('#next_image').removeClass().addClass('next_button_disabled');
	}
	if(curimg == 1) {
		$('#prev_image').removeClass().addClass('prev_button_disabled');
	}
});

$(document).on('click','.no_thumb',function(e) {
	$('#preview_images_div').show();
	if($('#noThumb').is(':checked')) {
		$('#preview_images_div').hide();
	}
});

4. index.php :

This is nothing or more than everything. The main index file to bind whole functionality in a single bundle.

Facebook Style Link Preview Demo
<script type="text/javascript" src="jquery-1.10.1.min.js"></script><script type="text/javascript" src="script.js"></script></pre>
<div id="fbpreview">
<div id="fb_loader" style="display: none;"><img src="loading.gif" alt="" /></div>
<div id="fblink_outer">
<div id="textarea"><textarea id="status"></textarea>
<input id="totalimg" type="hidden" value="" />
<input id="currimg" type="hidden" value="" /></div>
<div id="preview_div">
<div id="preview_images_div"></div>
<div id="preview_content">
<div id="preview_title"><span id="preview_title_span"> </span></div>
<div id="preview_url"></div>
<div id="preview_desc"><span id="preview_desc_span"> </span></div>
<div id="preview_buttons">
<div id="prev_image"></div>
<div id="next_image"></div>
<div>8 of 32</div>
<div>Choose a thumbnail</div>
</div>
<input id="noThumb" style="display: block;" type="checkbox" />
<label id="no_thumb_text" for="noThumb">No thumbnail</label></div>
<div id="closebtn" title="Remove"></div>
</div>
<div id="button_div"><button name="" value="" type="button">Post</button></div>
</div>
</div>
<pre>

5. Style.css :

Don’t ask me about this bloody file, that wasted too much time in writing the basic markup for the application. I spent a lot of time time to copy the styles from facebook to make it exactly like that. And I hope, I’ve done it perfectly. What you think??

/* CSS Document */
body {
margin:0;
padding:0;
font-family:Arial, Helvetica, sans-serif;
font-size:12px;
color:#666666;
}

#fbpreview {
width:513px;
margin: 20px auto auto;
}

#fblink_outer {
border: 1px solid #B4BBCD;
position: relative;
margin:10px auto;
float:left;
width:100%;
clear:right;
}

#fblink_outer #textarea {
float: left;
height: 54px;
padding: 5px;
width: 99%;
}

#textarea textarea{
border:none;
resize: none;
outline: 0 none;
width:100%;
line-height: 1.28;
font-family:'lucida grande',tahoma,verdana,arial,sans-serif;
color:#333333;
font-size: 13px;
height: 16px;
padding: 0;
resize: none;
overflow: hidden;
height:99%;
vertical-align: bottom;
}

#fblink_outer #button_div {
background-color: #F2F2F2;
border-top: 1px solid #E6E6E6;
float: left;
padding: 2px;
width: 99%;
}

button.css3button {
background-color: #5B74A8;
background-image: url("backgrounds.png");
background-position: 0 -49px;
background-repeat: no-repeat;
border: 1px solid #1A356E;
color: #FFFFFF;
cursor: pointer;
display: inline-block;
float: right;
font-family: "lucida grande",tahoma,verdana,arial,sans-serif;
font-size: 11px;
font-weight: bold;
line-height: 13px;
padding: 4px 16px;
text-align: center;
text-decoration: none;
vertical-align: top;
white-space: nowrap;
}

#fb_loader {
float: right;
height: 11px;
width: 16px;
}

#preview_div {
border-top: 1px dashed #CCCCCC;
float: left;
font-family: "lucida grande",tahoma,verdana,arial,sans-serif;
font-size: 12px;
padding: 8px 5px;
color:#000000;
width: 98%;
display:none;
}

#preview_images_div {
float: left;
margin-right: 12px;
width: 130px;
}

#closebtn {
background-image: url("close.png");
background-position: left 0;
background-repeat: no-repeat;
cursor: pointer;
float: right;
height: 15px;
width: 15px;
}

#closebtn:hover {
background-image: url("close.png");
background-position: left -32px;
background-repeat: no-repeat;
}

#preview_content {
float: left;
font-size: 11px;
width: 340px;
}

#preview_title {
font-size: 12px;
font-weight: bold;
margin: 0 auto 2px;
}

#preview_url {
color: #666666;
font-size: 11px;
margin: 0 auto 9px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 340px;
}

#preview_desc {
margin: 0 auto 8px;
width:340px;
}

#preview_desc_span,#preview_title_span {
cursor: pointer;
}

#preview_buttons {
margin-bottom: 39px;
margin-top: 12px;
width: 290px;
}

.prev_button:active {
background-image: url("selectionButtons.gif");
background-position: -100px -83px;
background-repeat: no-repeat;
}
.prev_button {
background-image: url("selectionButtons.gif");
background-position: 0 -83px;
background-repeat: no-repeat;
cursor: pointer;
float: left;
height: 22px;
width: 25px;
}

.prev_button_disabled {
background-image: url("selectionButtons.gif");
background-position: -50px -83px;
background-repeat: no-repeat;
float: left;
height: 22px;
width: 25px;
}

.next_button:active {
background-image: url("selectionButtons.gif");
background-position: -125px -83px;
background-repeat: no-repeat;
}

.next_button {
background-image: url("selectionButtons.gif");
background-position: -25px -83px;
background-repeat: no-repeat;
cursor: pointer;
float: left;
height: 22px;
margin-right: 10px;
width: 25px;
}

.next_button_disabled {
background-image: url("selectionButtons.gif");
background-position: -75px -83px;
background-repeat: no-repeat;
float: left;
height: 22px;
margin-right: 10px;
width: 25px;
cursor:default;
}

.image_count {
float: left;
font-size: 10px;
margin-right: 10px;
margin-top: 5px;
}

.choose_thumbnail {
color: #999999;
float: left;
font-size: 10px;
margin-top: 5px;
}

.no_thumb_checkbox {
float: left;
margin-left: 0;
margin-right: 4px;
vertical-align: -2px;
}

#no_thumb_text {
cursor:pointer;
color: #333333;
font-size: 11px;
float: left;
margin-top: 3px;
width: 270px;
}