Jonnie Grieve Digital Media: Blog

Home
by on 9th October, 2023 - 1:04pm (0)

Blog: Finishing a photo viewer application (More Posts)

I can’t really sing the praises of AI enough. Now let me just reiterate this again from the outset this technology is not perfect and not a replacement for the human brain.

The main problem with AI at the moment is AI is simply a collection and regurgitation of facts and figures. Sometimes inaccurate ones.  We are using a complex set of algorithms to generate a complex set of code; in its own nature also a complex set of problems solved by a set of implicit instructions; which is what computer programming as we know it now, is.

But at the same time, it has been a real delight in helping me solve problems with code otherwise beyond my reach.  I have come to think of it as like collaborating with someone in the same room with you.

If you have actual human beings in the room to do that with you, that is all the better.

But I work on my own and that’s okay too.

As before, I want to go through my processes of working with AI

The current state of the app

In 2021, I started work on a photo Viewer app where I created an interface that is designed to allow users to click 2 buttons so we can move through a photo image along with its digital metadata; all available at a glance.

I’ve already reacquainted myself with the project files and identified the data points. And this is what I’m starting with.

  • Photo Title: Photo 1
  • Image:  – displays a visible image
  • Filename: IMG_0010.JPG
  • Date: 01/02/2021: 00:00am
  • ISO: 6400 Aperture: f/10
  • Shutter Speed: 1/4000 secs
  • Focal Length: 55mm
  • Description Field: lorem ipsum…

The previous and next buttons are not functioning. The previous button decreases the button count. But it does not stop because there’s no edge in there to tell it when to do so.

The application should tell us what place we are in within the list of available photos (e.g. photo 2 OF N). Where “N” should be the length that is the total number of photos available to scroll.

And each time you click one of those buttons the data should iterate and display the image and its associated details.

The data files of note in this project are

  • photo_data
  • photo_data_portraits
  • photo_data_blank

which are all JSON files

The CSS Breakpoints for responsive web design are…

  • $xxl: 1500px;
  • $xl: 1499px;
  • $lg: 980px;
  • $md: 760px;
  • $sm: 480px;
  • $xs: 380px;

So now, let’s begin.

The first task was to get a reference in the browser to the number of photos available to scroll. So, to kick this off I asked ChatGPT…

Let's make a photo viewer app. I've started things off with some JavaScript but I've never been able to finish it. I think the best place to start would to get the length of the data source and display it as an integer to the screen at #num_pages. This way we have a reference to how many pages we'll be clicking through no matter how many photos are there. I've provided the HTML and JavaScript I started with

Now, I’ll show you the code I started off with rather than copy the code that was returned. The relevant markup is in the section element below.

<section>

    <article class="image">

        <h2 id="photo_title">Title</h2>

        <div class="pagination_buttons">

            <button id="btn_previous" class="pagination_btn" onclick="changePageNumNeg()" title=""><< Previous</button>
            <div id="data_pagination"> <span id="page_num">1</span> of <span id="num_pages">N</span> </div>
            <button id="btn_next" class="pagination_btn" onclick="changePageNumPlus()") title="">Next >></button>

        </div>

        <img src="assets/img/IMG_1.JPG" id="visible_photo" alt="main photo" title="main photo" role="display_visible_image">

    </article>

    <article class="photo_data">

        <div id="photo_filename">Filename:</div> <span>IMG_0010.JPG</span>
        <div id="photo_date">Date:</div> <span>01/02/2021: 00:00am</span>
        <div id="photo_iso">ISO: </div> <span>6400</span>
        <div id="photo_aperture">Aperture: </div> <span>f/10</span>
        <div id="photo_shutter">Shutter Speed: </div> <span>1/4000 secs</span>
        <div id="photo_focalLength">Focal Length: </div> <span>55mm</span>

        <div id="photo_description">Description</div>

        <span> Lorem ipsum dolor sit amet consectetur adipisicing elit. Modi dignissimos assumenda cupiditate eveniet aut repellat quos ducimus? 
        <!-- 130 chars -->
        </span>

    </article>

</section>

And the JavaScript is below.

console.log("app.js connected - photo viewer v1.1 - 14-09-2023 - 12:51");

const getPreviousBtn = document.getElementById("btn_previous");
const getNextBtn = document.getElementById("btn_next");

let num_pages = document.getElementById("num_pages")
let page_num = document.getElementById("page_num");

let numCount = 0;

const numPagesElement = document.getElementById("num_pages");

const dataSource = "assets/data/photo_data.json";
const dataSourcePortraits = "../assets/data/photo_data_portraits.json";

console.log(dataSource);
console.log(dataSourcePortraits);

async function loadData() {

    const response = await fetch(dataSource); // Adjust this based on your data source URL
    const data = await response.json();
    const totalItems = data.length;
    numPagesElement.textContent = totalItems;

    console.log(totalItems);
    console.log(data);

}

// Call the function to load data when your app starts
loadData();

function changePageNumPlus() {

if(numCount >= 1) {
    page_num.textContent = numCount;
    numCount += 1;
    /* } else {
        numCount = 18; 
    } */
}

function changePageNumNeg() {

    if(numCount <= 18) {
        page_num.textContent = numCount;
        numCount -= 1;
        /* } else {
            numCount = 1; 
        } */
}

Now, I was given some good and valid code to try and solve the problem. By valid I mean there was an issue holding me back confusing both me and ChatGPT. Despite many prompts and responses, the number of items was not being returned.

console.log(totalItems); // returned "undefiNed"
console.log(data); // returned an array of objects in JSON

Which tells us there’s an issue with the data, that can’t be picked up on by console logs or runtime errors. Between me and ChatGPT, we nailed down a number of things.

There was no change to the value of numCount…

let numCount

… in the loadData() function; only the global numCount which was set to 1.

There was no error in actually finding the JSON source, I verified that by logging data which returned an array of objects.

console.log("Data:", data);

Data:

(11) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
0: {photo_id: '1', photo_title: 'Luke', filename: .... 

Lastly, there was no let NumCount in the loadData() function. Only the global numcount which was set to 1, as this code demonstrates.

async function loadData() {
    try {
        const response = await fetch(dataSource); // Adjust this based on your data source URL
        if (!response.ok) {
            throw new Error(`Failed to fetch data: ${response.statusText}`);
        }
        const data = await response.json();

    } catch (error) {
        console.error("Error loading data:", error);
    }
}

I looked first at the data source URL in the dataSource variable.


const dataSource = "assets/data/photo_data.json";
../assets/data/photo_data.json

In order to find the data

assets/data/photo_data.json

This led me to the only possible course of action based on the data. Look again at the JSON data. All the issues were down to the structure of the JSON; not its validity but its structure. Which I’ve modified as below.

[  "photo_data":  [
      {},
      {}
    ] 
]

To get the length of the data to appear where I wanted it I removed the “photo_data” key which unnecessarily complicated things. So the correct structure for the project was

[
    {
        "photo_id": "1",
        "photo_title": "Luke",
        "filename":"IMG_0010.JPG",
        "filepath":"assets/img/IMG_0010.JPG",
        "date_taken": "22/07/2020: 20:00",
        "alt": "Luke",
        "photo_ISO": "3200",
        "aperture": "f/4.5",
        "shutter_speed": "1/25 sec",
        "focal_length": "35mm",
        "description": "Luke"

}, {
        "photo_id": "2",
        "photo_title": "Minibeast on Leaf",
        "filename":"IMG_0048.jpg",
        "filepath":"assets/img/IMG_0048.JPG",
        "date_taken": "24/07/2020: 11:39",
        "alt": "Minibeast on Leaf",
        "photo_ISO": "160",
        "aperture": "f/5.6",
        "shutter_speed": "1/80 sec",
        "focal_length": "55mm",
        "description": "Minibeast on Leaf"
}

]

not

{
"photo_data": [
    {
        "photo_id": "1",
        "photo_title": "Luke",
        "filename":"IMG_0010.JPG",
        "filepath":"assets/img/IMG_0010.JPG",
        "date_taken": "22/07/2020: 20:00",
        "alt": "Luke",
        "photo_ISO": "3200",
        "aperture": "f/4.5",
        "shutter_speed": "1/25 sec",
        "focal_length": "35mm",
        "description": "Luke"

}, {
        "photo_id": "2",
        "photo_title": "Minibeast on Leaf",
        "filename":"IMG_0048.jpg",
        "filepath":"assets/img/IMG_0048.JPG",
        "date_taken": "24/07/2020: 11:39",
        "alt": "Minibeast on Leaf",
        "photo_ISO": "160",
        "aperture": "f/5.6",
        "shutter_speed": "1/80 sec",
        "focal_length": "55mm",
        "description": "Minibeast on Leaf"

    }
]

The lesson to take away is that JSON can be valid but still not match the expected structure for your application.  And now the totalItems variable can take in the length of the JSON data inside the loadData() function.

Retrieving the Data

With the length of the photoData retrieved and used to tell us how many photos there are to be cycled through, the next task is to show all the data on each button click, one at a time.

We (that is to say the AI and I) went back and forth and tried a number of solutions to retrieve and display this data.  The code to do this is in the function below.

function generatePhotoHTML(photoData) {

    

}

In this function, we have the elements needed to place the date and add it to the DOM to the element we want.  The data itself is put into an array of objects and we use object notation

photoData.filename

to access the data and retrieve the specific properties.

function generatePhotoHTML(photoData) {
    // Create the main container for image and data
    const imageContainer = document.createElement("article");
    imageContainer.classList.add("image");


    // Create the title element
    const title = document.createElement("h2");
    title.id = "photo_title";
    title.textContent = photoData.title;


    // Create the container for photo data
    const dataContainer = document.createElement("article");
    dataContainer.classList.add("photo_data");


    // Create and populate the data elements
    const elements = [
        { id: "photo_filename", label: "Filename:", value: photoData.filename },
        { id: "photo_date", label: "Date:", value: photoData.date },
        { id: "photo_iso", label: "ISO:", value: photoData.iso },
        { id: "photo_aperture", label: "Aperture:", value: photoData.aperture },
        { id: "photo_shutter", label: "Shutter Speed:", value: photoData.shutter },
        { id: "photo_focalLength", label: "Focal Length:", value: photoData.focalLength },
    ];


    elements.forEach((elementData) => {
        const element = document.createElement("div");
        element.id = elementData.id;
        element.textContent = `${elementData.label} ${elementData.value}`;
        dataContainer.appendChild(element);
    });


    // Create the description element
    const description = document.createElement("div");
    description.id = "photo_description";
    description.textContent = photoData.description;


    // Append all elements to the main container
    dataContainer.appendChild(description);
    imageContainer.appendChild(title);
    imageContainer.appendChild(dataContainer);


    return imageContainer;
}

Unfortunately, there was a runtime error.

app.js:53 Uncaught ReferenceError: data is not defined

This error is happening due to a problem with scope. We have tried to access the value of a variable where it doesn’t have access or scope to see it,

How to fix it?

First, make sure const let in the global scope.

// define data in the local scrope. 
let data;

Then remove the datatype keyword from the data variable in local scope.

data = await response.json();

    if (Array.isArray(data)) {

    }

Here is the modified code.

async function loadData() {
    try {
        const response = await fetch(dataSource); // Adjust this based on your data source URL
        if (!response.ok) {
            throw new Error(`Failed to fetch data: ${response.statusText}`);
        }
        
        data = await response.json();
        
        if (Array.isArray(data)) {
            const totalItems = data.length;
            numPagesElement.textContent = totalItems;
            page_num.textContent = numCount; // Initially set the page number to 1


            console.log("Total Items:", totalItems);
            console.log("Data:", data);
        } else {
            console.error("Data is not an array:", data);
        }
    } catch (error) {
        console.error("Error loading data:", error);
    }
}

We should now start seeing a state where at least some of the data is appearing.

Or if it isn’t, we have the means to look into why.   Let’s look at one example with the photo_title key.

[
{
    . . ., 
    "photo_title": "Sunny. Sharp. Bright. Colourful",
    . . .

}, {

    . . .,
    "photo_title": "Sunny. Sharp. Bright. Colourful",
    . . . 

]

We have full control over where this data goes on the page by selecting the element and declaring the photo_title property as the value of the element’s text content.

// Update placeholders with JSON data
document.getElementById("photo_title").textContent = currentData.photo_title;

Let’s now expland the functionality I’ve so it can be used indefinitely.

function changePageNumPlus() {
    if (numCount < parseInt(numPagesElement.textContent)) {
        numCount += 1;
        page_num.textContent = numCount;

        // Get the data for the current page (assuming JSON data is 0-based index)
        const currentData = data[numCount - 1];

        // Update placeholders with JSON data
        document.getElementById("photo_title").textContent = currentData.photo_title;
    . . . 

}

function changePageNumNeg() {
    if (numCount > 1) {
        numCount -= 1;
        page_num.textContent = numCount;

        // Get the data for the current page (assuming JSON data is 0-based index)
        const currentData = data[numCount - 1];

        // Update placeholders with JSON data
        document.getElementById("photo_title").textContent = currentData.photo_title;
}

List

Just a few things to tidy things up. First, the function below returns a simple list of filenames.

<script>

// filenameList.js

document.addEventListener("DOMContentLoaded", () => {
    // This code will execute when the DOM is fully loaded

    // Define your data source URL here
    const dataSource = "assets/data/photo_data.json"; // Adjust the URL as needed

    const filenameList = document.getElementById("filename_list");

    async function loadData() {
    try {
        const response = await fetch(dataSource);
        if (!response.ok) {
            throw new Error(`Failed to fetch data: ${response.statusText}`);
        }
    const data = await response.json();

    if (Array.isArray(data)) {
        data.forEach((item) => {
            const li = document.createElement("li");
            li.textContent = item.filename;
            filenameList.appendChild(li);
        });
    } else {
        console.error("Data is not an array:", data);
    }
    } catch (error) {
        console.error("Error loading data:", error);
    }
}

// Call the loadData function when the page is loaded
loadData();
});

</script>

To retrieve only the filename property and append it as a child to the list element, make sure you save only the text content and then append that text content to the list container.

const filenameList = document.getElementById("filename_list");

li.textContent = item.filename;
filenameList.appendChild(li);

Finally, we’re going to take care of some UI concerns.

It’s all very well paginating through separate sets of data. But it’s no good when some sets have more characters of data than others. You get that jarring change of width in the text boxes because the container has to fit in varying lengths of content.

The way to fix this is to apply specific widths to various elements.

e.g.

.image #data_pagination {

width: 60px
}
page_num {

    display: inline-block;
    width: 20px;

}

 

This post has been assigned to the following categories

    Leave a Reply

    Your email address will not be published. Required fields are marked *