Jeremy Jeremy - 3 months ago 9
Javascript Question

How to loop over an associative array and present chunks of data with a delay between chunks

I am working on presenting birthdays on a page. What I am trying to achieve is to have the department as a title and then show 3 birthdays under it and loop over the department until all birthdays are shown (with a 5 second delay so they can be read), then continue to the next department.

I have an example on jsFiddle but here is the bulk of my code:

$.wait(3000, function() {
$(".action-area").animate({
'visibility': 'visible'
}, 2000, function() {
$(".action-area").fadeIn().removeClass('hidden');
presentMultipleData(_birthdayData, 5);
});
});

var _chunkLimit = 3;

function presentMultipleData(data, limit) {
for (var key in data) {
if (data.hasOwnProperty(key)) {
// Chunk out the array to only show a specific count
var chunkArray = chunk(_chunkLimit, data[key]);
$("h1.department").text(key);
// Chunk group
for (var i = 0, len = chunkArray.length; i < len; i++) {
// Empty for new group to be shown
$("#item-list").empty();
// Add group items
_addGroupItems(chunkArray[i]);
// @todo Need to delay after each chunk is added so it is presented for 5 seconds
// then go to the next group
}
}
}
}

function _addGroupItems(data) {
for (var i = 0, len = data.length; i < len; i++) {
var _item = buildItem(data[i]);
$("#item-list").append(_item);
}
}

function buildItem(row) {
var _item = $('<div/>', {
'class':'item',
'text': row['name']+' - '+row['date'],
});

return _item;
}

function chunk(chunkSize, array) {
return array.reduce(function(previous, current) {
var chunk;
if (previous.length === 0 || previous[previous.length -1].length === chunkSize) {
chunk = [];
previous.push(chunk);
}
else {
chunk = previous[previous.length -1];
}
chunk.push(current);
return previous;
}, []);
}


I am able to loop over everything and the logic is there to build the items and present them... the issue I have is delaying for 5 seconds then continuing.

I think the for loop is causing the issue here as I looked more into Promises and Deffered examples. I see many topics on SO for this type of thing but could not see one that would answer my needs.

As you can see in my demo, it runs but the only thing you see is the last result which is the last department and the last 3 people. I have tried numerous different methods and have cleaned them out of the code as nothing worked. I haven't worked with JavaScript in a while so when I was attempting to use Promises I was not successful, not was I sure if they are meant to be used in this manner.

I just need a push in the right direction on this. Hopefully someone here can help. Thanks

Answer

You can achieve what you want using Promise's and array#reduce

each iteration of the reduce callback returns a promise that resolves after 5 seconds. the callback "waits" for this promise like promise.then(....) (promise being the promise returned in the previous iteration

You have essentially an array within an array, so you nest the "chunks" reduce within the "departments" reduce

Working snippet also logs "all done" to console when the whole thing is finished. This is achieved by returning the promise from the outer reduce, and adding .then to the presentMultipleData call

    var _birthdayData = JSON.parse('{"Admin Operations":[{"name":"Blanca Tirado","date":"08\/26","department":"Admin Operations","position":"Reporter"}],"Customer Service":[{"name":"Perla Mendoza","date":"08\/26","department":"Customer Service","position":"Receptionist"},{"name":"Jeanette Lopez","date":"08\/30","department":"Customer Service","position":"Customer Service Rep"}],"Onion AM":[{"name":"Eutimio Merida","date":"08\/28","department":"Onion AM","position":"Onion Peeler"}],"Prep":[{"name":"Carlos Segovia","date":"08\/27","department":"Prep","position":"WIP"},{"name":"Margarita Rodriguez","date":"08\/29","department":"Prep","position":"WIP"},{"name":"Orlin Fuentes Nunes","date":"08\/29","department":"Prep","position":"WIP"},{"name":"Fairy Garcia","date":"09\/04","department":"Prep","position":"WIP"},{"name":"Mireya Lomeli","date":"09\/09","department":"Prep","position":"WIP"}],"Production 1":[{"name":"Jesus Alvarado","date":"09\/01","department":"Production 1","position":"Line Worker"},{"name":"Rosa Jimenez","date":"09\/03","department":"Production 1","position":"Line Worker"},{"name":"Natividad Jacuinde","date":"09\/08","department":"Production 1","position":"Line Worker"}],"Production 2":[{"name":"Juventino Sanchez","date":"09\/01","department":"Production 2","position":"Equipment Operator"},{"name":"Deysi Garcia","date":"09\/02","department":"Production 2","position":"Specialist"},{"name":"Aristeo Medina","date":"09\/03","department":"Production 2","position":"Line Worker"}],"Quality Assurance":[{"name":"Martha Lopez","date":"08\/31","department":"Quality Assurance","position":"QA Tech 2"},{"name":"Juana Robledo","date":"09\/01","department":"Quality Assurance","position":"QA Tech 2"}],"Retail B":[{"name":"Luz Cruz","date":"08\/26","department":"Retail B","position":"Specialist"}],"Retail C":[{"name":"Rosalina Lopez","date":"08\/30","department":"Retail C","position":"Specialist"}],"Sanitation":[{"name":"Yajaira Medina","date":"08\/26","department":"Sanitation","position":"Sanitation Tech 1"},{"name":"Luis Mendez Lopez","date":"08\/31","department":"Sanitation","position":"Sanitation Tech 1"}],"Warehouse":[{"name":"Ramon Cardenas","date":"08\/27","department":"Warehouse","position":"Warehouse Tech 1"},{"name":"Ramon Amezquita","date":"08\/28","department":"Warehouse","position":"Warehouse Tech 1"},{"name":"Juan Sayula","date":"09\/02","department":"Warehouse","position":"Shipping Tech"}]}');


jQuery(function($) {

        $.wait = function(duration, completeCallback, target) {
            $target = $(target || '<queue />');
            return $target.delay(duration).queue(function(next){completeCallback.call($target); next();});
        }

        $.fn.wait = function(duration, completeCallback) {
            return $.wait.call(this, duration, completeCallback, this);
        };


        $.wait(3000, function() {
            $(".action-area").animate({
                'visibility': 'visible'
            }, 2000, function() {
                $(".action-area").fadeIn().removeClass('hidden');
                presentMultipleData(_birthdayData, 5).then(function() {
                    console.log('all done');
                });
            });
        });

        var _chunkLimit = 3;
                   
        function presentMultipleData(data, limit) {
            var pdelay = function() {
                return new Promise(function(resolve) {
                    setTimeout(resolve, 5000);
                })
            };
            return Object.keys(data).reduce(function(promise, key) {
                return promise.then(function() {
                    // Chunk out the array to only show a specific count
                    var chunkArray = chunk(_chunkLimit, data[key]);
                    $("h1.department").text(key);
                    return chunkArray.reduce(function(promise, chunk) {
                        return promise.then(function() {
                            $("#item-list").empty();
                            _addGroupItems(chunk);
                            return pdelay();
                        });
                    }, Promise.resolve());
                });
            }, Promise.resolve());
        }
        function _addGroupItems(data) {
            for (var i = 0, len = data.length; i < len; i++) {
                var _item = buildItem(data[i]);
                $("#item-list").append(_item);
            }
        }

        function buildItem(row) {
            var _item = $('<div/>', {
                'class':'item',
                'text': row['name']+' - '+row['date'],
            });

            return _item;
        }

        function chunk(chunkSize, array) {
            return array.reduce(function(previous, current) {
                var chunk;
                if (previous.length === 0 || previous[previous.length -1].length === chunkSize) {
                    chunk = [];
                    previous.push(chunk);
                }
                else {
                    chunk = previous[previous.length -1];
                }
                chunk.push(current);
                return previous;
            }, []);
        }
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="cage">
    <div class="action-area hidden">
        <div class="modal-window">
            <h1 class="department"></h1>
            <div id="item-list" class="modal-content"></div>
        </div>
    </div>
</div>

non jquery, es2015+ version

var _birthdayData = JSON.parse('{"Admin Operations":[{"name":"Blanca Tirado","date":"08\/26","department":"Admin Operations","position":"Reporter"}],"Customer Service":[{"name":"Perla Mendoza","date":"08\/26","department":"Customer Service","position":"Receptionist"},{"name":"Jeanette Lopez","date":"08\/30","department":"Customer Service","position":"Customer Service Rep"}],"Onion AM":[{"name":"Eutimio Merida","date":"08\/28","department":"Onion AM","position":"Onion Peeler"}],"Prep":[{"name":"Carlos Segovia","date":"08\/27","department":"Prep","position":"WIP"},{"name":"Margarita Rodriguez","date":"08\/29","department":"Prep","position":"WIP"},{"name":"Orlin Fuentes Nunes","date":"08\/29","department":"Prep","position":"WIP"},{"name":"Fairy Garcia","date":"09\/04","department":"Prep","position":"WIP"},{"name":"Mireya Lomeli","date":"09\/09","department":"Prep","position":"WIP"}],"Production 1":[{"name":"Jesus Alvarado","date":"09\/01","department":"Production 1","position":"Line Worker"},{"name":"Rosa Jimenez","date":"09\/03","department":"Production 1","position":"Line Worker"},{"name":"Natividad Jacuinde","date":"09\/08","department":"Production 1","position":"Line Worker"}],"Production 2":[{"name":"Juventino Sanchez","date":"09\/01","department":"Production 2","position":"Equipment Operator"},{"name":"Deysi Garcia","date":"09\/02","department":"Production 2","position":"Specialist"},{"name":"Aristeo Medina","date":"09\/03","department":"Production 2","position":"Line Worker"}],"Quality Assurance":[{"name":"Martha Lopez","date":"08\/31","department":"Quality Assurance","position":"QA Tech 2"},{"name":"Juana Robledo","date":"09\/01","department":"Quality Assurance","position":"QA Tech 2"}],"Retail B":[{"name":"Luz Cruz","date":"08\/26","department":"Retail B","position":"Specialist"}],"Retail C":[{"name":"Rosalina Lopez","date":"08\/30","department":"Retail C","position":"Specialist"}],"Sanitation":[{"name":"Yajaira Medina","date":"08\/26","department":"Sanitation","position":"Sanitation Tech 1"},{"name":"Luis Mendez Lopez","date":"08\/31","department":"Sanitation","position":"Sanitation Tech 1"}],"Warehouse":[{"name":"Ramon Cardenas","date":"08\/27","department":"Warehouse","position":"Warehouse Tech 1"},{"name":"Ramon Amezquita","date":"08\/28","department":"Warehouse","position":"Warehouse Tech 1"},{"name":"Juan Sayula","date":"09\/02","department":"Warehouse","position":"Shipping Tech"}]}');

document.addEventListener("DOMContentLoaded", e => {
    var _chunkLimit = 3,
        pdelay = timeout => new Promise(resolve => setTimeout(resolve, timeout)),
        buildItem = row => {
            var div = document.createElement('div');
            div.classname = 'item';
            div.appendChild(document.createTextNode(row.name+' - '+row.date));
            return div;
        },
        _addGroupItems = data => data.map(item => buildItem(item)).forEach(item => document.getElementById("item-list").appendChild(item)),
        toChunk = (chunkSize, array) => array.reduce((previous, current) => {
            var chunk;
            if (previous.length === 0 || previous[previous.length -1].length === chunkSize) {
                chunk = [];
                previous.push(chunk);
            }
            else {
                chunk = previous[previous.length -1];
            }
            chunk.push(current);
            return previous;
        }, []),
        presentMultipleData = (data, limit) => Object.keys(data).reduce((promise, key) => promise.then(() => {
            // Chunk out the array to only show a specific count
            var chunkArray = toChunk(_chunkLimit, data[key]);
            document.querySelector("h1.department").textContent = key;
            return chunkArray.reduce((promise, chunk) => promise.then(() => {
                document.getElementById("item-list").innerHTML = '';
                _addGroupItems(chunk);
                return pdelay(5000);
            }), Promise.resolve());
        }), pdelay(5000));
        
    presentMultipleData(_birthdayData, 5).then(() => console.log('all done'));
});
<div id="cage">
    <div class="action-area hidden">
        <div class="modal-window">
            <h1 class="department"></h1>
            <div id="item-list" class="modal-content"></div>
        </div>
    </div>
</div>