Events and listeners – Laravel

In this tutorial, we are going to teach you how you can implement events and listeners in Laravel. Events are created and multiple listeners can be assigned to them. Listeners are responsible for performing an action like saving the data in the database, sending an email, etc. In your controllers, you only need to emit an event and all the listeners associated with it will be called automatically.

Video tutorial:

Creating an event

First, we are going to create an event. Open terminal or command prompt at the root of your Laravel project and run the following command:

php artisan make:event MyEvent

A new file will be created at app/Events/MyEvent.php. Now place all the variables in it that you will be passed while calling this event.

<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class MyEvent
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $my_value;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct($my_value)
    {
        $this->my_value = $my_value;
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
        return new PrivateChannel('channel-name');
    }
}

Creating a Listener

Now we need to create a listener that will be called when the above event is emitted. Open the terminal and run the following command in it:

php artisan make:listener MyListener

A new file will be created at app/Listeners/MyListener.php. It will have the following content:

<?php

namespace App\Listeners;

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;

class MyListener
{
    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Handle the event.
     *
     * @param  object  $event
     * @return void
     */
    public function handle($event)
    {
        dd($event->my_value);
    }
}

That $event variable will hold all the values sent from the event class (MyEvent, in this case).

Attach Listeners to Event

The event and listeners are created. Now is the time to tell the Laravel framework to which listeners should be called when a specific event is called. Goto app/Providers/EventServiceProvider.php and include the event and listener at the top:

use App\Events\MyEvent;
use App\Listeners\MyListener;

Then update the $listen array in it:

/**
 * The event listener mappings for the application.
 *
 * @var array
 */
protected $listen = [
    MyEvent::class => [
        MyListener::class
    ]
];

Now, whenever a MyEvent event is emitted, the MyListener listener will automatically be called.

Emitting an Event

Emitting an event in your Laravel project is very easy. You just need to include your event at the top:

use App\Events\MyEvent;

Then whenever you need to call it, you can simply call it like this:

event(new MyEvent("adnan-tech.com"));

At this point, the event will be emitted and the listener will be called. But we need to find a way to check if the listener is actually being called. We can simply do dd() inside listener’s handle() function.

Financial Ledger in Node JS, Mongo DB, and Vue JS

A Financial Ledger script is created in Node JS, Mongo DB, and Vue JS. A financial ledger is used to track your daily finances, for example, your income, your expenses, etc. This script allows you to track your total income, total spending, and your current total balance. They are referred to as “stats” in the script. You can debit the entry by entering the amount that is negative. And you can credit an entry by simply writing its amount.

We have used the HTML and CSS code from this codepen. We have made it dynamic by using the latest technologies like, Vue JS for front-end, Node JS for back-end, and Mongo DB as the database. It has complete CRUD (Create, Read, Update, Delete) operation. It has the following features:

  1. Insert data in Mongo DB using Node JS.
  2. Fetch all documents from Mongo DB.
  3. Update specific document in Mongo DB.
  4. Delete documents from Mongo DB.
  5. Case-insensitive search in Mongo DB documents.
  6. Search by sub-string from Mongo DB document’s keys.
  7. Realtime data insert.
  8. Real-time update.
  9. Realtime deletes.
  10. “Load more” capability.
  11. Datetimepicker library.
  12. EJS templating engine.
  13. Express-formidable for handling form fields.
  14. Skip, limit and sort in Mongo DB.

Video tutorial:

1. Insert Data in Mongo DB using Node JS

An interface that allows you to enter the values (time, description, and amount), created in Vue JS. It binds those input field values to the Vue JS model. To enter the time, we are using a library datetimepicker. We are calling an AJAX request to the Node JS server with the input field values as FormData object when the form submits. That will simply insert a new document in Mongo DB collection “entries”. When the data is inserted, it is also inserted in the Vue JS array. This allows the new data to be appended automatically without having to refresh the page. The stat values automatically are updated based on if the entry is debit or credit.

2. Fetch All Documents from Mongo DB

An AJAX request is sent from the client to view all the data from the database. The server will return all the documents sorting from latest to oldest i.e. newest records will be shown first. The sorting is performed based on the date field selected during insertion. When the data is returned, it is concatenated in the Vue JS array that allows the user to view the data when the page loads. When the data is fetched, the stats are also updated automatically.

3. Update Specific Document in Mongo DB

To update specific documents, we are using Mongo DB auto-generated ObjectId field “_id”. This allows you to search the document, then we can perform the update query. To update the document, we are using HTML’s contenteditable attribute that allows you to edit the HTML node innerHTML by just typing in it. When the content of that div changes, we are sending an AJAX request to the Node JS server that performs the update query. If you update the amount value, then the stats will also get a change in real-time.

4. Delete Documents from Mongo DB

Deleting an entry will remove the document from the Mongo DB collection too. The row will be removed and the stats will be updated too.

5. Case insensitive Search Mongo DB

You can search the entries by description you have put during the insertion, or you can search the entries by either they are debited or credited. For example, you can write “debit” in the search box and it will show all the entries that are debited. The same goes for the “credit” search query.

6. Sub-string Search Mongo DB

While searching by description, you do not need to know the exact words. You can type the part of the description you have remembered and it will search the record anyway.

7. Realtime Mongo DB Data Insert

When a new entry is added, it is automatically prepended in the list using Vue JS. You do not have to refresh the page to view the new entries.

8. Realtime Update in Mongo DB

The same goes for the updation. You can update the entry’s description or the amount by simply typing in its box. When you start typing it automatically gets updated in the Mongo DB too. Also, the stats values also get updated if there is any change in the amount.

9. Realtime Delete in Mongo DB

With each entry, there is a minus (-) sign. On clicking that button, it will remove the entry from the list and also from the Mongo DB. The stats also gets updated accordingly.

10. Load More Capability

In the real-world, when you are creating a financial ledger, there will be hundreds of entries. So loading all entries when the page loads will slow down your application. So what we do is, load a few entries when the page loads. Then we show a button called “Load more”. Upon clicking that button, we will fetch the next entries from Mongo DB and so on. We will be using AJAX to fetch more entries.

11. Datetimepicker Javascript

In the financial ledger, it is important to enter the date the entry was added. So on the web, we have a library called datetimepicker by XDSoft. We are using this library to enter dates and times easily.

12. EJS Templating Engine in Node JS

In Node JS, to render files, there is an engine called EJS. It is used to render HTML files. The rendered files will have an extension “.ejs” instead of “.html” or “.php”.

13. Express Formidable Module in Node JS

In Node JS, to handle FormData object sent using AJAX, we are using a Node JS module named express-formidable. As the complete app is in Javascript, so there are a lot of AJAX requests in this app. Each AJAX request will send a FormData object to send values that the server will process.

14. Skip, Limit and Sort in Mongo DB

While fetching the records from Mongo DB, you can skip the records that are already been displayed to the user. Similarly, to load the data faster, you can limit the number of records to be fetched from Mongo DB in one request. To display the latest entries added, you can sort the records fetched from Mongo DB.

You can download the required assets from here:

Socket IO emit an event to specific users – Node JS

Introduction

Sockets are used for real-time communication. They are now being used in chat apps, team collaboration tools, and many more. Socket IO emit events to the receivers and the receivers are constantly listening to that event. When the event is received on the client-side, they can perform the necessary action. You can attach as many event listeners as you want and perform different actions for each event.

Users are connected with a Node JS server using a client-side library called Socket IO. Users can also join the room which will be helpful if you are creating a group chat app. There are 4 ways in which socket events are fired.

  1. Send event to all connected users, including the sender.
  2. Send event to all users, except the sender.
  3. Emit event to all users in a room.
  4. Send event to specific users.

In this tutorial, we will be covering the 4th part i.e. send socket events to specific users.

Video tutorial:

Problem

Suppose you have a chat app where you want 2 people to have a private chat. Now you want to have a real-time effect i.e. to show the new messages without having to refresh the page. This requires sockets that send the data in real-time. And we can show the data on the client-side in real-time too. Now when a sender sends a message to a specific user, we need to send the socket event to that specific user only.

Solution

We will create a simple script that allows us to send events to a specific user only. You can then integrate and customize that logic in your project. First, you need to download and install Node JS. You also need to download the Socket IO JS client-side library. We will have a simple database from where we can show all the users in a list, with a button to send an event to that user only. So we need to create a database with a simple users table, you can use your own database as well.

Database

In your phpMyAdmin, create a database named “send_socket_event_to_specific_users”. In that database, create a users table:

CREATE TABLE `users` (
  `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
  `name` text NOT NULL
);

Add few rows in that table so we can show them in a list or a table.

INSERT INTO `users` (`id`, `name`) VALUES
(1, 'Adnan'),
(2, 'Afzal'),
(3, 'John'),
(4, 'Doe');

Then in your index.php, show all users.

<?php
	$conn = new PDO("mysql:host=localhost:3306;dbname=send_socket_event_to_specific_users", "root", "");
	$sql = "SELECT * FROM users";
	$result = $conn->prepare($sql);
	$result->execute([]);
	$users = $result->fetchAll();
?>

<table>
	<thead>
		<tr>
			<th>ID</th>
			<th>Name</th>
			<th>Action</th>
		</tr>
	</thead>

	<tbody>
		<?php foreach ($users as $user): ?>
			<tr>
				<td><?php echo $user['id']; ?></td>
				<td><?php echo $user['name']; ?></td>
				<td>
					<form method="POST" onsubmit="return sendEvent(this);">
						<input type="hidden" name="id" value="<?php echo $user['id']; ?>" required />
						<input type="submit" value="Send Message" />
					</form>
				</td>
			</tr>
		<?php endforeach; ?>
	</tbody>
</table>

It will show all users in a table with a button to send message. Now when the page loads, we need to get the ID of the user, you can also get it from PHP sessions.

Include Socket IO library

Before that, we need to include the Socket IO JS library. You can download it from here.

<script src="socket.io.js"></script>

<script>
	var userId = prompt("Enter user ID");

	var socketIO = io("http://localhost:3000");
	socketIO.emit("connected", userId);
</script>

It will store your ID in userId variable. And it will connect with Node JS server and emit an event “connected” with your ID.

Now we need to create a simple Node server. Create an empty folder and create a file named “server.js” in it. Then open the CMD in that folder and run the following commands one-by-one:

npm init
npm install express http socket.io mysql
npm install -g nodemon
nodemon server.js

Write the following code in your server.js file:

var express = require("express");
var app = express();

var http = require("http").createServer(app);
var socketIO = require("socket.io")(http, {
	cors: {
		origin: "*"
	}
});

var users = [];

socketIO.on("connection", function (socket) {

	socket.on("connected", function (userId) {
		users[userId] = socket.id;
	});

    // socket.on("sendEvent") goes here
});

http.listen(process.env.PORT || 3000, function () {
	console.log("Server is started.");
});

This will start the server at port 3000, creates a users array and store all connected user’s socket ID in it.

Send event using socket IO emit function

Back in index.php, we need to create a JS function to send the event when the user click the “Send message” button:

function sendEvent(form) {
	event.preventDefault();

	var message = prompt("Enter message");
	socketIO.emit("sendEvent", {
		"myId": userId,
		"userId": form.id.value,
		"message": message
	});
}

Now in server.js, we need to listen to that event and send the message to that user only. But before that, we need to include mysql module because the user’s names are stored in mysql database. At the top of your server.js:

var mysql = require("mysql");
var connection = mysql.createConnection({
	host: "localhost",
	port: 3306,
	user: "root",
	password: "",
	database: "send_socket_event_to_specific_users"
});

connection.connect(function (error) {
	console.log("Database connected: " + error);
});

And after the socket connected event:

socket.on("sendEvent", async function (data) {
	connection.query("SELECT * FROM users WHERE id = " + data.userId, function (error, receiver) {
		if (receiver != null) {
			if (receiver.length > 0) {

				connection.query("SELECT * FROM users WHERE id = " + data.myId, function (error, sender) {
					if (sender.length > 0) {
						var message = "New message received from: " + sender[0].name + ". Message: " + data.message;
						socketIO.to(users[receiver[0].id]).emit("messageReceived", message);
					}
				});
			}
		}
	});
});

This will search the sender and receiver by ID, and emit the event to the receiver with the name of the sender.

Listen to socket IO events

Now we need to listen to that event in our index.php and show a message in a list when that event is received. First, create a ul where all messages will be displayed:

<ul id="messages"></ul>

Then attach that event in JS:

socketIO.on("messageReceived", function (data) {
	var html = "<li>" + data + "</li>";
	document.getElementById("messages").innerHTML = html + document.getElementById("messages").innerHTML;
});

So that’s how you can use the socket IO emit function to send the event to a specific user only.

Check out realtime chat app tutorial using socket IO.

[wpdm_package id=’1295′]

Use SweetAlert confirmation dialog – HTML & Javascript

In this tutorial, we are going to show you, how you can show a sweetalert confirmation dialog when submitting a form. For example, if you have a form that when submits delete the data from the database. In that case, you must verify with the user because he might click that button by accident. So you can show a nice dialog using the Sweetalert library. Suppose you have the following form:

<form method="POST" action="do-something.php" onsubmit="return submitForm(this);">
	<input type="text" name="name" />
	<input type="submit" />
</form>

When the form submits, we are calling a Javascript function submitForm and passing the form as a parameter. Then you need to download the Sweetalert library from here. After downloading, paste that into your project and include it in your HTML file:

<script src="sweetalert.min.js"></script>

Now, we can create that Javascript function that will ask for confirmation. Once confirmed, it will submit the form.

<script>
	function submitForm(form) {
		swal({
			title: "Are you sure?",
			text: "This form will be submitted",
			icon: "warning",
			buttons: true,
			dangerMode: true,
		})
		.then(function (isOkay) {
			if (isOkay) {
				form.submit();
			}
		});
		return false;
	}
</script>

At this point, if you submit the form, you will see a SweetAlert confirmation dialog first. All the form fields will be submitted correctly on the server-side. You can check it by printing out all the values received from the form:

<?php
print_r($_POST);

Checkout 10 javascript libraries for every web project.

Email download link of a file – PHP and MySQL

In this tutorial, we are going to show you, how you can email a download link of a file to a user when they request to download files from your website. We are going to create a system where you can upload files and the user will be able to download them. But before downloading, they must enter their email address. The link to download the file will be emailed to the user. In this way, you will get a large number of emails. This will help you with a very large email list.

Prevent direct access to files

First, we are going to prevent users from downloading the files directly from the URL. We will be storing all our uploaded files in the “uploads” folder. So, create a new folder named “uploads” at the root of your project. Create a “.htaccess” file in this folder. In this file, write the following single line:

deny from all

This will gives a “403 Forbidden” error whenever someone tries to access the file directly from the browser.

Upload files

Now we are going to create a form that will allow you to upload files. You can create this form in your admin panel because usually, the administrator of the website can upload files.

<form method="POST" action="index.php" enctype="multipart/form-data">
    <input type="file" name="file" required />
    <input type="submit" name="upload" value="Upload" />
</form>

Create a database named “collect_emails_while_downloading_files” in your phpMyAdmin. Or you can use your own database if you already have one. In this database, you need to create a table where we will store the path and name of all uploaded files.

CREATE TABLE files (
	id INTEGER(11) PRIMARY KEY AUTO_INCREMENT NOT NULL,
	file_name TEXT NOT NULL,
    file_path TEXT NOT NULL
);

Then we need to save the selected file in the uploads folder and its path in the files table in the MySQL database. We will be using PHP PDO prepared statements that will help us from preventing the SQL injection.

<?php

    // connect with database
    $conn = new PDO("mysql:host=localhost:8889;dbname=collect_emails_while_downloading_files", "root", "root");

    // check if form is submitted, for admin panel only
    if (isset($_POST["upload"]))
    {
        // get the file
        $file = $_FILES["file"];
        
        // make sure it does not have any error
        if ($file["error"] == 0)
        {
            // save file in uploads folder
            $file_path = "uploads/" . $file["name"];
            move_uploaded_file($file["tmp_name"], $file_path);

            // save file path in database, prevent SQL injection too
            $sql = "INSERT INTO files(file_name, file_path) VALUES (:file_name, :file_path)";
            $result = $conn->prepare($sql);
            $result->execute([
                ":file_name" => $file["name"],
                ":file_path" => $file_path
            ]);
        }
        else
        {
            die("Error uploading file.");
        }
    }

    // get all files
    $sql = "SELECT * FROM files ORDER BY id DESC";
    $result = $conn->query($sql);
    $files = $result->fetchAll();

?>

Refresh the page and try uploading a file. You will see it will be saved in your uploads folder and its path and name will be stored in the files table. Also, try accessing that from the browser directly, it will give you a 403 Forbidden error.

Show all uploaded files

In the previous step, we ran a query to fetch all files sorting from latest to oldest. Now we need to show them on a table.

<table>
    <thead>
        <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Action</th>
        </tr>
    </thead>

    <tbody>
        <?php foreach ($files as $file): ?>
            <tr>
                <td><?php echo $file["id"]; ?></td>
                <td><?php echo $file["file_name"]; ?></td>
                <td>
                    <!-- button to download file -->
                    <form method="POST" action="check-email.php" onsubmit="return onFormSubmit(this);">
                        <input type="hidden" name="id" value="<?php echo $file['id']; ?>" required />
                        <input type="hidden" name="email" />
                        <input type="submit" value="Download" />
                    </form>
                </td>
            </tr>
        <?php endforeach; ?>
    </tbody>
</table>

This will show all files in a table with a button to download. When that button is clicked, we need to get the user’s email address so we can email him a download link for that file. That link will be valid for that file and for that email only.

<script>
    function onFormSubmit(form) {
        // get email address and submit
        var email = prompt("Enter your email:", "");
        if (email != null && email != "") {
            form.email.value = email;
            return true;
        }
        return false;
    }
</script>

Send download link in email

We will be using the PHPMailer library to send emails. Open CMD at the root folder of your project and run the following command. Make sure you have the composer downloaded and installed in your system.

composer require phpmailer/phpmailer

Create a table in your database that will store all the download requests of files sent by users. Run the following query in your database in phpMyAdmin:

CREATE TABLE download_requests (
	id INTEGER(11) PRIMARY KEY AUTO_INCREMENT NOT NULL,
	file_id INTEGER(11) NOT NULL,
	email TEXT NOT NULL,
	token TEXT NOT NULL,
	CONSTRAINT fk_file_id FOREIGN KEY (file_id) REFERENCES files (id) ON DELETE CASCADE ON UPDATE CASCADE
);

Create a file named “check-email.php” and write the following code in it. It will send the email to the user and also will store the data in the above created table.

<?php

    // composer require phpmailer/phpmailer

    // include PHPMailer library
    use PHPMailer\PHPMailer\PHPMailer;
    use PHPMailer\PHPMailer\SMTP;
    use PHPMailer\PHPMailer\Exception;
    
    require 'vendor/autoload.php';

    // connect with database
    $conn = new PDO("mysql:host=localhost:8889;dbname=collect_emails_while_downloading_files", "root", "root");

    // get all form values
    $id = $_POST["id"];
    $email = $_POST["email"];

    // generate a unique token for this email only
    $token = time() . md5($email);

    // get file from database
    $sql = "SELECT * FROM files WHERE id = :id";
    $result = $conn->prepare($sql);
    $result->execute([
        ":id" => $id
    ]);
    $file = $result->fetch();
    
    if ($file == null)
    {
        die("File not found");
    }

    // insert in download requests, prevent SQL injection too
    $sql = "INSERT INTO download_requests(file_id, email, token) VALUES (:id, :email, :token)";
    $result = $conn->prepare($sql);
    $result->execute([
        ":id" => $id,
        ":email" => $email,
        ":token" => $token
    ]);

    // send email to user
    $mail = new PHPMailer(true);

    try
    {
        $mail->SMTPDebug = 0;
        $mail->isSMTP();
        $mail->Host = 'smtp.gmail.com';
        $mail->SMTPAuth = true;
        $mail->Username = 'your_email@gmail.com';
        $mail->Password = 'your_password';
        $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
        $mail->Port = 587;

        $mail->setFrom('adnan@gmail.com', 'Adnan');
        $mail->addAddress($email); // Add a recipient
        $mail->addReplyTo('adnan@gmail.com', 'Adnan');

        // Content
        $mail->isHTML(true);
        $mail->Subject = 'Download your files';

        // mention download link in the email
        $email_content = "Kindly click the link below to download your files: <br />";
        $base_url = "http://localhost:8888/tutorials/collect-emails-while-downloading-files-php-mysql";
        $email_content .= "<a href='" . $base_url . "/download.php?email=" . $email . "&token=" . $token . "'>" . $file['file_name'] . "</a>";
        $mail->Body = $email_content;

        $mail->send();
        echo '<p>Link to download files has been sent to your email address: ' . $email . '</p>';
    }
    catch (Exception $e)
    {
        die("Message could not be sent. Mailer Error: " . $mail->ErrorInfo);
    }

Make sure to change the base URL at line 68. Also, change your email address and password on lines 53 and 54 respectively. This email will be used to send the emails. Goto this link and enable a less secure apps option for that email address.

Enable less secure apps – Gmail

Test the code now. You will be able to see a list of all uploaded files with a button to download. When clicked, it will show a prompt where you can enter your email address. When clicked “OK”, it will send an email with a download link and also it will store it in the “download_requests” table in the MySQL database.

In your email, you will see a link to download the file. But right now it will give a 404 Not found error because the file is not created yet.

Download the file from email download link

Create a file named “download.php” and write the following code in it. This will directly download the file into your system.

<?php

    // connect with database
    $conn = new PDO("mysql:host=localhost:8889;dbname=collect_emails_while_downloading_files", "root", "root");

    // get variables from email
    $email = $_GET["email"];
    $token = $_GET["token"];

    // check if the download request is valid
    $sql = "SELECT *, download_requests.id AS download_request_id FROM download_requests INNER JOIN files ON files.id = download_requests.file_id WHERE download_requests.email = :email AND download_requests.token = :token";
    $result = $conn->prepare($sql);
    $result->execute([
        ":email" => $email,
        ":token" => $token
    ]);
    $file = $result->fetch();

    if ($file == null)
    {
        die("File not found.");
    }

    // download the file
    $url_encoded_file_name = rawurlencode($file["file_name"]);
    $file_url = "http://localhost:8888/tutorials/collect-emails-while-downloading-files-php-mysql/uploads/" . $url_encoded_file_name;
    // die($file_url);

    // headers to download any type of file
    header('Content-Description: File Transfer');
    header('Content-Type: application/octet-stream');
    header('Content-Disposition: attachment; filename="' . $file["file_name"] . '"');
    header('Expires: 0');
    header('Cache-Control: must-revalidate');
    header('Pragma: public');
    header('Content-Length: ' . filesize($file["file_path"]));
    readfile($file["file_path"]);

Make sure to change your base URL at line 26. Now you can run a complete test cycle again. Upload a file, click the download button, and enter your email. Check your email and then click the download link from the email and the file will be downloaded. Verify the file that is downloaded correctly.

So that’s how you can collect a large number of emails by allowing people to simply download files. You can create a very large email list from it.

Learn how to send attachment with an email using PHP.

Attach files in email – PHP

[wpdm_package id=’1281′]

Web crawler in Node JS and Mongo DB

In this article, we are going to create a web crawler using Node JS and Mongo DB. It will take a URL as an input and fetch all the anchor tags, headings, and paragraphs. You can add more features to it if you want.

Requirements

Make sure you have the following things installed in your system:

  1. Node JS
  2. Mongo DB
  3. Code Editor (Sublime Text etc.)

Video tutorial:

Setup the Project

First, create an empty folder anywhere in your system. Create a file named server.js in that folder. Open CMD in that folder by running the following command:

cd "path_of_your_folder"

We are going to need multiple modules for this web crawler. So, install them from the command:

npm install express http ejs socket.io request cheerio express-formidable mongodb htmlspecialchars node-html-parser

Now we explain the reason for the installation of the above modules.

  1. express framework is used for routing.
  2. http is used to run HTTP requests.
  3. ejs is a template engine used for rendering HTML files.
  4. socket.io is used for realtime communication.
  5. request is used to fetch content of web page.
  6. cheerio is used for jQuery DOM manipulation.
  7. express-formidable to get values from FormData object.
  8. mongodb will be our database.
  9. htmlspecialchars is used to convert HTML tags into entities.
  10. node-html-parser to convert the HTML string into DOM nodes.

After all the modules are installed, run the following command to start the server:

npm install -g nodemon
nodemon server.js

Start the server

Open your server.js and write the following code in it to start the server at port 3000.

var express = require("express");
var app = express();
var http = require("http").createServer(app);
http.listen(3000, function () {
    console.log("Server started running at port: 3000");
});

Now your project will be up and running at http://localhost:3000/

Connect Node JS with Mongo DB

To connect Node JS with Mongo DB, first, we need to create an instance of Mongo DB client in our server.js. Place following lines before http.listen function.

var mongodb = require("mongodb");
var mongoClient = mongodb.MongoClient;
var ObjectID = mongodb.ObjectID;
var database = null;

Now write the following code inside the http.listen callback function.

mongoClient.connect("mongodb://localhost:27017", {
    useUnifiedTopology: true
}, function (error, client) {
    if (error) {
        throw error;
    }
    database = client.db("web_crawler");
    console.log("Database connected");
});

If you check your CMD now, you will see the message “Database connected”.

Crawl the web page

Now we need to create a form to get the URL as an input. So first we will tell our express app that we will be using EJS as our templating engine. And all our CSS and JS files will be inside the public folder. Place following lines before http.listen function.

// server.js
app.set('view engine', 'ejs');
app.use("/public", express.static(__dirname + "/public"));

Now create 2 folders at the root of your project, “public” and “views”. Download the latest jQuery, Bootstrap, DataTable, and Socket IO libraries and placed their files inside the public folder. Create a new file named index.ejs inside views folder. Create a GET route in our server.js when the Mongo DB is connected.

app.get("/", async function (request, result) {
    result.render("index");
});

If you access your project from the browser now, you will see an empty screen. Open your index.ejs and write the following code in it:

<link rel="stylesheet" href="/public/bootstrap.css" />
<div class="container" style="margin-top: 150px;">
    <div class="row">
        <div class="col-md-8">
            <form method="POST" onsubmit="return crawlPage(this);">
                <div class="form-group">
                    <label>Enter URL</label>
                    <input type="url" name="url" class="form-control" required />
                </div>
                <input type="submit" name="submit" value="Crawl" class="btn btn-info" />
            </form>
        </div>
    </div>
</div>
<script src="/public/jquery-3.3.1.min.js"></script>
<script src="/public/bootstrap.js"></script>
<style>
    body {
        background: linear-gradient(0deg, #00fff3, #a5a5a5);
    }
</style>

You will now see a simple form with an input field and a submit button. In that input field, you can enter the URL of the page you wanted to crawl. Now we need to create a Javascript function that will be called when the form is submitted. In that function, we will call an AJAX request to the Node JS server.

<script>
    function crawlPage(form) {
        var ajax = new XMLHttpRequest();
        ajax.open("POST", "/crawl-page", true);
        ajax.onreadystatechange = function () {
            if (this.readyState == 4) {
                if (this.status == 200) {
                    // console.log(this.responseText);
                    var data = JSON.parse(this.responseText);
                    // console.log(data);
                }
            }
        };
        var formData = new FormData(form);
        ajax.send(formData);
        return false;
    }
</script>

Get the web page content

To fetch the content of the web page, first, we will use express-formidable as our middleware. Also, we will require the modules required to read the web page and convert its HTML into DOM nodes. Write the following lines before http.listen function.

const formidableMiddleware = require('express-formidable');
app.use(formidableMiddleware());
const requestModule = require("request");
const cheerio = require('cheerio');
var htmlspecialchars = require("htmlspecialchars");
var HTMLParser = require('node-html-parser');
var io = require("socket.io")(http, {
    "cors": {
        "origin": "*"
    }
});

After that, we will create a POST route to crawl the web page.

app.post("/crawl-page", async function (request, result) {
    var url = request.fields.url;
    crawlPage(url);
    
    result.json({
        "status": "success",
        "message": "Page has been crawled",
        "url": url
    });
});

Our web crawler runs in a separate function to crawl the web page. Then we will create the functions to crawl the web page and save its content in Mongo DB. Write the following functions at the top of your server.js file.

function getTagContent(querySelector, content, pageUrl) {
    var tags = content.querySelectorAll(querySelector);
    var innerHTMLs = [];
    for (var a = 0; a < tags.length; a++) {
        var content = "";
        var anchorTag = tags[a].querySelector("a");
        if (anchorTag != null) {
            content = anchorTag.innerHTML;
        } else {
            content = tags[a].innerHTML;
        }
        content = content.replace(/\s+/g,' ').trim();
        if (content.length > 0) {
            innerHTMLs.push(content);
        }
    }
    return innerHTMLs;
}
function crawlPage(url, callBack = null) {
    var pathArray = url.split( '/' );
    var protocol = pathArray[0];
    var host = pathArray[2];
    var baseUrl = protocol + '//' + host;
    io.emit("crawl_update", "Crawling page: " + url);
    requestModule(url, async function (error, response, html) {
        if (!error && response.statusCode == 200) {
            var $ = cheerio.load(html);
            // Get text 
            // console.log("------- with request module -------")
            // console.log($.text());
            // Get HTML 
            // console.log($.html());
            var page = await database.collection("pages").findOne({
                "url": url
            });
            if (page == null) {
                var html = $.html();
                var htmlContent = HTMLParser.parse(html);
                var allAnchors = htmlContent.querySelectorAll("a");
                var anchors = [];
                for (var a = 0; a < allAnchors.length; a++) {
                    var href = allAnchors[a].getAttribute("href");
                    var title = allAnchors[a].innerHTML;
                    var hasAnyChildTag = (allAnchors[a].querySelector("div") != null)
                        || (allAnchors[a].querySelector("img") != null)
                        || (allAnchors[a].querySelector("p") != null)
                        || (allAnchors[a].querySelector("span") != null)
                        || (allAnchors[a].querySelector("svg") != null)
                        || (allAnchors[a].querySelector("strong") != null);
                    if (hasAnyChildTag) {
                        continue;
                    }
                    if (href != null) {
                        
                        if (href == "#" || href.search("javascript:void(0)") != -1) {
                            continue;
                        }
                        var first4Words = href.substr(0, 4);
                        if (href.search(url) == -1 && first4Words != "http") {
                            if (href[0] == "/") {
                                href = baseUrl + href;
                            } else {
                                href = baseUrl + "/" + href;
                            }
                        }
                        anchors.push({
                            "href": href,
                            "text": title
                        });
                    }
                }
                io.emit("crawl_update", htmlspecialchars("<a>") + " tags has been crawled");
                var titles = await getTagContent("title", htmlContent, url);
                var title = titles.length > 0 ? titles[0] : "";
                io.emit("crawl_update", htmlspecialchars("<title>") + " tag has been crawled");
                var h1s = await getTagContent("h1", htmlContent, url);
                io.emit("crawl_update", htmlspecialchars("<h1>") + " tags has been crawled");
                var h2s = await getTagContent("h2", htmlContent, url);
                io.emit("crawl_update", htmlspecialchars("<h2>") + " tags has been crawled");
                var h3s = await getTagContent("h3", htmlContent, url);
                io.emit("crawl_update", htmlspecialchars("<h3>") + " tags has been crawled");
                var h4s = await getTagContent("h4", htmlContent, url);
                io.emit("crawl_update", htmlspecialchars("<h4>") + " tags has been crawled");
                var h5s = await getTagContent("h5", htmlContent, url);
                io.emit("crawl_update", htmlspecialchars("<h5>") + " tags has been crawled");
                var h6s = await getTagContent("h6", htmlContent, url);
                io.emit("crawl_update", htmlspecialchars("<h6>") + " tags has been crawled");
                var ps = await getTagContent("p", htmlContent, url);
                io.emit("crawl_update", htmlspecialchars("<p>") + " tags has been crawled");
                var object = {
                    "url": url,
                    "anchors": anchors,
                    "title": title,
                    "h1s": h1s,
                    "h2s": h2s,
                    "h3s": h3s,
                    "h4s": h4s,
                    "h5s": h5s,
                    "h6s": h6s,
                    "ps": ps,
                    "time": new Date().getTime()
                };
                try {
                    await database.collection("pages").insertOne(object);
                } catch (e) {
                    console.log(e);
                }
                io.emit("page_crawled", object);
                io.emit("crawl_update", "Page crawled.");
            } else {
                io.emit("crawl_update", "Page already crawled.");
            }
            if (callBack != null) {
                callBack();
            }
        }
    });
}

If you refresh the page now and enter the URL of any web page and hit enter, you will see its content is stored in the Mongo DB database named web_crawler. To check the data from Mongo DB, you can download a software named Mongo DB Compass.

Show data in DataTable

Now whenever a new web page is crawled, we will display that in a table. We will be using a library called DataTable. We will also include the socket IO library for real-time communication. So include those files in your index.ejs:

<link rel="stylesheet" href="/public/jquery.dataTables.min.css" />
<script src="/public/socket.io.js"></script>
<script src="/public/jquery.dataTables.min.js"></script>

Then we will create a row with 2 columns. On the left column, we will create a table to display all crawled tables. And on the right column, we will display all crawled updates e.g. “headings has been crawled”, “paragraphs” has been crawled” etc.

<div class="row">
    <div class="col-md-8">
        <table class="table table-bordered" id="my-table">
            <thead>
                <tr>
                    <th>URL</th>
                    <th>Title</th>
                    <th>Time</th>
                </tr>
            </thead>
            <tbody id="data"></tbody>
        </table>
    </div>
    <div class="col-md-4">
        <ul class="list-group" id="my-updates"></ul>
    </div>
</div>

Just to make it look better, you can apply the following styles in CSS.

#my-updates {
    max-height: 300px;
    overflow-y: scroll;
    width: fit-content;
}
.table-bordered th, .table-bordered td,
.dataTables_wrapper .dataTables_filter input {
    border: 1px solid black !important;
}
.table thead th {
    border-bottom: 2px solid black !important;
}

Then we need to initialize the data table library. Also, attach event listeners for crawl updates. Crawl updates will ab prepended in the <ul> list. The complete crawled web pages will be appended in the data table.

var table = null;
var socketIO = io("http://localhost:3000/");
var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
window.addEventListener("load", function () {
    table = $('#my-table').DataTable({
        "order": [[ 2, "asc" ]]
    });
});
socketIO.on("crawl_update", function (data) {
    // console.log(data);
    var html = "";
    html += `<li class="list-group-item">` + data + `</li>`;
    document.getElementById("my-updates").innerHTML = html + document.getElementById("my-updates").innerHTML;
    document.getElementById('my-updates').scrollTop = 0;
});
socketIO.on("page_crawled", function (data) {
    // console.log(data);
    var date = new Date(data.time);
    var time = date.getDate() + " " + months[date.getMonth() + 1] + ", " + date.getFullYear() + " - " + date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds();
    table.row.add( [
        "<a href='/page/" + encodeURIComponent(data.url) + "'>" + data.url + "</a>",
        data.title,
        time
    ] ).draw( false );
});

Now you will see the data in the table when you crawl some page. You can crawl as many pages as you want.

Fetch data from Mongo DB

At this point, data in the data table is only displayed when you crawl some page. But when you reload the page, the data table will be empty. However, the data is still stored in the database. Our web crawler has saved all the crawled pages in a Mongo DB collection named “pages”. So we need to populate the previously saved pages from the database in the data table when the page loads.

First, change our “/” GET route in the server.js to the following:

app.get("/", async function (request, result) {
    var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
    var pages = await database.collection("pages").find({})
        .sort({
            "time": -1
        }).toArray();
    for (var index in pages) {
        var date = new Date(pages[index].time);
        var time = date.getDate() + " " + months[date.getMonth() + 1] + ", " + date.getFullYear() + " - " + date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds();
        
        pages[index].time = time;
    }            
    result.render("index", {
        "pages": pages
    });
});

And in our index.ejs inside the <tbody> tag, we will display all the pages.

<tbody id="data">
    <% for (var index in pages) { %>
        <tr>
            <td>
                <a href="/page/<%= encodeURIComponent(pages[index].url) %>">
                    <%= pages[index].url %>
                </a>
            </td>
            <td><%= pages[index].title %></td>
            <td><%= pages[index].time %></td>
        </tr>
    <% } %>
</tbody>

If you refresh the page now, you will see all pages in the data table. You will only see the URL, title, and the time when the page was recently crawled. But we need to know the anchor tags on that page, all the headings, and paragraphs in it.

Show page content

Click on any of the links from the data table and it will take you to an error page. We need to convert that error page into a detailed page. Create a GET route in our server.js that will fetch the page from the database and send it to an HTML file.

app.get("/page/:url", async function (request, result) {
    var url = request.params.url;
    var page = await database.collection("pages").findOne({
        "url": url
    });
    if (page == null) {
        result.render("404", {
            "message": "This page has not been crawled"
        });
        return false;
    }
    result.render("page", {
        "page": page
    });
});

In your views folder, create a file named 404.ejs that will be displayed when the URL is not been crawled yet.

<!-- 404.ejs -->
<link rel="stylesheet" href="/public/bootstrap.css" />
<div class="jumbotron">
    <h1 class="display-4">404 - Not Found</h1>
    <p class="lead"><%= message %></p>
</div>
<script src="/public/jquery-3.3.1.min.js"></script>
<script src="/public/bootstrap.js"></script>

Now create a file named page.ejs inside the views folder. Inside this file, we will show all the crawled tags in separate data tables.

<link rel="stylesheet" href="/public/bootstrap.css" />
<link rel="stylesheet" href="/public/font-awesome-4.7.0/css/font-awesome.css" />
<link rel="stylesheet" href="/public/jquery.dataTables.min.css" />
<div class="container" style="margin-top: 50px;">
    <div class="jumbotron">
        <h1><%= page.title %></h1>
        <div class="row">
            <div class="col-md-1">
                <form method="POST" action="/delete-page" onsubmit="return confirm('Are you sure you want to delete this page ?');">
                    <input type="hidden" name="url" value="<%= page.url %>" required />
                    <input type="submit" class="btn btn-danger" value="Delete" />
                </form>
            </div>
            <div class="col-md-1">
                <form method="POST" action="/reindex" onsubmit="return confirm('Are you sure you want to re-index this page ?');">
                    <input type="hidden" name="url" value="<%= page.url %>" required />
                    <input type="submit" class="btn btn-primary" value="Re-index" />
                </form>
            </div>
        </div>
    </div>
    <div class="row">
        <div class="col-md-12">
            <table class="table table-bordered my-table">
                <thead>
                    <tr>
                        <th>Anchors</th>
                    </tr>
                </thead>
                <tbody>
                    <% for (var index in page.anchors) { %>
                        <tr>
                            <td>
                                <a href="<%= page.anchors[index].href %>">
                                    <%= page.anchors[index].text %>
                                </a>
                            </td>
                        </tr>
                    <% } %>
                </tbody>
            </table>
        </div>
    </div>
    <div class="row">
        <div class="col-md-12">
            <table class="table table-bordered my-table">
                <thead>
                    <tr>
                        <th>H1</th>
                    </tr>
                </thead>
                <tbody>
                    <% for (var index in page.h1s) { %>
                        <tr>
                            <td>
                                <%= page.h1s[index] %>
                            </td>
                        </tr>
                    <% } %>
                </tbody>
            </table>
        </div>
    </div>
    <div class="row">
        <div class="col-md-12">
            <table class="table table-bordered my-table">
                <thead>
                    <tr>
                        <th>H2</th>
                    </tr>
                </thead>
                <tbody>
                    <% for (var index in page.h2s) { %>
                        <tr>
                            <td>
                                <%= page.h2s[index] %>
                            </td>
                        </tr>
                    <% } %>
                </tbody>
            </table>
        </div>
    </div>
    <div class="row">
        <div class="col-md-12">
            <table class="table table-bordered my-table">
                <thead>
                    <tr>
                        <th>H3</th>
                    </tr>
                </thead>
                <tbody>
                    <% for (var index in page.h3s) { %>
                        <tr>
                            <td>
                                <%= page.h3s[index] %>
                            </td>
                        </tr>
                    <% } %>
                </tbody>
            </table>
        </div>
    </div>
    <div class="row">
        <div class="col-md-12">
            <table class="table table-bordered my-table">
                <thead>
                    <tr>
                        <th>H4</th>
                    </tr>
                </thead>
                <tbody>
                    <% for (var index in page.h4s) { %>
                        <tr>
                            <td>
                                <%= page.h4s[index] %>
                            </td>
                        </tr>
                    <% } %>
                </tbody>
            </table>
        </div>
    </div>
    <div class="row">
        <div class="col-md-12">
            <table class="table table-bordered my-table">
                <thead>
                    <tr>
                        <th>H5</th>
                    </tr>
                </thead>
                <tbody>
                    <% for (var index in page.h5s) { %>
                        <tr>
                            <td>
                                <%= page.h5s[index] %>
                            </td>
                        </tr>
                    <% } %>
                </tbody>
            </table>
        </div>
    </div>
    <div class="row">
        <div class="col-md-12">
            <table class="table table-bordered my-table">
                <thead>
                    <tr>
                        <th>H6</th>
                    </tr>
                </thead>
                <tbody>
                    <% for (var index in page.h6s) { %>
                        <tr>
                            <td>
                                <%= page.h6s[index] %>
                            </td>
                        </tr>
                    <% } %>
                </tbody>
            </table>
        </div>
    </div>
    <div class="row">
        <div class="col-md-12">
            <table class="table table-bordered my-table">
                <thead>
                    <tr>
                        <th>P</th>
                    </tr>
                </thead>
                <tbody>
                    <% for (var index in page.ps) { %>
                        <tr>
                            <td>
                                <%= page.ps[index] %>
                            </td>
                        </tr>
                    <% } %>
                </tbody>
            </table>
        </div>
    </div>
</div>
<script>
    window.addEventListener("load", function () {
        $('.my-table').DataTable();
    });
</script>
<style>
    .row {
        margin-top: 50px;
    }
    .table-bordered th, .table-bordered td,
    .dataTables_wrapper .dataTables_filter input {
        border: 1px solid black !important;
    }
    .table thead th {
        border-bottom: 2px solid black !important;
    }
    body {
        background: linear-gradient(0deg, #00fff3, #a5a5a5);
    }
</style>
<script src="/public/jquery-3.3.1.min.js"></script>
<script src="/public/bootstrap.js"></script>
<script src="/public/jquery.dataTables.min.js"></script>

Along with all the data on the web page, it will also show 2 buttons to “delete” and to “reindex”. Delete simply means to delete the page from the database. “Reindex” means to re-crawl the web page to fetch updated content. First, we will create a POST route for deleting the page in our server.js file.

app.post("/delete-page", async function (request, result) {
    var url = request.fields.url;
    await database.collection("pages").deleteOne({
        "url": url
    });
    io.emit("page_deleted", url);
    var backURL = request.header('Referer') || '/';
    result.redirect(backURL);
});

And in our index.ejs we will attach an event listener that will be called when the page is deleted. In that function, we will simply remove that row from the data table.

Remove specific row from DataTable.js

socketIO.on("page_deleted", function (url) {
    table
        .rows( function ( idx, data, node ) {
            return data[0].includes(url);
        } )
        .remove()
        .draw();
});

This will search for the first row with the URL in its content and remove it. After removal, it will re-render the data table to reload the table.

Re-index the page

Now we need to add a function to re-index the page, which means to get the updated content of the page. As we did for delete, we will also create a form for re-indexing.

<div class="col-md-1">
    <form method="POST" action="/reindex" onsubmit="return confirm('Are you sure you want to re-index this page ?');">
        <input type="hidden" name="url" value="<%= page.url %>" required />
        <input type="submit" class="btn btn-primary" value="Re-index" />
    </form>
</div>

This will show a “Re-index” button along with a delete button. Then we need to create a POST route in our server.js:

app.post("/reindex", async function (request, result) {
    var url = request.fields.url;
    await database.collection("pages").deleteOne({
        "url": url
    });
    io.emit("page_deleted", url);
    crawlPage(url, function () {
        var backURL = request.header('Referer') || '/';
        result.redirect(backURL);
    });
});

That’s it. If you run the code now, you will be able to re-index the page and update your database. You can try this feature by first crawling a URL today. Then try to crawl the same URL again a few days or weeks later.

So that’s how you can create a simple web crawler in Node JS and Mongo DB. You can check our financial ledger tutorial to learn how to create a financial ledger in Node JS and Mongo DB.

Track hand rotational angle – Leap + Javascript

In this tutorial, we are going to teach you how you can track your hand rotation using Leap Motion Controller. Leap Motion Controller is a device that allows you to detect your hand motions and gestures, and perform any desired action on them. It provides SDK in Java, Javascript, Python, C++, and Unreal Engine. We will be using Javascript in this tutorial.

You can buy the Leap Motion Controller from their official website. After buying, connect the device with your computer using a USB or USB-C port. Then download the SDK from here and do the installation.

If you prefer the video tutorial:

From the above video, you can see what we are going to create. So you will need a Doctor Strange time-spell video and some images. You will also need the Leap JS library to connect with Leap Motion Controller. You can find all 3 in the source files below.

Setup the Project

The following code helps you to reverse a video and concatenate 2 videos using FFmpeg:

// reverse a video
// ffmpeg -i Pentagramm2_green.mp4 -vf reverse reversed.mp4

// concatenate 2 videos
// ffmpeg -i Pentagramm2_green.mp4 -i reversed.mp4 -filter_complex "[0:v] [0:a] [1:v] concat=n=2:v=1:a=1 [v] [a]" -map "[v]" -map "[a]" output.mp4

Then create a video tag for the time-spell video.

<video src="output.mp4" muted id="time-spell"></video>

After that, create a div to show the image and text below it.

<div style="position: absolute; right: 50px; top: 50px;">
    <img src="images/2021.JPG" id="image" />
    <h1 id="title">2021</h1>
</div>

Now include the Leap JS library in your project.

<script src="leap-0.6.4.min.js"></script>

Then apply some CSS styles to display the video, image, and text properly.

<style>
    /* remove margin and set background color as black */
    body {
        margin: 0px;
        background-color: black;
    }

    /* set width of time spell and its position */
    #time-spell {
        width: 1200px;
        position: absolute;
        left: -275px;
        top: 30px;
    }

    /* set width and height of image and fit inside container */
    #image {
        width: 800px;
        height: 600px;
        object-fit: contain;
    }

    /* set color, position, alignment, font size and background color of image text */
    #title {
        color: white;
        position: relative;
        bottom: 129px;
        text-align: center;
        font-size: 70px;
        background-color: rgba(0, 0, 0, 0.7);
    }
</style>

Then create an array of images and their text in Javascript.

<script>

    // list of images and their text
    var images = [{
        "src": "images/2021.JPG",
        "title": "2021"
    }];

    // index is out of bound right now
    var index = images.length;

</script>

After that, initialize some variables that we will be using throughout the tutorial.

// total duration of time spell video
var totalDuration = 40;

// state of time spell (back or forward)
var state = "";

// previous state of time spell
var previousState = "";

// hand rotation from Leap Motion Controller
var handRotation = "";

// is time spell start rotating
var isMediaPlaying = false;

// video tag
var media = document.getElementById("time-spell");

Leap motion controller connection

Now is the time to connect our Javascript app with Leap Motion Controller.

// setup Leap loop with frame callback function
var controllerOptions = {};

Leap.loop(controllerOptions, function(frame) {
    // get all hands in frame
    var hands = frame.hands;

    // if there is any hand
    if (hands.length > 0) {

        // get the first hand
        var hand = hands[0];

        // ignore if the hand is left
        if (hand.isLeft) {
            return false;
        }

        // get direction of hand (x,y,z co-ordinates)
        var axis = hand.direction;

        // get x-axis
        axis = axis[0];

        if (axis > 0 && axis < 0.9) {
            // if hand is rotated to right
            handRotation = "right";
        } else if (axis > -0.09 && axis < -0.01) {
            // if hand is in center (no rotation)
            handRotation = "center";

            media.pause();
        } else if (axis < 0 && axis < -0.5) {
            // if hand is rotated to left
            handRotation = "left";
        }
    } else {
        // if there is no hand in the frame, simply pause the time spell
        if (media != null) {
            media.pause();
        }
    }
});

For explanation, comments have been added with each line. Now we need to call a function every 0.5 seconds and rotate the time spell to the left if the hand is rotated to left, and right if the hand is rotated to right. Also, it will call a function to change the image as per hand rotation.

// called every 0.5 seconds
setInterval(function () {
    
    // if hand is rotating to right
    if (handRotation == "right") {
        
        // only called once to rotate time spell
        if (state == "") {
            // rotate to right
            mediaBackward();
        }

        // if time spell starts rotating
        if (isMediaPlaying) {
            // show next image
            nextImage();
        }

        // make it empty to give a delay to show next image
        handRotation = "";
    } else if (handRotation == "left") {

        // only called once to rotate time spell
        if (state == "") {
            // rotate to left
            mediaForward();
        }

        // if time spell starts rotating
        if (isMediaPlaying) {
            // show previous image
            previousImage();
        }

        // make it empty to give a delay to show previous image
        handRotation = "";
    } else if (handRotation == "center") {
        // pause the time spell, when hand is in center of Leap Motion Controller
        media.pause();
    }
}, 500);

Now we just need to create these 4 functions called from the above code.

// when hand is rotated right
function mediaBackward() {

    // "back" means reverse
    state = "back";

    // starts rotating time spell
    media.play();

    // current time of video
    var currentTime = media.currentTime;

    // if video stops playing
    if (currentTime <= 0) {
        // starts from middle to move right
        currentTime = totalDuration / 2;
    }

    // this condition becomes true only when the
    // time spell was rotating forward (left side)
    if (previousState != state) {
        // change direction of time spell
        var newTime = totalDuration - currentTime;
        media.currentTime = newTime;
    }
}

// when hand is rotated left
function mediaForward() {

    // "forward" means original video
    state = "forward";

    // starts rotating time spell
    media.play();

    // current time of video
    var currentTime = media.currentTime;

    // if video stops playing
    if (currentTime <= 0) {
        // starts from zero to move left
        currentTime = totalDuration;
    }

    // this condition becomes true only when the
    // time spell was rotating backward (right side)
    if (previousState != state) {
        // change direction of time spell
        var newTime = totalDuration - currentTime;
        media.currentTime = newTime;
    }
}

// show next image from images array
function nextImage() {
    // increment the index
    index++;

    // check if the index is out of bound
    if (index >= images.length || index < 0) {
        return false;
    }

    // get source of image
    var src = images[index].src;

    // get text of image
    var title = images[index].title;

    // update image
    document.getElementById("image").setAttribute("src", src);

    // update title
    document.getElementById("title").innerHTML = title;
}

// show previous image from images array
function previousImage() {
    // decrement the index
    index--;

    // check if the index is out of bound
    if (index < 0 || index >= images.length) {
        return false;
    }

    // get source of image
    var src = images[index].src;

    // get text of image
    var title = images[index].title;

    // update image
    document.getElementById("image").setAttribute("src", src);

    // update title
    document.getElementById("title").innerHTML = title;
}

Finally, 2 event listeners are added to check when the time spell is rotating and when is paused. Based on that, we will set our variables to change images.

// called when the time spell starts rotating
media.onplaying = function () {
    // set the variable to true
    setTimeout(function () {
        isMediaPlaying = true;
    }, 100);
};

// called when the time spell is paused
media.onpause = function () {
    // get the previous state
    previousState = state;

    // set the current state as empty
    state = "";
    
    // set the variable to false
    isMediaPlaying = false;
};

Following this tutorial, you will be able to track hand rotations using Leap motion controller and Javascript.

Download source code:

[wpdm_package id=’1271′]

Watermark image after upload – PHP, No Library

In this tutorial, we will teach you how you can put a watermark on an image uploaded by a user. You might have seen websites that allow you to upload images, but when you try to download them, they put their watermark on your image and ask you to pay to remove it. So let’s learn how they do this.

First I am going to create a form where we will show the input type file to select the image. Its method will be POST and the action will be “index.php”. The encoding (enctype) type must be multipart/form-data. This is necessary for uploading files. Then we are going to show a label that says “select file” and an input type file. The name attribute on the input type file will be used on the PHP side. It will also have an attribute accept=”image/*”, which means this input field will accept only image type, for example, PNG or JPEG, etc, but no PDF or DOCX type of files will be selected in this field. Finally, the form will have a submit button. Submit button will have a name attribute so we can know when this form is submitted.

Select a file

<form method="POST" action="index.php" enctype="multipart/form-data">

	<p>
		<label>Select file</label>
		<input type="file" name="image" accept="image/*" />
	</p>

	<input type="submit" name="submit" value="Upload" />
</form>

If you run the code now, you will see a form with an input type file and a submit button. Now we need to write the PHP code to handle this form.

Apply the watermark

First, we will check if the form is submitted.

<?php

if (isset($_POST["submit"]))

Then we will save the user uploaded image in a file named “image.png” because, in order to put a watermark on it, the file must be stored in our server.

move_uploaded_file($_FILES["image"]["tmp_name"], "image.png");

Then we need to get this image as an object. So call imagecreatefromjpeg function if your image was a JPEG image, imagecreatefrompng if it is a PNG. Parameter will be the path of saved image file.

$image_obj = imagecreatefromjpeg("image.png");

Similarly, we will create an object of our watermark image, which is a PNG image.

$watermark = imagecreatefrompng("watermark.png");

Then we will define the margin from the right and from the bottom, since we do not want the watermark to stick to the corners, we want a little margin.

$margin_right = 10;
$margin_bottom = 10;

Then we are going to get the watermark image width and height in a separate variable.

$watermark_width = imagesx($watermark);
$watermark_height = imagesy($watermark);

Now comes the tricky part. We need to tell the X and Y coordinates of original image where we will place the watermark. The X position from left will be width of uploaded image, minus width of watermark image, minus margin from right.

$destination_x = imagesx($image_obj) - $watermark_width - $margin_right;

Similarly, the Y position from top will be height of uploaded image, minus height of watermark image, minus margin from bottom.

$destination_y = imagesy($image_obj) - $watermark_height - $margin_bottom;

Now we need to copy the watermark image in user uploaded image. The syntax of this function is:

// imagecopy(dst_im, src_im, dst_x, dst_y, src_x, src_y, src_w, src_h)

Our destination image is the image uploaded by user. Source image is the watermark image. Destination x and y are just we calculated, I will explain this as well. And source x and y will be zero, because we do not want any margin in our watermark image. And finally the width and height of our source image which is watermark image.

imagecopy($image_obj, $watermark, $destination_x, $destination_y, 0, 0, $watermark_width, $watermark_height);

Now let me explain the calculation we did above.

Explaining X, Y co-ordinates

width of user image = 2560
width of watermark image = 640
margin from right = 10

2560 - 640 - 10 = 1910 = x

height of user image = 1600
height of watermark image = 345
margin from bottom = 10

1600 - 345 - 10 = 1245 = y

First is destination_x variable. The width of image object is 2560px, minus the width of watermark is 640px, minus the margin right which is 10. That equals 1910, so our watermark will be placed at 1910 on X axis from left. Now come to destination_y variable, height of user uploaded image is 1600px, it will be different for each image because user can upload image of any resolution. I am writing these values as sample. So the 1600px is the height of user uploaded image. Minus the height of watermark, which is 345px. Minus margin from bottom that is 10. That equals 1245. So our watermark image will be placed at 1245 on Y axis from top.

1910 + 640 = 2550
1245 + 345 = 1590

Now when the watermark is placed at 1910 from left and it has a width of 640px, then that equals 2550 which is 10px less than the width of user image, which is 2560px. So there is a difference of 10px, which means there will be a margin of 10px from right. Similarly, when the watermark is placed at 1245 from the top and it has a height of 345px, then that equals 1590 which is again 10px less than the height of the user image, which is 1600px. So it will have a margin of 10px from the bottom.

Generating watermarked image

Now the watermark image object has been copied to the user uploaded image object using imagecopy function above. Now we need to generate that image.

imagepng($image_obj, "output.png");

The first parameter will be an image object, which is a user object along with a watermark object because the watermark image has been copied to it. And second, will be the path of the output file.

Finally, we are going to destroy the image objects we just created for user-uploaded images and watermark image to free up the memory.

imagedestroy($image_obj);
imagedestroy($watermark);

Also, we saved the user image using the move uploaded file function because we need the image to be stored before putting a watermark on it. Now the watermark is placed, so we have no need for the original user-selected picture, so we are going to delete it using the unlink function.

unlink("image.png");

If you run the code now, select an image, and upload it, you will see that the original image was saved as image.png and the output.png is being rendered for a few seconds. When the output.png is completely rendered then the original image.png was automatically deleted because we have used the unlink function.

Complete code

<?php

if (isset($_POST["submit"]))
{
	move_uploaded_file($_FILES["image"]["tmp_name"], "image.png");
	$image_obj = imagecreatefromjpeg("image.png");

	$watermark = imagecreatefrompng("watermark.png");

	$margin_right = 10;
	$margin_bottom = 10;

	$watermark_width = imagesx($watermark);
	$watermark_height = imagesy($watermark);

	$destination_x = imagesx($image_obj) - $watermark_width - $margin_right;
	$destination_y = imagesy($image_obj) - $watermark_height - $margin_bottom;

	// imagecopy(dst_im, src_im, dst_x, dst_y, src_x, src_y, src_w, src_h)
	imagecopy($image_obj, $watermark, $destination_x, $destination_y, 0, 0, $watermark_width, $watermark_height);

	// 2560 - 640 - 10 = 1910
	// 1600 - 345 - 10 = 1245

	// x = 1910 + 640 = 2550
	// y = 1245 + 345 = 1590

	imagepng($image_obj, "output.png");

	imagedestroy($image_obj);
	imagedestroy($watermark);

	unlink("image.png");
}

?>

The final image will be of the same quality as of original image. If you face any problems in following this, kindly mention them in the comments section below.

How you can also resize the image without stretching in PHP by following this tutorial.

[wpdm_package id=’1262′]