Update XStatic-Angular to 1.5.8.0

Change-Id: I94e5ada8bac3d29513e83c9d7e58c7b73ed154a8
This commit is contained in:
Rob Cresswell 2016-06-28 16:34:09 +01:00
parent 5a30bee80d
commit 984630723e
18 changed files with 13791 additions and 6413 deletions

View File

@ -11,9 +11,9 @@ NAME = __name__.split('.')[-1] # package name (e.g. 'foo' or 'foo_bar')
# please use a all-lowercase valid python
# package name
VERSION = '1.4.10' # version of the packaged files, please use the upstream
VERSION = '1.5.8' # version of the packaged files, please use the upstream
# version number
BUILD = '1' # our package build number, so we can release new builds
BUILD = '0' # our package build number, so we can release new builds
# with fixes for xstatic stuff.
PACKAGE_VERSION = VERSION + '.' + BUILD # version used for PyPi

View File

@ -1,23 +1,9 @@
/**
* @license AngularJS v1.4.10
* (c) 2010-2015 Google, Inc. http://angularjs.org
* @license AngularJS v1.5.8
* (c) 2010-2016 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {'use strict';
/* jshint ignore:start */
var noop = angular.noop;
var copy = angular.copy;
var extend = angular.extend;
var jqLite = angular.element;
var forEach = angular.forEach;
var isArray = angular.isArray;
var isString = angular.isString;
var isObject = angular.isObject;
var isUndefined = angular.isUndefined;
var isDefined = angular.isDefined;
var isFunction = angular.isFunction;
var isElement = angular.isElement;
(function(window, angular) {'use strict';
var ELEMENT_NODE = 1;
var COMMENT_NODE = 8;
@ -43,7 +29,7 @@ var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMA
// Also, the only modern browser that uses vendor prefixes for transitions/keyframes is webkit
// therefore there is no reason to test anymore for other vendor prefixes:
// http://caniuse.com/#search=transition
if (isUndefined(window.ontransitionend) && isDefined(window.onwebkittransitionend)) {
if ((window.ontransitionend === void 0) && (window.onwebkittransitionend !== void 0)) {
CSS_PREFIX = '-webkit-';
TRANSITION_PROP = 'WebkitTransition';
TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend';
@ -52,7 +38,7 @@ if (isUndefined(window.ontransitionend) && isDefined(window.onwebkittransitionen
TRANSITIONEND_EVENT = 'transitionend';
}
if (isUndefined(window.onanimationend) && isDefined(window.onwebkitanimationend)) {
if ((window.onanimationend === void 0) && (window.onwebkitanimationend !== void 0)) {
CSS_PREFIX = '-webkit-';
ANIMATION_PROP = 'WebkitAnimation';
ANIMATIONEND_EVENT = 'webkitAnimationEnd animationend';
@ -74,10 +60,6 @@ var ANIMATION_DURATION_PROP = ANIMATION_PROP + DURATION_KEY;
var TRANSITION_DELAY_PROP = TRANSITION_PROP + DELAY_KEY;
var TRANSITION_DURATION_PROP = TRANSITION_PROP + DURATION_KEY;
var isPromiseLike = function(p) {
return p && p.then ? true : false;
};
var ngMinErr = angular.$$minErr('ng');
function assertArg(arg, name, reason) {
if (!arg) {
@ -132,8 +114,7 @@ function stripCommentsFromElement(element) {
if (element instanceof jqLite) {
switch (element.length) {
case 0:
return [];
break;
return element;
case 1:
// there is no point of stripping anything if the element
@ -146,7 +127,6 @@ function stripCommentsFromElement(element) {
default:
return jqLite(extractElementNode(element));
break;
}
}
@ -187,7 +167,7 @@ function applyAnimationClassesFactory($$jqLite) {
$$removeClass($$jqLite, element, options.removeClass);
options.removeClass = null;
}
}
};
}
function prepareAnimationOptions(options) {
@ -290,10 +270,10 @@ function resolveElementClasses(existing, toAdd, toRemove) {
var prop, allow;
if (val === ADD_CLASS) {
prop = 'addClass';
allow = !existing[klass];
allow = !existing[klass] || existing[klass + REMOVE_CLASS_SUFFIX];
} else if (val === REMOVE_CLASS) {
prop = 'removeClass';
allow = existing[klass];
allow = existing[klass] || existing[klass + ADD_CLASS_SUFFIX];
}
if (allow) {
if (classes[prop].length) {
@ -323,7 +303,7 @@ function resolveElementClasses(existing, toAdd, toRemove) {
}
function getDomNode(element) {
return (element instanceof angular.element) ? element[0] : element;
return (element instanceof jqLite) ? element[0] : element;
}
function applyGeneratedPreparationClasses(element, event, options) {
@ -396,7 +376,7 @@ var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) {
queue = scheduler.queue = [];
/* waitUntilQuiet does two things:
* 1. It will run the FINAL `fn` value only when an uncancelled RAF has passed through
* 1. It will run the FINAL `fn` value only when an uncanceled RAF has passed through
* 2. It will delay the next wave of tasks from running until the quiet `fn` has run.
*
* The motivation here is that animation code can request more time from the scheduler
@ -513,7 +493,7 @@ var $$AnimateChildrenDirective = ['$interpolate', function($interpolate) {
return {
link: function(scope, element, attrs) {
var val = attrs.ngAnimateChildren;
if (angular.isString(val) && val.length === 0) { //empty attribute
if (isString(val) && val.length === 0) { //empty attribute
element.data(NG_ANIMATE_CHILDREN_DATA, true);
} else {
// Interpolate and set the value, so that it is available to
@ -697,7 +677,7 @@ var ANIMATE_TIMER_KEY = '$$animateCss';
* ```
*
* To actually start the animation we need to run `animation.start()` which will then return a promise that we can hook into to detect when the animation ends.
* If we choose not to run the animation then we MUST run `animation.end()` to perform a cleanup on the element (since some CSS classes and stlyes may have been
* If we choose not to run the animation then we MUST run `animation.end()` to perform a cleanup on the element (since some CSS classes and styles may have been
* applied to the element during the preparation phase). Note that all other properties such as duration, delay, transitions and keyframes are just properties
* and that changing them will not reconfigure the parameters of the animation.
*
@ -734,11 +714,11 @@ var ANIMATE_TIMER_KEY = '$$animateCss';
* * `stagger` - A numeric time value representing the delay between successively animated elements
* ({@link ngAnimate#css-staggering-animations Click here to learn how CSS-based staggering works in ngAnimate.})
* * `staggerIndex` - The numeric index representing the stagger item (e.g. a value of 5 is equal to the sixth item in the stagger; therefore when a
* * `stagger` option value of `0.1` is used then there will be a stagger delay of `600ms`)
* * `applyClassesEarly` - Whether or not the classes being added or removed will be used when detecting the animation. This is set by `$animate` when enter/leave/move animations are fired to ensure that the CSS classes are resolved in time. (Note that this will prevent any transitions from occuring on the classes being added and removed.)
* `stagger` option value of `0.1` is used then there will be a stagger delay of `600ms`)
* * `applyClassesEarly` - Whether or not the classes being added or removed will be used when detecting the animation. This is set by `$animate` when enter/leave/move animations are fired to ensure that the CSS classes are resolved in time. (Note that this will prevent any transitions from occurring on the classes being added and removed.)
* * `cleanupStyles` - Whether or not the provided `from` and `to` styles will be removed once
* the animation is closed. This is useful for when the styles are used purely for the sake of
* the animation and do not have a lasting visual effect on the element (e.g. a colapse and open animation).
* the animation and do not have a lasting visual effect on the element (e.g. a collapse and open animation).
* By default this value is set to `false`.
*
* @return {object} an object with start and end methods and details about the animation.
@ -791,7 +771,7 @@ function computeCssStyles($window, element, properties) {
}
// by setting this to null in the event that the delay is not set or is set directly as 0
// then we can still allow for zegative values to be used later on and not mistake this
// then we can still allow for negative values to be used later on and not mistake this
// value for being greater than any other negative value.
if (val === 0) {
val = null;
@ -907,7 +887,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
}
// we keep putting this in multiple times even though the value and the cacheKey are the same
// because we're keeping an interal tally of how many duplicate animations are detected.
// because we're keeping an internal tally of how many duplicate animations are detected.
gcsLookup.put(cacheKey, timings);
return timings;
}
@ -1381,9 +1361,9 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
}
};
// checking the stagger duration prevents an accidently cascade of the CSS delay style
// checking the stagger duration prevents an accidentally cascade of the CSS delay style
// being inherited from the parent. If the transition duration is zero then we can safely
// rely that the delay value is an intential stagger delay style.
// rely that the delay value is an intentional stagger delay style.
var maxStagger = itemIndex > 0
&& ((timings.transitionDuration && stagger.transitionDuration === 0) ||
(timings.animationDuration && stagger.animationDuration === 0))
@ -1556,7 +1536,7 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro
var rootBodyElement = jqLite(
// this is to avoid using something that exists outside of the body
// we also special case the doc fragement case because our unit test code
// we also special case the doc fragment case because our unit test code
// appends the $rootElement to the body after the app has been bootstrapped
isDocumentFragment(rootNode) || bodyNode.contains(rootNode) ? rootNode : bodyNode
);
@ -1656,7 +1636,7 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro
var coords = getDomNode(anchor).getBoundingClientRect();
// we iterate directly since safari messes up and doesn't return
// all the keys for the coods object when iterated
// all the keys for the coords object when iterated
forEach(['width','height','top','left'], function(key) {
var value = coords[key];
switch (key) {
@ -2225,6 +2205,11 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
});
rules.cancel.push(function(element, newAnimation, currentAnimation) {
// cancel the animation if classes added / removed in both animation cancel each other out,
// but only if the current animation isn't structural
if (currentAnimation.structural) return false;
var nA = newAnimation.addClass;
var nR = newAnimation.removeClass;
var cA = currentAnimation.addClass;
@ -2294,7 +2279,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
}
);
var callbackRegistry = {};
var callbackRegistry = Object.create(null);
// remember that the classNameFilter is set during the provider/config
// stage therefore we can optimize here and setup a helper function
@ -2312,7 +2297,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
}
// IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259.
var contains = Node.prototype.contains || function(arg) {
var contains = window.Node.prototype.contains || function(arg) {
// jshint bitwise: false
return this === arg || !!(this.compareDocumentPosition(arg) & 16);
// jshint bitwise: true
@ -2337,7 +2322,24 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
return matches;
}
return {
function filterFromRegistry(list, matchContainer, matchCallback) {
var containerNode = extractElementNode(matchContainer);
return list.filter(function(entry) {
var isMatch = entry.node === containerNode &&
(!matchCallback || entry.callback === matchCallback);
return !isMatch;
});
}
function cleanupEventListeners(phase, element) {
if (phase === 'close' && !element[0].parentNode) {
// If the element is not attached to a parentNode, it has been removed by
// the domOperation, and we can safely remove the event callbacks
$animate.off(element);
}
}
var $animate = {
on: function(event, container, callback) {
var node = extractElementNode(container);
callbackRegistry[event] = callbackRegistry[event] || [];
@ -2345,24 +2347,36 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
node: node,
callback: callback
});
// Remove the callback when the element is removed from the DOM
jqLite(container).on('$destroy', function() {
var animationDetails = activeAnimationsLookup.get(node);
if (!animationDetails) {
// If there's an animation ongoing, the callback calling code will remove
// the event listeners. If we'd remove here, the callbacks would be removed
// before the animation ends
$animate.off(event, container, callback);
}
});
},
off: function(event, container, callback) {
if (arguments.length === 1 && !isString(arguments[0])) {
container = arguments[0];
for (var eventType in callbackRegistry) {
callbackRegistry[eventType] = filterFromRegistry(callbackRegistry[eventType], container);
}
return;
}
var entries = callbackRegistry[event];
if (!entries) return;
callbackRegistry[event] = arguments.length === 1
? null
: filterFromRegistry(entries, container, callback);
function filterFromRegistry(list, matchContainer, matchCallback) {
var containerNode = extractElementNode(matchContainer);
return list.filter(function(entry) {
var isMatch = entry.node === containerNode &&
(!matchCallback || entry.callback === matchCallback);
return !isMatch;
});
}
},
pin: function(element, parentElement) {
@ -2396,11 +2410,10 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
bool = animationsEnabled = !!element;
} else {
var node = getDomNode(element);
var recordExists = disabledElementsLookup.get(node);
if (argCount === 1) {
// (element) - Element getter
bool = !recordExists;
bool = !disabledElementsLookup.get(node);
} else {
// (element, bool) - Element setter
disabledElementsLookup.put(node, !bool);
@ -2412,6 +2425,8 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
}
};
return $animate;
function queueAnimation(element, event, initialOptions) {
// we always make a copy of the options since
// there should never be any side effects on
@ -2474,12 +2489,14 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0;
var documentHidden = $document[0].hidden;
// this is a hard disable of all animations for the application or on
// the element itself, therefore there is no need to continue further
// past this point if not enabled
// Animations are also disabled if the document is currently hidden (page is not visible
// to the user), because browsers slow down or do not flush calls to requestAnimationFrame
var skipAnimations = !animationsEnabled || $document[0].hidden || disabledElementsLookup.get(node);
var skipAnimations = !animationsEnabled || documentHidden || disabledElementsLookup.get(node);
var existingAnimation = (!skipAnimations && activeAnimationsLookup.get(node)) || {};
var hasExistingAnimation = !!existingAnimation.state;
@ -2490,7 +2507,10 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
}
if (skipAnimations) {
// Callbacks should fire even if the document is hidden (regression fix for issue #14120)
if (documentHidden) notifyProgress(runner, event, 'start');
close();
if (documentHidden) notifyProgress(runner, event, 'close');
return runner;
}
@ -2640,6 +2660,11 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
markElementAnimationState(element, RUNNING_STATE);
var realRunner = $$animation(element, event, animationDetails.options);
// this will update the runner's flow-control events based on
// the `realRunner` object.
runner.setHost(realRunner);
notifyProgress(runner, event, 'start', {});
realRunner.done(function(status) {
close(!status);
var animationDetails = activeAnimationsLookup.get(node);
@ -2648,11 +2673,6 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
}
notifyProgress(runner, event, 'close', {});
});
// this will update the runner's flow-control events based on
// the `realRunner` object.
runner.setHost(realRunner);
notifyProgress(runner, event, 'start', {});
});
return runner;
@ -2669,7 +2689,10 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
forEach(callbacks, function(callback) {
callback(element, phase, data);
});
cleanupEventListeners(phase, element);
});
} else {
cleanupEventListeners(phase, element);
}
});
runner.progress(event, phase, data);
@ -3126,7 +3149,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
};
// the anchor animations require that the from and to elements both have at least
// one shared CSS class which effictively marries the two elements together to use
// one shared CSS class which effectively marries the two elements together to use
// the same animation driver and to properly sequence the anchor animation.
if (group.classes.length) {
preparedAnimations.push(group);
@ -3169,8 +3192,6 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
// may attempt more elements, but custom drivers are more particular
for (var i = drivers.length - 1; i >= 0; i--) {
var driverName = drivers[i];
if (!$injector.has(driverName)) continue; // TODO(matsko): remove this check
var factory = $injector.get(driverName);
var driver = factory(animationDetails);
if (driver) {
@ -3199,7 +3220,8 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
}
function update(element) {
getRunner(element).setHost(newRunner);
var runner = getRunner(element);
if (runner) runner.setHost(newRunner);
}
}
@ -3229,18 +3251,120 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
}];
}];
/* global angularAnimateModule: true,
$$AnimateAsyncRunFactory,
$$rAFSchedulerFactory,
$$AnimateChildrenDirective,
$$AnimateQueueProvider,
$$AnimationProvider,
$AnimateCssProvider,
$$AnimateCssDriverProvider,
$$AnimateJsProvider,
$$AnimateJsDriverProvider,
*/
/**
* @ngdoc directive
* @name ngAnimateSwap
* @restrict A
* @scope
*
* @description
*
* ngAnimateSwap is a animation-oriented directive that allows for the container to
* be removed and entered in whenever the associated expression changes. A
* common usecase for this directive is a rotating banner or slider component which
* contains one image being present at a time. When the active image changes
* then the old image will perform a `leave` animation and the new element
* will be inserted via an `enter` animation.
*
* @animations
* | Animation | Occurs |
* |----------------------------------|--------------------------------------|
* | {@link ng.$animate#enter enter} | when the new element is inserted to the DOM |
* | {@link ng.$animate#leave leave} | when the old element is removed from the DOM |
*
* @example
* <example name="ngAnimateSwap-directive" module="ngAnimateSwapExample"
* deps="angular-animate.js"
* animations="true" fixBase="true">
* <file name="index.html">
* <div class="container" ng-controller="AppCtrl">
* <div ng-animate-swap="number" class="cell swap-animation" ng-class="colorClass(number)">
* {{ number }}
* </div>
* </div>
* </file>
* <file name="script.js">
* angular.module('ngAnimateSwapExample', ['ngAnimate'])
* .controller('AppCtrl', ['$scope', '$interval', function($scope, $interval) {
* $scope.number = 0;
* $interval(function() {
* $scope.number++;
* }, 1000);
*
* var colors = ['red','blue','green','yellow','orange'];
* $scope.colorClass = function(number) {
* return colors[number % colors.length];
* };
* }]);
* </file>
* <file name="animations.css">
* .container {
* height:250px;
* width:250px;
* position:relative;
* overflow:hidden;
* border:2px solid black;
* }
* .container .cell {
* font-size:150px;
* text-align:center;
* line-height:250px;
* position:absolute;
* top:0;
* left:0;
* right:0;
* border-bottom:2px solid black;
* }
* .swap-animation.ng-enter, .swap-animation.ng-leave {
* transition:0.5s linear all;
* }
* .swap-animation.ng-enter {
* top:-250px;
* }
* .swap-animation.ng-enter-active {
* top:0px;
* }
* .swap-animation.ng-leave {
* top:0px;
* }
* .swap-animation.ng-leave-active {
* top:250px;
* }
* .red { background:red; }
* .green { background:green; }
* .blue { background:blue; }
* .yellow { background:yellow; }
* .orange { background:orange; }
* </file>
* </example>
*/
var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $rootScope) {
return {
restrict: 'A',
transclude: 'element',
terminal: true,
priority: 600, // we use 600 here to ensure that the directive is caught before others
link: function(scope, $element, attrs, ctrl, $transclude) {
var previousElement, previousScope;
scope.$watchCollection(attrs.ngAnimateSwap || attrs['for'], function(value) {
if (previousElement) {
$animate.leave(previousElement);
}
if (previousScope) {
previousScope.$destroy();
previousScope = null;
}
if (value || value === 0) {
previousScope = scope.$new();
$transclude(previousScope, function(element) {
previousElement = element;
$animate.enter(element, null, $element);
});
}
});
}
};
}];
/**
* @ngdoc module
@ -3358,7 +3482,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
* <div ng-show="bool" class="fade">
* Show and hide me
* </div>
* <button ng-click="bool=true">Toggle</button>
* <button ng-click="bool=!bool">Toggle</button>
*
* <style>
* .fade.ng-hide {
@ -3514,7 +3638,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
*
* ngAnimate also allows for animations to be consumed by JavaScript code. The approach is similar to CSS-based animations (where there is a shared
* CSS class that is referenced in our HTML code) but in addition we need to register the JavaScript animation on the module. By making use of the
* `module.animation()` module function we can register the ainmation.
* `module.animation()` module function we can register the animation.
*
* Let's see an example of a enter/leave animation using `ngRepeat`:
*
@ -3927,31 +4051,6 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
* possible be sure to visit the {@link ng.$animate $animate service API page}.
*
*
* ### Preventing Collisions With Third Party Libraries
*
* Some third-party frameworks place animation duration defaults across many element or className
* selectors in order to make their code small and reuseable. This can lead to issues with ngAnimate, which
* is expecting actual animations on these elements and has to wait for their completion.
*
* You can prevent this unwanted behavior by using a prefix on all your animation classes:
*
* ```css
* /&#42; prefixed with animate- &#42;/
* .animate-fade-add.animate-fade-add-active {
* transition:1s linear all;
* opacity:0;
* }
* ```
*
* You then configure `$animate` to enforce this prefix:
*
* ```js
* $animateProvider.classNameFilter(/animate-/);
* ```
*
* This also may provide your application with a speed boost since only specific elements containing CSS class prefix
* will be evaluated for animation when any DOM changes occur in the application.
*
* ## Callbacks and Promises
*
* When `$animate` is called it returns a promise that can be used to capture when the animation has ended. Therefore if we were to trigger
@ -3983,6 +4082,19 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
* (Note that you will need to trigger a digest within the callback to get angular to notice any scope-related changes.)
*/
var copy;
var extend;
var forEach;
var isArray;
var isDefined;
var isElement;
var isFunction;
var isObject;
var isString;
var isUndefined;
var jqLite;
var noop;
/**
* @ngdoc service
* @name $animate
@ -3993,7 +4105,24 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
*
* Click here {@link ng.$animate to learn more about animations with `$animate`}.
*/
angular.module('ngAnimate', [])
angular.module('ngAnimate', [], function initAngularHelpers() {
// Access helpers from angular core.
// Do it inside a `config` block to ensure `window.angular` is available.
noop = angular.noop;
copy = angular.copy;
extend = angular.extend;
jqLite = angular.element;
forEach = angular.forEach;
isArray = angular.isArray;
isString = angular.isString;
isObject = angular.isObject;
isUndefined = angular.isUndefined;
isDefined = angular.isDefined;
isFunction = angular.isFunction;
isElement = angular.isElement;
})
.directive('ngAnimateSwap', ngAnimateSwapDirective)
.directive('ngAnimateChildren', $$AnimateChildrenDirective)
.factory('$$rAFScheduler', $$rAFSchedulerFactory)

View File

@ -1,9 +1,9 @@
/**
* @license AngularJS v1.4.10
* (c) 2010-2015 Google, Inc. http://angularjs.org
* @license AngularJS v1.5.8
* (c) 2010-2016 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {'use strict';
(function(window, angular) {'use strict';
/**
* @ngdoc module
@ -21,24 +21,29 @@
*
* For ngAria to do its magic, simply include the module `ngAria` as a dependency. The following
* directives are supported:
* `ngModel`, `ngDisabled`, `ngShow`, `ngHide`, `ngClick`, `ngDblClick`, and `ngMessages`.
* `ngModel`, `ngChecked`, `ngReadonly`, `ngRequired`, `ngValue`, `ngDisabled`, `ngShow`, `ngHide`, `ngClick`,
* `ngDblClick`, and `ngMessages`.
*
* Below is a more detailed breakdown of the attributes handled by ngAria:
*
* | Directive | Supported Attributes |
* |---------------------------------------------|----------------------------------------------------------------------------------------|
* | {@link ng.directive:ngModel ngModel} | aria-checked, aria-valuemin, aria-valuemax, aria-valuenow, aria-invalid, aria-required, input roles |
* | {@link ng.directive:ngDisabled ngDisabled} | aria-disabled |
* | {@link ng.directive:ngRequired ngRequired} | aria-required
* | {@link ng.directive:ngChecked ngChecked} | aria-checked
* | {@link ng.directive:ngReadonly ngReadonly} | aria-readonly |
* | {@link ng.directive:ngValue ngValue} | aria-checked |
* | {@link ng.directive:ngShow ngShow} | aria-hidden |
* | {@link ng.directive:ngHide ngHide} | aria-hidden |
* | {@link ng.directive:ngDblclick ngDblclick} | tabindex |
* | {@link module:ngMessages ngMessages} | aria-live |
* | {@link ng.directive:ngModel ngModel} | aria-checked, aria-valuemin, aria-valuemax, aria-valuenow, aria-invalid, aria-required, input roles |
* | {@link ng.directive:ngClick ngClick} | tabindex, keypress event, button role |
* | {@link ng.directive:ngClick ngClick} | tabindex, keypress event, button role |
*
* Find out more information about each directive by reading the
* {@link guide/accessibility ngAria Developer Guide}.
*
* ##Example
* ## Example
* Using ngDisabled with ngAria:
* ```html
* <md-checkbox ng-disabled="disabled">
@ -48,7 +53,7 @@
* <md-checkbox ng-disabled="disabled" aria-disabled="true">
* ```
*
* ##Disabling Attributes
* ## Disabling Attributes
* It's possible to disable individual attributes added by ngAria with the
* {@link ngAria.$ariaProvider#config config} method. For more details, see the
* {@link guide/accessibility Developer Guide}.
@ -92,10 +97,10 @@ function $AriaProvider() {
var config = {
ariaHidden: true,
ariaChecked: true,
ariaReadonly: true,
ariaDisabled: true,
ariaRequired: true,
ariaInvalid: true,
ariaMultiline: true,
ariaValue: true,
tabindex: true,
bindKeypress: true,
@ -110,14 +115,14 @@ function $AriaProvider() {
*
* - **ariaHidden** `{boolean}` Enables/disables aria-hidden tags
* - **ariaChecked** `{boolean}` Enables/disables aria-checked tags
* - **ariaReadonly** `{boolean}` Enables/disables aria-readonly tags
* - **ariaDisabled** `{boolean}` Enables/disables aria-disabled tags
* - **ariaRequired** `{boolean}` Enables/disables aria-required tags
* - **ariaInvalid** `{boolean}` Enables/disables aria-invalid tags
* - **ariaMultiline** `{boolean}` Enables/disables aria-multiline tags
* - **ariaValue** `{boolean}` Enables/disables aria-valuemin, aria-valuemax and aria-valuenow tags
* - **tabindex** `{boolean}` Enables/disables tabindex tags
* - **bindKeypress** `{boolean}` Enables/disables keypress event binding on `&lt;div&gt;` and
* `&lt;li&gt;` elements with ng-click
* - **bindKeypress** `{boolean}` Enables/disables keypress event binding on `div` and
* `li` elements with ng-click
* - **bindRoleForClick** `{boolean}` Adds role=button to non-interactive elements like `div`
* using ng-click, making them more accessible to users of assistive technologies
*
@ -156,15 +161,15 @@ function $AriaProvider() {
*
*```js
* ngAriaModule.directive('ngDisabled', ['$aria', function($aria) {
* return $aria.$$watchExpr('ngDisabled', 'aria-disabled');
* return $aria.$$watchExpr('ngDisabled', 'aria-disabled', nodeBlackList, false);
* }])
*```
* Shown above, the ngAria module creates a directive with the same signature as the
* traditional `ng-disabled` directive. But this ngAria version is dedicated to
* solely managing accessibility attributes. The internal `$aria` service is used to watch the
* boolean attribute `ngDisabled`. If it has not been explicitly set by the developer,
* `aria-disabled` is injected as an attribute with its value synchronized to the value in
* `ngDisabled`.
* solely managing accessibility attributes on custom elements. The internal `$aria` service is
* used to watch the boolean attribute `ngDisabled`. If it has not been explicitly set by the
* developer, `aria-disabled` is injected as an attribute with its value synchronized to the
* value in `ngDisabled`.
*
* Because ngAria hooks into the `ng-disabled` directive, developers do not have to do
* anything to enable this feature. The `aria-disabled` attribute is automatically managed
@ -172,12 +177,16 @@ function $AriaProvider() {
*
* The full list of directives that interface with ngAria:
* * **ngModel**
* * **ngChecked**
* * **ngReadonly**
* * **ngRequired**
* * **ngDisabled**
* * **ngValue**
* * **ngShow**
* * **ngHide**
* * **ngClick**
* * **ngDblclick**
* * **ngMessages**
* * **ngDisabled**
*
* Read the {@link guide/accessibility ngAria Developer Guide} for a thorough explanation of each
* directive.
@ -203,13 +212,28 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
.directive('ngHide', ['$aria', function($aria) {
return $aria.$$watchExpr('ngHide', 'aria-hidden', [], false);
}])
.directive('ngValue', ['$aria', function($aria) {
return $aria.$$watchExpr('ngValue', 'aria-checked', nodeBlackList, false);
}])
.directive('ngChecked', ['$aria', function($aria) {
return $aria.$$watchExpr('ngChecked', 'aria-checked', nodeBlackList, false);
}])
.directive('ngReadonly', ['$aria', function($aria) {
return $aria.$$watchExpr('ngReadonly', 'aria-readonly', nodeBlackList, false);
}])
.directive('ngRequired', ['$aria', function($aria) {
return $aria.$$watchExpr('ngRequired', 'aria-required', nodeBlackList, false);
}])
.directive('ngModel', ['$aria', function($aria) {
function shouldAttachAttr(attr, normalizedAttr, elem) {
return $aria.config(normalizedAttr) && !elem.attr(attr);
function shouldAttachAttr(attr, normalizedAttr, elem, allowBlacklistEls) {
return $aria.config(normalizedAttr) && !elem.attr(attr) && (allowBlacklistEls || !isNodeOneOf(elem, nodeBlackList));
}
function shouldAttachRole(role, elem) {
// if element does not have role attribute
// AND element type is equal to role (if custom element has a type equaling shape) <-- remove?
// AND element is not INPUT
return !elem.attr('role') && (elem.attr('type') === role) && (elem[0].nodeName !== 'INPUT');
}
@ -219,20 +243,19 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
return ((type || role) === 'checkbox' || role === 'menuitemcheckbox') ? 'checkbox' :
((type || role) === 'radio' || role === 'menuitemradio') ? 'radio' :
(type === 'range' || role === 'progressbar' || role === 'slider') ? 'range' :
(type || role) === 'textbox' || elem[0].nodeName === 'TEXTAREA' ? 'multiline' : '';
(type === 'range' || role === 'progressbar' || role === 'slider') ? 'range' : '';
}
return {
restrict: 'A',
require: '?ngModel',
require: 'ngModel',
priority: 200, //Make sure watches are fired after any other directives that affect the ngModel value
compile: function(elem, attr) {
var shape = getShape(attr, elem);
return {
pre: function(scope, elem, attr, ngModel) {
if (shape === 'checkbox' && attr.type !== 'checkbox') {
if (shape === 'checkbox') {
//Use the input[checkbox] $isEmpty implementation for elements with checkbox roles
ngModel.$isEmpty = function(value) {
return value === false;
@ -240,29 +263,18 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
}
},
post: function(scope, elem, attr, ngModel) {
var needsTabIndex = shouldAttachAttr('tabindex', 'tabindex', elem)
&& !isNodeOneOf(elem, nodeBlackList);
var needsTabIndex = shouldAttachAttr('tabindex', 'tabindex', elem, false);
function ngAriaWatchModelValue() {
return ngModel.$modelValue;
}
function getRadioReaction() {
if (needsTabIndex) {
needsTabIndex = false;
return function ngAriaRadioReaction(newVal) {
var boolVal = (attr.value == ngModel.$viewValue);
elem.attr('aria-checked', boolVal);
elem.attr('tabindex', 0 - !boolVal);
};
} else {
return function ngAriaRadioReaction(newVal) {
elem.attr('aria-checked', (attr.value == ngModel.$viewValue));
};
}
function getRadioReaction(newVal) {
var boolVal = (attr.value == ngModel.$viewValue);
elem.attr('aria-checked', boolVal);
}
function ngAriaCheckboxReaction() {
function getCheckboxReaction() {
elem.attr('aria-checked', !ngModel.$isEmpty(ngModel.$viewValue));
}
@ -272,9 +284,9 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
if (shouldAttachRole(shape, elem)) {
elem.attr('role', shape);
}
if (shouldAttachAttr('aria-checked', 'ariaChecked', elem)) {
if (shouldAttachAttr('aria-checked', 'ariaChecked', elem, false)) {
scope.$watch(ngAriaWatchModelValue, shape === 'radio' ?
getRadioReaction() : ngAriaCheckboxReaction);
getRadioReaction : getCheckboxReaction);
}
if (needsTabIndex) {
elem.attr('tabindex', 0);
@ -311,22 +323,17 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
elem.attr('tabindex', 0);
}
break;
case 'multiline':
if (shouldAttachAttr('aria-multiline', 'ariaMultiline', elem)) {
elem.attr('aria-multiline', true);
}
break;
}
if (ngModel.$validators.required && shouldAttachAttr('aria-required', 'ariaRequired', elem)) {
scope.$watch(function ngAriaRequiredWatch() {
return ngModel.$error.required;
}, function ngAriaRequiredReaction(newVal) {
elem.attr('aria-required', !!newVal);
if (!attr.hasOwnProperty('ngRequired') && ngModel.$validators.required
&& shouldAttachAttr('aria-required', 'ariaRequired', elem, false)) {
// ngModel.$error.required is undefined on custom controls
attr.$observe('required', function() {
elem.attr('aria-required', !!attr['required']);
});
}
if (shouldAttachAttr('aria-invalid', 'ariaInvalid', elem)) {
if (shouldAttachAttr('aria-invalid', 'ariaInvalid', elem, true)) {
scope.$watch(function ngAriaInvalidWatch() {
return ngModel.$invalid;
}, function ngAriaInvalidReaction(newVal) {
@ -339,7 +346,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
};
}])
.directive('ngDisabled', ['$aria', function($aria) {
return $aria.$$watchExpr('ngDisabled', 'aria-disabled', []);
return $aria.$$watchExpr('ngDisabled', 'aria-disabled', nodeBlackList, false);
}])
.directive('ngMessages', function() {
return {

View File

@ -1,9 +1,9 @@
/**
* @license AngularJS v1.4.10
* (c) 2010-2015 Google, Inc. http://angularjs.org
* @license AngularJS v1.5.8
* (c) 2010-2016 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {'use strict';
(function(window, angular) {'use strict';
/**
* @ngdoc module

View File

@ -1,13 +1,13 @@
/**
* @license AngularJS v1.4.10
* (c) 2010-2015 Google, Inc. http://angularjs.org
* @license AngularJS v1.5.8
* (c) 2010-2016 Google, Inc. http://angularjs.org
* License: MIT
*/
(function() {'use strict';
function isFunction(value) {return typeof value === 'function';};
/* global: toDebugString: true */
/* global toDebugString: true */
function serializeObject(obj) {
var seen = [];
@ -87,7 +87,7 @@ function minErr(module, ErrorConstructor) {
return match;
});
message += '\nhttp://errors.angularjs.org/1.4.10/' +
message += '\nhttp://errors.angularjs.org/1.5.8/' +
(module ? module + '/' : '') + code;
for (i = SKIP_INDEXES, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
@ -296,9 +296,9 @@ function setupModuleLoader(window) {
* @ngdoc method
* @name angular.Module#decorator
* @module ng
* @param {string} The name of the service to decorate.
* @param {Function} This function will be invoked when the service needs to be
* instantiated and should return the decorated service instance.
* @param {string} name The name of the service to decorate.
* @param {Function} decorFn This function will be invoked when the service needs to be
* instantiated and should return the decorated service instance.
* @description
* See {@link auto.$provide#decorator $provide.decorator()}.
*/
@ -381,6 +381,19 @@ function setupModuleLoader(window) {
*/
directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'),
/**
* @ngdoc method
* @name angular.Module#component
* @module ng
* @param {string} name Name of the component in camel-case (i.e. myComp which will match as my-comp)
* @param {Object} options Component definition object (a simplified
* {@link ng.$compile#directive-definition-object directive definition object})
*
* @description
* See {@link ng.$compileProvider#component $compileProvider.component()}.
*/
component: invokeLaterAndSetModuleName('$compileProvider', 'component'),
/**
* @ngdoc method
* @name angular.Module#config

View File

@ -1,20 +1,18 @@
/**
* @license AngularJS v1.4.10
* (c) 2010-2015 Google, Inc. http://angularjs.org
* @license AngularJS v1.5.8
* (c) 2010-2016 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {'use strict';
(function(window, angular) {'use strict';
// NOTE: ADVANCED_OPTIMIZATIONS mode.
//
// This file is compiled with Closure compiler's ADVANCED_OPTIMIZATIONS flag! Be wary of using
// constructs incompatible with that mode.
var $interpolateMinErr = window['angular']['$interpolateMinErr'];
var noop = window['angular']['noop'],
isFunction = window['angular']['isFunction'],
toJson = window['angular']['toJson'];
/* global isFunction: false */
/* global noop: false */
/* global toJson: false */
function stringify(value) {
if (value == null /* null/undefined */) { return ''; }
@ -861,31 +859,90 @@ MessageFormatParser.prototype.ruleInAngularExpression = function ruleInAngularEx
// This file is compiled with Closure compiler's ADVANCED_OPTIMIZATIONS flag! Be wary of using
// constructs incompatible with that mode.
/* global $interpolateMinErr: false */
/* global $interpolateMinErr: true */
/* global isFunction: true */
/* global noop: true */
/* global toJson: true */
/* global MessageFormatParser: false */
/* global stringify: false */
/**
* @ngdoc service
* @name $$messageFormat
* @ngdoc module
* @name ngMessageFormat
* @packageName angular-message-format
*
* @description
* Angular internal service to recognize MessageFormat extensions in interpolation expressions.
* For more information, see:
* https://docs.google.com/a/google.com/document/d/1pbtW2yvtmFBikfRrJd8VAsabiFkKezmYZ_PbgdjQOVU/edit
*
* ## Example
* ## What is ngMessageFormat?
*
* <example name="ngMessageFormat-example" module="msgFmtExample" deps="angular-message-format.min.js">
* The ngMessageFormat module extends the Angular {@link ng.$interpolate `$interpolate`} service
* with a syntax for handling pluralization and gender specific messages, which is based on the
* [ICU MessageFormat syntax][ICU].
*
* See [the design doc][ngMessageFormat doc] for more information.
*
* [ICU]: http://userguide.icu-project.org/formatparse/messages#TOC-MessageFormat
* [ngMessageFormat doc]: https://docs.google.com/a/google.com/document/d/1pbtW2yvtmFBikfRrJd8VAsabiFkKezmYZ_PbgdjQOVU/edit
*
* ## Examples
*
* ### Gender
*
* This example uses the "select" keyword to specify the message based on gender.
*
* <example name="ngMessageFormat-example-gender" module="msgFmtExample" deps="angular-message-format.js">
* <file name="index.html">
* <div ng-controller="AppController">
* Select Recipient:<br>
<select ng-model="recipient" ng-options="person as person.name for person in recipients">
</select>
<p>{{recipient.gender, select,
male {{{recipient.name}} unwrapped his gift. }
female {{{recipient.name}} unwrapped her gift. }
other {{{recipient.name}} unwrapped their gift. }
}}</p>
* </div>
* </file>
* <file name="script.js">
* function Person(name, gender) {
* this.name = name;
* this.gender = gender;
* }
*
* var alice = new Person("Alice", "female"),
* bob = new Person("Bob", "male"),
* ashley = new Person("Ashley", "");
*
* angular.module('msgFmtExample', ['ngMessageFormat'])
* .controller('AppController', ['$scope', function($scope) {
* $scope.recipients = [alice, bob, ashley];
* $scope.recipient = $scope.recipients[0];
* }]);
* </file>
* </example>
*
* ### Plural
*
* This example shows how the "plural" keyword is used to account for a variable number of entities.
* The "#" variable holds the current number and can be embedded in the message.
*
* Note that "=1" takes precedence over "one".
*
* The example also shows the "offset" keyword, which allows you to offset the value of the "#" variable.
*
* <example name="ngMessageFormat-example-plural" module="msgFmtExample" deps="angular-message-format.js">
* <file name="index.html">
* <div ng-controller="AppController">
* <button ng-click="decreaseRecipients()" id="decreaseRecipients">decreaseRecipients</button><br>
* <span>{{recipients.length, plural, offset:1
* <button ng-click="recipients.pop()" id="decreaseRecipients">decreaseRecipients</button><br>
* Select recipients:<br>
* <select multiple size=5 ng-model="recipients" ng-options="person as person.name for person in people">
* </select><br>
* <p>{{recipients.length, plural, offset:1
* =0 {{{sender.name}} gave no gifts (\#=#)}
* =1 {{{sender.name}} gave one gift to {{recipients[0].name}} (\#=#)}
* =1 {{{sender.name}} gave a gift to {{recipients[0].name}} (\#=#)}
* one {{{sender.name}} gave {{recipients[0].name}} and one other person a gift (\#=#)}
* other {{{sender.name}} gave {{recipients[0].name}} and # other people a gift (\#=#)}
* }}</span>
* }}</p>
* </div>
* </file>
*
@ -897,35 +954,79 @@ MessageFormatParser.prototype.ruleInAngularExpression = function ruleInAngularEx
*
* var alice = new Person("Alice", "female"),
* bob = new Person("Bob", "male"),
* charlie = new Person("Charlie", "male"),
* harry = new Person("Harry Potter", "male");
* sarah = new Person("Sarah", "female"),
* harry = new Person("Harry Potter", "male"),
* ashley = new Person("Ashley", "");
*
* angular.module('msgFmtExample', ['ngMessageFormat'])
* .controller('AppController', ['$scope', function($scope) {
* $scope.recipients = [alice, bob, charlie];
* $scope.people = [alice, bob, sarah, ashley];
* $scope.recipients = [alice, bob, sarah];
* $scope.sender = harry;
* $scope.decreaseRecipients = function() {
* --$scope.recipients.length;
* };
* }]);
* </file>
*
* <file name="protractor.js" type="protractor">
* describe('MessageFormat plural', function() {
*
* it('should pluralize initial values', function() {
* var messageElem = element(by.binding('recipients.length')), decreaseRecipientsBtn = element(by.id('decreaseRecipients'));
* var messageElem = element(by.binding('recipients.length')),
* decreaseRecipientsBtn = element(by.id('decreaseRecipients'));
*
* expect(messageElem.getText()).toEqual('Harry Potter gave Alice and 2 other people a gift (#=2)');
* decreaseRecipientsBtn.click();
* expect(messageElem.getText()).toEqual('Harry Potter gave Alice and one other person a gift (#=1)');
* decreaseRecipientsBtn.click();
* expect(messageElem.getText()).toEqual('Harry Potter gave one gift to Alice (#=0)');
* expect(messageElem.getText()).toEqual('Harry Potter gave a gift to Alice (#=0)');
* decreaseRecipientsBtn.click();
* expect(messageElem.getText()).toEqual('Harry Potter gave no gifts (#=-1)');
* });
* });
* </file>
* </example>
*
* ### Plural and Gender together
*
* This example shows how you can specify gender rules for specific plural matches - in this case,
* =1 is special cased for gender.
* <example name="ngMessageFormat-example-plural-gender" module="msgFmtExample" deps="angular-message-format.js">
* <file name="index.html">
* <div ng-controller="AppController">
Select recipients:<br>
<select multiple size=5 ng-model="recipients" ng-options="person as person.name for person in people">
</select><br>
<p>{{recipients.length, plural,
=0 {{{sender.name}} has not given any gifts to anyone.}
=1 { {{recipients[0].gender, select,
female { {{sender.name}} gave {{recipients[0].name}} her gift.}
male { {{sender.name}} gave {{recipients[0].name}} his gift.}
other { {{sender.name}} gave {{recipients[0].name}} their gift.}
}}
}
other {{{sender.name}} gave {{recipients.length}} people gifts.}
}}</p>
</file>
* <file name="script.js">
* function Person(name, gender) {
* this.name = name;
* this.gender = gender;
* }
*
* var alice = new Person("Alice", "female"),
* bob = new Person("Bob", "male"),
* harry = new Person("Harry Potter", "male"),
* ashley = new Person("Ashley", "");
*
* angular.module('msgFmtExample', ['ngMessageFormat'])
* .controller('AppController', ['$scope', function($scope) {
* $scope.people = [alice, bob, ashley];
* $scope.recipients = [alice];
* $scope.sender = harry;
* }]);
* </file>
</example>
*/
var $$MessageFormatFactory = ['$parse', '$locale', '$sce', '$exceptionHandler', function $$messageFormat(
$parse, $locale, $sce, $exceptionHandler) {
@ -963,16 +1064,19 @@ var $$interpolateDecorator = ['$$messageFormat', '$delegate', function $$interpo
return interpolate;
}];
var $interpolateMinErr;
var isFunction;
var noop;
var toJson;
/**
* @ngdoc module
* @name ngMessageFormat
* @packageName angular-message-format
* @description
*/
var module = window['angular']['module']('ngMessageFormat', ['ng']);
module['factory']('$$messageFormat', $$MessageFormatFactory);
module['config'](['$provide', function($provide) {
$interpolateMinErr = window['angular']['$interpolateMinErr'];
isFunction = window['angular']['isFunction'];
noop = window['angular']['noop'];
toJson = window['angular']['toJson'];
$provide['decorator']('$interpolate', $$interpolateDecorator);
}]);

View File

@ -1,17 +1,14 @@
/**
* @license AngularJS v1.4.10
* (c) 2010-2015 Google, Inc. http://angularjs.org
* @license AngularJS v1.5.8
* (c) 2010-2016 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {'use strict';
(function(window, angular) {'use strict';
/* jshint ignore:start */
// this code is in the core, but not in angular-messages.js
var isArray = angular.isArray;
var forEach = angular.forEach;
var isString = angular.isString;
var jqLite = angular.element;
/* jshint ignore:end */
var forEach;
var isArray;
var isString;
var jqLite;
/**
* @ngdoc module
@ -267,369 +264,404 @@ var jqLite = angular.element;
*
* {@link ngAnimate Click here} to learn how to use JavaScript animations or to learn more about ngAnimate.
*/
angular.module('ngMessages', [])
angular.module('ngMessages', [], function initAngularHelpers() {
// Access helpers from angular core.
// Do it inside a `config` block to ensure `window.angular` is available.
forEach = angular.forEach;
isArray = angular.isArray;
isString = angular.isString;
jqLite = angular.element;
})
/**
* @ngdoc directive
* @module ngMessages
* @name ngMessages
* @restrict AE
*
* @description
* `ngMessages` is a directive that is designed to show and hide messages based on the state
* of a key/value object that it listens on. The directive itself complements error message
* reporting with the `ngModel` $error object (which stores a key/value state of validation errors).
*
* `ngMessages` manages the state of internal messages within its container element. The internal
* messages use the `ngMessage` directive and will be inserted/removed from the page depending
* on if they're present within the key/value object. By default, only one message will be displayed
* at a time and this depends on the prioritization of the messages within the template. (This can
* be changed by using the `ng-messages-multiple` or `multiple` attribute on the directive container.)
*
* A remote template can also be used to promote message reusability and messages can also be
* overridden.
*
* {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`.
*
* @usage
* ```html
* <!-- using attribute directives -->
* <ANY ng-messages="expression" role="alert">
* <ANY ng-message="stringValue">...</ANY>
* <ANY ng-message="stringValue1, stringValue2, ...">...</ANY>
* <ANY ng-message-exp="expressionValue">...</ANY>
* </ANY>
*
* <!-- or by using element directives -->
* <ng-messages for="expression" role="alert">
* <ng-message when="stringValue">...</ng-message>
* <ng-message when="stringValue1, stringValue2, ...">...</ng-message>
* <ng-message when-exp="expressionValue">...</ng-message>
* </ng-messages>
* ```
*
* @param {string} ngMessages an angular expression evaluating to a key/value object
* (this is typically the $error object on an ngModel instance).
* @param {string=} ngMessagesMultiple|multiple when set, all messages will be displayed with true
*
* @example
* <example name="ngMessages-directive" module="ngMessagesExample"
* deps="angular-messages.js"
* animations="true" fixBase="true">
* <file name="index.html">
* <form name="myForm">
* <label>
* Enter your name:
* <input type="text"
* name="myName"
* ng-model="name"
* ng-minlength="5"
* ng-maxlength="20"
* required />
* </label>
* <pre>myForm.myName.$error = {{ myForm.myName.$error | json }}</pre>
*
* <div ng-messages="myForm.myName.$error" style="color:maroon" role="alert">
* <div ng-message="required">You did not enter a field</div>
* <div ng-message="minlength">Your field is too short</div>
* <div ng-message="maxlength">Your field is too long</div>
* </div>
* </form>
* </file>
* <file name="script.js">
* angular.module('ngMessagesExample', ['ngMessages']);
* </file>
* </example>
*/
.directive('ngMessages', ['$animate', function($animate) {
var ACTIVE_CLASS = 'ng-active';
var INACTIVE_CLASS = 'ng-inactive';
/**
* @ngdoc directive
* @module ngMessages
* @name ngMessages
* @restrict AE
*
* @description
* `ngMessages` is a directive that is designed to show and hide messages based on the state
* of a key/value object that it listens on. The directive itself complements error message
* reporting with the `ngModel` $error object (which stores a key/value state of validation errors).
*
* `ngMessages` manages the state of internal messages within its container element. The internal
* messages use the `ngMessage` directive and will be inserted/removed from the page depending
* on if they're present within the key/value object. By default, only one message will be displayed
* at a time and this depends on the prioritization of the messages within the template. (This can
* be changed by using the `ng-messages-multiple` or `multiple` attribute on the directive container.)
*
* A remote template can also be used to promote message reusability and messages can also be
* overridden.
*
* {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`.
*
* @usage
* ```html
* <!-- using attribute directives -->
* <ANY ng-messages="expression" role="alert">
* <ANY ng-message="stringValue">...</ANY>
* <ANY ng-message="stringValue1, stringValue2, ...">...</ANY>
* <ANY ng-message-exp="expressionValue">...</ANY>
* </ANY>
*
* <!-- or by using element directives -->
* <ng-messages for="expression" role="alert">
* <ng-message when="stringValue">...</ng-message>
* <ng-message when="stringValue1, stringValue2, ...">...</ng-message>
* <ng-message when-exp="expressionValue">...</ng-message>
* </ng-messages>
* ```
*
* @param {string} ngMessages an angular expression evaluating to a key/value object
* (this is typically the $error object on an ngModel instance).
* @param {string=} ngMessagesMultiple|multiple when set, all messages will be displayed with true
*
* @example
* <example name="ngMessages-directive" module="ngMessagesExample"
* deps="angular-messages.js"
* animations="true" fixBase="true">
* <file name="index.html">
* <form name="myForm">
* <label>
* Enter your name:
* <input type="text"
* name="myName"
* ng-model="name"
* ng-minlength="5"
* ng-maxlength="20"
* required />
* </label>
* <pre>myForm.myName.$error = {{ myForm.myName.$error | json }}</pre>
*
* <div ng-messages="myForm.myName.$error" style="color:maroon" role="alert">
* <div ng-message="required">You did not enter a field</div>
* <div ng-message="minlength">Your field is too short</div>
* <div ng-message="maxlength">Your field is too long</div>
* </div>
* </form>
* </file>
* <file name="script.js">
* angular.module('ngMessagesExample', ['ngMessages']);
* </file>
* </example>
*/
.directive('ngMessages', ['$animate', function($animate) {
var ACTIVE_CLASS = 'ng-active';
var INACTIVE_CLASS = 'ng-inactive';
return {
require: 'ngMessages',
restrict: 'AE',
controller: ['$element', '$scope', '$attrs', function($element, $scope, $attrs) {
var ctrl = this;
var latestKey = 0;
var nextAttachId = 0;
return {
require: 'ngMessages',
restrict: 'AE',
controller: ['$element', '$scope', '$attrs', function($element, $scope, $attrs) {
var ctrl = this;
var latestKey = 0;
var nextAttachId = 0;
this.getAttachId = function getAttachId() { return nextAttachId++; };
this.getAttachId = function getAttachId() { return nextAttachId++; };
var messages = this.messages = {};
var renderLater, cachedCollection;
var messages = this.messages = {};
var renderLater, cachedCollection;
this.render = function(collection) {
collection = collection || {};
this.render = function(collection) {
collection = collection || {};
renderLater = false;
cachedCollection = collection;
renderLater = false;
cachedCollection = collection;
// this is true if the attribute is empty or if the attribute value is truthy
var multiple = isAttrTruthy($scope, $attrs.ngMessagesMultiple) ||
isAttrTruthy($scope, $attrs.multiple);
// this is true if the attribute is empty or if the attribute value is truthy
var multiple = isAttrTruthy($scope, $attrs.ngMessagesMultiple) ||
isAttrTruthy($scope, $attrs.multiple);
var unmatchedMessages = [];
var matchedKeys = {};
var messageItem = ctrl.head;
var messageFound = false;
var totalMessages = 0;
var unmatchedMessages = [];
var matchedKeys = {};
var messageItem = ctrl.head;
var messageFound = false;
var totalMessages = 0;
// we use != instead of !== to allow for both undefined and null values
while (messageItem != null) {
totalMessages++;
var messageCtrl = messageItem.message;
// we use != instead of !== to allow for both undefined and null values
while (messageItem != null) {
totalMessages++;
var messageCtrl = messageItem.message;
var messageUsed = false;
if (!messageFound) {
forEach(collection, function(value, key) {
if (!messageUsed && truthy(value) && messageCtrl.test(key)) {
// this is to prevent the same error name from showing up twice
if (matchedKeys[key]) return;
matchedKeys[key] = true;
var messageUsed = false;
if (!messageFound) {
forEach(collection, function(value, key) {
if (!messageUsed && truthy(value) && messageCtrl.test(key)) {
// this is to prevent the same error name from showing up twice
if (matchedKeys[key]) return;
matchedKeys[key] = true;
messageUsed = true;
messageCtrl.attach();
}
});
}
messageUsed = true;
messageCtrl.attach();
}
});
}
if (messageUsed) {
// unless we want to display multiple messages then we should
// set a flag here to avoid displaying the next message in the list
messageFound = !multiple;
} else {
unmatchedMessages.push(messageCtrl);
}
if (messageUsed) {
// unless we want to display multiple messages then we should
// set a flag here to avoid displaying the next message in the list
messageFound = !multiple;
} else {
unmatchedMessages.push(messageCtrl);
}
messageItem = messageItem.next;
}
messageItem = messageItem.next;
}
forEach(unmatchedMessages, function(messageCtrl) {
messageCtrl.detach();
});
forEach(unmatchedMessages, function(messageCtrl) {
messageCtrl.detach();
});
unmatchedMessages.length !== totalMessages
unmatchedMessages.length !== totalMessages
? $animate.setClass($element, ACTIVE_CLASS, INACTIVE_CLASS)
: $animate.setClass($element, INACTIVE_CLASS, ACTIVE_CLASS);
};
};
$scope.$watchCollection($attrs.ngMessages || $attrs['for'], ctrl.render);
$scope.$watchCollection($attrs.ngMessages || $attrs['for'], ctrl.render);
this.reRender = function() {
if (!renderLater) {
renderLater = true;
$scope.$evalAsync(function() {
if (renderLater) {
cachedCollection && ctrl.render(cachedCollection);
}
});
}
};
// If the element is destroyed, proactively destroy all the currently visible messages
$element.on('$destroy', function() {
forEach(messages, function(item) {
item.message.detach();
});
});
this.register = function(comment, messageCtrl) {
var nextKey = latestKey.toString();
messages[nextKey] = {
message: messageCtrl
};
insertMessageNode($element[0], comment, nextKey);
comment.$$ngMessageNode = nextKey;
latestKey++;
this.reRender = function() {
if (!renderLater) {
renderLater = true;
$scope.$evalAsync(function() {
if (renderLater) {
cachedCollection && ctrl.render(cachedCollection);
}
});
}
};
ctrl.reRender();
};
this.register = function(comment, messageCtrl) {
var nextKey = latestKey.toString();
messages[nextKey] = {
message: messageCtrl
};
insertMessageNode($element[0], comment, nextKey);
comment.$$ngMessageNode = nextKey;
latestKey++;
this.deregister = function(comment) {
var key = comment.$$ngMessageNode;
delete comment.$$ngMessageNode;
removeMessageNode($element[0], comment, key);
delete messages[key];
ctrl.reRender();
};
ctrl.reRender();
};
function findPreviousMessage(parent, comment) {
var prevNode = comment;
var parentLookup = [];
while (prevNode && prevNode !== parent) {
var prevKey = prevNode.$$ngMessageNode;
if (prevKey && prevKey.length) {
return messages[prevKey];
}
this.deregister = function(comment) {
var key = comment.$$ngMessageNode;
delete comment.$$ngMessageNode;
removeMessageNode($element[0], comment, key);
delete messages[key];
ctrl.reRender();
};
// dive deeper into the DOM and examine its children for any ngMessage
// comments that may be in an element that appears deeper in the list
if (prevNode.childNodes.length && parentLookup.indexOf(prevNode) == -1) {
parentLookup.push(prevNode);
prevNode = prevNode.childNodes[prevNode.childNodes.length - 1];
} else {
prevNode = prevNode.previousSibling || prevNode.parentNode;
}
}
}
function findPreviousMessage(parent, comment) {
var prevNode = comment;
var parentLookup = [];
function insertMessageNode(parent, comment, key) {
var messageNode = messages[key];
if (!ctrl.head) {
ctrl.head = messageNode;
} else {
var match = findPreviousMessage(parent, comment);
if (match) {
messageNode.next = match.next;
match.next = messageNode;
} else {
messageNode.next = ctrl.head;
ctrl.head = messageNode;
}
}
}
while (prevNode && prevNode !== parent) {
var prevKey = prevNode.$$ngMessageNode;
if (prevKey && prevKey.length) {
return messages[prevKey];
}
function removeMessageNode(parent, comment, key) {
var messageNode = messages[key];
// dive deeper into the DOM and examine its children for any ngMessage
// comments that may be in an element that appears deeper in the list
if (prevNode.childNodes.length && parentLookup.indexOf(prevNode) === -1) {
parentLookup.push(prevNode);
prevNode = prevNode.childNodes[prevNode.childNodes.length - 1];
} else if (prevNode.previousSibling) {
prevNode = prevNode.previousSibling;
} else {
prevNode = prevNode.parentNode;
parentLookup.push(prevNode);
}
}
}
var match = findPreviousMessage(parent, comment);
if (match) {
match.next = messageNode.next;
} else {
ctrl.head = messageNode.next;
}
}
}]
};
function insertMessageNode(parent, comment, key) {
var messageNode = messages[key];
if (!ctrl.head) {
ctrl.head = messageNode;
} else {
var match = findPreviousMessage(parent, comment);
if (match) {
messageNode.next = match.next;
match.next = messageNode;
} else {
messageNode.next = ctrl.head;
ctrl.head = messageNode;
}
}
}
function isAttrTruthy(scope, attr) {
return (isString(attr) && attr.length === 0) || //empty attribute
truthy(scope.$eval(attr));
}
function removeMessageNode(parent, comment, key) {
var messageNode = messages[key];
function truthy(val) {
return isString(val) ? val.length : !!val;
}
}])
var match = findPreviousMessage(parent, comment);
if (match) {
match.next = messageNode.next;
} else {
ctrl.head = messageNode.next;
}
}
}]
};
/**
* @ngdoc directive
* @name ngMessagesInclude
* @restrict AE
* @scope
*
* @description
* `ngMessagesInclude` is a directive with the purpose to import existing ngMessage template
* code from a remote template and place the downloaded template code into the exact spot
* that the ngMessagesInclude directive is placed within the ngMessages container. This allows
* for a series of pre-defined messages to be reused and also allows for the developer to
* determine what messages are overridden due to the placement of the ngMessagesInclude directive.
*
* @usage
* ```html
* <!-- using attribute directives -->
* <ANY ng-messages="expression" role="alert">
* <ANY ng-messages-include="remoteTplString">...</ANY>
* </ANY>
*
* <!-- or by using element directives -->
* <ng-messages for="expression" role="alert">
* <ng-messages-include src="expressionValue1">...</ng-messages-include>
* </ng-messages>
* ```
*
* {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`.
*
* @param {string} ngMessagesInclude|src a string value corresponding to the remote template.
*/
.directive('ngMessagesInclude',
['$templateRequest', '$document', '$compile', function($templateRequest, $document, $compile) {
function isAttrTruthy(scope, attr) {
return (isString(attr) && attr.length === 0) || //empty attribute
truthy(scope.$eval(attr));
}
return {
restrict: 'AE',
require: '^^ngMessages', // we only require this for validation sake
link: function($scope, element, attrs) {
var src = attrs.ngMessagesInclude || attrs.src;
$templateRequest(src).then(function(html) {
$compile(html)($scope, function(contents) {
element.after(contents);
function truthy(val) {
return isString(val) ? val.length : !!val;
}
}])
// the anchor is placed for debugging purposes
var anchor = jqLite($document[0].createComment(' ngMessagesInclude: ' + src + ' '));
element.after(anchor);
/**
* @ngdoc directive
* @name ngMessagesInclude
* @restrict AE
* @scope
*
* @description
* `ngMessagesInclude` is a directive with the purpose to import existing ngMessage template
* code from a remote template and place the downloaded template code into the exact spot
* that the ngMessagesInclude directive is placed within the ngMessages container. This allows
* for a series of pre-defined messages to be reused and also allows for the developer to
* determine what messages are overridden due to the placement of the ngMessagesInclude directive.
*
* @usage
* ```html
* <!-- using attribute directives -->
* <ANY ng-messages="expression" role="alert">
* <ANY ng-messages-include="remoteTplString">...</ANY>
* </ANY>
*
* <!-- or by using element directives -->
* <ng-messages for="expression" role="alert">
* <ng-messages-include src="expressionValue1">...</ng-messages-include>
* </ng-messages>
* ```
*
* {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`.
*
* @param {string} ngMessagesInclude|src a string value corresponding to the remote template.
*/
.directive('ngMessagesInclude',
['$templateRequest', '$document', '$compile', function($templateRequest, $document, $compile) {
// we don't want to pollute the DOM anymore by keeping an empty directive element
element.remove();
});
});
}
};
}])
return {
restrict: 'AE',
require: '^^ngMessages', // we only require this for validation sake
link: function($scope, element, attrs) {
var src = attrs.ngMessagesInclude || attrs.src;
$templateRequest(src).then(function(html) {
if ($scope.$$destroyed) return;
/**
* @ngdoc directive
* @name ngMessage
* @restrict AE
* @scope
*
* @description
* `ngMessage` is a directive with the purpose to show and hide a particular message.
* For `ngMessage` to operate, a parent `ngMessages` directive on a parent DOM element
* must be situated since it determines which messages are visible based on the state
* of the provided key/value map that `ngMessages` listens on.
*
* More information about using `ngMessage` can be found in the
* {@link module:ngMessages `ngMessages` module documentation}.
*
* @usage
* ```html
* <!-- using attribute directives -->
* <ANY ng-messages="expression" role="alert">
* <ANY ng-message="stringValue">...</ANY>
* <ANY ng-message="stringValue1, stringValue2, ...">...</ANY>
* </ANY>
*
* <!-- or by using element directives -->
* <ng-messages for="expression" role="alert">
* <ng-message when="stringValue">...</ng-message>
* <ng-message when="stringValue1, stringValue2, ...">...</ng-message>
* </ng-messages>
* ```
*
* @param {expression} ngMessage|when a string value corresponding to the message key.
*/
.directive('ngMessage', ngMessageDirectiveFactory('AE'))
if (isString(html) && !html.trim()) {
// Empty template - nothing to compile
replaceElementWithMarker(element, src);
} else {
// Non-empty template - compile and link
$compile(html)($scope, function(contents) {
element.after(contents);
replaceElementWithMarker(element, src);
});
}
});
}
};
// Helpers
function replaceElementWithMarker(element, src) {
// A comment marker is placed for debugging purposes
var comment = $compile.$$createComment ?
$compile.$$createComment('ngMessagesInclude', src) :
$document[0].createComment(' ngMessagesInclude: ' + src + ' ');
var marker = jqLite(comment);
element.after(marker);
// Don't pollute the DOM anymore by keeping an empty directive element
element.remove();
}
}])
/**
* @ngdoc directive
* @name ngMessage
* @restrict AE
* @scope
*
* @description
* `ngMessage` is a directive with the purpose to show and hide a particular message.
* For `ngMessage` to operate, a parent `ngMessages` directive on a parent DOM element
* must be situated since it determines which messages are visible based on the state
* of the provided key/value map that `ngMessages` listens on.
*
* More information about using `ngMessage` can be found in the
* {@link module:ngMessages `ngMessages` module documentation}.
*
* @usage
* ```html
* <!-- using attribute directives -->
* <ANY ng-messages="expression" role="alert">
* <ANY ng-message="stringValue">...</ANY>
* <ANY ng-message="stringValue1, stringValue2, ...">...</ANY>
* </ANY>
*
* <!-- or by using element directives -->
* <ng-messages for="expression" role="alert">
* <ng-message when="stringValue">...</ng-message>
* <ng-message when="stringValue1, stringValue2, ...">...</ng-message>
* </ng-messages>
* ```
*
* @param {expression} ngMessage|when a string value corresponding to the message key.
*/
.directive('ngMessage', ngMessageDirectiveFactory())
/**
* @ngdoc directive
* @name ngMessageExp
* @restrict AE
* @scope
*
* @description
* `ngMessageExp` is a directive with the purpose to show and hide a particular message.
* For `ngMessageExp` to operate, a parent `ngMessages` directive on a parent DOM element
* must be situated since it determines which messages are visible based on the state
* of the provided key/value map that `ngMessages` listens on.
*
* @usage
* ```html
* <!-- using attribute directives -->
* <ANY ng-messages="expression">
* <ANY ng-message-exp="expressionValue">...</ANY>
* </ANY>
*
* <!-- or by using element directives -->
* <ng-messages for="expression">
* <ng-message when-exp="expressionValue">...</ng-message>
* </ng-messages>
* ```
*
* {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`.
*
* @param {expression} ngMessageExp|whenExp an expression value corresponding to the message key.
*/
.directive('ngMessageExp', ngMessageDirectiveFactory('A'));
/**
* @ngdoc directive
* @name ngMessageExp
* @restrict AE
* @priority 1
* @scope
*
* @description
* `ngMessageExp` is a directive with the purpose to show and hide a particular message.
* For `ngMessageExp` to operate, a parent `ngMessages` directive on a parent DOM element
* must be situated since it determines which messages are visible based on the state
* of the provided key/value map that `ngMessages` listens on.
*
* @usage
* ```html
* <!-- using attribute directives -->
* <ANY ng-messages="expression">
* <ANY ng-message-exp="expressionValue">...</ANY>
* </ANY>
*
* <!-- or by using element directives -->
* <ng-messages for="expression">
* <ng-message when-exp="expressionValue">...</ng-message>
* </ng-messages>
* ```
*
* {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`.
*
* @param {expression} ngMessageExp|whenExp an expression value corresponding to the message key.
*/
.directive('ngMessageExp', ngMessageDirectiveFactory());
function ngMessageDirectiveFactory(restrict) {
function ngMessageDirectiveFactory() {
return ['$animate', function($animate) {
return {
restrict: 'AE',
transclude: 'element',
priority: 1, // must run before ngBind, otherwise the text is set on the comment
terminal: true,
require: '^^ngMessages',
link: function(scope, element, attrs, ngMessagesCtrl, $transclude) {
@ -641,8 +673,8 @@ function ngMessageDirectiveFactory(restrict) {
var assignRecords = function(items) {
records = items
? (isArray(items)
? items
: items.split(/[\s,]+/))
? items
: items.split(/[\s,]+/))
: null;
ngMessagesCtrl.reRender();
};
@ -661,7 +693,7 @@ function ngMessageDirectiveFactory(restrict) {
},
attach: function() {
if (!currentElement) {
$transclude(scope, function(elm) {
$transclude(function(elm, newScope) {
$animate.enter(elm, null, element);
currentElement = elm;
@ -669,14 +701,15 @@ function ngMessageDirectiveFactory(restrict) {
// when we are destroying the node later.
var $$attachId = currentElement.$$attachId = ngMessagesCtrl.getAttachId();
// in the event that the parent element is destroyed
// by any other structural directive then it's time
// in the event that the element or a parent element is destroyed
// by another structural directive then it's time
// to deregister the message from the controller
currentElement.on('$destroy', function() {
if (currentElement && currentElement.$$attachId === $$attachId) {
ngMessagesCtrl.deregister(commentNode);
messageCtrl.detach();
}
newScope.$destroy();
});
});
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,9 @@
/**
* @license AngularJS v1.4.10
* (c) 2010-2015 Google, Inc. http://angularjs.org
* @license AngularJS v1.5.8
* (c) 2010-2016 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {'use strict';
(function(window, angular) {'use strict';
var $resourceMinErr = angular.$$minErr('$resource');
@ -61,13 +61,30 @@ function shallowClearAndCopy(src, dst) {
*
* <div doc-module-components="ngResource"></div>
*
* See {@link ngResource.$resource `$resource`} for usage.
* See {@link ngResource.$resourceProvider} and {@link ngResource.$resource} for usage.
*/
/**
* @ngdoc provider
* @name $resourceProvider
*
* @description
*
* Use `$resourceProvider` to change the default behavior of the {@link ngResource.$resource}
* service.
*
* ## Dependencies
* Requires the {@link ngResource } module to be installed.
*
*/
/**
* @ngdoc service
* @name $resource
* @requires $http
* @requires ng.$log
* @requires $q
* @requires ng.$timeout
*
* @description
* A factory which creates a resource object that lets you interact with
@ -102,8 +119,9 @@ function shallowClearAndCopy(src, dst) {
* can escape it with `/\.`.
*
* @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
* `actions` methods. If a parameter value is a function, it will be executed every time
* when a param value needs to be obtained for a request (unless the param was overridden).
* `actions` methods. If a parameter value is a function, it will be called every time
* a param value needs to be obtained for a request (unless the param was overridden). The function
* will be passed the current data value as an argument.
*
* Each key value in the parameter object is first bound to url template if present and then any
* excess keys are appended to the url search query after the `?`.
@ -111,10 +129,13 @@ function shallowClearAndCopy(src, dst) {
* Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in
* URL `/path/greet?salutation=Hello`.
*
* If the parameter value is prefixed with `@` then the value for that parameter will be extracted
* from the corresponding property on the `data` object (provided when calling an action method). For
* example, if the `defaultParam` object is `{someParam: '@someProp'}` then the value of `someParam`
* will be `data.someProp`.
* If the parameter value is prefixed with `@`, then the value for that parameter will be
* extracted from the corresponding property on the `data` object (provided when calling a
* "non-GET" action method).
* For example, if the `defaultParam` object is `{someParam: '@someProp'}` then the value of
* `someParam` will be `data.someProp`.
* Note that the parameter will be ignored, when calling a "GET" action method (i.e. an action
* method that does not accept a request body)
*
* @param {Object.<Object>=} actions Hash with declaration of custom actions that should extend
* the default set of resource actions. The declaration should be created in the format of {@link
@ -131,8 +152,9 @@ function shallowClearAndCopy(src, dst) {
* - **`method`** {string} Case insensitive HTTP method (e.g. `GET`, `POST`, `PUT`,
* `DELETE`, `JSONP`, etc).
* - **`params`** {Object=} Optional set of pre-bound parameters for this action. If any of
* the parameter value is a function, it will be executed every time when a param value needs to
* be obtained for a request (unless the param was overridden).
* the parameter value is a function, it will be called every time when a param value needs to
* be obtained for a request (unless the param was overridden). The function will be passed the
* current data value as an argument.
* - **`url`** {string} action specific `url` override. The url templating is supported just
* like for the resource-level urls.
* - **`isArray`** {boolean=} If true then the returned object for this action is an array,
@ -148,9 +170,9 @@ function shallowClearAndCopy(src, dst) {
* `{function(data, headersGetter)|Array.<function(data, headersGetter)>}`
* transform function or an array of such functions. The transform function takes the http
* response body and headers and returns its transformed (typically deserialized) version.
* By default, transformResponse will contain one function that checks if the response looks like
* a JSON string and deserializes it using `angular.fromJson`. To prevent this behavior, set
* `transformResponse` to an empty array: `transformResponse: []`
* By default, transformResponse will contain one function that checks if the response looks
* like a JSON string and deserializes it using `angular.fromJson`. To prevent this behavior,
* set `transformResponse` to an empty array: `transformResponse: []`
* - **`cache`** `{boolean|Cache}` If true, a default $http cache will be used to cache the
* GET request, otherwise if a cache instance built with
* {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
@ -158,8 +180,11 @@ function shallowClearAndCopy(src, dst) {
* - **`timeout`** `{number}` timeout in milliseconds.<br />
* **Note:** In contrast to {@link ng.$http#usage $http.config}, {@link ng.$q promises} are
* **not** supported in $resource, because the same value would be used for multiple requests.
* If you need support for cancellable $resource actions, you should upgrade to version 1.5 or
* higher.
* If you are looking for a way to cancel requests, you should use the `cancellable` option.
* - **`cancellable`** `{boolean}` if set to true, the request made by a "non-instance" call
* will be cancelled (if not already completed) by calling `$cancelRequest()` on the call's
* return value. Calling `$cancelRequest()` for a non-cancellable or an already
* completed/cancelled request will have no effect.<br />
* - **`withCredentials`** - `{boolean}` - whether to set the `withCredentials` flag on the
* XHR object. See
* [requests with credentials](https://developer.mozilla.org/en/http_access_control#section_5)
@ -171,12 +196,13 @@ function shallowClearAndCopy(src, dst) {
* with `http response` object. See {@link ng.$http $http interceptors}.
*
* @param {Object} options Hash with custom settings that should extend the
* default `$resourceProvider` behavior. The only supported option is
*
* Where:
* default `$resourceProvider` behavior. The supported options are:
*
* - **`stripTrailingSlashes`** {boolean} If true then the trailing
* slashes from any calculated URL will be stripped. (Defaults to true.)
* - **`cancellable`** {boolean} If true, the request made by a "non-instance" call will be
* cancelled (if not already completed) by calling `$cancelRequest()` on the call's return value.
* This can be overwritten per action. (Defaults to false.)
*
* @returns {Object} A resource "class" object with methods for the default set of resource actions
* optionally extended with custom `actions`. The default set contains these actions:
@ -224,7 +250,7 @@ function shallowClearAndCopy(src, dst) {
* Class actions return empty instance (with additional properties below).
* Instance actions return promise of the action.
*
* The Resource instances and collection have these additional properties:
* The Resource instances and collections have these additional properties:
*
* - `$promise`: the {@link ng.$q promise} of the original server interaction that created this
* instance or collection.
@ -244,6 +270,19 @@ function shallowClearAndCopy(src, dst) {
* rejection), `false` before that. Knowing if the Resource has been resolved is useful in
* data-binding.
*
* The Resource instances and collections have these additional methods:
*
* - `$cancelRequest`: If there is a cancellable, pending request related to the instance or
* collection, calling this method will abort the request.
*
* The Resource instances have these additional methods:
*
* - `toJSON`: It returns a simple object without any of the extra properties added as part of
* the Resource API. This object can be serialized through {@link angular.toJson} safely
* without attaching Angular-specific fields. Notice that `JSON.stringify` (and
* `angular.toJson`) automatically use this method when serializing a Resource instance
* (see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#toJSON()_behavior)).
*
* @example
*
* # Credit card resource
@ -288,6 +327,11 @@ function shallowClearAndCopy(src, dst) {
*
* Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and
* `headers`.
*
* @example
*
* # User resource
*
* When the data is returned from the server then the object is an instance of the resource type and
* all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD
* operations (create, read, update, delete) on server-side data.
@ -306,10 +350,10 @@ function shallowClearAndCopy(src, dst) {
*
```js
var User = $resource('/user/:userId', {userId:'@id'});
User.get({userId:123}, function(u, getResponseHeaders){
u.abc = true;
u.$save(function(u, putResponseHeaders) {
//u => saved user object
User.get({userId:123}, function(user, getResponseHeaders){
user.abc = true;
user.$save(function(user, putResponseHeaders) {
//user => saved user object
//putResponseHeaders => $http header getter
});
});
@ -324,8 +368,11 @@ function shallowClearAndCopy(src, dst) {
$scope.user = user;
});
```
*
* @example
*
* # Creating a custom 'PUT' request
*
* In this example we create a custom method on our resource to make a PUT request
* ```js
* var app = angular.module('app', ['ngResource', 'ngRoute']);
@ -353,16 +400,112 @@ function shallowClearAndCopy(src, dst) {
* // This will PUT /notes/ID with the note object in the request payload
* }]);
* ```
*
* @example
*
* # Cancelling requests
*
* If an action's configuration specifies that it is cancellable, you can cancel the request related
* to an instance or collection (as long as it is a result of a "non-instance" call):
*
```js
// ...defining the `Hotel` resource...
var Hotel = $resource('/api/hotel/:id', {id: '@id'}, {
// Let's make the `query()` method cancellable
query: {method: 'get', isArray: true, cancellable: true}
});
// ...somewhere in the PlanVacationController...
...
this.onDestinationChanged = function onDestinationChanged(destination) {
// We don't care about any pending request for hotels
// in a different destination any more
this.availableHotels.$cancelRequest();
// Let's query for hotels in '<destination>'
// (calls: /api/hotel?location=<destination>)
this.availableHotels = Hotel.query({location: destination});
};
```
*
*/
angular.module('ngResource', ['ng']).
provider('$resource', function() {
var PROTOCOL_AND_DOMAIN_REGEX = /^https?:\/\/[^\/]*/;
var provider = this;
/**
* @ngdoc property
* @name $resourceProvider#defaults
* @description
* Object containing default options used when creating `$resource` instances.
*
* The default values satisfy a wide range of usecases, but you may choose to overwrite any of
* them to further customize your instances. The available properties are:
*
* - **stripTrailingSlashes** `{boolean}` If true, then the trailing slashes from any
* calculated URL will be stripped.<br />
* (Defaults to true.)
* - **cancellable** `{boolean}` If true, the request made by a "non-instance" call will be
* cancelled (if not already completed) by calling `$cancelRequest()` on the call's return
* value. For more details, see {@link ngResource.$resource}. This can be overwritten per
* resource class or action.<br />
* (Defaults to false.)
* - **actions** - `{Object.<Object>}` - A hash with default actions declarations. Actions are
* high-level methods corresponding to RESTful actions/methods on resources. An action may
* specify what HTTP method to use, what URL to hit, if the return value will be a single
* object or a collection (array) of objects etc. For more details, see
* {@link ngResource.$resource}. The actions can also be enhanced or overwritten per resource
* class.<br />
* The default actions are:
* ```js
* {
* get: {method: 'GET'},
* save: {method: 'POST'},
* query: {method: 'GET', isArray: true},
* remove: {method: 'DELETE'},
* delete: {method: 'DELETE'}
* }
* ```
*
* #### Example
*
* For example, you can specify a new `update` action that uses the `PUT` HTTP verb:
*
* ```js
* angular.
* module('myApp').
* config(['resourceProvider', function ($resourceProvider) {
* $resourceProvider.defaults.actions.update = {
* method: 'PUT'
* };
* });
* ```
*
* Or you can even overwrite the whole `actions` list and specify your own:
*
* ```js
* angular.
* module('myApp').
* config(['resourceProvider', function ($resourceProvider) {
* $resourceProvider.defaults.actions = {
* create: {method: 'POST'}
* get: {method: 'GET'},
* getAll: {method: 'GET', isArray:true},
* update: {method: 'PUT'},
* delete: {method: 'DELETE'}
* };
* });
* ```
*
*/
this.defaults = {
// Strip slashes by default
stripTrailingSlashes: true,
// Make non-instance requests cancellable (via `$cancelRequest()`)
cancellable: false,
// Default actions configuration
actions: {
'get': {method: 'GET'},
@ -373,7 +516,7 @@ angular.module('ngResource', ['ng']).
}
};
this.$get = ['$http', '$log', '$q', function($http, $log, $q) {
this.$get = ['$http', '$log', '$q', '$timeout', function($http, $log, $q, $timeout) {
var noop = angular.noop,
forEach = angular.forEach,
@ -441,7 +584,9 @@ angular.module('ngResource', ['ng']).
}
if (!(new RegExp("^\\d+$").test(param)) && param &&
(new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) {
urlParams[param] = true;
urlParams[param] = {
isQueryParamValue: (new RegExp("\\?.*=:" + param + "(?:\\W|$)")).test(url)
};
}
});
url = url.replace(/\\:/g, ':');
@ -451,10 +596,14 @@ angular.module('ngResource', ['ng']).
});
params = params || {};
forEach(self.urlParams, function(_, urlParam) {
forEach(self.urlParams, function(paramInfo, urlParam) {
val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam];
if (angular.isDefined(val) && val !== null) {
encodedVal = encodeUriSegment(val);
if (paramInfo.isQueryParamValue) {
encodedVal = encodeUriQuery(val, true);
} else {
encodedVal = encodeUriSegment(val);
}
url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), function(match, p1) {
return encodedVal + p1;
});
@ -502,7 +651,7 @@ angular.module('ngResource', ['ng']).
var ids = {};
actionParams = extend({}, paramDefaults, actionParams);
forEach(actionParams, function(value, key) {
if (isFunction(value)) { value = value(); }
if (isFunction(value)) { value = value(data); }
ids[key] = value && value.charAt && value.charAt(0) == '@' ?
lookupDottedPath(data, value.substr(1)) : value;
});
@ -526,6 +675,20 @@ angular.module('ngResource', ['ng']).
forEach(actions, function(action, name) {
var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method);
var numericTimeout = action.timeout;
var cancellable = angular.isDefined(action.cancellable) ? action.cancellable :
(options && angular.isDefined(options.cancellable)) ? options.cancellable :
provider.defaults.cancellable;
if (numericTimeout && !angular.isNumber(numericTimeout)) {
$log.debug('ngResource:\n' +
' Only numeric values are allowed as `timeout`.\n' +
' Promises are not supported in $resource, because the same value would ' +
'be used for multiple requests. If you are looking for a way to cancel ' +
'requests, you should use the `cancellable` option.');
delete action.timeout;
numericTimeout = null;
}
Resource[name] = function(a1, a2, a3, a4) {
var params = {}, data, success, error;
@ -574,6 +737,8 @@ angular.module('ngResource', ['ng']).
defaultResponseInterceptor;
var responseErrorInterceptor = action.interceptor && action.interceptor.responseError ||
undefined;
var timeoutDeferred;
var numericTimeoutPromise;
forEach(action, function(value, key) {
switch (key) {
@ -583,28 +748,27 @@ angular.module('ngResource', ['ng']).
case 'params':
case 'isArray':
case 'interceptor':
break;
case 'timeout':
if (value && !angular.isNumber(value)) {
$log.debug('ngResource:\n' +
' Only numeric values are allowed as `timeout`.\n' +
' Promises are not supported in $resource, because the same value would ' +
'be used for multiple requests.\n' +
' If you need support for cancellable $resource actions, you should ' +
'upgrade to version 1.5 or higher.');
}
case 'cancellable':
break;
}
});
if (!isInstanceCall && cancellable) {
timeoutDeferred = $q.defer();
httpConfig.timeout = timeoutDeferred.promise;
if (numericTimeout) {
numericTimeoutPromise = $timeout(timeoutDeferred.resolve, numericTimeout);
}
}
if (hasBody) httpConfig.data = data;
route.setUrlParams(httpConfig,
extend({}, extractParams(data, action.params || {}), params),
action.url);
var promise = $http(httpConfig).then(function(response) {
var data = response.data,
promise = value.$promise;
var data = response.data;
if (data) {
// Need to convert action.isArray to boolean in case it is undefined
@ -629,24 +793,28 @@ angular.module('ngResource', ['ng']).
}
});
} else {
var promise = value.$promise; // Save the promise
shallowClearAndCopy(data, value);
value.$promise = promise;
value.$promise = promise; // Restore the promise
}
}
value.$resolved = true;
response.resource = value;
return response;
}, function(response) {
value.$resolved = true;
(error || noop)(response);
return $q.reject(response);
});
promise['finally'](function() {
value.$resolved = true;
if (!isInstanceCall && cancellable) {
value.$cancelRequest = angular.noop;
$timeout.cancel(numericTimeoutPromise);
timeoutDeferred = numericTimeoutPromise = httpConfig.timeout = null;
}
});
promise = promise.then(
function(response) {
var value = responseInterceptor(response);
@ -661,6 +829,7 @@ angular.module('ngResource', ['ng']).
// - return the instance / collection
value.$promise = promise;
value.$resolved = false;
if (cancellable) value.$cancelRequest = timeoutDeferred.resolve;
return value;
}

View File

@ -1,9 +1,43 @@
/**
* @license AngularJS v1.4.10
* (c) 2010-2015 Google, Inc. http://angularjs.org
* @license AngularJS v1.5.8
* (c) 2010-2016 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {'use strict';
(function(window, angular) {'use strict';
/* global shallowCopy: true */
/**
* Creates a shallow copy of an object, an array or a primitive.
*
* Assumes that there are no proto properties for objects.
*/
function shallowCopy(src, dst) {
if (isArray(src)) {
dst = dst || [];
for (var i = 0, ii = src.length; i < ii; i++) {
dst[i] = src[i];
}
} else if (isObject(src)) {
dst = dst || {};
for (var key in src) {
if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) {
dst[key] = src[key];
}
}
}
return dst || src;
}
/* global shallowCopy: false */
// There are necessary for `shallowCopy()` (included via `src/shallowCopy.js`).
// They are initialized inside the `$RouteProvider`, to ensure `window.angular` is available.
var isArray;
var isObject;
/**
* @ngdoc module
@ -40,6 +74,9 @@ var ngRouteModule = angular.module('ngRoute', ['ng']).
* Requires the {@link ngRoute `ngRoute`} module to be installed.
*/
function $RouteProvider() {
isArray = angular.isArray;
isObject = angular.isObject;
function inherit(parent, extra) {
return angular.extend(Object.create(parent), extra);
}
@ -105,8 +142,17 @@ function $RouteProvider() {
* If all the promises are resolved successfully, the values of the resolved promises are
* injected and {@link ngRoute.$route#$routeChangeSuccess $routeChangeSuccess} event is
* fired. If any of the promises are rejected the
* {@link ngRoute.$route#$routeChangeError $routeChangeError} event is fired. The map object
* is:
* {@link ngRoute.$route#$routeChangeError $routeChangeError} event is fired.
* For easier access to the resolved dependencies from the template, the `resolve` map will
* be available on the scope of the route, under `$resolve` (by default) or a custom name
* specified by the `resolveAs` property (see below). This can be particularly useful, when
* working with {@link angular.Module#component components} as route templates.<br />
* <div class="alert alert-warning">
* **Note:** If your scope already contains a property with this name, it will be hidden
* or overwritten. Make sure, you specify an appropriate name for this property, that
* does not collide with other properties on the scope.
* </div>
* The map object is:
*
* - `key` `{string}`: a name of a dependency to be injected into the controller.
* - `factory` - `{string|function}`: If `string` then it is an alias for a service.
@ -116,7 +162,10 @@ function $RouteProvider() {
* `ngRoute.$routeParams` will still refer to the previous route within these resolve
* functions. Use `$route.current.params` to access the new route parameters, instead.
*
* - `redirectTo` {(string|function())=} value to update
* - `resolveAs` - `{string=}` - The name under which the `resolve` map will be available on
* the scope of the route. If omitted, defaults to `$resolve`.
*
* - `redirectTo` `{(string|function())=}` value to update
* {@link ng.$location $location} path with and trigger route redirection.
*
* If `redirectTo` is a function, it will be called with the following parameters:
@ -129,13 +178,13 @@ function $RouteProvider() {
* The custom `redirectTo` function is expected to return a string which will be used
* to update `$location.path()` and `$location.search()`.
*
* - `[reloadOnSearch=true]` - {boolean=} - reload route when only `$location.search()`
* - `[reloadOnSearch=true]` - `{boolean=}` - reload route when only `$location.search()`
* or `$location.hash()` changes.
*
* If the option is set to `false` and url in the browser changes, then
* `$routeUpdate` event is broadcasted on the root scope.
*
* - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive
* - `[caseInsensitiveMatch=false]` - `{boolean=}` - match routes without being case sensitive
*
* If the option is set to `true`, then the particular route can be matched without being
* case sensitive
@ -147,7 +196,7 @@ function $RouteProvider() {
*/
this.when = function(path, route) {
//copy original route object to preserve params inherited from proto chain
var routeCopy = angular.copy(route);
var routeCopy = shallowCopy(route);
if (angular.isUndefined(routeCopy.reloadOnSearch)) {
routeCopy.reloadOnSearch = true;
}
@ -265,7 +314,7 @@ function $RouteProvider() {
* @property {Object} current Reference to the current route definition.
* The route definition contains:
*
* - `controller`: The controller constructor as define in route definition.
* - `controller`: The controller constructor as defined in the route definition.
* - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for
* controller instantiation. The `locals` contain
* the resolved values of the `resolve` map. Additionally the `locals` also contain:
@ -273,6 +322,10 @@ function $RouteProvider() {
* - `$scope` - The current route scope.
* - `$template` - The current route template HTML.
*
* The `locals` will be assigned to the route scope's `$resolve` property. You can override
* the property name, using `resolveAs` in the route definition. See
* {@link ngRoute.$routeProvider $routeProvider} for more info.
*
* @property {Object} routes Object with all route configuration Objects as its properties.
*
* @description
@ -588,35 +641,7 @@ function $RouteProvider() {
}
$q.when(nextRoute).
then(function() {
if (nextRoute) {
var locals = angular.extend({}, nextRoute.resolve),
template, templateUrl;
angular.forEach(locals, function(value, key) {
locals[key] = angular.isString(value) ?
$injector.get(value) : $injector.invoke(value, null, null, key);
});
if (angular.isDefined(template = nextRoute.template)) {
if (angular.isFunction(template)) {
template = template(nextRoute.params);
}
} else if (angular.isDefined(templateUrl = nextRoute.templateUrl)) {
if (angular.isFunction(templateUrl)) {
templateUrl = templateUrl(nextRoute.params);
}
if (angular.isDefined(templateUrl)) {
nextRoute.loadedTemplateUrl = $sce.valueOf(templateUrl);
template = $templateRequest(templateUrl);
}
}
if (angular.isDefined(template)) {
locals['$template'] = template;
}
return $q.all(locals);
}
}).
then(resolveLocals).
then(function(locals) {
// after route change
if (nextRoute == $route.current) {
@ -634,6 +659,41 @@ function $RouteProvider() {
}
}
function resolveLocals(route) {
if (route) {
var locals = angular.extend({}, route.resolve);
angular.forEach(locals, function(value, key) {
locals[key] = angular.isString(value) ?
$injector.get(value) :
$injector.invoke(value, null, null, key);
});
var template = getTemplateFor(route);
if (angular.isDefined(template)) {
locals['$template'] = template;
}
return $q.all(locals);
}
}
function getTemplateFor(route) {
var template, templateUrl;
if (angular.isDefined(template = route.template)) {
if (angular.isFunction(template)) {
template = template(route.params);
}
} else if (angular.isDefined(templateUrl = route.templateUrl)) {
if (angular.isFunction(templateUrl)) {
templateUrl = templateUrl(route.params);
}
if (angular.isDefined(templateUrl)) {
route.loadedTemplateUrl = $sce.valueOf(templateUrl);
template = $templateRequest(templateUrl);
}
}
return template;
}
/**
* @returns {Object} the current active route, by matching it against the URL
@ -733,11 +793,20 @@ ngRouteModule.directive('ngView', ngViewFillContentFactory);
* Requires the {@link ngRoute `ngRoute`} module to be installed.
*
* @animations
* enter - animation is used to bring new content into the browser.
* leave - animation is used to animate existing content away.
* | Animation | Occurs |
* |----------------------------------|-------------------------------------|
* | {@link ng.$animate#enter enter} | when the new element is inserted to the DOM |
* | {@link ng.$animate#leave leave} | when the old element is removed from to the DOM |
*
* The enter and leave animation occur concurrently.
*
* @knownIssue If `ngView` is contained in an asynchronously loaded template (e.g. in another
* directive's templateUrl or in a template loaded using `ngInclude`), then you need to
* make sure that `$route` is instantiated in time to capture the initial
* `$locationChangeStart` event and load the appropriate view. One way to achieve this
* is to have it as a dependency in a `.run` block:
* `myModule.run(['$route', function() {}]);`
*
* @scope
* @priority 400
* @param {string=} onload Expression to evaluate whenever the view updates.
@ -989,6 +1058,7 @@ function ngViewFillContentFactory($compile, $controller, $route) {
$element.data('$ngControllerController', controller);
$element.children().data('$ngControllerController', controller);
}
scope[current.resolveAs || '$resolve'] = locals;
link(scope);
}

View File

@ -1,9 +1,9 @@
/**
* @license AngularJS v1.4.10
* (c) 2010-2015 Google, Inc. http://angularjs.org
* @license AngularJS v1.5.8
* (c) 2010-2016 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {'use strict';
(function(window, angular) {'use strict';
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Any commits to this file should be reviewed with security in mind. *
@ -17,6 +17,14 @@
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
var $sanitizeMinErr = angular.$$minErr('$sanitize');
var bind;
var extend;
var forEach;
var isDefined;
var lowercase;
var noop;
var htmlParser;
var htmlSanitizeWriter;
/**
* @ngdoc module
@ -33,36 +41,23 @@ var $sanitizeMinErr = angular.$$minErr('$sanitize');
* See {@link ngSanitize.$sanitize `$sanitize`} for usage.
*/
/*
* HTML Parser By Misko Hevery (misko@hevery.com)
* based on: HTML Parser By John Resig (ejohn.org)
* Original code by Erik Arvidsson, Mozilla Public License
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
*
* // Use like so:
* htmlParser(htmlString, {
* start: function(tag, attrs, unary) {},
* end: function(tag) {},
* chars: function(text) {},
* comment: function(text) {}
* });
*
*/
/**
* @ngdoc service
* @name $sanitize
* @kind function
*
* @description
* Sanitizes an html string by stripping all potentially dangerous tokens.
*
* The input is sanitized by parsing the HTML into tokens. All safe tokens (from a whitelist) are
* then serialized back to properly escaped html string. This means that no unsafe input can make
* it into the returned string, however, since our parser is more strict than a typical browser
* parser, it's possible that some obscure input, which would be recognized as valid HTML by a
* browser, won't make it through the sanitizer. The input may also contain SVG markup.
* The whitelist is configured using the functions `aHrefSanitizationWhitelist` and
* `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider `$compileProvider`}.
* it into the returned string.
*
* The whitelist for URL sanitization of attribute values is configured using the functions
* `aHrefSanitizationWhitelist` and `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider
* `$compileProvider`}.
*
* The input may also contain SVG markup if this is enabled via {@link $sanitizeProvider}.
*
* @param {string} html HTML input.
* @returns {string} Sanitized HTML.
@ -148,401 +143,426 @@ var $sanitizeMinErr = angular.$$minErr('$sanitize');
</file>
</example>
*/
/**
* @ngdoc provider
* @name $sanitizeProvider
*
* @description
* Creates and configures {@link $sanitize} instance.
*/
function $SanitizeProvider() {
var svgEnabled = false;
this.$get = ['$$sanitizeUri', function($$sanitizeUri) {
if (svgEnabled) {
extend(validElements, svgElements);
}
return function(html) {
var buf = [];
htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) {
return !/^unsafe/.test($$sanitizeUri(uri, isImage));
return !/^unsafe:/.test($$sanitizeUri(uri, isImage));
}));
return buf.join('');
};
}];
/**
* @ngdoc method
* @name $sanitizeProvider#enableSvg
* @kind function
*
* @description
* Enables a subset of svg to be supported by the sanitizer.
*
* <div class="alert alert-warning">
* <p>By enabling this setting without taking other precautions, you might expose your
* application to click-hijacking attacks. In these attacks, sanitized svg elements could be positioned
* outside of the containing element and be rendered over other elements on the page (e.g. a login
* link). Such behavior can then result in phishing incidents.</p>
*
* <p>To protect against these, explicitly setup `overflow: hidden` css rule for all potential svg
* tags within the sanitized content:</p>
*
* <br>
*
* <pre><code>
* .rootOfTheIncludedContent svg {
* overflow: hidden !important;
* }
* </code></pre>
* </div>
*
* @param {boolean=} flag Enable or disable SVG support in the sanitizer.
* @returns {boolean|ng.$sanitizeProvider} Returns the currently configured value if called
* without an argument or self for chaining otherwise.
*/
this.enableSvg = function(enableSvg) {
if (isDefined(enableSvg)) {
svgEnabled = enableSvg;
return this;
} else {
return svgEnabled;
}
};
//////////////////////////////////////////////////////////////////////////////////////////////////
// Private stuff
//////////////////////////////////////////////////////////////////////////////////////////////////
bind = angular.bind;
extend = angular.extend;
forEach = angular.forEach;
isDefined = angular.isDefined;
lowercase = angular.lowercase;
noop = angular.noop;
htmlParser = htmlParserImpl;
htmlSanitizeWriter = htmlSanitizeWriterImpl;
// Regular Expressions for parsing tags and attributes
var SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
// Match everything outside of normal chars and " (quote character)
NON_ALPHANUMERIC_REGEXP = /([^\#-~ |!])/g;
// Good source of info about elements and attributes
// http://dev.w3.org/html5/spec/Overview.html#semantics
// http://simon.html5.org/html-elements
// Safe Void Elements - HTML5
// http://dev.w3.org/html5/spec/Overview.html#void-elements
var voidElements = toMap("area,br,col,hr,img,wbr");
// Elements that you can, intentionally, leave open (and which close themselves)
// http://dev.w3.org/html5/spec/Overview.html#optional-tags
var optionalEndTagBlockElements = toMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),
optionalEndTagInlineElements = toMap("rp,rt"),
optionalEndTagElements = extend({},
optionalEndTagInlineElements,
optionalEndTagBlockElements);
// Safe Block Elements - HTML5
var blockElements = extend({}, optionalEndTagBlockElements, toMap("address,article," +
"aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," +
"h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,section,table,ul"));
// Inline Elements - HTML5
var inlineElements = extend({}, optionalEndTagInlineElements, toMap("a,abbr,acronym,b," +
"bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," +
"samp,small,span,strike,strong,sub,sup,time,tt,u,var"));
// SVG Elements
// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Elements
// Note: the elements animate,animateColor,animateMotion,animateTransform,set are intentionally omitted.
// They can potentially allow for arbitrary javascript to be executed. See #11290
var svgElements = toMap("circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph," +
"hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline," +
"radialGradient,rect,stop,svg,switch,text,title,tspan");
// Blocked Elements (will be stripped)
var blockedElements = toMap("script,style");
var validElements = extend({},
voidElements,
blockElements,
inlineElements,
optionalEndTagElements);
//Attributes that have href and hence need to be sanitized
var uriAttrs = toMap("background,cite,href,longdesc,src,xlink:href");
var htmlAttrs = toMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' +
'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,' +
'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,' +
'scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,' +
'valign,value,vspace,width');
// SVG attributes (without "id" and "name" attributes)
// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Attributes
var svgAttrs = toMap('accent-height,accumulate,additive,alphabetic,arabic-form,ascent,' +
'baseProfile,bbox,begin,by,calcMode,cap-height,class,color,color-rendering,content,' +
'cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,font-size,font-stretch,' +
'font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,' +
'height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,keySplines,keyTimes,lang,' +
'marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mathematical,' +
'max,min,offset,opacity,orient,origin,overline-position,overline-thickness,panose-1,' +
'path,pathLength,points,preserveAspectRatio,r,refX,refY,repeatCount,repeatDur,' +
'requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,stemv,stop-color,' +
'stop-opacity,strikethrough-position,strikethrough-thickness,stroke,stroke-dasharray,' +
'stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,' +
'stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,underline-position,' +
'underline-thickness,unicode,unicode-range,units-per-em,values,version,viewBox,visibility,' +
'width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,xlink:show,xlink:title,' +
'xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,zoomAndPan', true);
var validAttrs = extend({},
uriAttrs,
svgAttrs,
htmlAttrs);
function toMap(str, lowercaseKeys) {
var obj = {}, items = str.split(','), i;
for (i = 0; i < items.length; i++) {
obj[lowercaseKeys ? lowercase(items[i]) : items[i]] = true;
}
return obj;
}
var inertBodyElement;
(function(window) {
var doc;
if (window.document && window.document.implementation) {
doc = window.document.implementation.createHTMLDocument("inert");
} else {
throw $sanitizeMinErr('noinert', "Can't create an inert html document");
}
var docElement = doc.documentElement || doc.getDocumentElement();
var bodyElements = docElement.getElementsByTagName('body');
// usually there should be only one body element in the document, but IE doesn't have any, so we need to create one
if (bodyElements.length === 1) {
inertBodyElement = bodyElements[0];
} else {
var html = doc.createElement('html');
inertBodyElement = doc.createElement('body');
html.appendChild(inertBodyElement);
doc.appendChild(html);
}
})(window);
/**
* @example
* htmlParser(htmlString, {
* start: function(tag, attrs) {},
* end: function(tag) {},
* chars: function(text) {},
* comment: function(text) {}
* });
*
* @param {string} html string
* @param {object} handler
*/
function htmlParserImpl(html, handler) {
if (html === null || html === undefined) {
html = '';
} else if (typeof html !== 'string') {
html = '' + html;
}
inertBodyElement.innerHTML = html;
//mXSS protection
var mXSSAttempts = 5;
do {
if (mXSSAttempts === 0) {
throw $sanitizeMinErr('uinput', "Failed to sanitize html because the input is unstable");
}
mXSSAttempts--;
// strip custom-namespaced attributes on IE<=11
if (window.document.documentMode) {
stripCustomNsAttrs(inertBodyElement);
}
html = inertBodyElement.innerHTML; //trigger mXSS
inertBodyElement.innerHTML = html;
} while (html !== inertBodyElement.innerHTML);
var node = inertBodyElement.firstChild;
while (node) {
switch (node.nodeType) {
case 1: // ELEMENT_NODE
handler.start(node.nodeName.toLowerCase(), attrToMap(node.attributes));
break;
case 3: // TEXT NODE
handler.chars(node.textContent);
break;
}
var nextNode;
if (!(nextNode = node.firstChild)) {
if (node.nodeType == 1) {
handler.end(node.nodeName.toLowerCase());
}
nextNode = node.nextSibling;
if (!nextNode) {
while (nextNode == null) {
node = node.parentNode;
if (node === inertBodyElement) break;
nextNode = node.nextSibling;
if (node.nodeType == 1) {
handler.end(node.nodeName.toLowerCase());
}
}
}
}
node = nextNode;
}
while (node = inertBodyElement.firstChild) {
inertBodyElement.removeChild(node);
}
}
function attrToMap(attrs) {
var map = {};
for (var i = 0, ii = attrs.length; i < ii; i++) {
var attr = attrs[i];
map[attr.name] = attr.value;
}
return map;
}
/**
* Escapes all potentially dangerous characters, so that the
* resulting string can be safely inserted into attribute or
* element text.
* @param value
* @returns {string} escaped text
*/
function encodeEntities(value) {
return value.
replace(/&/g, '&amp;').
replace(SURROGATE_PAIR_REGEXP, function(value) {
var hi = value.charCodeAt(0);
var low = value.charCodeAt(1);
return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';';
}).
replace(NON_ALPHANUMERIC_REGEXP, function(value) {
return '&#' + value.charCodeAt(0) + ';';
}).
replace(/</g, '&lt;').
replace(/>/g, '&gt;');
}
/**
* create an HTML/XML writer which writes to buffer
* @param {Array} buf use buf.join('') to get out sanitized html string
* @returns {object} in the form of {
* start: function(tag, attrs) {},
* end: function(tag) {},
* chars: function(text) {},
* comment: function(text) {}
* }
*/
function htmlSanitizeWriterImpl(buf, uriValidator) {
var ignoreCurrentElement = false;
var out = bind(buf, buf.push);
return {
start: function(tag, attrs) {
tag = lowercase(tag);
if (!ignoreCurrentElement && blockedElements[tag]) {
ignoreCurrentElement = tag;
}
if (!ignoreCurrentElement && validElements[tag] === true) {
out('<');
out(tag);
forEach(attrs, function(value, key) {
var lkey = lowercase(key);
var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background');
if (validAttrs[lkey] === true &&
(uriAttrs[lkey] !== true || uriValidator(value, isImage))) {
out(' ');
out(key);
out('="');
out(encodeEntities(value));
out('"');
}
});
out('>');
}
},
end: function(tag) {
tag = lowercase(tag);
if (!ignoreCurrentElement && validElements[tag] === true && voidElements[tag] !== true) {
out('</');
out(tag);
out('>');
}
if (tag == ignoreCurrentElement) {
ignoreCurrentElement = false;
}
},
chars: function(chars) {
if (!ignoreCurrentElement) {
out(encodeEntities(chars));
}
}
};
}
/**
* When IE9-11 comes across an unknown namespaced attribute e.g. 'xlink:foo' it adds 'xmlns:ns1' attribute to declare
* ns1 namespace and prefixes the attribute with 'ns1' (e.g. 'ns1:xlink:foo'). This is undesirable since we don't want
* to allow any of these custom attributes. This method strips them all.
*
* @param node Root element to process
*/
function stripCustomNsAttrs(node) {
if (node.nodeType === window.Node.ELEMENT_NODE) {
var attrs = node.attributes;
for (var i = 0, l = attrs.length; i < l; i++) {
var attrNode = attrs[i];
var attrName = attrNode.name.toLowerCase();
if (attrName === 'xmlns:ns1' || attrName.lastIndexOf('ns1:', 0) === 0) {
node.removeAttributeNode(attrNode);
i--;
l--;
}
}
}
var nextNode = node.firstChild;
if (nextNode) {
stripCustomNsAttrs(nextNode);
}
nextNode = node.nextSibling;
if (nextNode) {
stripCustomNsAttrs(nextNode);
}
}
}
function sanitizeText(chars) {
var buf = [];
var writer = htmlSanitizeWriter(buf, angular.noop);
var writer = htmlSanitizeWriter(buf, noop);
writer.chars(chars);
return buf.join('');
}
// Regular Expressions for parsing tags and attributes
var START_TAG_REGEXP =
/^<((?:[a-zA-Z])[\w:-]*)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*(>?)/,
END_TAG_REGEXP = /^<\/\s*([\w:-]+)[^>]*>/,
ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,
BEGIN_TAG_REGEXP = /^</,
BEGING_END_TAGE_REGEXP = /^<\//,
COMMENT_REGEXP = /<!--(.*?)-->/g,
DOCTYPE_REGEXP = /<!DOCTYPE([^>]*?)>/i,
CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g,
SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
// Match everything outside of normal chars and " (quote character)
NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g;
// Good source of info about elements and attributes
// http://dev.w3.org/html5/spec/Overview.html#semantics
// http://simon.html5.org/html-elements
// Safe Void Elements - HTML5
// http://dev.w3.org/html5/spec/Overview.html#void-elements
var voidElements = makeMap("area,br,col,hr,img,wbr");
// Elements that you can, intentionally, leave open (and which close themselves)
// http://dev.w3.org/html5/spec/Overview.html#optional-tags
var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),
optionalEndTagInlineElements = makeMap("rp,rt"),
optionalEndTagElements = angular.extend({},
optionalEndTagInlineElements,
optionalEndTagBlockElements);
// Safe Block Elements - HTML5
var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article," +
"aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," +
"h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul"));
// Inline Elements - HTML5
var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b," +
"bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," +
"samp,small,span,strike,strong,sub,sup,time,tt,u,var"));
// SVG Elements
// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Elements
// Note: the elements animate,animateColor,animateMotion,animateTransform,set are intentionally omitted.
// They can potentially allow for arbitrary javascript to be executed. See #11290
var svgElements = makeMap("circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph," +
"hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline," +
"radialGradient,rect,stop,svg,switch,text,title,tspan,use");
// Special Elements (can contain anything)
var specialElements = makeMap("script,style");
var validElements = angular.extend({},
voidElements,
blockElements,
inlineElements,
optionalEndTagElements,
svgElements);
//Attributes that have href and hence need to be sanitized
var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap,xlink:href");
var htmlAttrs = makeMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' +
'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,' +
'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,' +
'scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,' +
'valign,value,vspace,width');
// SVG attributes (without "id" and "name" attributes)
// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Attributes
var svgAttrs = makeMap('accent-height,accumulate,additive,alphabetic,arabic-form,ascent,' +
'baseProfile,bbox,begin,by,calcMode,cap-height,class,color,color-rendering,content,' +
'cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,font-size,font-stretch,' +
'font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,' +
'height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,keySplines,keyTimes,lang,' +
'marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mathematical,' +
'max,min,offset,opacity,orient,origin,overline-position,overline-thickness,panose-1,' +
'path,pathLength,points,preserveAspectRatio,r,refX,refY,repeatCount,repeatDur,' +
'requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,stemv,stop-color,' +
'stop-opacity,strikethrough-position,strikethrough-thickness,stroke,stroke-dasharray,' +
'stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,' +
'stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,underline-position,' +
'underline-thickness,unicode,unicode-range,units-per-em,values,version,viewBox,visibility,' +
'width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,xlink:show,xlink:title,' +
'xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,zoomAndPan', true);
var validAttrs = angular.extend({},
uriAttrs,
svgAttrs,
htmlAttrs);
function makeMap(str, lowercaseKeys) {
var obj = {}, items = str.split(','), i;
for (i = 0; i < items.length; i++) {
obj[lowercaseKeys ? angular.lowercase(items[i]) : items[i]] = true;
}
return obj;
}
/**
* @example
* htmlParser(htmlString, {
* start: function(tag, attrs, unary) {},
* end: function(tag) {},
* chars: function(text) {},
* comment: function(text) {}
* });
*
* @param {string} html string
* @param {object} handler
*/
function htmlParser(html, handler) {
if (typeof html !== 'string') {
if (html === null || typeof html === 'undefined') {
html = '';
} else {
html = '' + html;
}
}
var index, chars, match, stack = [], last = html, text;
stack.last = function() { return stack[stack.length - 1]; };
while (html) {
text = '';
chars = true;
// Make sure we're not in a script or style element
if (!stack.last() || !specialElements[stack.last()]) {
// Comment
if (html.indexOf("<!--") === 0) {
// comments containing -- are not allowed unless they terminate the comment
index = html.indexOf("--", 4);
if (index >= 0 && html.lastIndexOf("-->", index) === index) {
if (handler.comment) handler.comment(html.substring(4, index));
html = html.substring(index + 3);
chars = false;
}
// DOCTYPE
} else if (DOCTYPE_REGEXP.test(html)) {
match = html.match(DOCTYPE_REGEXP);
if (match) {
html = html.replace(match[0], '');
chars = false;
}
// end tag
} else if (BEGING_END_TAGE_REGEXP.test(html)) {
match = html.match(END_TAG_REGEXP);
if (match) {
html = html.substring(match[0].length);
match[0].replace(END_TAG_REGEXP, parseEndTag);
chars = false;
}
// start tag
} else if (BEGIN_TAG_REGEXP.test(html)) {
match = html.match(START_TAG_REGEXP);
if (match) {
// We only have a valid start-tag if there is a '>'.
if (match[4]) {
html = html.substring(match[0].length);
match[0].replace(START_TAG_REGEXP, parseStartTag);
}
chars = false;
} else {
// no ending tag found --- this piece should be encoded as an entity.
text += '<';
html = html.substring(1);
}
}
if (chars) {
index = html.indexOf("<");
text += index < 0 ? html : html.substring(0, index);
html = index < 0 ? "" : html.substring(index);
if (handler.chars) handler.chars(decodeEntities(text));
}
} else {
// IE versions 9 and 10 do not understand the regex '[^]', so using a workaround with [\W\w].
html = html.replace(new RegExp("([\\W\\w]*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'),
function(all, text) {
text = text.replace(COMMENT_REGEXP, "$1").replace(CDATA_REGEXP, "$1");
if (handler.chars) handler.chars(decodeEntities(text));
return "";
});
parseEndTag("", stack.last());
}
if (html == last) {
throw $sanitizeMinErr('badparse', "The sanitizer was unable to parse the following block " +
"of html: {0}", html);
}
last = html;
}
// Clean up any remaining tags
parseEndTag();
function parseStartTag(tag, tagName, rest, unary) {
tagName = angular.lowercase(tagName);
if (blockElements[tagName]) {
while (stack.last() && inlineElements[stack.last()]) {
parseEndTag("", stack.last());
}
}
if (optionalEndTagElements[tagName] && stack.last() == tagName) {
parseEndTag("", tagName);
}
unary = voidElements[tagName] || !!unary;
if (!unary) {
stack.push(tagName);
}
var attrs = {};
rest.replace(ATTR_REGEXP,
function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) {
var value = doubleQuotedValue
|| singleQuotedValue
|| unquotedValue
|| '';
attrs[name] = decodeEntities(value);
});
if (handler.start) handler.start(tagName, attrs, unary);
}
function parseEndTag(tag, tagName) {
var pos = 0, i;
tagName = angular.lowercase(tagName);
if (tagName) {
// Find the closest opened tag of the same type
for (pos = stack.length - 1; pos >= 0; pos--) {
if (stack[pos] == tagName) break;
}
}
if (pos >= 0) {
// Close all the open elements, up the stack
for (i = stack.length - 1; i >= pos; i--)
if (handler.end) handler.end(stack[i]);
// Remove the open elements from the stack
stack.length = pos;
}
}
}
var hiddenPre=document.createElement("pre");
/**
* decodes all entities into regular string
* @param value
* @returns {string} A string with decoded entities.
*/
function decodeEntities(value) {
if (!value) { return ''; }
hiddenPre.innerHTML = value.replace(/</g,"&lt;");
// innerText depends on styling as it doesn't display hidden elements.
// Therefore, it's better to use textContent not to cause unnecessary reflows.
return hiddenPre.textContent;
}
/**
* Escapes all potentially dangerous characters, so that the
* resulting string can be safely inserted into attribute or
* element text.
* @param value
* @returns {string} escaped text
*/
function encodeEntities(value) {
return value.
replace(/&/g, '&amp;').
replace(SURROGATE_PAIR_REGEXP, function(value) {
var hi = value.charCodeAt(0);
var low = value.charCodeAt(1);
return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';';
}).
replace(NON_ALPHANUMERIC_REGEXP, function(value) {
return '&#' + value.charCodeAt(0) + ';';
}).
replace(/</g, '&lt;').
replace(/>/g, '&gt;');
}
/**
* create an HTML/XML writer which writes to buffer
* @param {Array} buf use buf.jain('') to get out sanitized html string
* @returns {object} in the form of {
* start: function(tag, attrs, unary) {},
* end: function(tag) {},
* chars: function(text) {},
* comment: function(text) {}
* }
*/
function htmlSanitizeWriter(buf, uriValidator) {
var ignore = false;
var out = angular.bind(buf, buf.push);
return {
start: function(tag, attrs, unary) {
tag = angular.lowercase(tag);
if (!ignore && specialElements[tag]) {
ignore = tag;
}
if (!ignore && validElements[tag] === true) {
out('<');
out(tag);
angular.forEach(attrs, function(value, key) {
var lkey=angular.lowercase(key);
var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background');
if (validAttrs[lkey] === true &&
(uriAttrs[lkey] !== true || uriValidator(value, isImage))) {
out(' ');
out(key);
out('="');
out(encodeEntities(value));
out('"');
}
});
out(unary ? '/>' : '>');
}
},
end: function(tag) {
tag = angular.lowercase(tag);
if (!ignore && validElements[tag] === true) {
out('</');
out(tag);
out('>');
}
if (tag == ignore) {
ignore = false;
}
},
chars: function(chars) {
if (!ignore) {
out(encodeEntities(chars));
}
}
};
}
// define ngSanitize module and register $sanitize service
angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
/* global sanitizeText: false */
/**
* @ngdoc filter
* @name linky
* @kind function
*
* @description
* Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and
* Finds links in text input and turns them into html links. Supports `http/https/ftp/mailto` and
* plain email address links.
*
* Requires the {@link ngSanitize `ngSanitize`} module to be installed.
*
* @param {string} text Input text.
* @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in.
* @returns {string} Html-linkified text.
* @param {string} target Window (`_blank|_self|_parent|_top`) or named frame to open links in.
* @param {object|function(url)} [attributes] Add custom attributes to the link element.
*
* Can be one of:
*
* - `object`: A map of attributes
* - `function`: Takes the url as a parameter and returns a map of attributes
*
* If the map of attributes contains a value for `target`, it overrides the value of
* the target parameter.
*
*
* @returns {string} Html-linkified and {@link $sanitize sanitized} text.
*
* @usage
<span ng-bind-html="linky_expression | linky"></span>
@ -550,25 +570,13 @@ angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
* @example
<example module="linkyExample" deps="angular-sanitize.js">
<file name="index.html">
<script>
angular.module('linkyExample', ['ngSanitize'])
.controller('ExampleController', ['$scope', function($scope) {
$scope.snippet =
'Pretty text with some links:\n'+
'http://angularjs.org/,\n'+
'mailto:us@somewhere.org,\n'+
'another@somewhere.org,\n'+
'and one more: ftp://127.0.0.1/.';
$scope.snippetWithTarget = 'http://angularjs.org/';
}]);
</script>
<div ng-controller="ExampleController">
Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
<table>
<tr>
<td>Filter</td>
<td>Source</td>
<td>Rendered</td>
<th>Filter</th>
<th>Source</th>
<th>Rendered</th>
</tr>
<tr id="linky-filter">
<td>linky filter</td>
@ -582,10 +590,19 @@ angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
<tr id="linky-target">
<td>linky target</td>
<td>
<pre>&lt;div ng-bind-html="snippetWithTarget | linky:'_blank'"&gt;<br>&lt;/div&gt;</pre>
<pre>&lt;div ng-bind-html="snippetWithSingleURL | linky:'_blank'"&gt;<br>&lt;/div&gt;</pre>
</td>
<td>
<div ng-bind-html="snippetWithTarget | linky:'_blank'"></div>
<div ng-bind-html="snippetWithSingleURL | linky:'_blank'"></div>
</td>
</tr>
<tr id="linky-custom-attributes">
<td>linky custom attributes</td>
<td>
<pre>&lt;div ng-bind-html="snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}"&gt;<br>&lt;/div&gt;</pre>
</td>
<td>
<div ng-bind-html="snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}"></div>
</td>
</tr>
<tr id="escaped-html">
@ -595,6 +612,18 @@ angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
</tr>
</table>
</file>
<file name="script.js">
angular.module('linkyExample', ['ngSanitize'])
.controller('ExampleController', ['$scope', function($scope) {
$scope.snippet =
'Pretty text with some links:\n'+
'http://angularjs.org/,\n'+
'mailto:us@somewhere.org,\n'+
'another@somewhere.org,\n'+
'and one more: ftp://127.0.0.1/.';
$scope.snippetWithSingleURL = 'http://angularjs.org/';
}]);
</file>
<file name="protractor.js" type="protractor">
it('should linkify the snippet with urls', function() {
expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).
@ -622,10 +651,17 @@ angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
it('should work with the target property', function() {
expect(element(by.id('linky-target')).
element(by.binding("snippetWithTarget | linky:'_blank'")).getText()).
element(by.binding("snippetWithSingleURL | linky:'_blank'")).getText()).
toBe('http://angularjs.org/');
expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank');
});
it('should optionally add custom attributes', function() {
expect(element(by.id('linky-custom-attributes')).
element(by.binding("snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}")).getText()).
toBe('http://angularjs.org/');
expect(element(by.css('#linky-custom-attributes a')).getAttribute('rel')).toEqual('nofollow');
});
</file>
</example>
*/
@ -634,8 +670,21 @@ angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
/((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"\u201d\u2019]/i,
MAILTO_REGEXP = /^mailto:/i;
return function(text, target) {
if (!text) return text;
var linkyMinErr = angular.$$minErr('linky');
var isDefined = angular.isDefined;
var isFunction = angular.isFunction;
var isObject = angular.isObject;
var isString = angular.isString;
return function(text, target, attributes) {
if (text == null || text === '') return text;
if (!isString(text)) throw linkyMinErr('notstring', 'Expected string but received: {0}', text);
var attributesFn =
isFunction(attributes) ? attributes :
isObject(attributes) ? function getAttributesObject() {return attributes;} :
function getEmptyAttributesObject() {return {};};
var match;
var raw = text;
var html = [];
@ -664,8 +713,14 @@ angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
}
function addLink(url, text) {
var key, linkAttributes = attributesFn(url);
html.push('<a ');
if (angular.isDefined(target)) {
for (key in linkAttributes) {
html.push(key + '="' + linkAttributes[key] + '" ');
}
if (isDefined(target) && !('target' in linkAttributes)) {
html.push('target="',
target,
'" ');

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,12 @@
/**
* @license AngularJS v1.4.10
* (c) 2010-2015 Google, Inc. http://angularjs.org
* @license AngularJS v1.5.8
* (c) 2010-2016 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {'use strict';
(function(window, angular) {'use strict';
/* global ngTouchClickDirectiveFactory: false,
*/
/**
* @ngdoc module
@ -27,10 +30,108 @@
/* global -ngTouch */
var ngTouch = angular.module('ngTouch', []);
ngTouch.provider('$touch', $TouchProvider);
function nodeName_(element) {
return angular.lowercase(element.nodeName || (element[0] && element[0].nodeName));
}
/**
* @ngdoc provider
* @name $touchProvider
*
* @description
* The `$touchProvider` allows enabling / disabling {@link ngTouch.ngClick ngTouch's ngClick directive}.
*/
$TouchProvider.$inject = ['$provide', '$compileProvider'];
function $TouchProvider($provide, $compileProvider) {
/**
* @ngdoc method
* @name $touchProvider#ngClickOverrideEnabled
*
* @param {boolean=} enabled update the ngClickOverrideEnabled state if provided, otherwise just return the
* current ngClickOverrideEnabled state
* @returns {*} current value if used as getter or itself (chaining) if used as setter
*
* @kind function
*
* @description
* Call this method to enable/disable {@link ngTouch.ngClick ngTouch's ngClick directive}. If enabled,
* the default ngClick directive will be replaced by a version that eliminates the 300ms delay for
* click events on browser for touch-devices.
*
* The default is `false`.
*
*/
var ngClickOverrideEnabled = false;
var ngClickDirectiveAdded = false;
this.ngClickOverrideEnabled = function(enabled) {
if (angular.isDefined(enabled)) {
if (enabled && !ngClickDirectiveAdded) {
ngClickDirectiveAdded = true;
// Use this to identify the correct directive in the delegate
ngTouchClickDirectiveFactory.$$moduleName = 'ngTouch';
$compileProvider.directive('ngClick', ngTouchClickDirectiveFactory);
$provide.decorator('ngClickDirective', ['$delegate', function($delegate) {
if (ngClickOverrideEnabled) {
// drop the default ngClick directive
$delegate.shift();
} else {
// drop the ngTouch ngClick directive if the override has been re-disabled (because
// we cannot de-register added directives)
var i = $delegate.length - 1;
while (i >= 0) {
if ($delegate[i].$$moduleName === 'ngTouch') {
$delegate.splice(i, 1);
break;
}
i--;
}
}
return $delegate;
}]);
}
ngClickOverrideEnabled = enabled;
return this;
}
return ngClickOverrideEnabled;
};
/**
* @ngdoc service
* @name $touch
* @kind object
*
* @description
* Provides the {@link ngTouch.$touch#ngClickOverrideEnabled `ngClickOverrideEnabled`} method.
*
*/
this.$get = function() {
return {
/**
* @ngdoc method
* @name $touch#ngClickOverrideEnabled
*
* @returns {*} current value of `ngClickOverrideEnabled` set in the {@link ngTouch.$touchProvider $touchProvider},
* i.e. if {@link ngTouch.ngClick ngTouch's ngClick} directive is enabled.
*
* @kind function
*/
ngClickOverrideEnabled: function() {
return ngClickOverrideEnabled;
}
};
};
}
/* global ngTouch: false */
/**
@ -66,6 +167,12 @@ ngTouch.factory('$swipe', [function() {
move: 'touchmove',
end: 'touchend',
cancel: 'touchcancel'
},
'pointer': {
start: 'pointerdown',
move: 'pointermove',
end: 'pointerup',
cancel: 'pointercancel'
}
};
@ -100,15 +207,15 @@ ngTouch.factory('$swipe', [function() {
* The main method of `$swipe`. It takes an element to be watched for swipe motions, and an
* object containing event handlers.
* The pointer types that should be used can be specified via the optional
* third argument, which is an array of strings `'mouse'` and `'touch'`. By default,
* `$swipe` will listen for `mouse` and `touch` events.
* third argument, which is an array of strings `'mouse'`, `'touch'` and `'pointer'`. By default,
* `$swipe` will listen for `mouse`, `touch` and `pointer` events.
*
* The four events are `start`, `move`, `end`, and `cancel`. `start`, `move`, and `end`
* receive as a parameter a coordinates object of the form `{ x: 150, y: 310 }` and the raw
* `event`. `cancel` receives the raw `event` as its single parameter.
*
* `start` is called on either `mousedown` or `touchstart`. After this event, `$swipe` is
* watching for `touchmove` or `mousemove` events. These events are ignored until the total
* `start` is called on either `mousedown`, `touchstart` or `pointerdown`. After this event, `$swipe` is
* watching for `touchmove`, `mousemove` or `pointermove` events. These events are ignored until the total
* distance moved in either dimension exceeds a small threshold.
*
* Once this threshold is exceeded, either the horizontal or vertical delta is greater.
@ -116,12 +223,12 @@ ngTouch.factory('$swipe', [function() {
* - If the vertical distance is greater, this is a scroll, and we let the browser take over.
* A `cancel` event is sent.
*
* `move` is called on `mousemove` and `touchmove` after the above logic has determined that
* `move` is called on `mousemove`, `touchmove` and `pointermove` after the above logic has determined that
* a swipe is in progress.
*
* `end` is called when a swipe is successfully completed with a `touchend` or `mouseup`.
* `end` is called when a swipe is successfully completed with a `touchend`, `mouseup` or `pointerup`.
*
* `cancel` is called either on a `touchcancel` from the browser, or when we begin scrolling
* `cancel` is called either on a `touchcancel` or `pointercancel` from the browser, or when we begin scrolling
* as described above.
*
*/
@ -135,7 +242,7 @@ ngTouch.factory('$swipe', [function() {
// Whether a swipe is active.
var active = false;
pointerTypes = pointerTypes || ['mouse', 'touch'];
pointerTypes = pointerTypes || ['mouse', 'touch', 'pointer'];
element.on(getEvents(pointerTypes, 'start'), function(event) {
startCoords = getCoordinates(event);
active = true;
@ -202,8 +309,17 @@ ngTouch.factory('$swipe', [function() {
/**
* @ngdoc directive
* @name ngClick
* @deprecated
*
* @description
* <div class="alert alert-danger">
* **DEPRECATION NOTICE**: Beginning with Angular 1.5, this directive is deprecated and by default **disabled**.
* The directive will receive no further support and might be removed from future releases.
* If you need the directive, you can enable it with the {@link ngTouch.$touchProvider $touchProvider#ngClickOverrideEnabled}
* function. We also recommend that you migrate to [FastClick](https://github.com/ftlabs/fastclick).
* To learn more about the 300ms delay, this [Telerik article](http://developer.telerik.com/featured/300-ms-click-delay-ios-8/)
* gives a good overview.
* </div>
* A more powerful replacement for the default ngClick designed to be used on touchscreen
* devices. Most mobile browsers wait about 300ms after a tap-and-release before sending
* the click event. This version handles them immediately, and then prevents the
@ -235,15 +351,7 @@ ngTouch.factory('$swipe', [function() {
</example>
*/
ngTouch.config(['$provide', function($provide) {
$provide.decorator('ngClickDirective', ['$delegate', function($delegate) {
// drop the default ngClick directive
$delegate.shift();
return $delegate;
}]);
}]);
ngTouch.directive('ngClick', ['$parse', '$timeout', '$rootElement',
var ngTouchClickDirectiveFactory = ['$parse', '$timeout', '$rootElement',
function($parse, $timeout, $rootElement) {
var TAP_DURATION = 750; // Shorter than 750ms is a tap, longer is a taphold or drag.
var MOVE_TOLERANCE = 12; // 12px seems to work in most mobile browsers.
@ -487,7 +595,7 @@ ngTouch.directive('ngClick', ['$parse', '$timeout', '$rootElement',
});
};
}]);
}];
/* global ngTouch: false */

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"raw":"v1.4.10","major":1,"minor":4,"patch":10,"prerelease":[],"build":[],"version":"1.4.10","codeName":"benignant-oscillation","full":"1.4.10","branch":"v1.4.x","cdn":{"raw":"v1.4.9","major":1,"minor":4,"patch":9,"prerelease":[],"build":[],"version":"1.4.9","docsUrl":"http://code.angularjs.org/1.4.9/docs"}}
{"raw":"v1.5.8","major":1,"minor":5,"patch":8,"prerelease":[],"build":[],"version":"1.5.8","codeName":"arbitrary-fallbacks","full":"1.5.8","branch":"v1.5.x","cdn":{"raw":"v1.5.7","major":1,"minor":5,"patch":7,"prerelease":[],"build":[],"version":"1.5.7","docsUrl":"http://code.angularjs.org/1.5.7/docs"}}

View File

@ -1 +1 @@
1.4.10
1.5.8