15

I'm trying to make a table of images loaded from a json (not a real json, more like a javascript array) and every time the json changes (when I append a new image to the json file with some script, I want that my table of images to upload as well.

This is the json format:

[{
    "image": "images/set_1_UTC+03.jpg",
    "weight": 101
}, {
    "image": "images/set_1_UTC+03.jpg",
    "weight": 102
}, {
    "image": "images/set_1_UTC+03.jpg",
    "weight": 103
}, {
    "image": "images/set_1_UTC+03.jpg",
    "weight": 104
}]

For this I use isotope. I managed to achieve everything I mentioned above, the only problem is that I wanted to make the images clickable and whenever I click one of the image, to have it in a bigger size and when I click it again to get back to the small size. Here is the code:

<script>
    var previous = 0;
    var current = 0;
    loadJSON(function(response) {
        // Parse JSON string into object
        current = JSON.parse(response);
    });

    function loadJSON(callback) {
        var xobj = new XMLHttpRequest();
        xobj.overrideMimeType("application/json");
        xobj.open('GET', 'data.json', true); // Replace 'my_data' with the path to your file
        xobj.onreadystatechange = function() {
            if (xobj.readyState == 4 && xobj.status == "200") {
                // Required use of an anonymous callback as .open will NOT return a value but simply returns undefined in asynchronous mode
                callback(xobj.responseText);
            }
        };
        xobj.send(null);
    }

    let lengthOfprevious = previous.length;

    setInterval(function() {
        loadJSON(function(response) {
            // Parse JSON string into object
            current = JSON.parse(response);
        });
        previous = current;
        if (lengthOfprevious != current.length) {
            UpdateBody(lengthOfprevious);
        }
        lengthOfprevious = previous.length;
    }, 5000);

    function UpdateBody(startIndex) {
        var newElements = "";
        for (let i = startIndex; i < previous.length; i++) {
            $(document).ready(function() {
                newElements = "";
                newElements +=
                    '<div class="photo element-item">' +
                    '<a href="' + previous[i].image + '"><img class="small-image" src="' + previous[i].image + '"/></a>' +
                    '<a class="weight">' + previous[i].weight + '</a></div>';
                var $newElems = $(newElements);

                $('#container').append($newElems).imagesLoaded(function() {

                    $('#container').isotope('insert', $newElems);
                });
            });
        }

        // ============//
        $(function() {
            var $container = $('#container'),
                $photos = $container.find('.photo'),
                $loadingIndicator = $('<div class="loading"><span><img src="http://i.imgur.com/IE7iw.gif" /></span></div>');
            // trigger Isotope after images have loaded
            $container.imagesLoaded(function() {
                $container.isotope({
                    itemSelector: '.photo',
                    masonry: {
                        columnWidth: 200
                    }
                });
            });
            // shows the large version of the image
            // shows small version of previously large image
            function enlargeImage($photo) {
                $photos.filter('.large').removeClass('large');
                $photo.addClass('large');
                $container.isotope('reLayout');
            }

            $photos.find('a').click(function() {
                var $this = $(this),
                    $photo = $this.parents('.photo');

                if ($photo.hasClass('large')) {
                    // already large, just remove
                    $photo.removeClass('large');
                    $container.isotope('reLayout');
                } else {
                    if ($photo.hasClass('has-big-image')) {
                        enlargeImage($photo);
                    } else {
                        // add a loading indicator
                        $this.append($loadingIndicator);

                        // create big image
                        var $bigImage = $('<img>', {
                            src: this.href
                        });

                        // give it a wrapper and appended it to element
                        $('<div>', {
                                'class': 'big-image'
                            })
                            .append($bigImage)
                            .appendTo($this)
                            .imagesLoaded(function() {
                                $loadingIndicator.remove()
                                enlargeImage($photo);
                            });
                        // add a class, so we'll know not to do this next time
                        $photo.addClass('has-big-image');
                    }
                }
                return false;
            });
        });
    }
</script>

The problem is that after setInterval runs once, everything works as expected, when it runs again, the images aren't clickable anymore. If I move the part after the // ============// tag in the for, only the last image is clickable.

I cannot figure out the solution for this (I'm a beginner with javascript). Can someone point me in the right direction?

Update : Here you can get an archive of the project so that one can run it locally.

You need to manually copy and pasteduplicate some lines from the data file in order for the demo to work because it searches for the difference of the previous state and the current state of the data file. The problem is that it is clickable at first, and then it's not anymore.

Zac
  • 1,279
  • 3
  • 17
  • 25
AMayer
  • 405
  • 4
  • 19

3 Answers3

3

I found a couple of things that need to be modified in your code:

  1. Why is $(document).ready used more than 1 time?
  2. No need to use $(function()
  3. Because I see the photo class is being generated dynamically on run time and container class is already in DOM, I have modified the click event

    $container.on('click', '.photo a', function() {
    

Edit Please note code is not tested; I have just code corrected it.

Final updated code is following:

var previous = 0;
var current = 0;
loadJSON(function(response) {
    // Parse JSON string into object
    current = JSON.parse(response);
});

function loadJSON(callback) {
    var xobj = new XMLHttpRequest();
    xobj.overrideMimeType("application/json");
    xobj.open('GET', 'data.json', true); // Replace 'my_data' with the path to your file
    xobj.onreadystatechange = function() {
        if (xobj.readyState == 4 && xobj.status == "200") {
            // Required use of an anonymous callback as .open will NOT return a value but simply returns undefined in asynchronous mode
            callback(xobj.responseText);
        }
    };
    xobj.send(null);
}
let lengthOfprevious = previous.length;

setInterval(function() {
    loadJSON(function(response) {
        // Parse JSON string into object
        current = JSON.parse(response);
    });
    previous = current;
    if (lengthOfprevious != current.length) {
        UpdateBody(lengthOfprevious);
    }
    lengthOfprevious = previous.length;
}, 5000);

function UpdateBody(startIndex) {
    var newElements = "",
        $container = $('#container'),
        $photos = $container.find('.photo'),
        $loadingIndicator = $('<div class="loading"><span><img src="http://i.imgur.com/IE7iw.gif" /></span></div>');

    $(document).ready(function() {
        for (let i = startIndex; i < previous.length; i++) {
            newElements = "";
            newElements +=
                '<div class="photo element-item">' +
                '<a href="' + previous[i].image + '"><img class="small-image" src="' + previous[i].image + '"/></a>' +
                '<a class="weight">' + previous[i].weight + '</a></div>';

            var $newElems = $(newElements);

            $container.append($newElems).imagesLoaded(function() {
                $container.isotope('insert', $newElems);
            });
        }
        $container.imagesLoaded(function() {
            $container.isotope({
                itemSelector: '.photo',
                masonry: {
                    columnWidth: 200
                }
            });
        });
        // shows the large version of the image
        // shows small version of previously large image
        function enlargeImage($photo) {
            $photos.filter('.large').removeClass('large');
            $photo.addClass('large');
            $container.isotope('reLayout');
        }

        $container.on('click', '.photo a', function() {
            var $this = $(this),
                $photo = $this.parents('.photo');

            if ($photo.hasClass('large')) {
                // already large, just remove
                $photo.removeClass('large');
                $container.isotope('reLayout');
            } else {
                if ($photo.hasClass('has-big-image')) {
                    enlargeImage($photo);
                } else {
                    // add a loading indicator
                    $this.append($loadingIndicator);

                    // create big image
                    var $bigImage = $('<img>', {
                        src: this.href
                    });

                    // give it a wrapper and appended it to element
                    $('<div>', {
                            'class': 'big-image'
                        })
                        .append($bigImage)
                        .appendTo($this)
                        .imagesLoaded(function() {
                            $loadingIndicator.remove()
                            enlargeImage($photo);
                        });

                    // add a class, so we'll know not to do this next time
                    $photo.addClass('has-big-image');

                }
            }

            return false;
        });

    });
}
Andrew Myers
  • 2,567
  • 5
  • 26
  • 36
A. N. SINHA
  • 106
  • 5
  • Please find attached all the files in an archive so that you can run it locally. It's at the end of the post. I cannot make a jsFiddle because there is a text doc that needs to be updated manually so that the demo would work. – AMayer Jun 13 '18 at 11:32
1

You can replace your script block with the following script block and try to run:

<script>
    var previous = [];
    var current = [];

    function loadJSON(callback) {
        var xobj = new XMLHttpRequest();
        xobj.overrideMimeType("application/json");
        xobj.open('GET', 'data.json', true); // Replace 'my_data' with the path to your file
        xobj.onreadystatechange = function() {
            if (xobj.readyState == 4 && xobj.status == "200") {
                // Required use of an anonymous callback as .open will NOT return a value but simply returns undefined in asynchronous mode
                callback(xobj.responseText);
            }
        };
        xobj.send(null);
    }

    function start(){
        loadJSON(function(response) {
            previous = current;
            current = JSON.parse(response);
            if (previous.length != current.length) {
                UpdateBody(current);
            }
        });
    }

    function UpdateBody(data) {
        var newElements = "";
        for (var i in data) {            
            newElements +=
                '<div class="photo element-item">' +
                '<a href="' + data[i].image + '"><img class="small-image" src="' + data[i].image + '"/></a>' +
                '<br/>' +
                '<a class="weight">' + data[i].weight + '</a>' +
                '</div>';
        }

        if(newElements!=""){
            var $newElems = $(newElements);

            $('#container').append($newElems).imagesLoaded(function() {
                $('#container').isotope('insert', $newElems);
            });
        }
    }

    $(document).ready(function(){
        start();

        setInterval(start, 5000);

        var $container = $('#container'),
                $photos = $container.find('.photo'),
                $loadingIndicator = $('<div class="loading"><span><img src="http://i.imgur.com/IE7iw.gif" /></span></div>');
        // trigger Isotope after images have loaded
        $container.imagesLoaded(function() {
            $container.isotope({
                itemSelector: '.photo',
                masonry: {
                    columnWidth: 200
                }
            });
        });
        // shows the large version of the image
        // shows small version of previously large image
        function enlargeImage($photo) {
            $container.find('.photo.large').removeClass('large');
            $photo.addClass('large');
            $container.isotope('layout');
        }

        $(document).on('click','#container .photo a',function() {
            var $this = $(this),
                $photo = $this.parent('.photo');

            if ($photo.hasClass('large')) {
                // already large, just remove
                $photo.removeClass('large');
                $container.isotope('layout');
            } else {
                if ($photo.hasClass('has-big-image')) {
                    enlargeImage($photo);
                } else {
                    // add a loading indicator
                    $this.append($loadingIndicator);

                    // create big image
                    var $bigImage = $('<img>', {
                        src: this.href
                    });

                    // give it a wrapper and appended it to element
                    $('<div>', {
                            'class': 'big-image'
                        })
                        .append($bigImage)
                        .appendTo($this)
                        .imagesLoaded(function() {
                            $loadingIndicator.remove()
                            enlargeImage($photo);
                        });
                    // add a class, so we'll know not to do this next time
                    $photo.addClass('has-big-image');
                }
            }
            return false;
        });
    });
</script>

I have changed many things on your code. which helped me organize the code. but the most important part of click not working is the following:

$(document).on('click','#container .photo a',function() {

i attached the click event handler like the above because the elements are getting added after page load. so it needs to work with elements which are added later then the time when the handler is attached.

you can read more about jQuery.on function and try to understand how it works.

Inus Saha
  • 1,795
  • 9
  • 17
0

the problem in $photos.find('a').click( - this bind click to existing items, so new its does not fire click. You should you event delegation&

$('.items_wrap_class').on('click', 'a', function() { /* all existings an new items clicks will fire this */ })

UPD

In your case do delegate #container .photo a click handling.

replace $photos.find('a').click( function() { ... })

with

$('#container').on('click', '.photo a', function() { ... })

Evgeniy
  • 2,895
  • 3
  • 18
  • 35
  • I don't quite understand. Could you please explain more how should I modify my code? – AMayer Jun 12 '18 at 15:45
  • I did. It still doesnt work after I add new data. Please find attached all the files in an archive so that you can run it locally. It's at the end of the post. I cannot make a jsFiddle because there is a text doc that needs to be updated manually so that the demo would work. – AMayer Jun 13 '18 at 11:38