<commit_before>//
//  ____  _                           _
// / ___|| |_ __ _ _ __   ___  ___   (_)___  (*)
// \___ \| __/ _` | '_ \ / _ \/ __|  | / __|
//  ___) | || (_| | |_) |  __/\__ \_ | \__ \
// |____/ \__\__,_| .__/ \___||___(_)/ |___/
//              |_|              |__/
//
// (*) a (really) tiny Javascript MVC microframework
//
// (c) Hay Kranen < hay@bykr.org >
// Released under the terms of the MIT license
// < http://en.wikipedia.org/wiki/MIT_License >
//
// Stapes.js : http://hay.github.com/stapes

(function() {
    'use strict';

    var VERSION = "0.7.0-pre";

    // Global counter for all events in all modules (including mixed in objects)
    var guid = 1;

    // Makes _.create() faster
    if (!Object.create) {
        var CachedFunction = function(){};
    }

    // So we can use slice.call for arguments later on
    var slice = Array.prototype.slice;

    // Private attributes and helper functions, stored in an object so they
    // are overwritable by plugins
    var _ = {
        // Properties
        attributes : {},

        eventHandlers : {
            "-1" : {} // '-1' is used for the global event handling
        },

        guid : -1,

        // Methods
        addEvent : function(event) {
            // If we don't have any handlers for this type of event, add a new
            // array we can use to push new handlers
            if (!_.eventHandlers[event.guid][event.type]) {
                _.eventHandlers[event.guid][event.type] = [];
            }

            // Push an event object
            _.eventHandlers[event.guid][event.type].push({
                "guid" : event.guid,
                "handler" : event.handler,
                "scope" : event.scope,
                "type" : event.type
            });
        },

        addEventHandler : function(argTypeOrMap, argHandlerOrScope, argScope) {
            var eventMap = {},
                scope;

            if (typeof argTypeOrMap === "string") {
                scope = argScope || false;
                eventMap[ argTypeOrMap ] = argHandlerOrScope;
            } else {
                scope = argHandlerOrScope || false;
                eventMap = argTypeOrMap;
            }

            for (var eventString in eventMap) {
                var handler = eventMap[eventString];
                var events = eventString.split(" ");

                for (var i = 0, l = events.length; i < l; i++) {
                    var eventType = events[i];
                    _.addEvent.call(this, {
                        "guid" : this._guid || this._.guid,
                        "handler" : handler,
                        "scope" : scope,
                        "type" : eventType
                    });
                }
            }
        },

        addGuid : function(object, forceGuid) {
            if (object._guid && !forceGuid) return;

            object._guid = guid++;

            _.attributes[object._guid] = {};
            _.eventHandlers[object._guid] = {};
        },

        // This is a really small utility function to save typing and produce
        // better optimized code
        attr : function(guid) {
            return _.attributes[guid];
        },

        clone : function(obj) {
            return _.extend({}, obj);
        },

        create : function(proto) {
            if (Object.create) {
                return Object.create(proto);
            } else {
                CachedFunction.prototype = obj;
                return new CachedFunction();
            }
        },

        createSubclass : function(props, includeEvents) {
            props = props || {};
            includeEvents = includeEvents || false;

            var superclass = props.superclass.prototype;

            // Objects always have a constructor, so we need to be sure this is
            // a property instead of something from the prototype
            var realConstructor = props.hasOwnProperty('constructor') ? props.constructor : function(){};

            function constructor() {
                if (includeEvents) {
                    _.addGuid( this, true );
                }

                realConstructor.apply(this, arguments);
            }

            if (includeEvents) {
                _.extend(superclass, Events);
            }

            constructor.prototype = _.create(superclass);
            constructor.prototype.constructor = constructor;

            _.extend(constructor, {
                extend : function() {
                    return _.extendThis.apply(this, arguments);
                },

                proto : function() {
                    return _.extendThis.apply(this.prototype, arguments);
                },

                subclass : function(obj) {
                    obj = obj || {};
                    obj.superclass = this;
                    return _.createSubclass(obj);
                },

                super : superclass,
            });

            // Copy all props given in the definition to the prototype
            for (var key in props) {
                if (key !== 'constructor' && key !== 'superclass') {
                    constructor.prototype[key] = props[key];
                }
            }

            return constructor;
        },

        emitEvents : function(type, data, explicitType, explicitGuid) {
            explicitType = explicitType || false;
            explicitGuid = explicitGuid || this._guid;

            var handlers = _.eventHandlers[explicitGuid][type];

            for (var i = 0, l = handlers.length; i < l; i++) {
                // Clone the event to prevent issue #19
                var event = _.extend({}, handlers[i]);
                var scope = (event.scope) ? event.scope : this;

                if (explicitType) {
                    event.type = explicitType;
                }

                event.scope = scope;
                event.handler.call(event.scope, data, event);
            }
        },

        // Extend an object with more objects
        extend : function() {
            var args = slice.call(arguments);
            var object = args.shift();

            for (var i = 0, l = args.length; i < l; i++) {
                var props = args[i];
                for (var key in props) {
                    object[key] = props[key];
                }
            }

            return object;
        },

        // The same as extend, but uses the this value as the scope
        extendThis : function() {
            var args = slice.call(arguments);
            args.unshift(this);
            return _.extend.apply(this, args);
        },

        // from http://stackoverflow.com/a/2117523/152809
        makeUuid : function() {
            return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
                var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
                return v.toString(16);
            });
        },

        removeAttribute : function(keys, silent) {
            silent = silent || false;

            // Split the key, maybe we want to remove more than one item
            var attributes = _.trim(keys).split(" ");

            // Actually delete the item
            for (var i = 0, l = attributes.length; i < l; i++) {
                var key = _.trim(attributes[i]);

                if (key) {
                    delete _.attr(this._guid)[key];

                    // If 'silent' is set, do not throw any events
                    if (!silent) {
                        this.emit('change', key);
                        this.emit('change:' + key);
                        this.emit('remove', key);
                        this.emit('remove:' + key);
                    }
                }
            }
        },

        removeEventHandler : function(type, handler) {
            var handlers = _.eventHandlers[this._guid];

            if (type && handler) {
                // Remove a specific handler
                handlers = handlers[type];
                if (!handlers) return;

                for (var i = 0, l = handlers.length, h; i < l; i++) {
                    h = handlers[i].handler;
                    if (h && h === handler) {
                        handlers.splice(i--, 1);
                        l--;
                    }
                }
            } else if (type) {
                // Remove all handlers for a specific type
                delete handlers[type];
            } else {
                // Remove all handlers for this module
                _.eventHandlers[this._guid] = {};
            }
        },

        setAttribute : function(key, value, silent) {
            silent = silent || false;

            // We need to do this before we actually add the item :)
            var itemExists = this.has(key);
            var oldValue = _.attr(this._guid)[key];

            // Is the value different than the oldValue? If not, ignore this call
            if (value === oldValue) {
                return;
            }

            // Actually add the item to the attributes
            _.attr(this._guid)[key] = value;

            // If 'silent' flag is set, do not throw any events
            if (silent) {
                return;
            }

            // Throw a generic event
            this.emit('change', key);

            // And a namespaced event as well, NOTE that we pass value instead of
            // key here!
            this.emit('change:' + key, value);

            // Throw namespaced and non-namespaced 'mutate' events as well with
            // the old value data as well and some extra metadata such as the key
            var mutateData = {
                "key" : key,
                "newValue" : value,
                "oldValue" : oldValue || null
            };

            this.emit('mutate', mutateData);
            this.emit('mutate:' + key, mutateData);

            // Also throw a specific event for this type of set
            var specificEvent = itemExists ? 'update' : 'create';

            this.emit(specificEvent, key);

            // And a namespaced event as well, NOTE that we pass value instead of key
            this.emit(specificEvent + ':' + key, value);
        },

        trim : function(str) {
            return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
        },

        typeOf : function(val) {
            if (val === null || typeof val === "undefined") {
                return String(val);
            } else {
                return Object.prototype.toString.call(val).replace(/\[object |\]/g, '').toLowerCase();
            }
        },

        updateAttribute : function(key, fn, silent) {
            var item = this.get(key);
            var newValue = fn.call(this, _.clone(item), key);
            _.setAttribute.call(this, key, newValue, silent || false);
        }
    };

    // Can be mixed in later using Stapes.mixinEvents(object);
    var Events = {
        emit : function(types, data) {
            data = (typeof data === "undefined") ? null : data;

            var splittedTypes = types.split(" ");

            for (var i = 0, l = splittedTypes.length; i < l; i++) {
                var type = splittedTypes[i];

                // First 'all' type events: is there an 'all' handler in the
                // global stack?
                if (_.eventHandlers[-1].all) {
                    _.emitEvents.call(this, "all", data, type, -1);
                }

                // Catch all events for this type?
                if (_.eventHandlers[-1][type]) {
                    _.emitEvents.call(this, type, data, type, -1);
                }

                if (typeof this._guid === 'number') {
                    // 'all' event for this specific module?
                    if (_.eventHandlers[this._guid].all) {
                        _.emitEvents.call(this, "all", data, type);
                    }

                    // Finally, normal events :)
                    if (_.eventHandlers[this._guid][type]) {
                        _.emitEvents.call(this, type, data);
                    }
                }
            }
        },

        off : function() {
            _.removeEventHandler.apply(this, arguments);
        },

        on : function() {
            _.addEventHandler.apply(this, arguments);
        }
    };

    _.Module = function(){};
    _.Module.prototype = {
        // create() is deprecated from 0.8.0
        create : function() {
            throw new Error(''.concat(
                'create() on Stapes modules no longer works from 0.8.0. ',
                'Check the docs.'
            ));
        },

        each : function(fn, ctx) {
            var attr = _.attr(this._guid);
            for (var key in attr) {
                var value = attr[key];
                fn.call(ctx || this, value, key);
            }
        },

        extend : function() {
            return _.extendThis.apply(this, arguments);
        },

        filter : function(fn) {
            var filtered = [];
            var attributes = _.attr(this._guid);

            for (var key in attributes) {
                if ( fn.call(this, attributes[key], key)) {
                    filtered.push( attributes[key] );
                }
            }

            return filtered;
        },

        get : function(input) {
            if (typeof input === "string") {
                return this.has(input) ? _.attr(this._guid)[input] : null;
            } else if (typeof input === "function") {
                var items = this.filter(input);
                return (items.length) ? items[0] : null;
            }
        },

        getAll : function() {
            return _.clone( _.attr(this._guid) );
        },

        getAllAsArray : function() {
            var arr = [];
            var attributes = _.attr(this._guid);

            for (var key in attributes) {
                var value = attributes[key];

                if (_.typeOf(value) === "object" && !value.id) {
                    value.id = key;
                }

                arr.push(value);
            }

            return arr;
        },

        has : function(key) {
            return (typeof _.attr(this._guid)[key] !== "undefined");
        },

        map : function(fn, ctx) {
            var mapped = [];
            this.each(function(value, key) {
                mapped.push( fn.call(ctx || this, value, key) );
            }, ctx || this);
            return mapped;
        },

        // Akin to set(), but makes a unique id
        push : function(input, silent) {
            if (_.typeOf(input) === "array") {
                for (var i = 0, l = input.length; i < l; i++) {
                    _.setAttribute.call(this, _.makeUuid(), input[i]);
                }
            } else {
                _.setAttribute.call(this, _.makeUuid(), input, silent || false);
            }

            return this;
        },

        remove : function(input, silent) {
            if (typeof input === "function") {
                this.each(function(item, key) {
                    if (input(item)) {
                        _.removeAttribute.call(this, key, silent);
                    }
                });
            } else {
                // nb: checking for exists happens in removeAttribute
                _.removeAttribute.call(this, input, silent || false);
            }

            return this;
        },

        set : function(objOrKey, value, silent) {
            if (typeof objOrKey === "object") {
                for (var key in objOrKey) {
                    _.setAttribute.call(this, key, objOrKey[key]);
                }
            } else {
                _.setAttribute.call(this, objOrKey, value, silent || false);
            }

            return this;
        },

        size : function() {
            return Object.keys(_.attributes[this._guid]).length;
        },

        update : function(keyOrFn, fn, silent) {
            if (typeof keyOrFn === "string") {
                _.updateAttribute.call(this, keyOrFn, fn, silent || false);
            } else if (typeof keyOrFn === "function") {
                this.each(function(value, key) {
                    _.updateAttribute.call(this, key, keyOrFn);
                });
            }

            return this;
        }
    };

    var Stapes = {
        "_" : _, // private helper functions and properties

        // Compatiblity option, this method is deprecated
        "create" : function() {
            var instance = _.create( _.Module.prototype );
            _.addGuid( instance, true );

            // Mixin events
            Stapes.mixinEvents( instance );

            return instance;
        },

        "extend" : function() {
            return _.extendThis.apply(_.Moduel, arguments);
        },

        "mixinEvents" : function(obj) {
            obj = obj || {};

            _.addGuid(obj);

            return _.extend(obj, Events);
        },

        "on" : function() {
            _.addEventHandler.apply(this, arguments);
        },

        "subclass" : function(obj, classOnly) {
            classOnly = classOnly || false;
            obj = obj || {};
            obj.superclass = classOnly ? function(){} : _.Module;
            return _.createSubclass(obj, !classOnly);
        },

        "version" : VERSION
    };

    // This library can be used as an AMD module, a Node.js module, or an
    // old fashioned global
    if (typeof exports !== "undefined") {
        // Server
        if (typeof module !== "undefined" && module.exports) {
            exports = module.exports = Stapes;
        }
        exports.Stapes = Stapes;
    } else if (typeof define === "function" && define.amd) {
        // AMD
        define(function() {
            return Stapes;
        });
    } else {
        // Global scope
        window.Stapes = Stapes;
    }
})();<commit_msg>be kind to people forgetting new()<commit_after>//
//  ____  _                           _
// / ___|| |_ __ _ _ __   ___  ___   (_)___  (*)
// \___ \| __/ _` | '_ \ / _ \/ __|  | / __|
//  ___) | || (_| | |_) |  __/\__ \_ | \__ \
// |____/ \__\__,_| .__/ \___||___(_)/ |___/
//              |_|              |__/
//
// (*) a (really) tiny Javascript MVC microframework
//
// (c) Hay Kranen < hay@bykr.org >
// Released under the terms of the MIT license
// < http://en.wikipedia.org/wiki/MIT_License >
//
// Stapes.js : http://hay.github.com/stapes

(function() {
    'use strict';

    var VERSION = "0.7.0-pre";

    // Global counter for all events in all modules (including mixed in objects)
    var guid = 1;

    // Makes _.create() faster
    if (!Object.create) {
        var CachedFunction = function(){};
    }

    // So we can use slice.call for arguments later on
    var slice = Array.prototype.slice;

    // Private attributes and helper functions, stored in an object so they
    // are overwritable by plugins
    var _ = {
        // Properties
        attributes : {},

        eventHandlers : {
            "-1" : {} // '-1' is used for the global event handling
        },

        guid : -1,

        // Methods
        addEvent : function(event) {
            // If we don't have any handlers for this type of event, add a new
            // array we can use to push new handlers
            if (!_.eventHandlers[event.guid][event.type]) {
                _.eventHandlers[event.guid][event.type] = [];
            }

            // Push an event object
            _.eventHandlers[event.guid][event.type].push({
                "guid" : event.guid,
                "handler" : event.handler,
                "scope" : event.scope,
                "type" : event.type
            });
        },

        addEventHandler : function(argTypeOrMap, argHandlerOrScope, argScope) {
            var eventMap = {},
                scope;

            if (typeof argTypeOrMap === "string") {
                scope = argScope || false;
                eventMap[ argTypeOrMap ] = argHandlerOrScope;
            } else {
                scope = argHandlerOrScope || false;
                eventMap = argTypeOrMap;
            }

            for (var eventString in eventMap) {
                var handler = eventMap[eventString];
                var events = eventString.split(" ");

                for (var i = 0, l = events.length; i < l; i++) {
                    var eventType = events[i];
                    _.addEvent.call(this, {
                        "guid" : this._guid || this._.guid,
                        "handler" : handler,
                        "scope" : scope,
                        "type" : eventType
                    });
                }
            }
        },

        addGuid : function(object, forceGuid) {
            if (object._guid && !forceGuid) return;

            object._guid = guid++;

            _.attributes[object._guid] = {};
            _.eventHandlers[object._guid] = {};
        },

        // This is a really small utility function to save typing and produce
        // better optimized code
        attr : function(guid) {
            return _.attributes[guid];
        },

        clone : function(obj) {
            return _.extend({}, obj);
        },

        create : function(proto) {
            if (Object.create) {
                return Object.create(proto);
            } else {
                CachedFunction.prototype = obj;
                return new CachedFunction();
            }
        },

        createSubclass : function(props, includeEvents) {
            props = props || {};
            includeEvents = includeEvents || false;

            var superclass = props.superclass.prototype;

            // Objects always have a constructor, so we need to be sure this is
            // a property instead of something from the prototype
            var realConstructor = props.hasOwnProperty('constructor') ? props.constructor : function(){};

            function constructor() {
                // Be kind to people forgetting new
                if (!(this instanceof constructor)) {
                    throw new Error("Please use 'new' when initializing Stapes classes");
                }

                if (includeEvents) {
                    _.addGuid( this, true );
                }

                realConstructor.apply(this, arguments);
            }

            if (includeEvents) {
                _.extend(superclass, Events);
            }

            constructor.prototype = _.create(superclass);
            constructor.prototype.constructor = constructor;

            _.extend(constructor, {
                extend : function() {
                    return _.extendThis.apply(this, arguments);
                },

                proto : function() {
                    return _.extendThis.apply(this.prototype, arguments);
                },

                subclass : function(obj) {
                    obj = obj || {};
                    obj.superclass = this;
                    return _.createSubclass(obj);
                },

                super : superclass,
            });

            // Copy all props given in the definition to the prototype
            for (var key in props) {
                if (key !== 'constructor' && key !== 'superclass') {
                    constructor.prototype[key] = props[key];
                }
            }

            return constructor;
        },

        emitEvents : function(type, data, explicitType, explicitGuid) {
            explicitType = explicitType || false;
            explicitGuid = explicitGuid || this._guid;

            var handlers = _.eventHandlers[explicitGuid][type];

            for (var i = 0, l = handlers.length; i < l; i++) {
                // Clone the event to prevent issue #19
                var event = _.extend({}, handlers[i]);
                var scope = (event.scope) ? event.scope : this;

                if (explicitType) {
                    event.type = explicitType;
                }

                event.scope = scope;
                event.handler.call(event.scope, data, event);
            }
        },

        // Extend an object with more objects
        extend : function() {
            var args = slice.call(arguments);
            var object = args.shift();

            for (var i = 0, l = args.length; i < l; i++) {
                var props = args[i];
                for (var key in props) {
                    object[key] = props[key];
                }
            }

            return object;
        },

        // The same as extend, but uses the this value as the scope
        extendThis : function() {
            var args = slice.call(arguments);
            args.unshift(this);
            return _.extend.apply(this, args);
        },

        // from http://stackoverflow.com/a/2117523/152809
        makeUuid : function() {
            return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
                var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
                return v.toString(16);
            });
        },

        removeAttribute : function(keys, silent) {
            silent = silent || false;

            // Split the key, maybe we want to remove more than one item
            var attributes = _.trim(keys).split(" ");

            // Actually delete the item
            for (var i = 0, l = attributes.length; i < l; i++) {
                var key = _.trim(attributes[i]);

                if (key) {
                    delete _.attr(this._guid)[key];

                    // If 'silent' is set, do not throw any events
                    if (!silent) {
                        this.emit('change', key);
                        this.emit('change:' + key);
                        this.emit('remove', key);
                        this.emit('remove:' + key);
                    }
                }
            }
        },

        removeEventHandler : function(type, handler) {
            var handlers = _.eventHandlers[this._guid];

            if (type && handler) {
                // Remove a specific handler
                handlers = handlers[type];
                if (!handlers) return;

                for (var i = 0, l = handlers.length, h; i < l; i++) {
                    h = handlers[i].handler;
                    if (h && h === handler) {
                        handlers.splice(i--, 1);
                        l--;
                    }
                }
            } else if (type) {
                // Remove all handlers for a specific type
                delete handlers[type];
            } else {
                // Remove all handlers for this module
                _.eventHandlers[this._guid] = {};
            }
        },

        setAttribute : function(key, value, silent) {
            silent = silent || false;

            // We need to do this before we actually add the item :)
            var itemExists = this.has(key);
            var oldValue = _.attr(this._guid)[key];

            // Is the value different than the oldValue? If not, ignore this call
            if (value === oldValue) {
                return;
            }

            // Actually add the item to the attributes
            _.attr(this._guid)[key] = value;

            // If 'silent' flag is set, do not throw any events
            if (silent) {
                return;
            }

            // Throw a generic event
            this.emit('change', key);

            // And a namespaced event as well, NOTE that we pass value instead of
            // key here!
            this.emit('change:' + key, value);

            // Throw namespaced and non-namespaced 'mutate' events as well with
            // the old value data as well and some extra metadata such as the key
            var mutateData = {
                "key" : key,
                "newValue" : value,
                "oldValue" : oldValue || null
            };

            this.emit('mutate', mutateData);
            this.emit('mutate:' + key, mutateData);

            // Also throw a specific event for this type of set
            var specificEvent = itemExists ? 'update' : 'create';

            this.emit(specificEvent, key);

            // And a namespaced event as well, NOTE that we pass value instead of key
            this.emit(specificEvent + ':' + key, value);
        },

        trim : function(str) {
            return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
        },

        typeOf : function(val) {
            if (val === null || typeof val === "undefined") {
                return String(val);
            } else {
                return Object.prototype.toString.call(val).replace(/\[object |\]/g, '').toLowerCase();
            }
        },

        updateAttribute : function(key, fn, silent) {
            var item = this.get(key);
            var newValue = fn.call(this, _.clone(item), key);
            _.setAttribute.call(this, key, newValue, silent || false);
        }
    };

    // Can be mixed in later using Stapes.mixinEvents(object);
    var Events = {
        emit : function(types, data) {
            data = (typeof data === "undefined") ? null : data;

            var splittedTypes = types.split(" ");

            for (var i = 0, l = splittedTypes.length; i < l; i++) {
                var type = splittedTypes[i];

                // First 'all' type events: is there an 'all' handler in the
                // global stack?
                if (_.eventHandlers[-1].all) {
                    _.emitEvents.call(this, "all", data, type, -1);
                }

                // Catch all events for this type?
                if (_.eventHandlers[-1][type]) {
                    _.emitEvents.call(this, type, data, type, -1);
                }

                if (typeof this._guid === 'number') {
                    // 'all' event for this specific module?
                    if (_.eventHandlers[this._guid].all) {
                        _.emitEvents.call(this, "all", data, type);
                    }

                    // Finally, normal events :)
                    if (_.eventHandlers[this._guid][type]) {
                        _.emitEvents.call(this, type, data);
                    }
                }
            }
        },

        off : function() {
            _.removeEventHandler.apply(this, arguments);
        },

        on : function() {
            _.addEventHandler.apply(this, arguments);
        }
    };

    _.Module = function(){};
    _.Module.prototype = {
        // create() is deprecated from 0.8.0
        create : function() {
            throw new Error(''.concat(
                'create() on Stapes modules no longer works from 0.8.0. ',
                'Check the docs.'
            ));
        },

        each : function(fn, ctx) {
            var attr = _.attr(this._guid);
            for (var key in attr) {
                var value = attr[key];
                fn.call(ctx || this, value, key);
            }
        },

        extend : function() {
            return _.extendThis.apply(this, arguments);
        },

        filter : function(fn) {
            var filtered = [];
            var attributes = _.attr(this._guid);

            for (var key in attributes) {
                if ( fn.call(this, attributes[key], key)) {
                    filtered.push( attributes[key] );
                }
            }

            return filtered;
        },

        get : function(input) {
            if (typeof input === "string") {
                return this.has(input) ? _.attr(this._guid)[input] : null;
            } else if (typeof input === "function") {
                var items = this.filter(input);
                return (items.length) ? items[0] : null;
            }
        },

        getAll : function() {
            return _.clone( _.attr(this._guid) );
        },

        getAllAsArray : function() {
            var arr = [];
            var attributes = _.attr(this._guid);

            for (var key in attributes) {
                var value = attributes[key];

                if (_.typeOf(value) === "object" && !value.id) {
                    value.id = key;
                }

                arr.push(value);
            }

            return arr;
        },

        has : function(key) {
            return (typeof _.attr(this._guid)[key] !== "undefined");
        },

        map : function(fn, ctx) {
            var mapped = [];
            this.each(function(value, key) {
                mapped.push( fn.call(ctx || this, value, key) );
            }, ctx || this);
            return mapped;
        },

        // Akin to set(), but makes a unique id
        push : function(input, silent) {
            if (_.typeOf(input) === "array") {
                for (var i = 0, l = input.length; i < l; i++) {
                    _.setAttribute.call(this, _.makeUuid(), input[i]);
                }
            } else {
                _.setAttribute.call(this, _.makeUuid(), input, silent || false);
            }

            return this;
        },

        remove : function(input, silent) {
            if (typeof input === "function") {
                this.each(function(item, key) {
                    if (input(item)) {
                        _.removeAttribute.call(this, key, silent);
                    }
                });
            } else {
                // nb: checking for exists happens in removeAttribute
                _.removeAttribute.call(this, input, silent || false);
            }

            return this;
        },

        set : function(objOrKey, value, silent) {
            if (typeof objOrKey === "object") {
                for (var key in objOrKey) {
                    _.setAttribute.call(this, key, objOrKey[key]);
                }
            } else {
                _.setAttribute.call(this, objOrKey, value, silent || false);
            }

            return this;
        },

        size : function() {
            return Object.keys(_.attributes[this._guid]).length;
        },

        update : function(keyOrFn, fn, silent) {
            if (typeof keyOrFn === "string") {
                _.updateAttribute.call(this, keyOrFn, fn, silent || false);
            } else if (typeof keyOrFn === "function") {
                this.each(function(value, key) {
                    _.updateAttribute.call(this, key, keyOrFn);
                });
            }

            return this;
        }
    };

    var Stapes = {
        "_" : _, // private helper functions and properties

        // Compatiblity option, this method is deprecated
        "create" : function() {
            var instance = _.create( _.Module.prototype );
            _.addGuid( instance, true );

            // Mixin events
            Stapes.mixinEvents( instance );

            return instance;
        },

        "extend" : function() {
            return _.extendThis.apply(_.Moduel, arguments);
        },

        "mixinEvents" : function(obj) {
            obj = obj || {};

            _.addGuid(obj);

            return _.extend(obj, Events);
        },

        "on" : function() {
            _.addEventHandler.apply(this, arguments);
        },

        "subclass" : function(obj, classOnly) {
            classOnly = classOnly || false;
            obj = obj || {};
            obj.superclass = classOnly ? function(){} : _.Module;
            return _.createSubclass(obj, !classOnly);
        },

        "version" : VERSION
    };

    // This library can be used as an AMD module, a Node.js module, or an
    // old fashioned global
    if (typeof exports !== "undefined") {
        // Server
        if (typeof module !== "undefined" && module.exports) {
            exports = module.exports = Stapes;
        }
        exports.Stapes = Stapes;
    } else if (typeof define === "function" && define.amd) {
        // AMD
        define(function() {
            return Stapes;
        });
    } else {
        // Global scope
        window.Stapes = Stapes;
    }
})();<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import { Alert, Button, DropdownButton, MenuItem } from 'react-bootstrap';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import { connect } from 'react-redux';
import MarksTable from '../../components/MarksTable';
import UnitView from '../../components/UnitView';
import { setAnswer, setAnswerBody } from '../../actions/units';
import { setSecondMenu } from '../../actions/menu';
import updateAnswer from '../../gql/updateAnswer.gql';
import createAnswer from '../../gql/createAnswer.gql';
import retrieveAnswerQuery from '../../gql/retrieveAnswer.gql';
import s from './Unit.css';
import Link from '../../components/Link/Link';
import ModalUnitEdit from '../../components/ModalUnitEdit';
import { showModal } from '../../actions/modals';
import IconButton from '../../components/IconButton';

class Unit extends React.Component {
  static propTypes = {
    dispatch: PropTypes.func.isRequired,
    course: PropTypes.shape({
      id: PropTypes.string.isRequired,
      title: PropTypes.string.isRequired,
    }).isRequired,
    role: PropTypes.string.isRequired,
    user: PropTypes.shape({
      id: PropTypes.string,
      email: PropTypes.string,
    }),
    unit: PropTypes.shape({
      id: PropTypes.string,
      title: PropTypes.string,
      body: PropTypes.string,
    }).isRequired,
    answer: PropTypes.shape({
      id: PropTypes.string,
      body: PropTypes.shape,
    }).isRequired,
  };

  static contextTypes = {
    fetch: PropTypes.func.isRequired,
  };

  static defaultProps = {
    user: PropTypes.shape({
      id: PropTypes.string,
      email: PropTypes.string,
    }),
  };

  constructor(props) {
    super(props);
    this.state = {
      answerCur: 0,
      answers: [],
      isSaving: false,
      saveStatus: '',
      saveMassage: '',
    };
  }

  async componentDidMount() {
    const { user } = this.props;
    if (user) {
      await this.retrieveAnswer();
    }
  }

  componentWillUnmount() {
    const { dispatch } = this.props;
    dispatch(setSecondMenu('unit', []));
  }

  handleChange = name => ({ target: { value } }) =>
    this.setState({
      [name]: value,
    });

  handleAnswerSelect = eventKey => {
    const answerCur = parseInt(eventKey, 10);
    this.setState({ answerCur });
    this.props.dispatch(setAnswer(this.state.answers[answerCur]));
  };

  async uploadFile(key, file) {
    const { user } = this.props;
    const formData = new FormData();
    formData.append(
      'query',
      `mutation uploadFile($internalName: String!, $userId: String!) {
        uploadFile(internalName: $internalName, userId: $userId) { id }
      }`,
    );
    formData.append(
      'variables',
      JSON.stringify({
        userId: user.id,
        internalName: file.name,
      }),
    );
    formData.append('file', file);
    const res = await this.context.fetch('/graphql', {
      body: formData,
    });
    const { data, errors } = await res.json();
    if (errors && errors.length) throw new Error(errors);
    return { key, data: { type: 'file', id: data.uploadFile.id } };
  }

  async postProcessAnswer(answer) {
    const res = { ...answer };
    const files = Object.entries(answer).filter(
      ans => ans[1] instanceof window.File,
    );
    const tasks = [];
    for (let i = 0; i < files.length; i += 1) {
      tasks.push(this.uploadFile(files[i][0], files[i][1]));
    }
    const filesData = await Promise.all(tasks);
    filesData.forEach(fd => {
      res[fd.key] = fd.data;
    });
    return res;
  }

  saveAnswer = async () => {
    const { course, unit } = this.props;
    this.setState({ isSaving: true });
    const answer = await this.postProcessAnswer(this.props.answer.body);
    try {
      if (this.props.answer.id) {
        await this.context.fetch('/graphql', {
          body: JSON.stringify({
            query: updateAnswer,
            variables: {
              body: JSON.stringify(answer),
              id: this.props.answer.id,
            },
          }),
        });
      } else {
        await this.context.fetch('/graphql', {
          body: JSON.stringify({
            query: createAnswer,
            variables: {
              body: JSON.stringify(answer),
              courseId: course.id,
              unitId: unit.id,
            },
          }),
        });
      }
      this.setState({
        saveStatus: 'success',
        saveMassage: 'save completed successfully',
      });
    } catch (e) {
      this.setState({ saveStatus: 'danger', saveMassage: 'save error' });
    } finally {
      this.setState({ isSaving: false });
    }
  };

  async retrieveAnswer() {
    const { user, course, unit } = this.props;
    const resp = await this.context.fetch('/graphql', {
      body: JSON.stringify({
        query: retrieveAnswerQuery,
        variables: {
          userIds: (!user.isAdmin && [user.id]) || null,
          unitIds: [unit.id],
          courseIds: [course.id],
        },
      }),
    });
    const { data } = await resp.json();
    const { answers = [] } = data.courses[0].units[0];
    if (answers && answers.length) {
      this.setState({
        answers: answers.map(answer => {
          const ans = answer;
          ans.body = JSON.parse(answer.body);
          return ans;
        }),
      });
    }
    this.props.dispatch(setAnswer(answers[0] || { body: {} }));
  }

  render() {
    const { user = {}, role, unit, course, dispatch } = this.props;
    const { answers = [], answerCur, saveStatus, saveMassage } = this.state;
    return (

        <ModalUnitEdit modalId="modalUnitEdit" />

            <Link to={`/courses/${course.id}`}>{course.title}</Link>
            {`/${unit.title}`}
            {role === 'teacher' && (
              <IconButton
                onClick={() => dispatch(showModal('modalUnitEdit'))}
                glyph="pencil"
              />
            )}
            {(role === 'teacher' || user.isAdmin) &&
              answers[answerCur] && (
                <DropdownButton
                  id="answer_chooser"
                  title={`${answers[answerCur].user.profile.displayName} ${
                    answers[answerCur].createdAt
                  }`}
                  onSelect={this.handleAnswerSelect}
                >
                  {user.isAdmin &&
                    answers.map((ans, i) => (
                      <MenuItem
                        key={ans.id}
                        eventKey={i}
                        active={i === answerCur}
                      >
                        {`
                      ${ans.user.profile.displayName}
                      ${ans.createdAt}`}
                      </MenuItem>
                    ))}
                </DropdownButton>
              )}

          <UnitView
            answerId={this.props.answer.id}
            value={this.props.answer.body}
            body={unit.body}
            onChange={val => this.props.dispatch(setAnswerBody(val))}
            onHeadersChange={headers =>
              dispatch(
                setSecondMenu('unit', headers.filter(item => item.level === 2)),
              )
            }
          />
          {user && (
            <Button onClick={this.saveAnswer} disabled={this.state.isSaving}>
              Save
            </Button>
          )}
          {saveStatus && <Alert variant={saveStatus}>{saveMassage}</Alert>}
          {unit.answers[0] ? (
            <MarksTable />
          ) : (

This unit has no answers yet

          )}

    );
  }
}

const mapStateToProps = state => ({
  unit: state.unit,
  user: state.user,
  answer: state.answer,
});

export default connect(mapStateToProps)(withStyles(s)(Unit));
<commit_msg>split teacher's dropdown to 2 (#100)<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import { Alert, Button, DropdownButton, MenuItem } from 'react-bootstrap';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import { connect } from 'react-redux';
import MarksTable from '../../components/MarksTable';
import UnitView from '../../components/UnitView';
import { setAnswer, setAnswerBody } from '../../actions/units';
import { setSecondMenu } from '../../actions/menu';
import updateAnswer from '../../gql/updateAnswer.gql';
import createAnswer from '../../gql/createAnswer.gql';
import retrieveAnswerQuery from '../../gql/retrieveAnswer.gql';
import s from './Unit.css';
import Link from '../../components/Link/Link';
import ModalUnitEdit from '../../components/ModalUnitEdit';
import { showModal } from '../../actions/modals';
import IconButton from '../../components/IconButton';

class Unit extends React.Component {
  static propTypes = {
    dispatch: PropTypes.func.isRequired,
    course: PropTypes.shape({
      id: PropTypes.string.isRequired,
      title: PropTypes.string.isRequired,
    }).isRequired,
    role: PropTypes.string.isRequired,
    user: PropTypes.shape({
      id: PropTypes.string,
      email: PropTypes.string,
    }),
    unit: PropTypes.shape({
      id: PropTypes.string,
      title: PropTypes.string,
      body: PropTypes.string,
    }).isRequired,
    answer: PropTypes.shape({
      id: PropTypes.string,
      body: PropTypes.shape,
    }).isRequired,
  };

  static contextTypes = {
    fetch: PropTypes.func.isRequired,
  };

  static defaultProps = {
    user: PropTypes.shape({
      id: PropTypes.string,
      email: PropTypes.string,
    }),
  };

  static getAnswersByUser(answers, userId) {
    const data = {};
    answers.forEach(answer => {
      const { id } = answer.user;
      const ua = data[id] || { user: answer.user, answers: [] };
      data[id] = ua;
      ua.answers.push(answer);
    });
    return {
      users: Object.values(data).map(d => d.user),
      answers: (data[userId] || { answers: [] }).answers,
    };
  }

  constructor(props) {
    super(props);
    this.state = {
      answerCur: 0,
      answers: [],
      isSaving: false,
      saveStatus: '',
      saveMassage: '',
    };
  }

  async componentDidMount() {
    const { user } = this.props;
    if (user) {
      await this.retrieveAnswer();
    }
  }

  componentWillUnmount() {
    const { dispatch } = this.props;
    dispatch(setSecondMenu('unit', []));
  }

  handleChange = name => ({ target: { value } }) =>
    this.setState({
      [name]: value,
    });

  handleUserSelect = id => {
    const userCur = id;
    this.setState({ userCur, answerCur: undefined });
  };

  handleAnswerSelect = id => {
    const answerCur = id;
    this.setState({ answerCur });
    this.props.dispatch(
      setAnswer(this.state.answers.find(ans => ans.id === answerCur)),
    );
  };

  async uploadFile(key, file) {
    const { user } = this.props;
    const formData = new FormData();
    formData.append(
      'query',
      `mutation uploadFile($internalName: String!, $userId: String!) {
        uploadFile(internalName: $internalName, userId: $userId) { id }
      }`,
    );
    formData.append(
      'variables',
      JSON.stringify({
        userId: user.id,
        internalName: file.name,
      }),
    );
    formData.append('file', file);
    const res = await this.context.fetch('/graphql', {
      body: formData,
    });
    const { data, errors } = await res.json();
    if (errors && errors.length) throw new Error(errors);
    return { key, data: { type: 'file', id: data.uploadFile.id } };
  }

  async postProcessAnswer(answer) {
    const res = { ...answer };
    const files = Object.entries(answer).filter(
      ans => ans[1] instanceof window.File,
    );
    const tasks = [];
    for (let i = 0; i < files.length; i += 1) {
      tasks.push(this.uploadFile(files[i][0], files[i][1]));
    }
    const filesData = await Promise.all(tasks);
    filesData.forEach(fd => {
      res[fd.key] = fd.data;
    });
    return res;
  }

  saveAnswer = async () => {
    const { course, unit } = this.props;
    this.setState({ isSaving: true });
    const answer = await this.postProcessAnswer(this.props.answer.body);
    try {
      if (this.props.answer.id) {
        await this.context.fetch('/graphql', {
          body: JSON.stringify({
            query: updateAnswer,
            variables: {
              body: JSON.stringify(answer),
              id: this.props.answer.id,
            },
          }),
        });
      } else {
        await this.context.fetch('/graphql', {
          body: JSON.stringify({
            query: createAnswer,
            variables: {
              body: JSON.stringify(answer),
              courseId: course.id,
              unitId: unit.id,
            },
          }),
        });
      }
      this.setState({
        saveStatus: 'success',
        saveMassage: 'save completed successfully',
      });
    } catch (e) {
      this.setState({ saveStatus: 'danger', saveMassage: 'save error' });
    } finally {
      this.setState({ isSaving: false });
    }
  };

  async retrieveAnswer() {
    const { user, course, unit } = this.props;
    const resp = await this.context.fetch('/graphql', {
      body: JSON.stringify({
        query: retrieveAnswerQuery,
        variables: {
          userIds: (!user.isAdmin && [user.id]) || null,
          unitIds: [unit.id],
          courseIds: [course.id],
        },
      }),
    });
    const { data } = await resp.json();
    const { answers = [] } = data.courses[0].units[0];
    if (answers && answers.length) {
      this.setState({
        answers: answers.map(answer => {
          const ans = answer;
          ans.body = JSON.parse(answer.body);
          return ans;
        }),
      });
    }
    this.props.dispatch(setAnswer(answers[0] || { body: {} }));
  }

  render() {
    const { user = {}, role, unit, course, dispatch } = this.props;
    const {
      answers = [],
      userCur,
      answerCur,
      saveStatus,
      saveMassage,
    } = this.state;
    const ua = Unit.getAnswersByUser(answers, userCur);
    const answerUser = ua.users.find(u => u.id === userCur);
    const answer = answers.find(ans => ans.id === answerCur);
    return (

        <ModalUnitEdit modalId="modalUnitEdit" />

            <Link to={`/courses/${course.id}`}>{course.title}</Link>
            {`/${unit.title}`}
            {role === 'teacher' && (
              <IconButton
                onClick={() => dispatch(showModal('modalUnitEdit'))}
                glyph="pencil"
              />
            )}
            {(role === 'teacher' || user.isAdmin) && (
              <React.Fragment>
                <DropdownButton
                  id="user_chooser"
                  title={answerUser && answerUser.profile.displayName}
                  onSelect={this.handleUserSelect}
                >
                  {ua.users.map(u => (
                    <MenuItem
                      key={u.id}
                      eventKey={u.id}
                      active={u.id === userCur}
                    >
                      {u.profile.displayName}
                    </MenuItem>
                  ))}
                </DropdownButton>
                <DropdownButton
                  id="answer_chooser"
                  title={answer && answer.createdAt}
                  onSelect={this.handleAnswerSelect}
                >
                  {ua.answers.map(ans => (
                    <MenuItem
                      key={ans.id}
                      eventKey={ans.id}
                      active={ans.id === answerCur}
                    >
                      {ans.createdAt}
                    </MenuItem>
                  ))}
                </DropdownButton>
              </React.Fragment>
            )}

          <UnitView
            answerId={this.props.answer.id}
            value={this.props.answer.body}
            body={unit.body}
            onChange={val => this.props.dispatch(setAnswerBody(val))}
            onHeadersChange={headers =>
              dispatch(
                setSecondMenu('unit', headers.filter(item => item.level === 2)),
              )
            }
          />
          {user && (
            <Button onClick={this.saveAnswer} disabled={this.state.isSaving}>
              Save
            </Button>
          )}
          {saveStatus && <Alert variant={saveStatus}>{saveMassage}</Alert>}
          {unit.answers[0] ? (
            <MarksTable />
          ) : (

This unit has no answers yet

          )}

    );
  }
}

const mapStateToProps = state => ({
  unit: state.unit,
  user: state.user,
  answer: state.answer,
});

export default connect(mapStateToProps)(withStyles(s)(Unit));
<|endoftext|>


<commit_before>/*
 * Backpack - Skyscanner's Design System
 *
 * Copyright 2016-2020 Skyscanner Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* @flow */

/* eslint-disable no-console */

const fs = require('fs');
const util = require('util');
const https = require('https');

const colors = require('colors');
const cliProgress = require('cli-progress');

// TODO remove this warning once NPM is more reliable or we move to
// scoped packages and don't need the check at all.
console.warn(
  colors.yellow(
    `
Due to issues with NPM, this check often gets stuck between 90-99%.

If this happens, please quit and restart.

You may have to try a few times before it works. We're working on a more permanent solution.`,
  ),
);

const bar = new cliProgress.Bar({}, cliProgress.Presets.shades_classic);

let packagesDataFetched = 0;

const readdir = util.promisify(fs.readdir);

const meta = require('../../meta.json');

let failures = false;

const owners = meta.maintainers.map(maintainer => maintainer.npm).sort();

const packageDone = () => {
  packagesDataFetched += 1;
  bar.update(packagesDataFetched);
};

const getPackageMaintainers = pkg =>
  new Promise((resolve, reject) => {
    https.get(`https://registry.npmjs.org/${pkg}/`, res => {
      let body = '';
      res.setEncoding('utf8');
      res.on('data', d => {
        body += d;
      });
      res.on('error', reject);
      res.on('end', () => {
        const pkgData = JSON.parse(body);

        if (pkgData.maintainers) {
          packageDone();
          resolve({
            name: pkg,
            maintainers: pkgData.maintainers.map(m => m.name),
            new: false,
          });
        } else {
          packageDone();
          resolve({
            name: pkg,
            new: true,
          });
        }
      });
    });
  });

const verifyMaintainers = data => {
  if (data.new) {
    console.log(
      `${data.name} ⁇\n  Package does not seem to be in NPM registry (yet)`,
    );
    return;
  }

  const sortedMaintainers = data.maintainers
    .filter(m => m !== 'mattface')
    .sort();

  if (sortedMaintainers.join('') === owners.join('')) {
    console.log(`${data.name} ✔`);
  } else {
    console.log(
      `${data.name}\n  Expected\n    ${owners.join(
        ', ',
      )}\n  but got\n    ${sortedMaintainers.join(', ')}`,
    );
    process.exitCode = 1;
    failures = true;
  }
};

console.log(`Maintainers are:\n  ${owners.join('\n  ')}\n`);

readdir('packages/')
  .then(packages => packages.filter(i => !i.startsWith('.')))
  .then(packages => {
    bar.start(packages.length, 0);
    return packages;
  })
  .then(packages => Promise.all(packages.map(getPackageMaintainers)))
  .then(packages => {
    bar.stop();
    console.log('');
    return packages;
  })
  .then(maintainers => maintainers.forEach(verifyMaintainers))
  .then(() => {
    if (failures) {
      console.log(
        '\nPlease fix your maintainer list before publishing. Link: https://www.npmjs.com/settings/skyscanner/teams/team/backpack/access',
      );
      process.exit(1);
    } else {
      console.log('\nAll good 👍');
      process.exit(0);
    }
  })
  .catch(error => {
    console.error(
      'An unknown error occured. Please check your network connection and try again.',
    );
    if (error) {
      console.error(error);
    }
    process.exit(1);
  });
<commit_msg>[No Jira] Opt Tiago out of the check owners script<commit_after>/*
 * Backpack - Skyscanner's Design System
 *
 * Copyright 2016-2020 Skyscanner Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* @flow */

/* eslint-disable no-console */

const fs = require('fs');
const util = require('util');
const https = require('https');

const colors = require('colors');
const cliProgress = require('cli-progress');

// People who've left but we can't remove them because npm won't let us
// because they were the original publishers of some packages.
const DEARLY_DEPARTED = ['mattface', 'tiagohngl'];

// TODO remove this warning once NPM is more reliable or we move to
// scoped packages and don't need the check at all.
console.warn(
  colors.yellow(
    `
Due to issues with NPM, this check often gets stuck between 90-99%.

If this happens, please quit and restart.

You may have to try a few times before it works. We're working on a more permanent solution.`,
  ),
);

const bar = new cliProgress.Bar({}, cliProgress.Presets.shades_classic);

let packagesDataFetched = 0;

const readdir = util.promisify(fs.readdir);

const meta = require('../../meta.json');

let failures = false;

const owners = meta.maintainers.map(maintainer => maintainer.npm).sort();

const packageDone = () => {
  packagesDataFetched += 1;
  bar.update(packagesDataFetched);
};

const getPackageMaintainers = pkg =>
  new Promise((resolve, reject) => {
    https.get(`https://registry.npmjs.org/${pkg}/`, res => {
      let body = '';
      res.setEncoding('utf8');
      res.on('data', d => {
        body += d;
      });
      res.on('error', reject);
      res.on('end', () => {
        const pkgData = JSON.parse(body);

        if (pkgData.maintainers) {
          packageDone();
          resolve({
            name: pkg,
            maintainers: pkgData.maintainers.map(m => m.name),
            new: false,
          });
        } else {
          packageDone();
          resolve({
            name: pkg,
            new: true,
          });
        }
      });
    });
  });

const verifyMaintainers = data => {
  if (data.new) {
    console.log(
      `${data.name} ⁇\n  Package does not seem to be in NPM registry (yet)`,
    );
    return;
  }

  const sortedMaintainers = data.maintainers
    .filter(m => !DEARLY_DEPARTED.includes(m))
    .sort();

  if (sortedMaintainers.join('') === owners.join('')) {
    console.log(`${data.name} ✔`);
  } else {
    console.log(
      `${data.name}\n  Expected\n    ${owners.join(
        ', ',
      )}\n  but got\n    ${sortedMaintainers.join(', ')}`,
    );
    process.exitCode = 1;
    failures = true;
  }
};

console.log(`Maintainers are:\n  ${owners.join('\n  ')}\n`);

readdir('packages/')
  .then(packages => packages.filter(i => !i.startsWith('.')))
  .then(packages => {
    bar.start(packages.length, 0);
    return packages;
  })
  .then(packages => Promise.all(packages.map(getPackageMaintainers)))
  .then(packages => {
    bar.stop();
    console.log('');
    return packages;
  })
  .then(maintainers => maintainers.forEach(verifyMaintainers))
  .then(() => {
    if (failures) {
      console.log(
        '\nPlease fix your maintainer list before publishing. Link: https://www.npmjs.com/settings/skyscanner/teams/team/backpack/access',
      );
      process.exit(1);
    } else {
      console.log('\nAll good 👍');
      process.exit(0);
    }
  })
  .catch(error => {
    console.error(
      'An unknown error occured. Please check your network connection and try again.',
    );
    if (error) {
      console.error(error);
    }
    process.exit(1);
  });
<|endoftext|>


<commit_before>/*!

 * ${copyright}
 */

// Provides an abstraction for list bindings
sap.ui.define(['./Binding', './Filter', './Sorter'],
	function(Binding, Filter, Sorter) {
	"use strict";

	/**
	 * Constructor for ListBinding.
	 *
	 * @class
	 * ListBinding is a specific binding for lists in the model, which can be used
	 * to populate Tables or ItemLists.
	 *
	 * @param {sap.ui.model.Model} oModel Model instance that this binding belongs to
	 * @param {string} sPath Binding path for this binding;
	 *   a relative path will be resolved relative to a given context
	 * @param {sap.ui.model.Context} oContext Context to be used to resolve a relative path
	 * @param {sap.ui.model.Sorter|sap.ui.model.Sorter[]} [aSorters] Initial sort order (can be either a sorter or an array of sorters)
	 * @param {sap.ui.model.Filter|sap.ui.model.Filter[]} [aFilters] Predefined filter/s (can be either a filter or an array of filters)
	 * @param {object} [mParameters] Additional, implementation-specific parameters that should be used
	 *   by the new list binding; this base class doesn't define any parameters, check the API reference
	 *   for the concrete model implementations to learn about their supported parameters (if any)
	 *
	 * @public
	 * @alias sap.ui.model.ListBinding
	 * @extends sap.ui.model.Binding
	 */
	var ListBinding = Binding.extend("sap.ui.model.ListBinding", /** @lends sap.ui.model.ListBinding.prototype */ {

		constructor : function(oModel, sPath, oContext, aSorters, aFilters, mParameters){
			Binding.call(this, oModel, sPath, oContext, mParameters);

			this.aSorters = makeArray(aSorters, Sorter);
			this.aFilters = [];
			this.aApplicationFilters = makeArray(aFilters, Filter);
			this.bUseExtendedChangeDetection = false;
			this.bDetectUpdates = true;
		},

		metadata : {
			"abstract" : true,

			publicMethods : [
				// methods
				"getContexts", "getCurrentContexts", "sort", "attachSort", "detachSort", "filter", "attachFilter", "detachFilter", "getDistinctValues", "isGrouped", "getLength", "isLengthFinal"
			]
		}

	});

	function makeArray(a, FNClass) {
		if ( Array.isArray(a) ) {
			return a;
		}
		return a instanceof FNClass ? [a] : [];
	}

	// the 'abstract methods' to be implemented by child classes
	/**
	 * Returns an array of binding contexts for the bound target list.
	 *
	 * Note:The public usage of this method is deprecated, as calls from outside of controls will lead
	 * to unexpected side effects. For avoidance use {@link sap.ui.model.ListBinding.prototype.getCurrentContexts}
	 * instead.
	 *
	 * @function
	 * @name sap.ui.model.ListBinding.prototype.getContexts
	 * @param {int} [iStartIndex=0] the startIndex where to start the retrieval of contexts
	 * @param {int} [iLength=length of the list] determines how many contexts to retrieve beginning from the start index.
	 * @return {sap.ui.model.Context[]} the array of contexts for each row of the bound list
	 *
	 * @protected
	 */

	/**
	 * Applies a new set of filters to the list represented by this binding.
	 *
	 * Depending on the nature of the model (client or server), the operation might be
	 * executed locally or on a server and it might execute asynchronously.
	 *
	 *
Application and Control Filters

	 * Each list binding maintains two separate lists of filters, one for filters defined by the
	 * control that owns the binding and another list for filters that an application can define
	 * in addition. When executing the filter operation, both sets of filters are combined.
	 *
	 * By using the second parameter sFilterType of method filter,
	 * the caller can control which set of filters is modified. If no type is given, then the
	 * behavior depends on the model implementation and should be documented in the API reference
	 * for that model.
	 *
	 *
Auto-Grouping of Filters

	 * Filters are first grouped according to their binding path.
	 * All filters belonging to the same group are ORed and after that the
	 * results of all groups are ANDed.
	 * Usually this means, all filters applied to a single table column
	 * are ORed, while filters on different table columns are ANDed.
	 *
	 * @param {sap.ui.model.Filter[]} aFilters Array of filter objects
	 * @param {sap.ui.model.FilterType} [sFilterType=undefined] Type of the filter which should
	 *  be adjusted; if no type is given, the behavior depends on the model implementation
	 * @return {sap.ui.model.ListBinding} returns this to facilitate method chaining
	 *
	 * @function
	 * @name sap.ui.model.ListBinding.prototype.filter
	 * @public
	 */

	/**
	 * Sorts the list according to the sorter object
	 *
	 * @function
	 * @name sap.ui.model.ListBinding.prototype.sort
	 * @param {sap.ui.model.Sorter|Array} aSorters the Sorter object or an array of sorters which defines the sort order
	 * @return {sap.ui.model.ListBinding} returns this to facilitate method chaining
	 * @public
	 */

	/**
	 * Returns an array of currently used binding contexts of the bound control
	 *
	 * This method does not trigger any data requests from the backend or delta calculation, but just returns the context
	 * array as last requested by the control. This can be used by the application to get access to the data currently
	 * displayed by a list control.
	 *
	 * @return {sap.ui.model.Context[]} the array of contexts for each row of the bound list
	 * @since 1.28
	 * @public
	 */
	ListBinding.prototype.getCurrentContexts = function() {
		return this.getContexts();
	};

	/**
	 * Returns the number of entries in the list. This might be an estimated or preliminary length, in case
	 * the full length is not known yet, see method isLengthFinal().
	 *
	 * @return {int} returns the number of entries in the list
	 * @since 1.24
	 * @public
	 */
	ListBinding.prototype.getLength = function() {
		return 0;
	};

	/**
	 * Returns whether the length which can be retrieved using getLength() is a known, final length,
	 * or a preliminary or estimated length which may change if further data is requested.
	 *
	 * @return {boolean} returns whether the length is final
	 * @since 1.24
	 * @public
	 */
	ListBinding.prototype.isLengthFinal = function() {
		return true;
	};

	// base methods, may be overridden by child classes
	/**
	 * Returns list of distinct values for the given relative binding path
	 *
	 * @param {string} sPath the relative binding path
	 * @return {Array} the array of distinct values.
	 *
	 * @public
	 */
	ListBinding.prototype.getDistinctValues = function(sPath) {
		return null;
	};

	//Eventing and related
	/**
	 * Attach event-handler fnFunction to the 'sort' event of this sap.ui.model.ListBinding.

	 * @param {function} fnFunction The function to call, when the event occurs.
	 * @param {object} [oListener] object on which to call the given function.
	 * @protected
	 * @deprecated use the change event. It now contains a parameter (reason : "sort") when a sorter event is fired.
	 */
	ListBinding.prototype.attachSort = function(fnFunction, oListener) {
		this.attachEvent("sort", fnFunction, oListener);
	};

	/**
	 * Detach event-handler fnFunction from the 'sort' event of this sap.ui.model.ListBinding.

	 * @param {function} fnFunction The function to call, when the event occurs.
	 * @param {object} [oListener] object on which to call the given function.
	 * @protected
	 * @deprecated use the change event.
	 */
	ListBinding.prototype.detachSort = function(fnFunction, oListener) {
		this.detachEvent("sort", fnFunction, oListener);
	};

	/**
	 * Fire event _sort to attached listeners.
	 * @param {Map} [mArguments] the arguments to pass along with the event.
	 * @private
	 * @deprecated use the change event. It now contains a parameter (reason : "sort") when a sorter event is fired.
	 */
	ListBinding.prototype._fireSort = function(mArguments) {
		this.fireEvent("sort", mArguments);
	};

	/**
	 * Attach event-handler fnFunction to the 'filter' event of this sap.ui.model.ListBinding.

	 * @param {function} fnFunction The function to call, when the event occurs.
	 * @param {object} [oListener] object on which to call the given function.
	 * @protected
	 * @deprecated use the change event. It now contains a parameter (reason : "filter") when a filter event is fired.
	 */
	ListBinding.prototype.attachFilter = function(fnFunction, oListener) {
		this.attachEvent("filter", fnFunction, oListener);
	};

	/**
	 * Detach event-handler fnFunction from the 'filter' event of this sap.ui.model.ListBinding.

	 * @param {function} fnFunction The function to call, when the event occurs.
	 * @param {object} [oListener] object on which to call the given function.
	 * @protected
	 * @deprecated use the change event.
	 */
	ListBinding.prototype.detachFilter = function(fnFunction, oListener) {
		this.detachEvent("filter", fnFunction, oListener);
	};

	/**
	 * Fire event _filter to attached listeners.
	 * @param {Map} [mArguments] the arguments to pass along with the event.
	 * @private
	 * @deprecated use the change event. It now contains a parameter (reason : "filter") when a filter event is fired.
	 */
	ListBinding.prototype._fireFilter = function(mArguments) {
		this.fireEvent("filter", mArguments);
	};

	/**
	 * Indicates whether grouping is enabled for the binding.
	 * Grouping is enabled for a list binding, if at least one sorter exists on the binding and the first sorter
	 * is a grouping sorter.
	 * @public
	 * @returns {boolean} whether grouping is enabled
	 */
	ListBinding.prototype.isGrouped = function() {
		return !!(this.aSorters && this.aSorters[0] && this.aSorters[0].fnGroup);
	};

	/**
	 * Gets the group for the given context.
	 * Must only be called if isGrouped() returns that grouping is enabled for this binding. The grouping will be
	 * performed using the first sorter (in case multiple sorters are defined).
	 * @param {sap.ui.model.Context} oContext the binding context
	 * @public
	 * @returns {object} the group object containing a key property and optional custom properties
	 * @see sap.ui.model.Sorter#getGroup
	 */
	ListBinding.prototype.getGroup = function(oContext) {
		return this.aSorters[0].getGroup(oContext);
	};

	/**
	 * Enable extended change detection
	 *
	 * @param {boolean} bDetectUpdates Whether changes within the same entity should cause a delete and insert command
	 * @param {function|string} vKey The path of the property containing the key or a function getting the context as only parameter to calculate a key to identify an entry
	 * @private
	 */
	ListBinding.prototype.enableExtendedChangeDetection = function(bDetectUpdates, vKey) {
		this.bUseExtendedChangeDetection = true;
		this.bDetectUpdates = bDetectUpdates;
		if (typeof vKey === "string") {
			this.getEntryKey = function(oContext) {
				return oContext.getProperty(vKey);
			};
		} else if (typeof vKey === "function") {
			this.getEntryKey = vKey;
		}
		if (this.update) {
			this.update();
		}
	};

	/**
	 * Return the data used for the extended change detection. Dependent on the configuration this can either be a
	 * serialization of the complete data, or just a unique key identifying the entry. If grouping is enabled, the
	 * grouping key will also be included, to detect grouping changes.
	 *
	 * @param {sap.ui.model.Context} oContext the context object
	 * @returns {string} A string which is used for diff comparison
	 */
	ListBinding.prototype.getContextData = function(oContext) {
		var sContextData;
		if (this.getEntryKey && !this.bDetectUpdates) {
			sContextData = this.getEntryKey(oContext);
			if (this.isGrouped()) {
				sContextData += "-" + this.getGroup(oContext).key;
			}
		} else {
			sContextData = this.getEntryData(oContext);
		}
		return sContextData;
	};

	/**
	 * Return the entry data serialized as a string. The default implementation assumes a JS object and uses
	 * JSON.stringify to serialize it, subclasses may override as needed.
	 *
	 * @param {sap.ui.model.Context} oContext the context object
	 * @returns {string} The serialized object data
	 */
	ListBinding.prototype.getEntryData = function(oContext) {
		return JSON.stringify(oContext.getObject());
	};

	return ListBinding;

});
<commit_msg>[FEATURE] ListBinding: Make extended change detection available<commit_after>/*!

 * ${copyright}
 */

// Provides an abstraction for list bindings
sap.ui.define(['./Binding', './Filter', './Sorter'],
	function(Binding, Filter, Sorter) {
	"use strict";

	/**
	 * Constructor for ListBinding.
	 *
	 * @class
	 * ListBinding is a specific binding for lists in the model, which can be used
	 * to populate Tables or ItemLists.
	 *
	 * @param {sap.ui.model.Model} oModel Model instance that this binding belongs to
	 * @param {string} sPath Binding path for this binding;
	 *   a relative path will be resolved relative to a given context
	 * @param {sap.ui.model.Context} oContext Context to be used to resolve a relative path
	 * @param {sap.ui.model.Sorter|sap.ui.model.Sorter[]} [aSorters] Initial sort order (can be either a sorter or an array of sorters)
	 * @param {sap.ui.model.Filter|sap.ui.model.Filter[]} [aFilters] Predefined filter/s (can be either a filter or an array of filters)
	 * @param {object} [mParameters] Additional, implementation-specific parameters that should be used
	 *   by the new list binding; this base class doesn't define any parameters, check the API reference
	 *   for the concrete model implementations to learn about their supported parameters (if any)
	 *
	 * @public
	 * @alias sap.ui.model.ListBinding
	 * @extends sap.ui.model.Binding
	 */
	var ListBinding = Binding.extend("sap.ui.model.ListBinding", /** @lends sap.ui.model.ListBinding.prototype */ {

		constructor : function(oModel, sPath, oContext, aSorters, aFilters, mParameters){
			Binding.call(this, oModel, sPath, oContext, mParameters);

			this.aSorters = makeArray(aSorters, Sorter);
			this.aFilters = [];
			this.aApplicationFilters = makeArray(aFilters, Filter);
			this.bUseExtendedChangeDetection = false;
			this.bDetectUpdates = true;
		},

		metadata : {
			"abstract" : true,

			publicMethods : [
				// methods
				"getContexts", "getCurrentContexts", "sort", "attachSort", "detachSort", "filter", "attachFilter", "detachFilter", "getDistinctValues", "isGrouped", "getLength", "isLengthFinal"
			]
		}

	});

	function makeArray(a, FNClass) {
		if ( Array.isArray(a) ) {
			return a;
		}
		return a instanceof FNClass ? [a] : [];
	}

	// the 'abstract methods' to be implemented by child classes
	/**
	 * Returns an array of binding contexts for the bound target list.
	 *
	 *
Extended Change Detection

	 * If extended change detection is enabled using {@link sap.ui.model.ListBinding.prototype.enableExtendedChangeDetection},
	 * the context array may carry an additional property named diff, which contains an array of actual changes
	 * on the context array compared to the last call of getContexts().
	 * In case no diff property is available on the context array, the list is completely different and needs to
	 * be recreated. In case the diff property contains an empty array, there have been no changes on the list.
	 *
	 * Sample diff array:
	 * [{index: 1, type: "delete"}, {index: 4, type: "insert}]
	 *
	 * Note:The public usage of this method is deprecated, as calls from outside of controls will lead
	 * to unexpected side effects. To avoid these side effect, use {@link sap.ui.model.ListBinding.prototype.getCurrentContexts}
	 * instead.
	 *
	 * @function
	 * @name sap.ui.model.ListBinding.prototype.getContexts
	 * @param {int} [iStartIndex=0] the startIndex where to start the retrieval of contexts
	 * @param {int} [iLength=length of the list] determines how many contexts to retrieve beginning from the start index.
	 * @return {sap.ui.model.Context[]} the array of contexts for each row of the bound list
	 *
	 * @protected
	 */

	/**
	 * Applies a new set of filters to the list represented by this binding.
	 *
	 * Depending on the nature of the model (client or server), the operation might be
	 * executed locally or on a server and it might execute asynchronously.
	 *
	 *
Application and Control Filters

	 * Each list binding maintains two separate lists of filters, one for filters defined by the
	 * control that owns the binding and another list for filters that an application can define
	 * in addition. When executing the filter operation, both sets of filters are combined.
	 *
	 * By using the second parameter sFilterType of method filter,
	 * the caller can control which set of filters is modified. If no type is given, then the
	 * behavior depends on the model implementation and should be documented in the API reference
	 * for that model.
	 *
	 *
Auto-Grouping of Filters

	 * Filters are first grouped according to their binding path.
	 * All filters belonging to the same group are ORed and after that the
	 * results of all groups are ANDed.
	 * Usually this means, all filters applied to a single table column
	 * are ORed, while filters on different table columns are ANDed.
	 * Please either use the automatic grouping of filters (where applicable) or use explicit
	 * AND/OR filters, a mixture of both is not supported.
	 *
	 * @param {sap.ui.model.Filter[]} aFilters Array of filter objects
	 * @param {sap.ui.model.FilterType} [sFilterType=undefined] Type of the filter which should
	 *  be adjusted; if no type is given, the behavior depends on the model implementation
	 * @return {sap.ui.model.ListBinding} returns this to facilitate method chaining
	 *
	 * @function
	 * @name sap.ui.model.ListBinding.prototype.filter
	 * @public
	 */

	/**
	 * Sorts the list according to the sorter object.
	 *
	 * Instead of a single sorter also an array of sorters can be passed to the sort method. In this case they
	 * are processed in the sequence in which they are contained in the array.
	 *
	 *
Grouping

	 * Sorting and grouping are closely related, in case a list should be grouped, it must be sorted by the
	 * property to group with. Grouping is enabled by setting the group property on the sorter object. If it is
	 * enabled, you can get the current group of an item using {@link sap.ui.model.ListBinding.prototype.getGroup}.
	 * In case multiple sorters are provided, grouping can only be done on the first sorter, nested grouping is
	 * not supported.
	 *
	 * @function
	 * @name sap.ui.model.ListBinding.prototype.sort
	 * @param {sap.ui.model.Sorter|Array} aSorters the Sorter object or an array of sorters which defines the sort order
	 * @return {sap.ui.model.ListBinding} returns this to facilitate method chaining
	 * @public
	 */

	/**
	 * Returns an array of currently used binding contexts of the bound control
	 *
	 * This method does not trigger any data requests from the backend or delta calculation, but just returns the context
	 * array as last requested by the control. This can be used by the application to get access to the data currently
	 * displayed by a list control.
	 *
	 * @return {sap.ui.model.Context[]} the array of contexts for each row of the bound list
	 * @since 1.28
	 * @public
	 */
	ListBinding.prototype.getCurrentContexts = function() {
		return this.getContexts();
	};

	/**
	 * Returns the number of entries in the list. This might be an estimated or preliminary length, in case
	 * the full length is not known yet, see method isLengthFinal().
	 *
	 * @return {int} returns the number of entries in the list
	 * @since 1.24
	 * @public
	 */
	ListBinding.prototype.getLength = function() {
		return 0;
	};

	/**
	 * Returns whether the length which can be retrieved using getLength() is a known, final length,
	 * or a preliminary or estimated length which may change if further data is requested.
	 *
	 * @return {boolean} returns whether the length is final
	 * @since 1.24
	 * @public
	 */
	ListBinding.prototype.isLengthFinal = function() {
		return true;
	};

	// base methods, may be overridden by child classes
	/**
	 * Returns list of distinct values for the given relative binding path
	 *
	 * @param {string} sPath the relative binding path
	 * @return {Array} the array of distinct values.
	 *
	 * @public
	 */
	ListBinding.prototype.getDistinctValues = function(sPath) {
		return null;
	};

	//Eventing and related
	/**
	 * Attach event-handler fnFunction to the 'sort' event of this sap.ui.model.ListBinding.

	 * @param {function} fnFunction The function to call, when the event occurs.
	 * @param {object} [oListener] object on which to call the given function.
	 * @protected
	 * @deprecated use the change event. It now contains a parameter (reason : "sort") when a sorter event is fired.
	 */
	ListBinding.prototype.attachSort = function(fnFunction, oListener) {
		this.attachEvent("sort", fnFunction, oListener);
	};

	/**
	 * Detach event-handler fnFunction from the 'sort' event of this sap.ui.model.ListBinding.

	 * @param {function} fnFunction The function to call, when the event occurs.
	 * @param {object} [oListener] object on which to call the given function.
	 * @protected
	 * @deprecated use the change event.
	 */
	ListBinding.prototype.detachSort = function(fnFunction, oListener) {
		this.detachEvent("sort", fnFunction, oListener);
	};

	/**
	 * Fire event _sort to attached listeners.
	 * @param {Map} [mArguments] the arguments to pass along with the event.
	 * @private
	 * @deprecated use the change event. It now contains a parameter (reason : "sort") when a sorter event is fired.
	 */
	ListBinding.prototype._fireSort = function(mArguments) {
		this.fireEvent("sort", mArguments);
	};

	/**
	 * Attach event-handler fnFunction to the 'filter' event of this sap.ui.model.ListBinding.

	 * @param {function} fnFunction The function to call, when the event occurs.
	 * @param {object} [oListener] object on which to call the given function.
	 * @protected
	 * @deprecated use the change event. It now contains a parameter (reason : "filter") when a filter event is fired.
	 */
	ListBinding.prototype.attachFilter = function(fnFunction, oListener) {
		this.attachEvent("filter", fnFunction, oListener);
	};

	/**
	 * Detach event-handler fnFunction from the 'filter' event of this sap.ui.model.ListBinding.

	 * @param {function} fnFunction The function to call, when the event occurs.
	 * @param {object} [oListener] object on which to call the given function.
	 * @protected
	 * @deprecated use the change event.
	 */
	ListBinding.prototype.detachFilter = function(fnFunction, oListener) {
		this.detachEvent("filter", fnFunction, oListener);
	};

	/**
	 * Fire event _filter to attached listeners.
	 * @param {Map} [mArguments] the arguments to pass along with the event.
	 * @private
	 * @deprecated use the change event. It now contains a parameter (reason : "filter") when a filter event is fired.
	 */
	ListBinding.prototype._fireFilter = function(mArguments) {
		this.fireEvent("filter", mArguments);
	};

	/**
	 * Indicates whether grouping is enabled for the binding.
	 * Grouping is enabled for a list binding, if at least one sorter exists on the binding and the first sorter
	 * is a grouping sorter.
	 * @public
	 * @returns {boolean} whether grouping is enabled
	 */
	ListBinding.prototype.isGrouped = function() {
		return !!(this.aSorters && this.aSorters[0] && this.aSorters[0].fnGroup);
	};

	/**
	 * Gets the group for the given context.
	 * Must only be called if isGrouped() returns that grouping is enabled for this binding. The grouping will be
	 * performed using the first sorter (in case multiple sorters are defined).
	 * @param {sap.ui.model.Context} oContext the binding context
	 * @public
	 * @returns {object} the group object containing a key property and optional custom properties
	 * @see sap.ui.model.Sorter#getGroup
	 */
	ListBinding.prototype.getGroup = function(oContext) {
		return this.aSorters[0].getGroup(oContext);
	};

	/**
	 * Enable extended change detection.
	 * When extended change detection is enabled, the list binding provides detailed information about changes, for example
	 * which entries have been removed or inserted. This can be utilized by a control for fine-grained update of its elements.
	 * Please see {@link sap.ui.model.ListBinding.prototype.getContexts} for more information.
	 *
	 * For models that do not have a unique key on each entry by default, a key property or function can be set which is used to
	 * identify entries.
	 *
	 * @param {boolean} bDetectUpdates Whether changes within the same entity should cause a delete and insert command
	 * @param {function|string} vKey The path of the property containing the key or a function getting the context as only parameter to calculate a key to identify an entry
	 * @protected
	 */
	ListBinding.prototype.enableExtendedChangeDetection = function(bDetectUpdates, vKey) {
		this.bUseExtendedChangeDetection = true;
		this.bDetectUpdates = bDetectUpdates;
		if (typeof vKey === "string") {
			this.getEntryKey = function(oContext) {
				return oContext.getProperty(vKey);
			};
		} else if (typeof vKey === "function") {
			this.getEntryKey = vKey;
		}
		if (this.update) {
			this.update();
		}
	};

	/**
	 * Return the data used for the extended change detection. Dependent on the configuration this can either be a
	 * serialization of the complete data, or just a unique key identifying the entry. If grouping is enabled, the
	 * grouping key will also be included, to detect grouping changes.
	 *
	 * @param {sap.ui.model.Context} oContext the context object
	 * @returns {string} A string which is used for diff comparison
	 */
	ListBinding.prototype.getContextData = function(oContext) {
		var sContextData;
		if (this.getEntryKey && !this.bDetectUpdates) {
			sContextData = this.getEntryKey(oContext);
			if (this.isGrouped()) {
				sContextData += "-" + this.getGroup(oContext).key;
			}
		} else {
			sContextData = this.getEntryData(oContext);
		}
		return sContextData;
	};

	/**
	 * Return the entry data serialized as a string. The default implementation assumes a JS object and uses
	 * JSON.stringify to serialize it, subclasses may override as needed.
	 *
	 * @param {sap.ui.model.Context} oContext the context object
	 * @returns {string} The serialized object data
	 */
	ListBinding.prototype.getEntryData = function(oContext) {
		return JSON.stringify(oContext.getObject());
	};

	return ListBinding;

});
<|endoftext|>


<commit_before>'use strict';

import React from 'react';
import NicoAction from '../../actions/NicoAction';
import NotificationAction from '../../actions/NotificationAction';
import CommentAction from '../../actions/CommentAction';
import SettingAction from '../../actions/SettingAction';

import Colors from 'material-ui/lib/styles/colors';
import PlayIcon from 'material-ui/lib/svg-icons/av/airplay';
import SettingsIcon from 'material-ui/lib/svg-icons/action/settings';
import {
  Appbar,
  IconButton,
  TextField
} from 'material-ui';

const defaultIconButtonProps = {tooltipPosition: 'bottom-left'};
const connect = <PlayIcon color={Colors.white} hoverColor={Colors.cyanA100}/>;
const setting = <SettingsIcon  color={Colors.white} hoverColor={Colors.cyanA100}/>;
const icons = {connect, setting};

const tryConnect = (liveId) => {
  if (! isNaN(liveId)) { liveId = `lv${liveId}`; }
  if (liveId === '' || liveId === 'lv') {
    return NotificationAction.notify('生放送IDの書式ではありません');
  }
  CommentAction.resetAllComment();
  NicoAction.connect(liveId);
};

export default class Header extends React.Component {
  displayName: 'Header'

  constructor(props) {
    super(props);
    this.state = {
      lv: ''
    };
  }

  changeLiveid = (e) => {
    this.setState({lv: e.target.value});
  }

  handleClick = (type) => {
    switch (type) {
    case 'connect':
      tryConnect(this.state.lv.trim());
      break;
    case 'setting':
      SettingAction.open();
      break;
    }
  }

  renderButton(label, type) {
    return (
      <IconButton {...defaultIconButtonProps}
        className={`${type}Button`}
        tooltip={label}
        onMouseDown={() => this.handleClick(type)}
      >{icons[type]}</IconButton>
    );
  }

  render() {
    const textFieldProps = {
      className: 'LiveIdForm',
      value: this.state.lv,
      onChange: this.changeLiveid,
      style: { margin: '0 2em' },
      inputStyle: { color: 'white' },
      hintStyle: { color: 'rgba(255,255,255,.5)' },
      hintText: '放送番号(lvXXXXXX)'
    };

    return (

        <Appbar style={{position: 'fixed', top: '0', left: '0', right: '0'}}
          iconElementLeft={
}
          title={

              comelon
              <TextField {...textFieldProps} />

          }
          iconElementRight={

              {this.renderButton('接続', 'connect')}
              {this.renderButton('設定', 'setting')}

          }
        />

    );
  }

}

// vim:ft=javascript.jsx
<commit_msg>Fix: cannot rendering Header component<commit_after>'use strict';

import React from 'react';
import NicoAction from '../../actions/NicoAction';
import NotificationAction from '../../actions/NotificationAction';
import CommentAction from '../../actions/CommentAction';
import SettingAction from '../../actions/SettingAction';

import Colors from 'material-ui/lib/styles/colors';
import PlayIcon from 'material-ui/lib/svg-icons/av/airplay';
import SettingsIcon from 'material-ui/lib/svg-icons/action/settings';
import {
  AppBar,
  IconButton,
  TextField
} from 'material-ui';

const defaultIconButtonProps = {tooltipPosition: 'bottom-left'};
const connect = <PlayIcon color={Colors.white} hoverColor={Colors.cyanA100}/>;
const setting = <SettingsIcon  color={Colors.white} hoverColor={Colors.cyanA100}/>;
const icons = {connect, setting};

const tryConnect = (liveId) => {
  if (! isNaN(liveId)) { liveId = `lv${liveId}`; }
  if (liveId === '' || liveId === 'lv') {
    return NotificationAction.notify('生放送IDの書式ではありません');
  }
  CommentAction.resetAllComment();
  NicoAction.connect(liveId);
};

export default class Header extends React.Component {
  displayName: 'Header'

  constructor(props) {
    super(props);
    this.state = {
      lv: ''
    };
  }

  changeLiveid = (e) => {
    this.setState({lv: e.target.value});
  }

  handleClick = (type) => {
    switch (type) {
    case 'connect':
      tryConnect(this.state.lv.trim());
      break;
    case 'setting':
      SettingAction.open();
      break;
    }
  }

  renderButton(label, type) {
    return (
      <IconButton {...defaultIconButtonProps}
        className={`${type}Button`}
        tooltip={label}
        onMouseDown={() => this.handleClick(type)}
      >{icons[type]}</IconButton>
    );
  }

  render() {
    const textFieldProps = {
      className: 'LiveIdForm',
      value: this.state.lv,
      onChange: this.changeLiveid,
      style: { margin: '0 2em' },
      inputStyle: { color: 'white' },
      hintStyle: { color: 'rgba(255,255,255,.5)' },
      hintText: '放送番号(lvXXXXXX)'
    };

    return (

        <AppBar style={{position: 'fixed', top: '0', left: '0', right: '0'}}
          iconElementLeft={
}
          title={

              comelon
              <TextField {...textFieldProps} />

          }
          iconElementRight={

              {this.renderButton('接続', 'connect')}
              {this.renderButton('設定', 'setting')}

          }
        />

    );
  }

}

// vim:ft=javascript.jsx
<|endoftext|>