Skip to content

Inner Workings (Slide Show front end)

Ricky T edited this page Nov 20, 2017 · 1 revision

Node

Each node has 3 properties:

  1. data - contain the location of the image to download from (i.e http://X2X.XXX.X3.2X1/ao-nang-tossed-icecream.jpg) The data is fed into javascript's Image object. Then we have CSS download the image into the background.

  2. next reference - keep track of which node is after this node.

  3. previous reference - keeps track of which node is before this node.

Each node represents a pictorial. The pictorial is simply a large image that the user have uploaded onto the backend server. The pictorial is an image that's larger than 1600 by 1200.

Circular List

The circular list is just a collection of nodes, connected by their previous and next references. Node A's next references Node B. Node B's previous references Node A.

Hence the Circular List is a data representation of our slide show. The node that is being displayed is being pointed to by the "now" reference. When we click on the next/previous button, we call the CircularList's interface functions to traverse the "now" reference.

Circular List's public interface

  • insert
  • remove
  • stepPrev
  • stepNext

When the user goes to the admin page and inputs the picture and text, we call insert on the CircularList. When the user is viewing their SlideShow and clicks next and previous, we call stepPrev and stepNext on the CircularList.

insertion

We basically test for 2 cases.

  1. If both references are null, we simply create a new Node and then have both head and tail point to it.
  2. If some nodes exist, the list grows via the tail reference. The head is anchored and does not move. When we remove data, the head moves. When we insert data, the tail moves.
        if (head === null && tail === null) {
           head = new ListNode(data, null, null);
           tail = head;
        } else {
           tail = new ListNode(data, tail, head);
           tail.prev.next = tail;
           head.prev = tail;
        }

remove

Removing nodes involves three basic situations:

  1. removing at the head
  2. removing in between
  3. removing at the tail

We go through the list and find if the node data matches. If it does, we are given the reference of the node we want to remove. The thing is, removal is different depending on if the node to be removed is at the head, tail or within the list.

Thus, we check if the node is at the head. If it is, we do a head removal. We check if the node to be removed is at the end. If it is, we do a tail removal. And if the node is somewhere within the list, we do a standard removal.

    this.remove = function(data) {
        console.log("-- we want to remove {" + data + "} --");

        if (isEmpty()) {
          console.log("Nothing to remove because list is empty");
          return;
        }

        var traversal = head;
        if(removeLastNode(traversal)) return;
        do {
            if (dataMatches(data.toUpperCase(), traversal.data.toUpperCase())) {
              if (removeNodeAtHead(traversal)) return;
              if (removeNodeAtTail(traversal)) return;
              if (removeNodeInBetween(traversal)) return;
            }
            traversal = traversal.next;
        } while (traversal != head);
        console.log(data + " cannot be found in our list.");
    };

TimeLiner

The circular list is used by the class Timeliner. Timeliner simply has the interface to let javascript tell it what we want to do as far as showing off images.

    function TimeLiner() {
        this.circularList = new CircularList();

        // insertTimeFrame
        // currentFrame
        // nextFrame
        // previousFrame
        // setCurrentToFirstFrame
        // setCurrentToLastFrame
        // displayAllFrames 
}

So for example, if the next button is pressed, we call nextFrame. If the previous button is pressed, we can previousFrame. Each function satisfies a certain functionality from the javascript calls.

When we have the url locations of the images, we insert it like so:

    timeline.insertTimeFrame("http://xxx.xx.xxx.xxx/my-image.jpg");

Thus, our circular list keeps track of the data involved.

Displaying the Images

  1. we create a javascript Image object: document.createElement('img') or Image()

  2. we get the url of the image location: timeline.currentFrame().data

  3. assign the url of the image to the Image's src property.

  4. run the onload function. Once the image has been downloaded by the browser, the onload is called. Thus, in onload, we then use DOM's style property to set the image as the background.

    function loadImage(timeline) {
    var img = new Image(), url = timeline.currentFrame().data;
  
    img.onload = function() {
        var body = document.getElementsByTagName(BODY_CLASS)[0];
        body.style.backgroundImage = "url(" + url + ")";
    };
    img.src = url;
      
  }

Description Text

When the browser first loads, it gets all the data objects from the Mongo DB and returns it to us in the form of an array. In other words, this array is a list of objects, where each object contains name of the file, description, url location, etc.

setup.js

fetch("http://xxx.xxx.xx.xxx/pictorials")
    .then((resp) => resp.json()) // Transform the data into json
    .then(function(data) {
    // data is an array of all the pictorial data.
}

What you get back is an array of objects:

However, there is a small issue. Our CircularList's data is for Strings only, and thus cannot take the objects into it. We can surely implement the list so that it takes in objects of type Pictorial, where Pictorial has filename, url, description text, etc. However, as of now, the list just takes in Strings. When it tries to find matches, it does so by doing a string comparison:

CircularList.js

    function dataMatches (data1, data2) {
        return (data1.toUpperCase() === data2.toUpperCase());
    };

If we were to implement Pictorial object we'd do something like:

    function dataMatches (data1, data2) {
        return (data1.filename.toUpperCase() === data2.filename.toUpperCase());
    };

Hence in our front end code, we can only insert ur locations like so:

    function setupTimeline(timeline, downloadLocations) {
        for (var index = 0; index < downloadLocations.length; index++) {
            timeline.insertTimeFrame(downloadLocations[index]);
        }
    }

Now, this gives the situation of having 2 arrays:

  1. An array of image url locations.
  2. An array of all data objects {filename, description text,..etc}

When we want the description of the current image, we'd get the name of the image we're currently on, and try to find it in the data objects array. When found, we return the description.

Even though this works, its always an O(n) operation because we have to traverse through the data object array to find that file name. This is not very wieldy on an browser.

In this particular situation, a better solution is to use a hash table because in average case, its O(1). Given the name of the current image, the hash table will spit back out the pictorial data in O(1) time.

Hash Table

  • The hash table takes in file names, String types. The hash is calculated via the sum of (ASCII value of each character * index of each character). (https://www.hackerearth.com/practice/data-structures/hash-tables/basics-of-hash-tables/tutorial/)

  • If and when file names are hashed to the same location, I used a queue to keep them in order. Due to it being a Queue, we only need next reference for the Node.

  • The hash table has 31 entries. We use prime number so that if we have a particular number set such as {10, 20, 30, 40...etc}, using a prime number would give us different remainders. If we were to use say 4, then the remainders will always hash to similar locations. In other words, the reason prime numbers are used is to neutralize the effect of patterns in the keys in the distribution of collisions of a hash function. (http://shanghaiseagull.com/index.php/2017/09/11/why-do-hash-functions-use-prime-numbers/)

Thus, now when the user clicks on the description button, we get the file name, say "beijing-square". We give it to our hash table, and in constant time, we can get the description text back.

    var found = descriptionHashTable.access(fileName); // see if file name "beijing-square" exists
    var elem = document.getElementById(descriptionElementID);
    elem.innerHTML = (found) ? found.description : "";