Jonnie Grieve Digital Media: Blog

Home
by on 30th August, 2023 - 12:44pm (0)

Blog: More organised scripts for my photography website (More Posts)

In today’s blog, I want to go further in my quest to remove code bloat in my ever-expanding photography website. Earlier this year I refactored the website to be PHP based instead of HTML so that the website would be easier to maintain. Now I can go into various templates and make changes in one place rather than having to edit multiple HTML pages.

I also spent time refactoring the styles behind the scenes to a structured network of SASS files to make that easier to maintain too. Today I’m turning my attention to the script files.

Brief Recap

On my photography website, I currently have JavaScript stored in a structured way like this:

2023/

scripts/2023/canon_2023.js
scripts/2023/canon_18_200.js
scripts/2023/canon_75_300.js

2022/

scripts/2022/canon_2022.js
scripts/2022/canon_18_200.js
scripts/2022/canon_75_300.js

And I have similar scripts in place for, 2021, 2020, and so on. And using PHP conditions, only one of these script files per page is linked on each page which means I don’t have console errors showing up for script files that don’t need to be there.

Each of these scripts has hundreds of lines of code. To give you an idea of the kind of codebase I started with, let me set it out below.

First, I select the elements for category counts

/**
*
* Select project count elements via the DOM
*/

//january
let adventure_jan_durhamcity = document.querySelector("#jan_durham_count");
let adventure_jan_hardwickpark = document.querySelector("#jan_hardwickpark_count");
let adventure_jan_whartonpark = document.querySelector("#jan_whartonpark_count");
let adventure_jan_sealsands = document.querySelector("#jan_sealsands_count");

Then I store the count of individual category totals/lengths as an integer (whole number).

/**
*
* Store count of individual category totals/lengths
*/

// january
let durham_city = photoData.jan_durham_city.length;
let hardwick_park = photoData.jan_hardwick_park.length;
let wharton_park = photoData.jan_wharton_park.length;
let seal_sands = photoData.jan_seal_sands.length;

Again, I get the total number of photos in the category as an integer and make sure the text content is visible where it should be on the screen.

/**
*
* Send individual category counts to the browser
*/

// january
adventure_jan_durhamcity.textContent = durham_city;
adventure_jan_hardwickpark.textContent = hardwick_park;
adventure_jan_whartonpark.textContent = wharton_park;
adventure_jan_sealsands.textContent = seal_sands;

So this is how we actually display the number on the screen.

Finally, I have a raft of other JSON requests each with their own unique text content that shows up when you over the images.

// CATEGORY: ADVENTURE 1 - January - Durham
jQuery.getJSON('../../data/2023/canon_2023.json', function(photoData) {

    let initial_items = photoData.jan_durham_city.length;
    console.log("January: Durham City - (04-01-2023) Total => (" + photoData.jan_durham_city.length + ")");

    // get data
    jQuery(`<a href="${ photoData.jan_durham_city[0].url }" class="lazy category_image" data-lightbox="${ photoData.jan_durham_city[0].lightbox }" data-title="${ photoData.jan_durham_city[0].caption}">

        <img id="image_poster" href="${ photoData.jan_durham_city[0].url }" alt="View Durham City (January) Category" title="View Durham City (January) Category" src="${ photoData.jan_durham_city[0].url }" class="open_modal" />

            <div class="category_text_container">
                <div class="category_text">

                    <h4>Durham City</h4>
                    <p>What better way to start another year of "Photographic Expeditioning" than a visit to the great City of Durham (<span class="date_stamp">04-01-2023</span>) - (<span class="lens_type">18_200mm</span>) </p>

                </div>
          </div>

</a>`).appendTo('#adventure2023_jan_durhamcity');

    for (let i=1; i < initial_items; i++) {

        jQuery(
            `<a href="${ photoData.jan_durham_city[i].url }" class="lazy" class="image ${ photoData.jan_durham_city[i].class }" title="${ photoData.jan_durham_city[i].caption }" data-lightbox="${ photoData.jan_durham_city[i].lightbox }" data-title="${ photoData.jan_durham_city[i].caption }"></a>`

        ).appendTo('#adventure2023_jan_durhamcity');
    }
})
// CATEGORY: ADVENTURE 2  . . .
/* And so on.... */

Reducing the Code bloat

As of the date (29-08-2023), I have duplicated this JSON method over 40 times in order to get the data for over 40 adventure categories. And that is just for this script file on this one page (canon_2023.js). So. Much. Code bloat.

There has to be a better way than this.

And there is.

In the past, I have tried to implement the structural technique in my code where I put all the code that writes photo category content and individual photo categories into a single function with multiple arguments and then call this function multiple times.  Like this…

function photoContentFunction(parameterOne, ParameterTwo, MoreParameters) {
    // Function Content
}
photoContentFunction( "argument 1", "Argument 2", "more arguments");

And I wasn’t far wrong with this technique. Most of the information and parameters were perfectly callable with String arguments. Where I always got stuck was how to import the references to the JSON files that have the photo data.

e.g.

"jan_durham_city": [

    {
        "url": "https://jg-photography.s3.eu-west-2.amazonaws.com/photography/images/2023/canon_2023/jan/durham_city/IMG_0110.JPG",
        "caption": "Durham City - 04-01-2023",
        "class": "lazy",
        "lightbox": "jan_durham_city",
        "alt": ""
} ]

I couldn’t find a way to use something like photoData[i].caption in a function call. I spent a great deal of time on it before I considered myself beaten.

Working out a solution

In programming, there is almost always a way. More than one way. And I asked ChatGPT to look at the problem for me and see what it could come up with. The AI got a bit mixed up in what it came up with, but the logic was sound and it was enough for me to be able to study the code, work out what was happening, add my own tweaks, and finally integrate it into my codebase.

The first point of difference is that both sets of functions require at least one parameter. jQuery’s getJSON() method takes the path to the JSON file as one argument and a function as another. This function is an anonymous function that takes data as a single parameter of its own.

jQuery.getJSON('../../data/2023/canon_2023.json', function(data) {

    . . .
}

In the code below, photoData is another important parameter and its name goes in as an array as a property in the data object.

function createAdventureCategory( otherParameters, photoData, otherParameters) {

    jQuery.getJSON('../../data/2023/canon_2023.json', function(data) {

    }

}

And here’s where the magic happens…

/* dynamically insert the value of a URL (json key) from JSON data into a string */
data[photoData][0].url

In this expression what’s happening is that it fetches the URL key of the first image from the specified category (photoData) within the data object, with it all interpolated inside a template literal. This means I don’t have to use an argument inside any function call as I previously tried to do. All the logic is handled in one function embedded in a getJSON() method.

Now we can look at how to call the function with all its parameters. We need to call the function as many times as there are photo categories.

/* Call the function for each adventure category for parameters - 
categorySelector, categoryName, photoData */
createAdventureCategory("#adventure2023_jan_durhamcity", "January: Durham City", "jan_durham_city");

To explain what these parameters do

  • categorySelector – This is how the JSON knows where to put the text content it finds, into the element with the ID selector. For example  #adventure2023_jan_durhamcity
<div class="photo_set">

    <article id="adventure2023_jan_durhamcity" class="category category2023_jandurhamcity">

    </article>

</div>
  • CategoryName – Shows in the console logs as was originally written
    • console.log(`${categoryName} - (${categoryDate}) Total => (${categoryItems})`);
  • photoData – takes the parameter of the lightbox key in the JSON data, so the JavaScript interpreter knows where new categories of photo data begin and end and don’t bleed into each other.

Resolving a mixup in the AI where it tried to get the data for category dates from a JSON field.

One of the problems I had to solve was that the solution I was given tried to use one of the interpolation expressions to look for a date key that didn’t exist. resulting in the word “undefined” appearing in the JavaScript console.

January: Durham City - undefined Total => (15)

Let’s add a new parameter to the code, of the category date, so we can add this unique information to the function calls.

/* Call the function for each adventure category for the following parameters
- categoryId, categorySelector, categoryName, photoData, categoryDate  */
function createAdventureCategory(categorySelector, categoryName, photoData, categoryDate) {
    
}

createAdventureCategory("#adventure2023_jan_durhamcity", "January: Durham City", "jan_durham_city", "01-01-2023");

let categoryItems = data[photoData].length;
console.log(`${categoryName} - (${categoryDate}) Total => (${categoryItems})`);

This will fix the undefined error, with the function able to see the categoryDate value and display it to the console.

January: Durham City - 01-01-2023 Total => (15)

Final result

This solution allows me to reduce the lines of code used in my script files by a considerable amount. One file alone reduces line numbers from approximately 1700 lines to approximately 300 – with the potential for tremendous page load savings across the board.

It allows me to separate concerns in a clear and distinct way. So in one section, elements and selected values are stored. In the next section, one function is defined that controls the data retrieval; and the final section calls the same function with unique arguments passed in as many times as are needed.

// parameters: categoryDate categoryTitle, categoryText, lensType
function createAdventureCategory(categorySelector, categoryName, photoData, categoryDate, categoryTitle, categoryText, lensType) {

    jQuery.getJSON('../../data/2023/canon_2023.json', function(data) {
        let categoryItems = data[photoData].length;
        console.log(`${categoryName} - (${categoryDate}) Total => (${categoryItems})`);

        // Creating the category content
        jQuery(`<a href="${data[photoData][0].url}" class="lazy category_image" data-lightbox="${data[photoData][0].lightbox}" data-title="${data[photoData][0].caption}">

        <img id="image_poster" href="${ data[photoData][0].url }" alt="alt" title="title" src="${ data[photoData][0].url }" class="open_modal" />

        <!-- Category image and text content here -->
        <div class="category_text_container">

        <div class="category_text">
            <h4>${ categoryTitle }</h4>
            <p>${ categoryText } <span class="date_stamp">( ${ categoryDate }</span>) - (<span class="lens_type">${ lensType }</span>) </p>
        </div>
    </div>
</a>`).appendTo(categorySelector);

    for (let i = 1; i < categoryItems; i++) {
        jQuery(`<a href="${data[photoData][i].url}" class="lazy" class="image ${data[photoData][i].class}" title="${data[photoData][i].caption}" data-lightbox="${data[photoData][i].lightbox}" data-title="${data[photoData][i].caption}"></a>`).appendTo(categorySelector);}
    });
    }

// Call the function for each adventure category - categoryId, categorySelector, categoryName, photoData, categoryDate, categoryTitle, categoryText, lensType
createAdventureCategory("#adventure2023_jan_durhamcity", "January: Durham City", "jan_durham_city", "01-01-2023", "Durham City", "<p>DH</p> <p>Text 1", "18_200mm");
createAdventureCategory("#adventure2023_jan_hardwickpark", "January: Hardwick Park", "jan_hardwick_park", "01-01-2023", "Hardwick Park", "<p>HP</p> <p>Text 2", "18_200mm");
createAdventureCategory("#adventure2023_jan_whartonpark", "January: Wharton Park", "jan_wharton_park", "01-01-2023", "Wharton Park", "<p>WP</p> <p>Text 3", "18_200mm");
createAdventureCategory("#adventure2023_jan_sealsands", "January: Seal Sands", "jan_seal_sands", "01-01-2023", "Seal Sands", "<p>SS</p> <p>Text 4", "18_200mm");

So all this serves to prove that it is possible after all to refactor the code in this fashion to reduce code duplication and still work the same way. All that remains now is to work on applying this new logic to every applicable script in the codebase.

I won’t bore you all with that. 😉

This post has been assigned to the following categories

    Uncategorised

    Leave a Reply

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