views:

127

answers:

3

I was just trying to learn and understand jQuery source code (so far with little success X_X) in order to improve my JavaScript skills. As my understand of JavaScript increases I came up with this little logging/debugging tool. As of my level of JavaScript I am posting the code here for people to judge and audit. So potentially I can learn from the comments made. Can someone please point out potential issues, improvements? I tried to encapsulate the console implementation and map it to window.$console (the only place that messes with global scope).

(function() {
    var proxy = {}, //use private proxy object to prevent binding to global (window) object
        _id = "",
        _warning = false;
        results = {};

    if (this.$console) { //check if $console exists in global (window)
        warning("$console is conflicting with existing object and could not be mapped.");
    }
    else {
        this.$console = proxy; //if undefined we then map proxy to global (window) object
    }

    proxy.init = function(id) { //map the display ol html element on the page
        _id = id;
        results = document.getElementById(id);
        return this;
    }

    proxy.log = function(msg) {
        append(msg);
        return this;
    };

    proxy.assert = function(pass, msg) {
        var html = (pass) ? "<b style=\"color: green;\">Pass</b>, " + msg
                    : "<b style=\"color: red;\">Fail</b>, " + msg ;
        append(html);
        return this;
    }

    proxy.error = function(msg) {
        var html = "<b style=\"color: red;\">Error</b>, " + msg + "";
        append(html);
        return this;
    }

    function append(msg) {
        if (results != null) {
            results.appendChild(getChild("li", msg));
        }
        else {
            warning("Message could not be appended to element with \"Id: " + _id + "\".");
        }
        return this;
    };

    function getChild(type, html) {
        var child = document.createElement(type);
        child.innerHTML = html;
        return child;
    }

    function warning(msg) {
        if (!_warning) {
            _warning = true;
            alert(msg);
        }
    }

    return proxy;
}());

Usage

$console.init("console").log("hello world");
$console.assert(true, "This is a pass.");

ps: As I've made a few revisions to the code the question is quite different from what it was originally.

+2  A: 

The only issue I see as far, is that you are actually exposing two globals, window.Console that is fine, since you want to expose it there, and $c.

This is because you are not using the var statement on the assignment, should be:

 var $c = this.Console;

If you don't use it, $c will be global.

Aside of that, maybe you may want to work on naming conventions, usually in JavaScript you name almost everything in camelCase, and only constructor functions in PascalCase, it's just a comment, I personally try to stick that convention but is up to you and your team.

Edit: about the concatenation made using the innerHTML property, if you will handle large amounts of data in your div, I would recommend you to use DOM manipulation, instead of replacing the whole innerHTML every time.

By DOM manipulation I refer to create your log messages as nested DOM elements of your div, by using document.createElement and element.appendChild.

CMS
+1 - I missed that he didn't have a 'var'.
James Black
I fixed it. I missed it...
Jeffrey C
+4  A: 

It seems like it would work OK. I find your use of anonymous functions a bit confusing, though. Since Console doesn't have any private data, why not define it like so:

var Console = {

    instance: document.getElementById('console'),

    Print: function (msg) {
        this.instance.innerHTML += msg;
        return this;
    },

    Log: function (msg) {
        this.Print("<br/>").Print(msg);
    }
};

I also removed the anonymous function used in the assignment of instance, since it didn't appear to be doing anything.

Edit

The evaluation-of-an-anonymous-function technique is typically used to hide declared variables. See http://yuiblog.com/blog/2007/06/12/module-pattern/ for a discussion.

If, for example, you wanted to hide the instance property, you might achieve that using an anonymous function in the following way:

var Console = (function () {

    // object containing public members to be returned
    var c = {};

    // not visible outside anonymous function
    var instance = document.getElementById('console');

    // a 'public' property
    c.Print = function (msg) {
        instance.innerHTML += msg;
        return this;
    };

    // a 'public' property
    c.Log = function (msg) {
        this.Print("<br/>").Print(msg);
    };

    return c;
}());

The resulting Console object exposes only the Print and Log properties.

harto
What's the difference compared your style to mine? I was just reading jQuery source code (most of it I don't understand) but I did pick up the style... ^_^
Jeffrey C
@Jeffrey: the difference is harto's does without all the `(function(){})()` cruft. It's easier for readers of the code to deduce what the object structure of `Console` is.
Crescent Fresh
Typically I would only use the anonymous function style if I wanted to encapsulate/hide some data - I find the literal object notation (i.e. `var foo = { ... }`) a bit clearer.
harto
+1 that's *much* more readable.
cletus
@harto: i think in your 2nd example, the method chaining is broken.
Jeffrey C
@harto: is it possible to make print private while be able to chain the method?
Jeffrey C
Oh yeah, whoops. I'll fix that
harto
@Jeffrey - I couldn't think of an obvious way to do method chaining, so I just exposed both functions in the 2nd example. You could possibly do it using `apply()` or something like that
harto
@hardo: and i think you need to use return c instead of return this. as this will pollute global scope i think since you are not using new keyword?
Jeffrey C
@Jeffrey- ehh... when you invoke `Console.Print('foo')`, the returned object is the same one referred to by `c` in the function. `c` is returned from the anonymous function evaluation, which is assigned to the `Console` variable.Perhaps this illustrates why my first example is better :)
harto
@hardo: actually i think now i am confused and i am not sure if i can tell the difference between var console = {}; and var console = (function(){}());.
Jeffrey C
@Jeffrey - I suggest reading the link in my answer - that might give you a better idea. Sorry for confusing things. I would just stick with the first form I suggested in my answer.
harto
A: 

I'd try to use the same API that Firebug and the IE Developer Toolbar expose so that you can degrade nicely. One build in that direction would be that if window.console already exists, then don't execute your code. That way you'll get the native debugger when it's available, otherwise you'll get your implementation.

Bialecki
Would you elaborate?
Jeffrey C
Basically, they expose an API that has the methods "log", "info", "warn", "error", etc. to denote different types of messages. It's not important that you make those appear differently, but if I'm used to running my code in FF, I might have those kind of log statements in there. In addition, if I'm working in FF, I'll expect to get those messages in Firebug. However if I'm in IE 6, it'd be great to still get those log messages in some form or fashion. That's where your code could add in the logging utility if window.console is undefined.
Bialecki