in HOW

Why? Because you can! Because it’s fun and gives me the opportunity to learn a bit more, and perhaps it helps someone else in the process.

TL;DR – here it is: https://100.evervee.me/9/

What is an image board? Think of Pinterest. The “what” is in the name – a board where one can store images, perhaps with titles and descriptions. Like a photo album, either with pages or infinite scroll downwards.

Because I am a fairly visual person, I tend to ‘whiteboard’ a lot of the things I think about. It helps me break down the problems into their smallest parts, and makes it easy to divide and devise actionable parts that I can then work on, after planning them out nicely. Like a visual outline, if you will. Here’s what I mean:

So what does an image board do, in essence?

Well, allows for uploading of images and allows for categorization. Think of files in directories on your hard drive, but with some ‘social’ features – like, re-arrange, move/copy from one board to another, and so on.


We won’t implement all those features. Just the barebones ones that show the potential of what can be done.

 

What elements are there in a basic site like this? Setting aside some of the bells and whistles, like users, authentication, security, many social features, permalinks, and all the optimizations these modern and mature sites like Pinterest have, a basic imageboard site will have, at a minimum, the following elements and relationships between them:

  • board or boards; as such, these would be the “parent” or top level elements. Think of them like containers for a bunch of images. Like photo albumbs. Perhaps each board is a web-page. With a permanent address. Makes it easy to share with other people, I suppose.
  • each board can contain one or more images, that generally are arranged and displayed in some sort of order, either randomly, or based on the date when they were uploaded, sorted chronologically or in reverse of chronology, and so on; there are probably too many ways to present images in a board, but we are only going to implement a few, as examples. These boards, if they happen to contain a lot of images, they become difficult to present and navigate. As such, it’s maybe a good idea to either paginate the content (the images), or make it so that when the user scrolls down, the board (webpage) will load on demand more images to fill up the space. Think of the way twitter and fakebook keep populating content as you keep scrolling down. It’s not all loaded at once, but in chunks, on demand, based on your scrolling down.
  • alongside images, there may or may not be captions, or at least image titles. This decision complicates things further, because if we want to include text, we are de facto thinking of reddit or 4chan or any of those other japanese image boards. If the images are only to have titles and a very short description, then it’s more akin to Pinterest.

 

 

Furthermore, whether we decide to have images posted as replies to each other or not – further complicates things and makes the result more like reddit or 4chan.

In computer science parlance, a many-to-many relationship and linked lists are required to make threaded posts of text with images work. It’s not that much more difficult than one-to-many, but I digress.

But if we were to only maintain a one-to-many relationship between the board and the images therein, and also no parent-child relationship between the images themselves, then we are describing Pinterest.

Let’s go with a Pinterest clone, as it’s perhaps easier to implement.

No fancy JS or animations, because the whole point of these boards is anonimity, and usage on shitty old devices not capable of modern web

The point of this is to learn how to break down problems and how to code an entire solution end to end, from snippets to completion (include example configuration files)

Another feature for this machine is auto cleaning every 5 minutes (with the exception of a set of default posts with images). But that is not a feature you’d want on a production site, of course.

 

 

Alright, let’s get it done!

 

 

First, you want to have the data model defined, and set up in the database

 

So let’s start with the board table. It will have an id (board_id), a name, a description perhaps, and definitely its own url/slug (like /my-awesome-board, agnostic of the domain name it lives on, of course);

then, it would make sense to also have a status field, which can be maybe ‘active’/’inactive’ (or public/private, or any number of things that you can tie extra functionality on). Maybe even an owner field, but we’re not adding users and authentication, in order to keep it simple enough for this exercise.

This table will look something like this:

id, name, description, slug, status
23, "Wish List", "this is my wishlist of things", my-awesome-board, active
24, "cars", "board with cool cars", car-board, inactive

 

Then, we continue on with the associations table. This is the big table that maintains the connection between boards and images. This is that many-to-many relationship part that we touched on above. You can have a board with many images. But you can also have the same image be part of multiple boards.

And since we don’t want to duplicate actual images on disk, they will be referenced by some id, which is simply a number; which, obviously, is cheap enough to store in a database table/record, even duplicated a whole lot, it’s still cheap to store.

This table is relatively simple, but since it is going to be potentially the largest, we must design and index it properly.

The most important bit, the fields with image IDs that are associated with this some board ID.

This associations table should look something like this:

id, board_id, image_id
1, 23, 859
2, 23, 34098
3, 23, 151

Now in that example, this simply means that board 23 has 3 images on it, images with image IDs 859, 34098, 151 in that order. Order doesn’t really matter, because we can order by that image ID, assuming higher number means newer.

If you want to, for example, remove an image from a board (without actually deleting the image itself), then you’d simply delete the appropriate record based on the image ID desired. Say you don’t want image with ID 151 to be part of board with ID 23. Then remove record ID 3 (last) from this associations table.

We could of course do away with the associations table. And maintain associations in either the Boards table, or the Images table. But it’s far messier and less easy to maintain, in my opinion. Ideally we want the boards table to contain only unique board IDs, and the images table to contain only unique image IDs. Without the Associations table, that is not possible.

 

Finally, the images table. Like with anything else, each image will have an id, maybe a name, an url/slug (important for view item page and board view page), and that’s about it.

id, name, slug
151, "car1", "/images/car1.jpg"
859, "house34", "/images/house34.jpg"
34098, "fancy laptop", "/images/fancy_laptop.jpg"

That’s it, for database tables. The database (schema) name is unimportant. Just configure the web logic part to connect to it accordingly, by modifying the config.inc file example I’m supplying.

 

Then, we want to define a set of pages that closely follow functionality:

  1. view homepage, where all the posts appear, seemingly randomly, but actually the newest on top, and the oldest on the bottom, and the only thing random is perhaps the boards they’re part of; but for this page, the boards are irrelevant. Or it could be that the home page is actually a collection of boards, and not images, for a site like Pinterest; but again, outside the scope of this exercise.

  2. view item page, where a particular image appears, enlarged, with a description, author/owner, the board it’s part of, and more importantly – the key feature – ADD TO BOARD…; which allows you to associate that image with one or more boards you have

  3. view board, which is the page where only the images that are ‘part of’ a board appear. Perhaps some controls for the board, like Edit Title/Name, Delete Board, etc. Also, control to disassociate images from said board. Like Remove image from Board

  4. – maybe a search function, though that depends on the metadata associated with each image, and whether or not whoever uploaded it had the decency to tag the image with enough descriptive words that represent what is pictured in the image itself. Short of that, one would need to use some ML to categorize images without human intervention, and that is obviously way outside of the scope of this endeavor

 

 

HOMEPAGE

The homepage, or index page, has to display both all the images and all the boards, with the images associated with them at any time.

As such, it has 2 sections:

images

In this section, I’ve chosen to display images in an html table, in reverse order; that is, the latest one uploaded on the site and in the database – first. And the oldest one – last.

In SQL lingo, that’s done with a simple select, like so select * from images_table order by id desc. Then we iterate over all the results from the database, and display the images from their storage, on disk.

Here’s the code for images:

//begin images
echo "<h3><u>IMAGES</u> (newest first)</h3>";
echo "<table border=0>";
$image_result = $mysqli->query("select * from $mysql_images_table order by id desc;");

if ($image_result->num_rows > 0) {
//echo "<tr><td>id</td><td>title</td><td><strong># of views</strong></td></tr>";
// output data of each row
echo "<table border=0><tr>";
while($row = $image_result->fetch_assoc()) {
echo "<td>";
$image_id = $row["id"];
$image_title = $row["image_title"];
$image_path = $row["image_path"];
$image_desc = $row["image_description"];
echo("<table border=0>
<tr><td>$image_id</td><td><a href='./vip.php?id=$image_id'>$image_title</a></td></tr>
<tr><td colspan=2><a href='./vip.php?id=$image_id'><img src='$image_path/$image_title' width=120 height=150></a></td></tr>
<tr><td colspan=2>$image_desc</td></tr>
</table>");
echo "</td>";
}
echo "</tr></table>";
} else {
echo "<tr><td>no data</td></tr>";
}

echo "</table>";
//end images

boards

In this boards section, we’re making use of another table. We’re going over all the boards first (something like select * from boards_table), in a loop. And for each board, we’re iterating over all the images associated with it in the database; and then compose the list of images, and display them, adjacent to the board name, in the table.

That’s done something like this, say, for board_id 3:
select * from associations_table where board_id=3. This returns a result set, say with 2 images; images also have ids, say 15 and 42. With these known variables, we read the names of the images based on their ids, in order to know which ones to load from disk and display on screen; something like so: select * from images_table where image id=15, then get its file name (stored in the database, coinciding with the file name of the image on disk). And just put an <img src=./images/car.jpg as output. Assuming ‘car.jpg’ corresponds to image id 15, for instance. And so on with the rest of the images.

Here’s the gist of the code for boards:

//begin boards
echo "<h3><u><strong>BOARDS</strong></u> (no new pins in Inactive boards)</h3>";
$board_result = $mysqli->query("select * from $mysql_boards_table;");
echo "<table border=2>";
if ($board_result->num_rows > 0) {
//echo "<tr><td>id</td><td>title</td><td><strong># of views</strong></td></tr>";
// output data of each row
while($boardrow = $board_result->fetch_assoc()) {
$board_id = $boardrow["id"];
$board_status = $boardrow["status"];
$board_name = $boardrow["name"];
$board_url = $boardrow["board_url"];
$board_desc = $boardrow["board_description"];

echo "<tr><td>";

echo("<table border=1>
<tr><td>$board_id</td><td><a href='$board_url$board_id'>$board_name</a></td><td> -$board_desc-</td><td>:$board_status:</td>");
//iterate over images of a board and put them in cells
// <td>a</td>
$boardimages_result = $mysqli->query("select * from $mysql_assoc_table where board_id=$board_id;");
//var_dump($boardimages_result);
if ($boardimages_result->num_rows > 0) {
while ($imgsrow = $boardimages_result->fetch_assoc()) {
$image_id=$imgsrow["image_id"];
//echo "imageID:" . $image_id;
//go to images table and grab the path and the name, to be able to compose an <img src> out of those to display the images in cells
$tinyimages_result = $mysqli->query("select * from $mysql_images_table where id=$image_id;");
if ($tinyimages_result->num_rows > 0) {
while ($imgrow = $tinyimages_result->fetch_assoc()) {
$image_path=$imgrow["image_path"];
$image_title=$imgrow["image_title"];
echo ("<td><a href='./vip.php?id=$image_id'><img src='$image_path/$image_title' width=50 height=50></a></td>");

}
}
}
}
echo("</tr>
</table>");

}
} else {
echo "no data>";
}


//end boards

 

 

VIEW IMAGE PAGE (VIP)

The VIP page centers attention on one single image, that got clicked anywhere on the homepage, displays it enlarged (or at its natural size, rather), and under it – there are controls to ‘pin‘ it to one of the existing boards. That means, to associate the image id with the board id, in the database, so next refresh of the home page or the View Board Page will display this image as well.

By controls to pin, I mean it’s a simple html <form></form>, with <input> fields and a <input type='submit'> button. The ‘destination’ of all these interactive pages are simple .php pages that take the values of these input fields, and with those details, they alter some database table, typically only the ‘associations’ table, for simplicity’s sake.

$image_id = $_REQUEST['id'];
if (empty($image_id)) {
echo "no item supplied... you rascal<br>";
echo "Please go <a href=\"./index.php\">BACK</a> and just click on existing links, you don't have to input anything in this app";
exit;
}
$image_id = htmlspecialchars($mysqli->real_escape_string($image_id));
if (!is_numeric($image_id)) { echo "somehow we didn't get a number... a team of highly trained monkeys has been dispatched to solve this issue, come back later"; die(); }


$image_result = $mysqli->query("select * from $mysql_images_table where id=$image_id;");
if (!$image_result) {
echo $mysqli->error;
}
echo ("<table style='border:1px solid black;margin-left:auto;margin-right:auto;'><tr>");
if ($image_result->num_rows > 0) {
// output data of each row
while($image_row = $image_result->fetch_assoc()) {
$image_title = $image_row["image_title"];
$image_path = $image_row["image_path"];
$image_description = $image_row["image_description"];
echo ("<tr><td colspan=3><strong>$image_title</strong> - $image_description</td></tr>");
echo ("<tr><td colspan=3><img src='$image_path/$image_title' width=640 height=480></td>");
//dropdown with all boards to pin this image on; we have $image_id in this block/loop
echo ("<tr><td>Boards:</td><td>");
$boards_result = $mysqli->query("select * from $mysql_boards_table;");
if (!$boards_result) {
echo $mysqli->error;
}
if ($boards_result->num_rows > 0) {
echo("<select name='board_id'>");
while ($board_row = $boards_result->fetch_assoc()) {
$board_id = $board_row["id"];
$board_name = $board_row["name"];
$board_description = $board_row["board_description"];
$board_status = $board_row["status"];
$board_url = $board_row["board_url"];
echo("<option value='$board_id'>$board_name</option>");
}
//put the relevant info as hidden fields to the next page
echo("<input type='hidden' name='image_id' value='$image_id'>");
echo("</select></td><td><input type=submit name=submit value='Pin to board!'");
} else {
echo "no board data";
}
}
} else {
echo "no image data";
}
echo("</tr></table>");

echo "<br><a href=\"./index.php\">BACK</a>";

 

 

VIEW BOARD PAGE (VBP)

The VBP iterates over all the images that are associated with a board. They can be displayed slightly larger than they are on the homepage. But obviously, the assumption is that there are usually many images pinned on a board. So it doesn’t make sense to show the images at their natural sizes. That’s why we are capping them at 200 by 200 pixels or something. Still thumbnail-ish size, but not so tiny as to not see what’s in there. As it’s the case on the homepage, because of space constraints.

Also on this board page, to the right of each image, there’s a button to ‘UnPin’. This means that based on that image id, this page’s <form></form> section will alter the database, namely the associations table, by deleting the entry where board id equals the current board id AND the image_id equals the selected image id, of course.

There are of course other checks in there, but this is the gist of it:

$board_id = $_REQUEST['id'];
if (empty($board_id)) {
	echo "no item supplied... you rascal<br>";
	echo "Please go <a href=\"./index.php\">BACK</a> and just click on existing links, you don't have to input anything in this app";
	exit;
}
$board_id = htmlspecialchars($mysqli->real_escape_string($board_id));
if (!is_numeric($board_id)) { echo "somehow we didn't get a number... a team of highly trained monkeys has been dispatched to solve this issue, come back later"; die(); }

echo "<input type=hidden name=board_id value=$board_id>";

$board_result = $mysqli->query("select * from $mysql_boards_table where id=$board_id;");
echo "<table border=2 style='margin-left:auto; margin-right:auto;'>";
if ($board_result->num_rows > 0) {
    while($boardrow = $board_result->fetch_assoc()) {
        $board_id = $boardrow["id"];
        $board_status = $boardrow["status"];
        $board_name = $boardrow["name"];
        $board_url = $boardrow["board_url"];
        $board_desc = $boardrow["board_description"];
        echo "<tr><td>";
        echo("<table border=1 style='margin-left:auto;margin-right:auto;'>
                <tr><th>$board_id</th><th><a href='$board_url$board_id'>$board_name</a></th><th style='font-size:32px;'><strong><u>$board_desc</u></strong></th><th>status:$board_status</th>");
        $boardimages_result = $mysqli->query("select * from $mysql_assoc_table where board_id=$board_id;");
        //var_dump($boardimages_result);
        if ($boardimages_result->num_rows > 0) {
                while ($imgsrow = $boardimages_result->fetch_assoc()) {
                        $image_id=$imgsrow["image_id"];
                        //echo "imageID:" . $image_id;
                        //go to images table and grab the path and the name, to be able to compose an <img src> out of those to display the images in cells
                        $tinyimages_result = $mysqli->query("select * from $mysql_images_table where id=$image_id;");
                        if ($tinyimages_result->num_rows > 0) { 
                                while ($imgrow = $tinyimages_result->fetch_assoc()) {
                                        $image_path=$imgrow["image_path"];
                                        $image_title=$imgrow["image_title"];
                                        echo ("<tr><td colspan=3 align=center><a href='./vip.php?id=$image_id'><img src='$image_path/$image_title' width=200 height=200></a></td>");
					echo "<input type=hidden name=image_id value=$image_id>";
					echo "<td><input type=submit name=submit value='UnPin!'</td>";
					echo "</tr>";
                                }
                        }
                }
        }
        echo("</tr>
              </table>");
    }
} else {
    echo "no data>";
}


echo("</tr></table></form>");

echo "<br><a href=\"./index.php\">BACK</a>";

 

 

 

 

That’s most of it. I mean, obviously it doesn’t have all the bells and whistles of the official Pinterest website and app, but the ‘engine’ is there.

With probably a lot of frontend and javascript work, this can be made to look beautiful, and function nicer than it does (as in, no page refresh needed, infinite scroll in case of lots of images, and so on).

But we’re here to learn the basics of how to code, by breaking down problems and barriers; and avoiding being overwhelmed by huge household names like ‘pinterest’, ‘google’, etc.

All those are built by people like you and I. So I challenge myself to at least replicate the basics of these major sites, just to learn more about how stuff works.

 

Again, you can see it in action at https://100.evervee.me/9/

 

Write a Comment

Comment