silentmatt
6/17/2009 - 6:20 PM

JavaScript class to represent and format names

JavaScript class to represent and format names

var Name = (function() {
    function Name(prefix, first, middle, last, suffix, called) {
        this.prefix = prefix || "";
        this.first = first || "";
        this.middle = middle || "";
        this.last = last || "";
        this.suffix = suffix || "";
        this.called = called || "";
    }

    function capitalize(s) {
        return s.charAt(0).toUpperCase() + s.substring(1);
    }

    function addSetter(cls, name, fn) {
        cls.prototype["set" + capitalize(name)] = fn || function(value) {
            this[name] = value;
            return this;
        };

        // Define a JS setter if it's supported and needed
        if (fn && cls.prototype.__defineGetter__) {
            cls.prototype.__defineGetter__(name, fn);
        }
    }

    function addGetter(cls, name, fn) {
        cls.prototype["get" + capitalize(name)] = fn;
        if (cls.prototype.__defineGetter__) {
            cls.prototype.__defineGetter__(name, fn);
        }
    }

    // Chainable setters
    addSetter(Name, "prefix");
    addSetter(Name, "prefix");
    addSetter(Name, "first");
    addSetter(Name, "middle");
    addSetter(Name, "last");
    addSetter(Name, "suffix");
    addSetter(Name, "called");

    // Common name formats
    addGetter(Name, "fullName", function() {
        return this.toString("PFMLS");
    });

    addGetter(Name, "lastFirst", function() {
        return this.toString("L,F");
        //return (this.last + ", " + this.first).replace(/(^, )|(, $)/, "");
    });

    addGetter(Name, "lastFirstMiddle", function() {
        return this.toString("L,FM");
    });

    addGetter(Name, "firstLast", function() {
        return this.toString("FL");
    });

    addGetter(Name, "firstMiddleLast", function() {
        return this.toString("FML");
    });

    addGetter(Name, "informal", function() {
        return this.toString("CL");
    });


    // Initials
    addGetter(Name, "firstInitial", function() {
        return (this.first || "").substring(0, 1);
    });

    addGetter(Name, "middleInitial", function() {
        return (this.middle || "").substring(0, 1);
    });

    addGetter(Name, "lastInitial", function() {
        return (this.last || "").substring(0, 1);
    });

    addGetter(Name, "monogram", function() {
        return this.getFirstInitial() + this.getMiddleInitial() + this.getLastInitial();
    });

    // Names match if all the common parts match
    Name.prototype.matches = function(other) {
        if (this.prefix && other.prefix) {
            if (this.prefix != other.prefix) {
                return false;
            }
        }
        if (this.first && other.first) {
            if (this.first != other.first) {
                return false;
            }
        }
        if (this.middle && other.middle) {
            if (this.middle != other.middle) {
                return false;
            }
        }
        if (this.last && other.last) {
            if (this.last != other.last) {
                return false;
            }
        }
        if (this.suffix && other.suffix) {
            if (this.suffix != other.suffix) {
                return false;
            }
        }

        return true;
    }

    // Spaces are automatically inserted between parts where it (hopefully) makes sense
    // Leading and trailing spaces and commas, as well as empty quotes, braces, etc. are stripped.
    // Multiple spaces are squeezed into a single space.
    //
    // Default format is "PFMLS" - the same as getFullName()
    //
    // Format characters:
    //     P       - Prefix
    //     N       - Nickname (called)
    //     C       - Called By (called or first)
    //     F,f     - First / first initial
    //     M,m     - Middle / middle initial
    //     L,l     - Last / last initial
    //     S       - Suffix
    //     (,[,{," - Literal (no implied space after)
    //     ),],}   - Literal
    //     .,comma - Literal (skipped if the last part was missing)
    //     space   - Force a space (useful after an end quote)
    Name.prototype.toString = function(fmt) {
        fmt = fmt || "PFMLS";
        var parts = [];
        var sep = "";
        var hadLast = false;

        function pushPart(value) {
            hadLast = false;
            if (value) {
                parts.push(sep + value);
                sep = " ";
                hadLast = true;
            }
        }

        for (var i = 0; i < fmt.length; i++) {
            var c = fmt.charAt(i);

            switch (c) {
            case ',':
            case '.':
                if (hadLast) {
                    parts.push(c);
                    sep = " ";
                }
                break;
            case ' ':
                parts.push(" ");
                sep = "";
                break;
            case 'P':
                pushPart(this.prefix);
                break;
            case 'N':
                pushPart(this.called);
                break;
            case 'C':
                pushPart(this.called || this.first);
                break;
            case 'F':
                pushPart(this.first);
                break;
            case 'f':
                pushPart(this.getFirstInitial());
                break;
            case 'M':
                pushPart(this.middle);
                break;
            case 'm':
                pushPart(this.getMiddleInitial());
                break;
            case 'L':
                pushPart(this.last);
                break;
            case 'l':
                pushPart(this.getLastInitial());
                break;
            case 'S':
                pushPart(this.suffix);
                break;
            case '"':
                parts.push('"');
                sep = "";
                break;
            case '{':
            case '[':
            case '(':
                parts.push(sep + c);
                sep = "";
                break;
            case '}':
            case ']':
            case ')':
                parts.push(c);
                sep = " ";
                break;
            default:
                throw new Error("Invalid format character: '" + c + "'");
            }
        }

        return parts.join("").replace(/(^[, ]+)|([, ]+$)|(\(\))|("")|(\[\])|({})/g, "").replace(/  +/g, " ");
    }

    Name.test = function() {
        function expect(actual, expected) {
            if (expected !== actual) {
                print("Expected <" + expected + "> but got <" + actual + ">");
                return false;
            }
            return true;
        }

        var me = new Name();
        me.setFirst("Matthew").setLast("Crumley").setCalled("M@");
        expect(me.first, "Matthew");
        expect(me.last, "Crumley");
        expect(me.middle, "");
        expect(me.firstInitial, "M");
        expect(me.lastInitial, "C");
        expect(me.monogram, "MC");
        expect(me.toString(), "Matthew Crumley");
        expect(me.toString('F "N" L'), 'Matthew "M@" Crumley');
        expect(me.toString('L,F'), 'Crumley, Matthew');

        me.called = "";
        expect(me.toString('L,N'), 'Crumley');
        expect(me.toString('L,C'), 'Crumley, Matthew');
    }

    return Name;
})();