From 1feb62062fdb46db7c7dddd77ac732266d370287 Mon Sep 17 00:00:00 2001 From: Jeffrey Phillips Date: Tue, 1 Mar 2016 07:54:41 -0500 Subject: [PATCH] Add Cards View example to the toolbar directive docs. --- dist/angular-patternfly.js | 15 +- dist/docs/css/angular-patternfly.css | 4 +- dist/docs/grunt-scripts/angular-animate.js | 344 +++- dist/docs/grunt-scripts/angular-patternfly.js | 15 +- dist/docs/grunt-scripts/angular-sanitize.js | 526 +++--- dist/docs/grunt-scripts/angular.js | 1683 ++++++++++++----- dist/docs/js/docs-setup.js | 2 +- .../api/patternfly.autofocus.pfFocused.html | 10 +- ....card.directive.pfAggregateStatusCard.html | 14 +- .../api/patternfly.card.directive.pfCard.html | 14 +- ...patternfly.charts.directive.pfC3Chart.html | 14 +- ...nfly.charts.directive.pfDonutPctChart.html | 14 +- ...patternfly.charts.directive.pfHeatMap.html | 14 +- ...fly.charts.directive.pfSparklineChart.html | 14 +- ...ernfly.charts.directive.pfTrendsChart.html | 14 +- ...harts.directive.pfUtilizationBarChart.html | 14 +- ...rts.directive.pfUtilizationTrendChart.html | 14 +- ...patternfly.filters.directive.pfFilter.html | 14 +- ...nfly.filters.directive.pfFilterFields.html | 2 +- ...fly.filters.directive.pfFilterResults.html | 2 +- ...atternfly.form.directive.pfDatepicker.html | 14 +- ...tternfly.form.directive.pfFormButtons.html | 14 +- ...patternfly.form.directive.pfFormGroup.html | 14 +- ....form.directive.pfRemainingCharsCount.html | 14 +- .../patternfly.notification.Notification.html | 14 +- ...cation.directive.pfInlineNotification.html | 14 +- ...fication.directive.pfNotificationList.html | 14 +- .../api/patternfly.select.pfSelect.html | 14 +- .../api/patternfly.sort.directive.pfSort.html | 14 +- ...tternfly.toolbars.directive.pfToolbar.html | 27 +- ...tternfly.utils.directive.pfTransclude.html | 14 +- .../patternfly.validation.pfValidation.html | 14 +- ...patternfly.views.directive.pfCardView.html | 14 +- ...patternfly.views.directive.pfListView.html | 14 +- dist/styles/angular-patternfly.css | 4 +- dist/styles/angular-patternfly.min.css | 2 +- src/toolbars/toolbar-directive.js | 15 +- styles/angular-patternfly.css | 4 +- 38 files changed, 2041 insertions(+), 936 deletions(-) diff --git a/dist/angular-patternfly.js b/dist/angular-patternfly.js index 1928146a7..bc6534910 100644 --- a/dist/angular-patternfly.js +++ b/dist/angular-patternfly.js @@ -3893,7 +3893,7 @@ angular.module('patternfly.sort').directive('pfSort', function () { * * * @example - +
@@ -3923,6 +3923,19 @@ angular.module('patternfly.sort').directive('pfSort', function () {
+
+
+
+ {{item.name}} +
+
+ {{item.address}} +
+
+ {{item.birthMonth}} +
+
+
diff --git a/dist/docs/css/angular-patternfly.css b/dist/docs/css/angular-patternfly.css index a245f4bb5..19b1e3e8d 100644 --- a/dist/docs/css/angular-patternfly.css +++ b/dist/docs/css/angular-patternfly.css @@ -149,7 +149,7 @@ .card-view-pf .card.active, .card-view-pf .card.active:hover, .card-view-pf .card.active:focus { - border: solid 3px #39a5dc; + border: solid 3px #00a8e1; } .card-view-pf .card:hover, @@ -349,7 +349,7 @@ position: relative; } -.-heatmap--pf-container-pf .loading { +.heatmap-pf-container-pf .loading { position: absolute; top: 100px; right: 50%; diff --git a/dist/docs/grunt-scripts/angular-animate.js b/dist/docs/grunt-scripts/angular-animate.js index cb3eeb597..2778fc564 100644 --- a/dist/docs/grunt-scripts/angular-animate.js +++ b/dist/docs/grunt-scripts/angular-animate.js @@ -1,6 +1,6 @@ /** - * @license AngularJS v1.4.9 - * (c) 2010-2015 Google, Inc. http://angularjs.org + * @license AngularJS v1.5.0 + * (c) 2010-2016 Google, Inc. http://angularjs.org * License: MIT */ (function(window, angular, undefined) {'use strict'; @@ -26,6 +26,7 @@ var ADD_CLASS_SUFFIX = '-add'; var REMOVE_CLASS_SUFFIX = '-remove'; var EVENT_CLASS_PREFIX = 'ng-'; var ACTIVE_CLASS_SUFFIX = '-active'; +var PREPARE_CLASS_SUFFIX = '-prepare'; var NG_ANIMATE_CLASSNAME = 'ng-animate'; var NG_ANIMATE_CHILDREN_DATA = '$$ngAnimateChildren'; @@ -222,7 +223,10 @@ function applyAnimationToStyles(element, options) { } } -function mergeAnimationOptions(element, target, newOptions) { +function mergeAnimationDetails(element, oldAnimation, newAnimation) { + var target = oldAnimation.options || {}; + var newOptions = newAnimation.options || {}; + var toAdd = (target.addClass || '') + ' ' + (newOptions.addClass || ''); var toRemove = (target.removeClass || '') + ' ' + (newOptions.removeClass || ''); var classes = resolveElementClasses(element.attr('class'), toAdd, toRemove); @@ -254,6 +258,9 @@ function mergeAnimationOptions(element, target, newOptions) { target.removeClass = null; } + oldAnimation.addClass = target.addClass; + oldAnimation.removeClass = target.removeClass; + return target; } @@ -389,7 +396,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 @@ -424,16 +431,101 @@ var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) { } }]; -var $$AnimateChildrenDirective = [function() { - return function(scope, element, attrs) { - var val = attrs.ngAnimateChildren; - if (angular.isString(val) && val.length === 0) { //empty attribute - element.data(NG_ANIMATE_CHILDREN_DATA, true); - } else { - attrs.$observe('ngAnimateChildren', function(value) { +/** + * @ngdoc directive + * @name ngAnimateChildren + * @restrict AE + * @element ANY + * + * @description + * + * ngAnimateChildren allows you to specify that children of this element should animate even if any + * of the children's parents are currently animating. By default, when an element has an active `enter`, `leave`, or `move` + * (structural) animation, child elements that also have an active structural animation are not animated. + * + * Note that even if `ngAnimteChildren` is set, no child animations will run when the parent element is removed from the DOM (`leave` animation). + * + * + * @param {string} ngAnimateChildren If the value is empty, `true` or `on`, + * then child animations are allowed. If the value is `false`, child animations are not allowed. + * + * @example + * + +
+ + +
+
+
+ List of items: +
Item {{item}}
+
+
+
+
+ + + .container.ng-enter, + .container.ng-leave { + transition: all ease 1.5s; + } + + .container.ng-enter, + .container.ng-leave-active { + opacity: 0; + } + + .container.ng-leave, + .container.ng-enter-active { + opacity: 1; + } + + .item { + background: firebrick; + color: #FFF; + margin-bottom: 10px; + } + + .item.ng-enter, + .item.ng-leave { + transition: transform 1.5s ease; + } + + .item.ng-enter { + transform: translateX(50px); + } + + .item.ng-enter-active { + transform: translateX(0); + } + + + angular.module('ngAnimateChildren', ['ngAnimate']) + .controller('mainController', function() { + this.animateChildren = false; + this.enterElement = false; + }); + +
+ */ +var $$AnimateChildrenDirective = ['$interpolate', function($interpolate) { + return { + link: function(scope, element, attrs) { + var val = attrs.ngAnimateChildren; + if (angular.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 + // animations that run right after compilation + setData($interpolate(val)(scope)); + attrs.$observe('ngAnimateChildren', setData); + } + + function setData(value) { value = value === 'on' || value === 'true'; element.data(NG_ANIMATE_CHILDREN_DATA, value); - }); + } } }; }]; @@ -605,7 +697,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. * @@ -642,11 +734,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. @@ -699,7 +791,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; @@ -815,7 +907,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; } @@ -1194,6 +1286,13 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) { element.off(events.join(' '), onAnimationProgress); } + //Cancel the fallback closing timeout and remove the timer data + var animationTimerData = element.data(ANIMATE_TIMER_KEY); + if (animationTimerData) { + $timeout.cancel(animationTimerData[0].timer); + element.removeData(ANIMATE_TIMER_KEY); + } + // if the preparation function fails then the promise is not setup if (runner) { runner.complete(!rejected); @@ -1282,9 +1381,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)) @@ -1457,7 +1556,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 ); @@ -1557,7 +1656,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) { @@ -2086,22 +2185,21 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { }); } - function hasAnimationClasses(options, and) { - options = options || {}; - var a = (options.addClass || '').length > 0; - var b = (options.removeClass || '').length > 0; + function hasAnimationClasses(animation, and) { + var a = (animation.addClass || '').length > 0; + var b = (animation.removeClass || '').length > 0; return and ? a && b : a || b; } rules.join.push(function(element, newAnimation, currentAnimation) { // if the new animation is class-based then we can just tack that on - return !newAnimation.structural && hasAnimationClasses(newAnimation.options); + return !newAnimation.structural && hasAnimationClasses(newAnimation); }); rules.skip.push(function(element, newAnimation, currentAnimation) { // there is no need to animate anything if no classes are being added and // there is no structural animation that will be triggered - return !newAnimation.structural && !hasAnimationClasses(newAnimation.options); + return !newAnimation.structural && !hasAnimationClasses(newAnimation); }); rules.skip.push(function(element, newAnimation, currentAnimation) { @@ -2127,19 +2225,17 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { }); rules.cancel.push(function(element, newAnimation, currentAnimation) { - - - var nA = newAnimation.options.addClass; - var nR = newAnimation.options.removeClass; - var cA = currentAnimation.options.addClass; - var cR = currentAnimation.options.removeClass; + var nA = newAnimation.addClass; + var nR = newAnimation.removeClass; + var cA = currentAnimation.addClass; + var cR = currentAnimation.removeClass; // early detection to save the global CPU shortage :) if ((isUndefined(nA) && isUndefined(nR)) || (isUndefined(cA) && isUndefined(cR))) { return false; } - return (hasMatchingClasses(nA, cR)) || (hasMatchingClasses(nR, cA)); + return hasMatchingClasses(nA, cR) || hasMatchingClasses(nR, cA); }); this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$HashMap', @@ -2211,8 +2307,8 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { var applyAnimationClasses = applyAnimationClassesFactory($$jqLite); - function normalizeAnimationOptions(element, options) { - return mergeAnimationOptions(element, options, {}); + function normalizeAnimationDetails(element, animation) { + return mergeAnimationDetails(element, animation, {}); } // IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259. @@ -2406,6 +2502,8 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { structural: isStructural, element: element, event: event, + addClass: options.addClass, + removeClass: options.removeClass, close: close, options: options, runner: runner @@ -2418,11 +2516,10 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { close(); return runner; } else { - mergeAnimationOptions(element, existingAnimation.options, options); + mergeAnimationDetails(element, existingAnimation, newAnimation); return existingAnimation.runner; } } - var cancelAnimationFlag = isAllowed('cancel', element, newAnimation, existingAnimation); if (cancelAnimationFlag) { if (existingAnimation.state === RUNNING_STATE) { @@ -2437,7 +2534,8 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { existingAnimation.close(); } else { // this will merge the new animation options into existing animation options - mergeAnimationOptions(element, existingAnimation.options, newAnimation.options); + mergeAnimationDetails(element, existingAnimation, newAnimation); + return existingAnimation.runner; } } else { @@ -2447,12 +2545,12 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { var joinAnimationFlag = isAllowed('join', element, newAnimation, existingAnimation); if (joinAnimationFlag) { if (existingAnimation.state === RUNNING_STATE) { - normalizeAnimationOptions(element, options); + normalizeAnimationDetails(element, newAnimation); } else { applyGeneratedPreparationClasses(element, isStructural ? event : null, options); event = newAnimation.event = existingAnimation.event; - options = mergeAnimationOptions(element, existingAnimation.options, newAnimation.options); + options = mergeAnimationDetails(element, existingAnimation, newAnimation); //we return the same runner since only the option values of this animation will //be fed into the `existingAnimation`. @@ -2463,7 +2561,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { } else { // normalization in this case means that it removes redundant CSS classes that // already exist (addClass) or do not exist (removeClass) on the element - normalizeAnimationOptions(element, options); + normalizeAnimationDetails(element, newAnimation); } // when the options are merged and cleaned up we may end up not having to do @@ -2473,7 +2571,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { if (!isValidAnimation) { // animate (from/to) can be quickly checked first, otherwise we check if any classes are present isValidAnimation = (newAnimation.event === 'animate' && Object.keys(newAnimation.options.to || {}).length > 0) - || hasAnimationClasses(newAnimation.options); + || hasAnimationClasses(newAnimation); } if (!isValidAnimation) { @@ -2503,7 +2601,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { var isValidAnimation = parentElement.length > 0 && (animationDetails.event === 'animate' || animationDetails.structural - || hasAnimationClasses(animationDetails.options)); + || hasAnimationClasses(animationDetails)); // this means that the previous animation was cancelled // even if the follow-up animation is the same event @@ -2535,7 +2633,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { // this combined multiple class to addClass / removeClass into a setClass event // so long as a structural event did not take over the animation - event = !animationDetails.structural && hasAnimationClasses(animationDetails.options, true) + event = !animationDetails.structural && hasAnimationClasses(animationDetails, true) ? 'setClass' : animationDetails.event; @@ -2857,6 +2955,12 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) { options.tempClasses = null; } + var prepareClassName; + if (isStructural) { + prepareClassName = 'ng-' + event + PREPARE_CLASS_SUFFIX; + $$jqLite.addClass(element, prepareClassName); + } + animationQueue.push({ // this data is used by the postDigest code and passed into // the driver step function @@ -3021,7 +3125,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); @@ -3079,6 +3183,10 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) { if (tempClasses) { $$jqLite.addClass(element, tempClasses); } + if (prepareClassName) { + $$jqLite.removeClass(element, prepareClassName); + prepareClassName = null; + } } function updateAnimationRunners(animation, newRunner) { @@ -3120,8 +3228,118 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) { }]; }]; +/** + * @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 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. + * + * @example + * + * + *
+ *
+ * {{ number }} + *
+ *
+ *
+ * + * 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]; + * }; + * }]); + * + * + * .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; } + * + *
+ */ +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); + }); + } + }); + } + }; +}]; + /* global angularAnimateModule: true, + ngAnimateSwapDirective, $$AnimateAsyncRunFactory, $$rAFSchedulerFactory, $$AnimateChildrenDirective, @@ -3373,11 +3591,39 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) { * the CSS class once an animation has completed.) * * + * ### The `ng-[event]-prepare` class + * + * This is a special class that can be used to prevent unwanted flickering / flash of content before + * the actual animation starts. The class is added as soon as an animation is initialized, but removed + * before the actual animation starts (after waiting for a $digest). + * It is also only added for *structural* animations (`enter`, `move`, and `leave`). + * + * In practice, flickering can appear when nesting elements with structural animations such as `ngIf` + * into elements that have class-based animations such as `ngClass`. + * + * ```html + *
+ *
+ *
+ *
+ *
+ * ``` + * + * It is possible that during the `enter` animation, the `.message` div will be briefly visible before it starts animating. + * In that case, you can add styles to the CSS that make sure the element stays hidden before the animation starts: + * + * ```css + * .message.ng-enter-prepare { + * opacity: 0; + * } + * + * ``` + * * ## JavaScript-based Animations * * 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`: * @@ -3857,6 +4103,8 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) { * Click here {@link ng.$animate to learn more about animations with `$animate`}. */ angular.module('ngAnimate', []) + .directive('ngAnimateSwap', ngAnimateSwapDirective) + .directive('ngAnimateChildren', $$AnimateChildrenDirective) .factory('$$rAFScheduler', $$rAFSchedulerFactory) diff --git a/dist/docs/grunt-scripts/angular-patternfly.js b/dist/docs/grunt-scripts/angular-patternfly.js index 1928146a7..bc6534910 100644 --- a/dist/docs/grunt-scripts/angular-patternfly.js +++ b/dist/docs/grunt-scripts/angular-patternfly.js @@ -3893,7 +3893,7 @@ angular.module('patternfly.sort').directive('pfSort', function () { * * * @example - +
@@ -3923,6 +3923,19 @@ angular.module('patternfly.sort').directive('pfSort', function () {
+
+
+
+ {{item.name}} +
+
+ {{item.address}} +
+
+ {{item.birthMonth}} +
+
+
diff --git a/dist/docs/grunt-scripts/angular-sanitize.js b/dist/docs/grunt-scripts/angular-sanitize.js index bc405bf66..8b610c766 100644 --- a/dist/docs/grunt-scripts/angular-sanitize.js +++ b/dist/docs/grunt-scripts/angular-sanitize.js @@ -1,6 +1,6 @@ /** - * @license AngularJS v1.4.9 - * (c) 2010-2015 Google, Inc. http://angularjs.org + * @license AngularJS v1.5.0 + * (c) 2010-2016 Google, Inc. http://angularjs.org * License: MIT */ (function(window, angular, undefined) {'use strict'; @@ -33,36 +33,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,16 +135,70 @@ var $sanitizeMinErr = angular.$$minErr('$sanitize');
*/ + + +/** + * @ngdoc provider + * @name $sanitizeProvider + * + * @description + * Creates and configures {@link $sanitize} instance. + */ function $SanitizeProvider() { + var svgEnabled = false; + this.$get = ['$$sanitizeUri', function($$sanitizeUri) { + if (svgEnabled) { + angular.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. + * + *
+ *

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.

+ * + *

To protect against these, explicitly setup `overflow: hidden` css rule for all potential svg + * tags within the sanitized content:

+ * + *
+ * + *

+   *   .rootOfTheIncludedContent svg {
+   *     overflow: hidden !important;
+   *   }
+   *   
+ *
+ * + * @param {boolean=} regexp New regexp to whitelist urls with. + * @returns {boolean|ng.$sanitizeProvider} Returns the currently configured value if called + * without an argument or self for chaining otherwise. + */ + this.enableSvg = function(enableSvg) { + if (angular.isDefined(enableSvg)) { + svgEnabled = enableSvg; + return this; + } else { + return svgEnabled; + } + }; } function sanitizeText(chars) { @@ -169,18 +210,9 @@ function sanitizeText(chars) { // 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 = /^/g, - DOCTYPE_REGEXP = /]*?)>/i, - CDATA_REGEXP = //g, - SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g, +var SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g, // Match everything outside of normal chars and " (quote character) - NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; + NON_ALPHANUMERIC_REGEXP = /([^\#-~ |!])/g; // Good source of info about elements and attributes @@ -189,23 +221,23 @@ var START_TAG_REGEXP = // Safe Void Elements - HTML5 // http://dev.w3.org/html5/spec/Overview.html#void-elements -var voidElements = makeMap("area,br,col,hr,img,wbr"); +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 = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"), - optionalEndTagInlineElements = makeMap("rp,rt"), +var optionalEndTagBlockElements = toMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"), + optionalEndTagInlineElements = toMap("rp,rt"), optionalEndTagElements = angular.extend({}, optionalEndTagInlineElements, optionalEndTagBlockElements); // Safe Block Elements - HTML5 -var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article," + +var blockElements = angular.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,script,section,table,ul")); + "h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,section,table,ul")); // Inline Elements - HTML5 -var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b," + +var inlineElements = angular.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")); @@ -213,24 +245,23 @@ var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a // 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," + +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,use"); + "radialGradient,rect,stop,svg,switch,text,title,tspan"); -// Special Elements (can contain anything) -var specialElements = makeMap("script,style"); +// Blocked Elements (will be stripped) +var blockedElements = toMap("script,style"); var validElements = angular.extend({}, voidElements, blockElements, inlineElements, - optionalEndTagElements, - svgElements); + optionalEndTagElements); //Attributes that have href and hence need to be sanitized -var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap,xlink:href"); +var uriAttrs = toMap("background,cite,href,longdesc,src,xlink:href"); -var htmlAttrs = makeMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' + +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,' + @@ -238,7 +269,7 @@ var htmlAttrs = makeMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspac // 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,' + +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,' + @@ -259,7 +290,7 @@ var validAttrs = angular.extend({}, svgAttrs, htmlAttrs); -function makeMap(str, lowercaseKeys) { +function toMap(str, lowercaseKeys) { var obj = {}, items = str.split(','), i; for (i = 0; i < items.length; i++) { obj[lowercaseKeys ? angular.lowercase(items[i]) : items[i]] = true; @@ -267,11 +298,32 @@ function makeMap(str, lowercaseKeys) { 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, unary) {}, + * start: function(tag, attrs) {}, * end: function(tag) {}, * chars: function(text) {}, * comment: function(text) {} @@ -281,169 +333,74 @@ function makeMap(str, lowercaseKeys) { * @param {object} handler */ function htmlParser(html, handler) { - if (typeof html !== 'string') { - if (html === null || typeof html === 'undefined') { - html = ''; - } else { - html = '' + html; - } + if (html === null || html === undefined) { + html = ''; + } else if (typeof html !== 'string') { + html = '' + html; } - var index, chars, match, stack = [], last = html, text; - stack.last = function() { return stack[stack.length - 1]; }; + inertBodyElement.innerHTML = html; - 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("", 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; - } + //mXSS protection + var mXSSAttempts = 5; + do { + if (mXSSAttempts === 0) { + throw $sanitizeMinErr('uinput', "Failed to sanitize html because the input is unstable"); + } + mXSSAttempts--; - // start tag - } else if (BEGIN_TAG_REGEXP.test(html)) { - match = html.match(START_TAG_REGEXP); + // strip custom-namespaced attributes on IE<=11 + if (document.documentMode <= 11) { + 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; + } - 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); + 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()); } - 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; + node = nextNode; } - // 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); + while (node = inertBodyElement.firstChild) { + inertBodyElement.removeChild(node); } +} - 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; - } +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; } -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(/' : '>'); + out('>'); } }, end: function(tag) { - tag = angular.lowercase(tag); - if (!ignore && validElements[tag] === true) { - out(''); - } - if (tag == ignore) { - ignore = false; - } - }, + tag = angular.lowercase(tag); + if (!ignoreCurrentElement && validElements[tag] === true && voidElements[tag] !== true) { + out(''); + } + if (tag == ignoreCurrentElement) { + ignoreCurrentElement = false; + } + }, chars: function(chars) { - if (!ignore) { - out(encodeEntities(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 === 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.indexOf('ns1:') === 0) { + node.removeAttributeNode(attrNode); + i--; + l--; + } + } + } + + var nextNode = node.firstChild; + if (nextNode) { + stripCustomNsAttrs(nextNode); + } + + nextNode = node.nextSibling; + if (nextNode) { + stripCustomNsAttrs(nextNode); + } +} + + + // define ngSanitize module and register $sanitize service angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider); @@ -535,14 +526,25 @@ angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider); * @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 @@ -550,25 +552,13 @@ angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider); * @example -
Snippet: - - - + + + @@ -582,10 +572,19 @@ angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider); + + + + + @@ -595,6 +594,18 @@ angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
FilterSourceRenderedFilterSourceRendered
linky filter
linky target -
<div ng-bind-html="snippetWithTarget | linky:'_blank'">
</div>
+
<div ng-bind-html="snippetWithSingleURL | linky:'_blank'">
</div>
+
+
+
linky custom attributes +
<div ng-bind-html="snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}">
</div>
-
+
+ + 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/'; + }]); + it('should linkify the snippet with urls', function() { expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()). @@ -622,10 +633,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'); + }); */ @@ -634,8 +652,13 @@ 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 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 match; var raw = text; var html = []; @@ -664,8 +687,19 @@ angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) { } function addLink(url, text) { + var key; html.push(']+>)/)[1]. - replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); }); + replace(/^<([\w\-]+)/, function(match, nodeName) {return '<' + lowercase(nodeName);}); } catch (e) { return lowercase(elemHtml); } @@ -1752,7 +1766,6 @@ function snake_case(name, separator) { } var bindJQueryFired = false; -var skipDestroyOnNextJQueryCleanData; function bindJQuery() { var originalCleanData; @@ -1786,15 +1799,11 @@ function bindJQuery() { originalCleanData = jQuery.cleanData; jQuery.cleanData = function(elems) { var events; - if (!skipDestroyOnNextJQueryCleanData) { - for (var i = 0, elem; (elem = elems[i]) != null; i++) { - events = jQuery._data(elem, "events"); - if (events && events.$destroy) { - jQuery(elem).triggerHandler('$destroy'); - } + for (var i = 0, elem; (elem = elems[i]) != null; i++) { + events = jQuery._data(elem, "events"); + if (events && events.$destroy) { + jQuery(elem).triggerHandler('$destroy'); } - } else { - skipDestroyOnNextJQueryCleanData = false; } originalCleanData(elems); }; @@ -2194,6 +2203,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 @@ -2352,6 +2374,7 @@ function toDebugString(obj) { $BrowserProvider, $CacheFactoryProvider, $ControllerProvider, + $DateProvider, $DocumentProvider, $ExceptionHandlerProvider, $FilterProvider, @@ -2401,11 +2424,11 @@ function toDebugString(obj) { * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat". */ var version = { - full: '1.4.9', // all of these placeholder strings will be replaced by grunt's + full: '1.5.0', // all of these placeholder strings will be replaced by grunt's major: 1, // package task - minor: 4, - dot: 9, - codeName: 'implicit-superannuation' + minor: 5, + dot: 0, + codeName: 'ennoblement-facilitation' }; @@ -2744,6 +2767,12 @@ function jqLiteHasData(node) { return false; } +function jqLiteCleanData(nodes) { + for (var i = 0, ii = nodes.length; i < ii; i++) { + jqLiteRemoveData(nodes[i]); + } +} + function jqLiteBuildFragment(html, context) { var tmp, tag, wrap, fragment = context.createDocumentFragment(), @@ -2796,6 +2825,16 @@ function jqLiteParseHTML(html, context) { return []; } +function jqLiteWrapNode(node, wrapper) { + var parent = node.parentNode; + + if (parent) { + parent.replaceChild(wrapper, node); + } + + wrapper.appendChild(node); +} + // IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259. var jqLiteContains = Node.prototype.contains || function(arg) { @@ -3046,7 +3085,7 @@ function jqLiteRemove(element, keepData) { function jqLiteDocumentLoaded(action, win) { win = win || window; if (win.document.readyState === 'complete') { - // Force the action to be run async for consistent behaviour + // Force the action to be run async for consistent behavior // from the action's point of view // i.e. it will definitely not be in a $apply win.setTimeout(action); @@ -3132,7 +3171,8 @@ function getAliasedAttrName(name) { forEach({ data: jqLiteData, removeData: jqLiteRemoveData, - hasData: jqLiteHasData + hasData: jqLiteHasData, + cleanData: jqLiteCleanData }, function(fn, name) { JQLite[name] = fn; }); @@ -3487,12 +3527,7 @@ forEach({ }, wrap: function(element, wrapNode) { - wrapNode = jqLite(wrapNode).eq(0).clone()[0]; - var parent = element.parentNode; - if (parent) { - parent.replaceChild(wrapNode, element); - } - wrapNode.appendChild(element); + jqLiteWrapNode(element, jqLite(wrapNode).eq(0).clone()[0]); }, remove: jqLiteRemove, @@ -3770,17 +3805,23 @@ var $$HashMapProvider = [function() { * Implicit module which gets automatically added to each {@link auto.$injector $injector}. */ +var ARROW_ARG = /^([^\(]+?)=>/; var FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m; var FN_ARG_SPLIT = /,/; var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; var $injectorMinErr = minErr('$injector'); +function extractArgs(fn) { + var fnText = fn.toString().replace(STRIP_COMMENTS, ''), + args = fnText.match(ARROW_ARG) || fnText.match(FN_ARGS); + return args; +} + function anonFn(fn) { // For anonymous functions, showing at the very least the function signature can help in // debugging. - var fnText = fn.toString().replace(STRIP_COMMENTS, ''), - args = fnText.match(FN_ARGS); + var args = extractArgs(fn); if (args) { return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')'; } @@ -3789,7 +3830,6 @@ function anonFn(fn) { function annotate(fn, strictDi, name) { var $inject, - fnText, argDecl, last; @@ -3804,8 +3844,7 @@ function annotate(fn, strictDi, name) { throw $injectorMinErr('strictdi', '{0} is not using explicit annotation and cannot be invoked in strict mode', name); } - fnText = fn.toString().replace(STRIP_COMMENTS, ''); - argDecl = fnText.match(FN_ARGS); + argDecl = extractArgs(fn); forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) { arg.replace(FN_ARG, function(all, underscore, name) { $inject.push(name); @@ -4195,8 +4234,20 @@ function annotate(fn, strictDi, name) { * * Register a **service constructor**, which will be invoked with `new` to create the service * instance. - * This is short for registering a service where its provider's `$get` property is the service - * constructor function that will be used to instantiate the service instance. + * This is short for registering a service where its provider's `$get` property is a factory + * function that returns an instance instantiated by the injector from the service constructor + * function. + * + * Internally it looks a bit like this: + * + * ``` + * { + * $get: function() { + * return $injector.instantiate(constructor); + * } + * } + * ``` + * * * You should use {@link auto.$provide#service $provide.service(class)} if you define your service * as a type/class. @@ -4346,14 +4397,19 @@ function createInjector(modulesToLoad, strictDi) { throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- ')); })), instanceCache = {}, - instanceInjector = (instanceCache.$injector = + protoInstanceInjector = createInternalInjector(instanceCache, function(serviceName, caller) { var provider = providerInjector.get(serviceName + providerSuffix, caller); - return instanceInjector.invoke(provider.$get, provider, undefined, serviceName); - })); - + return instanceInjector.invoke( + provider.$get, provider, undefined, serviceName); + }), + instanceInjector = protoInstanceInjector; - forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); }); + providerCache['$injector' + providerSuffix] = { $get: valueFn(protoInstanceInjector) }; + var runBlocks = loadModules(modulesToLoad); + instanceInjector = protoInstanceInjector.get('$injector'); + instanceInjector.strictDi = strictDi; + forEach(runBlocks, function(fn) { if (fn) instanceInjector.invoke(fn); }); return instanceInjector; @@ -4503,48 +4559,67 @@ function createInjector(modulesToLoad, strictDi) { } } - function invoke(fn, self, locals, serviceName) { - if (typeof locals === 'string') { - serviceName = locals; - locals = null; - } + function injectionArgs(fn, locals, serviceName) { var args = [], - $inject = createInjector.$$annotate(fn, strictDi, serviceName), - length, i, - key; + $inject = createInjector.$$annotate(fn, strictDi, serviceName); - for (i = 0, length = $inject.length; i < length; i++) { - key = $inject[i]; + for (var i = 0, length = $inject.length; i < length; i++) { + var key = $inject[i]; if (typeof key !== 'string') { throw $injectorMinErr('itkn', 'Incorrect injection token! Expected service name as string, got {0}', key); } - args.push( - locals && locals.hasOwnProperty(key) - ? locals[key] - : getService(key, serviceName) - ); + args.push(locals && locals.hasOwnProperty(key) ? locals[key] : + getService(key, serviceName)); + } + return args; + } + + function isClass(func) { + // IE 9-11 do not support classes and IE9 leaks with the code below. + if (msie <= 11) { + return false; + } + // Workaround for MS Edge. + // Check https://connect.microsoft.com/IE/Feedback/Details/2211653 + return typeof func === 'function' + && /^(?:class\s|constructor\()/.test(Function.prototype.toString.call(func)); + } + + function invoke(fn, self, locals, serviceName) { + if (typeof locals === 'string') { + serviceName = locals; + locals = null; } + + var args = injectionArgs(fn, locals, serviceName); if (isArray(fn)) { - fn = fn[length]; + fn = fn[fn.length - 1]; } - // http://jsperf.com/angularjs-invoke-apply-vs-switch - // #5388 - return fn.apply(self, args); + if (!isClass(fn)) { + // http://jsperf.com/angularjs-invoke-apply-vs-switch + // #5388 + return fn.apply(self, args); + } else { + args.unshift(null); + return new (Function.prototype.bind.apply(fn, args))(); + } } + function instantiate(Type, locals, serviceName) { // Check if Type is annotated and use just the given function at n-1 as parameter // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]); - // Object creation: http://jsperf.com/create-constructor/2 - var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype || null); - var returnedValue = invoke(Type, instance, locals, serviceName); - - return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance; + var ctor = (isArray(Type) ? Type[Type.length - 1] : Type); + var args = injectionArgs(Type, locals, serviceName); + // Empty object at position 0 is ignored for invocation with `new`, but required. + args.unshift(null); + return new (Function.prototype.bind.apply(ctor, args))(); } + return { invoke: invoke, instantiate: instantiate, @@ -5159,8 +5234,8 @@ var $AnimateProvider = ['$provide', function($provide) { * // remove all the animation event listeners listening for `enter` on the given element and its children * $animate.off('enter', container); * - * // remove the event listener function provided by `listenerFn` that is set - * // to listen for `enter` on the given `element` as well as its children + * // remove the event listener function provided by `callback` that is set + * // to listen for `enter` on the given `container` as well as its children * $animate.off('enter', container, callback); * ``` * @@ -5383,7 +5458,7 @@ var $AnimateProvider = ['$provide', function($provide) { * * @description Performs an inline animation on the element which applies the provided to and from CSS styles to the element. * If any detected CSS transition, keyframe or JavaScript matches the provided className value, then the animation will take - * on the provided styles. For example, if a transition animation is set for the given className, then the provided `from` and + * on the provided styles. For example, if a transition animation is set for the given classNamem, then the provided `from` and * `to` styles will be applied alongside the given transition. If the CSS style provided in `from` does not have a corresponding * style in `to`, the style in `from` is applied immediately, and no animation is run. * If a JavaScript animation is detected then the provided styles will be given in as function parameters into the `animate` @@ -5405,7 +5480,7 @@ var $AnimateProvider = ['$provide', function($provide) { * @param {object} to the to (destination) CSS styles that will be applied to the element and across the animation. * @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If * this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element. - * (Note that if no animation is detected then this value will not be appplied to the element.) + * (Note that if no animation is detected then this value will not be applied to the element.) * @param {object=} options an optional collection of options/styles that will be applied to the element * * @return {Promise} the animation callback promise @@ -5643,7 +5718,7 @@ var $CoreAnimateCssProvider = function() { options.from = null; } - /* jshint newcap: false*/ + /* jshint newcap: false */ var closed, runner = new $$AnimateRunner(); return { start: run, @@ -6569,7 +6644,7 @@ function $TemplateCacheProvider() { * When this property is set to true, the HTML compiler will collect DOM nodes between * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them * together as the directive elements. It is recommended that this feature be used on directives - * which are not strictly behavioural (such as {@link ngClick}), and which + * which are not strictly behavioral (such as {@link ngClick}), and which * do not manipulate or replace child nodes (such as {@link ngInclude}). * * #### `priority` @@ -6607,35 +6682,62 @@ function $TemplateCacheProvider() { * is bound to the parent scope, via matching attributes on the directive's element: * * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is - * always a string since DOM attributes are strings. If no `attr` name is specified then the - * attribute name is assumed to be the same as the local name. - * Given `` and widget definition - * of `scope: { localName:'@myAttr' }`, then widget scope property `localName` will reflect - * the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the - * `localName` property on the widget scope. The `name` is read from the parent scope (not - * component scope). - * - * * `=` or `=attr` - set up bi-directional binding between a local scope property and the - * parent scope property of name defined via the value of the `attr` attribute. If no `attr` - * name is specified then the attribute name is assumed to be the same as the local name. - * Given `` and widget definition of - * `scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the + * always a string since DOM attributes are strings. If no `attr` name is specified then the + * attribute name is assumed to be the same as the local name. Given `` and the isolate scope definition `scope: { localName:'@myAttr' }`, + * the directive's scope property `localName` will reflect the interpolated value of `hello + * {{name}}`. As the `name` attribute changes so will the `localName` property on the directive's + * scope. The `name` is read from the parent scope (not the directive's scope). + * + * * `=` or `=attr` - set up a bidirectional binding between a local scope property and an expression + * passed via the attribute `attr`. The expression is evaluated in the context of the parent scope. + * If no `attr` name is specified then the attribute name is assumed to be the same as the local + * name. Given `` and the isolate scope definition `scope: { + * localModel: '=myAttr' }`, the property `localModel` on the directive's scope will reflect the + * value of `parentModel` on the parent scope. Changes to `parentModel` will be reflected in + * `localModel` and vice versa. Optional attributes should be marked as such with a question mark: + * `=?` or `=?attr`. If the binding expression is non-assignable, or if the attribute isn't + * optional and doesn't exist, an exception ({@link error/$compile/nonassign `$compile:nonassign`}) + * will be thrown upon discovering changes to the local value, since it will be impossible to sync + * them back to the parent scope. By default, the {@link ng.$rootScope.Scope#$watch `$watch`} + * method is used for tracking changes, and the equality check is based on object identity. + * However, if an object literal or an array literal is passed as the binding expression, the + * equality check is done by value (using the {@link angular.equals} function). It's also possible + * to watch the evaluated value shallowly with {@link ng.$rootScope.Scope#$watchCollection + * `$watchCollection`}: use `=*` or `=*attr` (`=*?` or `=*?attr` if the attribute is optional). + * + * * `<` or `` and directive definition of + * `scope: { localModel:'` and widget definition of - * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to - * a function wrapper for the `count = count + value` expression. Often it's desirable to - * pass data from the isolated scope via an expression to the parent scope, this can be - * done by passing a map of local variable names and values into the expression wrapper fn. - * For example, if the expression is `increment(amount)` then we can specify the amount value - * by calling the `localFn` as `localFn({amount: 22})`. + * in `localModel`, but changes in `localModel` will not reflect in `parentModel`. There are however + * two caveats: + * 1. one-way binding does not copy the value from the parent to the isolate scope, it simply + * sets the same value. That means if your bound value is an object, changes to its properties + * in the isolated scope will be reflected in the parent scope (because both reference the same object). + * 2. one-way binding watches changes to the **identity** of the parent value. That means the + * {@link ng.$rootScope.Scope#$watch `$watch`} on the parent value only fires if the reference + * to the value has changed. In most cases, this should not be of concern, but can be important + * to know if you one-way bind to an object, and then replace that object in the isolated scope. + * If you now change a property of the object in your parent scope, the change will not be + * propagated to the isolated scope, because the identity of the object on the parent scope + * has not changed. Instead you must assign a new object. + * + * One-way binding is useful if you do not plan to propagate changes to your isolated scope bindings + * back to the parent. However, it does not make this completely impossible. + * + * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. If + * no `attr` name is specified then the attribute name is assumed to be the same as the local name. + * Given `` and the isolate scope definition `scope: { + * localFn:'&myAttr' }`, the isolate scope property `localFn` will point to a function wrapper for + * the `count = count + value` expression. Often it's desirable to pass data from the isolated scope + * via an expression to the parent scope. This can be done by passing a map of local variable names + * and values into the expression wrapper fn. For example, if the expression is `increment(amount)` + * then we can specify the amount value by calling the `localFn` as `localFn({amount: 22})`. * * In general it's possible to apply more than one directive to one element, but there might be limitations * depending on the type of scope required by the directives. The following points will help explain these limitations. @@ -6659,8 +6761,18 @@ function $TemplateCacheProvider() { * definition: `controller: 'myCtrl as myAlias'`. * * When an isolate scope is used for a directive (see above), `bindToController: true` will - * allow a component to have its properties bound to the controller, rather than to scope. When the controller - * is instantiated, the initial values of the isolate scope bindings are already available. + * allow a component to have its properties bound to the controller, rather than to scope. + * + * After the controller is instantiated, the initial values of the isolate scope bindings will be bound to the controller + * properties. You can access these bindings once they have been initialized by providing a controller method called + * `$onInit`, which is called after all the controllers on an element have been constructed and had their bindings + * initialized. + * + *
+ * **Deprecation warning:** although bindings for non-ES6 class controllers are currently + * bound to `this` before the controller constructor is called, this use is now deprecated. Please place initialization + * code that relies upon bindings inside a `$onInit` method on the controller, instead. + *
* * It is also possible to set `bindToController` to an object hash with the same format as the `scope` property. * This will set up the scope bindings to the controller directly. Note that `scope` can still be used @@ -6680,10 +6792,10 @@ function $TemplateCacheProvider() { * * `$element` - Current element * * `$attrs` - Current attributes object for the element * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope: - * `function([scope], cloneLinkingFn, futureParentElement)`. - * * `scope`: optional argument to override the scope. - * * `cloneLinkingFn`: optional argument to create clones of the original transcluded content. - * * `futureParentElement`: + * `function([scope], cloneLinkingFn, futureParentElement, slotName)`: + * * `scope`: (optional) override the scope. + * * `cloneLinkingFn`: (optional) argument to create clones of the original transcluded content. + * * `futureParentElement` (optional): * * defines the parent to which the `cloneLinkingFn` will add the cloned elements. * * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`. * * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements) @@ -6691,14 +6803,34 @@ function $TemplateCacheProvider() { * as those elements need to created and cloned in a special way when they are defined outside their * usual containers (e.g. like ``). * * See also the `directive.templateNamespace` property. + * * `slotName`: (optional) the name of the slot to transclude. If falsy (e.g. `null`, `undefined` or `''`) + * then the default translusion is provided. + * The `$transclude` function also has a method on it, `$transclude.isSlotFilled(slotName)`, which returns + * `true` if the specified slot contains content (i.e. one or more DOM nodes). * + * The controller can provide the following methods that act as life-cycle hooks: + * * `$onInit` - Called on each controller after all the controllers on an element have been constructed and + * had their bindings initialized (and before the pre & post linking functions for the directives on + * this element). This is a good place to put initialization code for your controller. * * #### `require` * Require another directive and inject its controller as the fourth argument to the linking function. The - * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the - * injected argument will be an array in corresponding order. If no such directive can be - * found, or if the directive does not have a controller, then an error is raised (unless no link function - * is specified, in which case error checking is skipped). The name can be prefixed with: + * `require` property can be a string, an array or an object: + * * a **string** containing the name of the directive to pass to the linking function + * * an **array** containing the names of directives to pass to the linking function. The argument passed to the + * linking function will be an array of controllers in the same order as the names in the `require` property + * * an **object** whose property values are the names of the directives to pass to the linking function. The argument + * passed to the linking function will also be an object with matching keys, whose values will hold the corresponding + * controllers. + * + * If the `require` property is an object and `bindToController` is truthy, then the required controllers are + * bound to the controller using the keys of the `require` property. This binding occurs after all the controllers + * have been constructed but before `$onInit` is called. + * See the {@link $compileProvider#component} helper for an example of how this can be used. + * + * If no such required directive(s) can be found, or if the directive does not have a controller, then an error is + * raised (unless no link function is specified and the required controllers are not being bound to the directive + * controller, in which case error checking is skipped). The name can be prefixed with: * * * (no prefix) - Locate the required controller on the current element. Throw an error if not found. * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found. @@ -6791,14 +6923,6 @@ function $TemplateCacheProvider() { * The contents are compiled and provided to the directive as a **transclusion function**. See the * {@link $compile#transclusion Transclusion} section below. * - * There are two kinds of transclusion depending upon whether you want to transclude just the contents of the - * directive's element or the entire element: - * - * * `true` - transclude the content (i.e. the child nodes) of the directive's element. - * * `'element'` - transclude the whole of the directive's element including any directives on this - * element that defined at a lower priority than this directive. When used, the `template` - * property is ignored. - * * * #### `compile` * @@ -6826,7 +6950,7 @@ function $TemplateCacheProvider() { *
* **Note:** The compile function cannot handle directives that recursively use themselves in their - * own templates or compile functions. Compiling these directives results in an infinite loop and a + * own templates or compile functions. Compiling these directives results in an infinite loop and * stack overflow errors. * * This can be avoided by manually using $compile in the postLink function to imperatively compile @@ -6928,6 +7052,34 @@ function $TemplateCacheProvider() { * Testing Transclusion Directives}. *
* + * There are three kinds of transclusion depending upon whether you want to transclude just the contents of the + * directive's element, the entire element or multiple parts of the element contents: + * + * * `true` - transclude the content (i.e. the child nodes) of the directive's element. + * * `'element'` - transclude the whole of the directive's element including any directives on this + * element that defined at a lower priority than this directive. When used, the `template` + * property is ignored. + * * **`{...}` (an object hash):** - map elements of the content onto transclusion "slots" in the template. + * + * **Mult-slot transclusion** is declared by providing an object for the `transclude` property. + * + * This object is a map where the keys are the name of the slot to fill and the value is an element selector + * used to match the HTML to the slot. The element selector should be in normalized form (e.g. `myElement`) + * and will match the standard element variants (e.g. `my-element`, `my:element`, `data-my-element`, etc). + * + * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives} + * + * If the element selector is prefixed with a `?` then that slot is optional. + * + * For example, the transclude object `{ slotA: '?myCustomElement' }` maps `` elements to + * the `slotA` slot, which can be accessed via the `$transclude` function or via the {@link ngTransclude} directive. + * + * Slots that are not marked as optional (`?`) will trigger a compile time error if there are no matching elements + * in the transclude content. If you wish to know if an optional slot was filled with content, then you can call + * `$transclude.isSlotFilled(slotName)` on the transclude function passed to the directive's link function and + * injectable into the directive's controller. + * + * * #### Transclusion Functions * * When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion @@ -6948,7 +7100,7 @@ function $TemplateCacheProvider() { * content and the `scope` is the newly created transclusion scope, to which the clone is bound. * *
- * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a translude function + * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a transclude function * since you then get a fresh clone of the original DOM and also have access to the new transclusion scope. *
* @@ -6980,7 +7132,7 @@ function $TemplateCacheProvider() { *
* * The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat} - * automatically destroy their transluded clones as necessary so you do not need to worry about this if + * automatically destroy their transcluded clones as necessary so you do not need to worry about this if * you are simply using {@link ngTransclude} to inject the transclusion into your directive. * * @@ -7025,10 +7177,9 @@ function $TemplateCacheProvider() { * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the * `link()` or `compile()` functions. It has a variety of uses. * - * accessing *Normalized attribute names:* - * Directives like 'ngBind' can be expressed in many ways: 'ng:bind', `data-ng-bind`, or 'x-ng-bind'. - * the attributes object allows for normalized access to - * the attributes. + * * *Accessing normalized attribute names:* Directives like 'ngBind' can be expressed in many ways: + * 'ng:bind', `data-ng-bind`, or 'x-ng-bind'. The attributes object allows for normalized access + * to the attributes. * * * *Directive inter-communication:* All directives share the same instance of the attributes * object which allows the directives to use the attributes object as inter directive @@ -7218,7 +7369,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; function parseIsolateBindings(scope, directiveName, isController) { - var LOCAL_REGEXP = /^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/; + var LOCAL_REGEXP = /^\s*([@&<]|=(\*?))(\??)\s*(\w*)\s*$/; var bindings = {}; @@ -7305,8 +7456,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { * @param {string|Object} name Name of the directive in camel-case (i.e. ngBind which * will match as ng-bind), or an object map of directives where the keys are the * names and the values are the factories. - * @param {Function|Array} directiveFactory An injectable directive factory function. See - * {@link guide/directive} for more info. + * @param {Function|Array} directiveFactory An injectable directive factory function. See the + * {@link guide/directive directive guide} and the {@link $compile compile API} for more info. * @returns {ng.$compileProvider} Self for chaining. */ this.directive = function registerDirective(name, directiveFactory) { @@ -7353,6 +7504,128 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { return this; }; + /** + * @ngdoc method + * @name $compileProvider#component + * @module ng + * @param {string} name Name of the component in camelCase (i.e. `myComp` which will match ``) + * @param {Object} options Component definition object (a simplified + * {@link ng.$compile#directive-definition-object directive definition object}), + * with the following properties (all optional): + * + * - `controller` – `{(string|function()=}` – controller constructor function that should be + * associated with newly created scope or the name of a {@link ng.$compile#-controller- + * registered controller} if passed as a string. An empty `noop` function by default. + * - `controllerAs` – `{string=}` – identifier name for to reference the controller in the component's scope. + * If present, the controller will be published to scope under the `controllerAs` name. + * If not present, this will default to be `$ctrl`. + * - `template` – `{string=|function()=}` – html template as a string or a function that + * returns an html template as a string which should be used as the contents of this component. + * Empty string by default. + * + * If `template` is a function, then it is {@link auto.$injector#invoke injected} with + * the following locals: + * + * - `$element` - Current element + * - `$attrs` - Current attributes object for the element + * + * - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html + * template that should be used as the contents of this component. + * + * If `templateUrl` is a function, then it is {@link auto.$injector#invoke injected} with + * the following locals: + * + * - `$element` - Current element + * - `$attrs` - Current attributes object for the element + * + * - `bindings` – `{object=}` – defines bindings between DOM attributes and component properties. + * Component properties are always bound to the component controller and not to the scope. + * See {@link ng.$compile#-bindtocontroller- `bindToController`}. + * - `transclude` – `{boolean=}` – whether {@link $compile#transclusion content transclusion} is enabled. + * Disabled by default. + * - `$...` – `{function()=}` – additional annotations to provide to the directive factory function. + * + * @returns {ng.$compileProvider} the compile provider itself, for chaining of function calls. + * @description + * Register a **component definition** with the compiler. This is a shorthand for registering a special + * type of directive, which represents a self-contained UI component in your application. Such components + * are always isolated (i.e. `scope: {}`) and are always restricted to elements (i.e. `restrict: 'E'`). + * + * Component definitions are very simple and do not require as much configuration as defining general + * directives. Component definitions usually consist only of a template and a controller backing it. + * + * In order to make the definition easier, components enforce best practices like use of `controllerAs`, + * `bindToController`. They always have **isolate scope** and are restricted to elements. + * + * Here are a few examples of how you would usually define components: + * + * ```js + * var myMod = angular.module(...); + * myMod.component('myComp', { + * template: '
My name is {{$ctrl.name}}
', + * controller: function() { + * this.name = 'shahar'; + * } + * }); + * + * myMod.component('myComp', { + * template: '
My name is {{$ctrl.name}}
', + * bindings: {name: '@'} + * }); + * + * myMod.component('myComp', { + * templateUrl: 'views/my-comp.html', + * controller: 'MyCtrl as ctrl', + * bindings: {name: '@'} + * }); + * + * ``` + * For more examples, and an in-depth guide, see the {@link guide/component component guide}. + * + *
+ * See also {@link ng.$compileProvider#directive $compileProvider.directive()}. + */ + this.component = function registerComponent(name, options) { + var controller = options.controller || function() {}; + + function factory($injector) { + function makeInjectable(fn) { + if (isFunction(fn) || isArray(fn)) { + return function(tElement, tAttrs) { + return $injector.invoke(fn, this, {$element: tElement, $attrs: tAttrs}); + }; + } else { + return fn; + } + } + + var template = (!options.template && !options.templateUrl ? '' : options.template); + return { + controller: controller, + controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl', + template: makeInjectable(template), + templateUrl: makeInjectable(options.templateUrl), + transclude: options.transclude, + scope: {}, + bindToController: options.bindings || {}, + restrict: 'E', + require: options.require + }; + } + + // Copy any annotation properties (starting with $) over to the factory function + // These could be used by libraries such as the new component router + forEach(options, function(val, key) { + if (key.charAt(0) === '$') { + factory[key] = val; + } + }); + + factory.$inject = ['$injector']; + + return this.directive(name, factory); + }; + /** * @ngdoc method @@ -7450,6 +7723,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse, $controller, $rootScope, $sce, $animate, $$sanitizeUri) { + var SIMPLE_ATTR_NAME = /^\w/; + var specialAttrHolder = document.createElement('div'); var Attributes = function(element, attributesToCopy) { if (attributesToCopy) { var keys = Object.keys(attributesToCopy); @@ -7585,7 +7860,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { nodeName = nodeName_(this.$$element); - if ((nodeName === 'a' && key === 'href') || + if ((nodeName === 'a' && (key === 'href' || key === 'xlinkHref')) || (nodeName === 'img' && key === 'src')) { // sanitize a[href] and img[src] values this[key] = value = $$sanitizeUri(value, key === 'src'); @@ -7629,7 +7904,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { if (value === null || isUndefined(value)) { this.$$element.removeAttr(attrName); } else { - this.$$element.attr(attrName, value); + if (SIMPLE_ATTR_NAME.test(attrName)) { + this.$$element.attr(attrName, value); + } else { + setSpecialAttr(this.$$element[0], attrName, value); + } } } @@ -7683,6 +7962,18 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } }; + function setSpecialAttr(element, attrName, value) { + // Attributes names that do not start with letters (such as `(click)`) cannot be set using `setAttribute` + // so we have to jump through some hoops to get such an attribute + // https://github.com/angular/angular.js/pull/13318 + specialAttrHolder.innerHTML = ""; + var attributes = specialAttrHolder.firstChild.attributes; + var attribute = attributes[0]; + // We have to remove the attribute from its container element before we can add it to the destination element + attributes.removeNamedItem(attribute.name); + attribute.value = value; + element.attributes.setNamedItem(attribute); + } function safeAddClass($element, className) { try { @@ -7696,7 +7987,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { var startSymbol = $interpolate.startSymbol(), endSymbol = $interpolate.endSymbol(), - denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}') + denormalizeTemplate = (startSymbol == '{{' && endSymbol == '}}') ? identity : function denormalizeTemplate(template) { return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); @@ -7740,13 +8031,19 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { // modify it. $compileNodes = jqLite($compileNodes); } + + var NOT_EMPTY = /\S+/; + // We can not compile top level text elements since text nodes can be merged and we will // not be able to attach scope data to them, so we will wrap them in - forEach($compileNodes, function(node, index) { - if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */ ) { - $compileNodes[index] = jqLite(node).wrap('').parent()[0]; + for (var i = 0, len = $compileNodes.length; i < len; i++) { + var domNode = $compileNodes[i]; + + if (domNode.nodeType === NODE_TYPE_TEXT && domNode.nodeValue.match(NOT_EMPTY) /* non-empty */) { + jqLiteWrapNode(domNode, $compileNodes[i] = document.createElement('span')); } - }); + } + var compositeLinkFn = compileNodes($compileNodes, transcludeFn, $compileNodes, maxPriority, ignoreDirective, previousCompileContext); @@ -7817,7 +8114,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { if (!node) { return 'html'; } else { - return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg' : 'html'; + return nodeName_(node) !== 'foreignobject' && toString.call(node).match(/SVG/) ? 'svg' : 'html'; } } @@ -7951,6 +8248,17 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { }); }; + // We need to attach the transclusion slots onto the `boundTranscludeFn` + // so that they are available inside the `controllersBoundTransclude` function + var boundSlots = boundTranscludeFn.$$slots = createMap(); + for (var slotName in transcludeFn.$$slots) { + if (transcludeFn.$$slots[slotName]) { + boundSlots[slotName] = createBoundTranscludeFn(scope, transcludeFn.$$slots[slotName], previousBoundTranscludeFn); + } else { + boundSlots[slotName] = null; + } + } + return boundTranscludeFn; } @@ -8109,6 +8417,37 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { }; } + /** + * A function generator that is used to support both eager and lazy compilation + * linking function. + * @param eager + * @param $compileNodes + * @param transcludeFn + * @param maxPriority + * @param ignoreDirective + * @param previousCompileContext + * @returns {Function} + */ + function compilationGenerator(eager, $compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext) { + if (eager) { + return compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext); + } + + var compiled; + + return function() { + if (!compiled) { + compiled = compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext); + + // Null out all of these references in order to make them eligible for garbage collection + // since this is a potentially long lived closure + $compileNodes = transcludeFn = previousCompileContext = null; + } + + return compiled.apply(this, arguments); + }; + } + /** * Once the directives have been collected, their compile functions are executed. This method * is responsible for inlining directive templates as well as terminating the application @@ -8153,6 +8492,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { replaceDirective = originalReplaceDirective, childTranscludeFn = transcludeFn, linkFn, + didScanForMultipleTransclusion = false, + mightHaveMultipleTransclusionError = false, directiveValue; // executes all directives on the current element @@ -8195,6 +8536,27 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { directiveName = directive.name; + // If we encounter a condition that can result in transclusion on the directive, + // then scan ahead in the remaining directives for others that may cause a multiple + // transclusion error to be thrown during the compilation process. If a matching directive + // is found, then we know that when we encounter a transcluded directive, we need to eagerly + // compile the `transclude` function rather than doing it lazily in order to throw + // exceptions at the correct time + if (!didScanForMultipleTransclusion && ((directive.replace && (directive.templateUrl || directive.template)) + || (directive.transclude && !directive.$$tlb))) { + var candidateDirective; + + for (var scanningIndex = i + 1; candidateDirective = directives[scanningIndex++];) { + if ((candidateDirective.transclude && !candidateDirective.$$tlb) + || (candidateDirective.replace && (candidateDirective.templateUrl || candidateDirective.template))) { + mightHaveMultipleTransclusionError = true; + break; + } + } + + didScanForMultipleTransclusion = true; + } + if (!directive.templateUrl && directive.controller) { directiveValue = directive.controller; controllerDirectives = controllerDirectives || createMap(); @@ -8224,7 +8586,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { compileNode = $compileNode[0]; replaceWith(jqCollection, sliceArgs($template), compileNode); - childTranscludeFn = compile($template, transcludeFn, terminalPriority, + childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, terminalPriority, replaceDirective && replaceDirective.name, { // Don't pass in: // - controllerDirectives - otherwise we'll create duplicates controllers @@ -8236,10 +8598,69 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { nonTlbTranscludeDirective: nonTlbTranscludeDirective }); } else { + + var slots = createMap(); + $template = jqLite(jqLiteClone(compileNode)).contents(); + + if (isObject(directiveValue)) { + + // We have transclusion slots, + // collect them up, compile them and store their transclusion functions + $template = []; + + var slotMap = createMap(); + var filledSlots = createMap(); + + // Parse the element selectors + forEach(directiveValue, function(elementSelector, slotName) { + // If an element selector starts with a ? then it is optional + var optional = (elementSelector.charAt(0) === '?'); + elementSelector = optional ? elementSelector.substring(1) : elementSelector; + + slotMap[elementSelector] = slotName; + + // We explicitly assign `null` since this implies that a slot was defined but not filled. + // Later when calling boundTransclusion functions with a slot name we only error if the + // slot is `undefined` + slots[slotName] = null; + + // filledSlots contains `true` for all slots that are either optional or have been + // filled. This is used to check that we have not missed any required slots + filledSlots[slotName] = optional; + }); + + // Add the matching elements into their slot + forEach($compileNode.contents(), function(node) { + var slotName = slotMap[directiveNormalize(nodeName_(node))]; + if (slotName) { + filledSlots[slotName] = true; + slots[slotName] = slots[slotName] || []; + slots[slotName].push(node); + } else { + $template.push(node); + } + }); + + // Check for required slots that were not filled + forEach(filledSlots, function(filled, slotName) { + if (!filled) { + throw $compileMinErr('reqslot', 'Required transclusion slot `{0}` was not filled.', slotName); + } + }); + + for (var slotName in slots) { + if (slots[slotName]) { + // Only define a transclusion function if the slot was filled + slots[slotName] = compilationGenerator(mightHaveMultipleTransclusionError, slots[slotName], transcludeFn); + } + } + } + $compileNode.empty(); // clear contents - childTranscludeFn = compile($template, transcludeFn, undefined, + childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, undefined, undefined, { needsNewScope: directive.$$isolateScope || directive.$$newScope}); + childTranscludeFn.$$slots = slots; } } @@ -8402,6 +8823,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { for (var i = 0, ii = require.length; i < ii; i++) { value[i] = getControllers(directiveName, require[i], $element, elementControllers); } + } else if (isObject(require)) { + value = {}; + forEach(require, function(controller, property) { + value[property] = getControllers(directiveName, controller, $element, elementControllers); + }); } return value || null; @@ -8439,7 +8865,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) { - var linkFn, isolateScope, controllerScope, elementControllers, transcludeFn, $element, + var i, ii, linkFn, isolateScope, controllerScope, elementControllers, transcludeFn, $element, attrs, removeScopeBindingWatches, removeControllerBindingWatches; if (compileNode === linkNode) { @@ -8462,6 +8888,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { // is later passed as `parentBoundTranscludeFn` to `publicLinkFn` transcludeFn = controllersBoundTransclude; transcludeFn.$$boundTransclude = boundTranscludeFn; + // expose the slots on the `$transclude` function + transcludeFn.isSlotFilled = function(slotName) { + return !!boundTranscludeFn.$$slots[slotName]; + }; } if (controllerDirectives) { @@ -8506,6 +8936,21 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } } + // Bind the required controllers to the controller, if `require` is an object and `bindToController` is truthy + forEach(controllerDirectives, function(controllerDirective, name) { + var require = controllerDirective.require; + if (controllerDirective.bindToController && !isArray(require) && isObject(require)) { + extend(elementControllers[name].instance, getControllers(name, require, $element, elementControllers)); + } + }); + + // Trigger the `$onInit` method on all controllers that have one + forEach(elementControllers, function(controller) { + if (isFunction(controller.instance.$onInit)) { + controller.instance.$onInit(); + } + }); + // PRELINKING for (i = 0, ii = preLinkFns.length; i < ii; i++) { linkFn = preLinkFns[i]; @@ -8541,11 +8986,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { // This is the function that is injected as `$transclude`. // Note: all arguments are optional! - function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement) { + function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement, slotName) { var transcludeControllers; - // No scope passed in: if (!isScope(scope)) { + slotName = futureParentElement; futureParentElement = cloneAttachFn; cloneAttachFn = scope; scope = undefined; @@ -8557,7 +9002,23 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { if (!futureParentElement) { futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element; } - return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild); + if (slotName) { + // slotTranscludeFn can be one of three things: + // * a transclude function - a filled slot + // * `null` - an optional slot that was not filled + // * `undefined` - a slot that was not declared (i.e. invalid) + var slotTranscludeFn = boundTranscludeFn.$$slots[slotName]; + if (slotTranscludeFn) { + return slotTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild); + } else if (isUndefined(slotTranscludeFn)) { + throw $compileMinErr('noslot', + 'No parent directive that requires a transclusion with slot name "{0}". ' + + 'Element: {1}', + slotName, startingTag($element)); + } + } else { + return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild); + } } } } @@ -8989,9 +9450,14 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { parent.replaceChild(newNode, firstElementToRemove); } - // TODO(perf): what's this document fragment for? is it needed? can we at least reuse it? + // Append all the `elementsToRemove` to a fragment. This will... + // - remove them from the DOM + // - allow them to still be traversed with .nextSibling + // - allow a single fragment.qSA to fetch all elements being removed var fragment = document.createDocumentFragment(); - fragment.appendChild(firstElementToRemove); + for (i = 0; i < removeCount; i++) { + fragment.appendChild(elementsToRemove[i]); + } if (jqLite.hasData(firstElementToRemove)) { // Copy over user data (that includes Angular's $scope etc.). Don't copy private @@ -8999,31 +9465,18 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { // event listeners (which is the main use of private data) wouldn't work anyway. jqLite.data(newNode, jqLite.data(firstElementToRemove)); - // Remove data of the replaced element. We cannot just call .remove() - // on the element it since that would deallocate scope that is needed - // for the new node. Instead, remove the data "manually". - if (!jQuery) { - delete jqLite.cache[firstElementToRemove[jqLite.expando]]; - } else { - // jQuery 2.x doesn't expose the data storage. Use jQuery.cleanData to clean up after - // the replaced element. The cleanData version monkey-patched by Angular would cause - // the scope to be trashed and we do need the very same scope to work with the new - // element. However, we cannot just cache the non-patched version and use it here as - // that would break if another library patches the method after Angular does (one - // example is jQuery UI). Instead, set a flag indicating scope destroying should be - // skipped this one time. - skipDestroyOnNextJQueryCleanData = true; - jQuery.cleanData([firstElementToRemove]); - } + // Remove $destroy event listeners from `firstElementToRemove` + jqLite(firstElementToRemove).off('$destroy'); } - for (var k = 1, kk = elementsToRemove.length; k < kk; k++) { - var element = elementsToRemove[k]; - jqLite(element).remove(); // must do this way to clean up expando - fragment.appendChild(element); - delete elementsToRemove[k]; - } + // Cleanup any data/listeners on the elements and children. + // This includes invoking the $destroy event on any elements with listeners. + jqLite.cleanData(fragment.querySelectorAll('*')); + // Update the jqLite collection to only contain the `newNode` + for (i = 1; i < removeCount; i++) { + delete elementsToRemove[i]; + } elementsToRemove[0] = newNode; elementsToRemove.length = 1; } @@ -9052,7 +9505,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { optional = definition.optional, mode = definition.mode, // @, =, or & lastValue, - parentGet, parentSet, compare; + parentGet, parentSet, compare, removeWatch; switch (mode) { @@ -9066,10 +9519,15 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } }); attrs.$$observers[attrName].$$scope = scope; - if (isString(attrs[attrName])) { + lastValue = attrs[attrName]; + if (isString(lastValue)) { // If the attribute has been provided then we trigger an interpolation to ensure // the value is there for use in the link fn - destination[scopeName] = $interpolate(attrs[attrName])(scope); + destination[scopeName] = $interpolate(lastValue)(scope); + } else if (isBoolean(lastValue)) { + // If the attributes is one of the BOOLEAN_ATTR then Angular will have converted + // the value to boolean rather than a string, so we special case this situation + destination[scopeName] = lastValue; } break; @@ -9090,8 +9548,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { // reset the change, or we will throw this exception on every $digest lastValue = destination[scopeName] = parentGet(scope); throw $compileMinErr('nonassign', - "Expression '{0}' used with directive '{1}' is non-assignable!", - attrs[attrName], directive.name); + "Expression '{0}' in attribute '{1}' used with directive '{2}' is non-assignable!", + attrs[attrName], attrName, directive.name); }; lastValue = destination[scopeName] = parentGet(scope); var parentValueWatch = function parentValueWatch(parentValue) { @@ -9108,7 +9566,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { return lastValue = parentValue; }; parentValueWatch.$stateful = true; - var removeWatch; if (definition.collection) { removeWatch = scope.$watchCollection(attrs[attrName], parentValueWatch); } else { @@ -9117,6 +9574,24 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { removeWatchCollection.push(removeWatch); break; + case '<': + if (!hasOwnProperty.call(attrs, attrName)) { + if (optional) break; + attrs[attrName] = void 0; + } + if (optional && !attrs[attrName]) break; + + parentGet = $parse(attrs[attrName]); + + destination[scopeName] = parentGet(scope); + + removeWatch = scope.$watch(parentGet, function parentValueWatchAction(newParentValue) { + destination[scopeName] = newParentValue; + }, parentGet.literal); + + removeWatchCollection.push(removeWatch); + break; + case '&': // Don't assign Object.prototype method to scope parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop; @@ -10036,7 +10511,7 @@ function $HttpProvider() { * * ``` * module.run(function($http) { - * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w' + * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w'; * }); * ``` * @@ -10264,13 +10739,13 @@ function $HttpProvider() { * * ### Cross Site Request Forgery (XSRF) Protection * - * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is a technique by which - * an unauthorized site can gain your user's private data. Angular provides a mechanism - * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie - * (by default, `XSRF-TOKEN`) and sets it as an HTTP header (`X-XSRF-TOKEN`). Since only - * JavaScript that runs on your domain could read the cookie, your server can be assured that - * the XHR came from JavaScript running on your domain. The header will not be set for - * cross-domain requests. + * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is an attack technique by + * which the attacker can trick an authenticated user into unknowingly executing actions on your + * website. Angular provides a mechanism to counter XSRF. When performing XHR requests, the + * $http service reads a token from a cookie (by default, `XSRF-TOKEN`) and sets it as an HTTP + * header (`X-XSRF-TOKEN`). Since only JavaScript that runs on your domain could read the + * cookie, your server can be assured that the XHR came from JavaScript running on your domain. + * The header will not be set for cross-domain requests. * * To take advantage of this, your server needs to set a token in a JavaScript readable session * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the @@ -10429,7 +10904,7 @@ function $HttpProvider() { */ function $http(requestConfig) { - if (!angular.isObject(requestConfig)) { + if (!isObject(requestConfig)) { throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig); } @@ -10549,7 +11024,7 @@ function $HttpProvider() { defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]); - // using for-in instead of forEach to avoid unecessary iteration after header has been found + // using for-in instead of forEach to avoid unnecessary iteration after header has been found defaultHeadersIteration: for (defHeaderName in defHeaders) { lowercaseDefHeaderName = lowercase(defHeaderName); @@ -11048,6 +11523,14 @@ $interpolateMinErr.interr = function(text, err) { * * Used for configuring the interpolation markup. Defaults to `{{` and `}}`. * + *
+ * This feature is sometimes used to mix different markup languages, e.g. to wrap an Angular + * template within a Python Jinja template (or any other template language). Mixing templating + * languages is **very dangerous**. The embedding template language will not safely escape Angular + * expressions, so any user-controlled values in the template will cause Cross Site Scripting (XSS) + * security bugs! + *
+ * * @example @@ -11148,6 +11631,15 @@ function $InterpolateProvider() { return value; } + //TODO: this is the same as the constantWatchDelegate in parse.js + function constantWatchDelegate(scope, listener, objectEquality, constantInterp) { + var unwatch; + return unwatch = scope.$watch(function constantInterpolateWatch(scope) { + unwatch(); + return constantInterp(scope); + }, listener, objectEquality); + } + /** * @ngdoc service * @name $interpolate @@ -11243,6 +11735,19 @@ function $InterpolateProvider() { * - `context`: evaluation context for all expressions embedded in the interpolated text */ function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) { + // Provide a quick exit and simplified result function for text with no interpolation + if (!text.length || text.indexOf(startSymbol) === -1) { + var constantInterp; + if (!mustHaveExpression) { + var unescapedText = unescapeText(text); + constantInterp = valueFn(unescapedText); + constantInterp.exp = text; + constantInterp.expressions = []; + constantInterp.$$watchDelegate = constantWatchDelegate; + } + return constantInterp; + } + allOrNothing = !!allOrNothing; var startIndex, endIndex, @@ -11379,8 +11884,8 @@ function $InterpolateProvider() { } function $IntervalProvider() { - this.$get = ['$rootScope', '$window', '$q', '$$q', - function($rootScope, $window, $q, $$q) { + this.$get = ['$rootScope', '$window', '$q', '$$q', '$browser', + function($rootScope, $window, $q, $$q, $browser) { var intervals = {}; @@ -11521,11 +12026,12 @@ function $IntervalProvider() { count = isDefined(count) ? count : 0; - promise.then(null, null, (!hasParams) ? fn : function() { - fn.apply(null, args); - }); - promise.$$intervalId = setInterval(function tick() { + if (skipApply) { + $browser.defer(callback); + } else { + $rootScope.$evalAsync(callback); + } deferred.notify(iteration++); if (count > 0 && iteration >= count) { @@ -11541,6 +12047,14 @@ function $IntervalProvider() { intervals[promise.$$intervalId] = deferred; return promise; + + function callback() { + if (!hasParams) { + fn(iteration); + } else { + fn.apply(null, args); + } + } } @@ -12780,23 +13294,22 @@ function ensureSafeMemberName(name, fullExpression) { return name; } -function getStringValue(name, fullExpression) { - // From the JavaScript docs: +function getStringValue(name) { // Property names must be strings. This means that non-string objects cannot be used // as keys in an object. Any non-string object, including a number, is typecasted // into a string via the toString method. + // -- MDN, https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Property_accessors#Property_names // - // So, to ensure that we are checking the same `name` that JavaScript would use, - // we cast it to a string, if possible. - // Doing `name + ''` can cause a repl error if the result to `toString` is not a string, - // this is, this will handle objects that misbehave. - name = name + ''; - if (!isString(name)) { - throw $parseMinErr('iseccst', - 'Cannot convert object to primitive value! ' - + 'Expression: {0}', fullExpression); - } - return name; + // So, to ensure that we are checking the same `name` that JavaScript would use, we cast it + // to a string. It's not always possible. If `name` is an object and its `toString` method is + // 'broken' (doesn't return a string, isn't a function, etc.), an error will be thrown: + // + // TypeError: Cannot convert object to primitive value + // + // For performance reasons, we don't catch this error here and allow it to propagate up the call + // stack. Note that you'll get the same error in JavaScript if you try to access a property using + // such a 'broken' object as a key. + return name + ''; } function ensureSafeObject(obj, fullExpression) { @@ -13057,6 +13570,7 @@ AST.ArrayExpression = 'ArrayExpression'; AST.Property = 'Property'; AST.ObjectExpression = 'ObjectExpression'; AST.ThisExpression = 'ThisExpression'; +AST.LocalsExpression = 'LocalsExpression'; // Internal use only AST.NGValueParameter = 'NGValueParameter'; @@ -13357,7 +13871,8 @@ AST.prototype = { 'false': { type: AST.Literal, value: false }, 'null': { type: AST.Literal, value: null }, 'undefined': {type: AST.Literal, value: undefined }, - 'this': {type: AST.ThisExpression } + 'this': {type: AST.ThisExpression }, + '$locals': {type: AST.LocalsExpression } } }; @@ -13477,6 +13992,10 @@ function findConstantAndWatchExpressions(ast, $filter) { ast.constant = false; ast.toWatch = []; break; + case AST.LocalsExpression: + ast.constant = false; + ast.toWatch = []; + break; } } @@ -13720,6 +14239,9 @@ ASTCompiler.prototype = { intoId = intoId || this.nextId(); self.recurse(ast.object, left, undefined, function() { self.if_(self.notNull(left), function() { + if (create && create !== 1) { + self.addEnsureSafeAssignContext(left); + } if (ast.computed) { right = self.nextId(); self.recurse(ast.property, right); @@ -13843,6 +14365,10 @@ ASTCompiler.prototype = { this.assign(intoId, 's'); recursionFn('s'); break; + case AST.LocalsExpression: + this.assign(intoId, 'l'); + recursionFn('l'); + break; case AST.NGValueParameter: this.assign(intoId, 'v'); recursionFn('v'); @@ -13950,7 +14476,7 @@ ASTCompiler.prototype = { }, getStringValue: function(item) { - this.assign(item, 'getStringValue(' + item + ',text)'); + this.assign(item, 'getStringValue(' + item + ')'); }, ensureSafeAssignContext: function(item) { @@ -14170,6 +14696,10 @@ ASTInterpreter.prototype = { return function(scope) { return context ? {value: scope} : scope; }; + case AST.LocalsExpression: + return function(scope, locals) { + return context ? {value: locals} : locals; + }; case AST.NGValueParameter: return function(scope, locals, assign, inputs) { return context ? {value: assign} : assign; @@ -14334,8 +14864,11 @@ ASTInterpreter.prototype = { rhs = right(scope, locals, assign, inputs); rhs = getStringValue(rhs); ensureSafeMemberName(rhs, expression); - if (create && create !== 1 && lhs && !(lhs[rhs])) { - lhs[rhs] = {}; + if (create && create !== 1) { + ensureSafeAssignContext(lhs); + if (lhs && !(lhs[rhs])) { + lhs[rhs] = {}; + } } value = lhs[rhs]; ensureSafeObject(value, expression); @@ -14350,8 +14883,11 @@ ASTInterpreter.prototype = { nonComputedMember: function(left, right, expensiveChecks, context, create, expression) { return function(scope, locals, assign, inputs) { var lhs = left(scope, locals, assign, inputs); - if (create && create !== 1 && lhs && !(lhs[right])) { - lhs[right] = {}; + if (create && create !== 1) { + ensureSafeAssignContext(lhs); + if (lhs && !(lhs[right])) { + lhs[right] = {}; + } } var value = lhs != null ? lhs[right] : undefined; if (expensiveChecks || isPossiblyDangerousMemberName(right)) { @@ -14467,10 +15003,19 @@ function $ParseProvider() { csp: noUnsafeEval, expensiveChecks: true }; + var runningChecksEnabled = false; + + $parse.$$runningExpensiveChecks = function() { + return runningChecksEnabled; + }; + + return $parse; - return function $parse(exp, interceptorFn, expensiveChecks) { + function $parse(exp, interceptorFn, expensiveChecks) { var parsedExpression, oneTime, cacheKey; + expensiveChecks = expensiveChecks || runningChecksEnabled; + switch (typeof exp) { case 'string': exp = exp.trim(); @@ -14496,6 +15041,9 @@ function $ParseProvider() { } else if (parsedExpression.inputs) { parsedExpression.$$watchDelegate = inputsWatchDelegate; } + if (expensiveChecks) { + parsedExpression = expensiveChecksInterceptor(parsedExpression); + } cache[cacheKey] = parsedExpression; } return addInterceptor(parsedExpression, interceptorFn); @@ -14506,7 +15054,31 @@ function $ParseProvider() { default: return addInterceptor(noop, interceptorFn); } - }; + } + + function expensiveChecksInterceptor(fn) { + if (!fn) return fn; + expensiveCheckFn.$$watchDelegate = fn.$$watchDelegate; + expensiveCheckFn.assign = expensiveChecksInterceptor(fn.assign); + expensiveCheckFn.constant = fn.constant; + expensiveCheckFn.literal = fn.literal; + for (var i = 0; fn.inputs && i < fn.inputs.length; ++i) { + fn.inputs[i] = expensiveChecksInterceptor(fn.inputs[i]); + } + expensiveCheckFn.inputs = fn.inputs; + + return expensiveCheckFn; + + function expensiveCheckFn(scope, locals, assign, inputs) { + var expensiveCheckOldValue = runningChecksEnabled; + runningChecksEnabled = true; + try { + return fn(scope, locals, assign, inputs); + } finally { + runningChecksEnabled = expensiveCheckOldValue; + } + } + } function expressionInputDirtyCheck(newValue, oldValueOfValue) { @@ -14623,13 +15195,9 @@ function $ParseProvider() { function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) { var unwatch; return unwatch = scope.$watch(function constantWatch(scope) { - return parsedExpression(scope); - }, function constantListener(value, old, scope) { - if (isFunction(listener)) { - listener.apply(this, arguments); - } unwatch(); - }, objectEquality); + return parsedExpression(scope); + }, listener, objectEquality); } function addInterceptor(parsedExpression, interceptorFn) { @@ -14722,7 +15290,7 @@ function $ParseProvider() { * * Note: progress/notify callbacks are not currently supported via the ES6-style interface. * - * Note: unlike ES6 behaviour, an exception thrown in the constructor function will NOT implicitly reject the promise. + * Note: unlike ES6 behavior, an exception thrown in the constructor function will NOT implicitly reject the promise. * * However, the more traditional CommonJS-style usage is still available, and documented below. * @@ -14912,18 +15480,6 @@ function $$QProvider() { */ function qFactory(nextTick, exceptionHandler) { var $qMinErr = minErr('$q', TypeError); - function callOnce(self, resolveFn, rejectFn) { - var called = false; - function wrap(fn) { - return function(value) { - if (called) return; - called = true; - fn.call(self, value); - }; - } - - return [wrap(resolveFn), wrap(rejectFn)]; - } /** * @ngdoc method @@ -14936,7 +15492,12 @@ function qFactory(nextTick, exceptionHandler) { * @returns {Deferred} Returns a new instance of deferred. */ var defer = function() { - return new Deferred(); + var d = new Deferred(); + //Necessary to support unbound execution :/ + d.resolve = simpleBind(d, d.resolve); + d.reject = simpleBind(d, d.reject); + d.notify = simpleBind(d, d.notify); + return d; }; function Promise() { @@ -15009,10 +15570,6 @@ function qFactory(nextTick, exceptionHandler) { function Deferred() { this.promise = new Promise(); - //Necessary to support unbound execution :/ - this.resolve = simpleBind(this, this.resolve); - this.reject = simpleBind(this, this.reject); - this.notify = simpleBind(this, this.notify); } extend(Deferred.prototype, { @@ -15030,23 +15587,34 @@ function qFactory(nextTick, exceptionHandler) { }, $$resolve: function(val) { - var then, fns; - - fns = callOnce(this, this.$$resolve, this.$$reject); + var then; + var that = this; + var done = false; try { if ((isObject(val) || isFunction(val))) then = val && val.then; if (isFunction(then)) { this.promise.$$state.status = -1; - then.call(val, fns[0], fns[1], this.notify); + then.call(val, resolvePromise, rejectPromise, simpleBind(this, this.notify)); } else { this.promise.$$state.value = val; this.promise.$$state.status = 1; scheduleProcessQueue(this.promise.$$state); } } catch (e) { - fns[1](e); + rejectPromise(e); exceptionHandler(e); } + + function resolvePromise(val) { + if (done) return; + done = true; + that.$$resolve(val); + } + function rejectPromise(val) { + if (done) return; + done = true; + that.$$reject(val); + } }, reject: function(reason) { @@ -15235,11 +15803,6 @@ function qFactory(nextTick, exceptionHandler) { throw $qMinErr('norslvr', "Expected resolverFn, got '{0}'", resolver); } - if (!(this instanceof Q)) { - // More useful when $Q is the Promise itself. - return new Q(resolver); - } - var deferred = new Deferred(); function resolveFn(value) { @@ -15255,6 +15818,10 @@ function qFactory(nextTick, exceptionHandler) { return deferred.promise; }; + // Let's make the instanceof operator work for promises, so that + // `new $q(fn) instanceof $q` would evaluate to true. + $Q.prototype = Promise.prototype; + $Q.defer = defer; $Q.reject = reject; $Q.when = when; @@ -15388,8 +15955,8 @@ function $RootScopeProvider() { return ChildScope; } - this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser', - function($injector, $exceptionHandler, $parse, $browser) { + this.$get = ['$exceptionHandler', '$parse', '$browser', + function($exceptionHandler, $parse, $browser) { function destroyChildScope($event) { $event.currentScope.$$destroyed = true; @@ -15673,7 +16240,7 @@ function $RootScopeProvider() { * - `newVal` contains the current value of the `watchExpression` * - `oldVal` contains the previous value of the `watchExpression` * - `scope` refers to the current scope - * @param {boolean=} objectEquality Compare for object equality using {@link angular.equals} instead of + * @param {boolean=} [objectEquality=false] Compare for object equality using {@link angular.equals} instead of * comparing for reference equality. * @returns {function()} Returns a deregistration function for this listener. */ @@ -16038,7 +16605,7 @@ function $RootScopeProvider() { * */ $digest: function() { - var watch, value, last, + var watch, value, last, fn, get, watchers, length, dirty, ttl = TTL, @@ -16084,7 +16651,8 @@ function $RootScopeProvider() { // Most common watches are on primitives, in which case we can short // circuit it with === operator, only when === fails do we use .equals if (watch) { - if ((value = watch.get(current)) !== (last = watch.last) && + get = watch.get; + if ((value = get(current)) !== (last = watch.last) && !(watch.eq ? equals(value, last) : (typeof value === 'number' && typeof last === 'number' @@ -16092,7 +16660,8 @@ function $RootScopeProvider() { dirty = true; lastDirtyWatch = watch; watch.last = watch.eq ? copy(value, null) : value; - watch.fn(value, ((last === initWatchVal) ? value : last), current); + fn = watch.fn; + fn(value, ((last === initWatchVal) ? value : last), current); if (ttl < 5) { logIdx = 4 - ttl; if (!watchLog[logIdx]) watchLog[logIdx] = []; @@ -16292,7 +16861,7 @@ function $RootScopeProvider() { }); } - asyncQueue.push({scope: this, expression: expr, locals: locals}); + asyncQueue.push({scope: this, expression: $parse(expr), locals: locals}); }, $$postDigest: function(fn) { @@ -16384,6 +16953,7 @@ function $RootScopeProvider() { $applyAsync: function(expr) { var scope = this; expr && applyAsyncQueue.push($applyAsyncExpression); + expr = $parse(expr); scheduleApplyAsync(); function $applyAsyncExpression() { @@ -16887,13 +17457,15 @@ function $SceDelegateProvider() { * @kind function * * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value - * provided. This must be an array or null. A snapshot of this array is used so further - * changes to the array are ignored. + * provided. This must be an array or null. A snapshot of this array is used so further + * changes to the array are ignored. * - * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items - * allowed in this array. + * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items + * allowed in this array. * - * Note: **an empty whitelist array will block all URLs**! + *
+ * **Note:** an empty whitelist array will block all URLs! + *
* * @return {Array} the currently set whitelist array. * @@ -16916,17 +17488,17 @@ function $SceDelegateProvider() { * @kind function * * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value - * provided. This must be an array or null. A snapshot of this array is used so further - * changes to the array are ignored. + * provided. This must be an array or null. A snapshot of this array is used so further + * changes to the array are ignored. * - * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items - * allowed in this array. + * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items + * allowed in this array. * - * The typical usage for the blacklist is to **block - * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as - * these would otherwise be trusted but actually return content from the redirected domain. + * The typical usage for the blacklist is to **block + * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as + * these would otherwise be trusted but actually return content from the redirected domain. * - * Finally, **the blacklist overrides the whitelist** and has the final say. + * Finally, **the blacklist overrides the whitelist** and has the final say. * * @return {Array} the currently set blacklist array. * @@ -17085,6 +17657,11 @@ function $SceDelegateProvider() { * returns the originally supplied value if the queried context type is a supertype of the * created type. If this condition isn't satisfied, throws an exception. * + *
+ * Disabling auto-escaping is extremely dangerous, it usually creates a Cross Site Scripting + * (XSS) vulnerability in your application. + *
+ * * @param {string} type The kind of context in which this value is to be used. * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs * `$sceDelegate.trustAs`} call. @@ -17892,26 +18469,63 @@ function $SnifferProvider() { var $compileMinErr = minErr('$compile'); /** - * @ngdoc service - * @name $templateRequest - * + * @ngdoc provider + * @name $templateRequestProvider * @description - * The `$templateRequest` service runs security checks then downloads the provided template using - * `$http` and, upon success, stores the contents inside of `$templateCache`. If the HTTP request - * fails or the response data of the HTTP request is empty, a `$compile` error will be thrown (the - * exception can be thwarted by setting the 2nd parameter of the function to true). Note that the - * contents of `$templateCache` are trusted, so the call to `$sce.getTrustedUrl(tpl)` is omitted - * when `tpl` is of type string and `$templateCache` has the matching entry. - * - * @param {string|TrustedResourceUrl} tpl The HTTP request template URL - * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty + * Used to configure the options passed to the {@link $http} service when making a template request. * - * @return {Promise} a promise for the HTTP response data of the given URL. - * - * @property {number} totalPendingRequests total amount of pending template requests being downloaded. + * For example, it can be used for specifying the "Accept" header that is sent to the server, when + * requesting a template. */ function $TemplateRequestProvider() { + + var httpOptions; + + /** + * @ngdoc method + * @name $templateRequestProvider#httpOptions + * @description + * The options to be passed to the {@link $http} service when making the request. + * You can use this to override options such as the "Accept" header for template requests. + * + * The {@link $templateRequest} will set the `cache` and the `transformResponse` properties of the + * options if not overridden here. + * + * @param {string=} value new value for the {@link $http} options. + * @returns {string|self} Returns the {@link $http} options when used as getter and self if used as setter. + */ + this.httpOptions = function(val) { + if (val) { + httpOptions = val; + return this; + } + return httpOptions; + }; + + /** + * @ngdoc service + * @name $templateRequest + * + * @description + * The `$templateRequest` service runs security checks then downloads the provided template using + * `$http` and, upon success, stores the contents inside of `$templateCache`. If the HTTP request + * fails or the response data of the HTTP request is empty, a `$compile` error will be thrown (the + * exception can be thwarted by setting the 2nd parameter of the function to true). Note that the + * contents of `$templateCache` are trusted, so the call to `$sce.getTrustedUrl(tpl)` is omitted + * when `tpl` is of type string and `$templateCache` has the matching entry. + * + * If you want to pass custom options to the `$http` service, such as setting the Accept header you + * can configure this via {@link $templateRequestProvider#httpOptions}. + * + * @param {string|TrustedResourceUrl} tpl The HTTP request template URL + * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty + * + * @return {Promise} a promise for the HTTP response data of the given URL. + * + * @property {number} totalPendingRequests total amount of pending template requests being downloaded. + */ this.$get = ['$templateCache', '$http', '$q', '$sce', function($templateCache, $http, $q, $sce) { + function handleRequestFn(tpl, ignoreRequestError) { handleRequestFn.totalPendingRequests++; @@ -17934,12 +18548,10 @@ function $TemplateRequestProvider() { transformResponse = null; } - var httpOptions = { - cache: $templateCache, - transformResponse: transformResponse - }; - - return $http.get(tpl, httpOptions) + return $http.get(tpl, extend({ + cache: $templateCache, + transformResponse: transformResponse + }, httpOptions)) ['finally'](function() { handleRequestFn.totalPendingRequests--; }) @@ -19394,13 +20006,13 @@ function dateFilter($locale) { var dateTimezoneOffset = date.getTimezoneOffset(); if (timezone) { - dateTimezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset()); + dateTimezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset); date = convertTimezoneToLocal(date, timezone, true); } forEach(parts, function(value) { fn = DATE_FORMATS[value]; text += fn ? fn(date, $locale.DATETIME_FORMATS, dateTimezoneOffset) - : value.replace(/(^'|'$)/g, '').replace(/''/g, "'"); + : value === "''" ? "'" : value.replace(/(^'|'$)/g, '').replace(/''/g, "'"); }); return text; @@ -19604,8 +20216,9 @@ function limitToFilter() { * Orders a specified `array` by the `expression` predicate. It is ordered alphabetically * for strings and numerically for numbers. Note: if you notice numbers are not being sorted * as expected, make sure they are actually being saved as numbers and not strings. + * Array-like values (e.g. NodeLists, jQuery objects, TypedArrays, Strings, etc) are also supported. * - * @param {Array} array The array to sort. + * @param {Array} array The array (or array-like object) to sort. * @param {function(*)|string|Array.<(function(*)|string)>=} expression A predicate to be * used by the comparator to determine the order of elements. * @@ -19792,7 +20405,10 @@ orderByFilter.$inject = ['$parse']; function orderByFilter($parse) { return function(array, sortPredicate, reverseOrder) { - if (!(isArrayLike(array))) return array; + if (array == null) return array; + if (!isArrayLike(array)) { + throw minErr('orderBy')('notarray', 'Expected array but received: {0}', array); + } if (!isArray(sortPredicate)) { sortPredicate = [sortPredicate]; } if (sortPredicate.length === 0) { sortPredicate = ['+']; } @@ -20502,7 +21118,7 @@ function FormController(element, attrs, $scope, $animate, $interpolate) { * * However, if the method is used programmatically, for example by adding dynamically created controls, * or controls that have been previously removed without destroying their corresponding DOM element, - * it's the developers responsiblity to make sure the current state propagates to the parent form. + * it's the developers responsibility to make sure the current state propagates to the parent form. * * For example, if an input control is added that is already `$dirty` and has `$error` properties, * calling `$setDirty()` and `$validate()` afterwards will propagate the state to the parent form. @@ -20979,8 +21595,8 @@ var inputType = { * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string * that contains the regular expression body that will be converted to a regular expression * as in the ngPattern directive. - * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match - * a RegExp found by evaluating the Angular expression given in the attribute value. + * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} + * does not match a RegExp found by evaluating the Angular expression given in the attribute value. * If the expression evaluates to a RegExp object, then this is used directly. * If the expression evaluates to a string, then it will be converted to a RegExp * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to @@ -21267,7 +21883,7 @@ var inputType = { * * @description * Input with time validation and transformation. In browsers that do not yet support - * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 + * the HTML5 time input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`. * @@ -21614,8 +22230,8 @@ var inputType = { * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string * that contains the regular expression body that will be converted to a regular expression * as in the ngPattern directive. - * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match - * a RegExp found by evaluating the Angular expression given in the attribute value. + * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} + * does not match a RegExp found by evaluating the Angular expression given in the attribute value. * If the expression evaluates to a RegExp object, then this is used directly. * If the expression evaluates to a string, then it will be converted to a RegExp * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to @@ -21712,8 +22328,8 @@ var inputType = { * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string * that contains the regular expression body that will be converted to a regular expression * as in the ngPattern directive. - * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match - * a RegExp found by evaluating the Angular expression given in the attribute value. + * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} + * does not match a RegExp found by evaluating the Angular expression given in the attribute value. * If the expression evaluates to a RegExp object, then this is used directly. * If the expression evaluates to a string, then it will be converted to a RegExp * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to @@ -21811,8 +22427,8 @@ var inputType = { * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string * that contains the regular expression body that will be converted to a regular expression * as in the ngPattern directive. - * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match - * a RegExp found by evaluating the Angular expression given in the attribute value. + * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} + * does not match a RegExp found by evaluating the Angular expression given in the attribute value. * If the expression evaluates to a RegExp object, then this is used directly. * If the expression evaluates to a string, then it will be converted to a RegExp * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to @@ -22272,11 +22888,7 @@ function badInputChecker(scope, element, attr, ctrl) { if (nativeValidation) { ctrl.$parsers.push(function(value) { var validity = element.prop(VALIDITY_STATE_PROPERTY) || {}; - // Detect bug in FF35 for input[email] (https://bugzilla.mozilla.org/show_bug.cgi?id=1064430): - // - also sets validity.badInput (should only be validity.typeMismatch). - // - see http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#e-mail-state-(type=email) - // - can ignore this case as we can still read out the erroneous email... - return validity.badInput && !validity.typeMismatch ? undefined : value; + return validity.badInput || validity.typeMismatch ? undefined : value; }); } } @@ -22448,8 +23060,8 @@ function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filt * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any * length. - * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match - * a RegExp found by evaluating the Angular expression given in the attribute value. + * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} + * does not match a RegExp found by evaluating the Angular expression given in the attribute value. * If the expression evaluates to a RegExp object, then this is used directly. * If the expression evaluates to a string, then it will be converted to a RegExp * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to @@ -22487,8 +23099,8 @@ function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filt * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any * length. - * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match - * a RegExp found by evaluating the Angular expression given in the attribute value. + * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} + * value does not match a RegExp found by evaluating the Angular expression given in the attribute value. * If the expression evaluates to a RegExp object, then this is used directly. * If the expression evaluates to a string, then it will be converted to a RegExp * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to @@ -23714,7 +24326,7 @@ var ngControllerDirective = [function() { * * * no-inline-style: this stops Angular from injecting CSS styles into the DOM * - * * no-unsafe-eval: this stops Angular from optimising $parse with unsafe eval of strings + * * no-unsafe-eval: this stops Angular from optimizing $parse with unsafe eval of strings * * You can use these values in the following combinations: * @@ -23731,7 +24343,7 @@ var ngControllerDirective = [function() { * inline styles. E.g. ``. * * * Specifying only `no-inline-style` tells Angular that we must not inject styles, but that we can - * run eval - no automcatic check for unsafe eval will occur. E.g. `` + * run eval - no automatic check for unsafe eval will occur. E.g. `` * * * Specifying both `no-unsafe-eval` and `no-inline-style` tells Angular that we must not inject * styles nor use eval, which is the same as an empty: ng-csp. @@ -24763,7 +25375,7 @@ var ngIncludeFillContentDirective = ['$compile', priority: -400, require: 'ngInclude', link: function(scope, $element, $attr, ctrl) { - if (/SVG/.test($element[0].toString())) { + if (toString.call($element[0]).match(/SVG/)) { // WebKit: https://bugs.webkit.org/show_bug.cgi?id=135698 --- SVG elements do not // support innerHTML, so detect this here and try to generate the contents // specially. @@ -24992,7 +25604,9 @@ var VALID_CLASS = 'ng-valid', DIRTY_CLASS = 'ng-dirty', UNTOUCHED_CLASS = 'ng-untouched', TOUCHED_CLASS = 'ng-touched', - PENDING_CLASS = 'ng-pending'; + PENDING_CLASS = 'ng-pending', + EMPTY_CLASS = 'ng-empty', + NOT_EMPTY_CLASS = 'ng-not-empty'; var ngModelMinErr = minErr('ngModel'); @@ -25296,6 +25910,17 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ return isUndefined(value) || value === '' || value === null || value !== value; }; + this.$$updateEmptyClasses = function(value) { + if (ctrl.$isEmpty(value)) { + $animate.removeClass($element, NOT_EMPTY_CLASS); + $animate.addClass($element, EMPTY_CLASS); + } else { + $animate.removeClass($element, EMPTY_CLASS); + $animate.addClass($element, NOT_EMPTY_CLASS); + } + }; + + var currentValidationRunId = 0; /** @@ -25659,6 +26284,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ if (ctrl.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !ctrl.$$hasNativeValidators)) { return; } + ctrl.$$updateEmptyClasses(viewValue); ctrl.$$lastCommittedViewValue = viewValue; // change to dirty @@ -25757,7 +26383,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ * However, custom controls might also pass objects to this method. In this case, we should make * a copy of the object before passing it to `$setViewValue`. This is because `ngModel` does not * perform a deep watch of objects, it only looks for a change of identity. If you only change - * the property of the object then ngModel will not realise that the object has changed and + * the property of the object then ngModel will not realize that the object has changed and * will not invoke the `$parsers` and `$validators` pipelines. For this reason, you should * not change properties of the copy once it has been passed to `$setViewValue`. * Otherwise you may cause the model value on the scope to change incorrectly. @@ -25841,6 +26467,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ viewValue = formatters[idx](viewValue); } if (ctrl.$viewValue !== viewValue) { + ctrl.$$updateEmptyClasses(viewValue); ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue; ctrl.$render(); @@ -25871,7 +26498,8 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ * require. * - Providing validation behavior (i.e. required, number, email, url). * - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors). - * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, `ng-untouched`) including animations. + * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, + * `ng-untouched`, `ng-empty`, `ng-not-empty`) including animations. * - Registering the control with its parent {@link ng.directive:form form}. * * Note: `ngModel` will try to bind to the property given by evaluating the expression on the @@ -25899,6 +26527,22 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ * - {@link ng.directive:select select} * - {@link ng.directive:textarea textarea} * + * # Complex Models (objects or collections) + * + * By default, `ngModel` watches the model by reference, not value. This is important to know when + * binding inputs to models that are objects (e.g. `Date`) or collections (e.g. arrays). If only properties of the + * object or collection change, `ngModel` will not be notified and so the input will not be re-rendered. + * + * The model must be assigned an entirely new object or collection before a re-rendering will occur. + * + * Some directives have options that will cause them to use a custom `$watchCollection` on the model expression + * - for example, `ngOptions` will do so when a `track by` clause is included in the comprehension expression or + * if the select is given the `multiple` attribute. + * + * The `$watchCollection()` method only does a shallow comparison, meaning that changing properties deeper than the + * first level of the object (or only changing the properties of an item in the collection if it's an array) will still + * not trigger a re-rendering of the model. + * * # CSS classes * The following CSS classes are added and removed on the associated input/select/textarea element * depending on the validity of the model. @@ -25912,13 +26556,16 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ * - `ng-touched`: the control has been blurred * - `ng-untouched`: the control hasn't been blurred * - `ng-pending`: any `$asyncValidators` are unfulfilled + * - `ng-empty`: the view does not contain a value or the value is deemed "empty", as defined + * by the {@link ngModel.NgModelController#$isEmpty} method + * - `ng-not-empty`: the view contains a non-empty value * * Keep in mind that ngAnimate can detect each of these classes when added and removed. * * ## Animation Hooks * * Animations within models are triggered when any of the associated CSS classes are added and removed - * on the input element which is attached to the model. These classes are: `.ng-pristine`, `.ng-dirty`, + * on the input element which is attached to the model. These classes include: `.ng-pristine`, `.ng-dirty`, * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself. * The animations that are triggered within ngModel are similar to how they work in ngClass and * animations can be hooked into using CSS transitions, keyframes as well as JS animations. @@ -26811,14 +27458,10 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) { var optionTemplate = document.createElement('option'), optGroupTemplate = document.createElement('optgroup'); - function ngOptionsPostLink(scope, selectElement, attr, ctrls) { - // if ngModel is not defined, we don't need to do anything - var ngModelCtrl = ctrls[1]; - if (!ngModelCtrl) return; - var selectCtrl = ctrls[0]; + var ngModelCtrl = ctrls[1]; var multiple = attr.multiple; // The emptyOption allows the application developer to provide their own custom "empty" @@ -27078,7 +27721,7 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) { var groupElement; var optionElement; - if (option.group) { + if (isDefined(option.group)) { // This option is to live in a group // See if we have already created this group @@ -27152,7 +27795,7 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) { return { restrict: 'A', terminal: true, - require: ['select', '?ngModel'], + require: ['select', 'ngModel'], link: { pre: function ngOptionsPreLink(scope, selectElement, attr, ctrls) { // Deactivate the SelectController.register method to prevent @@ -27380,7 +28023,7 @@ var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale, } // If both `count` and `lastCount` are NaN, we don't need to re-register a watch. - // In JS `NaN !== NaN`, so we have to exlicitly check. + // In JS `NaN !== NaN`, so we have to explicitly check. if ((count !== lastCount) && !(countIsNaN && isNumber(lastCount) && isNaN(lastCount))) { watchRemover(); var whenExpFn = whensExpFns[count]; @@ -27497,7 +28140,7 @@ var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale, * by the identifier instead of the whole object. Should you reload your data later, `ngRepeat` * will not have to rebuild the DOM elements for items it has already rendered, even if the * JavaScript objects in the collection have been substituted for new ones. For large collections, - * this signifincantly improves rendering performance. If you don't have a unique identifier, + * this significantly improves rendering performance. If you don't have a unique identifier, * `track by $index` can also provide a performance boost. * * ```html @@ -27574,6 +28217,8 @@ var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale, * * **.move** - when an adjacent item is filtered out causing a reorder or when the item contents are reordered * + * See the example below for defining CSS animations with ngRepeat. + * * @element ANY * @scope * @priority 1000 @@ -27626,22 +28271,11 @@ var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale, * For example: `item in items | filter : x | orderBy : order | limitTo : limit as results` . * * @example - * This example initializes the scope to a list of names and - * then uses `ngRepeat` to display every person: - + * This example uses `ngRepeat` to display a list of people. A filter is used to restrict the displayed + * results by name. New (entering) and removed (leaving) items are animated. + -
+
I have {{friends.length}} friends. They are:
    @@ -27654,6 +28288,22 @@ var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale,
+ + angular.module('ngRepeat', ['ngAnimate']).controller('repeatController', function($scope) { + $scope.friends = [ + {name:'John', age:25, gender:'boy'}, + {name:'Jessie', age:30, gender:'girl'}, + {name:'Johanna', age:28, gender:'girl'}, + {name:'Joy', age:15, gender:'girl'}, + {name:'Mary', age:28, gender:'girl'}, + {name:'Peter', age:95, gender:'boy'}, + {name:'Sebastian', age:50, gender:'boy'}, + {name:'Erika', age:27, gender:'girl'}, + {name:'Patrick', age:40, gender:'boy'}, + {name:'Samantha', age:60, gender:'girl'} + ]; + }); + .example-animate-container { background:white; @@ -27664,7 +28314,7 @@ var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale, } .animate-repeat { - line-height:40px; + line-height:30px; list-style:none; box-sizing:border-box; } @@ -27686,7 +28336,7 @@ var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale, .animate-repeat.ng-move.ng-move-active, .animate-repeat.ng-enter.ng-enter-active { opacity:1; - max-height:40px; + max-height:30px; } @@ -28543,67 +29193,186 @@ var ngSwitchDefaultDirective = ngDirective({ * @description * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion. * - * Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted. + * You can specify that you want to insert a named transclusion slot, instead of the default slot, by providing the slot name + * as the value of the `ng-transclude` or `ng-transclude-slot` attribute. + * + * If the transcluded content is not empty (i.e. contains one or more DOM nodes, including whitespace text nodes), any existing + * content of this element will be removed before the transcluded content is inserted. + * If the transcluded content is empty, the existing content is left intact. This lets you provide fallback content in the case + * that no transcluded content is provided. * * @element ANY * + * @param {string} ngTransclude|ngTranscludeSlot the name of the slot to insert at this point. If this is not provided, is empty + * or its value is the same as the name of the attribute then the default slot is used. + * * @example - - - -
-
-
- {{text}} -
-
- - it('should have transcluded', function() { - var titleElement = element(by.model('title')); - titleElement.clear(); - titleElement.sendKeys('TITLE'); - var textElement = element(by.model('text')); - textElement.clear(); - textElement.sendKeys('TEXT'); - expect(element(by.binding('title')).getText()).toEqual('TITLE'); - expect(element(by.binding('text')).getText()).toEqual('TEXT'); - }); - -
+ * ### Basic transclusion + * This example demonstrates basic transclusion of content into a component directive. + * + * + * + *
+ *
+ *
+ * {{text}} + *
+ *
+ * + * it('should have transcluded', function() { + * var titleElement = element(by.model('title')); + * titleElement.clear(); + * titleElement.sendKeys('TITLE'); + * var textElement = element(by.model('text')); + * textElement.clear(); + * textElement.sendKeys('TEXT'); + * expect(element(by.binding('title')).getText()).toEqual('TITLE'); + * expect(element(by.binding('text')).getText()).toEqual('TEXT'); + * }); + * + *
+ * + * @example + * ### Transclude fallback content + * This example shows how to use `NgTransclude` with fallback content, that + * is displayed if no transcluded content is provided. + * + * + * + * + * + * + * + * + * Button2 + * + * + * + * it('should have different transclude element content', function() { + * expect(element(by.id('fallback')).getText()).toBe('Button1'); + * expect(element(by.id('modified')).getText()).toBe('Button2'); + * }); + * + * * + * @example + * ### Multi-slot transclusion + * This example demonstrates using multi-slot transclusion in a component directive. + * + * + * + *
+ *
+ *
+ * + *
{{title}} + *

{{text}}

+ * + *
+ * + * + * angular.module('multiSlotTranscludeExample', []) + * .directive('pane', function(){ + * return { + * restrict: 'E', + * transclude: { + * 'title': '?paneTitle', + * 'body': 'paneBody', + * 'footer': '?paneFooter' + * }, + * template: '
' + + * '
Fallback Title
' + + * '
' + + * '' + + * '
' + * }; + * }) + * .controller('ExampleController', ['$scope', function($scope) { + * $scope.title = 'Lorem Ipsum'; + * $scope.link = "https://google.com"; + * $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; + * }]); + *
+ * + * it('should have transcluded the title and the body', function() { + * var titleElement = element(by.model('title')); + * titleElement.clear(); + * titleElement.sendKeys('TITLE'); + * var textElement = element(by.model('text')); + * textElement.clear(); + * textElement.sendKeys('TEXT'); + * expect(element(by.css('.title')).getText()).toEqual('TITLE'); + * expect(element(by.binding('text')).getText()).toEqual('TEXT'); + * expect(element(by.css('.footer')).getText()).toEqual('Fallback Footer'); + * }); + * + * */ +var ngTranscludeMinErr = minErr('ngTransclude'); var ngTranscludeDirective = ngDirective({ restrict: 'EAC', link: function($scope, $element, $attrs, controller, $transclude) { + + if ($attrs.ngTransclude === $attrs.$attr.ngTransclude) { + // If the attribute is of the form: `ng-transclude="ng-transclude"` + // then treat it like the default + $attrs.ngTransclude = ''; + } + + function ngTranscludeCloneAttachFn(clone) { + if (clone.length) { + $element.empty(); + $element.append(clone); + } + } + if (!$transclude) { - throw minErr('ngTransclude')('orphan', + throw ngTranscludeMinErr('orphan', 'Illegal use of ngTransclude directive in the template! ' + 'No parent directive that requires a transclusion found. ' + 'Element: {0}', startingTag($element)); } - $transclude(function(clone) { - $element.empty(); - $element.append(clone); - }); + // If there is no slot name defined or the slot name is not optional + // then transclude the slot + var slotName = $attrs.ngTransclude || $attrs.ngTranscludeSlot; + $transclude(ngTranscludeCloneAttachFn, null, slotName); } }); @@ -28735,6 +29504,9 @@ var SelectController = // Tell the select control that an option, with the given value, has been added self.addOption = function(value, element) { + // Skip comment nodes, as they only pollute the `optionsMap` + if (element[0].nodeType === NODE_TYPE_COMMENT) return; + assertNotHasOwnProperty(value, '"option value"'); if (value === '') { self.emptyOption = element; @@ -28819,7 +29591,7 @@ var SelectController = * *
* Note that the value of a `select` directive used without `ngOptions` is always a string. - * When the model needs to be bound to a non-string value, you must either explictly convert it + * When the model needs to be bound to a non-string value, you must either explicitly convert it * using a directive (see example below) or use `ngOptions` to specify the set of options. * This is because an option element can only be bound to string values at present. *
@@ -29107,7 +29879,6 @@ var optionDirective = ['$interpolate', function($interpolate) { restrict: 'E', priority: 100, compile: function(element, attr) { - if (isDefined(attr.value)) { // If the value attribute is defined, check if it contains an interpolation var interpolateValueFn = $interpolate(attr.value, true); @@ -29121,7 +29892,6 @@ var optionDirective = ['$interpolate', function($interpolate) { } return function(scope, element, attr) { - // This is an optimization over using ^^ since we don't want to have to search // all the way to the root of the DOM for every single option element var selectCtrlName = '$selectController', @@ -29158,7 +29928,7 @@ var styleDirective = valueFn({ * for more info. * * The validator will set the `required` error key to true if the `required` attribute is set and - * calling {@link ngModel.NgModelController#$isEmpty `NgModelController.$isEmpty` with the + * calling {@link ngModel.NgModelController#$isEmpty `NgModelController.$isEmpty`} with the * {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} returns `true`. For example, the * `$isEmpty()` implementation for `input[text]` checks the length of the `$viewValue`. When developing * custom controls, `$isEmpty()` can be overwritten to account for a $viewValue that is not string-based. @@ -29644,6 +30414,7 @@ $provide.value("$locale", { ] }, "id": "en-us", + "localeID": "en_US", "pluralCat": function(n, opt_precision) { var i = n | 0; var vf = getVF(n, opt_precision); if (i == 1 && vf.v == 0) { return PLURAL_CATEGORY.ONE; } return PLURAL_CATEGORY.OTHER;} }); }]); diff --git a/dist/docs/js/docs-setup.js b/dist/docs/js/docs-setup.js index 63b43f058..99da027b3 100644 --- a/dist/docs/js/docs-setup.js +++ b/dist/docs/js/docs-setup.js @@ -208,7 +208,7 @@ NG_DOCS={ "type": "directive", "moduleName": "patternfly.toolbars", "shortDescription": "Directive for standard toolbar. Includes filtering and view selection capabilities", - "keywords": "$scope action actionfn actions actions-label actionsconfig actionstext address age allitems alpha angular api apple appliedfilters applyfilters april august bedrock birth birthmonth boulevard button call capabilities checkdisabled cincinatti class col-md-12 comparefn comparisons compvalue config configuration controller currentfield currently currentview december deps dinosaur directive disable disabled display displayed east elm events-label example-container exampletoolbar false february fields filter filterchange filterconfig filtering filters filterstext filtertype filtervalues flintstone foreach frank fred function getcardview getlistview green grouped html icon iconclass includes invoke isascending isdisabled isseparator item item1 item2 items january john js judy july june length list list-group-item-heading list-group-item-text list-style-type list-view-container list-view-pf-additional-info list-view-pf-additional-info-item list-view-pf-description listconfig listview livingston localecompare main march match matches matchesfilter matchesfilters menu module month monthvals moreactions ng-controller ng-if norfolk november null numeric october ohio onfilterchange onsortchange onviewselect optional options parseint pat patternfly pennsylvania perform performaction pf-list-view pf-toolbar pfsimplefilter pfsort pfviewutils pittsburgh placehodler placeholder primary primaryactions pulldown push resultscount return row rows script second secondary select selected selection selectionmatchprop selector separator september set settings smith sort sortchange sortconfig sorttype standard street style text thing thomas title toolbar toolbarconfig toolbars tooltip true type unavailable undefined unique var view viewctrl viewid views viewsconfig viewselected viewtype virginia washingstone york" + "keywords": "$scope action actionfn actions actions-label actionsconfig actionstext address age allitems alpha angular api apple appliedfilters applyfilters april august bedrock birth birthmonth boulevard button call capabilities card-view-container cardview checkdisabled cincinatti class col-md-12 comparefn comparisons compvalue config configuration controller currentfield currently currentview december deps dinosaur directive disable disabled display displayed east elm events-label example-container exampletoolbar false february fields filter filterchange filterconfig filtering filters filterstext filtertype filtervalues flintstone foreach frank fred function getcardview getlistview green grouped html icon iconclass includes invoke isascending isdisabled isseparator item item1 item2 items january john js judy july june length list list-group-item-heading list-group-item-text list-style-type list-view-container list-view-pf-additional-info list-view-pf-additional-info-item list-view-pf-description listconfig listview livingston localecompare main march match matches matchesfilter matchesfilters menu module month monthvals moreactions ng-controller ng-if norfolk november null numeric october ohio onfilterchange onsortchange onviewselect optional options parseint pat patternfly pennsylvania perform performaction pf-card-view pf-list-view pf-toolbar pfsimplefilter pfsort pfviewutils pittsburgh placehodler placeholder primary primaryactions pulldown push resultscount return row rows script second secondary select selected selection selectionmatchprop selector separator september set settings smith sort sortchange sortconfig sorttype standard street style text thing thomas title toolbar toolbarconfig toolbars tooltip true type unavailable undefined unique var view viewctrl viewid views viewsconfig viewselected viewtype virginia vm washingstone york" }, { "section": "api", diff --git a/dist/docs/partials/api/patternfly.autofocus.pfFocused.html b/dist/docs/partials/api/patternfly.autofocus.pfFocused.html index 8c6f65e4c..f573916f9 100644 --- a/dist/docs/partials/api/patternfly.autofocus.pfFocused.html +++ b/dist/docs/partials/api/patternfly.autofocus.pfFocused.html @@ -1,4 +1,4 @@ - Improve this doc View source

pfFocused + Improve this doc View source

pfFocused
directive in module patternfly
@@ -15,10 +15,10 @@

Parameters

Example

Source

-
+
-

-
 

Demo

-
+
diff --git a/dist/docs/partials/api/patternfly.card.directive.pfAggregateStatusCard.html b/dist/docs/partials/api/patternfly.card.directive.pfAggregateStatusCard.html index a865c9a4b..c50917044 100644 --- a/dist/docs/partials/api/patternfly.card.directive.pfAggregateStatusCard.html +++ b/dist/docs/partials/api/patternfly.card.directive.pfAggregateStatusCard.html @@ -1,4 +1,4 @@ - Improve this doc View source

pfAggregateStatusCard + Improve this doc View source

pfAggregateStatusCard
directive in module patternfly.card
@@ -45,10 +45,10 @@

Parameters

Example

Source

-
+
-

-
 
-

-
 

Demo

-
+
diff --git a/dist/docs/partials/api/patternfly.card.directive.pfCard.html b/dist/docs/partials/api/patternfly.card.directive.pfCard.html index 0aeff0747..0b5944493 100644 --- a/dist/docs/partials/api/patternfly.card.directive.pfCard.html +++ b/dist/docs/partials/api/patternfly.card.directive.pfCard.html @@ -1,4 +1,4 @@ - Improve this doc View source

pfCard + Improve this doc View source

pfCard
directive in module patternfly.card
@@ -41,10 +41,10 @@

Parameters

Example

Source

-
+
-

-
 
-

-
 

Demo

-
+
diff --git a/dist/docs/partials/api/patternfly.charts.directive.pfC3Chart.html b/dist/docs/partials/api/patternfly.charts.directive.pfC3Chart.html index 494a2abd1..e5a7175d2 100644 --- a/dist/docs/partials/api/patternfly.charts.directive.pfC3Chart.html +++ b/dist/docs/partials/api/patternfly.charts.directive.pfC3Chart.html @@ -1,4 +1,4 @@ - Improve this doc View source

pfC3Chart + Improve this doc View source

pfC3Chart
directive in module patternfly.charts
@@ -19,10 +19,10 @@

Parameters

Example

Source

-
+
-

-
 
-

-
 

Demo

-
+
diff --git a/dist/docs/partials/api/patternfly.charts.directive.pfDonutPctChart.html b/dist/docs/partials/api/patternfly.charts.directive.pfDonutPctChart.html index 1a9b26ed8..37693eef7 100644 --- a/dist/docs/partials/api/patternfly.charts.directive.pfDonutPctChart.html +++ b/dist/docs/partials/api/patternfly.charts.directive.pfDonutPctChart.html @@ -1,4 +1,4 @@ - Improve this doc View source

pfDonutPctChart + Improve this doc View source

pfDonutPctChart
directive in module patternfly.charts
@@ -46,10 +46,10 @@

Parameters

Example

Source

-
+
-

-
 
-

-
 

Demo

-
+
diff --git a/dist/docs/partials/api/patternfly.charts.directive.pfHeatMap.html b/dist/docs/partials/api/patternfly.charts.directive.pfHeatMap.html index 6c7a696eb..5d3752b5d 100644 --- a/dist/docs/partials/api/patternfly.charts.directive.pfHeatMap.html +++ b/dist/docs/partials/api/patternfly.charts.directive.pfHeatMap.html @@ -1,4 +1,4 @@ - Improve this doc View source

pfHeatMap + Improve this doc View source

pfHeatMap
directive in module patternfly.charts
@@ -39,10 +39,10 @@

Parameters

Example

Source

-
+
-

-
 
-

-
 

Demo

-
+
diff --git a/dist/docs/partials/api/patternfly.charts.directive.pfSparklineChart.html b/dist/docs/partials/api/patternfly.charts.directive.pfSparklineChart.html index bafa34279..c41fac8cc 100644 --- a/dist/docs/partials/api/patternfly.charts.directive.pfSparklineChart.html +++ b/dist/docs/partials/api/patternfly.charts.directive.pfSparklineChart.html @@ -1,4 +1,4 @@ - Improve this doc View source

pfSparklineChart + Improve this doc View source

pfSparklineChart
directive in module patternfly.charts
@@ -47,10 +47,10 @@

Parameters

Example

Source

-
+
-

-
 
-

-
 

Demo

-
+
diff --git a/dist/docs/partials/api/patternfly.charts.directive.pfTrendsChart.html b/dist/docs/partials/api/patternfly.charts.directive.pfTrendsChart.html index cae4eea94..f8d917809 100644 --- a/dist/docs/partials/api/patternfly.charts.directive.pfTrendsChart.html +++ b/dist/docs/partials/api/patternfly.charts.directive.pfTrendsChart.html @@ -1,4 +1,4 @@ - Improve this doc View source

pfTrendsChart + Improve this doc View source

pfTrendsChart
directive in module patternfly.charts
@@ -40,10 +40,10 @@

Parameters

Example

Source

-
+
-

-
 
-

-
 

Demo

-
+
diff --git a/dist/docs/partials/api/patternfly.charts.directive.pfUtilizationBarChart.html b/dist/docs/partials/api/patternfly.charts.directive.pfUtilizationBarChart.html index dd55a37d6..b3917873c 100644 --- a/dist/docs/partials/api/patternfly.charts.directive.pfUtilizationBarChart.html +++ b/dist/docs/partials/api/patternfly.charts.directive.pfUtilizationBarChart.html @@ -1,4 +1,4 @@ - Improve this doc View source

pfUtilizationBarChart + Improve this doc View source

pfUtilizationBarChart
directive in module patternfly.charts
@@ -49,10 +49,10 @@

Parameters

Example

Source

-
+
-

-
 
-

-
 

Demo

-
+
diff --git a/dist/docs/partials/api/patternfly.charts.directive.pfUtilizationTrendChart.html b/dist/docs/partials/api/patternfly.charts.directive.pfUtilizationTrendChart.html index ff2994eb4..e48030f5c 100644 --- a/dist/docs/partials/api/patternfly.charts.directive.pfUtilizationTrendChart.html +++ b/dist/docs/partials/api/patternfly.charts.directive.pfUtilizationTrendChart.html @@ -1,4 +1,4 @@ - Improve this doc View source

pfUtilizationTrendChart + Improve this doc View source

pfUtilizationTrendChart
directive in module patternfly.charts
@@ -49,10 +49,10 @@

Parameters

Example

Source

-
+
-

-
 
-

-
 

Demo

-
+
diff --git a/dist/docs/partials/api/patternfly.filters.directive.pfFilter.html b/dist/docs/partials/api/patternfly.filters.directive.pfFilter.html index f7128fe4b..1b6eb2ad3 100644 --- a/dist/docs/partials/api/patternfly.filters.directive.pfFilter.html +++ b/dist/docs/partials/api/patternfly.filters.directive.pfFilter.html @@ -1,4 +1,4 @@ - Improve this doc View source

pfFilter + Improve this doc View source

pfFilter
directive in module patternfly.filters
@@ -28,10 +28,10 @@

Parameters

Example

Source

-
+
-

-
 
-

-
 

Demo

-
+
diff --git a/dist/docs/partials/api/patternfly.filters.directive.pfFilterFields.html b/dist/docs/partials/api/patternfly.filters.directive.pfFilterFields.html index 72f90658a..9c53b47f8 100644 --- a/dist/docs/partials/api/patternfly.filters.directive.pfFilterFields.html +++ b/dist/docs/partials/api/patternfly.filters.directive.pfFilterFields.html @@ -1,4 +1,4 @@ - Improve this doc View source

pfFilterFields + Improve this doc View source

pfFilterFields
directive in module patternfly.filters
diff --git a/dist/docs/partials/api/patternfly.filters.directive.pfFilterResults.html b/dist/docs/partials/api/patternfly.filters.directive.pfFilterResults.html index b6cf4348f..c526f7691 100644 --- a/dist/docs/partials/api/patternfly.filters.directive.pfFilterResults.html +++ b/dist/docs/partials/api/patternfly.filters.directive.pfFilterResults.html @@ -1,4 +1,4 @@ - Improve this doc View source

pfFilterResults + Improve this doc View source

pfFilterResults
directive in module patternfly.filters
diff --git a/dist/docs/partials/api/patternfly.form.directive.pfDatepicker.html b/dist/docs/partials/api/patternfly.form.directive.pfDatepicker.html index 07ac35333..f54f3d220 100644 --- a/dist/docs/partials/api/patternfly.form.directive.pfDatepicker.html +++ b/dist/docs/partials/api/patternfly.form.directive.pfDatepicker.html @@ -1,4 +1,4 @@ - Improve this doc View source

pfDatepicker + Improve this doc View source

pfDatepicker
directive in module patternfly.form
@@ -17,10 +17,10 @@

Parameters

Example

Source

-
+
-

-
 
-

-
 

Demo

-
+
diff --git a/dist/docs/partials/api/patternfly.form.directive.pfFormButtons.html b/dist/docs/partials/api/patternfly.form.directive.pfFormButtons.html index 1ffa092cf..1b2f56441 100644 --- a/dist/docs/partials/api/patternfly.form.directive.pfFormButtons.html +++ b/dist/docs/partials/api/patternfly.form.directive.pfFormButtons.html @@ -1,4 +1,4 @@ - Improve this doc View source

pfFormButtons + Improve this doc View source

pfFormButtons
directive in module patternfly.form
@@ -23,10 +23,10 @@

Parameters

Example

Source

-
+
-

-
 
-

-
 

Demo

-
+
diff --git a/dist/docs/partials/api/patternfly.form.directive.pfFormGroup.html b/dist/docs/partials/api/patternfly.form.directive.pfFormGroup.html index 913febe48..eb688aa57 100644 --- a/dist/docs/partials/api/patternfly.form.directive.pfFormGroup.html +++ b/dist/docs/partials/api/patternfly.form.directive.pfFormGroup.html @@ -1,4 +1,4 @@ - Improve this doc View source

pfFormGroup + Improve this doc View source

pfFormGroup
directive in module patternfly.form
@@ -23,10 +23,10 @@

Parameters

Example

Source

-
+
-

-
 
-

-
 

Demo

-
+
diff --git a/dist/docs/partials/api/patternfly.form.directive.pfRemainingCharsCount.html b/dist/docs/partials/api/patternfly.form.directive.pfRemainingCharsCount.html index cb2b28e28..913e1a63b 100644 --- a/dist/docs/partials/api/patternfly.form.directive.pfRemainingCharsCount.html +++ b/dist/docs/partials/api/patternfly.form.directive.pfRemainingCharsCount.html @@ -1,4 +1,4 @@ - Improve this doc View source

pfRemainingCharsCount + Improve this doc View source

pfRemainingCharsCount
directive in module patternfly.form
@@ -40,10 +40,10 @@

Parameters

Example

Source

-
+
-

-
 
-

-
 

Demo

-
+
diff --git a/dist/docs/partials/api/patternfly.notification.Notification.html b/dist/docs/partials/api/patternfly.notification.Notification.html index 6d69d3655..ad61410b6 100644 --- a/dist/docs/partials/api/patternfly.notification.Notification.html +++ b/dist/docs/partials/api/patternfly.notification.Notification.html @@ -1,4 +1,4 @@ - Improve this doc View source

Notification + Improve this doc View source

Notification
service in module patternfly.notification
@@ -29,10 +29,10 @@

Dependencies

Example

Source

-
+
-

-
 
-

-
 

Demo

-
+
diff --git a/dist/docs/partials/api/patternfly.notification.directive.pfInlineNotification.html b/dist/docs/partials/api/patternfly.notification.directive.pfInlineNotification.html index a8e69edf3..0449b197b 100644 --- a/dist/docs/partials/api/patternfly.notification.directive.pfInlineNotification.html +++ b/dist/docs/partials/api/patternfly.notification.directive.pfInlineNotification.html @@ -1,4 +1,4 @@ - Improve this doc View source

pfInlineNotification + Improve this doc View source

pfInlineNotification
directive in module patternfly.notification
@@ -24,10 +24,10 @@

Parameters

Example

Source

-
+
-

-
 
-

-
 

Demo

-
+
diff --git a/dist/docs/partials/api/patternfly.notification.directive.pfNotificationList.html b/dist/docs/partials/api/patternfly.notification.directive.pfNotificationList.html index a14e1a900..326acb213 100644 --- a/dist/docs/partials/api/patternfly.notification.directive.pfNotificationList.html +++ b/dist/docs/partials/api/patternfly.notification.directive.pfNotificationList.html @@ -1,4 +1,4 @@ - Improve this doc View source

pfNotificationList + Improve this doc View source

pfNotificationList
directive in module patternfly.notification
@@ -12,10 +12,10 @@

Usage

Example

Source

-
+
-

-
 
-

-
 

Demo

-
+
diff --git a/dist/docs/partials/api/patternfly.select.pfSelect.html b/dist/docs/partials/api/patternfly.select.pfSelect.html index a7e289137..5adbac58e 100644 --- a/dist/docs/partials/api/patternfly.select.pfSelect.html +++ b/dist/docs/partials/api/patternfly.select.pfSelect.html @@ -1,4 +1,4 @@ - Improve this doc View source

pfSelect + Improve this doc View source

pfSelect
directive in module patternfly
@@ -19,10 +19,10 @@

Parameters

Example

Source

-
+
-

-
 
-

-
 

Demo

-
+
diff --git a/dist/docs/partials/api/patternfly.sort.directive.pfSort.html b/dist/docs/partials/api/patternfly.sort.directive.pfSort.html index 813f0e05f..813df5a06 100644 --- a/dist/docs/partials/api/patternfly.sort.directive.pfSort.html +++ b/dist/docs/partials/api/patternfly.sort.directive.pfSort.html @@ -1,4 +1,4 @@ - Improve this doc View source

pfSort + Improve this doc View source

pfSort
directive in module patternfly.sort
@@ -26,10 +26,10 @@

Parameters

Example

Source

-
+
-

-
 
-

-
 

Demo

-
+
diff --git a/dist/docs/partials/api/patternfly.toolbars.directive.pfToolbar.html b/dist/docs/partials/api/patternfly.toolbars.directive.pfToolbar.html index 34ec680b3..ec6991779 100644 --- a/dist/docs/partials/api/patternfly.toolbars.directive.pfToolbar.html +++ b/dist/docs/partials/api/patternfly.toolbars.directive.pfToolbar.html @@ -1,4 +1,4 @@ - Improve this doc View source

pfToolbar + Improve this doc View source

pfToolbar
directive in module patternfly.toolbars
@@ -50,10 +50,10 @@

Parameters

Example

Source

-
+
-

-
 
-

-
 

Demo

-
+
diff --git a/dist/docs/partials/api/patternfly.utils.directive.pfTransclude.html b/dist/docs/partials/api/patternfly.utils.directive.pfTransclude.html index 7d4561c13..6e1a4f769 100644 --- a/dist/docs/partials/api/patternfly.utils.directive.pfTransclude.html +++ b/dist/docs/partials/api/patternfly.utils.directive.pfTransclude.html @@ -1,4 +1,4 @@ - Improve this doc View source

pfTransclude + Improve this doc View source

pfTransclude
directive in module patternfly.utils
@@ -20,10 +20,10 @@

Parameters

Example

Source

-
+
-

-
 
-

-
 

Demo

-
+
diff --git a/dist/docs/partials/api/patternfly.validation.pfValidation.html b/dist/docs/partials/api/patternfly.validation.pfValidation.html index 3aa408ace..0b07f4571 100644 --- a/dist/docs/partials/api/patternfly.validation.pfValidation.html +++ b/dist/docs/partials/api/patternfly.validation.pfValidation.html @@ -1,4 +1,4 @@ - Improve this doc View source

pfValidation + Improve this doc View source

pfValidation
directive in module patternfly
@@ -18,10 +18,10 @@

Parameters

Example

Source

-
+
-

-
 
-

-
 

Demo

-
+
diff --git a/dist/docs/partials/api/patternfly.views.directive.pfCardView.html b/dist/docs/partials/api/patternfly.views.directive.pfCardView.html index 4e94fa543..64727f6a8 100644 --- a/dist/docs/partials/api/patternfly.views.directive.pfCardView.html +++ b/dist/docs/partials/api/patternfly.views.directive.pfCardView.html @@ -1,4 +1,4 @@ - Improve this doc View source

pfCardView + Improve this doc View source

pfCardView
directive in module patternfly.views
@@ -31,10 +31,10 @@

Parameters

Example

Source

-
+
-

-
 
-

-
 

Demo

-
+
diff --git a/dist/docs/partials/api/patternfly.views.directive.pfListView.html b/dist/docs/partials/api/patternfly.views.directive.pfListView.html index 8bccc7dbe..d2010f592 100644 --- a/dist/docs/partials/api/patternfly.views.directive.pfListView.html +++ b/dist/docs/partials/api/patternfly.views.directive.pfListView.html @@ -1,4 +1,4 @@ - Improve this doc View source

pfListView + Improve this doc View source

pfListView
directive in module patternfly.views
@@ -50,10 +50,10 @@

Parameters

Example

Source

-
+
-

-
 
-

-
 

Demo

-
+
diff --git a/dist/styles/angular-patternfly.css b/dist/styles/angular-patternfly.css index a245f4bb5..19b1e3e8d 100644 --- a/dist/styles/angular-patternfly.css +++ b/dist/styles/angular-patternfly.css @@ -149,7 +149,7 @@ .card-view-pf .card.active, .card-view-pf .card.active:hover, .card-view-pf .card.active:focus { - border: solid 3px #39a5dc; + border: solid 3px #00a8e1; } .card-view-pf .card:hover, @@ -349,7 +349,7 @@ position: relative; } -.-heatmap--pf-container-pf .loading { +.heatmap-pf-container-pf .loading { position: absolute; top: 100px; right: 50%; diff --git a/dist/styles/angular-patternfly.min.css b/dist/styles/angular-patternfly.min.css index 3fc031feb..99babbb90 100644 --- a/dist/styles/angular-patternfly.min.css +++ b/dist/styles/angular-patternfly.min.css @@ -1 +1 @@ -.card-pf-aggregate-status-alt .card-pf-body{padding-bottom:20px}.card-pf-aggregate-status-alt .card-pf-title{line-height:22px;margin:20px 0 10px;font-weight:300}.card-pf-aggregate-status-alt .card-pf-aggregate-status-count{font-size:24px}.card-pf-aggregate-status-alt .card-pf-aggregate-status-title{display:block;font-size:12px}.card-pf-aggregate-status-alt .card-pf-aggregate-status-notifications .card-pf-aggregate-status-notification{border-left:none}.card-pf-aggregate-status-alt .card-pf-aggregate-status-notifications .fa,.card-pf-aggregate-status-alt .card-pf-aggregate-status-notifications .pficon{position:relative;top:-1px}.card-pf-heading-no-bottom{margin:0 -20px 0;padding:0 20px}.empty-chart-content{text-align:center}.empty-chart-content .pficon{font-size:24px}.empty-chart-content span{vertical-align:middle;width:100%}.utilization-trend-chart-pf .donut-chart-pf{width:100%;float:left;padding-top:15px}.utilization-trend-chart-pf h3{font-weight:400}.utilization-trend-chart-pf .current-values{border-bottom:1px solid #d1d1d1;float:left;padding:0 5px 10px 0;width:100%}.utilization-trend-chart-pf .available-count{margin:3px 0;padding-left:0;padding-right:5px}.utilization-trend-chart-pf .available-text{font-size:12px;font-weight:400;line-height:14px;margin:2px 0;padding:0 5px}.utilization-trend-chart-pf .radial-chart{float:left;padding-top:10px;width:100%}.utilization-trend-chart-pf .sparkline-chart{float:left;margin-left:-5px;margin-right:-5px;width:100%}.utilization-trend-chart-pf .legend-text{color:inherit;display:block;font-size:12px;font-weight:400;margin-left:0}.utilization-trend-chart-pf.data-unavailable-pf .current-values{color:transparent}.card-view-pf{overflow:auto;padding-top:20px;padding-left:2px}.card-view-pf .card{-webkit-box-shadow:0 1px 1px rgba(0,0,0,.175);background:#fff;border-top:2px solid transparent;box-shadow:0 1px 1px rgba(0,0,0,.175);display:block;float:left;height:290px;margin-right:20px;margin-bottom:20px;padding:10px;position:relative;text-align:center;width:260px}.card-view-pf .card .card-check-box{left:10px;position:absolute;top:8px;width:20px;z-index:3;visibility:hidden}.card-view-pf .card-content{height:100%;margin:2px 0 10px;overflow:auto;width:100%}.card-view-pf .card-title{color:#1186C1;font-weight:500;font-size:16px;line-height:1.1;margin-top:0}.card-view-pf .card.active,.card-view-pf .card.active:focus,.card-view-pf .card.active:hover{border:solid 3px #39a5dc}.card-view-pf .card:focus,.card-view-pf .card:hover{-webkit-box-shadow:0 3px 10px -2px rgba(0,0,0,.24);-moz-box-shadow:0 3px 10px -2px rgba(0,0,0,.24);box-shadow:0 3px 10px -2px rgba(0,0,0,.24);border:1px solid #d1d1d1}.card-view-pf .card.active .pficon,.card-view-pf .card.active:focus .pficon,.card-view-pf .card.active:hover .pficon{color:#fff}.card-view-pf .card.active .card-check-box,.card-view-pf .card:hover .card-check-box{visibility:visible}.card-view-pf .card.disabled,.card.disabled:focus,.card.disabled:hover{border:1px solid #eee;color:#999;cursor:not-allowed;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.trend-card-large-pf .trend-header-pf{font-size:16px;font-weight:400;display:block;margin-left:10px}.trend-card-small-pf .trend-header-pf{font-size:12px;font-weight:400;display:block;margin-left:10px}.trend-card-large-pf .trend-title-big-pf{font-size:26px;font-weight:300;margin-left:10px}.trend-card-small-pf .trend-title-big-pf{font-size:17px;font-weight:400;margin-left:10px}.trend-card-large-pf .trend-title-small-pf{font-size:12px;font-weight:400}.trend-card-small-pf .trend-title-small-pf{font-size:10px;font-weight:400}.trend-flat-details{display:table;margin-top:5px}@media (min-width:768px){.trend-flat-details{margin-top:25px}}.trend-flat-details-cell{display:table-cell;vertical-align:bottom;min-width:70px}.trend-header-compact-pf{display:block;font-size:12px;font-weight:400}.trend-title-compact-big-pf{font-size:36px;font-weight:300;line-height:1}.trend-title-compact-small-pf{font-size:12px;font-weight:400}.trend-title-flat-big-pf{font-size:26px;font-weight:300;line-height:1;margin-right:15px}.trend-label-flat-pf{font-size:12px;font-weight:400;line-height:1}.trend-label-flat-strong-pf{display:block;font-size:12px;font-weight:700;line-height:1}.trend-footer-pf{font-size:10px;font-weight:400;color:#333;margin-left:10px}.data-unavailable-pf .trend-footer-pf,.data-unavailable-pf [class*=trend-label-],.data-unavailable-pf [class*=trend-title-]{color:transparent}.filter-pf a{cursor:pointer}.filter-pf.filter-fields .form-group{padding-left:0;width:275px}.filter-select .btn-default{font-size:12px;font-style:italic;font-weight:400;background-color:#fff;background-image:none;color:#999}.sort-pf .form-group .btn-link{color:#222;font-size:16px;line-height:1;padding:4px 0;margin-left:10px}.sort-pf .form-group .btn-link:hover{color:#0099d3}.input-group .input-group-btn .dropdown-menu>.selected>a{background-color:#0099d3!important;border-color:#0076b7!important;color:#fff!important}.toolbar-pf-actions .dropdown-menu a,.toolbar-pf-actions .toolbar-pf-view-selector a{cursor:pointer}.toolbar-pf-actions .dropdown-kebab-pf{float:right}.utilization-bar-chart-pf .progress-bar{-webkit-transition:width .75s ease-in-out;-moz-transition:width .75s ease-in-out;-o-transition:width .75s ease-in-out;transition:width .75s ease-in-out}.utilization-bar-chart-pf .progress-bar.animate{width:0!important}.heatmap-pf-container{position:relative}.-heatmap--pf-container-pf .loading{position:absolute;top:100px;right:50%;z-index:10}.heatmap-pf-container .heatmap-container{margin-left:-1px}.heatmap-pf-svg{width:100%;height:100%}.heatmap-pf-legend-container{list-style-type:none;margin-top:5px;padding:0;overflow:auto}.heatmap-pf-legend-items{float:left}.legend-pf-color-box{width:11px;height:11px;margin-left:5px;margin-right:5px;display:inline-block}.legend-pf-color-box:first-of-type{margin-left:0}.legend-pf-text{font-size:11px;font-weight:400;line-height:11px;padding-right:5px}.toolbar-pf-actions.no-filter-results{margin-bottom:10px} \ No newline at end of file +.card-pf-aggregate-status-alt .card-pf-body{padding-bottom:20px}.card-pf-aggregate-status-alt .card-pf-title{line-height:22px;margin:20px 0 10px;font-weight:300}.card-pf-aggregate-status-alt .card-pf-aggregate-status-count{font-size:24px}.card-pf-aggregate-status-alt .card-pf-aggregate-status-title{display:block;font-size:12px}.card-pf-aggregate-status-alt .card-pf-aggregate-status-notifications .card-pf-aggregate-status-notification{border-left:none}.card-pf-aggregate-status-alt .card-pf-aggregate-status-notifications .fa,.card-pf-aggregate-status-alt .card-pf-aggregate-status-notifications .pficon{position:relative;top:-1px}.card-pf-heading-no-bottom{margin:0 -20px 0;padding:0 20px}.empty-chart-content{text-align:center}.empty-chart-content .pficon{font-size:24px}.empty-chart-content span{vertical-align:middle;width:100%}.utilization-trend-chart-pf .donut-chart-pf{width:100%;float:left;padding-top:15px}.utilization-trend-chart-pf h3{font-weight:400}.utilization-trend-chart-pf .current-values{border-bottom:1px solid #d1d1d1;float:left;padding:0 5px 10px 0;width:100%}.utilization-trend-chart-pf .available-count{margin:3px 0;padding-left:0;padding-right:5px}.utilization-trend-chart-pf .available-text{font-size:12px;font-weight:400;line-height:14px;margin:2px 0;padding:0 5px}.utilization-trend-chart-pf .radial-chart{float:left;padding-top:10px;width:100%}.utilization-trend-chart-pf .sparkline-chart{float:left;margin-left:-5px;margin-right:-5px;width:100%}.utilization-trend-chart-pf .legend-text{color:inherit;display:block;font-size:12px;font-weight:400;margin-left:0}.utilization-trend-chart-pf.data-unavailable-pf .current-values{color:transparent}.card-view-pf{overflow:auto;padding-top:20px;padding-left:2px}.card-view-pf .card{-webkit-box-shadow:0 1px 1px rgba(0,0,0,.175);background:#fff;border-top:2px solid transparent;box-shadow:0 1px 1px rgba(0,0,0,.175);display:block;float:left;height:290px;margin-right:20px;margin-bottom:20px;padding:10px;position:relative;text-align:center;width:260px}.card-view-pf .card .card-check-box{left:10px;position:absolute;top:8px;width:20px;z-index:3;visibility:hidden}.card-view-pf .card-content{height:100%;margin:2px 0 10px;overflow:auto;width:100%}.card-view-pf .card-title{color:#1186C1;font-weight:500;font-size:16px;line-height:1.1;margin-top:0}.card-view-pf .card.active,.card-view-pf .card.active:focus,.card-view-pf .card.active:hover{border:solid 3px #00a8e1}.card-view-pf .card:focus,.card-view-pf .card:hover{-webkit-box-shadow:0 3px 10px -2px rgba(0,0,0,.24);-moz-box-shadow:0 3px 10px -2px rgba(0,0,0,.24);box-shadow:0 3px 10px -2px rgba(0,0,0,.24);border:1px solid #d1d1d1}.card-view-pf .card.active .pficon,.card-view-pf .card.active:focus .pficon,.card-view-pf .card.active:hover .pficon{color:#fff}.card-view-pf .card.active .card-check-box,.card-view-pf .card:hover .card-check-box{visibility:visible}.card-view-pf .card.disabled,.card.disabled:focus,.card.disabled:hover{border:1px solid #eee;color:#999;cursor:not-allowed;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.trend-card-large-pf .trend-header-pf{font-size:16px;font-weight:400;display:block;margin-left:10px}.trend-card-small-pf .trend-header-pf{font-size:12px;font-weight:400;display:block;margin-left:10px}.trend-card-large-pf .trend-title-big-pf{font-size:26px;font-weight:300;margin-left:10px}.trend-card-small-pf .trend-title-big-pf{font-size:17px;font-weight:400;margin-left:10px}.trend-card-large-pf .trend-title-small-pf{font-size:12px;font-weight:400}.trend-card-small-pf .trend-title-small-pf{font-size:10px;font-weight:400}.trend-flat-details{display:table;margin-top:5px}@media (min-width:768px){.trend-flat-details{margin-top:25px}}.trend-flat-details-cell{display:table-cell;vertical-align:bottom;min-width:70px}.trend-header-compact-pf{display:block;font-size:12px;font-weight:400}.trend-title-compact-big-pf{font-size:36px;font-weight:300;line-height:1}.trend-title-compact-small-pf{font-size:12px;font-weight:400}.trend-title-flat-big-pf{font-size:26px;font-weight:300;line-height:1;margin-right:15px}.trend-label-flat-pf{font-size:12px;font-weight:400;line-height:1}.trend-label-flat-strong-pf{display:block;font-size:12px;font-weight:700;line-height:1}.trend-footer-pf{font-size:10px;font-weight:400;color:#333;margin-left:10px}.data-unavailable-pf .trend-footer-pf,.data-unavailable-pf [class*=trend-label-],.data-unavailable-pf [class*=trend-title-]{color:transparent}.filter-pf a{cursor:pointer}.filter-pf.filter-fields .form-group{padding-left:0;width:275px}.filter-select .btn-default{font-size:12px;font-style:italic;font-weight:400;background-color:#fff;background-image:none;color:#999}.sort-pf .form-group .btn-link{color:#222;font-size:16px;line-height:1;padding:4px 0;margin-left:10px}.sort-pf .form-group .btn-link:hover{color:#0099d3}.input-group .input-group-btn .dropdown-menu>.selected>a{background-color:#0099d3!important;border-color:#0076b7!important;color:#fff!important}.toolbar-pf-actions .dropdown-menu a,.toolbar-pf-actions .toolbar-pf-view-selector a{cursor:pointer}.toolbar-pf-actions .dropdown-kebab-pf{float:right}.utilization-bar-chart-pf .progress-bar{-webkit-transition:width .75s ease-in-out;-moz-transition:width .75s ease-in-out;-o-transition:width .75s ease-in-out;transition:width .75s ease-in-out}.utilization-bar-chart-pf .progress-bar.animate{width:0!important}.heatmap-pf-container{position:relative}.heatmap-pf-container-pf .loading{position:absolute;top:100px;right:50%;z-index:10}.heatmap-pf-container .heatmap-container{margin-left:-1px}.heatmap-pf-svg{width:100%;height:100%}.heatmap-pf-legend-container{list-style-type:none;margin-top:5px;padding:0;overflow:auto}.heatmap-pf-legend-items{float:left}.legend-pf-color-box{width:11px;height:11px;margin-left:5px;margin-right:5px;display:inline-block}.legend-pf-color-box:first-of-type{margin-left:0}.legend-pf-text{font-size:11px;font-weight:400;line-height:11px;padding-right:5px}.toolbar-pf-actions.no-filter-results{margin-bottom:10px} \ No newline at end of file diff --git a/src/toolbars/toolbar-directive.js b/src/toolbars/toolbar-directive.js index ea54b6c09..5a4d3d22d 100644 --- a/src/toolbars/toolbar-directive.js +++ b/src/toolbars/toolbar-directive.js @@ -44,7 +44,7 @@ * * * @example - +
@@ -74,6 +74,19 @@
+
+
+
+ {{item.name}} +
+
+ {{item.address}} +
+
+ {{item.birthMonth}} +
+
+
diff --git a/styles/angular-patternfly.css b/styles/angular-patternfly.css index a245f4bb5..19b1e3e8d 100644 --- a/styles/angular-patternfly.css +++ b/styles/angular-patternfly.css @@ -149,7 +149,7 @@ .card-view-pf .card.active, .card-view-pf .card.active:hover, .card-view-pf .card.active:focus { - border: solid 3px #39a5dc; + border: solid 3px #00a8e1; } .card-view-pf .card:hover, @@ -349,7 +349,7 @@ position: relative; } -.-heatmap--pf-container-pf .loading { +.heatmap-pf-container-pf .loading { position: absolute; top: 100px; right: 50%;