<commit_before>/**
 * Gmailr v0.0.1
 * Licensed under The MIT License
 *
 * Copyright 2011, James Yu
 */

(function($, window) {

    // Utility methods

    var dbg = function(msg) {
        if(Gmailr.debug)
            console.log(msg);
    };

    var p = function(els, m) {
        if(!els) {
            dbg(els);
        } else if(els.each) {
            if(els.length == 0) {
                dbg('p: Empty');
            } else {
                els.each(function(i,e) {
                    dbg(e);
                });
            }
        } else {
            dbg(els);
        }
    };

    var pm = function(m, els) {
        dbg(m);
        p(els);
    };

    var isDescendant = function(el, t) {
        return t.parents().index(el) >= 0;
    };

    var Gmailr = {

        /*****************************************************************************************
         * Public Methods and Variables
         */

        debug: false,
        inboxLink: null,
        priorityInboxLink: null,
        currentNumUnread: null,
        currentInboxCount: null,
        archived: [],
        elements: {},

        /*
            This is the main initialization routine. It bootstraps Gmailr into the Gmail interface.
            You must call this with a callback, like so:

            Gmailr.init(funciton(G) {
                // .. G is the Gmailr API object
            });
        */

        init: function(cb) {
            var self = this;

            dbg('Initializing Gmailr API');

            var delayed_loader_count = 0;

            // Here we do delayed loading until success. This is in the case
            // that our script loads after Gmail has already loaded.
            self.delayed_loader = setInterval(function() {
                self.elements.canvas = $(document.getElementById("canvas_frame").contentDocument);
                self.elements.body   = self.elements.canvas.find('body').first();

                if(self.loaded) {
                    if(delayed_loader_count != 0)
                        dbg('Delayed loader success.');

                    self.elements.canvas.bind("DOMSubtreeModified", function(e) {
                        self.detectDOMEvents(e);
                    });
                    clearInterval(self.delayed_loader);
                } else {
                    dbg('Calling delayed loader...');
                    delayed_loader_count += 1;
                    // we search from the body node, since there's no event to attach to
                    self.bootstrap(self.elements.body, cb);
                }
            }, 500);
        },

        /*
            Inserts the element to the top of the Gmail interface.
        */

        insertTop: function(el) {
            if(!this.loaded) throw "Call to insertTop before Gmail has loaded";
            this.elements.body.prepend($(el));
        },

        /*
            Allows you to apply jQuery selectors in the Gmail DOM, like so:

            G.$('.my_class');
        */

        $: function(selector) {
            return this.elements.body.find(selector);
        },

        /*
            Inserts a CSS file into the Gmail DOM.
        */

        insertCss: function(cssFile) {
            var css = $('<link rel="stylesheet" type="text/css">');
            css.attr('href', cssFile);
            this.elements.canvas.find('head').first().append(css);
        },

        /**
         * Subscribe to a specific event in Gmail
         *   name                arguments passed to callback
         *   'archive'         - count
         *   'numUnreadChange' - currentVal, previousVal
         *   'delete'          - count
         *   'spam'            - count
         *   'compose'
         *   'viewChanged'
         *   'applyLabel'
         */
        observe: function(type, cb) {
            this.ob_queues[type].push(cb);
        },

        /**
         * Number of unread messages.
         */
        numUnread: function() {
            if(!this.loaded) throw "Call to numUnread before Gmail has loaded";

            // We always look to the inbox link, bc it always displays the number unread
            // no matter where you are in Gmail

            //var title = this.inboxLink[0].title;
            var title = this.inboxLink[0].title;
            var m = /\((\d+)\)/.exec(title);
            return (m && m[1]) ? parseInt(m[1]) : 0;
        },

        /**
         * Email address of the Gmail account.
         */
        emailAddress: function() {
            if(!this.loaded) throw "Call to emailAddress before Gmail has loaded";

            // First, try old Gmail header
            var el = this.elements.canvas.find('#guser b');
            if(el.length > 0) return el.first().html();

            // Try the new one
            var el = this.elements.canvas.find('.gbmp1');
            if(el.length > 0) return el.first().html();
        },

        /**
         * Returns whether the current view is a threaded or conversation view.
         */
        currentView: function() {
            if(!this.loaded) throw "Call to currentView before Gmail has loaded";

            if(this.elements.canvas.find('h1.ha').length > 0) {
                return 'conversation';
            } else {
                return 'thread';
            }
        },

        /*****************************************************************************************
         * Private Methods
         */

        /**
         * This method attempts to bootstrap Gmailr into the Gmail interface.
         * Basically, this amounts polling to make sure Gmail has fully loaded,
         * and then setting up some basic hooks.
         */

        bootstrap: function(el, cb) {
            var self = this;
            if(el) {
                var el = $(el);

                // get handle on the left menu
                if(!this.leftMenu || this.leftMenu.length == 0) {
//                  this.leftMenu = el.find('.no .nM .TK').first().closest('.nn');

                    // use the inbox link as an anchor
                    var v = el.find('a[href$="#inbox"]');
                    if(v.length > 0) this.inboxLink = v.first();

                    var v = el.find('a[href$="#mbox"]');
                    if(v.length > 0) this.priorityInboxLink = v.first();

                    if(this.inboxLink) {
                        this.leftMenu = this.inboxLink.closest('.TO').closest('div');
                    } else if(this.priorityInboxLink) {
                        this.leftMenu = this.priorityInboxLink.closest('.TO').closest('div');
                    }

                    if(this.leftMenu && this.leftMenu.length > 0) {
                        this.leftMenuItems = this.leftMenu.find('.TO');

                        p('Fully loaded.');
                        this.loaded = true;

                        this.currentNumUnread = this.numUnread();

                        if(this.inboxTabHighlighted())
                            this.currentInboxCount = this.toolbarCount();

                        this.xhrWatcher.init();

                        if(cb) cb(self);
                    }

                }
            }
        },

        xhrWatcher: {
            xhrParams: null,
            initialized: null,

            init: function() {
                var self = this;

                if(!this.initialized) {
                    this.initialized = true;
                    var win = top.document.getElementById("js_frame").contentDocument.defaultView;

                    win.XMLHttpRequest.prototype._Gmail_open = win.XMLHttpRequest.prototype.open;
                    win.XMLHttpRequest.prototype.open = function (method, url, async, user, password) {
                        var out = this._Gmail_open.apply(this, arguments);
                        this.xhrParams = {
                            method: method.toString(),
                            url: url.toString()
                        };
                        return out;
                    };

                    win.XMLHttpRequest.prototype._Gmail_send = win.XMLHttpRequest.prototype.send;
                    win.XMLHttpRequest.prototype.send = function (body) {
                        var out = this._Gmail_send.apply(this, arguments);
                        if (this.xhrParams) {
                            this.xhrParams.body = body;
                            Gmailr.detectXHREvents(this.xhrParams);
                        }
                        return out;
                    }

                    if (!top._Gmail_iframeFn) {
                        top._Gmail_iframeFn = top.GG_iframeFn;
                        this.iframeData = {};
                        this.iframeCachedData = [];
                        this.iframeCachedData.push({
                            responseDataId: 1,
                            url: top.location.href,
                            responseData: top.VIEW_DATA
                        });

                        top.GG_iframeFn = function(win, data) {
                            var d = top._Gmail_iframeFn.apply(this, arguments);
                            try {
                                var url = win && win.location ? win.location.href: null;
                                if (url && data && (url.indexOf("act=") != -1 )) {

                                    if(!self.iframeData[url]) {

                                        var body = "";
                                        var parent = win.frameElement.parentNode;
                                        if(parent && $(parent).find('form').length > 0)
                                            body = $(parent).find('form').first().serialize();

                                        self.iframeData[url] = true;
                                        Gmailr.detectXHREvents({
                                            body: body,
                                            url: url
                                        });
                                    }
                                }
                            } catch(e) {
                                try {
                                    dbg("DEBUG error in GG_iframeFn: " + e);
                                } catch(e) {}
                            }
                            return d
                        }
                    }

                }
            }
        },

        currentLeftMenuItem: null,
        ob_queues: {
            archive: [],
            'delete': [],
            spam: [],
            reply: [],
            tabChange: [],
            compose: [],
            numUnreadChange: [],
            inboxCountChange: [],
            viewChanged: [],
            applyLabel: []
        },
        loaded: false,

        liveLeftMenuItem: function() {
            if(!this.loaded) return null;

            var el = this.leftMenuItems.filter('.nZ').find('a');
            if(el[0]) {
                return el[0].title;
            } else {
                return null;
            }
        },

        inboxTabHighlighted: function() {
            return this.currentTabHighlighted() == 'inbox' || this.currentTabHighlighted() == 'priority_inbox';
        },

        currentTabHighlighted: function() {
            if(this.inboxLink && this.inboxLink.closest('.TO').hasClass('nZ')) {
                return 'inbox';
            } else if(this.priorityInboxLink && this.priorityInboxLink.closest('.TO').hasClass('nZ')) {
                return 'priority_inbox';
            } else {
                return null;
            }
        },

        // Return true if a yellow archive highlight actually means the user is archiving
        archiveableState: function() {
            // For threads, this overrestricts:
            //   TODO: should detect if user is archiving an inbox item from a non-inbox view
            // For conversations, this underrestricts:
            //   TODO: should detect if the current email is in the inbox, and only assign points if it is
            return (this.inboxTabHighlighted() && this.currentView() == 'thread') || (this.currentView() != 'thread');
        },

        mainListingEl: function() {
            return this.elements.canvas.find('.Cp').first();
        },

        mainListingEmpty: function() {
            if(this.mainListingEl().length > 0 && this.currentView() == 'thread') {
                return this.mainListingEl().find('table tr').length == 0;
            } else {
                return null;
            }
        },

        toolbarEl: function() {
            return this.elements.canvas.find('.A1.D.E').first();
        },

        toolbarCount: function() {
            var el = this.toolbarEl().find('.Dj');
            if(el[0]) {
                var t = el[0].innerHTML;

                var m = /of <b>(\d+)<\/b>/.exec(t);

                if(m && m[1]) {
                    return parseInt(m[1]);
                } else {
                    return null;
                }
            } else {
                if(this.mainListingEmpty()) {
                    return 0;
                } else {
                    return null;
                }
            }
        },

        detectXHREvents: function(params) {
            var self = this;

            try {
                var m = /[?&]act=([^&]+)/.exec(params.url);
                if(m && m[1]) {
                    var action = decodeURIComponent(m[1]);
                    var count = 1;

                    var urlParams = $.deparam(params.url);

                    if(params.body.length > 0) {
                        var postParams = $.deparam(params.body);
                        if(postParams['t'] instanceof Array)
                            count = postParams['t'].length;

                        if(postParams['ba']) {
                            // The user has cleared more than a pageful, just give'em 50 points
                            count = 50;
                        }
                    }
                    switch(action) {
                        case "rc_^i": // Archiving
                            // only count if from inbox or query
                            // TODO: could do better
                            if(urlParams['search'] == 'inbox' || urlParams['search'] == 'query' || urlParams['search'] == 'cat' || urlParams['search'] == 'mbox') {
                                if(postParams) {
                                    // Get list of email hashes
                                    var emails = postParams['t'] instanceof Array ? postParams['t'] : [ postParams['t'] ];
                                    var emailsArchived = [];
                                    emails.forEach(function(e) {
                                        // Make sure user didn't already archive this email
                                        if(self.archived.indexOf(e) == -1)
                                            emailsArchived.push(e);
                                    });

                                    this.archived = this.archived.concat(emailsArchived);

                                    if(emailsArchived.length > 0) {
                                        p("User archived " + emailsArchived.length + " emails.");
                                        this.executeObQueues('archive', count);
                                    }

                                    delete emails;
                                    delete emailsArchived;
                                }
                            }
                            break;
                        case "arl": //applying label
                            var label = urlParams["acn"];
                            var emails = postParams['t'] instanceof Array ? postParams['t'] : [ postParams['t'] ];
                            this.executeObQueues('applyLabel', label, emails);
                            break;
                        case "tr": // Deleting
                            p("User deleted " + count + " emails.");
                            this.executeObQueues('delete', count);
                            break;
                        case "sm": // Composing
                            if(this.currentView() == 'conversation') {
                                p("User replied to an email.");
                                this.executeObQueues('reply', 1);
                            } else {
                                p("User composed an email.");
                                this.executeObQueues('compose', 1);
                            }
                            break;
                        case "sp": // Spam
                            p("User spammed " + count + " emails.");
                            this.executeObQueues('spam', count);
                            break;
                    }
                }
            } catch(e) {
                dbg("Error in detectXHREvents: " + e);
            }
        },

        inConversationView: false,

        detectDOMEvents: function(e) {
            var el = $(e.target);

            // Left Menu Changes
            /*var s = this.liveLeftMenuItem();
            if(this.currentLeftMenuItem != s) {
                this.currentLeftMenuItem = s;
                this.executeObQueues('tabChange', s);
            }

            // Unread change
            var l = this.inboxLink ? this.inboxLink : this.priorityInboxLink;
            if((el[0] == l[0]) || isDescendant(el, l)) {
                if(this.currentNumUnread != this.numUnread()) {
                    this.executeObQueues('numUnreadChange', this.numUnread(), this.currentNumUnread);
                    this.currentNumUnread = this.numUnread();
                }
            }*/

            if(this.elements.canvas.find('.ha').length > 0) {
                if(!this.inConversationView) {
                    this.inConversationView = true;
                    this.executeObQueues('viewChanged', 'conversation');
                }
            } else {
                if(this.inConversationView) {
                    this.inConversationView = false;
                    this.executeObQueues('viewChanged', 'threaded');
                }
            }

            // Inbox count change
            if(isDescendant(this.toolbarEl(), el)) {
                var toolbarCount = this.toolbarCount();
                if(this.inboxTabHighlighted() && toolbarCount !== null) {
                    if((this.currentInboxCount === null) || (toolbarCount != this.currentInboxCount)) {
                        if(this.currentInboxCount !== null)
                            this.executeObQueues('inboxCountChange', toolbarCount, this.currentInboxCount);
                        this.currentInboxCount = toolbarCount;
                    }
                }
            }
        },

        executeObQueues: function(type, arg) {
            if(this.ob_queues[type])
                for(var i = 0; i < this.ob_queues[type].length; i++) {
                    var args = Array.prototype.slice.call(arguments, 1);
                    (this.ob_queues[type][i]).apply(this, args)
                }
        }
    };

    window.Gmailr = Gmailr;
})(jQuery, window);
<commit_msg>fixed inbox link for recent changes in gmail<commit_after>/**
 * Gmailr v0.0.1
 * Licensed under The MIT License
 *
 * Copyright 2011, James Yu
 */

(function($, window) {

    // Utility methods

    var dbg = function(msg) {
        if(Gmailr.debug)
            console.log(msg);
    };

    var p = function(els, m) {
        if(!els) {
            dbg(els);
        } else if(els.each) {
            if(els.length == 0) {
                dbg('p: Empty');
            } else {
                els.each(function(i,e) {
                    dbg(e);
                });
            }
        } else {
            dbg(els);
        }
    };

    var pm = function(m, els) {
        dbg(m);
        p(els);
    };

    var isDescendant = function(el, t) {
        return t.parents().index(el) >= 0;
    };

    var Gmailr = {

        /*****************************************************************************************
         * Public Methods and Variables
         */

        debug: false,
        inboxLink: null,
        priorityInboxLink: null,
        currentNumUnread: null,
        currentInboxCount: null,
        archived: [],
        elements: {},

        /*
            This is the main initialization routine. It bootstraps Gmailr into the Gmail interface.
            You must call this with a callback, like so:

            Gmailr.init(funciton(G) {
                // .. G is the Gmailr API object
            });
        */

        init: function(cb) {
            var self = this;

            dbg('Initializing Gmailr API');

            var delayed_loader_count = 0;

            // Here we do delayed loading until success. This is in the case
            // that our script loads after Gmail has already loaded.
            self.delayed_loader = setInterval(function() {
                self.elements.canvas = $(document.getElementById("canvas_frame").contentDocument);
                self.elements.body   = self.elements.canvas.find('body').first();

                if(self.loaded) {
                    if(delayed_loader_count != 0)
                        dbg('Delayed loader success.');

                    self.elements.canvas.bind("DOMSubtreeModified", function(e) {
                        self.detectDOMEvents(e);
                    });
                    clearInterval(self.delayed_loader);
                } else {
                    dbg('Calling delayed loader...');
                    delayed_loader_count += 1;
                    // we search from the body node, since there's no event to attach to
                    self.bootstrap(self.elements.body, cb);
                }
            }, 500);
        },

        /*
            Inserts the element to the top of the Gmail interface.
        */

        insertTop: function(el) {
            if(!this.loaded) throw "Call to insertTop before Gmail has loaded";
            this.elements.body.prepend($(el));
        },

        /*
            Allows you to apply jQuery selectors in the Gmail DOM, like so:

            G.$('.my_class');
        */

        $: function(selector) {
            return this.elements.body.find(selector);
        },

        /*
            Inserts a CSS file into the Gmail DOM.
        */

        insertCss: function(cssFile) {
            var css = $('<link rel="stylesheet" type="text/css">');
            css.attr('href', cssFile);
            this.elements.canvas.find('head').first().append(css);
        },

        /**
         * Subscribe to a specific event in Gmail
         *   name                arguments passed to callback
         *   'archive'         - count
         *   'numUnreadChange' - currentVal, previousVal
         *   'delete'          - count
         *   'spam'            - count
         *   'compose'
         *   'viewChanged'
         *   'applyLabel'
         */
        observe: function(type, cb) {
            this.ob_queues[type].push(cb);
        },

        /**
         * Number of unread messages.
         */
        numUnread: function() {
            if(!this.loaded) throw "Call to numUnread before Gmail has loaded";

            // We always look to the inbox link, bc it always displays the number unread
            // no matter where you are in Gmail

            //var title = this.inboxLink[0].title;
            var title = this.inboxLink[0].title;
            var m = /\((\d+)\)/.exec(title);
            return (m && m[1]) ? parseInt(m[1]) : 0;
        },

        /**
         * Email address of the Gmail account.
         */
        emailAddress: function() {
            if(!this.loaded) throw "Call to emailAddress before Gmail has loaded";

            // First, try old Gmail header
            var el = this.elements.canvas.find('#guser b');
            if(el.length > 0) return el.first().html();

            // Try the new one
            var el = this.elements.canvas.find('.gbmp1');
            if(el.length > 0) return el.first().html();
        },

        /**
         * Returns whether the current view is a threaded or conversation view.
         */
        currentView: function() {
            if(!this.loaded) throw "Call to currentView before Gmail has loaded";

            if(this.elements.canvas.find('h1.ha').length > 0) {
                return 'conversation';
            } else {
                return 'thread';
            }
        },

        /*****************************************************************************************
         * Private Methods
         */

        /**
         * This method attempts to bootstrap Gmailr into the Gmail interface.
         * Basically, this amounts polling to make sure Gmail has fully loaded,
         * and then setting up some basic hooks.
         */

        bootstrap: function(el, cb) {
            var self = this;
            if(el) {
                var el = $(el);

                // get handle on the left menu
                if(!this.leftMenu || this.leftMenu.length == 0) {
//                  this.leftMenu = el.find('.no .nM .TK').first().closest('.nn');

                    // use the inbox link as an anchor
                    var v = el.find('a[href$="#inbox"][title^="Inbox"]');
                    if(v.length > 0) this.inboxLink = v.first();

                    var v = el.find('a[href$="#mbox"]');
                    if(v.length > 0) this.priorityInboxLink = v.first();

                    if(this.inboxLink) {
                        this.leftMenu = this.inboxLink.closest('.TO').closest('div');
                    } else if(this.priorityInboxLink) {
                        this.leftMenu = this.priorityInboxLink.closest('.TO').closest('div');
                    }

                    if(this.leftMenu && this.leftMenu.length > 0) {
                        this.leftMenuItems = this.leftMenu.find('.TO');

                        p('Fully loaded.');
                        this.loaded = true;

                        this.currentNumUnread = this.numUnread();

                        if(this.inboxTabHighlighted())
                            this.currentInboxCount = this.toolbarCount();

                        this.xhrWatcher.init();

                        if(cb) cb(self);
                    }

                }
            }
        },

        xhrWatcher: {
            xhrParams: null,
            initialized: null,

            init: function() {
                var self = this;

                if(!this.initialized) {
                    this.initialized = true;
                    var win = top.document.getElementById("js_frame").contentDocument.defaultView;

                    win.XMLHttpRequest.prototype._Gmail_open = win.XMLHttpRequest.prototype.open;
                    win.XMLHttpRequest.prototype.open = function (method, url, async, user, password) {
                        var out = this._Gmail_open.apply(this, arguments);
                        this.xhrParams = {
                            method: method.toString(),
                            url: url.toString()
                        };
                        return out;
                    };

                    win.XMLHttpRequest.prototype._Gmail_send = win.XMLHttpRequest.prototype.send;
                    win.XMLHttpRequest.prototype.send = function (body) {
                        var out = this._Gmail_send.apply(this, arguments);
                        if (this.xhrParams) {
                            this.xhrParams.body = body;
                            Gmailr.detectXHREvents(this.xhrParams);
                        }
                        return out;
                    }

                    if (!top._Gmail_iframeFn) {
                        top._Gmail_iframeFn = top.GG_iframeFn;
                        this.iframeData = {};
                        this.iframeCachedData = [];
                        this.iframeCachedData.push({
                            responseDataId: 1,
                            url: top.location.href,
                            responseData: top.VIEW_DATA
                        });

                        top.GG_iframeFn = function(win, data) {
                            var d = top._Gmail_iframeFn.apply(this, arguments);
                            try {
                                var url = win && win.location ? win.location.href: null;
                                if (url && data && (url.indexOf("act=") != -1 )) {

                                    if(!self.iframeData[url]) {

                                        var body = "";
                                        var parent = win.frameElement.parentNode;
                                        if(parent && $(parent).find('form').length > 0)
                                            body = $(parent).find('form').first().serialize();

                                        self.iframeData[url] = true;
                                        Gmailr.detectXHREvents({
                                            body: body,
                                            url: url
                                        });
                                    }
                                }
                            } catch(e) {
                                try {
                                    dbg("DEBUG error in GG_iframeFn: " + e);
                                } catch(e) {}
                            }
                            return d
                        }
                    }

                }
            }
        },

        currentLeftMenuItem: null,
        ob_queues: {
            archive: [],
            'delete': [],
            spam: [],
            reply: [],
            tabChange: [],
            compose: [],
            numUnreadChange: [],
            inboxCountChange: [],
            viewChanged: [],
            applyLabel: []
        },
        loaded: false,

        liveLeftMenuItem: function() {
            if(!this.loaded) return null;

            var el = this.leftMenuItems.filter('.nZ').find('a');
            if(el[0]) {
                return el[0].title;
            } else {
                return null;
            }
        },

        inboxTabHighlighted: function() {
            return this.currentTabHighlighted() == 'inbox' || this.currentTabHighlighted() == 'priority_inbox';
        },

        currentTabHighlighted: function() {
            if(this.inboxLink && this.inboxLink.closest('.TO').hasClass('nZ')) {
                return 'inbox';
            } else if(this.priorityInboxLink && this.priorityInboxLink.closest('.TO').hasClass('nZ')) {
                return 'priority_inbox';
            } else {
                return null;
            }
        },

        // Return true if a yellow archive highlight actually means the user is archiving
        archiveableState: function() {
            // For threads, this overrestricts:
            //   TODO: should detect if user is archiving an inbox item from a non-inbox view
            // For conversations, this underrestricts:
            //   TODO: should detect if the current email is in the inbox, and only assign points if it is
            return (this.inboxTabHighlighted() && this.currentView() == 'thread') || (this.currentView() != 'thread');
        },

        mainListingEl: function() {
            return this.elements.canvas.find('.Cp').first();
        },

        mainListingEmpty: function() {
            if(this.mainListingEl().length > 0 && this.currentView() == 'thread') {
                return this.mainListingEl().find('table tr').length == 0;
            } else {
                return null;
            }
        },

        toolbarEl: function() {
            return this.elements.canvas.find('.A1.D.E').first();
        },

        toolbarCount: function() {
            var el = this.toolbarEl().find('.Dj');
            if(el[0]) {
                var t = el[0].innerHTML;

                var m = /of <b>(\d+)<\/b>/.exec(t);

                if(m && m[1]) {
                    return parseInt(m[1]);
                } else {
                    return null;
                }
            } else {
                if(this.mainListingEmpty()) {
                    return 0;
                } else {
                    return null;
                }
            }
        },

        detectXHREvents: function(params) {
            var self = this;

            try {
                var m = /[?&]act=([^&]+)/.exec(params.url);
                if(m && m[1]) {
                    var action = decodeURIComponent(m[1]);
                    var count = 1;

                    var urlParams = $.deparam(params.url);

                    if(params.body.length > 0) {
                        var postParams = $.deparam(params.body);
                        if(postParams['t'] instanceof Array)
                            count = postParams['t'].length;

                        if(postParams['ba']) {
                            // The user has cleared more than a pageful, just give'em 50 points
                            count = 50;
                        }
                    }
                    switch(action) {
                        case "rc_^i": // Archiving
                            // only count if from inbox or query
                            // TODO: could do better
                            if(urlParams['search'] == 'inbox' || urlParams['search'] == 'query' || urlParams['search'] == 'cat' || urlParams['search'] == 'mbox') {
                                if(postParams) {
                                    // Get list of email hashes
                                    var emails = postParams['t'] instanceof Array ? postParams['t'] : [ postParams['t'] ];
                                    var emailsArchived = [];
                                    emails.forEach(function(e) {
                                        // Make sure user didn't already archive this email
                                        if(self.archived.indexOf(e) == -1)
                                            emailsArchived.push(e);
                                    });

                                    this.archived = this.archived.concat(emailsArchived);

                                    if(emailsArchived.length > 0) {
                                        p("User archived " + emailsArchived.length + " emails.");
                                        this.executeObQueues('archive', count);
                                    }

                                    delete emails;
                                    delete emailsArchived;
                                }
                            }
                            break;
                        case "arl": //applying label
                            var label = urlParams["acn"];
                            var emails = postParams['t'] instanceof Array ? postParams['t'] : [ postParams['t'] ];
                            this.executeObQueues('applyLabel', label, emails);
                            break;
                        case "tr": // Deleting
                            p("User deleted " + count + " emails.");
                            this.executeObQueues('delete', count);
                            break;
                        case "sm": // Composing
                            if(this.currentView() == 'conversation') {
                                p("User replied to an email.");
                                this.executeObQueues('reply', 1);
                            } else {
                                p("User composed an email.");
                                this.executeObQueues('compose', 1);
                            }
                            break;
                        case "sp": // Spam
                            p("User spammed " + count + " emails.");
                            this.executeObQueues('spam', count);
                            break;
                    }
                }
            } catch(e) {
                dbg("Error in detectXHREvents: " + e);
            }
        },

        inConversationView: false,

        detectDOMEvents: function(e) {
            var el = $(e.target);

            // Left Menu Changes
            /*var s = this.liveLeftMenuItem();
            if(this.currentLeftMenuItem != s) {
                this.currentLeftMenuItem = s;
                this.executeObQueues('tabChange', s);
            }

            // Unread change
            var l = this.inboxLink ? this.inboxLink : this.priorityInboxLink;
            if((el[0] == l[0]) || isDescendant(el, l)) {
                if(this.currentNumUnread != this.numUnread()) {
                    this.executeObQueues('numUnreadChange', this.numUnread(), this.currentNumUnread);
                    this.currentNumUnread = this.numUnread();
                }
            }*/

            if(this.elements.canvas.find('.ha').length > 0) {
                if(!this.inConversationView) {
                    this.inConversationView = true;
                    this.executeObQueues('viewChanged', 'conversation');
                }
            } else {
                if(this.inConversationView) {
                    this.inConversationView = false;
                    this.executeObQueues('viewChanged', 'threaded');
                }
            }

            // Inbox count change
            if(isDescendant(this.toolbarEl(), el)) {
                var toolbarCount = this.toolbarCount();
                if(this.inboxTabHighlighted() && toolbarCount !== null) {
                    if((this.currentInboxCount === null) || (toolbarCount != this.currentInboxCount)) {
                        if(this.currentInboxCount !== null)
                            this.executeObQueues('inboxCountChange', toolbarCount, this.currentInboxCount);
                        this.currentInboxCount = toolbarCount;
                    }
                }
            }
        },

        executeObQueues: function(type, arg) {
            if(this.ob_queues[type])
                for(var i = 0; i < this.ob_queues[type].length; i++) {
                    var args = Array.prototype.slice.call(arguments, 1);
                    (this.ob_queues[type][i]).apply(this, args)
                }
        }
    };

    window.Gmailr = Gmailr;
})(jQuery, window);
<|endoftext|>


<commit_before>const cp = require('child_process');
const fs = require('fs');

fs.watch('src', run('build'));
fs.watch('dist/redom.js', () => run('uglify')); // don't do init run
fs.watch('dist/doc.md', run('build-doc'));
fs.watch('test/test.js', run('test'));

function run (script) {
  const child = cp.spawn('npm', ['run', script]);

  child.stdout.pipe(process.stdout);
  child.stderr.pipe(process.stderr);

  return () => run(script);
}
<commit_msg>Preserve stdout/stderr colors<commit_after>const cp = require('child_process');
const fs = require('fs');

fs.watch('src', run('build'));
fs.watch('dist/redom.js', () => run('uglify')); // don't do init run
fs.watch('dist/doc.md', run('build-doc'));
fs.watch('test/test.js', run('test'));

function run (script) {
  cp.spawn('npm', ['run', script], { stdio: 'inherit' });

  return () => run(script);
}
<|endoftext|>


<commit_before>//  Backbone.vm.js 1.0.0

//  (c) 2013-2014 wangxian, DocumentCloud and Investigative Reporters & Editors
//  Backbone may be freely distributed under the MIT license.
//  For all details and documentation:
//  http://github.com/wangxian/backbone.vm

var $        = require("jquery");
var _        = require("underscore");
var Backbone = require("backbone");

// List of view options to be merged as properties.
var vmOptions = ["el", "defaults"];

// Cached regex for stripping vm node attributes. space, \r \n
var vmAttrStripper = /\s+/g;

// for:stuct replace && clear template string
var forTplReplaceStripper = /<!--|-->|\n|'|{|}/g;

// for:struct match dom bind setting string
var forTplOnStripper = /on:(\w+)=(\w+)/g;

// Handle all struct vm attribute tags,
// eg: html, text, on...
var VMhooks = {
  // VM->DOM, text, html, val...
  simple: function(node, funcName) {
    return function(str) { node[funcName](str); };
  },

  // DOM->VM, INPUT(except checkbox/radio) on change
  simpleOnChange: function(model, name) {
    return function() { model.set(name, this.value); };
  },

  // Show/Hide HTML element if vm item is true or false
  show: function(node) {
    return function(isShow) { node[isShow?'show':'hide'](); };
  },

  // Remove HTML element if vm item value is true
  remove: function(node) {
    return function(isRemoved) { if(isRemoved) node.remove(); };
  },

  // DOM->VM, checkbox on click
  checkboxOnClick: function(model, name) {
    return function() {
      var checkboxList = model.get(name);
      var checked = this.checked;
      var value   = this.value;
      if(! _.isArray(checkboxList)) checkboxList = [];
      // if(_.contains(checkboxList, this.value)) {
      //   if(this.checked)
      // }
      if(!checked){
        checkboxList = _.filter(checkboxList, function(num){ return num !== value; });
      } else {
        checkboxList.push(value);
      }
      model.update(name, checkboxList);
    };
  },

  // VM->DOM, radio
  // Handle input radio
  radio: function(radios) {
    return function(value) {
      value = value.toString();
      for(var r in radios) {
        if(r !== value) {
          radios[r].checked = false;
        } else {
          radios[r].checked = true;
        }
      }
    };
  },

  // VM->DOM, checkbox
  // Handle input checkbox
  checkbox: function(checkboxes) {
    return function(arr) {
      if(! _.isArray(arr)) return;
      for(var r in checkboxes) {
        if(! _.contains(arr, r) ) {
          checkboxes[r].checked = false;
        } else {
          checkboxes[r].checked = true;
        }
      }
    };
  },

  // Bind html dom Event to VM function
  bindEventListener: function(vmObj, funcName){
    if(typeof vmObj[funcName] === "function") {
      return _.bind(vmObj[funcName], vmObj);
    }
    return function(){};
  },

  // Bind for:struct each item delete element
  // , function(e, id) {
  // }
  bindForListener: function(vmObj, funcName){
    if(typeof vmObj[funcName] === "function") {
      // return _.bind(vmObj[funcName], vmObj);
      return function(e) {
        _.bind(vmObj[funcName], vmObj)( e, e.currentTarget.getAttribute("vm-forkey") );
      };
    }
  },

  forTemplate: function(node) {
    var tpl = node.innerHTML;
    tpl = tpl.replace(forTplReplaceStripper, function(match){
      if(match === "<!--" || match === "-->" || match === "\n") return "";
      else if(match === "'") return "\\'";
      else if(match === "{") return "'+";
      else if(match === "}") return "+'";
    }).replace(/vm="on:\w+=(\w+)"+/g, 'vm-forkey-$1="true" vm-forkey="\'+ $key +\'"');

    var source = "var isArray = Array.isArray ? Array.isArray(obj) : Object.prototype.toString.call(obj) === '[object Array]';";
    source += "var out = '';";
    source += "if(isArray){ ";
    source += "for(var $key=0,_len=obj.length;$key<_len;$key++){ var $value = obj[$key]; out += '"+ tpl +"'; }";
    source += "} else {";
    source += "for(var $key in obj){ var $value = obj[$key]; out += '"+ tpl +"'; }";
    source += "}; return out;";
    var render = new Function("obj", source);
    // console.log(source);

    return function(obj) {
      var type = Object.prototype.toString.call(obj);
      if(type === "[object Array]" || type === "[object Object]") {
        node.innerHTML = render(obj);
      }
    };
  }

};

// Add a new method to Backbone.Model.prototype
// support vm.delete("userlist[0]") using namespace
Backbone.Model.prototype.delete = function(key) {
  // get key, don't contains . or []
  if(key.indexOf(".") === -1 && key.indexOf("[") === -1) {
    return this.get(key);
  }

  var source = 'return obj.'+ key +';';
  try {
    var execValue = new Function("obj", source);
    return execValue(this.attributes);
  } catch(e) {
    // var e = ex;
    throw new Error("get "+ e.message +" error, source="+ source);
  }
};

// Add a new method to Backbone.Model.prototype
// support vm.read("userlist[0].name") using namespace
// Backbone.Model.prototype.read = function(key) {
//   // get key, don't contains . or []
//   if(key.indexOf(".") === -1 && key.indexOf("[") === -1) {
//     return this.get(key);
//   }

//   var source = 'return obj.'+ key +';';
//   try {
//     var execValue = new Function("obj", source);
//     return execValue(this.attributes);
//   } catch(e) {
//     // var e = ex;
//     throw new Error("get "+ e.message +" error, source="+ source);
//   }
// };

// Create a plugin from backbone.js, Similar usage and backbone.view
var VM = Backbone.VM = function(options) {
  this.cid = _.uniqueId('vm');
  if(!options) { options = {}; }
  _.extend(this, _.pick(options, vmOptions));

  this.$el = $(this.el);
  this._vm  = new Backbone.Model();

  this._scanAttrs();

  // this._vm.on("all", function(){
  //   console.log(arguments);
  // });
  this._vm.on("change", this._updateVM, this);
  this._vm.set(this.defaults);

  this.initialize.apply(this, arguments);
};

_.extend(VM.prototype, {

  // Set default vm scope,
  // When user's app not defined, set the defulat el 'body'
  el: "body",

  // Wrapper el using jQuery
  $el: null,

  // Store the relationship between Dom and model
  // eg, { "nickname":[ function(){}, .... ]}
  attrs: {},

  // Default VM value
  defaults: {},

  // Initialize is an empty function by default. Override it with your own
  // initialization logic.
  initialize: function() {},

  // VM's model, Your app can use it to set VM value
  // eg, this._vm.set("name", "tom")
  vm: null,

  // Store input[type=radio] & input[type=checkbox] bind data
  input: {
    radio: {},
    checkbox: {}
  },

  // Update VM bind node when vm model is updated
  _updateVM: function(model) {
    // console.info("model updated:", model.changed, model.toJSON());
    var it = this;
    var attrs = _.pick(this.attrs, _.keys(model.changed));
    _.each(attrs, function(v, k){
      _.each(v, function(func){
        func(it._vm.get(k));
      });
    });
  },

  // Scan html dom attribute contains vm="*"
  _scanAttrs: function() {
    var it = this;
    this.$el.find("[vm]").each(function(k, node){
      // VM HTML DOM node
      var vmNodeEl   = node.getAttribute("vm").replace(vmAttrStripper, "").split(",");
      _.each(vmNodeEl, function(v){
        var arr = v.split(":");
        var vmKey = arr[0];
        var vmVal = arr[1];

        if(vmKey === "on") {
          var ev = vmVal.split("=");
          // console.log(ev);
          $(node).on( ev[0], VMhooks.bindEventListener(it, ev[1]) ).attr("vm-dombind", "");
        } else if(vmKey === "show") {
          it.attrs[ vmVal ] = [ VMhooks.show( $(node) ) ];
        } else if(vmKey === "remove") {
          it.attrs[ vmVal ] = [ VMhooks.remove( $(node) ) ];
        } else if(vmKey === "for") {
          // bind for:struct dom event
          var bindInfo;
          var isAddforkey = false;
          while( (bindInfo = forTplOnStripper.exec(node.innerHTML)) !== null) {
            var $node = $(node).on( bindInfo[1], "[vm-forkey-"+ bindInfo[2] +"]", VMhooks.bindForListener(it, bindInfo[2]) );
            if(!isAddforkey) {
              isAddforkey = true;
              $node.attr("vm-dombind", "");
            }
          }

          if(! it.attrs[ vmVal ] ) it.attrs[ vmVal ] = [];
          it.attrs[ vmVal ].push( VMhooks.forTemplate( node ) );
        } else {

          // Bind VM -> DOM, for: text, val <-> vm model
          if(! it.attrs[ vmVal ] ) it.attrs[ vmVal ] = [];
          if(node.type === "radio" || node.type === "checkbox") {
            if(! it.input[node.type][vmVal] ) {
              // When it.input.[node.type][vmVal] is empty
              // input.[node.type] like { "age":{"1": Node }}
              var ri = {};
              ri[ node.value ] = node;
              it.input[node.type][ vmVal ] = ri;
              // console.log(it.input[node.type]);

              it.attrs[ vmVal ].push( VMhooks[node.type]( it.input[node.type][ vmVal ]) );
            } else {
              // input[node.type] like { "age":{"1": Node }}
              it.input[node.type][ vmVal ][node.value] = node;
            }
          } else {
            // simple div,
            it.attrs[ vmVal ].push( VMhooks.simple( $(node), vmKey ) );
          }

          // Bind DOM -> VM, for: input on change
          if( (node.nodeName === "INPUT" || node.nodeName === "SELECT") && vmKey === "val") {
            if(node.type === "radio") {
              $(node).on("click", VMhooks.simpleOnChange( it._vm, vmVal ) ).attr("vm-dombind", "");
            } else if(node.type === "checkbox"){
              $(node).on("click", VMhooks.checkboxOnClick( it._vm, vmVal ) ).attr("vm-dombind", "");
            } else {
              $(node).on("change", VMhooks.simpleOnChange( it._vm, vmVal ) ).attr("vm-dombind", "");
            }
          }
        }

      });
    });
  },

  // get value from this._vm
  get: function(key){
    return this._vm.get(key);
  },

  // Add a new method to Backbone.Model.prototype
  // support update model every time
  // support vm.update("userlist[0].name") using namespace
  set: function(key, value, options) {
    var attr, changed;
    if(typeof options === "undefined") options = {};
    var silent = options.silent;

    if(typeof key === "object") {
      this._vm.changed = key;
      for(attr in key) this._vm.attributes[attr] = key[attr];
    } else {
      var firstKeyPos = key.search(/\[|\./);

      if(firstKeyPos === -1) {
        this._vm.attributes[key] = value;
        (changed = {})[key] = value;
        this._vm.changed = changed;
      } else {
        var newKey = key;
        var key      = newKey.slice(0, firstKeyPos);
        var lastKey  = newKey.slice(firstKeyPos);
        var itemObj  = this._vm.attributes[key];

        var source = 'obj'+ lastKey +'=value;';
        try {
          var execValue = new Function("obj,value", source);
          execValue(itemObj, value);
        } catch(e) {
          // var e = ex;
          throw new Error("update "+ key + lastKey+" error, source="+ source);
        }
      }
      if(!silent) this._vm.trigger("change:"+ key, this._vm);
    }
    if(!silent) this._vm.trigger("change", this._vm);
  },

  // destory VM object when a new vm is not used
  // clear vm variable, unbind dom delegate
  // @todo ...
  destroy: function() {
    this._vm.off(null, null, this);
    this.$el.find("[vm-dombind]").off();
    this.$el = null;
    this._vm  = null;
    this.attrs = null;
    this.input = null;
  }

});

// Set up inheritance for the VM
// Similar to `goog.inherits`, but uses a hash of prototype properties and
// class properties to be extended.
VM.extend = Backbone.Model.extend;

// Return a CMD module from seajs,
// It must be loaded after backbone.js
module.exports = VM;
<commit_msg>FIXED: event bind fixed, use vm.set<commit_after>//  Backbone.vm.js 1.0.0

//  (c) 2013-2014 wangxian, DocumentCloud and Investigative Reporters & Editors
//  Backbone may be freely distributed under the MIT license.
//  For all details and documentation:
//  http://github.com/wangxian/backbone.vm

var $        = require("jquery");
var _        = require("underscore");
var Backbone = require("backbone");

// List of view options to be merged as properties.
var vmOptions = ["el", "defaults"];

// Cached regex for stripping vm node attributes. space, \r \n
var vmAttrStripper = /\s+/g;

// for:stuct replace && clear template string
var forTplReplaceStripper = /<!--|-->|\n|'|{|}/g;

// for:struct match dom bind setting string
var forTplOnStripper = /on:(\w+)=(\w+)/g;

// Handle all struct vm attribute tags,
// eg: html, text, on...
var VMhooks = {
  // VM->DOM, text, html, val...
  simple: function(node, funcName) {
    return function(str) { node[funcName](str); };
  },

  // DOM->VM, INPUT(except checkbox/radio) on change
  simpleOnChange: function(vm, name) {
    return function() { vm.set(name, this.value); };
  },

  // Show/Hide HTML element if vm item is true or false
  show: function(node) {
    return function(isShow) { node[isShow?'show':'hide'](); };
  },

  // Remove HTML element if vm item value is true
  remove: function(node) {
    return function(isRemoved) { if(isRemoved) node.remove(); };
  },

  // DOM->VM, checkbox on click
  checkboxOnClick: function(vm, name) {
    return function() {
      var checkboxList = vm.get(name);
      var checked = this.checked;
      var value   = this.value;
      if(! _.isArray(checkboxList)) checkboxList = [];
      // if(_.contains(checkboxList, this.value)) {
      //   if(this.checked)
      // }
      if(!checked){
        checkboxList = _.filter(checkboxList, function(num){ return num !== value; });
      } else {
        checkboxList.push(value);
      }
      vm.set(name, checkboxList);
    };
  },

  // VM->DOM, radio
  // Handle input radio
  radio: function(radios) {
    return function(value) {
      value = value.toString();
      for(var r in radios) {
        if(r !== value) {
          radios[r].checked = false;
        } else {
          radios[r].checked = true;
        }
      }
    };
  },

  // VM->DOM, checkbox
  // Handle input checkbox
  checkbox: function(checkboxes) {
    return function(arr) {
      if(! _.isArray(arr)) return;
      for(var r in checkboxes) {
        if(! _.contains(arr, r) ) {
          checkboxes[r].checked = false;
        } else {
          checkboxes[r].checked = true;
        }
      }
    };
  },

  // Bind html dom Event to VM function
  bindEventListener: function(vmObj, funcName){
    if(typeof vmObj[funcName] === "function") {
      return _.bind(vmObj[funcName], vmObj);
    }
    return function(){};
  },

  // Bind for:struct each item delete element
  // , function(e, id) {
  // }
  bindForListener: function(vmObj, funcName){
    if(typeof vmObj[funcName] === "function") {
      // return _.bind(vmObj[funcName], vmObj);
      return function(e) {
        _.bind(vmObj[funcName], vmObj)( e, e.currentTarget.getAttribute("vm-forkey") );
      };
    }
  },

  forTemplate: function(node) {
    var tpl = node.innerHTML;
    tpl = tpl.replace(forTplReplaceStripper, function(match){
      if(match === "<!--" || match === "-->" || match === "\n") return "";
      else if(match === "'") return "\\'";
      else if(match === "{") return "'+";
      else if(match === "}") return "+'";
    }).replace(/vm="on:\w+=(\w+)"+/g, 'vm-forkey-$1="true" vm-forkey="\'+ $key +\'"');

    var source = "var isArray = Array.isArray ? Array.isArray(obj) : Object.prototype.toString.call(obj) === '[object Array]';";
    source += "var out = '';";
    source += "if(isArray){ ";
    source += "for(var $key=0,_len=obj.length;$key<_len;$key++){ var $value = obj[$key]; out += '"+ tpl +"'; }";
    source += "} else {";
    source += "for(var $key in obj){ var $value = obj[$key]; out += '"+ tpl +"'; }";
    source += "}; return out;";
    var render = new Function("obj", source);
    // console.log(source);

    return function(obj) {
      var type = Object.prototype.toString.call(obj);
      if(type === "[object Array]" || type === "[object Object]") {
        node.innerHTML = render(obj);
      }
    };
  }

};

// Add a new method to Backbone.Model.prototype
// support vm.delete("userlist[0]") using namespace
// Backbone.Model.prototype.delete = function(key) {
//   // get key, don't contains . or []
//   if(key.indexOf(".") === -1 && key.indexOf("[") === -1) {
//     return this.get(key);
//   }

//   var source = 'return obj.'+ key +';';
//   try {
//     var execValue = new Function("obj", source);
//     return execValue(this.attributes);
//   } catch(e) {
//     // var e = ex;
//     throw new Error("get "+ e.message +" error, source="+ source);
//   }
// };

// Add a new method to Backbone.Model.prototype
// support vm.read("userlist[0].name") using namespace
// Backbone.Model.prototype.read = function(key) {
//   // get key, don't contains . or []
//   if(key.indexOf(".") === -1 && key.indexOf("[") === -1) {
//     return this.get(key);
//   }

//   var source = 'return obj.'+ key +';';
//   try {
//     var execValue = new Function("obj", source);
//     return execValue(this.attributes);
//   } catch(e) {
//     // var e = ex;
//     throw new Error("get "+ e.message +" error, source="+ source);
//   }
// };

// Create a plugin from backbone.js, Similar usage and backbone.view
var VM = Backbone.VM = function(options) {
  this.cid = _.uniqueId('vm');
  if(!options) { options = {}; }
  _.extend(this, _.pick(options, vmOptions));

  this.$el = $(this.el);
  this._vm  = new Backbone.Model();

  this._scanAttrs();

  // this._vm.on("all", function(){
  //   console.log(arguments);
  // });
  this._vm.on("change", this._updateVM, this);
  this._vm.set(this.defaults);

  this.initialize.apply(this, arguments);
};

_.extend(VM.prototype, {

  // Set default vm scope,
  // When user's app not defined, set the defulat el 'body'
  el: "body",

  // Wrapper el using jQuery
  $el: null,

  // Store the relationship between Dom and model
  // eg, { "nickname":[ function(){}, .... ]}
  attrs: {},

  // Default VM value
  defaults: {},

  // Initialize is an empty function by default. Override it with your own
  // initialization logic.
  initialize: function() {},

  // VM's model, Your app can use it to set VM value
  // eg, this._vm.set("name", "tom")
  vm: null,

  // Store input[type=radio] & input[type=checkbox] bind data
  input: {
    radio: {},
    checkbox: {}
  },

  // Update VM bind node when vm model is updated
  _updateVM: function(model) {
    // console.info("model updated:", model.changed, model.toJSON());
    var it = this;
    var attrs = _.pick(this.attrs, _.keys(model.changed));
    _.each(attrs, function(v, k){
      _.each(v, function(func){
        func(it._vm.get(k));
      });
    });
  },

  // Scan html dom attribute contains vm="*"
  _scanAttrs: function() {
    var it = this;
    this.$el.find("[vm]").each(function(k, node){
      // VM HTML DOM node
      var vmNodeEl   = node.getAttribute("vm").replace(vmAttrStripper, "").split(",");
      _.each(vmNodeEl, function(v){
        var arr = v.split(":");
        var vmKey = arr[0];
        var vmVal = arr[1];

        if(vmKey === "on") {
          var ev = vmVal.split("=");
          // console.log(ev);
          $(node).on( ev[0], VMhooks.bindEventListener(it, ev[1]) ).attr("vm-dombind", "");
        } else if(vmKey === "show") {
          it.attrs[ vmVal ] = [ VMhooks.show( $(node) ) ];
        } else if(vmKey === "remove") {
          it.attrs[ vmVal ] = [ VMhooks.remove( $(node) ) ];
        } else if(vmKey === "for") {
          // bind for:struct dom event
          var bindInfo;
          var isAddforkey = false;
          while( (bindInfo = forTplOnStripper.exec(node.innerHTML)) !== null) {
            var $node = $(node).on( bindInfo[1], "[vm-forkey-"+ bindInfo[2] +"]", VMhooks.bindForListener(it, bindInfo[2]) );
            if(!isAddforkey) {
              isAddforkey = true;
              $node.attr("vm-dombind", "");
            }
          }

          if(! it.attrs[ vmVal ] ) it.attrs[ vmVal ] = [];
          it.attrs[ vmVal ].push( VMhooks.forTemplate( node ) );
        } else {

          // Bind VM -> DOM, for: text, val <-> vm model
          if(! it.attrs[ vmVal ] ) it.attrs[ vmVal ] = [];
          if(node.type === "radio" || node.type === "checkbox") {
            if(! it.input[node.type][vmVal] ) {
              // When it.input.[node.type][vmVal] is empty
              // input.[node.type] like { "age":{"1": Node }}
              var ri = {};
              ri[ node.value ] = node;
              it.input[node.type][ vmVal ] = ri;
              // console.log(it.input[node.type]);

              it.attrs[ vmVal ].push( VMhooks[node.type]( it.input[node.type][ vmVal ]) );
            } else {
              // input[node.type] like { "age":{"1": Node }}
              it.input[node.type][ vmVal ][node.value] = node;
            }
          } else {
            // simple div,
            it.attrs[ vmVal ].push( VMhooks.simple( $(node), vmKey ) );
          }

          // Bind DOM -> VM, for: input on change
          if( (node.nodeName === "INPUT" || node.nodeName === "SELECT") && vmKey === "val") {
            if(node.type === "radio") {
              $(node).on("click", VMhooks.simpleOnChange( it, vmVal ) ).attr("vm-dombind", "");
            } else if(node.type === "checkbox"){
              $(node).on("click", VMhooks.checkboxOnClick( it, vmVal ) ).attr("vm-dombind", "");
            } else {
              $(node).on("change", VMhooks.simpleOnChange( it, vmVal ) ).attr("vm-dombind", "");
            }
          }
        }

      });
    });
  },

  // get value from this._vm
  get: function(key){
    return this._vm.get(key);
  },

  // Add a new method to Backbone.Model.prototype
  // support update model every time
  // support vm.update("userlist[0].name") using namespace
  set: function(key, value, options) {
    var attr, changed;
    if(typeof options === "undefined") options = {};
    var silent = options.silent;

    if(typeof key === "object") {
      this._vm.changed = key;
      for(attr in key) this._vm.attributes[attr] = key[attr];
    } else {
      var firstKeyPos = key.search(/\[|\./);

      if(firstKeyPos === -1) {
        this._vm.attributes[key] = value;
        (changed = {})[key] = value;
        this._vm.changed = changed;
      } else {
        var newKey = key;
        var key      = newKey.slice(0, firstKeyPos);
        var lastKey  = newKey.slice(firstKeyPos);
        var itemObj  = this._vm.attributes[key];

        var source = 'obj'+ lastKey +'=value;';
        try {
          var execValue = new Function("obj,value", source);
          execValue(itemObj, value);
        } catch(e) {
          // var e = ex;
          throw new Error("update "+ key + lastKey+" error, source="+ source);
        }
      }
      if(!silent) this._vm.trigger("change:"+ key, this._vm);
    }
    if(!silent) this._vm.trigger("change", this._vm);
  },

  // destory VM object when a new vm is not used
  // clear vm variable, unbind dom delegate
  // @todo ...
  destroy: function() {
    this._vm.off(null, null, this);
    this.$el.find("[vm-dombind]").off();
    this.$el = null;
    this._vm  = null;
    this.attrs = null;
    this.input = null;
  }

});

// Set up inheritance for the VM
// Similar to `goog.inherits`, but uses a hash of prototype properties and
// class properties to be extended.
VM.extend = Backbone.Model.extend;

// Return a CMD module from seajs,
// It must be loaded after backbone.js
module.exports = VM;
<|endoftext|>


<commit_before>/*
 * --------------------------------------------
 * 全局通用接口实现文件
 *  NEJ.O  - 空对象实例
 *  NEJ.R  - 空数组实例
 *  NEJ.F  - 空函数实例
 *  NEJ.P  - 名字空间申明
 *  NEJ.C  - 类构造，带扩展接口
 *  NEJ.X  - 对象属性拷贝，带过滤
 *  NEJ.EX - 对象属性选择性拷贝
 *
 *  nej.c  - 配置信息名字空间
 *  nej.g  - 全局常量名字空间
 *  nej.p  - 平台接口名字空间
 *  nej.u  - 通用接口名字空间
 *  nej.e  - 节点接口名字空间
 *  nej.v  - 事件接口名字空间
 *  nej.j  - 请求接口名字空间
 *  nej.x  - 用于链式调用扩展名字空间
 *
 *  nej.ui - UI控件名字空间
 *  nej.ut - 通用控件名字空间
 *  nej.mb - 移动控件名字空间
 *
 * @version  1.0
 * @author   genify(caijf@corp.netease.com)
 * --------------------------------------------
 */
var f = function(){
    // NEJ namespace
    window.NEJ = window.NEJ||{};
    /**
     * 空对象实例，使用过程中不允许对其做设置操作

     * @const {NEJ.O}
     * @type  {Object}
     */
    NEJ.O = {};
    /**
     * 空数组实例，使用过程中不允许对其做设置操作

     * @const {NEJ.R}
     * @type  {Array}
     */
    NEJ.R = [];
    /**
     * 空函数实例

     * @const {NEJ.F}
     * @type  {Function}
     */
    NEJ.F = function(){return !1;};
    /**
     * 返回指定的命名空间，如果不存在则新建一个命名空间

     * 注意：命名空间不要使用浏览器保留的关键字
     * [code]
     *   // 以下两者都将建立 window.ui, 然后返回 window.ui.package
     *   var p1 = NEJ.P("ui.package");
     *   var p2 = NEJ.P("window.ui.package");
     * [/code]
     * @api    {NEJ.P}
     * @param  {String} 命名空间的名称，大小写敏感
     * @return {Object} 生成的命名空间对象
     */
    NEJ.P = function(_namespace){
        if (!_namespace||!_namespace.length) return null;
        var _package = window;
        for(var a=_namespace.split('.'),
                l=a.length,i=(a[0]=='window')?1:0;i<l;
                _package=_package[a[i]]=_package[a[i]]||{},i++);
        return  _package;
    };
    /**
     * 读取上下文中指定名字空间的值
     * [code]
     *     var obj = {
     *         a:{
     *             b:{
     *                 c:{
     *                     d:'ddddd'
     *                 }
     *             }
     *         }
     *     };
     *     // print ddddd
     *     console.log(NEJ.Q(obj,'a.b.c.d'));
     *     // print undefined
     *     console.log(NEJ.Q(null,'a.b.c.d'));
     * [/code]
     * @api    {NEJ.Q}
     * @param  {Object}   上下文
     * @param  {String}   名字空间
     * @return {Varaible} 值
     */
    NEJ.Q = function(_context,_namespace){
        _context = _context||NEJ.O;
        var _arr = _namespace.split('.');
        for(var i=0,l=_arr.length;i<l;i++){
            _context = _context[_arr[i]];
            if (!_context) break;
        }
        return _context;
    };
    /**
     * 定义类，通过此api定义的类具有以下特性：

     * [ul]
     *   具有静态扩展接口_$extend
     *   __init作为类的初始化函数，如果继承了其他类，则可以通过__supInit调用父类初始化函数
     *   如果按照NEJ控件体系规范，则__reset/__destroy/__initNode可以使用__supXxx的形式调用父类接口
     * [/ul]
     * [code]
     *   // 定义类A
     *   var A = NEJ.C();
     *   // 初始化
     *   A.prototype.__init = function(){
     *       // do init
     *   };
     *
     *   // 定义类B，并继承自A
     *   // _$extend接口第二个参数控制是否继承父类静态接口，默认继承，传false禁止继承
     *   var B = NEJ.C();
     *   B._$extend(A);
     *   // B._$extend(A,false); // 不继承父类静态接口
     *   // 初始化
     *   B.prototype.__init = function(){
     *       // 调用A的初始化逻辑
     *       this.__supInit();
     *       // TODO B的初始化逻辑
     *   };
     * [/code]
     * @api    {NEJ.C}
     * @return {Function} 返回创建的类
     */
    NEJ.C = (function(){
        var _isNotFunction = function(){
            return NEJ.O.toString.call(
                   arguments[0])!='[object Function]';
        };
        var _getMethodName = function(_value,_map){
            for(var x in _map)
                if (_value==_map[x])
                    return x;
            return null;
        };
        // build super for method
        var _mmap = {__init:0,__reset:1,__destroy:2,__initNode:3,
                     __doBuild:4,__onShow:5,__onHide:6,__onRefresh:7},
            _umap = {__supInit:0,__supReset:1,__supDestroy:2,__supInitNode:3,
                     __supDoBuild:4,__supOnShow:5,__supOnHide:6,__supOnRefresh:7};
        return function(){
            // class constructor
            var _class = function(){
                this.__cp__();
                return this.__init.apply(this,arguments);
            };
            // class inherit
            _class.prototype.__cp__ = NEJ.F;
            _class.prototype.__init = NEJ.F;
            _class._$extend = function(_super,_static){
                if (_isNotFunction(_super)) return;
                // extend static methods
                if (_static==null||!!_static)
                    NEJ.X(this,_super,_isNotFunction);
                // extend instance properties and methods
                this._$super = _super;
                this._$supro = _super.prototype;
                var _parent = function(){};
                _parent.prototype = _super.prototype;
                this.prototype = new _parent();
                var _prototype = this.prototype;
                _prototype.constructor = this;
                var _tmp;
                // for common method
                for(var x in _mmap){
                    _tmp = _getMethodName(_mmap[x],_umap);
                    if (!_tmp||!this._$supro[x]) continue;
                    _prototype[x] = (function(_name){
                        return function(){
                            this[_name].apply(this,arguments);
                        };
                    })(_tmp);
                }
                // for super method
                var _pmap = {};
                for(var x in _umap){
                    _tmp = _getMethodName(_umap[x],_mmap);
                    if (!_tmp||!this._$supro[_tmp]) continue;
                    _pmap[_tmp] = _super;
                    _prototype[x] = (function(_name){
                        return function(){
                            var _result,
                                _parent = this.__ancestor__[_name],
                                _method = _parent.prototype[_name];
                            this.__ancestor__[_name] = _parent._$super||_super;
                            if (!!_method)
                                _result = _method.apply(this,arguments);
                            this.__ancestor__[_name] = _super; // fix broken chain
                            return _result;
                        };
                    })(_tmp);
                }
                _prototype.__cp__ = function(){
                    this.__ancestor__ = NEJ.X({},_pmap);
                };
                _prototype.__super = _prototype.__supInit;
                return _prototype;
            };
            return _class;
        };
    })();
    /**
     * 拷贝对象属性，过滤接口返回值含义
     *
     * 脚本举例
     * [code]
     *   var _obj0 = {a:0,b:1},
     *       _obj1 = {a:"a",b:"b",c:"c"};
     *   // 根据_obj1的属性拷贝对象到_obj0
     *   // 结果是_obj0.a = "a",_obj.b = "b",_obj.c = "c";
     *   var _obj = NEJ.X(_obj0,_obj1);
     * [/code]
     *
     * 脚本举例
     * [code]
     *   var _obj0 = {a:0,b:1},
     *       _obj1 = {a:"a",b:"b",c:"c"};
     *   var _filter = function(_value,_key){
     *       if (_key != "a")
     *           return false;
     *   }
     *   // 根据_obj1的属性拷贝对象到_obj0,过滤器过滤掉所有key值不是a的属性
     *   // 结果是_obj0.a = 0,_obj.b = "b",_obj.c = "c";
     *   var _obj = NEJ.X(_obj0,_obj1,_filter);
     * [/code]
     *
     * @see    {#.EX}
     * @api    {NEJ.X}
     * @param  {Object}   原始对象
     * @param  {Object}   待拷贝对象
     * @param  {Function} 过滤接口
     * [ntb]
     *  输入 | Variable | 值
     *       | String   | 键
     *  输出 | Boolean  | 是否过滤
     * [/ntb]
     * @return {Object} 拷贝后对象
     */
    NEJ.X = function(_object,_config,_filter){
        if (!_object||!_config)
            return _object;
        _filter = _filter||NEJ.F;
        for(var x in _config){
            if (_config.hasOwnProperty(x)&&
               !_filter(_config[x],x))
                _object[x] = _config[x];
        }
        return _object;
    };
    /**
     * 根据原始对象属性，从目标对象拷贝相应值

     *
     * 脚本举例
     * [code]
     *   var _obj0 = {a:0,b:1},
     *       _obj1 = {a:"a",b:"b",c:"c"};
     *   // 根据_obj0的属性,从_obj1拷贝属性到_obj0中,如果属性的value是null不会被拷贝
     *   // 结果是_obj0.a = "a",_obj.b = "b",没有拷贝c属性;
     *   var _obj = NEJ.EX(_obj0,_obj1);
     * [/code]
     *
     * @see    {#.X}
     * @api    {NEJ.EX}
     * @param  {Object} 原始对象
     * @param  {Object} 目标对象
     * @return {Void}
     */
    NEJ.EX = function(_object,_config){
        if (!_object||!_config)
            return _object;
        for(var x in _object){
            if (_object.hasOwnProperty(x)&&
                _config[x]!=null)
                _object[x] = _config[x];
        }
        return _object;
    };
    // extend native object method
    var _extpro = Function.prototype;
    /**
     * AOP增强操作，增强操作接受一个输入参数包含以下信息
     *
     * [ntb]
     *  参数名称       | 参数类型          | 参数描述
     *  ------------------------------------
     *  args    | Array    | 函数调用时实际输入参数，各增强操作中可以改变值后将影响至后续的操作
     *  stopped | Boolean  | 是否结束操作，终止后续操作
     *  value   | Variable | 输出结果
     * [/ntb]
     *
     * @api    {Function.prototype._$aop}
     * @param  {Function} 之前操作，接受一个输入参数，见描述信息
     * @param  {Function} 之后操作，接受一个输入参数，见描述信息
     * @return {Function} 增强后操作函数
     */
    _extpro._$aop = function(_before,_after){
        var f = NEJ.F,
            _after = _after||f,
            _before = _before||f,
            _handler = this;
        return function(){
            var _event = {args:NEJ.R.slice.call(arguments,0)};
            _before(_event);
            if (!_event.stopped){
                _event.value = _handler
                      .apply(this,_event.args);
                _after(_event);
            }
            return _event.value;
        };
    };
    /**
     * 绑定接口及参数，使其的调用对象保持一致

     *
     * 脚本举例
     * [code]
     *   var _v = NEJ.P("nej.v"),
     *       _obj = {a:0};
     *   var _f = function(_a,_b){
     *       // 第一个参数 ：1
     *       console.log(_a);
     *       // 第二个参数 ： 2
     *       consoel.log(_b);
     *       // 当前this.a ： 0
     *       console.log(this.a);
     *   }._$bind(_obj,"1");
     *   _f(2);
     * [/code]
     *
     * @see    {#_$bind2}
     * @api    {Function.prototype._$bind}
     * @param  {Object}   _object 需要保持一致的对象，null表示window对象
     * @param  {Variable} [argument0[,argument1 ...]] 函数调用时需要的参数
     * @return {Function} 返回绑定后的函数
     */
    _extpro._$bind = function() {
        var _args = arguments,
            _object = arguments[0],
            _function = this;
        return function(){
            // not use slice for chrome 10 beta and Array.apply for android
            var _argc = NEJ.R.slice.call(_args,1);
            NEJ.R.push.apply(_argc,arguments);
            return _function.apply(_object||window,_argc);
        };
    };
    /**
     * 绑定接口及参数，使其的调用对象保持一致，
     * 该接口与_$bind接口的差别在于绑定时参数和调用时参数的顺序不一样，
     * _$bind优先传入绑定时参数
     * _$bind2优先传入调用时参数

     *
     * 脚本举例
     * [code]
     *   var _v = NEJ.P("nej.v"),
     *       _obj = {a:0};
     *   var _f = function(_a,_b){
     *       // 第一个参数 ：2
     *       console.log(_a);
     *       // 第二个参数 ： 1
     *       consoel.log(_b);
     *       // 当前this.a ： 0
     *       console.log(this.a);
     *   }._$bind(_obj,"1");
     *   _f(2);
     * [/code]
     *
     * @see    {#_$bind}
     * @api    {Function.prototype._$bind2}
     * @param  {Object}   需要保持一致的对象，null表示window对象
     * @param  {Variable} 函数调用时需要的参数
     * @return {Function} 返回绑定后的事件函数
     */
    _extpro._$bind2 = function() {
        var _args = arguments,
            _object = NEJ.R.shift.call(_args),
            _function = this;
        return function(){
            NEJ.R.push.apply(arguments,_args);
            return _function.apply(_object||window,arguments);
        }
    };
    var _extpro = String.prototype;
    if (!_extpro.trim){
         _extpro.trim = (function(){
            var _reg = /(?:^\s+)|(?:\s+$)/g;
            return function(){
                return this.replace(_reg,'');
            };
         })();
    }
    // mwf adaptation
    if (!window.MWF) window.MWF = NEJ;
    if (!window.mwf) window.mwf = NEJ.P('nej');
    if (!window.console){
        NEJ.P('console').log = NEJ.F;
        NEJ.P('console').error = NEJ.F;
    }
};
NEJ.define('{lib}base/global.js',f);<commit_msg>void generating html entry when publish<commit_after>/*
 * --------------------------------------------
 * 全局通用接口实现文件
 *  NEJ.O  - 空对象实例
 *  NEJ.R  - 空数组实例
 *  NEJ.F  - 空函数实例
 *  NEJ.P  - 名字空间申明
 *  NEJ.C  - 类构造，带扩展接口
 *  NEJ.X  - 对象属性拷贝，带过滤
 *  NEJ.EX - 对象属性选择性拷贝
 *
 *  nej.c  - 配置信息名字空间
 *  nej.g  - 全局常量名字空间
 *  nej.p  - 平台接口名字空间
 *  nej.u  - 通用接口名字空间
 *  nej.e  - 节点接口名字空间
 *  nej.v  - 事件接口名字空间
 *  nej.j  - 请求接口名字空间
 *  nej.x  - 用于链式调用扩展名字空间
 *
 *  nej.ui - UI控件名字空间
 *  nej.ut - 通用控件名字空间
 *  nej.mb - 移动控件名字空间
 *
 * @version  1.0
 * @author   genify(caijf@corp.netease.com)
 * --------------------------------------------
 */
var f = function(){
    // NEJ namespace
    window.NEJ = window.NEJ||{};
    /**
     * 空对象实例，使用过程中不允许对其做设置操作

     * @const {NEJ.O}
     * @type  {Object}
     */
    NEJ.O = {};
    /**
     * 空数组实例，使用过程中不允许对其做设置操作

     * @const {NEJ.R}
     * @type  {Array}
     */
    NEJ.R = [];
    /**
     * 空函数实例

     * @const {NEJ.F}
     * @type  {Function}
     */
    NEJ.F = function(){return !1;};
    /**
     * 返回指定的命名空间，如果不存在则新建一个命名空间

     * 注意：命名空间不要使用浏览器保留的关键字
     * [code]
     *   // 以下两者都将建立 window.ui, 然后返回 window.ui.package
     *   var p1 = NEJ.P("ui.package");
     *   var p2 = NEJ.P("window.ui.package");
     * [/code]
     * @api    {NEJ.P}
     * @param  {String} 命名空间的名称，大小写敏感
     * @return {Object} 生成的命名空间对象
     */
    NEJ.P = function(_namespace){
        if (!_namespace||!_namespace.length) return null;
        var _package = window;
        for(var a=_namespace.split('.'),
                l=a.length,i=(a[0]=='window')?1:0;i<l;
                _package=_package[a[i]]=_package[a[i]]||{},i++);
        return  _package;
    };
    /**
     * 读取上下文中指定名字空间的值
     * [code]
     *     var obj = {
     *         a:{
     *             b:{
     *                 c:{
     *                     d:'ddddd'
     *                 }
     *             }
     *         }
     *     };
     *     // print ddddd
     *     console.log(NEJ.Q(obj,'a.b.c.d'));
     *     // print undefined
     *     console.log(NEJ.Q(null,'a.b.c.d'));
     * [/code]
     * @api    {NEJ.Q}
     * @param  {Object}   上下文
     * @param  {String}   名字空间
     * @return {Varaible} 值
     */
    NEJ.Q = function(_context,_namespace){
        _context = _context||NEJ.O;
        var _arr = _namespace.split('.');
        for(var i=0,l=_arr.length;i<l;i++){
            _context = _context[_arr[i]];
            if (!_context) break;
        }
        return _context;
    };
    /**
     * 定义类，通过此api定义的类具有以下特性：

     * [ul]
     *   具有静态扩展接口_$extend
     *   __init作为类的初始化函数，如果继承了其他类，则可以通过__supInit调用父类初始化函数
     *   如果按照NEJ控件体系规范，则__reset/__destroy/__initNode可以使用__supXxx的形式调用父类接口
     * [/ul]
     * [code]
     *   // 定义类A
     *   var A = NEJ.C();
     *   // 初始化
     *   A.prototype.__init = function(){
     *       // do init
     *   };
     *
     *   // 定义类B，并继承自A
     *   // _$extend接口第二个参数控制是否继承父类静态接口，默认继承，传false禁止继承
     *   var B = NEJ.C();
     *   B._$extend(A);
     *   // B._$extend(A,false); // 不继承父类静态接口
     *   // 初始化
     *   B.prototype.__init = function(){
     *       // 调用A的初始化逻辑
     *       this.__supInit();
     *       // TODO B的初始化逻辑
     *   };
     * [/code]
     * @api    {NEJ.C}
     * @return {Function} 返回创建的类
     */
    NEJ.C = (function(){
        var _isNotFunction = function(){
            return NEJ.O.toString.call(
                   arguments[0])!='[object Function]';
        };
        var _getMethodName = function(_value,_map){
            for(var x in _map)
                if (_value==_map[x])
                    return x;
            return null;
        };
        // build super for method
        var _mmap = {__init:0,__reset:1,__destroy:2,__initNode:3,
                     __doBuild:4,__onShow:5,__onHide:6,__onRefresh:7},
            _umap = {__supInit:0,__supReset:1,__supDestroy:2,__supInitNode:3,
                     __supDoBuild:4,__supOnShow:5,__supOnHide:6,__supOnRefresh:7};
        return function(){
            // class constructor
            var _class = function(){
                this.__cp__();
                return this.__init.apply(this,arguments);
            };
            // class inherit
            _class.prototype.__cp__ = NEJ.F;
            _class.prototype.__init = NEJ.F;
            _class._$extend = function(_super,_static){
                if (_isNotFunction(_super)) return;
                // extend static methods
                if (_static==null||!!_static)
                    NEJ.X(this,_super,_isNotFunction);
                // extend instance properties and methods
                this._$super = _super;
                this._$supro = _super.prototype;
                var _parent = function(){};
                _parent.prototype = _super.prototype;
                this.prototype = new _parent();
                var _prototype = this.prototype;
                _prototype.constructor = this;
                var _tmp;
                // for common method
                for(var x in _mmap){
                    _tmp = _getMethodName(_mmap[x],_umap);
                    if (!_tmp||!this._$supro[x]) continue;
                    _prototype[x] = (function(_name){
                        return function(){
                            this[_name].apply(this,arguments);
                        };
                    })(_tmp);
                }
                // for super method
                var _pmap = {};
                for(var x in _umap){
                    _tmp = _getMethodName(_umap[x],_mmap);
                    if (!_tmp||!this._$supro[_tmp]) continue;
                    _pmap[_tmp] = _super;
                    _prototype[x] = (function(_name){
                        return function(){
                            var _result,
                                _parent = this.__ancestor__[_name],
                                _method = _parent.prototype[_name];
                            this.__ancestor__[_name] = _parent._$super||_super;
                            if (!!_method)
                                _result = _method.apply(this,arguments);
                            this.__ancestor__[_name] = _super; // fix broken chain
                            return _result;
                        };
                    })(_tmp);
                }
                _prototype.__cp__ = function(){
                    this.__ancestor__ = NEJ.X({},_pmap);
                };
                _prototype.__super = _prototype.__supInit;
                return _prototype;
            };
            return _class;
        };
    })();
    /**
     * 拷贝对象属性，过滤接口返回值含义
     *
     * 脚本举例
     * [code]
     *   var _obj0 = {a:0,b:1},
     *       _obj1 = {a:"a",b:"b",c:"c"};
     *   // 根据_obj1的属性拷贝对象到_obj0
     *   // 结果是_obj0.a = "a",_obj.b = "b",_obj.c = "c";
     *   var _obj = NEJ.X(_obj0,_obj1);
     * [/code]
     *
     * 脚本举例
     * [code]
     *   var _obj0 = {a:0,b:1},
     *       _obj1 = {a:"a",b:"b",c:"c"};
     *   var _filter = function(_value,_key){
     *       if (_key != "a")
     *           return false;
     *   }
     *   // 根据_obj1的属性拷贝对象到_obj0,过滤器过滤掉所有key值不是a的属性
     *   // 结果是_obj0.a = 0,_obj.b = "b",_obj.c = "c";
     *   var _obj = NEJ.X(_obj0,_obj1,_filter);
     * [/code]
     *
     * @see    {#.EX}
     * @api    {NEJ.X}
     * @param  {Object}   原始对象
     * @param  {Object}   待拷贝对象
     * @param  {Function} 过滤接口
     * [ntb]
     *  输入 | Variable | 值
     *       | String   | 键
     *  输出 | Boolean  | 是否过滤
     * [/ntb]
     * @return {Object} 拷贝后对象
     */
    NEJ.X = function(_object,_config,_filter){
        if (!_object||!_config)
            return _object;
        _filter = _filter||NEJ.F;
        for(var x in _config){
            if (_config.hasOwnProperty(x)&&
               !_filter(_config[x],x))
                _object[x] = _config[x];
        }
        return _object;
    };
    /**
     * 根据原始对象属性，从目标对象拷贝相应值

     *
     * 脚本举例
     * [code]
     *   var _obj0 = {a:0,b:1},
     *       _obj1 = {a:"a",b:"b",c:"c"};
     *   // 根据_obj0的属性,从_obj1拷贝属性到_obj0中,如果属性的value是null不会被拷贝
     *   // 结果是_obj0.a = "a",_obj.b = "b",没有拷贝c属性;
     *   var _obj = NEJ.EX(_obj0,_obj1);
     * [/code]
     *
     * @see    {#.X}
     * @api    {NEJ.EX}
     * @param  {Object} 原始对象
     * @param  {Object} 目标对象
     * @return {Void}
     */
    NEJ.EX = function(_object,_config){
        if (!_object||!_config)
            return _object;
        for(var x in _object){
            if (_object.hasOwnProperty(x)&&
                _config[x]!=null)
                _object[x] = _config[x];
        }
        return _object;
    };
    // extend native object method
    var _extpro = Function.prototype;
    /**
     * AOP增强操作，增强操作接受一个输入参数包含以下信息
     *
     * [ntb]
     *  参数名称       | 参数类型          | 参数描述
     *  ------------------------------------
     *  args    | Array    | 函数调用时实际输入参数，各增强操作中可以改变值后将影响至后续的操作
     *  stopped | Boolean  | 是否结束操作，终止后续操作
     *  value   | Variable | 输出结果
     * [/ntb]
     *
     * @api    {Function.prototype._$aop}
     * @param  {Function} 之前操作，接受一个输入参数，见描述信息
     * @param  {Function} 之后操作，接受一个输入参数，见描述信息
     * @return {Function} 增强后操作函数
     */
    _extpro._$aop = function(_before,_after){
        var f = NEJ.F,
            _after = _after||f,
            _before = _before||f,
            _handler = this;
        return function(){
            var _event = {args:NEJ.R.slice.call(arguments,0)};
            _before(_event);
            if (!_event.stopped){
                _event.value = _handler
                      .apply(this,_event.args);
                _after(_event);
            }
            return _event.value;
        };
    };
    /**
     * 绑定接口及参数，使其的调用对象保持一致

     *
     * 脚本举例
     * [code]
     *   var _v = NEJ.P("nej.v"),
     *       _obj = {a:0};
     *   var _f = function(_a,_b){
     *       // 第一个参数 ：1
     *       console.log(_a);
     *       // 第二个参数 ： 2
     *       consoel.log(_b);
     *       // 当前this.a ： 0
     *       console.log(this.a);
     *   }._$bind(_obj,"1");
     *   _f(2);
     * [/code]
     *
     * @see    {#_$bind2}
     * @api    {Function.prototype._$bind}
     * @param  {Object}   _object 需要保持一致的对象，null表示window对象
     * @param  {Variable} [argument0[,argument1 ...]] 函数调用时需要的参数
     * @return {Function} 返回绑定后的函数
     */
    _extpro._$bind = function() {
        var _args = arguments,
            _object = arguments[0],
            _function = this;
        return function(){
            // not use slice for chrome 10 beta and Array.apply for android
            var _argc = NEJ.R.slice.call(_args,1);
            NEJ.R.push.apply(_argc,arguments);
            return _function.apply(_object||window,_argc);
        };
    };
    /**
     * 绑定接口及参数，使其的调用对象保持一致，
     * 该接口与_$bind接口的差别在于绑定时参数和调用时参数的顺序不一样，
     * _$bind优先传入绑定时参数
     * _$bind2优先传入调用时参数

     *
     * 脚本举例
     * [code]
     *   var _v = NEJ.P("nej.v"),
     *       _obj = {a:0};
     *   var _f = function(_a,_b){
     *       // 第一个参数 ：2
     *       console.log(_a);
     *       // 第二个参数 ： 1
     *       consoel.log(_b);
     *       // 当前this.a ： 0
     *       console.log(this.a);
     *   }._$bind(_obj,"1");
     *   _f(2);
     * [/code]
     *
     * @see    {#_$bind}
     * @api    {Function.prototype._$bind2}
     * @param  {Object}   需要保持一致的对象，null表示window对象
     * @param  {Variable} 函数调用时需要的参数
     * @return {Function} 返回绑定后的事件函数
     */
    _extpro._$bind2 = function() {
        var _args = arguments,
            _object = NEJ.R.shift.call(_args),
            _function = this;
        return function(){
            NEJ.R.push.apply(arguments,_args);
            return _function.apply(_object||window,arguments);
        }
    };
    var _extpro = String.prototype;
    if (!_extpro.trim){
         _extpro.trim = (function(){
            var _reg = /(?:^\s+)|(?:\s+$)/g;
            return function(){
                return this.replace(_reg,'');
            };
         })();
    }
    // mwf adaptation
    if (!window.MWF) window.MWF = NEJ;
    if (!window.mwf) window.mwf = NEJ.P('nej');
    if (!window.console){
        NEJ.P('console').log = NEJ.F;
        NEJ.P('console').error = NEJ.F;
    }
    // void generating these name when publish
    var lt,gt,amp,nbsp,quot,apos,copy,reg;
};
NEJ.define('{lib}base/global.js',f);<|endoftext|>