<commit_before>goog.provide('lime.style');
goog.provide('lime.style.Transform');

goog.require('goog.dom');
goog.require('goog.style');
goog.require('lime.userAgent');

(function() {

var prefix = goog.userAgent.WEBKIT ? 'Webkit' :
             goog.userAgent.GECKO ? 'Moz' :
             goog.userAgent.OPERA ? 'O' :
             goog.userAgent.IE ? 'ms' : '';

var testDivStyle = goog.dom.createDom('div').style;

lime.style.transformProperty = '-' + prefix.toLowerCase() + '-transform';

/**
 * Try if a CSS style property with given name exists
 * @param {string} name Property name.
 * @return {boolen} If property exists.
 */
lime.style.tryProperty = function(name) {
    return testDivStyle[name] !== undefined ? name : false;
};

/**
 * Get the name of actual CSS property from general(unprefixed) name
 * @param {string} name Unprefixed name.
 * @return {string} Actual valid property name.
 */
lime.style.getCSSproperty = function(name) {
    var name_lower = name.charAt(0).toLowerCase() + name.substr(1);
    return lime.style.tryProperty(name) ?
        name : (lime.style.tryProperty(name_lower)  ? name_lower : lime.style.tryProperty(prefix + name));
};

/**
 * Set border radisu of a DOM element
 * @param {DomElement} el Element to change.
 * @param {Array.<number>} values Radius values.
 * @param {Array.<number>=} opt_vertical Vertical radius values.
 * @param {boolen=} opt_isPerc If values are given in percentages.
 */
lime.style.setBorderRadius = (function() {
    var stylename = lime.style.getCSSproperty('BorderRadius');
    var out = function(values, unit) {
        return (goog.isArray(values) ? values.join(unit + ' ') : values) + unit;
    };
    return function(el, values, opt_vertical, opt_isPerc) {
        var unit = opt_isPerc ? '%' : 'px';
        var vertical = goog.isDef(opt_vertical) ? opt_vertical : values;
        var value = out(values, unit) + '/' + out(vertical, unit);
        if (value != el.border_radius_cache_) {
            el.style[stylename] = el.border_radius_cache_ = value;
        }
    }
})();

// There are classes like CSSMatrix in some browsers.
// Maybe this would make more sense.

(function(){

    /*
     Workaround to not use transforms where possible due to bug
     https://bugzilla.mozilla.org/show_bug.cgi?id=637597
     in Firefox4. You may wish to set it to false if you need
     setPrecision() */

    var IS_FF4 = /Firefox\/4./.test(goog.userAgent.getUserAgentString());

/**
 * Object representing CSS Transform.
 * @constructor
 * @param {opt_precision=} number Default precision.
 */
lime.style.Transform = function(opt_precision) {
    this.values = [];
    this.precision = 1;
    if (this.opt_precision) {
        this.setPrecision(opt_precision);
    }
    if(IS_FF4){
        this.scalex_ = this.scaley_ = 1;
        this.x_ = this.y_ = 0;
        this.angleSet_ = false;
    }
};

/**
 * Scale current transform object
 * @param {number} sx X-axis scale factor.
 * @param {number} sy y-axis scale factor.
 * @return {lime.style.Transform} obejct itself.
 */
lime.style.Transform.prototype.scale = function(sx, sy) {
    //if(sx!=1 && sy!=1)
    if(IS_FF4){
        sx = sx.toFixed(2);
        sy = sy.toFixed(2);
    }

    this.values.push('scale(' + sx + ',' + sy + ')');
    if(IS_FF4){
        this.scalex_ *= sx, this.scaley_ *= sy;
    }
    return this;
};

/**
 * Rotate current transform object
 * @param {number} angle Angle to rotate.
 * @param {string=} opt_unit Units.
 * @return {lime.style.Transform} obejct itself.
 */
lime.style.Transform.prototype.rotate = function(angle, opt_unit) {
    var rot_str = 'rotate(' + angle + (opt_unit ? opt_unit : 'deg') + ')';
    if (angle != 0)
        this.values.push(rot_str);

    if(IS_FF4 && angle!=0){
        this.angleSet_ = true;
    }
    return this;
};

/**
 * Translate(move) current transform object
 * @param {number} tx Offset in x-axis.
 * @param {number} ty Offset in y-axis.
 * @param {number=} tz Offset in z-axis.
 * @return {lime.style.Transform} obejct itself.
 */
lime.style.Transform.prototype.translate = function(tx, ty, opt_tz) {

    var p = 1 / this.precision;
    var val = 'translate';
    if (lime.userAgent.IOS) val += '3d';
    val += '(' + (tx * p) + 'px,' + (ty * p) + 'px';
    if (lime.userAgent.IOS) val += ',' + ((opt_tz ? opt_tz : 0) * p) + 'px';
    this.values.push(val + ')');

    if(IS_FF4){
        this.x_ += this.scalex_ * tx, this.y_ += this.scaley_ * ty;
    }

    return this;
};

/**
 * Set the current precision of transform. This is handled as a
 * state machine so its added when called not when done.
 * @param {number} p Precision(Lowest value to make a difference).
 * @return {lime.style.Transform} obejct itself.
 */
lime.style.Transform.prototype.setPrecision = function(p) {
    if(IS_FF4) return this;

    if (this.precision != 1) {
        var opposite = 1 / this.precision;
        this.scale(opposite, opposite);
        this.precision = 1;
    }
    if (p != 1) {
        this.scale(p, p);
        this.precision = p;
    }
    return this;
};

/**
 * Return CSS transform string from the obejct
 * @return {string} CSS value string.
 */
lime.style.Transform.prototype.toString = function() {
    if (this.precision != 1) {
        this.setPrecision(1);
    }
    return this.values.join(' ');
};

/**
 * Set transform to a DOM element.
 * @param {DomElement} el Element to change.
 * @param {lime.style.Transform} transform Transform.
 */
lime.style.setTransform = (function() {
    var stylename = lime.style.getCSSproperty('Transform');
    return function(el, transform) {
        var value = transform.toString();

        if(IS_FF4){//console.log('ff4 '+transform.scalex_+' '+ transform.scaley_);
            if(!transform.angleSet_ && transform.scalex_ == 1 && transform.scaley_ == 1){
                el.style[stylename] = '';
                el.style['left'] = transform.x_ + 'px';
                el.style['top'] = transform.y_ + 'px';
            }
            else {
                if (value != el.transform_cache_) {
                    el.style[stylename] = el.transform_cache_ = value;
                }
                el.style['left'] = '0px';
                el.style['top'] = '0px';
            }
        }
        else {
            if (value != el.transform_cache_) {
                el.style[stylename] = el.transform_cache_ = value;
            }
        }

    }
})();

})();

/**
 * Set transform origin point for a DOM element.
 * @param {DomElement} el Element to change.
 * @param {number} ox X Offset.
 * @param {number} oy Y Offset.
 * @param {boolean} opt_isPerc If unit is percentage.
 */
lime.style.setTransformOrigin = (function() {
    var stylename = lime.style.getCSSproperty('TransformOrigin');
    return function(el, ox, oy, opt_isPerc) {
        var unit = opt_isPerc ? '%' : 'px';
        var value = ox + unit + ' ' + oy + unit;
        if (value != el.transform_origin_cache_) {
            el.style[stylename] = el.transform_origin_cache_ = value;
        }
    }
})();

var stylename = lime.style.getCSSproperty('Transition');
lime.style.isTransitionsSupported = !!stylename && !goog.userAgent.OPERA;
// Opera's CSS3 transitions seem to be unstable atm. No shorthand plus
// doesn't work if the duration property has not been previously set inside
// CSS style sheet @tonis

var clearProp = function(str, prop) {
    if (!str.length) return str;
    var proplist = str.split('),');
    for (var i = 0; i < proplist.length - 1; i++) {
        proplist[i] += ')';
    }

    proplist = goog.array.filter(proplist, function(part) {
               return part.indexOf(prop) == -1;
   });
    return proplist.join(',');
};

/**
 * Activate transition rule for a property
 * @param {DomElement} el Element to change.
 * @param {string} property Transition property name.
 * @param {number} time Transition duration.
 * @param {lime.animation.EasingFunction} ease Easing function.
 */
lime.style.setTransition = function(el, property, time, ease) {
    if (!stylename) return;
    var curvalue = clearProp(el.style[stylename], property);
    if (curvalue.length) curvalue += ', ';
    //console.log(time+'s cubic-bezier('+ease[1]+',
    //'+ease[2]+','+ease[3]+','+ease[4]+')');
    curvalue += property + ' ' + time + 's cubic-bezier(' + ease[1] +
        ',' + ease[2] + ',' + ease[3] + ',' + ease[4] + ')';
    el.style[stylename] = curvalue;
};

/**
 * Clear previously set transition rule.
 * @param {DomElement} el Element to change.
 * @param {štring} property Transition property name.
 */
lime.style.clearTransition = function(el, property) {
    if (!stylename || !el) return;
    el.style[stylename] = clearProp(el.style[stylename], property);

   // console.log('clear',el.style[stylename],property);
};

/**
 * Change size of a DOM element. Has cache built in for speed boost.
 * @param {DomElement} el Element to change.
 * @param {number} w New width.
 * @param {number} h New height.
 */
lime.style.setSize = function(el, w, h) {
    if (el.width_cache_ != w || el.height_cache_ != h) {
        el.width_cache_ = w;
        el.height_cache_ = h;
        return goog.style.setSize(el, w, h);
    }
}

})();
<commit_msg>Fix FF4 alternative transformations bug.<commit_after>goog.provide('lime.style');
goog.provide('lime.style.Transform');

goog.require('goog.dom');
goog.require('goog.style');
goog.require('lime.userAgent');

(function() {

var prefix = goog.userAgent.WEBKIT ? 'Webkit' :
             goog.userAgent.GECKO ? 'Moz' :
             goog.userAgent.OPERA ? 'O' :
             goog.userAgent.IE ? 'ms' : '';

var testDivStyle = goog.dom.createDom('div').style;

lime.style.transformProperty = '-' + prefix.toLowerCase() + '-transform';

/**
 * Try if a CSS style property with given name exists
 * @param {string} name Property name.
 * @return {boolen} If property exists.
 */
lime.style.tryProperty = function(name) {
    return testDivStyle[name] !== undefined ? name : false;
};

/**
 * Get the name of actual CSS property from general(unprefixed) name
 * @param {string} name Unprefixed name.
 * @return {string} Actual valid property name.
 */
lime.style.getCSSproperty = function(name) {
    var name_lower = name.charAt(0).toLowerCase() + name.substr(1);
    return lime.style.tryProperty(name) ?
        name : (lime.style.tryProperty(name_lower)  ? name_lower : lime.style.tryProperty(prefix + name));
};

/**
 * Set border radisu of a DOM element
 * @param {DomElement} el Element to change.
 * @param {Array.<number>} values Radius values.
 * @param {Array.<number>=} opt_vertical Vertical radius values.
 * @param {boolen=} opt_isPerc If values are given in percentages.
 */
lime.style.setBorderRadius = (function() {
    var stylename = lime.style.getCSSproperty('BorderRadius');
    var out = function(values, unit) {
        return (goog.isArray(values) ? values.join(unit + ' ') : values) + unit;
    };
    return function(el, values, opt_vertical, opt_isPerc) {
        var unit = opt_isPerc ? '%' : 'px';
        var vertical = goog.isDef(opt_vertical) ? opt_vertical : values;
        var value = out(values, unit) + '/' + out(vertical, unit);
        if (value != el.border_radius_cache_) {
            el.style[stylename] = el.border_radius_cache_ = value;
        }
    }
})();

// There are classes like CSSMatrix in some browsers.
// Maybe this would make more sense.

(function(){

    /*
     Workaround to not use transforms where possible due to bug
     https://bugzilla.mozilla.org/show_bug.cgi?id=637597
     in Firefox4. You may wish to set it to false if you need
     setPrecision() */

    var IS_FF4 = /Firefox\/4./.test(goog.userAgent.getUserAgentString());

/**
 * Object representing CSS Transform.
 * @constructor
 * @param {opt_precision=} number Default precision.
 */
lime.style.Transform = function(opt_precision) {
    this.values = [];
    this.precision = 1;
    if (this.opt_precision) {
        this.setPrecision(opt_precision);
    }
    if(IS_FF4){
        this.scalex_ = this.scaley_ = 1;
        this.x_ = this.y_ = 0;
        this.angleSet_ = false;
    }
};

/**
 * Scale current transform object
 * @param {number} sx X-axis scale factor.
 * @param {number} sy y-axis scale factor.
 * @return {lime.style.Transform} obejct itself.
 */
lime.style.Transform.prototype.scale = function(sx, sy) {
    //if(sx!=1 && sy!=1)
    if(IS_FF4){
        sx = sx.toFixed(2);
        sy = sy.toFixed(2);
    }

    this.values.push('scale(' + sx + ',' + sy + ')');
    if(IS_FF4){
        this.scalex_ *= sx, this.scaley_ *= sy;
    }
    return this;
};

/**
 * Rotate current transform object
 * @param {number} angle Angle to rotate.
 * @param {string=} opt_unit Units.
 * @return {lime.style.Transform} obejct itself.
 */
lime.style.Transform.prototype.rotate = function(angle, opt_unit) {
    var rot_str = 'rotate(' + angle + (opt_unit ? opt_unit : 'deg') + ')';
    if (angle != 0)
        this.values.push(rot_str);

    if(IS_FF4 && angle!=0){
        this.angleSet_ = true;
    }
    return this;
};

/**
 * Translate(move) current transform object
 * @param {number} tx Offset in x-axis.
 * @param {number} ty Offset in y-axis.
 * @param {number=} tz Offset in z-axis.
 * @return {lime.style.Transform} obejct itself.
 */
lime.style.Transform.prototype.translate = function(tx, ty, opt_tz) {

    var p = 1 / this.precision;
    var val = 'translate';
    if (lime.userAgent.IOS) val += '3d';
    val += '(' + (tx * p) + 'px,' + (ty * p) + 'px';
    if (lime.userAgent.IOS) val += ',' + ((opt_tz ? opt_tz : 0) * p) + 'px';
    this.values.push(val + ')');

    if(IS_FF4){
        this.x_ += this.scalex_ * tx, this.y_ += this.scaley_ * ty;
    }

    return this;
};

/**
 * Set the current precision of transform. This is handled as a
 * state machine so its added when called not when done.
 * @param {number} p Precision(Lowest value to make a difference).
 * @return {lime.style.Transform} obejct itself.
 */
lime.style.Transform.prototype.setPrecision = function(p) {
    if(IS_FF4) return this;

    if (this.precision != 1) {
        var opposite = 1 / this.precision;
        this.scale(opposite, opposite);
        this.precision = 1;
    }
    if (p != 1) {
        this.scale(p, p);
        this.precision = p;
    }
    return this;
};

/**
 * Return CSS transform string from the obejct
 * @return {string} CSS value string.
 */
lime.style.Transform.prototype.toString = function() {
    if (this.precision != 1) {
        this.setPrecision(1);
    }
    return this.values.join(' ');
};

/**
 * Set transform to a DOM element.
 * @param {DomElement} el Element to change.
 * @param {lime.style.Transform} transform Transform.
 */
lime.style.setTransform = (function() {
    var stylename = lime.style.getCSSproperty('Transform');
    return function(el, transform) {
        var value = transform.toString();

        if(IS_FF4){//console.log('ff4 '+transform.scalex_+' '+ transform.scaley_);
            if(!transform.angleSet_ && transform.scalex_ == 1 && transform.scaley_ == 1){
                el.style[stylename] = el.transform_cache_ =  '';
                el.style['left'] = transform.x_ + 'px';
                el.style['top'] = transform.y_ + 'px';
            }
            else {
                if (value != el.transform_cache_) {
                    el.style[stylename] = el.transform_cache_ = value;
                }
                el.style['left'] = '0px';
                el.style['top'] = '0px';
            }
        }
        else {
            if (value != el.transform_cache_) {
                el.style[stylename] = el.transform_cache_ = value;
            }
        }

    }
})();

})();

/**
 * Set transform origin point for a DOM element.
 * @param {DomElement} el Element to change.
 * @param {number} ox X Offset.
 * @param {number} oy Y Offset.
 * @param {boolean} opt_isPerc If unit is percentage.
 */
lime.style.setTransformOrigin = (function() {
    var stylename = lime.style.getCSSproperty('TransformOrigin');
    return function(el, ox, oy, opt_isPerc) {
        var unit = opt_isPerc ? '%' : 'px';
        var value = ox + unit + ' ' + oy + unit;
        if (value != el.transform_origin_cache_) {
            el.style[stylename] = el.transform_origin_cache_ = value;
        }
    }
})();

var stylename = lime.style.getCSSproperty('Transition');
lime.style.isTransitionsSupported = !!stylename && !goog.userAgent.OPERA;
// Opera's CSS3 transitions seem to be unstable atm. No shorthand plus
// doesn't work if the duration property has not been previously set inside
// CSS style sheet @tonis

var clearProp = function(str, prop) {
    if (!str.length) return str;
    var proplist = str.split('),');
    for (var i = 0; i < proplist.length - 1; i++) {
        proplist[i] += ')';
    }

    proplist = goog.array.filter(proplist, function(part) {
               return part.indexOf(prop) == -1;
   });
    return proplist.join(',');
};

/**
 * Activate transition rule for a property
 * @param {DomElement} el Element to change.
 * @param {string} property Transition property name.
 * @param {number} time Transition duration.
 * @param {lime.animation.EasingFunction} ease Easing function.
 */
lime.style.setTransition = function(el, property, time, ease) {
    if (!stylename) return;
    var curvalue = clearProp(el.style[stylename], property);
    if (curvalue.length) curvalue += ', ';
    //console.log(time+'s cubic-bezier('+ease[1]+',
    //'+ease[2]+','+ease[3]+','+ease[4]+')');
    curvalue += property + ' ' + time + 's cubic-bezier(' + ease[1] +
        ',' + ease[2] + ',' + ease[3] + ',' + ease[4] + ')';
    el.style[stylename] = curvalue;
};

/**
 * Clear previously set transition rule.
 * @param {DomElement} el Element to change.
 * @param {štring} property Transition property name.
 */
lime.style.clearTransition = function(el, property) {
    if (!stylename || !el) return;
    el.style[stylename] = clearProp(el.style[stylename], property);

   // console.log('clear',el.style[stylename],property);
};

/**
 * Change size of a DOM element. Has cache built in for speed boost.
 * @param {DomElement} el Element to change.
 * @param {number} w New width.
 * @param {number} h New height.
 */
lime.style.setSize = function(el, w, h) {
    if (el.width_cache_ != w || el.height_cache_ != h) {
        el.width_cache_ = w;
        el.height_cache_ = h;
        return goog.style.setSize(el, w, h);
    }
}

})();
<|endoftext|>


<commit_before>'use strict';

/**
 * @ngdoc service
 * @name ng.$q
 * @requires $rootScope
 *
 * @description
 * A promise/deferred implementation inspired by [Kris Kowal's Q](https://github.com/kriskowal/q).
 *
 * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an
 * interface for interacting with an object that represents the result of an action that is
 * performed asynchronously, and may or may not be finished at any given point in time.
 *
 * From the perspective of dealing with error handling, deferred and promise APIs are to
 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming.
 *
 *

 *   // for the purpose of this example let's assume that variables `$q` and `scope` are
 *   // available in the current lexical scope (they could have been injected or passed in).
 *
 *   function asyncGreet(name) {
 *     var deferred = $q.defer();
 *
 *     setTimeout(function() {
 *       // since this fn executes async in a future turn of the event loop, we need to wrap
 *       // our code into an $apply call so that the model changes are properly observed.
 *       scope.$apply(function() {
 *         if (okToGreet(name)) {
 *           deferred.resolve('Hello, ' + name + '!');
 *         } else {
 *           deferred.reject('Greeting ' + name + ' is not allowed.');
 *         }
 *       });
 *     }, 1000);
 *
 *     return deferred.promise;
 *   }
 *
 *   var promise = asyncGreet('Robin Hood');
 *   promise.then(function(greeting) {
 *     alert('Success: ' + greeting);
 *   }, function(reason) {
 *     alert('Failed: ' + reason);
 *   });
 *

 *
 * At first it might not be obvious why this extra complexity is worth the trouble. The payoff
 * comes in the way of
 * [guarantees that promise and deferred APIs make](https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md).
 *
 * Additionally the promise api allows for composition that is very hard to do with the
 * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach.
 * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the
 * section on serial or parallel joining of promises.
 *
 *
 * # The Deferred API
 *
 * A new instance of deferred is constructed by calling `$q.defer()`.
 *
 * The purpose of the deferred object is to expose the associated Promise instance as well as APIs
 * that can be used for signaling the successful or unsuccessful completion of the task.
 *
 * **Methods**
 *
 * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection
 *   constructed via `$q.reject`, the promise will be rejected instead.
 * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to
 *   resolving it with a rejection constructed via `$q.reject`.
 *
 * **Properties**
 *
 * - promise – `{Promise}` – promise object associated with this deferred.
 *
 *
 * # The Promise API
 *
 * A new promise instance is created when a deferred instance is created and can be retrieved by
 * calling `deferred.promise`.
 *
 * The purpose of the promise object is to allow for interested parties to get access to the result
 * of the deferred task when it completes.
 *
 * **Methods**
 *
 * - `then(successCallback, errorCallback)` – regardless of when the promise was or will be resolved
 *   or rejected calls one of the success or error callbacks asynchronously as soon as the result
 *   is available. The callbacks are called with a single argument the result or rejection reason.
 *
 *   This method *returns a new promise* which is resolved or rejected via the return value of the
 *   `successCallback` or `errorCallback`.
 *
 * - `always(callback)` – allows you to observe either the fulfillment or rejection of a promise,
 *   but to do so without modifying the final value. This is useful to release resources or do some
 *   clean-up that needs to be done whether the promise was rejected or resolved. See the [full
 *   specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for
 *   more information.
 *
 * # Chaining promises
 *
 * Because calling `then` api of a promise returns a new derived promise, it is easily possible
 * to create a chain of promises:
 *
 *

 *   promiseB = promiseA.then(function(result) {
 *     return result + 1;
 *   });
 *
 *   // promiseB will be resolved immediately after promiseA is resolved and its value will be
 *   // the result of promiseA incremented by 1
 *

 *
 * It is possible to create chains of any length and since a promise can be resolved with another
 * promise (which will defer its resolution further), it is possible to pause/defer resolution of
 * the promises at any point in the chain. This makes it possible to implement powerful apis like
 * $http's response interceptors.
 *
 *
 * # Differences between Kris Kowal's Q and $q
 *
 *  There are three main differences:
 *
 * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation
 *   mechanism in angular, which means faster propagation of resolution or rejection into your
 *   models and avoiding unnecessary browser repaints, which would result in flickering UI.
 * - $q promises are recognized by the templating engine in angular, which means that in templates
 *   you can treat promises attached to a scope as if they were the resulting values.
 * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains
 *   all the important functionality needed for common async tasks.
 *
 *  # Testing
 *
 *

 *    it('should simulate promise', inject(function($q, $rootScope) {
 *      var deferred = $q.defer();
 *      var promise = deferred.promise;
 *      var resolvedValue;
 *
 *      promise.then(function(value) { resolvedValue = value; });
 *      expect(resolvedValue).toBeUndefined();
 *
 *      // Simulate resolving of promise
 *      deferred.resolve(123);
 *      // Note that the 'then' function does not get called synchronously.
 *      // This is because we want the promise API to always be async, whether or not
 *      // it got called synchronously or asynchronously.
 *      expect(resolvedValue).toBeUndefined();
 *
 *      // Propagate promise resolution to 'then' functions using $apply().
 *      $rootScope.$apply();
 *      expect(resolvedValue).toEqual(123);
 *    });
 *

 */
function $QProvider() {

  this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) {
    return qFactory(function(callback) {
      $rootScope.$evalAsync(callback);
    }, $exceptionHandler);
  }];
}

/**
 * Constructs a promise manager.
 *
 * @param {function(function)} nextTick Function for executing functions in the next turn.
 * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for
 *     debugging purposes.
 * @returns {object} Promise manager.
 */
function qFactory(nextTick, exceptionHandler) {

  /**
   * @ngdoc
   * @name ng.$q#defer
   * @methodOf ng.$q
   * @description
   * Creates a `Deferred` object which represents a task which will finish in the future.
   *
   * @returns {Deferred} Returns a new instance of deferred.
   */
  var defer = function() {
    var pending = [],
        value, deferred;

    deferred = {

      resolve: function(val) {
        if (pending) {
          var callbacks = pending;
          pending = undefined;
          value = ref(val);

          if (callbacks.length) {
            nextTick(function() {
              var callback;
              for (var i = 0, ii = callbacks.length; i < ii; i++) {
                callback = callbacks[i];
                value.then(callback[0], callback[1]);
              }
            });
          }
        }
      },

      reject: function(reason) {
        deferred.resolve(reject(reason));
      },

      promise: {
        then: function(callback, errback) {
          var result = defer();

          var wrappedCallback = function(value) {
            try {
              result.resolve((callback || defaultCallback)(value));
            } catch(e) {
              exceptionHandler(e);
              result.reject(e);
            }
          };

          var wrappedErrback = function(reason) {
            try {
              result.resolve((errback || defaultErrback)(reason));
            } catch(e) {
              exceptionHandler(e);
              result.reject(e);
            }
          };

          if (pending) {
            pending.push([wrappedCallback, wrappedErrback]);
          } else {
            value.then(wrappedCallback, wrappedErrback);
          }

          return result.promise;
        },
        always: function(callback) {

          function makePromise(value, resolved) {
            var result = defer();
            if (resolved) {
              result.resolve(value);
            } else {
              result.reject(value);
            }
            return result.promise;
          }

          function handleCallback(value, isResolved) {
            var callbackOutput = null;
            try {
              callbackOutput = (callback ||defaultCallback)();
            } catch(e) {
              return makePromise(e, false);
            }
            if (callbackOutput && callbackOutput.then) {
              return callbackOutput.then(function() {
                return makePromise(value, isResolved);
              }, function(error) {
                return makePromise(error, false);
              });
            } else {
              return makePromise(value, isResolved);
            }
          }

          return this.then(function(value) {
            return handleCallback(value, true);
          }, function(error) {
            return handleCallback(error, false);
          });
        }
      }
    };

    return deferred;
  };

  var ref = function(value) {
    if (value && value.then) return value;
    return {
      then: function(callback) {
        var result = defer();
        nextTick(function() {
          result.resolve(callback(value));
        });
        return result.promise;
      }
    };
  };

  /**
   * @ngdoc
   * @name ng.$q#reject
   * @methodOf ng.$q
   * @description
   * Creates a promise that is resolved as rejected with the specified `reason`. This api should be
   * used to forward rejection in a chain of promises. If you are dealing with the last promise in
   * a promise chain, you don't need to worry about it.
   *
   * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of
   * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via
   * a promise error callback and you want to forward the error to the promise derived from the
   * current promise, you have to "rethrow" the error by returning a rejection constructed via
   * `reject`.
   *
   *

   *   promiseB = promiseA.then(function(result) {
   *     // success: do something and resolve promiseB
   *     //          with the old or a new result
   *     return result;
   *   }, function(reason) {
   *     // error: handle the error if possible and
   *     //        resolve promiseB with newPromiseOrValue,
   *     //        otherwise forward the rejection to promiseB
   *     if (canHandle(reason)) {
   *      // handle the error and recover
   *      return newPromiseOrValue;
   *     }
   *     return $q.reject(reason);
   *   });
   *

   *
   * @param {*} reason Constant, message, exception or an object representing the rejection reason.
   * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`.
   */
  var reject = function(reason) {
    return {
      then: function(callback, errback) {
        var result = defer();
        nextTick(function() {
          result.resolve((errback || defaultErrback)(reason));
        });
        return result.promise;
      }
    };
  };

  /**
   * @ngdoc
   * @name ng.$q#when
   * @methodOf ng.$q
   * @description
   * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise.
   * This is useful when you are dealing with an object that might or might not be a promise, or if
   * the promise comes from a source that can't be trusted.
   *
   * @param {*} value Value or a promise
   * @returns {Promise} Returns a promise of the passed value or promise
   */
  var when = function(value, callback, errback) {
    var result = defer(),
        done;

    var wrappedCallback = function(value) {
      try {
        return (callback || defaultCallback)(value);
      } catch (e) {
        exceptionHandler(e);
        return reject(e);
      }
    };

    var wrappedErrback = function(reason) {
      try {
        return (errback || defaultErrback)(reason);
      } catch (e) {
        exceptionHandler(e);
        return reject(e);
      }
    };

    nextTick(function() {
      ref(value).then(function(value) {
        if (done) return;
        done = true;
        result.resolve(ref(value).then(wrappedCallback, wrappedErrback));
      }, function(reason) {
        if (done) return;
        done = true;
        result.resolve(wrappedErrback(reason));
      });
    });

    return result.promise;
  };

  function defaultCallback(value) {
    return value;
  }

  function defaultErrback(reason) {
    return reject(reason);
  }

  /**
   * @ngdoc
   * @name ng.$q#all
   * @methodOf ng.$q
   * @description
   * Combines multiple promises into a single promise that is resolved when all of the input
   * promises are resolved.
   *
   * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises.
   * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values,
   *   each value corresponding to the promise at the same index/key in the `promises` array/hash. If any of
   *   the promises is resolved with a rejection, this resulting promise will be resolved with the
   *   same rejection.
   */
  function all(promises) {
    var deferred = defer(),
        counter = 0,
        results = isArray(promises) ? [] : {};

    forEach(promises, function(promise, key) {
      counter++;
      ref(promise).then(function(value) {
        if (results.hasOwnProperty(key)) return;
        results[key] = value;
        if (!(--counter)) deferred.resolve(results);
      }, function(reason) {
        if (results.hasOwnProperty(key)) return;
        deferred.reject(reason);
      });
    });

    if (counter === 0) {
      deferred.resolve(results);
    }

    return deferred.promise;
  }

  return {
    defer: defer,
    reject: reject,
    when: when,
    all: all
  };
}
<commit_msg>docs($q): fix a few issues<commit_after>'use strict';

/**
 * @ngdoc service
 * @name ng.$q
 * @requires $rootScope
 *
 * @description
 * A promise/deferred implementation inspired by [Kris Kowal's Q](https://github.com/kriskowal/q).
 *
 * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an
 * interface for interacting with an object that represents the result of an action that is
 * performed asynchronously, and may or may not be finished at any given point in time.
 *
 * From the perspective of dealing with error handling, deferred and promise APIs are to
 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming.
 *
 *

 *   // for the purpose of this example let's assume that variables `$q` and `scope` are
 *   // available in the current lexical scope (they could have been injected or passed in).
 *
 *   function asyncGreet(name) {
 *     var deferred = $q.defer();
 *
 *     setTimeout(function() {
 *       // since this fn executes async in a future turn of the event loop, we need to wrap
 *       // our code into an $apply call so that the model changes are properly observed.
 *       scope.$apply(function() {
 *         if (okToGreet(name)) {
 *           deferred.resolve('Hello, ' + name + '!');
 *         } else {
 *           deferred.reject('Greeting ' + name + ' is not allowed.');
 *         }
 *       });
 *     }, 1000);
 *
 *     return deferred.promise;
 *   }
 *
 *   var promise = asyncGreet('Robin Hood');
 *   promise.then(function(greeting) {
 *     alert('Success: ' + greeting);
 *   }, function(reason) {
 *     alert('Failed: ' + reason);
 *   });
 *

 *
 * At first it might not be obvious why this extra complexity is worth the trouble. The payoff
 * comes in the way of
 * [guarantees that promise and deferred APIs make](https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md).
 *
 * Additionally the promise api allows for composition that is very hard to do with the
 * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach.
 * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the
 * section on serial or parallel joining of promises.
 *
 *
 * # The Deferred API
 *
 * A new instance of deferred is constructed by calling `$q.defer()`.
 *
 * The purpose of the deferred object is to expose the associated Promise instance as well as APIs
 * that can be used for signaling the successful or unsuccessful completion of the task.
 *
 * **Methods**
 *
 * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection
 *   constructed via `$q.reject`, the promise will be rejected instead.
 * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to
 *   resolving it with a rejection constructed via `$q.reject`.
 *
 * **Properties**
 *
 * - promise – `{Promise}` – promise object associated with this deferred.
 *
 *
 * # The Promise API
 *
 * A new promise instance is created when a deferred instance is created and can be retrieved by
 * calling `deferred.promise`.
 *
 * The purpose of the promise object is to allow for interested parties to get access to the result
 * of the deferred task when it completes.
 *
 * **Methods**
 *
 * - `then(successCallback, errorCallback)` – regardless of when the promise was or will be resolved
 *   or rejected, `then` calls one of the success or error callbacks asynchronously as soon as the result
 *   is available. The callbacks are called with a single argument: the result or rejection reason.
 *
 *   This method *returns a new promise* which is resolved or rejected via the return value of the
 *   `successCallback` or `errorCallback`.
 *
 * - `always(callback)` – allows you to observe either the fulfillment or rejection of a promise,
 *   but to do so without modifying the final value. This is useful to release resources or do some
 *   clean-up that needs to be done whether the promise was rejected or resolved. See the [full
 *   specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for
 *   more information.
 *
 * # Chaining promises
 *
 * Because calling the `then` method of a promise returns a new derived promise, it is easily possible
 * to create a chain of promises:
 *
 *

 *   promiseB = promiseA.then(function(result) {
 *     return result + 1;
 *   });
 *
 *   // promiseB will be resolved immediately after promiseA is resolved and its value
 *   // will be the result of promiseA incremented by 1
 *

 *
 * It is possible to create chains of any length and since a promise can be resolved with another
 * promise (which will defer its resolution further), it is possible to pause/defer resolution of
 * the promises at any point in the chain. This makes it possible to implement powerful APIs like
 * $http's response interceptors.
 *
 *
 * # Differences between Kris Kowal's Q and $q
 *
 *  There are three main differences:
 *
 * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation
 *   mechanism in angular, which means faster propagation of resolution or rejection into your
 *   models and avoiding unnecessary browser repaints, which would result in flickering UI.
 * - $q promises are recognized by the templating engine in angular, which means that in templates
 *   you can treat promises attached to a scope as if they were the resulting values.
 * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains
 *   all the important functionality needed for common async tasks.
 *
 *  # Testing
 *
 *

 *    it('should simulate promise', inject(function($q, $rootScope) {
 *      var deferred = $q.defer();
 *      var promise = deferred.promise;
 *      var resolvedValue;
 *
 *      promise.then(function(value) { resolvedValue = value; });
 *      expect(resolvedValue).toBeUndefined();
 *
 *      // Simulate resolving of promise
 *      deferred.resolve(123);
 *      // Note that the 'then' function does not get called synchronously.
 *      // This is because we want the promise API to always be async, whether or not
 *      // it got called synchronously or asynchronously.
 *      expect(resolvedValue).toBeUndefined();
 *
 *      // Propagate promise resolution to 'then' functions using $apply().
 *      $rootScope.$apply();
 *      expect(resolvedValue).toEqual(123);
 *    });
 *

 */
function $QProvider() {

  this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) {
    return qFactory(function(callback) {
      $rootScope.$evalAsync(callback);
    }, $exceptionHandler);
  }];
}

/**
 * Constructs a promise manager.
 *
 * @param {function(function)} nextTick Function for executing functions in the next turn.
 * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for
 *     debugging purposes.
 * @returns {object} Promise manager.
 */
function qFactory(nextTick, exceptionHandler) {

  /**
   * @ngdoc
   * @name ng.$q#defer
   * @methodOf ng.$q
   * @description
   * Creates a `Deferred` object which represents a task which will finish in the future.
   *
   * @returns {Deferred} Returns a new instance of deferred.
   */
  var defer = function() {
    var pending = [],
        value, deferred;

    deferred = {

      resolve: function(val) {
        if (pending) {
          var callbacks = pending;
          pending = undefined;
          value = ref(val);

          if (callbacks.length) {
            nextTick(function() {
              var callback;
              for (var i = 0, ii = callbacks.length; i < ii; i++) {
                callback = callbacks[i];
                value.then(callback[0], callback[1]);
              }
            });
          }
        }
      },

      reject: function(reason) {
        deferred.resolve(reject(reason));
      },

      promise: {
        then: function(callback, errback) {
          var result = defer();

          var wrappedCallback = function(value) {
            try {
              result.resolve((callback || defaultCallback)(value));
            } catch(e) {
              exceptionHandler(e);
              result.reject(e);
            }
          };

          var wrappedErrback = function(reason) {
            try {
              result.resolve((errback || defaultErrback)(reason));
            } catch(e) {
              exceptionHandler(e);
              result.reject(e);
            }
          };

          if (pending) {
            pending.push([wrappedCallback, wrappedErrback]);
          } else {
            value.then(wrappedCallback, wrappedErrback);
          }

          return result.promise;
        },
        always: function(callback) {

          function makePromise(value, resolved) {
            var result = defer();
            if (resolved) {
              result.resolve(value);
            } else {
              result.reject(value);
            }
            return result.promise;
          }

          function handleCallback(value, isResolved) {
            var callbackOutput = null;
            try {
              callbackOutput = (callback ||defaultCallback)();
            } catch(e) {
              return makePromise(e, false);
            }
            if (callbackOutput && callbackOutput.then) {
              return callbackOutput.then(function() {
                return makePromise(value, isResolved);
              }, function(error) {
                return makePromise(error, false);
              });
            } else {
              return makePromise(value, isResolved);
            }
          }

          return this.then(function(value) {
            return handleCallback(value, true);
          }, function(error) {
            return handleCallback(error, false);
          });
        }
      }
    };

    return deferred;
  };

  var ref = function(value) {
    if (value && value.then) return value;
    return {
      then: function(callback) {
        var result = defer();
        nextTick(function() {
          result.resolve(callback(value));
        });
        return result.promise;
      }
    };
  };

  /**
   * @ngdoc
   * @name ng.$q#reject
   * @methodOf ng.$q
   * @description
   * Creates a promise that is resolved as rejected with the specified `reason`. This api should be
   * used to forward rejection in a chain of promises. If you are dealing with the last promise in
   * a promise chain, you don't need to worry about it.
   *
   * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of
   * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via
   * a promise error callback and you want to forward the error to the promise derived from the
   * current promise, you have to "rethrow" the error by returning a rejection constructed via
   * `reject`.
   *
   *

   *   promiseB = promiseA.then(function(result) {
   *     // success: do something and resolve promiseB
   *     //          with the old or a new result
   *     return result;
   *   }, function(reason) {
   *     // error: handle the error if possible and
   *     //        resolve promiseB with newPromiseOrValue,
   *     //        otherwise forward the rejection to promiseB
   *     if (canHandle(reason)) {
   *      // handle the error and recover
   *      return newPromiseOrValue;
   *     }
   *     return $q.reject(reason);
   *   });
   *

   *
   * @param {*} reason Constant, message, exception or an object representing the rejection reason.
   * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`.
   */
  var reject = function(reason) {
    return {
      then: function(callback, errback) {
        var result = defer();
        nextTick(function() {
          result.resolve((errback || defaultErrback)(reason));
        });
        return result.promise;
      }
    };
  };

  /**
   * @ngdoc
   * @name ng.$q#when
   * @methodOf ng.$q
   * @description
   * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise.
   * This is useful when you are dealing with an object that might or might not be a promise, or if
   * the promise comes from a source that can't be trusted.
   *
   * @param {*} value Value or a promise
   * @returns {Promise} Returns a promise of the passed value or promise
   */
  var when = function(value, callback, errback) {
    var result = defer(),
        done;

    var wrappedCallback = function(value) {
      try {
        return (callback || defaultCallback)(value);
      } catch (e) {
        exceptionHandler(e);
        return reject(e);
      }
    };

    var wrappedErrback = function(reason) {
      try {
        return (errback || defaultErrback)(reason);
      } catch (e) {
        exceptionHandler(e);
        return reject(e);
      }
    };

    nextTick(function() {
      ref(value).then(function(value) {
        if (done) return;
        done = true;
        result.resolve(ref(value).then(wrappedCallback, wrappedErrback));
      }, function(reason) {
        if (done) return;
        done = true;
        result.resolve(wrappedErrback(reason));
      });
    });

    return result.promise;
  };

  function defaultCallback(value) {
    return value;
  }

  function defaultErrback(reason) {
    return reject(reason);
  }

  /**
   * @ngdoc
   * @name ng.$q#all
   * @methodOf ng.$q
   * @description
   * Combines multiple promises into a single promise that is resolved when all of the input
   * promises are resolved.
   *
   * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises.
   * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values,
   *   each value corresponding to the promise at the same index/key in the `promises` array/hash. If any of
   *   the promises is resolved with a rejection, this resulting promise will be resolved with the
   *   same rejection.
   */
  function all(promises) {
    var deferred = defer(),
        counter = 0,
        results = isArray(promises) ? [] : {};

    forEach(promises, function(promise, key) {
      counter++;
      ref(promise).then(function(value) {
        if (results.hasOwnProperty(key)) return;
        results[key] = value;
        if (!(--counter)) deferred.resolve(results);
      }, function(reason) {
        if (results.hasOwnProperty(key)) return;
        deferred.reject(reason);
      });
    });

    if (counter === 0) {
      deferred.resolve(results);
    }

    return deferred.promise;
  }

  return {
    defer: defer,
    reject: reject,
    when: when,
    all: all
  };
}
<|endoftext|>


<commit_before>'use strict';

var ords = angular.module('ords',['ngRoute', 'ngResource', 'angular-growl', 'ngMessages', 'angularUtils.directives.dirPagination', 'gettext'])

	//
	// Setup the gettext() function
	//
	.run(function (gettextCatalog) {
	    gettextCatalog.setCurrentLanguage('en');
		//gettextCatalog.debug = true;
		//gettextCatalog.showTranslatedMarkers = true;
	})

	//
	// Project REST Resources
	//
	.factory('Project', function( $resource ) {
		return $resource(
			'/api/1.0/project/:id/',
			null,
			{'update': { method:'PUT' }}
		)
	})

	.factory('Statistics', function( $resource ) {
		return $resource('/api/1.0/statistics')
	})

	.factory('Audit', function( $resource ) {
		return $resource('/api/1.0/audit/project/:id')
	})

	.factory('Invitation', function( $resource ) {
		return $resource('/api/1.0/project/:id/invitation/:inviteId')
	})

	.factory('User', function( $resource ) {
		return $resource(
			'/api/1.0/user/:id',
			null,
			{
				'lookup': { method: 'GET' },
			 	'update': { method: 'PUT' }
		 	}
		);
	})

	.factory('Member', function( $resource, User ) {

		var Member =  $resource('/api/1.0/project/:id/role/:roleid', null, {'update': { method:'PUT' }});

		Member.prototype.name = "";

		//
		// The "name" property of a Member is held by the User resource
		// so we have to load that asynchronously from the REST API
		//
		Member.prototype.getName = function () {

			if (this.name !== "") return this.name;
			var that = this;
			User.lookup( {name: this.principalName}, function(user){
				that.name = user.name;
			}
			);
			this.name = "loading ...";
		};

		return Member;
	})

	//
	// A "really?" handler to confirm actions
	// Usage: Add attributes: ng-really-message="Are you sure"? ng-really-click="takeAction()" function
	// See: https://gist.github.com/asafge/7430497
	//
	.directive('ngReallyClick', [function() {
	    return {
	        restrict: 'A',
	        link: function(scope, element, attrs) {
	            element.bind('click', function() {
	                var message = attrs.ngReallyMessage;
	                if (message && confirm(message)) {
	                    scope.$apply(attrs.ngReallyClick);
	                }
	            });
	        }
	    }
	}])

	//
	// Service that checks if user is currently authenticated
	//
	.factory('AuthService', function ($rootScope, $location, $routeParams, User, Project){
		var svc = {};
		svc.check = function(){
			if ($rootScope.loggedIn !== "yes"){
				$rootScope.user = User.get(
					 function successCallback() {
						$rootScope.loggedIn="yes"
						 if ($location.path() === "/"){

			 	 			//
			 	 			// Load initial projects
			 	 			//
			 	 			Project.query({}, function(data){
			 	 				$rootScope.projects = data;
			 	 			});
			 				$location.path("/projects");
						 }
					 },
					 function errorCallback(response) {

						 //
						 // If we have a 401, we aren't logged in...
						 //
						 if (response.status === 401){
							 $rootScope.loggedIn="no";
							 $location.path("/");
						 }

						 //
						 // If we have a 404, we're logged in, but haven't registered yet
						 //
						 if (response.status === 404){
							 $rootScope.loggedIn="no";
							 if (!$routeParams.code){
 							 	$location.path("/register");
							 }
						 }
					 }
				);
			} else {
				//
				// If we are already logged in, bypass the front page and
				// view the projects page
				//
				if ($location.path() === "/"){
					$location.path("/projects");
				}
			}
		};
		return svc;
	})

	//
	// Configure alerts
	//
	.config(['growlProvider', function(growlProvider) {
	  growlProvider.globalTimeToLive(10000);
	  growlProvider.globalDisableCountDown(true);
	}])

	//
	// configure  routes
	//
	.config(function($routeProvider) {
	        $routeProvider

	            // Home page
	            .when('/projects', {
	                templateUrl : 'project/views/home.html',
	                controller  : 'projectsController'
	            })

	            // Search results
	            .when('/search', {
	                templateUrl : 'views/searchresults.html',
	                controller  : 'searchController'
	            })

	            // Home page for login
	            .when('/', {
	                templateUrl : 'views/home.html',
	                controller  : 'mainController'
	            })

	            // User page
	            .when('/profile', {
	                templateUrl : 'views/profile.html',
	                controller  : 'profileController'
	            })

	            // Project Details
	            .when('/project/:id', {
	                templateUrl : 'project/views/project.html',
	                controller  : 'projectController'
	            })

				// New Project Form
				.when('/newproject', {
	                templateUrl : 'project/views/newproject.html',
	                controller  : 'newProjectController'
				})

	            // Edit Project Details
	            .when('/project/:id/edit', {
	                templateUrl : 'project/views/editproject.html',
	                controller  : 'editProjectController'
	            })

	            // Project Audit
	            .when('/project/:id/audit', {
	                templateUrl : 'project/views/audit.html',
	                controller  : 'auditController'
	            })

				// Edit Member Form
				.when('/project/:projectId/member/:memberId', {
	                templateUrl : 'project/views/editmember.html',
	                controller  : 'editMemberController'
				})

				// New Member Form
				.when('/project/:id/newmember', {
	                templateUrl : 'project/views/newmember.html',
	                controller  : 'newMemberController'
				})

				// New User Registration Form
				.when('/register', {
	                templateUrl : 'views/register.html',
	                controller  : 'registerController'
				})

				// Email verification
				.when('/verify/:code', {
	                templateUrl : 'views/verify.html',
	                controller  : 'verifyController'
				})

				// Invite code verification
				.when('/invite/:code', {
	                templateUrl : 'views/invite.html',
	                controller  : 'inviteController'
				})

				.otherwise({
					redirectTo: '/'
				})

				;
	    })

;

<commit_msg>Added new resources and routes<commit_after>'use strict';

var ords = angular.module('ords',['ngRoute', 'ngResource', 'angular-growl', 'ngMessages', 'angularUtils.directives.dirPagination', 'gettext'])

	//
	// Setup the gettext() function
	//
	.run(function (gettextCatalog) {
	    gettextCatalog.setCurrentLanguage('en');
		//gettextCatalog.debug = true;
		//gettextCatalog.showTranslatedMarkers = true;
	})

	//
	// Project REST Resources
	//
	.factory('Project', function( $resource ) {
		return $resource(
			'/api/1.0/project/:id/',
			null,
			{'update': { method:'PUT' }}
		)
	})

	.factory('Statistics', function( $resource ) {
		return $resource('/api/1.0/statistics')
	})

	.factory('Group', function( $resource ) {
		return $resource('/api/1.0/group/group/:id/')
	})

	.factory('Databases', function( $resource ) {
		return $resource('/api/1.0/group/group/:id/database')
	})

	.factory('ProjectDatabase', function( $resource ) {
		return $resource('/api/1.0/project/:id/database/:databaseId')
	})

	.factory('Audit', function( $resource ) {
		return $resource('/api/1.0/audit/project/:id')
	})

	.factory('Invitation', function( $resource ) {
		return $resource('/api/1.0/project/:id/invitation/:inviteId')
	})

	.factory('User', function( $resource ) {
		return $resource(
			'/api/1.0/user/:id',
			null,
			{
				'lookup': { method: 'GET' },
			 	'update': { method: 'PUT' }
		 	}
		);
	})

	.factory('Member', function( $resource, User ) {

		var Member =  $resource('/api/1.0/project/:id/role/:roleid', null, {'update': { method:'PUT' }});

		Member.prototype.name = "";

		//
		// The "name" property of a Member is held by the User resource
		// so we have to load that asynchronously from the REST API
		//
		Member.prototype.getName = function () {

			if (this.name !== "") return this.name;
			var that = this;
			User.lookup( {name: this.principalName}, function(user){
				that.name = user.name;
			}
			);
			this.name = "loading ...";
		};

		return Member;
	})

	//
	// A "really?" handler to confirm actions
	// Usage: Add attributes: ng-really-message="Are you sure"? ng-really-click="takeAction()" function
	// See: https://gist.github.com/asafge/7430497
	//
	.directive('ngReallyClick', [function() {
	    return {
	        restrict: 'A',
	        link: function(scope, element, attrs) {
	            element.bind('click', function() {
	                var message = attrs.ngReallyMessage;
	                if (message && confirm(message)) {
	                    scope.$apply(attrs.ngReallyClick);
	                }
	            });
	        }
	    }
	}])

	//
	// Service that checks if user is currently authenticated
	//
	.factory('AuthService', function ($rootScope, $location, $routeParams, User, Project){
		var svc = {};
		svc.check = function(){
			if ($rootScope.loggedIn !== "yes"){
				$rootScope.user = User.get(
					 function successCallback() {
						$rootScope.loggedIn="yes"
						 if ($location.path() === "/"){

			 	 			//
			 	 			// Load initial projects
			 	 			//
			 	 			Project.query({}, function(data){
			 	 				$rootScope.projects = data;
			 	 			});
			 				$location.path("/projects");
						 }
					 },
					 function errorCallback(response) {

						 //
						 // If we have a 401, we aren't logged in...
						 //
						 if (response.status === 401){
							 $rootScope.loggedIn="no";
							 $location.path("/");
						 }

						 //
						 // If we have a 404, we're logged in, but haven't registered yet
						 //
						 if (response.status === 404){
							 $rootScope.loggedIn="no";
							 if (!$routeParams.code){
 							 	$location.path("/register");
							 }
						 }
					 }
				);
			} else {
				//
				// If we are already logged in, bypass the front page and
				// view the projects page
				//
				if ($location.path() === "/"){
					$location.path("/projects");
				}
			}
		};
		return svc;
	})

	//
	// Configure alerts
	//
	.config(['growlProvider', function(growlProvider) {
	  growlProvider.globalTimeToLive(10000);
	  growlProvider.globalDisableCountDown(true);
	}])

	//
	// configure  routes
	//
	.config(function($routeProvider) {
	        $routeProvider

	            // Home page
	            .when('/projects', {
	                templateUrl : 'project/views/home.html',
	                controller  : 'projectsController'
	            })

	            // Search results
	            .when('/search', {
	                templateUrl : 'views/searchresults.html',
	                controller  : 'searchController'
	            })

	            // Home page for login
	            .when('/', {
	                templateUrl : 'views/home.html',
	                controller  : 'mainController'
	            })

	            // User page
	            .when('/profile', {
	                templateUrl : 'views/profile.html',
	                controller  : 'profileController'
	            })

	            // Project Details
	            .when('/project/:id', {
	                templateUrl : 'project/views/project.html',
	                controller  : 'projectController'
	            })

				// New Project Form
				.when('/newproject', {
	                templateUrl : 'project/views/newproject.html',
	                controller  : 'newProjectController'
				})

	            // Edit Project Details
	            .when('/project/:id/edit', {
	                templateUrl : 'project/views/editproject.html',
	                controller  : 'editProjectController'
	            })

	            // Project Audit
	            .when('/project/:id/audit', {
	                templateUrl : 'project/views/audit.html',
	                controller  : 'auditController'
	            })

				// Edit Member Form
				.when('/project/:projectId/member/:memberId', {
	                templateUrl : 'project/views/editmember.html',
	                controller  : 'editMemberController'
				})

				// New Member Form
				.when('/project/:id/newmember', {
	                templateUrl : 'project/views/newmember.html',
	                controller  : 'newMemberController'
				})

				// New Database
				.when('/project/:id/newdatabase', {
	                templateUrl : 'project/views/newdatabase.html',
	                controller  : 'newDatabaseController'
				})

				//  Database Page
				.when('/project/:id/:databaseId', {
	                templateUrl : 'database/views/database.html',
	                controller  : 'databaseController'
				})

				// New User Registration Form
				.when('/register', {
	                templateUrl : 'views/register.html',
	                controller  : 'registerController'
				})

				// Email verification
				.when('/verify/:code', {
	                templateUrl : 'views/verify.html',
	                controller  : 'verifyController'
				})

				// Invite code verification
				.when('/invite/:code', {
	                templateUrl : 'views/invite.html',
	                controller  : 'inviteController'
				})

				.otherwise({
					redirectTo: '/'
				})

				;
	    })

;

<|endoftext|>


<commit_before>import $ from 'jquery'
import { version } from '../package.json'

const uploadcare = {
  version,
  jQuery: $,

  utils: {
    abilities: {
    //     fileAPI
    //     sendFileAPI
    //     dragAndDrop
    //     canvas
    //     fileDragAndDrop
    //     iOSVersion
    //     Blob
    //     URL
    //     FileReader
    },
    //   Collection
    //   UniqCollection
    //   CollectionOfPromises
    //   imageLoader
    //   videoLoader
    //   log
    //   debug
    //   warn
    //   warnOnce
    //   commonWarning
    //   registerMessage
    //   unregisterMessage
    //   unique
    //   defer
    //   gcd
    //   once
    //   wrapToPromise
    //   then
    //   bindAll
    //   upperCase
    //   publicCallbacks
    //   uuid
    //   splitUrlRegex:
    //   uuidRegex:
    //   groupIdRegex:
    //   cdnUrlRegex:
    //   splitCdnUrl
    //   escapeRegExp
    //   globRegexp
    //   normalizeUrl
    //   fitText
    //   fitSizeInCdnLimit
    //   fitSize
    //   applyCropCoordsToInfo
    //   fileInput
    //   fileSelectDialog
    //   fileSizeLabels
    //   readableFileSize
    //   ajaxDefaults:
    //     dataType
    //     crossDomain
    //     cache
    //   jsonp
    //   canvasToBlob
    //   taskRunner
    //   fixedPipe
    //   isFile
    //   valueToFile

    image: {
    //     shrinkFile
    //     shrinkImage
    //     drawFileToCanvas
    //     readJpegChunks
    //     replaceJpegChunk
    //     getExif
    //     parseExifOrientation
    //     hasTransparency
    }

    //   pusher:
    //     getPusher
    //   isFileGroup
    //   valueToGroup
    //   isFileGroupsEqual

  },

  settings: {
  //   globals
  //   common
  //   build
  //   waitForSettings
  //   CssCollector
  },

  tabsCss: {
  //   urls
  //   styles
  },

  locale: {
  //   translations
  //   pluralize
  //   rebuild
  //   t
  },

  templates: {
  //   JST:
  //     dialog
  //     dialog__panel
  //     icons
  //     progress__text
  //     styles
  //     tab-camera-capture
  //     tab-camera
  //     tab-file
  //     tab-preview-error
  //     tab-preview-image
  //     tab-preview-multiple-file
  //     tab-preview-multiple
  //     tab-preview-regular
  //     tab-preview-unknown
  //     tab-preview-video
  //     tab-url
  //     widget-button
  //     widget-file-name
  //     widget
  //   tpl
  },

  crop: {
    //   CropWidget
  },

  files: {
    //   BaseFile
    //   ObjectFile
    //   InputFile
    //   UrlFile
    //   UploadedFile
    //   ReadyFile
    //   FileGroup
    //   SavedFileGroup
  },

  // Pusher
  // FileGroup
  // loadFileGroup
  // fileFrom
  // filesFrom

  dragdrop: {
    //   support
    //   uploadDrop
    //   receiveDrop
    //   watchDragging
  },

  ui: {
    progress: {
    //     Circle
    //     BaseRenderer
    //     TextRenderer
    //     CanvasRenderer
    }
  },

  widget: {
    tabs: {
    //     FileTab
    //     UrlTab
    //     CameraTab
    //     RemoteTab
    //     BasePreviewTab
    //     PreviewTab
    //     PreviewTabMultiple
    }
  //   Template
  //   BaseWidget
  //   Widget
  //   MultipleWidget
  },

  // isDialogOpened
  // closeDialog
  // openDialog
  // openPreviewDialog
  // openPanel
  // registerTab
  // initialize
  // SingleWidget
  // MultipleWidget
  // Widget
  // start

  __exports: {
    plugin: function (fn) {
      return fn(uploadcare)
    },
    version,
    jQuery: $
  },

  namespace: (path, fn) => {
    let target = uploadcare

    if (path) {
      const ref = path.split('.')

      for (let i = 0, len = ref.length; i < len; i++) {
        const part = ref[i]

        if (!target[part]) {
          target[part] = {}
        }

        target = target[part]
      }
    }

    return fn(target)
  },

  expose: (key, value) => {
    const parts = key.split('.')
    const last = parts.pop()
    let target = uploadcare.__exports
    let source = uploadcare

    for (let i = 0, len = parts.length; i < len; i++) {
      const part = parts[i]
      if (!target[part]) {
        target[part] = {}
      }

      target = target[part]
      source = source != null ? source[part] : undefined
    }

    target[last] = value || source[last]
  }
}

export default uploadcare
<commit_msg>add pusher to namespace<commit_after>import $ from 'jquery'
import { version } from '../package.json'

import { getPusher } from './utils/pusher'

const uploadcare = {
  version,
  jQuery: $,

  utils: {
    abilities: {
    //     fileAPI
    //     sendFileAPI
    //     dragAndDrop
    //     canvas
    //     fileDragAndDrop
    //     iOSVersion
    //     Blob
    //     URL
    //     FileReader
    },
    //   Collection
    //   UniqCollection
    //   CollectionOfPromises
    //   imageLoader
    //   videoLoader
    //   log
    //   debug
    //   warn
    //   warnOnce
    //   commonWarning
    //   registerMessage
    //   unregisterMessage
    //   unique
    //   defer
    //   gcd
    //   once
    //   wrapToPromise
    //   then
    //   bindAll
    //   upperCase
    //   publicCallbacks
    //   uuid
    //   splitUrlRegex:
    //   uuidRegex:
    //   groupIdRegex:
    //   cdnUrlRegex:
    //   splitCdnUrl
    //   escapeRegExp
    //   globRegexp
    //   normalizeUrl
    //   fitText
    //   fitSizeInCdnLimit
    //   fitSize
    //   applyCropCoordsToInfo
    //   fileInput
    //   fileSelectDialog
    //   fileSizeLabels
    //   readableFileSize
    //   ajaxDefaults:
    //     dataType
    //     crossDomain
    //     cache
    //   jsonp
    //   canvasToBlob
    //   taskRunner
    //   fixedPipe
    //   isFile
    //   valueToFile

    image: {
    //     shrinkFile
    //     shrinkImage
    //     drawFileToCanvas
    //     readJpegChunks
    //     replaceJpegChunk
    //     getExif
    //     parseExifOrientation
    //     hasTransparency
    },

    pusher: {
      getPusher
    }

    //   isFileGroup
    //   valueToGroup
    //   isFileGroupsEqual

  },

  settings: {
  //   globals
  //   common
  //   build
  //   waitForSettings
  //   CssCollector
  },

  tabsCss: {
  //   urls
  //   styles
  },

  locale: {
  //   translations
  //   pluralize
  //   rebuild
  //   t
  },

  templates: {
  //   JST:
  //     dialog
  //     dialog__panel
  //     icons
  //     progress__text
  //     styles
  //     tab-camera-capture
  //     tab-camera
  //     tab-file
  //     tab-preview-error
  //     tab-preview-image
  //     tab-preview-multiple-file
  //     tab-preview-multiple
  //     tab-preview-regular
  //     tab-preview-unknown
  //     tab-preview-video
  //     tab-url
  //     widget-button
  //     widget-file-name
  //     widget
  //   tpl
  },

  crop: {
    //   CropWidget
  },

  files: {
    //   BaseFile
    //   ObjectFile
    //   InputFile
    //   UrlFile
    //   UploadedFile
    //   ReadyFile
    //   FileGroup
    //   SavedFileGroup
  },

  // Pusher
  // FileGroup
  // loadFileGroup
  // fileFrom
  // filesFrom

  dragdrop: {
    //   support
    //   uploadDrop
    //   receiveDrop
    //   watchDragging
  },

  ui: {
    progress: {
    //     Circle
    //     BaseRenderer
    //     TextRenderer
    //     CanvasRenderer
    }
  },

  widget: {
    tabs: {
    //     FileTab
    //     UrlTab
    //     CameraTab
    //     RemoteTab
    //     BasePreviewTab
    //     PreviewTab
    //     PreviewTabMultiple
    }
  //   Template
  //   BaseWidget
  //   Widget
  //   MultipleWidget
  },

  // isDialogOpened
  // closeDialog
  // openDialog
  // openPreviewDialog
  // openPanel
  // registerTab
  // initialize
  // SingleWidget
  // MultipleWidget
  // Widget
  // start

  __exports: {
    plugin: function (fn) {
      return fn(uploadcare)
    },
    version,
    jQuery: $
  },

  namespace: (path, fn) => {
    let target = uploadcare

    if (path) {
      const ref = path.split('.')

      for (let i = 0, len = ref.length; i < len; i++) {
        const part = ref[i]

        if (!target[part]) {
          target[part] = {}
        }

        target = target[part]
      }
    }

    return fn(target)
  },

  expose: (key, value) => {
    const parts = key.split('.')
    const last = parts.pop()
    let target = uploadcare.__exports
    let source = uploadcare

    for (let i = 0, len = parts.length; i < len; i++) {
      const part = parts[i]
      if (!target[part]) {
        target[part] = {}
      }

      target = target[part]
      source = source != null ? source[part] : undefined
    }

    target[last] = value || source[last]
  }
}

export default uploadcare
<|endoftext|>


<commit_before>import Popper from 'popper.js'
import { NAME_DROPDOWN } from '../constants/components'
import { CODE_DOWN, CODE_ENTER, CODE_ESC, CODE_SPACE, CODE_UP } from '../constants/key-codes'
import {
  PLACEMENT_TOP_START,
  PLACEMENT_TOP_END,
  PLACEMENT_BOTTOM_START,
  PLACEMENT_BOTTOM_END,
  PLACEMENT_RIGHT_START,
  PLACEMENT_LEFT_START
} from '../constants/popper'
import { BvEvent } from '../utils/bv-event.class'
import { attemptFocus, closest, contains, isVisible, requestAF, selectAll } from '../utils/dom'
import { stopEvent } from '../utils/events'
import { isNull } from '../utils/inspect'
import { mergeDeep } from '../utils/object'
import { HTMLElement } from '../utils/safe-types'
import { warn } from '../utils/warn'
import clickOutMixin from './click-out'
import focusInMixin from './focus-in'
import idMixin from './id'

// --- Constants ---

// Root dropdown event names
const ROOT_EVENT_PREFIX = 'bv::dropdown::'
const ROOT_EVENT_SHOWN = `${ROOT_EVENT_PREFIX}shown`
const ROOT_EVENT_HIDDEN = `${ROOT_EVENT_PREFIX}hidden`

// CSS selectors
const SELECTOR_FORM_CHILD = '.dropdown form'
const SELECTOR_ITEM = ['.dropdown-item', '.b-dropdown-form']
  .map(selector => `${selector}:not(.disabled):not([disabled])`)
  .join(', ')

// --- Utility methods ---

// Return an array of visible items
const filterVisibles = els => (els || []).filter(isVisible)

// --- Props ---

export const commonProps = {
  dropup: {
    // place on top if possible
    type: Boolean,
    default: false
  },
  dropright: {
    // place right if possible
    type: Boolean,
    default: false
  },
  dropleft: {
    // place left if possible
    type: Boolean,
    default: false
  },
  right: {
    // Right align menu (default is left align)
    type: Boolean,
    default: false
  },
  offset: {
    // Number of pixels to offset menu, or a CSS unit value (i.e. `1px`, `1rem`, etc.)
    type: [Number, String],
    default: 0
  },
  noFlip: {
    // Disable auto-flipping of menu from bottom <=> top
    type: Boolean,
    default: false
  },
  popperOpts: {
    type: Object,
    default: () => {}
  },
  boundary: {
    // String: `scrollParent`, `window` or `viewport`
    // HTMLElement: HTML Element reference
    type: [String, HTMLElement],
    default: 'scrollParent'
  }
}

// @vue/component
export default {
  mixins: [idMixin, clickOutMixin, focusInMixin],
  provide() {
    return { bvDropdown: this }
  },
  inject: {
    bvNavbar: { default: null }
  },
  props: {
    ...commonProps,
    disabled: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      visible: false,
      visibleChangePrevented: false
    }
  },
  computed: {
    inNavbar() {
      return !isNull(this.bvNavbar)
    },
    toggler() {
      const toggle = this.$refs.toggle
      return toggle ? toggle.$el || toggle : null
    },
    directionClass() {
      if (this.dropup) {
        return 'dropup'
      } else if (this.dropright) {
        return 'dropright'
      } else if (this.dropleft) {
        return 'dropleft'
      }
      return ''
    },
    boundaryClass() {
      // Position `static` is needed to allow menu to "breakout" of the `scrollParent`
      // boundaries when boundary is anything other than `scrollParent`
      // See: https://github.com/twbs/bootstrap/issues/24251#issuecomment-341413786
      return this.boundary !== 'scrollParent' && !this.inNavbar ? 'position-static' : ''
    }
  },
  watch: {
    visible(newValue, oldValue) {
      if (this.visibleChangePrevented) {
        this.visibleChangePrevented = false
        return
      }

      if (newValue !== oldValue) {
        const evtName = newValue ? 'show' : 'hide'
        const bvEvt = new BvEvent(evtName, {
          cancelable: true,
          vueTarget: this,
          target: this.$refs.menu,
          relatedTarget: null,
          componentId: this.safeId ? this.safeId() : this.id || null
        })
        this.emitEvent(bvEvt)
        if (bvEvt.defaultPrevented) {
          // Reset value and exit if canceled
          this.visibleChangePrevented = true
          this.visible = oldValue
          // Just in case a child element triggered `this.hide(true)`
          this.$off('hidden', this.focusToggler)
          return
        }
        if (evtName === 'show') {
          this.showMenu()
        } else {
          this.hideMenu()
        }
      }
    },
    disabled(newValue, oldValue) {
      if (newValue !== oldValue && newValue && this.visible) {
        // Hide dropdown if disabled changes to true
        this.visible = false
      }
    }
  },
  created() {
    // Create private non-reactive props
    this.$_popper = null
  },
  /* istanbul ignore next */
  deactivated() /* istanbul ignore next: not easy to test */ {
    // In case we are inside a `<keep-alive>`
    this.visible = false
    this.whileOpenListen(false)
    this.destroyPopper()
  },
  beforeDestroy() {
    this.visible = false
    this.whileOpenListen(false)
    this.destroyPopper()
  },
  methods: {
    // Event emitter
    emitEvent(bvEvt) {
      const { type } = bvEvt
      this.$emit(type, bvEvt)
      this.$root.$emit(`${ROOT_EVENT_PREFIX}${type}`, bvEvt)
    },
    showMenu() {
      if (this.disabled) {
        /* istanbul ignore next */
        return
      }

      // Only instantiate Popper.js when dropdown is not in `<b-navbar>`
      if (!this.inNavbar) {
        if (typeof Popper === 'undefined') {
          /* istanbul ignore next */
          warn('Popper.js not found. Falling back to CSS positioning', NAME_DROPDOWN)
        } else {
          // For dropup with alignment we use the parent element as popper container
          let el = (this.dropup && this.right) || this.split ? this.$el : this.$refs.toggle
          // Make sure we have a reference to an element, not a component!
          el = el.$el || el
          // Instantiate Popper.js
          this.createPopper(el)
        }
      }

      // Ensure other menus are closed
      this.$root.$emit(ROOT_EVENT_SHOWN, this)

      // Enable listeners
      this.whileOpenListen(true)

      // Wrap in `$nextTick()` to ensure menu is fully rendered/shown
      this.$nextTick(() => {
        // Focus on the menu container on show
        this.focusMenu()
        // Emit the shown event
        this.$emit('shown')
      })
    },
    hideMenu() {
      this.whileOpenListen(false)
      this.$root.$emit(ROOT_EVENT_HIDDEN, this)
      this.$emit('hidden')
      this.destroyPopper()
    },
    createPopper(element) {
      this.destroyPopper()
      this.$_popper = new Popper(element, this.$refs.menu, this.getPopperConfig())
    },
    // Ensure popper event listeners are removed cleanly
    destroyPopper() {
      this.$_popper && this.$_popper.destroy()
      this.$_popper = null
    },
    // Instructs popper to re-computes the dropdown position
    // useful if the content changes size
    updatePopper() /* istanbul ignore next: not easy to test */ {
      try {
        this.$_popper.scheduleUpdate()
      } catch {}
    },
    getPopperConfig() {
      let placement = PLACEMENT_BOTTOM_START
      if (this.dropup) {
        placement = this.right ? PLACEMENT_TOP_END : PLACEMENT_TOP_START
      } else if (this.dropright) {
        placement = PLACEMENT_RIGHT_START
      } else if (this.dropleft) {
        placement = PLACEMENT_LEFT_START
      } else if (this.right) {
        placement = PLACEMENT_BOTTOM_END
      }
      const popperConfig = {
        placement,
        modifiers: {
          offset: { offset: this.offset || 0 },
          flip: { enabled: !this.noFlip }
        }
      }
      const boundariesElement = this.boundary
      if (boundariesElement) {
        popperConfig.modifiers.preventOverflow = { boundariesElement }
      }
      return mergeDeep(popperConfig, this.popperOpts || {})
    },
    // Turn listeners on/off while open
    whileOpenListen(isOpen) {
      // Hide the dropdown when clicked outside
      this.listenForClickOut = isOpen
      // Hide the dropdown when it loses focus
      this.listenForFocusIn = isOpen
      // Hide the dropdown when another dropdown is opened
      const method = isOpen ? '$on' : '$off'
      this.$root[method](ROOT_EVENT_SHOWN, this.rootCloseListener)
    },
    rootCloseListener(vm) {
      if (vm !== this) {
        this.visible = false
      }
    },
    show() {
      // Public method to show dropdown
      if (this.disabled) {
        return
      }
      // Wrap in a `requestAF()` to allow any previous
      // click handling to occur first
      requestAF(() => {
        this.visible = true
      })
    },
    hide(refocus = false) {
      // Public method to hide dropdown
      if (this.disabled) {
        /* istanbul ignore next */
        return
      }
      this.visible = false
      if (refocus) {
        // Child element is closing the dropdown on click
        this.$once('hidden', this.focusToggler)
      }
    },
    // Called only by a button that toggles the menu
    toggle(evt) {
      evt = evt || {}
      // Early exit when not a click event or ENTER, SPACE or DOWN were pressed
      const { type, keyCode } = evt
      if (
        type !== 'click' &&
        !(type === 'keydown' && [CODE_ENTER, CODE_SPACE, CODE_DOWN].indexOf(keyCode) !== -1)
      ) {
        /* istanbul ignore next */
        return
      }
      /* istanbul ignore next */
      if (this.disabled) {
        this.visible = false
        return
      }
      this.$emit('toggle', evt)
      stopEvent(evt)
      // Toggle visibility
      if (this.visible) {
        this.hide(true)
      } else {
        this.show()
      }
    },
    // Mousedown handler for the toggle
    /* istanbul ignore next */
    onMousedown(evt) /* istanbul ignore next */ {
      // We prevent the 'mousedown' event for the toggle to stop the
      // 'focusin' event from being fired
      // The event would otherwise be picked up by the global 'focusin'
      // listener and there is no cross-browser solution to detect it
      // relates to the toggle click
      // The 'click' event will still be fired and we handle closing
      // other dropdowns there too
      // See https://github.com/bootstrap-vue/bootstrap-vue/issues/4328
      stopEvent(evt, { propagation: false })
    },
    // Called from dropdown menu context
    onKeydown(evt) {
      const { keyCode } = evt
      if (keyCode === CODE_ESC) {
        // Close on ESC
        this.onEsc(evt)
      } else if (keyCode === CODE_DOWN) {
        // Down Arrow
        this.focusNext(evt, false)
      } else if (keyCode === CODE_UP) {
        // Up Arrow
        this.focusNext(evt, true)
      }
    },
    // If user presses ESC, close the menu
    onEsc(evt) {
      if (this.visible) {
        this.visible = false
        stopEvent(evt)
        // Return focus to original trigger button
        this.$once('hidden', this.focusToggler)
      }
    },
    // Called only in split button mode, for the split button
    onSplitClick(evt) {
      /* istanbul ignore next */
      if (this.disabled) {
        this.visible = false
        return
      }
      this.$emit('click', evt)
    },
    // Shared hide handler between click-out and focus-in events
    hideHandler(evt) {
      const { target } = evt
      if (this.visible && !contains(this.$refs.menu, target) && !contains(this.toggler, target)) {
        this.hide()
      }
    },
    // Document click-out listener
    clickOutHandler(evt) {
      this.hideHandler(evt)
    },
    // Document focus-in listener
    focusInHandler(evt) {
      this.hideHandler(evt)
    },
    // Keyboard nav
    focusNext(evt, up) {
      // Ignore key up/down on form elements
      const { target } = evt
      if (!this.visible || (evt && closest(SELECTOR_FORM_CHILD, target))) {
        /* istanbul ignore next: should never happen */
        return
      }
      stopEvent(evt)
      this.$nextTick(() => {
        const items = this.getItems()
        if (items.length < 1) {
          /* istanbul ignore next: should never happen */
          return
        }
        let index = items.indexOf(target)
        if (up && index > 0) {
          index--
        } else if (!up && index < items.length - 1) {
          index++
        }
        if (index < 0) {
          /* istanbul ignore next: should never happen */
          index = 0
        }
        this.focusItem(index, items)
      })
    },
    focusItem(index, items) {
      const el = items.find((el, i) => i === index)
      attemptFocus(el)
    },
    getItems() {
      // Get all items
      return filterVisibles(selectAll(SELECTOR_ITEM, this.$refs.menu))
    },
    focusMenu() {
      attemptFocus(this.$refs.menu)
    },
    focusToggler() {
      this.$nextTick(() => {
        attemptFocus(this.toggler)
      })
    }
  }
}
<commit_msg>fix(b-dropdown): click handling on close (closes #5982) (#6009)<commit_after>import Popper from 'popper.js'
import { NAME_DROPDOWN } from '../constants/components'
import { CODE_DOWN, CODE_ENTER, CODE_ESC, CODE_SPACE, CODE_UP } from '../constants/key-codes'
import {
  PLACEMENT_TOP_START,
  PLACEMENT_TOP_END,
  PLACEMENT_BOTTOM_START,
  PLACEMENT_BOTTOM_END,
  PLACEMENT_RIGHT_START,
  PLACEMENT_LEFT_START
} from '../constants/popper'
import { BvEvent } from '../utils/bv-event.class'
import { attemptFocus, closest, contains, isVisible, requestAF, selectAll } from '../utils/dom'
import { stopEvent } from '../utils/events'
import { isNull } from '../utils/inspect'
import { mergeDeep } from '../utils/object'
import { HTMLElement } from '../utils/safe-types'
import { warn } from '../utils/warn'
import clickOutMixin from './click-out'
import focusInMixin from './focus-in'
import idMixin from './id'

// --- Constants ---

// Root dropdown event names
const ROOT_EVENT_PREFIX = 'bv::dropdown::'
const ROOT_EVENT_SHOWN = `${ROOT_EVENT_PREFIX}shown`
const ROOT_EVENT_HIDDEN = `${ROOT_EVENT_PREFIX}hidden`

// CSS selectors
const SELECTOR_FORM_CHILD = '.dropdown form'
const SELECTOR_ITEM = ['.dropdown-item', '.b-dropdown-form']
  .map(selector => `${selector}:not(.disabled):not([disabled])`)
  .join(', ')

// --- Utility methods ---

// Return an array of visible items
const filterVisibles = els => (els || []).filter(isVisible)

// --- Props ---

export const commonProps = {
  dropup: {
    // place on top if possible
    type: Boolean,
    default: false
  },
  dropright: {
    // place right if possible
    type: Boolean,
    default: false
  },
  dropleft: {
    // place left if possible
    type: Boolean,
    default: false
  },
  right: {
    // Right align menu (default is left align)
    type: Boolean,
    default: false
  },
  offset: {
    // Number of pixels to offset menu, or a CSS unit value (i.e. `1px`, `1rem`, etc.)
    type: [Number, String],
    default: 0
  },
  noFlip: {
    // Disable auto-flipping of menu from bottom <=> top
    type: Boolean,
    default: false
  },
  popperOpts: {
    type: Object,
    default: () => {}
  },
  boundary: {
    // String: `scrollParent`, `window` or `viewport`
    // HTMLElement: HTML Element reference
    type: [String, HTMLElement],
    default: 'scrollParent'
  }
}

// @vue/component
export default {
  mixins: [idMixin, clickOutMixin, focusInMixin],
  provide() {
    return { bvDropdown: this }
  },
  inject: {
    bvNavbar: { default: null }
  },
  props: {
    ...commonProps,
    disabled: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      visible: false,
      visibleChangePrevented: false
    }
  },
  computed: {
    inNavbar() {
      return !isNull(this.bvNavbar)
    },
    toggler() {
      const toggle = this.$refs.toggle
      return toggle ? toggle.$el || toggle : null
    },
    directionClass() {
      if (this.dropup) {
        return 'dropup'
      } else if (this.dropright) {
        return 'dropright'
      } else if (this.dropleft) {
        return 'dropleft'
      }
      return ''
    },
    boundaryClass() {
      // Position `static` is needed to allow menu to "breakout" of the `scrollParent`
      // boundaries when boundary is anything other than `scrollParent`
      // See: https://github.com/twbs/bootstrap/issues/24251#issuecomment-341413786
      return this.boundary !== 'scrollParent' && !this.inNavbar ? 'position-static' : ''
    }
  },
  watch: {
    visible(newValue, oldValue) {
      if (this.visibleChangePrevented) {
        this.visibleChangePrevented = false
        return
      }

      if (newValue !== oldValue) {
        const evtName = newValue ? 'show' : 'hide'
        const bvEvt = new BvEvent(evtName, {
          cancelable: true,
          vueTarget: this,
          target: this.$refs.menu,
          relatedTarget: null,
          componentId: this.safeId ? this.safeId() : this.id || null
        })
        this.emitEvent(bvEvt)
        if (bvEvt.defaultPrevented) {
          // Reset value and exit if canceled
          this.visibleChangePrevented = true
          this.visible = oldValue
          // Just in case a child element triggered `this.hide(true)`
          this.$off('hidden', this.focusToggler)
          return
        }
        if (evtName === 'show') {
          this.showMenu()
        } else {
          this.hideMenu()
        }
      }
    },
    disabled(newValue, oldValue) {
      if (newValue !== oldValue && newValue && this.visible) {
        // Hide dropdown if disabled changes to true
        this.visible = false
      }
    }
  },
  created() {
    // Create private non-reactive props
    this.$_popper = null
    this.$_hideTimeout = null
  },
  /* istanbul ignore next */
  deactivated() /* istanbul ignore next: not easy to test */ {
    // In case we are inside a `<keep-alive>`
    this.visible = false
    this.whileOpenListen(false)
    this.destroyPopper()
  },
  beforeDestroy() {
    this.visible = false
    this.whileOpenListen(false)
    this.destroyPopper()
    this.clearHideTimeout()
  },
  methods: {
    // Event emitter
    emitEvent(bvEvt) {
      const { type } = bvEvt
      this.$emit(type, bvEvt)
      this.$root.$emit(`${ROOT_EVENT_PREFIX}${type}`, bvEvt)
    },
    showMenu() {
      if (this.disabled) {
        /* istanbul ignore next */
        return
      }

      // Only instantiate Popper.js when dropdown is not in `<b-navbar>`
      if (!this.inNavbar) {
        if (typeof Popper === 'undefined') {
          /* istanbul ignore next */
          warn('Popper.js not found. Falling back to CSS positioning', NAME_DROPDOWN)
        } else {
          // For dropup with alignment we use the parent element as popper container
          let el = (this.dropup && this.right) || this.split ? this.$el : this.$refs.toggle
          // Make sure we have a reference to an element, not a component!
          el = el.$el || el
          // Instantiate Popper.js
          this.createPopper(el)
        }
      }

      // Ensure other menus are closed
      this.$root.$emit(ROOT_EVENT_SHOWN, this)

      // Enable listeners
      this.whileOpenListen(true)

      // Wrap in `$nextTick()` to ensure menu is fully rendered/shown
      this.$nextTick(() => {
        // Focus on the menu container on show
        this.focusMenu()
        // Emit the shown event
        this.$emit('shown')
      })
    },
    hideMenu() {
      this.whileOpenListen(false)
      this.$root.$emit(ROOT_EVENT_HIDDEN, this)
      this.$emit('hidden')
      this.destroyPopper()
    },
    createPopper(element) {
      this.destroyPopper()
      this.$_popper = new Popper(element, this.$refs.menu, this.getPopperConfig())
    },
    // Ensure popper event listeners are removed cleanly
    destroyPopper() {
      this.$_popper && this.$_popper.destroy()
      this.$_popper = null
    },
    // Instructs popper to re-computes the dropdown position
    // useful if the content changes size
    updatePopper() /* istanbul ignore next: not easy to test */ {
      try {
        this.$_popper.scheduleUpdate()
      } catch {}
    },
    clearHideTimeout() {
      clearTimeout(this.$_hideTimeout)
      this.$_hideTimeout = null
    },
    getPopperConfig() {
      let placement = PLACEMENT_BOTTOM_START
      if (this.dropup) {
        placement = this.right ? PLACEMENT_TOP_END : PLACEMENT_TOP_START
      } else if (this.dropright) {
        placement = PLACEMENT_RIGHT_START
      } else if (this.dropleft) {
        placement = PLACEMENT_LEFT_START
      } else if (this.right) {
        placement = PLACEMENT_BOTTOM_END
      }
      const popperConfig = {
        placement,
        modifiers: {
          offset: { offset: this.offset || 0 },
          flip: { enabled: !this.noFlip }
        }
      }
      const boundariesElement = this.boundary
      if (boundariesElement) {
        popperConfig.modifiers.preventOverflow = { boundariesElement }
      }
      return mergeDeep(popperConfig, this.popperOpts || {})
    },
    // Turn listeners on/off while open
    whileOpenListen(isOpen) {
      // Hide the dropdown when clicked outside
      this.listenForClickOut = isOpen
      // Hide the dropdown when it loses focus
      this.listenForFocusIn = isOpen
      // Hide the dropdown when another dropdown is opened
      const method = isOpen ? '$on' : '$off'
      this.$root[method](ROOT_EVENT_SHOWN, this.rootCloseListener)
    },
    rootCloseListener(vm) {
      if (vm !== this) {
        this.visible = false
      }
    },
    // Public method to show dropdown
    show() {
      if (this.disabled) {
        return
      }
      // Wrap in a `requestAF()` to allow any previous
      // click handling to occur first
      requestAF(() => {
        this.visible = true
      })
    },
    // Public method to hide dropdown
    hide(refocus = false) {
      /* istanbul ignore next */
      if (this.disabled) {
        return
      }
      this.visible = false
      if (refocus) {
        // Child element is closing the dropdown on click
        this.$once('hidden', this.focusToggler)
      }
    },
    // Called only by a button that toggles the menu
    toggle(evt) {
      evt = evt || {}
      // Early exit when not a click event or ENTER, SPACE or DOWN were pressed
      const { type, keyCode } = evt
      if (
        type !== 'click' &&
        !(type === 'keydown' && [CODE_ENTER, CODE_SPACE, CODE_DOWN].indexOf(keyCode) !== -1)
      ) {
        /* istanbul ignore next */
        return
      }
      /* istanbul ignore next */
      if (this.disabled) {
        this.visible = false
        return
      }
      this.$emit('toggle', evt)
      stopEvent(evt)
      // Toggle visibility
      if (this.visible) {
        this.hide(true)
      } else {
        this.show()
      }
    },
    // Mousedown handler for the toggle
    /* istanbul ignore next */
    onMousedown(evt) /* istanbul ignore next */ {
      // We prevent the 'mousedown' event for the toggle to stop the
      // 'focusin' event from being fired
      // The event would otherwise be picked up by the global 'focusin'
      // listener and there is no cross-browser solution to detect it
      // relates to the toggle click
      // The 'click' event will still be fired and we handle closing
      // other dropdowns there too
      // See https://github.com/bootstrap-vue/bootstrap-vue/issues/4328
      stopEvent(evt, { propagation: false })
    },
    // Called from dropdown menu context
    onKeydown(evt) {
      const { keyCode } = evt
      if (keyCode === CODE_ESC) {
        // Close on ESC
        this.onEsc(evt)
      } else if (keyCode === CODE_DOWN) {
        // Down Arrow
        this.focusNext(evt, false)
      } else if (keyCode === CODE_UP) {
        // Up Arrow
        this.focusNext(evt, true)
      }
    },
    // If user presses ESC, close the menu
    onEsc(evt) {
      if (this.visible) {
        this.visible = false
        stopEvent(evt)
        // Return focus to original trigger button
        this.$once('hidden', this.focusToggler)
      }
    },
    // Called only in split button mode, for the split button
    onSplitClick(evt) {
      /* istanbul ignore next */
      if (this.disabled) {
        this.visible = false
        return
      }
      this.$emit('click', evt)
    },
    // Shared hide handler between click-out and focus-in events
    hideHandler(evt) {
      const { target } = evt
      if (this.visible && !contains(this.$refs.menu, target) && !contains(this.toggler, target)) {
        this.clearHideTimeout()
        this.$_hideTimeout = setTimeout(() => this.hide(), this.inNavbar ? 300 : 0)
      }
    },
    // Document click-out listener
    clickOutHandler(evt) {
      this.hideHandler(evt)
    },
    // Document focus-in listener
    focusInHandler(evt) {
      this.hideHandler(evt)
    },
    // Keyboard nav
    focusNext(evt, up) {
      // Ignore key up/down on form elements
      const { target } = evt
      if (!this.visible || (evt && closest(SELECTOR_FORM_CHILD, target))) {
        /* istanbul ignore next: should never happen */
        return
      }
      stopEvent(evt)
      this.$nextTick(() => {
        const items = this.getItems()
        if (items.length < 1) {
          /* istanbul ignore next: should never happen */
          return
        }
        let index = items.indexOf(target)
        if (up && index > 0) {
          index--
        } else if (!up && index < items.length - 1) {
          index++
        }
        if (index < 0) {
          /* istanbul ignore next: should never happen */
          index = 0
        }
        this.focusItem(index, items)
      })
    },
    focusItem(index, items) {
      const el = items.find((el, i) => i === index)
      attemptFocus(el)
    },
    getItems() {
      // Get all items
      return filterVisibles(selectAll(SELECTOR_ITEM, this.$refs.menu))
    },
    focusMenu() {
      attemptFocus(this.$refs.menu)
    },
    focusToggler() {
      this.$nextTick(() => {
        attemptFocus(this.toggler)
      })
    }
  }
}
<|endoftext|>