0

Note: If you're 'just' a jQuery developer some things in this post may look a tad complex (Base62 encoding etc.) - it's really not. Although the more technical details are relevant to the question, the core is that jQuery won't select stuff with capitals. Thanks!

Hi folks!

So I have a list generated by Ajax. When you click the list's title, it's ID is sent and the list item appears alongside it. Standard stuff.

Since we're using an auto_increment ID, we don't want the users knowing how many submissions there are in the database. So, I'm encoding it into Base62, then decoding back again. [Note that this is - or, should be, irrelevant to the problem].

So as my list is generated, this code is output. We're using CodeIgniter PHP alongside the jQuery - this is in a loop of database results. $this->basecrypt->encode() is a simple CI library to convert an integer (the ID) to Base62:

$('#title-<?php echo $this->basecrypt->encode($row->codeid); ?>').click(function() {
        alert("clicked");
        [...]

And then, further down the page:

<div id="title-<?php echo $this->basecrypt->encode($row->codeid);?>" class="title">

As you can see, this is all generated in the same loop - and viewing the outputted source code shows, for example:

$('#title-1T').click[...] and then <div id="title-1T" [...]

So, jQuery shouldn't have any trouble, right? It was all working fine until we started Base62-ing the IDs. I believe that jQuery can't/won't select our IDs when they contain capital letters.

Now forgive me if I'm wrong - I am, relatively speaking, fairly new to jQuery - but to test my point I changed my $this->basecrypt->encode() into Base36. Before, it was using 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ After, it was using 0123456789abcdefghijklmnopqrstuvwxyz

With no capital letters, jQuery could select (and show the alert for testing purposes) just fine.

So what can I do? Is it safe for me to continue just using numbers and lowcase letters, in Base36 - and if so, what's the maximum integer size this can go up to? If not, what can I do about jQuery's problematic selection process?

Thanks!

Jack

EDIT: Included below is some example code from the page.

This is a part of the script returned in the file ajaxlist.php - it's called from Ajax and appears a couple of seconds after the page loads. I added in alert("clicked"); right near the beginning to see if that would appear - sadly, it doesn't... $(document).ready(function() {

    $('#title-<?php echo $this->basecrypt->encode($row->codeid); ?>').click(function() {
        alert("clicked");
        var form_data = {
            id: <?php echo $this->basecrypt->encode($row->codeid); ?>
        };

        $('.resultselected').removeClass('resultselected');
        $(this).parent().parent().addClass('resultselected');

        $('#col3').fadeOut('slow', function() {
            $.ajax({
                url: "<?php echo site_url('code/viewajax');?>",
                type: 'POST',
                data: form_data,
                success: function(msg) {
                    $('#col3').html(msg);
                    $('#col3').fadeIn('fast');
                }
        });
        });
    });
}); 
</script>

Also returned from the same file, at the same time as the code above (just beneath it) is this:

<div class="result">

    <div class="resulttext">

        <div id="title-<?php echo $this->basecrypt->encode($row->codeid);?>" class="title">
            <?php echo anchor('#',$row->codetitle); ?>
        </div>   [.......]

If this helps anymore, let me know!


EDIT 2: ACTUAL OUTPUT RETURNED TO THE BROWSER.

This was taken from Firebug, and is the returned data (Ajax) to the browser:

    <script type="text/javascript">
    $(document).ready(function() {

        $('#title-1T').click(function() {
            alert("clicked");

            var form_data = {
                id: 1T      };

            $('.resultselected').removeClass('resultselected');
            $(this).parent().parent().addClass('resultselected');

            $('#col3').fadeOut('slow', function() {
                $.ajax({
                    url: "http://localhost:8888/code/viewajax",
                    type: 'POST',
                    data: form_data,
                    success: function(msg) {
                        $('#col3').html(msg);
                        $('#col3').fadeIn('fast');
                    }
            });
            });
        }); 
    }); 
    </script>

    <div class="result">

        <div class="resulttext">

<div id="title-1T" class="title">

                <a href="http://localhost:8888/#"><p>This is an example </p></a>        </div>`

            <div class="summary">
                gibberish summary text      </div>

            <div class="bottom">


                <div class="author">
                    by <a href="http://localhost:8888/user/7/author">author</a>         </div>

                <div class="tagbuttoncontainer">
                                        <div class="tagbutton listv">
                                                    <span>tag1</span>
                        </div>  
                                </div>

                <!-- Now insert the rating system -->
                <div class="ratingswrapper">

                    <p>4.0</p> 
                </div>

            </div>

        </div>

    </div>

Come on - you cannot say that shouldn't work... can you?!

Jack
  • 9,615
  • 18
  • 72
  • 112
  • The alpha case issue is definitely not your problem. jQuery has no issues with that, as a trivially simple test page can demonstrate. You should do a "view source" on one of your pages and inspect the identifier values. – Pointy Jun 30 '10 at 18:58
  • Since it doesn't answer your question I'll post this here. If you really want to hide your id's and you can't access another item in the database, it might be worthwhile using a hashing algorithm instead, as base64 can easily be decoded. (Obviously adding a pepper into the hash). – William Jun 30 '10 at 19:00
  • Thanks Pointy. I've checked - they're definitely the same... And William, it's not that I 'seriously' want the users not to see the ID, I realise Base62 is decryptable. It just makes our URL segments look a bit nicer and slightly less obvious :) – Jack Jun 30 '10 at 19:02
  • What are the same? I absolutely guarantee you that mixes of upper- and lower-case letters do not confuse jQuery. – Pointy Jun 30 '10 at 19:03
  • Are you using the same "id" value for more than one element on the page? It would probably help a lot if you would post some of the generated HTML here. – Pointy Jun 30 '10 at 19:05
  • A test page demonstrating that jQuery understands capital letters: http://gutfullofbeer.net/hello.html – Pointy Jun 30 '10 at 19:08
  • OK everybody, I'll admit it then - jQuery understands capitals... *facepalm*... By 'both the same' @Pointy, I mean both the selector and the
    IDs are the same. And it's not very easy for me to show you the generated HTML, as the list is returned via Ajax so not viewable in View-Source... but I'll see what I can find with Firebug.
    – Jack Jun 30 '10 at 19:16
  • Update: wondering whether it was because of the Ajax-ey-ness, I tried running it on live('click', function() - still no luck, sorry... – Jack Jun 30 '10 at 19:20
  • Are you sure the php echo isn't adding unwanted spaces? – Mottie Jun 30 '10 at 20:01
  • OK, I've pasted in some more 'full' examples, if that'll help! :) And fudgey, apparently it's not, the source shows everything output as would be expected. – Jack Jun 30 '10 at 20:18
  • Hmmm, try adding a `console.debug($('#title-basecrypt->encode($row->codeid); ?>'));` and see if it is giving you a valid target. – Mottie Jun 30 '10 at 20:28
  • Hey, we've made a development! Well, two in fact. So fudgey, I tried adding your console line in a perfectly valid, logical place. NOTHING HAPPENED?! So then, I commented out EVERYTHING from start to finish, leaving only the $(document).ready(function() { opening and the }); closing. Then I put your console line in between - and voila. It worked. So, it's like something in the code I've got stops everything from being executed...? The output of the Console said [div#title-1T.title], [div#title-1S.title], [div#title-1R.title], [div#title-1Q.title]... – Jack Jun 30 '10 at 20:35
  • Just done a few more attempts... When basecrypt() is in Base36 it works fine! When it's in Base64 it fails...?! – Jack Jun 30 '10 at 21:45
  • BRAINWAVE! OK, so, the only real outputted differences between Base36 and Base64 is outputting in 64 outputs it as a letter/number combo - so, for example, 1N. With 36, my integers aren't high enough yet to output as a letter/number, so they come out as a number/number - e.g. 94 (or, #title-94...) - previously #title-[...] was a numerical ID which jQuery had no problem in selecting. Now that Base36 outputs a number, it still selects it fine. Only when a LETTER is added, does it fail to select it...! So where now?! :D – Jack Jun 30 '10 at 21:49
  • Would it be better to start a seperate question now as to why jQuery won't select it when it has a letter in? – Jack Jun 30 '10 at 21:52
  • Or am I still barking up the wrong tree... – Jack Jun 30 '10 at 21:52
  • OK! Added fully generated HTML. – Jack Jun 30 '10 at 22:22
  • @Jack - it's kinda funny now, but did you check the console logs for any error in the generated html? The moment I pasted this code on jsfiddle, it complained: `Uncaught SyntaxError: Unexpected token ILLEGAL` and it was referring to `var form_data = { id: 1T };`. `1T` needs to be enclosed in quotes here. @fudgey posted an answer for this. – Anurag Jul 01 '10 at 02:30
  • Reason why it started breaking when you introduced letters (caps or small) was because `{ id: 123 }` by itself is valid, but `{ id: A2 }` is not, as `A2` is not a valid literal of any type and has to be enclosed in quotes. – Anurag Jul 01 '10 at 02:36
  • Anurag, Fudgey too came up with the same idea and you are both undoubtedly correct. Thank you SO much to everybody who helped me, and thanks for putting up with me for so long, haha... ;) – Jack Jul 01 '10 at 08:11

4 Answers4

5

Ok I found your problem... you need to put quotes around the variable in the form_data definition (demo):

    var form_data = {
        id: "<?php echo $this->basecrypt->encode($row->codeid); ?>"
    };

I also had to add a return false; so the demo doesn't try to follow the link

Mottie
  • 84,355
  • 30
  • 126
  • 241
  • :O - wow... thanks so much Fudgey! I mean, I guess looking back on it now it's a fairly simple one, but I would've never thought of that myself... just tried it on my source and it works perfectly. You, sir, are brilliant! – Jack Jul 01 '10 at 08:11
2

I don't think that jQuery is the problem.

Please double-check that the IDs you generate are unique to the page and conform to the definition of ID tokens:

ID and NAME tokens must begin with a letter ([A-Za-z]) and may be followed by any number of letters, digits ([0-9]), hyphens ("-"), underscores ("_"), colons (":"), and periods (".").

Also do a validity test of your output HTML to make sure your HTML is not broken in places where you did not look.

Tomalak
  • 332,285
  • 67
  • 532
  • 628
  • I'll go one better and say that I'm 100% positive that jQuery isn't the problem! – Pointy Jun 30 '10 at 19:00
  • @Pointy: Yeah, I'm also sure it isn't. The error must be somewhere else. – Tomalak Jun 30 '10 at 19:02
  • Well the ID definitely conforms, as they will always follow the structure "title-[something here]". The only Base62 characters will be 0-9, a-z, and A-Z, so it definitely complies. – Jack Jun 30 '10 at 19:05
  • @Jack: hm. I think revealing a simplified, but still non-working code sample might be necesary. – Tomalak Jun 30 '10 at 19:58
  • OK Tomalak, I've added some more to my original question :) – Jack Jun 30 '10 at 20:20
  • @Jack: Close, but no cigar. :) Better add a sample of the generated HTML, this is much easier to debug than the code that *generates* the HTML. – Tomalak Jun 30 '10 at 21:00
  • That IS the generated HTML! Or, do you mean $this->basecrypt? – Jack Jun 30 '10 at 21:28
  • Oh, wait, I see what you mean now... Well, the generated HTML is prettymuch exactly the same, except the PHP echoes are replaced with something Base62-ed like '1T' or '3S' or something. It always matches up with the jQuery selector, mind you. – Jack Jun 30 '10 at 21:30
1

Why does

<div id="title" id="1T" [...]

contain two ids?

Pointy
  • 405,095
  • 59
  • 585
  • 614
Joel McCracken
  • 2,275
  • 3
  • 17
  • 11
1

This doesn't solve your specific issue, but would couldn't you just do something like this:

$('#container-of-the-divs div[id^=title-]').click(function(e) {
    alert('Clicked div with ID: ' + e.target.id);
});

You could also just add a class to these elements and select that instead. If you're looking for performance with a ton of items, you could also add the click event onto a parent item, and then do an if statement inside which would create only one event listener, instead of N event listeners. Example:

$('#container-of-the-divs').click(function(e) {
    if (e.target.id.substring(0, 6) == 'title-') {
        alert('Clicked div with ID: ' + e.target.id);
    }
});

Or you could just check if $(e.target).hasClass() like mentioned before.

Updated: Here is a working example based off the code you gave:

<div class="result">
    <div class="resulttext">
        <div id="title-A0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" class="title">
            <a href="#">A0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</a>
        </div>
        <div id="title-B0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" class="title">
            <a href="#">B0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</a>
        </div>
        <div id="title-C0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" class="title">
            <a href="#">C0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</a>
        </div>
        <div id="title-D0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" class="title">
            <a href="#">D0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</a>
        </div>
    </div>
</div>

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript">
$('div.resulttext div.title').click(function() {
    $i = $(this);
    alert('Clicked div with ID: ' + $i.attr('id'));
});
</script>
William
  • 15,465
  • 8
  • 36
  • 32
  • Just tried your first snippet. Well, this is a bit strange, it won't detect that happening either...?! – Jack Jun 30 '10 at 19:21
  • @Jack Webb-Heller: Yeah there was a typo, I updated both snippets and they both worked for me. – William Jun 30 '10 at 19:40
  • Ouch! Tried them both after your correction. Still no luck :'( – Jack Jun 30 '10 at 20:11
  • OK, I've posted some more examples to my original question. – Jack Jun 30 '10 at 20:19
  • This just keeps getting stranger. Works perfectly and as expected with the example. Works perfectly and as expected when encoding in Base36. But, epicly fails when encoding in Base64...?! – Jack Jun 30 '10 at 22:02
  • @Jack Webb-Heller: Probably because base64 usually has at least one "=" at the end of the string, which as stated above isn't valid as an ID. base36 is only a-z0-9. – William Jun 30 '10 at 22:10
  • @Jack Webb-Heller: Why don't you just post the exact output, and remove content that is confidential, but the html structure/id values alone. – William Jun 30 '10 at 22:12
  • But, there's no "=" output anywhere! An example would look exactly like this -
    – Jack Jun 30 '10 at 22:13
  • @William, I can't post the exact output as it's generated via Ajax and Right-Click->View Source doesn't show that. Sorry for the amateur reply, but, Firebug won't output – Jack Jun 30 '10 at 22:14
  • and if you remove the encoding, it works fine? with the CURRENT version? – William Jun 30 '10 at 22:15
  • OK, sorry! I got the output from Firebug's XHR. I'll append it to my question now :) – Jack Jun 30 '10 at 22:16
  • And, @William, yep, removing the capital A-Z encoding (since my IDs aren't high enough for letters yet) it therefore only generates numbers - and that works fine. With the latest jQuery... – Jack Jun 30 '10 at 22:16
  • What do you mean by removing the capital A-Z, you're taking the encoded output, and then removing any A-Z characters? Or you're just outputting the ID's (without encoding them) – William Jun 30 '10 at 22:19
  • OK William, I've added some actual Outputted data to the second edit of my question. What I mean is, base62's characters are 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ. Base36's characters are 0123456789abcdefghijklmnopqrstuvwxyz - just the same, without capital A-Z included as character options. So, I mean I'm sending the integer ID to be encoded using 0-9 and a-z, not 0-9,a-z AND A-Z. Makes sense? – Jack Jun 30 '10 at 22:24
  • @Jack Webb-Heller: You're re-applying the click event on the newly pulled HTML right? – William Jun 30 '10 at 22:25
  • @Jack Webb-Heller: Also, your document.ready() method will not fire when the AJAX is pulled as the document has already been loaded, since you're pulling it via AJAX just put the JavaScript code under the div element and have it execute right instantly. – William Jun 30 '10 at 22:31
  • Um, yep... I think so? The jQuery code is sent along with the pulled HTML at the same time, in the same loop, so the values will be the same. I've also tried jQuery's live() thinking that might make a difference, but, nope... – Jack Jun 30 '10 at 22:31
  • OK, I'll try your last suggestion now! I was using that previously though with no luck, but, hey, things might be different second time round... – Jack Jun 30 '10 at 22:32
  • Nope! Still no luck! This is ridiculous... :S – Jack Jun 30 '10 at 22:33
  • @Jack Webb-Heller: Well the method isn't going to be fired with it there, so even if it didn't fully solve the problem, if you did find the problem before, you wouldn't have known because the event was never being fired at all. Make sure your JavaScript is after the HTML. – William Jun 30 '10 at 22:33
  • FHDUOASE`P1! etc. placed Javascript definitely after the HTML. nothing's happening still. :'( – Jack Jun 30 '10 at 22:39
  • sorry for my impatient tone, only, I've been stuck with this problem for 4 hours... – Jack Jun 30 '10 at 22:40
  • If I were you, I'd start commenting a large amount of your code out one by one until you find the problem. First, make your "click" event ONLY contain the alert(), comment the rest out. Do an alert BEFORE the click event, to make sure the AJAX is pulling the data and executing the data correctly. – William Jun 30 '10 at 22:43
  • OK - so, I'm in the UK, and it's nearly midnight here. I need some sleep or I'll go mad. Just briefly, I tried doing what you suggested. I commented out EVERYTHING between – Jack Jun 30 '10 at 22:48
  • Thanks, William, for having the patience of a saint! – Jack Jun 30 '10 at 22:49
  • 1
    It's most likely because there is an error somewhere, so JavaScript is "dieing". Check your error logs in Firefox. Good night. – William Jun 30 '10 at 22:52