0

I'm trying to create a feature that allows an empty div code snippet to be placed on the page and then for that to be replaced with more complicated markup with Javascript. The problem is that when there are more than one of these empty divs on the page it only displays one and the one it uses seems to be random. I think there is an issue with the loop.

I'm using data attributes on the div to store the database id of the required row.

<div data-foo="25"></div>

There can be any number of these on a page so the javascript needs to search for any element with a 'data-foo' attribute and then loop through them. The information is retrieved from the database and the output generated with a php file. This is returned to the javascript and replaces the empty div with the same data-foo id.

Javascritp/jQuery

    var profiles = document.querySelectorAll('[data-foo]');
    // console.log(profiles);

    if(profiles.length) {
        for(var i = 0; i < profiles.length; i++) {
            var profileId = profiles[i].dataset.foo;
            // console.log('[data-foo="' + profileId +'"]');

            $.ajax({ 
                url: '/file/path/profile.php',
                data: {
                    profile: profiles[i].dataset.foo
                },
                type: 'get',
                success: function(response) {
                    $('[data-foo="' + profileId + '"]').replaceWith(response);
                }
            });
        }
    }

PHP

The php runs for each id found by the js, using the loop above but I'm guessing only one is being output because the loop finishes before displaying it on the page?

if(isset($_GET['profile'])) {
    try {
        $sth = $dbh -> prepare("SELECT * FROM profiles WHERE id = :profile");
        $sth -> bindParam(':profile', $_GET['profile'], PDO::PARAM_INT);
        $sth -> execute();
    } catch(PDOException $e) {
        pdo_caught($e);
    }
    $result = $sth -> fetch(PDO::FETCH_ASSOC);
}

if(!empty($result)) {
    echo '<div class="profile">
            <img src="/img/'.$result["picture"].'" alt="'.$result['name'].'">
            <h3>'.$result["name"].'</h3>
            <p>'.$result["quote"].'</p>
        </div>';
}
Yabsley
  • 380
  • 1
  • 4
  • 17
  • 1
    `profiles[i].dataset.profile;`.... The data `profile` does not exist in `
    `
    – Xlander Dec 19 '14 at 09:56
  • @Xlander sorry yeah, you're right. I changed data-profile to data-foo when posting the question but neglected to change it in the js. I've edited it now. – Yabsley Dec 19 '14 at 10:02
  • 1
    Just to note: searching for any element with specific attribute is very very slow; you better add a class (or at least tag) to mark relevant elements: $('.profile[data-foo]') or $('div[data-foo]') – Radek Pech Dec 19 '14 at 10:02

3 Answers3

2

I bet on a closure problem, try this:

var profiles = document.querySelectorAll('[data-foo]');
    // console.log(profiles);

    if(profiles.length) {
        for(var i = 0; i < profiles.length; i++) {
            (function(i) {
                var profileId = profiles[i].dataset.foo;
                // console.log('[data-profile="' + profileId +'"]');

                $.ajax({ 
                    url: '/file/path/profile.php',
                    data: {
                        profile: profiles[i].dataset.profile
                    },
                    type: 'get',
                    success: function(response) {
                        $('[data-foo="' + profileId + '"]').replaceWith(response);
                    }
                });
            })(i)
        }
    }

Explanation:

When the success function is executed it takes the variable profileId coming from the closure scope. At that point the variable profileId has the value of the last iteration of the loop, ie. profiles[profiles.length - 1].dataset.foo. So you replace the same div with each response from your AJAX requests. As AJAX is asynchronous and you don't know the order you will recieve the responses, you have a random result in that div because the final display is the last response you got.

EDIT:

Here is the solution with Jquery selector and each:

$('[data-foo]').each(function() {
    var self = $(this),
        profileId = self.data('foo')
    ;

    $.ajax({ 
        url: '/file/path/profile.php',
        data: {
            profile: profileId
        },
        type: 'get',
        success: function(response) {
            self.replaceWith(response);
        }
    });
});
Gnucki
  • 5,043
  • 2
  • 29
  • 44
  • Yeah I've tried your solution and it seems to work on first test. Thanks. I'm a novice at this so might take me a while to understand it. On the whole is this fairly robust/sensible? Meaning what I've done. Is there a more elegant way to achieve it? – Yabsley Dec 19 '14 at 10:04
  • 1
    Scope can be a little bit hard to understand for a beginner. Take a look at that: http://stackoverflow.com/questions/500431/what-is-the-scope-of-variables-in-javascript – Gnucki Dec 19 '14 at 10:06
  • 1
    There is no problem in the way you do that for me. You can replace the auto executed anonymous function I used by a function set in a variable: `var getProfile = function(i) { /*...*/ }` and then call this function like that `getProfile(i)` if you prefer. – Gnucki Dec 19 '14 at 10:08
  • 1
    With jquery you can use `$('[data-foo]').each(function() { /* your code */ });` then `$(this).replaceWith(response);` in your success function. – Gnucki Dec 19 '14 at 10:21
  • Thanks for your help with this. I think I like the last method better. It seems simpler if I'm already using jQuery. However, if I try to use $(this).replaceWith(response); in the success function it doesn't work. Is this because $(this) is not in scope inside of the ajax success? – Yabsley Jan 07 '15 at 09:51
  • 1
    My bad! This is because you are in the scope of the success function, so `this` refers to this function. If you want to use that, you have to do `$('[data-foo]').each(function() { var self = this; /* your code */ });` then `$(self).replaceWith(response);`. I updated the answer with the code. – Gnucki Jan 07 '15 at 10:06
  • Glad if I could help you! – Gnucki Jan 07 '15 at 10:18
1

Use better something like this:

jQuery('[data-foo]').each(function () {
    var $this = $(this)

    $this.load('/file/path/profile.php?profile=' + $this.dataset.foo)
})
Vladyslav Savchenko
  • 1,282
  • 13
  • 10
1

You could try something like this as your loop approach http://jsfiddle.net/fb5ykj93/

$("div[data-foo]").each(function () {
    alert($(this).data("foo"));
});