Home > Enterprise >  How to preserve an order of divs generated by Javascript
How to preserve an order of divs generated by Javascript

Time:12-10

I am consuming an api in which I want to show the generated data in my HTML. For that I am creating the div elements using JavaScript. When displaying the information it does it without problems, but every time I update the page, the data shows them in a different order. That is, if I have 3 divs, the div that was first will now appear last and thus the order changes each time I update the page.

This is my Javascript code:

const cargar_noticias_recientes = async () => {
    const noticias_recientes = document.querySelector('#pnl-body-content-card-reci');
    const res = await fetch(`WebService_Default.asmx/cargar_card_noticia_recientes`, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: ''
    });
    const datos = await res.json();
    let nuevos = [...datos.d];
    nuevos.map(async nuevo => {
        const div = document.createElement('div');
        const peticion = await fetch(`http://localhost:51482/api/values/${nuevo[3]}`, {
            method: 'GET'
        });
        const imagen = await peticion.blob();
        const url = URL.createObjectURL(imagen);
        div.innerHTML = `
            <div >
                <div >
                    <span>Nuevo</span>
                </div>
                <div >
                    <div  style="background-image: url(${url});"></div>
                </div>
                <div >
                    <p>
                        <i ></i> ${nuevo[1]}
                    </p>
                </div>
            </div>
        `
        noticias_recientes.appendChild(div);
    });
}

CodePudding user response:

The different order may occur because some of the await-ed promises within the multiple map callbacks may resolve sooner than others, giving a different order of execution.

You can get back the results in their original order by using Promise.all -- still allowing the non-JS asynchronous tasks to run independently of each other:

    const promises = nuevos.map(async nuevo => {
        const div = document.createElement('div');
        const peticion = await fetch(`http://localhost:51482/api/values/${nuevo[3]}`, {
            method: 'GET'
        });
        const imagen = await peticion.blob();
        const url = URL.createObjectURL(imagen);
        div.innerHTML = `
            <div >
                <div >
                    <span>Nuevo</span>
                </div>
                <div >
                    <div  style="background-image: url(${url});"></div>
                </div>
                <div >
                    <p>
                        <i ></i> ${nuevo[1]}
                    </p>
                </div>
            </div>
        `;
        // Don't append to the DOM yet, but *return*
        return div;
    });
    // Collect the DIVs in the order of the originally fetched data:
    const divs = await Promise.all(promises);
    // ...And populate the DOM
    for (const div of divs) noticias_recientes.appendChild(div);

CodePudding user response:

Your problem is related to the use of asynchronous.

    nuevos.map(async nuevo => {
        const div = document.createElement('div');
        const peticion = await fetch(`http://localhost:51482/api/values/${nuevo[3]}`, {
            method: 'GET'
        });
        const imagen = await peticion.blob();
        const url = URL.createObjectURL(imagen);
        div.innerHTML = `
            <div >
                <div >
                    <span>Nuevo</span>
                </div>
                <div >
                    <div  style="background-image: url(${url});"></div>
                </div>
                <div >
                    <p>
                        <i ></i> ${nuevo[1]}
                    </p>
                </div>
            </div>
        `
        noticias_recientes.appendChild(div);
    });

In this function you do a fetch. This is an HTTP request that has some more or less random execution time. The first fetch that finishes will be the first div created, the second fetch that finishes will be the second div created, and so on.

If you want to constantly keep the order of your divs creation, you have to get it out of your map callback because each fetch doesn't wait for the previous one to finish. Like this :

for (nuevo of nuevos) {
    const div = document.createElement('div');
    const peticion = await fetch(`http://localhost:51482/api/values/${nuevo[3]}`, {
        method: 'GET'
    });
    const imagen = await peticion.blob();
    const url = URL.createObjectURL(imagen);
    div.innerHTML = `
            <div >
                <div >
                    <span>Nuevo</span>
                </div>
                <div >
                    <div  style="background-image: url(${url});"></div>
                </div>
                <div >
                    <p>
                        <i ></i> ${nuevo[1]}
                    </p>
                </div>
            </div>
        `
    noticias_recientes.appendChild(div);
}

This way, each fetch will wait patiently for the previous one to finish, and your divs will always be created in the same order.

Beware that it is clearly not optimized to do this. Because your requests are no longer done in parallel, but one after the other. So the execution time of this function will be (avgTimeOfFetch) * nbRequestToDo seconds

If you want something more optimized, you should probably keep running your requests in parallel as you did in your initial version, then sort data the way you want, and then at that point you can create your divs and insert them in the DOM.

  • Related