1

One would think that converting a 3 character abbreviation to a number representation would be an easy task. JavaScript says "NOPE!".

$(selector).each(function() {
    // convert month abbreviation to numerical representation
    var monthStr = $(this).text().match(/[^\/]*/)[0];
    var months = {
        JAN: 1,
        FEB: 2,
        MAR: 3,
        APR: 4,
        MAY: 5,
        JUN: 6,
        JUL: 7,
        AUG: 8,
        SEP: 9,
        OCT: 10,
        NOV: 11,
        DEC: 12
    };
    var month = months[monthStr].toString();
    var date = $(this).text().replace(monthStr, month);
    $(this).text(date);
});

Even though the code is valid and works, my console still goes haywire with the following error:

Uncaught TypeError: Cannot call method 'toString' of undefined

which makes absolutely no sense since the selector's text is replaced correctly (at least according to the eye).

The issue seems to begin when I declare the 'monthStr' variable. If I set it to a static 'MAR' as an example and remove the '.toString()' from my 'month' variable definition the error no longer occurs.

Furthermore, the only thing saving the code is the already mentioned '.toString()' conversion for my month variable. If it was not there I would end up with a 'undefined' value.

Just wanted to share my JavaScript-being-dumb experience of the day and hope that someone could possibly elaborate on why this error is being thrown, even when the code works flawlessly.

tenub
  • 3,386
  • 1
  • 16
  • 25
  • 2
    Try to log the value of `monthStr` after setting it. – Haralan Dobrev Mar 27 '13 at 00:08
  • 1
    `months` is an object, and you're trying to evaluate its contents as an array. That's why `months[monthStr]` is failing. – justacoder Mar 27 '13 at 00:09
  • If `monthStr` has whitespace in it, or some other unexpected character, that would explain it. Why aren't you logging monthStr? And when you do log `monthStr`, figure out whether it exactly corresponds to one of your month abbreviations. (That entails checking for whitespace at the end.) – ktm5124 Mar 27 '13 at 00:12
  • 1
    The reason why it's failing is probably what @Haralan is hinting at: the result of `$(this).text().match(/[^\/]*/)[0]` is probably not any of the month abbreviations you declared in your object. Maybe the regular expression is wrong or maybe it's just in lower case. `months["dec"]` would be undefined and throw an error when you try to call `toString`. Put a breakpoint there and check out what's going on – juandopazo Mar 27 '13 at 00:14
  • 2
    Hint: Javascript is NOT dumb ! – Blacksad Mar 27 '13 at 00:58
  • The problem is with `$(selector).each(function({...});`. The selector is wrong. Ironically it's the one thing you are omitting. You are probably selecting DOM objects that don't have dates as text. See my answer below... – ktm5124 Mar 27 '13 at 00:59
  • Uncaught TypeError: Cannot call method 'toString' of undefined means that months[monthStr] returns undefined. Most likely you are trying to get months["nope, not in here"] It's frustrating sometimes, especially when you know that the computer doesn't make mistakes; you do. – HMR Mar 27 '13 at 01:03
  • 1
    @AngrySpartan: Huh, what? Where does he use it as an array? – Bergi Mar 27 '13 at 01:26
  • @Bergi, someone's never seen [square bracket notation](http://www.jibbering.com/faq/faq_notes/square_brackets.html) before – SomeShinyObject Mar 27 '13 at 01:29
  • With a simple check to see if the element in the object exists in the first place, it wouldn't matter what selector you used, see my answer – SomeShinyObject Mar 27 '13 at 01:31
  • @Christoper, someone has, but only thought it was used for array iteration. And knowing is half the battle. – justacoder Mar 27 '13 at 01:52
  • For the future, may I recommend a more descriptive title for your question, please? – Andrew Barber Mar 29 '13 at 00:05

4 Answers4

1

The problem most likely has to do with:

$(selector).each(function() { ... });

What is the selector, and how many DOM objects are being selected? It is likely a DOM object is being selected that doesn't have a date as text.

Here is an example that reproduces your error: http://jsfiddle.net/m3Kza/1/ (see console)

Console: 'Uncaught TypeError: Cannot call method 'toString' of undefined'

<p>MAR/26/2013</p>
<p>Hello world!</p>

$('p').each(function() {
    var month = $(this).text().match(/[^\/]*/)[0];

    var months = {
        MAR: 3
    };

    alert(months[month].toString());
});

The reason for this is because we are selecting two <p> elements, and only one has a date as text.

If we limit our code to DOM objects that have dates as text, it works fine: http://jsfiddle.net/m3Kza/

<p>MAR/26/2013</p>

var month = $('p').text().match(/[^\/]*/)[0];

var months = {
    MAR: 3
};

alert(months[month].toString());   

You should be using a CSS class for elements that have date as text, and only for these elements, that way you know what you're selecting.

ktm5124
  • 11,861
  • 21
  • 74
  • 119
0

Use trim

 var monthStr = $(this).text().match(/[^\/]*/)[0];
    monthStr = monthStr.trim();

    var months = {
            "JAN": 1,
            "FEB": 2,
            "MAR": 3,       
        };

        var month = months[monthStr].toString();
        alert(month);
What have you tried
  • 11,018
  • 4
  • 31
  • 45
  • Believe it or not that's what I originally had. Still throws the same error. Edit: I wasn't the one down-voting btw. :( – tenub Mar 27 '13 at 00:12
  • @tenub What does `monthStr` return? – What have you tried Mar 27 '13 at 00:13
  • Quotes don't make a difference: http://stackoverflow.com/questions/4348478/what-is-the-difference-between-object-keys-with-quotes-and-without-quotes. – ktm5124 Mar 27 '13 at 00:14
  • The monthStr match is always one of the keys of months, ie. MAR, APR, etc. and is a string. – tenub Mar 27 '13 at 00:16
  • 3
    How do you know that? Are you setting a breakpoint? Are you logging? When you log, do you check for whitespace? The most obvious answer is that monthStr does not correspond to a month abbreviation. – ktm5124 Mar 27 '13 at 00:17
  • @tenub Try with `trim` - shown in the last update - it *may* help , worth a try. – What have you tried Mar 27 '13 at 00:21
  • @ktm5124 There is no whitespace. `$(this).text()` ALWAYS has a value similar to 'MAR/26/2013 05:00 PM', etc. I am applying `.match()` to find & replace all characters up until the first '/' with a number. – tenub Mar 27 '13 at 00:22
  • So you are 110% positive that after your regex, a value similar to `MAR` - or whatever the month is - is returned? Not `MAR/` – What have you tried Mar 27 '13 at 00:25
  • Yes, that's why the code currently works. :) Anyway, I utilized $.trim and the code still acts the exact same. :( – tenub Mar 27 '13 at 00:26
  • @tenub I was able to recreate your code without any errors, I just got rid of toString ... http://jsfiddle.net/qVg4z/2/ – What have you tried Mar 27 '13 at 00:31
0

I've made some slight improvements on your code and do not get any error:

<!doctype html>
<html lang="en">
    <head> 
        <meta charset="utf-8" />  
        <title>Test the script</title>
        <script type="text/javascript" src="jquery-1.9.0.js"></script>
        <script>
            $(document).ready(function(){
                $("div").each(function() {
                    // convert month abbreviation to numerical representation
                    var orgStr=$(this).text().trim().match(/[^\/]*/)[0];
                    var monthStr = orgStr.toUpperCase().substr(0,3);
                    console.log("monthStr is:", monthStr);
                    var months = {
                        JAN: 1,
                        FEB: 2,
                        MAR: 3,
                        APR: 4,
                        MAY: 5,
                        JUN: 6,
                        JUL: 7,
                        AUG: 8,
                        SEP: 9,
                        OCT: 10,
                        NOV: 11,
                        DEC: 12
                    };
                    if(months[monthStr]!==undefined){
                        var month = months[monthStr].toString();
                        var date = $(this).text().replace(orgStr, month);
                        $(this).text(date);
                    }
                }); 
            });
        </script>  

    </head>
    <body> 
        <div>JAN/22/2012</div>
        <div>
            JAN/22/2012</div>
        <div>nope/22/2012</div>
        <div>Feb/22/2012</div>
        <div>dec/22/2012</div>
        <div>jul/22/2012</div>
        <div>
            october/22/2012</div>
    </body>
</html>
HMR
  • 37,593
  • 24
  • 91
  • 160
0
  • Calling trim() directly should work for you:

var monthStr = $(this).text().match(/[^\/]*/)[0].trim();
  • Adding an if/else block to determine a correct value for the month:

if(monthStr !== undefined && monthStr.length === 3) {

Actually you don't need this check if you just check if the element in the object exists.

  • And then checking if months[monthStr] even exists:

var month = (months[monthStr]) ? months[monthStr].toString() : monthStr

And for full code

$("div").each(function() {
    // convert month abbreviation to numerical representation
    var el = $(this);
    var monthStr = el.text().match(/[^\/]*/)[0].trim();        
    var months = {
        JAN: 1,
        FEB: 2,
        MAR: 3,
        APR: 4,
        MAY: 5,
        JUN: 6,
        JUL: 7,
        AUG: 8,
        SEP: 9,
        OCT: 10,
        NOV: 11,
        DEC: 12
    };
    var month = (months[monthStr]) ? months[monthStr].toString() : monthStr;
    el.text(el.text().replace(monthStr, month));
});

Number of block elements in each() shouldn't matter. My fiddle has multiple divs running through an each() and it works just fine.

Fiddle with it

SomeShinyObject
  • 7,581
  • 6
  • 39
  • 59