Thursday, 16 March 2017

Web Tutorial: Hacker Republic Login Sequence (Part 2/4)

Now that you have that gallery up, it's time to interact with it. Here's how Lisbeth Salander does it in the story.

She put her cursor on the spire of the church tower and clicked. She instantly got a pop-up dialog box that asked for her I.D. and password. She took out her stylus and wrote the word Remarkable on the screen as her I.D. and A(89)Cx#magnolia as the password. She got a dialog box with the text [ERROR  -  you have the wrong password] and a button that said [OK  -  Try again]. Lisbeth knew that if she clicked on [OK  -  Try again] and tried a different password, she would get the same dialog box again  -  for years and years, for as long as she kept trying. Instead she clicked on the [O] in [ERROR].

So this it how it goes. You have to click on the spire of the church to get the pop-up dialogue box. Anything else, something harmless happens. To do this, we're going to divide the image into several clickable squares, like so. Look at the spire of the church. It's right at the 9th square of the 1st row. For your reference, I've numbered the squares for you in x,y format, where x is the row and y is the column.
0,0
0,1
0,2
0,3
0,4
0,5
0,6
0,7
0,8
0,9
1,0
1,1
1,2
1,3
1,4
1,5
1,6
1,7
1,8
1,9
2,0
2,1
2,2
2,3
2,4
2,5
2,6
2,7
2,8
2,9
3,0
3,1
3,2
3,3
3,4
3,5
3,6
3,7
3,8
3,9
4,0
4,1
4,2
4,3
4,4
4,5
4,6
4,7
4,8
4,9
5,0
5,1
5,2
5,3
5,4
5,5
5,6
5,7
5,8
5,9
6,0
6,1
6,2
6,3
6,4
6,5
6,6
6,7
6,8
6,9
7,0
7,1
7,2
7,3
7,4
7,5
7,6
7,7
7,8
7,9
8,0
8,1
8,2
8,3
8,4
8,5
8,6
8,7
8,8
8,9
9,0
9,1
9,2
9,3
9,4
9,5
9,6
9,7
9,8
9,9


Back to your code...

Clear the div by setting its innerHTML property to an empty string. You'll see why in a minute. Next, declare the variable grid, and set up a nested For loop. We're going to create a grid of 10 squares by 10 squares.

tt_millenniumlogin.js
function open_overlay(ovid,imgid)
{
    var overlay=document.getElementById(ovid);
    overlay.style.display="block";

    if (ovid=="gallery_overlay")
    {
        var img_actual=document.getElementById("gallery_actual");
        img_actual.style.backgroundImage="url(img/"+imgid+".jpg)";
        img_actual.innerHTML="";

        var grid;

        for (var i=0;i<10;i++)
        {
            for (var j=0;j<10;j++)
            {

            }
        }
    }
}


Here, we create div elements and set grid to reference them, then set the class to gallery_grid.

tt_millenniumlogin.js
function open_overlay(ovid,imgid)
{
    var overlay=document.getElementById(ovid);
    overlay.style.display="block";

    if (ovid=="gallery_overlay")
    {
        var img_actual=document.getElementById("gallery_actual");
        img_actual.style.backgroundImage="url(img/"+imgid+".jpg)";
        img_actual.innerHTML="";

        var grid;

        for (var i=0;i<10;i++)
        {
            for (var j=0;j<10;j++)
            {
                grid=document.createElement("div");
                grid.className="gallery_grid";
                grid.id=imgid+"_"+i+"_"+j;
                img_actual.appendChild(grid);
            }
        }
    }
}


Now, in your HTML file, create the CSS class gallery_grid. Each one has the float property set to left, and has a height and width of 50 pixels. It's a 10x10 grid, and (10x50=500) pixels, which is the width and height of your image! Of course, the squares are invisible, so you won't see them.

index.html
        <style>

            body, html {height:100%;}

            #gallery
            {
                width:100%;
                height:200%;
            }

            .gallery_row
            {
                width:100%;
                height:5%;
                margin-top:10px;
            }

            .gallery_row .thumbnail
            {
                width:9.5%;
                height:100%;
                float:left;
                margin-left:5px;
                background-size:cover;
                background-position:center center;
                cursor:pointer;
            }

            #gallery_overlay
            {
                position:fixed;
                left:0;
                top:0;
                width:100%;
                height:100%;
                display:none;
                z-index:100;
                background-color:rgba(0,0,0,0.9);
            }

            #gallery_actual
            {
                width:500px;
                height:500px;
                margin-top:5%;
                margin-left:auto;
                margin-right:auto;
                background-size:cover;
                background-position:left top;
                cursor:pointer;
            }

            .gallery_grid
            {
                width:50px;
                height:50px;
                float:left;
            }
        </style>


Add this line to your JavaScript. It'll set a click event which will call the function process_grid(), with a string parameter. You'll pass in the id of the current square as an argument.

tt_millenniumlogin.js
function open_overlay(ovid,imgid)
{
    var overlay=document.getElementById(ovid);
    overlay.style.display="block";

    if (ovid=="gallery_overlay")
    {
        var img_actual=document.getElementById("gallery_actual");
        img_actual.style.backgroundImage="url(img/"+imgid+".jpg)";
        img_actual.innerHTML="";

        var grid;

        for (var i=0;i<10;i++)
        {
            for (var j=0;j<10;j++)
            {
                grid=document.createElement("div");
                grid.className="gallery_grid";
                grid.id=imgid+"_"+i+"_"+j;
                grid.onclick=function(){process_grid(this.id)};
                img_actual.appendChild(grid);
            }
        }
    }
}


So declare process_grid().

tt_millenniumlogin.js
function open_overlay(ovid,imgid)
{
    var overlay=document.getElementById(ovid);
    overlay.style.display="block";

    if (ovid=="gallery_overlay")
    {
        var img_actual=document.getElementById("gallery_actual");
        img_actual.style.backgroundImage="url(img/"+imgid+".jpg)";
        img_actual.innerHTML="";

        var grid;

        for (var i=0;i<10;i++)
        {
            for (var j=0;j<10;j++)
            {
                grid=document.createElement("div");
                grid.className="gallery_grid";
                grid.id=imgid+"_"+i+"_"+j;
                grid.onclick=function(){process_grid(this.id)};
                img_actual.appendChild(grid);
            }
        }
    }
}

function process_grid(gridid)
{

}


Create an If conditional. The pop-up dialogue box will only appear if the id of the square, passed into the function, is "img_166_0_8". That is to say, image number 167, 1st row, 9th column! So if you click on the correct square, the function open_overlay() is called (we're re-using the function we created earlier) but this time with arguments "login_overlay" and an integer. An integer needs to be passed in because that's the signature of the function, but it doesn't matter what integer because we won't be using it. Just pass in a 0 and be done with it.

tt_millenniumlogin.js
function process_grid(gridid)
{
    if (gridid=="img_166_0_8")
    {
        open_overlay("login_overlay",0);
    }
    else
    {

    }
}


If you click on any other square, we open the image. We do this by using the split() method and obtaining the second element in the resultant array. This gives you the image number, which we'll then pass into the getURL() function, to obtain the URL of that image. Then we'll just open a new window to display the image.

tt_millenniumlogin.js
function process_grid(gridid)
{
    if (gridid=="img_166_0_8")
    {
        open_overlay("login_overlay",0);
    }
    else
    {
        var params=gridid.split("_");
        var url=getUrl(params[1]);
        window.open(url);
    }
}


As mentioned in the previous parts, all the images (save for the church) were random images taken off the internet. However, I don't want to remote-link them to the gallery (which is legally questionable and, well, just not very nice) and I don't want to not reference them in any way. So I've decided to link the images to the original URLs where I took the images from. If this were a real login page, you would want to do differently, of course.

Here, I've created the getURL() function which returns the URL based on the number passed in. urls is an array which holds all the URLs, and the final line simply returns the element at the index which you passed in as an argument. So if you passed in "2", you would get the URL "http://cdn.visitjacksonville.com/pages/4185/offshore_fishing__flexslider.jpg".

tt_millenniumlogin.js
function process_grid(gridid)
{
    if (gridid=="img_166_0_8")
    {
        open_overlay("login_overlay",0);
    }
    else
    {
        var params=gridid.split("_");
        var url=getUrl(params[1]);
        window.open(url);
    }
}

function getUrl(imgindex)
{
    var urls=[
        "http://www.atlantisbahamas.com/media/Things%20To%20Do/Water%20Park/Beaches/Gallery/waterpark_beaches_sexy.jpg",
        "http://cutedogandcat.xyz/wp-content/uploads/2016/06/cool-seeing-eye-dog-for-blind-dog-plus-a-brief-history-of-guide-dogs-the-best-dog-blog-dogpacer.jpg",
        "http://cdn.visitjacksonville.com/pages/4185/offshore_fishing__flexslider.jpg",
        "http://midcenturymotels.com/wp-content/uploads/2016/03/Blue-Swallow-Motel.jpg",
        "https://static-ssl.businessinsider.com/image/57e41098077dcc1c008b7964-2048/14066261_2122557997968462_5010985606905929631_o.jpg",
        "https://static01.nyt.com/images/2015/12/21/dining/21COOKING_DEVILSFOODCAKE/21COOKING_DEVILSFOODCAKE-superJumbo.jpg",
        "http://kingofwallpapers.com/race/race-002.jpg",
   
 (Click here for the rest of the code)
    ];

    return urls[imgindex];
}


Try it! Click on any of the images. You should get a new window that shows the original image. Of course, things on the internet being as fluid as they are, it's entirely possible that you'll get a different image or a 404 error.




Of course, if you click on the spire of image #167, nothing should happen. That's because you haven't defined the case of "login_overlay" being passed into open_overlay(). So let's do it here. This basically hides the div element login_message and displays the div element login_form.

tt_millenniumlogin.js
function open_overlay(ovid,imgid)
{
    var overlay=document.getElementById(ovid);
    overlay.style.display="block";

    if (ovid=="login_overlay")
    {
        document.getElementById("login_form").style.display="block";
        document.getElementById("login_message").style.display="none";
    }

    if (ovid=="gallery_overlay")
    {
        var img_actual=document.getElementById("gallery_actual");
        img_actual.style.backgroundImage="url(img/"+imgid+".jpg)";
        img_actual.innerHTML="";

        var grid;

        for (var i=0;i<10;i++)
        {
            for (var j=0;j<10;j++)
            {
                grid=document.createElement("div");
                grid.className="gallery_grid";
                grid.id=imgid+"_"+i+"_"+j;
                grid.onclick=function(){process_grid(this.id)};
                img_actual.appendChild(grid);
            }
        }
    }
}

But there are no such elements!

Of course. You haven't created them yet! Let's do that right now. Back to your HTML file, create a div with the id login_overlay.

index.html
    <body onload="load_gallery();">
        <div id="gallery_overlay" onclick="close_overlay(this.id);">
            <div id="gallery_actual">

            </div>
        </div>

        <div id="login_overlay">

        </div>

        <div id="gallery">

        </div>
    </body>


This time, you only want the overlay to close if you click on a "close" button, so add this code.

index.html
    <body onload="load_gallery();">
        <div id="gallery_overlay" onclick="close_overlay(this.id);">
            <div id="gallery_actual">

            </div>
        </div>

        <div id="login_overlay">
            <div id="login_x">
                <span onclick="close_overlay('login_overlay');">close x</span>
            </div>
        </div>

        <div id="gallery">

        </div>
    </body>


Now style login_overlay and login_x. login_overlay has almost the same styling as gallery_overlay, except that it only covers 50% of the screen's height, and appears somewhere center of screen.

For login_x, the styling is as follows.
width:80% - to give it a bit of spacing at the sides.
margin: 0 auto 0 auto - to place the div center of the login_overlay div.
color:#FFFFFF - to make the text stand out.
text-align:right - to set the text flush right of the login_x div.
cursor:pointer - to make it damn obvious that this is clickable.

index.html
        <style>

            body, html {height:100%;}

            #gallery
            {
                width:100%;
                height:200%;
            }

            .gallery_row
            {
                width:100%;
                height:5%;
                margin-top:10px;
            }

            .gallery_row .thumbnail
            {
                width:9.5%;
                height:100%;
                float:left;
                margin-left:5px;
                background-size:cover;
                background-position:center center;
                cursor:pointer;
            }

            #gallery_overlay
            {
                position:fixed;
                left:0;
                top:0;
                width:100%;
                height:100%;
                display:none;
                z-index:100;
                background-color:rgba(0,0,0,0.9);
            }

            #gallery_actual
            {
                width:500px;
                height:500px;
                margin-top:5%;
                margin-left:auto;
                margin-right:auto;
                background-size:cover;
                background-position:left top;
                cursor:pointer;
            }

            .gallery_grid
            {
                width:50px;
                height:50px;
                float:left;
            }

            #login_overlay
            {
                position:fixed;
                left:0;
                top:20%;
                width:100%;
                height:50%;
                display:none;
                z-index:100;
                background-color:rgba(0,0,0,0.9);
            }

            #login_x
            {
                width:80%;
                margin: 0 auto 0 auto;
                color:#FFFFFF;
                text-align:right;
                cursor:pointer;
            }
        </style>


Try your code. Click on thumbnail #167, then click on the spire. You should see the gallery_overlay disappear and the login_overlay appear with the "close x". Click on "close x" and the login_overlay div should disappear as well. It's pretty hard to see since the text is in black, so I've highlighted it for you in the screenshot.


Then add the login_form div with text boxes for login id, password and a login button. We'll impose a maximum length of 20 characters for the text boxes, as a good practice.

index.html
    <body onload="load_gallery();">
        <div id="gallery_overlay" onclick="close_overlay(this.id);">
            <div id="gallery_actual">

            </div>
        </div>

        <div id="login_overlay">
            <div id="login_x">
                <span onclick="close_overlay('login_overlay');">close x</span>
            </div>

            <div id="login_form">
                <input type="text" id="login_id" placeholder="login" maxlength="20"><br />
                <input type="password" id="login_password" placeholder="password" maxlength="20"><br />
                <input type="button" value="LOGIN">
            </div>
        </div>

        <div id="gallery">

        </div>
    </body>


Next, we have a div for login_message which holds the error message which is going to appear. The word "ERROR" is divided into a series of span elements, with each character occupying one span. Note that each span is styled using the CSS class message_error. The "OK - Try again" button only has one function - to rerun the open_overlay() function using the arguments "login_overlay" and an empty string.

index.html
    <body onload="load_gallery();">
        <div id="gallery_overlay" onclick="close_overlay(this.id);">
            <div id="gallery_actual">

            </div>
        </div>

        <div id="login_overlay">
            <div id="login_x">
                <span onclick="close_overlay('login_overlay');">close x</span>
            </div>

            <div id="login_form">
                <input type="text" id="login_id" placeholder="login" maxlength="20"><br />
                <input type="password" id="login_password" placeholder="password" maxlength="20"><br />
                <input type="button" value="LOGIN">
            </div>

            <div id="login_message">           
                <span class="message_error">E</span>
                <span class="message_error">R</span>
                <span class="message_error">R</span>
                <span class="message_error">O</span>
                <span class="message_error">R</span>
                 - You have the wrong password
                <br />
                <input type="button" value="OK - Try again">
            </div>
        </div>

        <div id="gallery">

        </div>
    </body>


OK, this is pretty ugly, so just spice it up a little with some CSS. Again, I've had to highlight it for you, for better visibility.


index.html
            #login_overlay
            {
                position:fixed;
                left:0;
                top:20%;
                width:100%;
                height:50%;
                display:none;
                z-index:100;
                background-color:rgba(0,0,0,0.9);
            }

            #login_form,#login_message
            {
                width:40%;
                margin: 10% auto 0 auto;
                color:#FFFFFF;
            }

            #login_message
            {
                font-size:10px;
                font-weight:bold;
            }

            #login_x
            {
                width:80%;
                margin: 0 auto 0 auto;
                color:#FFFFFF;
                text-align:right;
                cursor:pointer;
            }


Better now. It's still not very pretty, but I'm depending on your good asthetic sense to take care of that. For now, it's at least functional and that's enough.

Note that the error message and "Try again" button have fallen outside the overlay now. That's OK, it should be invisible anyway. We'll take care of that later.


Our next step is to write code that will process the login. So alter your HTML. The button should run the login() function with the argument "login_form" when clicked.

index.html
<input type="button" value="LOGIN" onclick="login('login_form');">


And in your JavaScript file, write the function login(). We first declare the variable parameters, then add an If block for the string "login_form" You guessed it, we'll be reusing the function later. I'm lazy that way.

tt_millenniumlogin.js
function process_grid(gridid)
{
    if (gridid=="img_166_0_8")
    {
        open_overlay("login_overlay",0);
    }
    else
    {
        var params=gridid.split("_");
        var url=getUrl(params[1]);
        window.open(url);
    }
}

function login(formid)
{
    var parameters;

    if (formid=="login_form")
    {
   
    }
}

function close_overlay(ovid)
{
    var overlay=document.getElementById(ovid);
    overlay.style.display="none";
}


First, make the login_form div disappear and login_message div appear.

tt_millenniumlogin.js
function login(formid)
{
    var parameters;

    if (formid=="login_form")
    {
        document.getElementById("login_form").style.display="none";
        document.getElementById("login_message").style.display="block";   
    }
}


Next, set the parameters string to pass in the values from the form. The first will be a parameter type with a value of 0. This will be increasingly relevant later.

tt_millenniumlogin.js
function login(formid)
{
    var parameters;

    if (formid=="login_form")
    {
        document.getElementById("login_form").style.display="none";
        document.getElementById("login_message").style.display="block";

        parameters="type=0";
        parameters=parameters+"&id="+document.getElementById("login_id").value;
        parameters=parameters+"&password="+document.getElementById("login_password").value;   
    }
}


Next is a standard AJAX block that sends the data to ajax_login.php via POST. We'll come back to this later.

tt_millenniumlogin.js
    var parameters;

    if (formid=="login_form")
    {
        document.getElementById("login_form").style.display="none";
        document.getElementById("login_message").style.display="block";

        parameters="type=0";
        parameters=parameters+"&id="+document.getElementById("login_id").value;
        parameters=parameters+"&password="+document.getElementById("login_password").value;   

        if (window.XMLHttpRequest)
        {// code for IE7+, Firefox, Chrome, Opera, Safari
            xmlhttp_loginform=new XMLHttpRequest();
        }
        else
        {// code for IE6, IE5
            xmlhttp_loginform=new ActiveXObject("Microsoft.XMLHTTP");
        }
       
        xmlhttp_loginform.onreadystatechange=function()
        {
            if (xmlhttp_loginform.readyState==4 && xmlhttp_loginform.status==200)
            {

            }
        }

        xmlhttp_loginform.open("POST","ajax_login.php",true);
        xmlhttp_loginform.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
        xmlhttp_loginform.send(encodeURI(parameters));
    }


For now, let's set up the file ajax_login.php.  First, we start a session using the function session_start(). Next, we declare a variable, type, and set it to the type value sent via POST. In this case, it would be 0. But to be absolutely sure you get an integer, we use the intval() function.

Then we set a conditional block for type 0.

ajax_login.php
<?php
session_start();

$type=intval($_POST["type"]);

if ($type==0)
{

}
?>


First, we provide dummy data. In a genuine scenario, the data (suitably encrypted, of course) would be provided by a database; however this is a simulation and as such, data will be provided in the form of arrays. In this case, we use the array logins. The data provided in the first element reflects Lisbeth Saander entering "Remarkable" as the id and "A(89)Cx#magnolia" as the password. We also have two other login ids, "x1" and "x2", prepared for testing. Because typing in "x1" or "x2" is way less troublesome than typing in "Remarkable" or "A(89)Cx#magnolia".
ajax_login.php
if ($type==0)
{
    $logins=array();
    $logins["Remarkable"]="A(89)Cx#magnolia";
    $logins["x1"]="x1";
    $logins["x2"]="x2";
}


Next, we obtain the values for the variables id and password from the POST data.

ajax_login.php
if ($type==0)
{
    $logins=array();
    $logins["Remarkable"]="A(89)Cx#magnolia";
    $logins["x1"]="x1";
    $logins["x2"]="x2";

    $id=$_POST["id"];
    $password=$_POST["password"];
}


Next, we use the array_exists() function in a conditional block. Basically, if the key id exists in the logins array, we process the information. If not, we print out a JSON string for the AJAX code to get.

ajax_login.php
if ($type==0)
{
    $logins=array();
    $logins["Remarkable"]="A(89)Cx#magnolia";
    $logins["x1"]="x1";
    $logins["x2"]="x2";

    $id=$_POST["id"];
    $password=$_POST["password"];

    if (array_key_exists($id,$logins))
    {

    }
    else
    {
        echo json_encode(array("valid" => false));
    }
}


So if the id is valid, we use another conditional block to check if the password is correct. If it's not, again, we output a JSON string to store a false value.

ajax_login.php
if ($type==0)
{
    $logins=array();
    $logins["Remarkable"]="A(89)Cx#magnolia";
    $logins["x1"]="x1";
    $logins["x2"]="x2";

    $id=$_POST["id"];
    $password=$_POST["password"];

    if (array_key_exists($id,$logins))
    {
        if ($logins[$id]==$password)
        {

        }
        else
        {
            echo json_encode(array("valid" => false));
        }
    }
    else
    {
        echo json_encode(array("valid" => false));
    }
}


If the password is correct, we set a session variable, loggedin, to true. And then we output a JSON string with a true value for the AJAX code to get.

ajax_login.php
if ($type==0)
{
    $logins=array();
    $logins["Remarkable"]="A(89)Cx#magnolia";
    $logins["x1"]="x1";
    $logins["x2"]="x2";

    $id=$_POST["id"];
    $password=$_POST["password"];

    if (array_key_exists($id,$logins))
    {
        if ($logins[$id]==$password)
        {
            $_SESSION["loggedin"]=true;
            echo json_encode(array("valid" => true));
        }
        else
        {
            echo json_encode(array("valid" => false));
        }
    }
    else
    {
        echo json_encode(array("valid" => false));
    }
}


Back to the AJAX code, we declare the variable logindetails and set it to grab the output from ajax_login.php, parsing it for a JSON value. We then take the resultant object and retrieve the data for the property valid. If it's false, do nothing. If the login and password were both correct, however, we grab all elements in the current HTML document with the class of message_error, and then set a click event on the fourth element in the resultant array, ie. the character "O". This means that the error message appears no matter what, but if id and password are correct, the letter "O" is now clickable. Clicking on it will send the user to the page confirm.php.

tt_millenniumlogin.js
    var parameters;

    if (formid=="login_form")
    {
        parameters="type=0";
        parameters=parameters+"&id="+document.getElementById("login_id").value;
        parameters=parameters+"&password="+document.getElementById("login_password").value;   

        if (window.XMLHttpRequest)
        {// code for IE7+, Firefox, Chrome, Opera, Safari
            xmlhttp_loginform=new XMLHttpRequest();
        }
        else
        {// code for IE6, IE5
            xmlhttp_loginform=new ActiveXObject("Microsoft.XMLHTTP");
        }
       
        xmlhttp_loginform.onreadystatechange=function()
        {
            if (xmlhttp_loginform.readyState==4 && xmlhttp_loginform.status==200)
            {
                var logindetails=JSON.parse(xmlhttp_loginform.responseText);

                if (logindetails.valid)
                {
                    var errormessage=document.getElementsByClassName("message_error");
                    errormessage[3].onclick=function(){window.location="confirm.php"};
                }
            }
        }

        xmlhttp_loginform.open("POST","ajax_login.php",true);
        xmlhttp_loginform.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
        xmlhttp_loginform.send(encodeURI(parameters));
    }


Try it. Type in an id other than "Remarkable", "x1" or "x2", and/or an incorrect password. It should do nothing except show you the error message. However, if you type in a correct id/password combination, the "O" in "ERROR" should be clickable.




Click on it and it should show you this. That's because you currently don't have confirm.php created.


Next

Creating confirm.php

Tuesday, 14 March 2017

Web Tutorial: Hacker Republic Login Sequence (Part 1/4)

Following my review of The Girl Who Kicked The Hornet's Nest, I'd like to present a truly awesome (by my humble standards) web tutorial. There was a login sequence on page 319, which the heroine of the story uses to gain access to the Hacker Republic site.

And that's what we're going to do today - a simulation of that kick-ass login sequence. For this, we'll be using a PHP backend with JavaScript and AJAX. HTML and CSS will make up the view, of course.

The sequence starts off like this.
She started by going on to a website that advertised rather uninteresting pictures by an unknown and not especially skilled amateur photographer called Gil Bates in Jobsville, Pennsylvania. Salander had once checked it out and confirmed that the town of Jobsville did not exist. Nevertheless, Bates had taken more than 200 photographs of the community and created a gallery of small thumbnails. She scrolled down to image 167 and clicked to enlarge it. It showed the church in Jobsville.


We're going to start off by creating that gallery of pictures. According to the text, there are more than 200 pictures, so we will create a gallery of 210 pictures - 21 rows featuring 10 photographs each.

For the photographs themselves, I downloaded 209 random pictures off the internet to serve as dummies...

Hold on, wasn't there supposed to be 210 photographs?

Why, the odd one out is that of the church, of course! I selected one with a church tower and spire.

Original image at
http://www.zionfriedheim.org/
images/
gifs/zion%20church%20new.jpg

Each image is renamed using the following formula: img_x.jpg, where x represents the number of the picture.

The images are all saved in the img subfolder.



So here goes. Create index.html and enter in the basic HTML layout for the gallery. The div gallery holds the (duh) gallery. You'll notice that the HTML references tt_millenniumlogin.js from the js folder. We'll create that later.

index.html
<!DOCTYPE html>
<html>
    <head>
        <title>Photo Gallery of Gil Bates</title>

        <script type="text/javascript" src="js/tt_millenniumlogin.js"></script>
    </head>

    <body>
        <div id="gallery">

        </div>
    </body>
</html>


Style gallery as follows and create the CSS class gallery_row. With over 200 thumbnails, the gallery is expected to scroll, so we need to set the height of gallery to 200% of the screen size. There are expected to be 20 rows in the gallery, so let's just set the height of each row to 5%. Then give the gallery_row CSS class a tiny margin of 10 pixels at the top.

index.html
    <head>
        <title>Photo Gallery of Gil Bates</title>

        <style>
            body, html {height:100%;}

            #gallery
            {
                width:100%;
                height:200%;
            }

            .gallery_row
            {
                width:100%;
                height:5%;
                margin-top:10px;
            }

            .gallery_row .thumbnail
            {
                width:9.5%;
                height:100%;
                float:left;
                margin-left:5px;
                background-size:cover;
                background-position:center center;
                cursor:pointer;
            }
        </style>

        <script type="text/javascript" src="js/tt_millenniumlogin.js"></script>
    </head>


Also create the thumbnail CSS class. There'll be 10 thumbails on each row, so give each thumbnail a width just shy of 10%. 9.5% should do it. The height will be 100% of the row, of course. Set the float property to left and give the margin on the left 5 pixels. cursor:pointer  makes it apparent that the thumbnail can be clicked, and the background properties will be useful for ensuring that the images fit somewhat nicely into the thumbnails.

index.html
    <head>
        <title>Photo Gallery of Gil Bates</title>

        <style>
            body, html {height:100%;}

            #gallery
            {
                width:100%;
                height:200%;
            }

            .gallery_row
            {
                width:100%;
                height:5%;
                margin-top:10px;
            }

            .gallery_row .thumbnail
            {
                width:9.5%;
                height:100%;
                float:left;
                margin-left:5px;
                background-size:cover;
                background-position:left top;
                cursor:pointer;
            }
        </style>

        <script type="text/javascript" src="js/tt_millenniumlogin.js"></script>
    </head>


However, we will not be writing this into the HTML, not manually, anyway. No, we'll use JavaScript to accomplish this. Alter your HTML...

index.html
    <body onload="load_gallery();">


And then create tt_millenniumlogin.js and save in in the js folder. In this file, create the load_gallery() function.

Declare the variables row, thumbnail and gallery. Set gallery to reference the gallery div.

tt_millenniumlogin.js
function load_gallery()
{
    var row,thumbnail;
    var gallery=document.getElementById("gallery");
}


We're going to create a gallery with 21 rows and 10 columns, so create a nested For loop.

tt_millenniumlogin.js
function load_gallery()
{
    var row,thumbnail;
    var gallery=document.getElementById("gallery");

    for (var i=0;i<21;i++)
    {
        for (var j=0;j<10;j++)
        {

        }
    }
}


Create a div element and assign it to the row variable. Set its class to gallery_row.

tt_millenniumlogin.js
function load_gallery()
{
    var row,thumbnail;
    var gallery=document.getElementById("gallery");

    for (var i=0;i<21;i++)
    {
        row=document.createElement("div");
        row.className="gallery_row";

        for (var j=0;j<10;j++)
        {

        }
    }
}


In the inner loop, create a div element and assign it to the variable thumbnail. Set its class to thumbnail.

tt_millenniumlogin.js
function load_gallery()
{
    var row,thumbnail;
    var gallery=document.getElementById("gallery");

    for (var i=0;i<21;i++)
    {
        row=document.createElement("div");
        row.className="gallery_row";

        for (var j=0;j<10;j++)
        {
            thumbnail=document.createElement("div");
            thumbnail.className="thumbnail";
        }
    }
}


Set the id of the thumbnail, and the background image.

tt_millenniumlogin.js
function load_gallery()
{
    var row,thumbnail;
    var gallery=document.getElementById("gallery");

    for (var i=0;i<21;i++)
    {
        row=document.createElement("div");
        row.className="gallery_row";

        for (var j=0;j<10;j++)
        {
            thumbnail=document.createElement("div");
            thumbnail.className="thumbnail";
            thumbnail.id="img_"+((i*10)+j);
            thumbnail.style.backgroundImage="url(img/img_"+((i*10)+j)+".jpg)";
        }
    }
}


Append the thumbnail to the row using the appendChild() method.

tt_millenniumlogin.js
function load_gallery()
{
    var row,thumbnail;
    var gallery=document.getElementById("gallery");

    for (var i=0;i<21;i++)
    {
        row=document.createElement("div");
        row.className="gallery_row";

        for (var j=0;j<10;j++)
        {
            thumbnail=document.createElement("div");
            thumbnail.className="thumbnail";
            thumbnail.id="img_"+((i*10)+j);
            thumbnail.style.backgroundImage="url(img/img_"+((i*10)+j)+".jpg)";
            row.appendChild(thumbnail);
        }
    }
}


Again using the appendChild() method, append the row to the gallery div.

tt_millenniumlogin.js
function load_gallery()
{
    var row,thumbnail;
    var gallery=document.getElementById("gallery");

    for (var i=0;i<21;i++)
    {
        row=document.createElement("div");
        row.className="gallery_row";

        for (var j=0;j<10;j++)
        {
            thumbnail=document.createElement("div");
            thumbnail.className="thumbnail";
            thumbnail.id="img_"+((i*10)+j);
            thumbnail.style.backgroundImage="url(img/img_"+((i*10)+j)+".jpg)";
            row.appendChild(thumbnail);
        }

        gallery.appendChild(row);
    }
}


If you've used the entire directory of images I've provided in the example, you should see this. But this is confusing because you need to see the numbers.


So add this line to your code. It will calculate the appropriate number and insert it into the thumbnail.

tt_millenniumlogin.js
function load_gallery()
{
    var row,thumbnail;
    var gallery=document.getElementById("gallery");

    for (var i=0;i<21;i++)
    {
        row=document.createElement("div");
        row.className="gallery_row";

        for (var j=0;j<10;j++)
        {
            thumbnail=document.createElement("div");
            thumbnail.className="thumbnail";
            thumbnail.id="img_"+((i*10)+j);
            thumbnail.style.backgroundImage="url(img/img_"+((i*10)+j)+".jpg)";
            thumbnail.innerHTML="#"+((i*10)+j+1);
            row.appendChild(thumbnail);
        }

        gallery.appendChild(row);
    }
}


Here's your gallery! Look at picture number 167. It's a church!


Now let's make this sucker clickable. Set a listener for onclick to call the function open_overlay(), which will take in two string parameters. In this case, "gallery_overlay" and the current thumbnail id are passed in as arguments.

And of course, create the open_overlay() function.

tt_millenniumlogin.js
function load_gallery()
{
    var row,thumbnail;
    var gallery=document.getElementById("gallery");

    for (var i=0;i<21;i++)
    {
        row=document.createElement("div");
        row.className="gallery_row";

        for (var j=0;j<10;j++)
        {
            thumbnail=document.createElement("div");
            thumbnail.className="thumbnail";
            thumbnail.id="img_"+((i*10)+j);
            thumbnail.style.backgroundImage="url(img/img_"+((i*10)+j)+".jpg)";
            thumbnail.innerHTML="#"+((i*10)+j+1);
            thumbnail.onclick=function(){open_overlay("gallery_overlay",this.id);};
            row.appendChild(thumbnail);
        }

        gallery.appendChild(row);
    }
}

function open_overlay(ovid,imgid)
{

}


But before we do anything else, let's set up the HTML elements. We have a gallery_overlay div and inside it, we have a gallery_actual div. gallery_overlay is the overlay that the enlarged photo will sit on, and gallery_actual is the placeholder for the photo itself.

index.html
    <body onload="load_gallery();">
        <div id="gallery_overlay">
            <div id="gallery_actual">

            </div>
        </div>

        <div id="gallery">

        </div>
    </body>
</html>


Here's the styling.

index.html
        <style>
            body, html {height:100%;}

            #gallery
            {
                width:100%;
                height:200%;
            }

            .gallery_row
            {
                width:100%;
                height:5%;
                margin-top:10px;
            }

            .gallery_row .thumbnail
            {
                width:9.5%;
                height:100%;
                float:left;
                margin-left:5px;
                background-size:cover;
                background-position:center center;
                cursor:pointer;
            }

            #gallery_overlay
            {
                position:fixed;
                left:0;
                top:0;
                width:100%;
                height:100%;
                display:none;
                z-index:100;
                background-color:rgba(0,0,0,0.9);
            }

            #gallery_actual
            {
                width:500px;
                height:500px;
                margin-top:5%;
                margin-left:auto;
                margin-right:auto;
                background-size:cover;
                background-position:left top;
                cursor:pointer;
            }
        </style>


For gallery_overlay, we have...
position:fixed, left:0, right:0, width:100%, height:100% - makes the div cover the entire screen.
display:none - makes it invisible, which is the default state.
z-index:100 - sends it to the front of the display.
background-color:rgba(0,0,0,0.9) - makes the div translucent.

For gallery_actual...
width:500px, height:500px - because each picture is at least that big.
margin-top:5%, margin-left:auto, margin-right:auto - sets the gallery_actual div squarely in the middle of its parent, the gallery_overlay div.
background-size:cover, background-position:left top - ensures the correct positioning of whatever image we set as its background.
cursor:pointer - makes the photo appear clickable.

Now back to your JavaScript.

Start off by using a variable, overlay, to reference the element whose id is passed in as the first argument.

tt_millenniumlogin.js
function open_overlay(ovid,imgid)
{
    var overlay=document.getElementById(ovid);
    overlay.style.display="block";
}


Try it. Click on any of the pics. You should see a big grey overlay!

We'll be reusing this function for other stuff, so let's just create an If conditional for this use case.
Reference the gallery_actual div with the variable img_actual, then set the background image to the url of the image to be displayed. The filename (sans extension) is provided by the second argument to open_overlay().

tt_millenniumlogin.js
function open_overlay(ovid,imgid)
{
    var overlay=document.getElementById(ovid);
    overlay.style.display="block";

    if (ovid=="gallery_overlay")
    {
        var img_actual=document.getElementById("gallery_actual");
        img_actual.style.backgroundImage="url(img/"+imgid+".jpg)";
    }
}


Try it. Click on any of the images. You should get the enlarged version!



To keep things clean, let's create the close_overlay() function. This is simple enough - it accepts the id of the element to be "closed",  references the element with the variable overlay, and then sets the display property to none.

tt_millenniumlogin.js
function open_overlay(ovid,imgid)
{
    var overlay=document.getElementById(ovid);
    overlay.style.display="block";

    if (ovid=="gallery_overlay")
    {
        var img_actual=document.getElementById("gallery_actual");
        img_actual.style.backgroundImage="url(img/"+imgid+".jpg)";
    }
}

function close_overlay(ovid)
{
    var overlay=document.getElementById(ovid);
    overlay.style.display="none";
}


Just alter your HTML code and we're good. Try this. Open up the image by clicking on any of the thumbnails, then close by clicking anywhere else.

index.html
        <div id="gallery_overlay" onclick="close_overlay(this.id);">
            <div id="gallery_actual">

            </div>
        </div>

Next

We're going to implement some funky clicky goodness on the gallery you've just created. Watch this space!

Thursday, 9 March 2017

Cyber-secure Nation

Cyber security seems to be the new buzzword. Just last week, it was announced that a new military unit, the Defence Cyber Organization (DCO) would be set up specifically to counter infocomm threats such as cyber terrorism and fake news.

Online threats

The idea itself wasn't bad, though the timing of the announcement coincided wth news of the Ministry of Defence (MINDEF) being hacked, resulting in the personal data of 850 national servicemen and employees being stolen. While I'm sure the move has been planned carefully for some time before being announced, the timing does give the impression that the Government is still coming to terms with the loaded gun that is the Internet, a full decade after 56kbps modems stopped being a thing.

Elite forces?

"They will be entering a very selective and demanding vocation, comparable to the commandos or naval divers" - Second Defence Minister Ong Ye Kung

Also, Minister Ong Ye Kung's comparison of the proposed new cyber defenders to the Commandos and Naval Diver units, caused a certain amount of jeering. I'm not saying he was wrong, or that his attempt to talk up the proposed new unit wasn't well-intentioned. I'm merely suggesting that the comparison was somewhat tactless. In terms of physicality, the average infantryman might find these remarks to be trivializing his training, much less someone from the elite forces, such as Guardsmen and Commandos!

Come on, dude. Really?

But, technically at least, he's not wrong. Cyber terrorism is a very real threat, and military secrets are at risk, and have been for a while now. The hacking of MINDEF is probably just the tip of a very intimidating iceberg.

The same argument, of course, could be applied to other less glamorous vocations such as Drivers, Clerks and Cooks. Each of these vocations bring something valuable to the table - support for the macho alpha males manning the front lines. And the military simply would not function just as well without them. So I'm of the opinion that as long as you served with pride and passion, you can hold your head high.

Also, the good Minister is right in the sense that not every vocation is that easily filled, much less that of Cyber Defenders, Commandos or Guardsmen. As a seaman on board a warship in the Republic of Singapore Navy, I once witnessed a Commando swagger on board my ship. He probably had a very low opinion of seamen, whose training admittedly cannot be compared to Commando training in terms of physicality. He was forced to revise his opinion, an hour later out at sea when the ship started to roll. Hey, if you've never seen a Commando turn green, you're missing out.

Having to clean up his puke later? Totally worth it.

Professional application

This may be one of the few military vocations that have an actual equivalent outside of the military. One of the problems career soldiers run into is that after they finish their contracts, finding jobs outside that leverage on their military experience, can be a real pain in the ass. Not for this particular vocation. The Government is even launching the Cybersecurity Professional Scheme in July, with an eye towards increasing the number of cyber security professionals in the market.

Other concerns

What I am interested in, is how this new unit is going to work. If, as reported, there are plans to draw their numbers from both regulars and conscripts, how does this work for conscripts? As a conscript whose term of service was a trifling 2 years plus, my access to information was on a need-to-know basis. But if Cyber Defenders are going to be any good at their jobs, they would need to have a fair idea of what they are protecting, and where all the entry points are.

Would they still be required to operate firearms? Dig foxholes? Charge up and down hills?

Also, what cool stuff will they be learning? The basics are probably out. They are taking these guys from the pool of IT Diploma holders, after all.

Guess we'll find out in the coming months...

Curious mind(ef)s want to know!
T___T

Saturday, 4 March 2017

A very unsexy sexism scandal for Uber

In the past two weeks, Uber has been in the news for the wrong reasons.


Susan Fowler, former site reliability engineer at Uber, now working for payment gateway company Stripe, released a blogpost which detailed an unpleasant account of the sexist treatment she received while working for Uber. You can read her story for yourself - it's a pretty absorbing tale. I'm just going to paraphrase some of the injustices she allegedly suffered.

- Fowler was sexually propositioned by her superior on her first day at work.

- Upon reporting the offence to HR, HR decided that the offender would not be penalized because he was a high performer and it was his first offense.

- Fowler was offered an option to transfer. However, if she opted to remain, she would risk a retaliatory bad performance review from her superior, and this review would be considered legit because she had refused the option to transfer. (Are you rolling your eyes at this crap? I am.)

- Subsequent complaints about the offender from other colleagues were similarly covered up with the "first offense" excuse.

- There's also some weird shit about leather jackets...

- Her Manager threatened to fire her because she reported his Manager to HR.

- Her performance reviews were altered negatively, blocking her chances for a raise, bonus and transfer.

Do I believe Fowler? I'm inclined to. It seems to be consistent with the overall "frat-bro" culture I've been hearing about at Silicon Valley. But just because I believe her doesn't automatically make her allegations true - and until this is confirmed by the other party, they remain allegations.

In all fairness, we don't have Uber's side of the story. And it's pretty hard to get at the moment considering CEO Travis Kalanick declared that he was going to conduct an internal investigation concerning Fowler's allegations, and said investigation is still ongoing. Coming from a guy who once described his company as "Boober", this strikes me as rather ironic.

But if - if Fowler's allegations are true, this is nothing short of disgusting.

Now, I've never been under the illusion that Human Resource was on the employee's side. I've long accepted that HR is on the side of management in most cases, and the sooner one comes to terms with this reality, the better. Even so, there ought to be limits. Covering up outright wrongdoing crosses that line twice. It's not so much that the offender in question was a hotshot who delivered consistently and that Fowler was just one of many engineers. This would have been as great a travesty if Fowler had been a janitor.

What are the consequences of this bad press?

As long as Uber continues to deliver quality services, I'm not about to support the #DeleteUber. campaign. Not that I think users shouldn't delete the Uber app - I think users should do whatever they damn well please. My objection to Uber's employment practices are not moral in nature. I don't care if Uber hires perverts with weird deviant fetishes in their spare time, wife abusers or utter assholes. As long as these people are good at what they do, all's well and dandy.

And no, I certainly don't give a flying fuck that Uber didn't participate in the taxi strike by the New York Taxi Workers Alliance in protest over the so-called Muslim ban by the Trump administration, which was the entire catalyst for the #DeleteUber campaign. Not everything is about Donald Trump, dammit!

No, my objections are from an ethical and professional standpoint. It is unethical to discriminate in terms of anything but ability. It is unprofessional to subject your co-workers to sexual harassment, which by any objective standard is a huge no-no. Nobody works in a vacuum. Nobody is that good. And when somebody unwilling or unable to play nicely with others, gets put in a position of power, everyone suffers. The company eventually suffers. No matter how good you are, it's only half the picture. The other half of the picture is all about how well you work with others.

By that measure, Uber has failed. Not as a service provider, but as a company. And I won't be shedding any tears if they do go down due to talent leaving, because based on this account, it's nothing less than they deserve.

However, users don't care about all that. And neither are they required to. Uber's user base does not look likely to significantly decrease. They may suffer a dip in profits, but this won't be a deal-breaker for their continued sustainability.

The real fallout is likely to be in employment. If Uber doesn't review the way it treats its employees pronto, they may find real difficulty in retaining current talent, and hiring future talent. No self-respecting engineer wants to be rated on anything but ability. In fact, the average engineer probably will not thrive in a toxic environment where playing office politics is required to get ahead.

I don't generally take cabs, so deleting Uber from my mobile is a moot point. But assuming I was an Uber user, this would not cause me to delete the app. I would, however, not want to work in Uber at all.

Having just a fraction of Fowler's ability and experience, I doubt Uber would be interested in hiring me (though I might make a pretty good janitor) but even if they were, I think it's safe to say I won't be touching that cesspool with a ten-foot pole. There are few things I detest more than a tech company that doesn't even attempt to treat its employees fairly. I've worked for enough of these companies to feel a severe antipathy towards such practices.

Damage Control

Richard Levick, founder and CEO of Levick, suggested that Uber could repair the situation by hiring more female engineers.

Are you fucking
serious?!

Firstly, hiring based on gender rather than ability is just more sexism. Not what Uber needs right now.

Secondly, hiring females just to appease the public is insulting to both the hired personnel and the people being appeased.

Thirdly, until Uber's macho-shit culture is improved and Human Resource practices put under severe scrutiny, they have no business hiring any female.

Yep, something smells fowl at Uber, all right.
T___T