Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

JavaScript

Jonathan Grieve
MOD
Jonathan Grieve
Treehouse Moderator 91,253 Points

What is the DRY code I need for this JSON problem?

I have a JSON array of objects which holds data for Categories of photos. I'm retrieving all of the data successfully and I have it working but I want to convert it to a DRY Design pattern. At the moment I'm using the same functions for the countless categories, which was fine before but as my website continues to expand and I've been trying to convert the function to the DRY coding pattern.

So here's the page I'm developing to try and get around it. https://photography.jonniegrieve.co.uk/main_dry.html

This is how my JSON looks.

{ 

    "pets": [ 
        {
            "url": "",
            "caption": "",
            "class": "lazy",
            "lightbox": "pets",
            "alt":"" 
        }, {
            "url": "",
            "caption": "",
            "class": "lazy",
            "lightbox": "pets",
            "alt":""
    }
     ], 

    "favourites": [

     //snip

    ],   

    "best_of": [

    //snip

    ]
}

And below is my attempt to write one function that does all the data retrieval.

app.js
//CATEGORY: get category data
jQuery.getJSON('../assets/data/canon_photos.json', function( photoData, jsonCategory, logMessage, categoryPoster, appendCategory, categoryCount ) { 

    function getPhotosData() {


            let getCategory = photoData.jsonCategory.length;
            console.log("**Sum Category Total** " +  "\n>>>  " + categoryTotal + "\n\n");


            console.log(logMessage + ": Category Total (" + getCategory + ")");

            jQuery(`<a href="${ photoData.jsonCategory[0].url }" data-lightbox="${ photoData.jsonCategory[0].lightbox }" data-title="${ photoData.jsonCategory[0].caption}">

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

            </a>`).appendTo( appendCategory );

            for (let j=1; j < getCategory; j++) { 

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

                ).appendTo( appendCategory );
            }

        };

        getPhotosData( "favourites", favourites, "FAVOURITES", "Favourites Category poster", "#favourites_category" );
    });

It currently returns the following in the console.

Uncaught TypeError: Cannot read property 'length' of undefined
    at getPhotosData (canon_dry.js:210)
    at Object.success (canon_dry.js:232)
    at i (jquery.min.js:2)
    at Object.fireWith [as resolveWith] (jquery.min.js:2)
    at y (jquery.min.js:4)
    at XMLHttpRequest.c (jquery.min.js:4)

Here's my HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Jonathan Grieve | Amateur Photography</title>

    <!-- Styles  -->
    <link rel="stylesheet" type="text/css" href ="styles/style.css" />

    <!-- Slick Styling-->
    <link rel="stylesheet" type="text/css" href ="assets/slick.css" />
    <link rel="stylesheet" type="text/css" href ="assets/slick/slick-theme.css" />

    <!-- Lightbox-->
    <link href="assets/lightbox/dist/css/lightbox.css" rel="stylesheet" />

    <!-- Google Font-->
    <link href="https://fonts.googleapis.com/css?family=Merriweather|Odibee+Sans|Quicksand&display=swap" rel="stylesheet">

    <!-- Favicon -->
    <link rel="icon" href="favicon.png" type="image/png">

    <!-- meta tags -->
    <meta name="description" content="">
    <meta name="keywords" content=""> 
    <meta name="image" content="">

    <!-- FACEBOOK: Open Graph -->
    <meta property="og:title" content="Jonathan Grieve | Amateur Photography">
    <meta property="og:description" content="My name is Jonathan and this is my website for my amateur photography.  I hope you enjoy my photos!">
    <meta property="og:image" content="">
    <meta property="og:url" content="">

    <!-- TWITTER: Open Graph -->
    <meta name="twitter:title" content="">
    <meta name="twitter:description" content=">
    <meta name="twitter:image" content="">
    <meta name="twitter:card" content="">

    <!-- Canonical link -->
    <link rel="canonical" href="">

</head>
<body>

    <header>

        <h1 id="top">Jonnie Grieve Photography</h1>

        <ul class="jump_links">
            <li><a href="#latest">Latest</a></li>
            <li><a href="#pets">Pets</a></li>
            <li><a href="#general">General</a></li>
            <li><a href="#specific">Specific</a></li>
        </ul>

        <div class="ham" onclick="openNav()" role="open-menu">&#9776;
        </div>

        <div id="mySidenav" class="sidenav" role="button" style="width: 0px;">

            <div class="menu">

                <span class="close_menu" onClick="closeNav()" role="close-menu">X</span> 

                <ul>
                    <li><a href="index.html">Home</a></li>
                    <li><a href="lockdown/main.html">Huawei P Smart (March to July)</a></li>
                    <li><a href="#top">Back to Top</a></li>
                    <li><a href="https://www.facebook.com/jgphotography1/l" target="blank" id="facebook_link">Facebook Page<img src="images/facebook.png" alt="Link to Facebook" /></a></li>

                </ul>
            </div>
        </div>

    </header>

    <main class="container">

        <h2>Photos by Category to view!</h2>        

        <section class="category_panel">  

            <h3 id="latest">The Latest <span><a href="#top">(Back to top)</a> </span></h3>           

            <div class="photo_set">

                <article id="favourites_category" class="category category_favourites">                      

                </article>              

                <h2 id="favourites" class="category-title">Favourites (<span id="favourites_count"> N </span>)</h2>

            </div>         

    </main>

    <!-- Footer -->
    <footer>
        <p>&copy; Jonnie Grieve Digital Media (2020)</p>
    </footer>


    <!-- scripts -->
    <script type="text/javascript" src="scripts/jquery.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
    <script type="text/javascript" src="assets/slick/slick.min.js"></script>
    <script type="text/javascript" src="assets/slick/slick.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vanilla-lazyload@17.1.0/dist/lazyload.min.js"></script>    
    <script type="text/javascript" src="scripts/canon_dry.js"></script>

    <script type="text/javascript">

        /* Add lazy loading Object */
        var lazyLoadInstance = new LazyLoad({
            elements_selector: ".lazy",
            // ... more custom settings?
            threshold: 1072,
        });

    </script>

    <script src="assets/lightbox/dist/js/lightbox.js"></script>

</body>
</html>

I'm trying to find the way to reference the category properties so I can call the getCategoryData functions as below and generate the markup that way.

  getPhotosData( "favourites", favourites, "FAVOURITES", "Favourites Category poster", "#favourites_category", "favourites_count" );

1 Answer

Steven Parker
Steven Parker
231,007 Points

It looks like you may have some function/argument mismatches here.

The jQuery documentation indicates that jQuery.getJSON takes as an optional 2nd (or 3rd) argument a "success" function, and that function will be given 3 arguments: a data object, a textStatus string, and a jqXHR object.

But the function being defined above as the success callback takes six arguments named photoData, jsonCategory, logMessage, categoryPoster, appendCategory, categoryCount. This would seem to be a calling syntax mismatch.

Also, inside that function, there's local function definition for getPhotosData which takes no arguments.

But when it is called right after the definition, it is being passed 5 arguments (4 of them are string literals). This also appears to be a calling syntax mismatch.

Hopefully, addressing these issues will resolve your issues or at least get you started on fixing the others.

Jonathan Grieve
Jonathan Grieve
Treehouse Moderator 91,253 Points

Hi Steven.

Thanks for replying. I know it looks like a shoddily written function and probably is. Haha I still after all these years have so much to learn but that’s a story for another day.

There’s a reason for the string literal arguments though. The idea is they’re passed into the variables so they become values for ID attributes and the JSON data does the rest. Which all works if I’m copying and pasting the markup and I’ve simply tried to adapt it so I only need to write the function once. I and I can see those parameters work because of the syntax highlighting.

Thank you for at least trying. I’ll have a closer look at the documentation and see if o can do something with the getJSON arguments.

Could be I just need to go back again and try another way. 🙂

Steven Parker
Steven Parker
231,007 Points

If you want those string literals to get plugged into variables, be sure to name those variables where you define the function as arguments (right now it has no arguments).

Jonathan Grieve
Jonathan Grieve
Treehouse Moderator 91,253 Points

Well Steven I had another good go. ;)

I did manage to get broken images to appear with their unique alt tags, which is progress compared to what it was before but it remains a tough ask. I had hoped that by simply passing in the index of categoryData would be enough, so it would be enough, or even changing the values of categoryData to string literals would help. but it just returns undefined.

//CATEGORY: Favourites
jQuery.getJSON('../assets/data/canon_photos.json', function( photoData ) { 

        let favourites_count = document.querySelector("#favourites_category");
        let best_of_count = document.querySelector("#favourites_count");

        favouritesCount = photoData.favourites.length;
        bestOfRestCount = photoData.best_of_rest.length;


        favourites_count.textContent = favouritesCount;
        best_of_count.textContent = bestOfRestCount;

        //let getCategoryCount;

        let categoryData = [
            photoData.favourites,
            photoData.best_of_rest,
        ];

        let categoryCounts = [
            photoData.favourites.length,
            photoData.best_of_rest.length
        ];

            //getCategoryCount = photoData.favourites.length;
            //getBestCount = photoData.best_of_rest.length;

        function getCategoryData( logMessage, categoryIndex, dataIndex, poster_alts, append_id ) {        

            for(i=0; i < categoryCounts.length; i++ ) {

                console.log(logMessage + ": Category Total (" + categoryCounts[categoryIndex] + ")");

            }    

            jQuery(`<a href="${ categoryData[dataIndex].url }" data-lightbox="${ categoryData[dataIndex].lightbox }" data-title="${ categoryData[dataIndex].caption}">

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

            `).appendTo(append_id);

        }  

        getCategoryData( "Favourites...", [0], [0], "Favourites alts", favourites_count);
        getCategoryData( "best...", [1], [1], "best of alts", best_of_count );

})